generated from coulomb/repo-seed
Implement SAND-WP-0006: SaaS payments, routing, and ext.saas-stub
Add credits store, metering on create/destroy, extension routing resolver, metered SaaS stub extension, burst/saas profiles, credits CLI, docs, and tests.
This commit is contained in:
@@ -92,11 +92,16 @@ with patch.object(SSHConfig, "run", return_value=(0, "ready")):
|
||||
handle = ext.provision(profile, {"vm": "haskell-build"}, "localhost")
|
||||
```
|
||||
|
||||
## Metered extensions (SAND-WP-0006)
|
||||
|
||||
Implement `estimate_cost` and `meter_actual` on `SandboxExtension`. Register with
|
||||
`pricing_model: metered`. See `docs/payments.md` and `ext.saas-stub`.
|
||||
|
||||
## Deferred
|
||||
|
||||
| Feature | Workplan |
|
||||
|---------|----------|
|
||||
| Packer build orchestration from `create` | Future WP |
|
||||
| SaaS adapters + `estimate_cost` | SAND-WP-0006 |
|
||||
| Multi-backend routing engine | SAND-WP-0006 |
|
||||
| E2B / Modal / Daytona cloud adapters | Post SAND-WP-0006 |
|
||||
| fin-hub billing export | Future |
|
||||
| Snapshot / restore hooks | SAND-WP-0007 |
|
||||
@@ -153,7 +153,7 @@ When multiple extensions satisfy a profile capability:
|
||||
| `lowest-latency` | Closest region / host wins |
|
||||
| `explicit` | Profile names a single `extension`; no auto-routing |
|
||||
|
||||
v0 resolves `profile.extension` directly — routing engine deferred to SAND-WP-0006.
|
||||
Routing engine v0: `sandboxer.routing.resolver` — see `docs/routing.md`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ Deferred: Packer orchestration from API, `make remote-build` shim.
|
||||
|
||||
| Item | Workplan |
|
||||
|------|----------|
|
||||
| SaaS extensions + payments | SAND-WP-0006 |
|
||||
| ~~SaaS extensions + payments v0~~ | SAND-WP-0006 — stub + routing + credits |
|
||||
| E2B / Modal real adapters | Post SAND-WP-0006 |
|
||||
| Snapshot / restore | SAND-WP-0007 |
|
||||
| TTL enforcement + scheduled reap | TBD |
|
||||
45
docs/payments.md
Normal file
45
docs/payments.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Payments and metering
|
||||
|
||||
Version 0.1 — SAND-WP-0006. OpenRouter-style credits for metered SaaS extensions.
|
||||
|
||||
## Credits store
|
||||
|
||||
Path: `~/.local/share/sandboxer/credits.json` (override via test injection).
|
||||
|
||||
| Env | Default |
|
||||
|-----|---------|
|
||||
| `SANDBOXER_DEFAULT_CREDITS` | `10.0` USD on first use |
|
||||
|
||||
```bash
|
||||
sandboxer credits show
|
||||
sandboxer credits add 25.00
|
||||
```
|
||||
|
||||
## Metered lifecycle
|
||||
|
||||
1. **create** — `estimate_cost` on metered extension; block if balance insufficient
|
||||
2. **ready** — `SandboxStatus.meter.estimate_usd` recorded
|
||||
3. **destroy** — `meter_actual` (or prorated estimate); debit credits; State Hub note event
|
||||
|
||||
Self-hosted extensions (`pricing_model: self-hosted`) skip credits.
|
||||
|
||||
## Extension contract
|
||||
|
||||
Metered extensions implement on `SandboxExtension`:
|
||||
|
||||
```python
|
||||
def estimate_cost(self, profile, inputs, *, duration_s=3600) -> MeterQuote | None
|
||||
def meter_actual(self, handle, *, duration_s: float) -> float | None
|
||||
```
|
||||
|
||||
Reference: `ext.saas-stub` (no external API).
|
||||
|
||||
## BYOK
|
||||
|
||||
Provider API keys are resolved at provision boundary via `secret_refs` / OpenBao —
|
||||
not implemented in v0 stub. Set provider env vars per extension when adapters land.
|
||||
|
||||
## Billing export
|
||||
|
||||
sand-boxer meters sandbox consumption only. Domain billing authority (fin-hub) is a
|
||||
future export consumer of State Hub meter events — not owned here.
|
||||
44
docs/routing.md
Normal file
44
docs/routing.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Extension routing
|
||||
|
||||
Version 0.1 — SAND-WP-0006. Select backend when a profile lists multiple extensions.
|
||||
|
||||
## Profile route block
|
||||
|
||||
```yaml
|
||||
extension: ext.compose-ssh # default / explicit fallback
|
||||
route:
|
||||
strategy: prefer-self-hosted
|
||||
extensions:
|
||||
- ext.compose-ssh
|
||||
- ext.saas-stub
|
||||
max_cost_per_hour_usd: 1.0
|
||||
```
|
||||
|
||||
## Strategies
|
||||
|
||||
| Strategy | Behavior |
|
||||
|----------|----------|
|
||||
| `explicit` | Use `profile.extension` (default when no route) |
|
||||
| `prefer-self-hosted` | First self-hosted candidate with resolvable host; else SaaS |
|
||||
| `lowest-cost` | Self-hosted if available; else cheapest `estimate_cost` |
|
||||
| `lowest-latency` | Self-hosted if available; else last candidate (v0) |
|
||||
|
||||
## Testing SaaS fallback
|
||||
|
||||
```bash
|
||||
# Skip self-hosted when host unavailable:
|
||||
unset SANDBOXER_HOST
|
||||
SANDBOXER_FORCE_SAAS=1 sandboxer create --profile profile.burst-sandbox
|
||||
|
||||
# Or use metered-only profile:
|
||||
sandboxer create --profile profile.saas-stub
|
||||
```
|
||||
|
||||
## Reference profiles
|
||||
|
||||
| Profile | Route |
|
||||
|---------|-------|
|
||||
| `profile.burst-sandbox` | compose-ssh → saas-stub fallback |
|
||||
| `profile.saas-stub` | explicit `ext.saas-stub` |
|
||||
|
||||
Resolver: `sandboxer.routing.resolver.resolve_extension`.
|
||||
Reference in New Issue
Block a user