b7a07f981c
ROADMAP: remove the resolved Design (contract conformance) section — the three blockers shipped (admin SPA in :59, landing buy button on keysat.xyz) and the structural + token tiers were dropped during adjudication. Current state: live is now :59, blockers done; the Zaprite auto-charge silent-lapse bug is the top remaining payments item.
4.5 KiB
4.5 KiB
ROADMAP — Keysat
Longer-term backlog. Near-term state lives in AGENTS.md → Current state.
Payments & subscriptions
- Per-profile SMTP override (schema fields exist from the keysat-smtp-emails plan; needs the form + send path).
- Rail-preference editing UI — only matters when two providers on one profile both serve the same rail; settable today via
PUT /v1/admin/merchant-profiles/:id/rail-preferences/:rail. - Auto-charge silently lapses a subscription on a 200-with-failure response (money-path bug; elevated above the other parked payments items).
try_auto_charge_zapritereturnsOk(true)on any HTTP 2xx (subscriptions.rs:1403-1410), reading the orderstatusonly for a log line. If Zaprite returns 200 carrying aFAILED/DECLINED/EXPIREDorder status, the daemon firesauto_charge_initiatedand then waits for anorder.paidwebhook that never arrives — the subscription silently lapses, no error surfaced, the customer churns. Safe fix (no production data needed): treat any non-PAIDterminal order status as not-success and fall through to the manual-pay path — a conservative fail-safe, ~10 lines + a mock-provider test. (Found during the 2026-06-17 adjudication; it replaces the old "harden Zaprite failure-body shapes" item, which was already satisfied for non-2xx responses — those route correctly to WARN +auto_charge_failedaudit + webhook + manual-pay fallback.)
Agent compatibility & scoped API keys
- Agent-delegable payment-provider connect (approved, not urgent — see
plans/agent-payment-connect-scope.md). Add an à-la-cartepayment_providers:writescope (never bundled intomerchant-onboard), gated by a daemon-level sandbox-mode flag as the outer gate (production daemons reject scoped connect entirely) with a network gate inner defense (regtest/testnet/signet only, fail-closed to mainnet). BTCPay network is derived from an on-chain address prefix (noserver/infofield exists). - Onboarding doc-harness — Stage 2 (Path 2, regtest buyer-pays). Gated on slices 3–5 above.
Stage 1 (Path 1, no payments) shipped
completed-cleanthis session — harness atlicensing-service-startos/onboarding-harness/, record in itsSTAGE1-RESULT.md. Stage 2 reuses the harness but boots the fixture withKEYSAT_SANDBOX_MODEon, stands up a Dockerized BTCPay regtest stack (bitcoind regtest + NBXplorer + Postgres + BTCPay) as additional disposable infra, and grants the agentmerchant-onboard+payment_providers:write. Goal: the agent connects BTCPay (regtest) over the API and drives a test buyer payment that activates a license, with zero master-key steps. The walkthrough must be explicitly labeled regtest/test-network and must state that connecting a real mainnet wallet is the one operator-reserved step by design (a key that can redirect funds stays with the human) — a security feature, not a gap.
Packaging & distribution
- Start9 Community Registry submission — a 2026-06-17 spec check found the wrapper passes the functional
criteria (manifest, interfaces, health check, backup/restore, BTCPay dep, actions). Remaining gap before
submission: add a
prepare.shto set up a clean Debian box for the first build (copy the one fromhello-world-startos), then run the on-box manual verification (install / backup / restore / logs). Submission criteria themselves remain unpublished; reach out to Start9 when ready. (Icon-render and the source-available license are intentionally not treated as blockers.)
Licensing model
- Evaluate Elastic License v2 vs the current custom
LicenseRef-Keysat-1.0(parked decision).
Validation
- Re-test
KEYSAT_INTEGRATION.mdagainst a fresh downstream app to confirm a clean one-shot SDK integration. - Add an automated regression test for multi-profile webhook routing (adjudicated 2026-06-17 → DO, low blast radius — replaces the parked "manual Zaprite sandbox pass"). The routing is a deterministic provider-id→profile primary-key lookup with an anti-forgery re-fetch backstop, so the manual sandbox ceremony isn't worth it — but the path-keyed route (
/v1/{provider}/webhook/:provider_id→handle_for_provider) currently has zero automated coverage on the money path. Plan: intests/api.rs, reuse the two-provider fixture (~:3958), POST a Settled webhook to/v1/zaprite/webhook/{provider-A-id}, assert only profile A settles (B untouched; an unknown path-id 404s). Existing mock seam, no external account, runs incargo test. Effort S.