Compare commits
2 Commits
f036871111
...
0007f917b9
| Author | SHA1 | Date | |
|---|---|---|---|
| 0007f917b9 | |||
| 601ccea39c |
@@ -110,27 +110,27 @@ Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-keysat/
|
||||
policy entitlements (admin UI), then re-issue the master self-license so it takes
|
||||
effect.
|
||||
|
||||
## Current state (2026-06-17)
|
||||
## Current state (2026-06-18)
|
||||
|
||||
- **Live / canonical: `0.2.0:58`** — universal s9pk at `files.keysat.xyz/keysat.s9pk` + GitHub `v0.2.0-58`;
|
||||
live box `immense-voyage.local` on `:58`. Migrations through 0025; four SDKs published; two public sites
|
||||
(keysat.xyz, docs.keysat.xyz) live. `keysat-registry-landing` deleted this session — local + all refs gone;
|
||||
the GitHub + Gitea remote repos still need operator deletion (gh needs `delete_repo` scope).
|
||||
(keysat.xyz, docs.keysat.xyz) live. `keysat-registry-landing` local + refs gone; the GitHub + Gitea remote
|
||||
repos still need operator deletion (gh needs `delete_repo` scope).
|
||||
|
||||
- **This session — documentation audit + fix sweep across every public repo and both sites** (all committed +
|
||||
pushed to GitHub + gitea; sites redeployed and verified live): daemon docs → 0.2.0 (admin-UI replaces the
|
||||
removed StartOS actions, Zaprite shipped, roles, runtime image, validate reasons); SDK READMEs fixed (the
|
||||
Rust crate name/version was a copy-paste blocker) and expanded (TS/Python tiers, seats, free-license);
|
||||
landing SDK snippets + tier-card fallback prices; docs change-tier example + install-step resequence;
|
||||
Makefile pins `ARCHES=x86 arm`. No daemon source touched.
|
||||
- **This session — adjudicated the parked P2/P3 backlog** (ROADMAP-only, no source touched). Commit `601ccea`
|
||||
in `keysat-root` is local but **NOT yet pushed** — Gitea host `immense-voyage.local` was unreachable
|
||||
(off-LAN); retry `git push gitea main` when back on the box's network. Verdicts: dropped the design
|
||||
structural + token-gap tiers; reframed the manual Zaprite webhook "sandbox pass" into a small automated
|
||||
routing test (DO, ready plan in ROADMAP); dedup-cache → lean DROP pending one sandbox check; the 3 design
|
||||
"blocker" CSS one-liners → lean DO (need an owner glance — public/admin visuals).
|
||||
|
||||
- **Start9 Community Registry:** functional criteria pass; remaining gap is a `prepare.sh` for the clean-Debian
|
||||
first build (+ on-box verification) (ROADMAP). (Note: `registry.keysat.xyz` works as a marketplace on a Start9
|
||||
box; a plain browser/curl GET 404s **by design** — no HTML page is served there. Not an outage.)
|
||||
- **New top payments item (surfaced by the adjudication):** `try_auto_charge_zaprite` returns `Ok(true)` on
|
||||
any HTTP 2xx, so a Zaprite 200 carrying a FAILED/DECLINED/EXPIRED status silently lapses the subscription.
|
||||
Safe fail-safe fix needs no prod data; now the highest-priority payments item in ROADMAP.
|
||||
|
||||
- **Next (priority):** 1) Operator data action (master key): grant `unlimited_merchant_profiles` to Pro/Patron
|
||||
on live master (steps in Open TODOs). 2) Delete registry-landing GitHub + Gitea remotes. 3) 3 multi-profile
|
||||
UIs + split `audit:read`.
|
||||
- **Next (priority):** 1) Push `601ccea` to Gitea once reachable. 2) Operator data action (master key): grant
|
||||
`unlimited_merchant_profiles` to Pro/Patron on live master (Open TODOs). 3) Fix the auto-charge silent-lapse
|
||||
bug. 4) The 3 design blocker one-liners + the webhook routing test. 5) Delete registry-landing remotes.
|
||||
|
||||
- **Tests/build:** docs-only session, no code touched; last full suite green (lib/api/subscriptions/upgrades/
|
||||
worker/crosscheck/migrations through 0025), `cargo check` + `npm run check` clean. Debt (P2/P3) in ROADMAP.
|
||||
- **Tests/build:** ROADMAP-only session, no code touched; last full suite green (through migration 0025),
|
||||
`cargo check` + `npm run check` clean. Debt (P2/P3) in ROADMAP.
|
||||
|
||||
+15
-29
@@ -6,8 +6,8 @@ Longer-term backlog. Near-term state lives in `AGENTS.md` → Current state.
|
||||
|
||||
- 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`.
|
||||
- Keysat-side dedup cache for Zaprite contacts (same buyer purchasing recurring twice can create duplicate Zaprite contacts).
|
||||
- Zaprite declined-card / expired-profile failure-body shapes are undocumented — harden `try_auto_charge_zaprite` once observed in production.
|
||||
- **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_zaprite` returns `Ok(true)` on *any* HTTP 2xx (`subscriptions.rs:1403-1410`), reading the order `status` only for a log line. If Zaprite returns 200 carrying a `FAILED`/`DECLINED`/`EXPIRED` order status, the daemon fires `auto_charge_initiated` and then waits for an `order.paid` webhook that never arrives — the subscription silently lapses, no error surfaced, the customer churns. Safe fix (no production data needed): treat any non-`PAID` terminal 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_failed` audit + webhook + manual-pay fallback.)
|
||||
- Keysat-side dedup cache for Zaprite contacts (same buyer purchasing recurring twice can create duplicate Zaprite contacts). **Adjudicated 2026-06-17 → lean DROP.** Harm is cosmetic (duplicate rows in the operator's Zaprite contact list) and unverified — Zaprite's own dedup-on-email behavior is undocumented; the fix (new `zaprite_contacts` table + threading a DB handle into `ZapriteProvider`) is HIGH blast radius on the money path and could introduce a stale-cache misrouting bug. **Cheap check before dropping for good: buy twice with the same email on a Zaprite sandbox — one contact → drop outright; two → defer until real recurring revenue makes it worth it.**
|
||||
|
||||
## Agent compatibility & scoped API keys
|
||||
|
||||
@@ -45,36 +45,22 @@ Longer-term backlog. Near-term state lives in `AGENTS.md` → Current state.
|
||||
## Validation
|
||||
|
||||
- Re-test `KEYSAT_INTEGRATION.md` against a fresh downstream app to confirm a clean one-shot SDK integration.
|
||||
- End-to-end Zaprite sandbox pass on the multi-merchant-profile webhook routing before relying on it in production.
|
||||
- **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: in `tests/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 in `cargo test`. Effort S.
|
||||
|
||||
## Design (contract conformance)
|
||||
|
||||
The brand contract now lives in `design/DESIGN.md` + `design/tokens.tokens.json` (distilled
|
||||
2026-06-16 from the prior Claude Design system, now archived in `design/_imports/`). A
|
||||
`design-checker` audit (2026-06-16) found high fidelity overall, with these items where the
|
||||
**code contradicts the contract's stated rules** or bypasses the token scale:
|
||||
The brand contract lives in `design/DESIGN.md` + `design/tokens.tokens.json` (distilled
|
||||
2026-06-16 from the prior Claude Design system, archived in `design/_imports/`). A
|
||||
`design-checker` audit (2026-06-16) found high fidelity overall. **Adjudicated 2026-06-17:**
|
||||
the structural palette-consolidation and the token-gap nitpicks were **dropped** — the
|
||||
consolidation can't actually remove the duplication it targets (the rust-embedded admin SPA
|
||||
can't `@import` a shared file, so "consolidation" is a verbatim re-copy), and the token-gap
|
||||
list was partly mis-specified by the audit. Only the three contract-"never" blockers survive.
|
||||
|
||||
**Blockers (code violates a named "never" rule):**
|
||||
**Blockers — approved to fix (adjudicated → lean DO; wants an owner glance since they change
|
||||
public landing + admin visuals). Three reversible CSS one-liners:**
|
||||
- Gold used as an actionable *fill* (contract: gold is accent/border only, never a fill).
|
||||
(a) admin SPA `.featured-pill-toggle.on` → `web/index.html:418`; (b) admin sidebar
|
||||
upgrade CTA `#tier-banner-cta` → `web/index.html:537-542`. Fix to navy-fill or
|
||||
gold-border/text.
|
||||
(a) admin SPA `.featured-pill-toggle.on` → `web/index.html:417-419`; (b) admin sidebar
|
||||
upgrade CTA `#tier-banner-cta` → `web/index.html:537`. Fix to navy-fill or gold-border/text.
|
||||
- Primary buy CTA uses pill radius `999px` (contract: buttons are `r-md` 8px; pill is
|
||||
badges-only) — `keysat-xyz-landing/index.html:384-385`. Set to 8px.
|
||||
|
||||
**Structural (headline):**
|
||||
- All three surfaces inline their own copy of the CSS variables instead of importing the
|
||||
canonical `design/brand/palette.css` (landing :33-56, docs.css :7-21, admin :9-25). Copies
|
||||
are currently exact but one edit from drift. Consolidate onto `palette.css`.
|
||||
|
||||
**Token gaps / drift (decide: tokenize the as-built value, or snap to an existing token):**
|
||||
- `14px` card radius used throughout the marketing landing — not a token (between `r-lg` 12
|
||||
and `r-xl` 18). Snap to a token or add one.
|
||||
- Wordmark letter-spacing is `0.30em` (landing) vs `0.28em` (docs/admin) and has no token —
|
||||
pick one value, add a `letterSpacing.wordmark` token.
|
||||
- Semantic badge *text* colors (`#205c47`/`#7a5814`/`#8a2828`) are darker one-offs with no
|
||||
token — add `semantic.*-text` tokens or reference existing ones.
|
||||
- Syntax-highlight colors hardcoded as hex (`#d4b985`=gold-400, `#a6b7cf`=navy-300) — switch
|
||||
to `var()`. One admin hex `#f6f1e7` isn't a token (closest cream-50/100) — reconcile.
|
||||
- Sticky-header backdrop on docs/admin (`blur(10px)`/`blur(8px)`) diverges from the contract's
|
||||
`blur(12px)` — align if a single header treatment is wanted.
|
||||
badges-only) — `keysat-xyz-landing/index.html:390`. Set to 8px.
|
||||
|
||||
Reference in New Issue
Block a user