Files
keysat-root/AGENTS.md
T
Keysat 9c3ddc01e7 Fix doc drift; document no-enforce-mode and universal publish
Corrections surfaced by doc-auditor + start9-spec-checker:
- testing.md: api suite 47 -> 54
- payments.md: FK enforcement confirmed at db/mod.rs:29
- startos-packaging.md: publish.sh now ships a universal s9pk
- licensing-tiers.md: record enforce-mode retirement and Creator caps
Refresh Current state for the StartOS submission-blocker work.
2026-06-13 06:40:06 -05:00

196 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md — Keysat workspace
Self-hosted, Bitcoin-native software licensing service running as a StartOS 0.4.x
package, with four wire-compatible SDKs and a public landing/docs site.
This file holds whole-repo, every-session facts. Subsystem detail lives in scoped
guides under `docs/guides/` (symlinked to `.claude/rules/` so Claude Code
auto-loads each when you edit matching files). **Before editing a subsystem, read
its guide** — see the index below.
## Stack
- **Daemon**: Rust 1.88, `axum`, `sqlx` + SQLite, Ed25519 signing.
- **Wrapper**: TypeScript, `@start9labs/start-sdk ^1.3.2`, `@vercel/ncc` bundle, Node 22.
- **SDKs**: TS (npm), Rust (crates.io), Python (PyPI), Go (proxy.golang.org).
- **Platform**: StartOS 0.4.0.x (LXC under the hood — commands/paths reflect that, not Docker).
- **Payment providers**: BTCPay Server (required dep); Zaprite (optional, gated by `zaprite_payments`).
## Subsystem guides (read before editing the area)
- Before editing the daemon source, read `docs/guides/daemon-architecture.md`.
- Before editing payment / provider / merchant-profile code or migrations 00200022, read `docs/guides/payments.md`.
- Before touching self-license or tier-gating code, read `docs/guides/licensing-tiers.md`.
- Before changing the LIC1 wire format, crypto, or crosscheck fixtures, read `docs/guides/crypto-wire-format.md`.
- Before building, bumping the version, or editing the StartOS wrapper, read `docs/guides/startos-packaging.md`.
- Before editing the admin SPA (`web/index.html`), read `docs/guides/admin-ui.md`.
- Before editing public site/docs copy, read `docs/guides/website-copy.md`.
- Before adding/altering tests or relying on lint/CI, read `docs/guides/testing.md`.
## Build / test / run (quick ref)
From `licensing-service-startos/`: `make x86` | `make arm` | `make universal` |
`make install` | `make clean` | `npm run check`. From
`licensing-service-startos/licensing-service/`: `cargo check` | `cargo build
--release` | `cargo test` | `cargo test --test <suite>` | `cargo test <name>`.
Details, the version-bump-before-build rule, and release scripts:
`docs/guides/startos-packaging.md`. Test suites, the no-CI / formatting-not-enforced
status, and known-failing tests: `docs/guides/testing.md`.
## Directory layout
```
licensing-service-startos/ daemon + StartOS wrapper (s9pk package source)
licensing-service/src/ Rust daemon → guides/daemon-architecture.md
licensing-service/migrations/ SQLite migrations (numbered, additive)
licensing-service/web/index.html embedded admin SPA → guides/admin-ui.md
licensing-service/tests/ integration suites → guides/testing.md
startos/ wrapper TS → guides/startos-packaging.md
Dockerfile Makefile s9pk.mk build pipeline
keysat-xyz-landing/ keysat-docs/ keysat-registry-landing/ public sites → guides/website-copy.md
licensing-client-{rust,ts,python,go}/ the four SDK source repos
activate-license-template/ Tauri desktop template for license activation
keysat-design-system/ design tokens / brand assets
plans/ design specs (multi-provider-payment-model.md, keysat-smtp-emails.md)
tests/crosscheck/ cross-language LIC1 verifier → guides/crypto-wire-format.md
```
Note: the daemon (`licensing-service-startos`, repo `keysat`), each SDK, and
`plans/` are **separate git repos** — commit code/plan changes in their own repo.
The root `Licensing` repo (`keysat-root`) tracks only `AGENTS.md` + `docs/guides/`
+ `.claude/rules/` + `EVALUATION.md` (the latest full-eval report; overwritten
each run, history in git log). **Remotes differ per repo**: the daemon's `main` tracks
**GitHub** (`origin`, the public upstream) with a `gitea` backup — plain `git push`
goes to GitHub, so also `git push gitea main`; root + plans are **Gitea-only**.
Run `git remote -v` (full) and check what the branch tracks before pushing.
## Conventions (whole-repo)
- Daemon licensed `LicenseRef-Keysat-1.0` (custom, source-available); SDKs MIT.
- Commits in imperative mood, body only when the "why" isn't obvious. **Sign as
Keysat (Grant), not Claude** — git user is `Keysat`.
- Direct push to `main` + run `~/.keysat/publish.sh` is the authorized release flow
until launch.
- Never rewrite user-facing copy outside the explicit scope of a request.
## Never
- **No AI co-authorship** on commits or PRs (no "Co-Authored-By", no "Generated with…").
- **Don't push `--no-verify`** or bypass hooks unless explicitly authorized.
- **Don't commit built artifacts** (`*.s9pk`, `keysat-*.s9pk`, `javascript/`) or
**secrets** — reference env-var names; real values live in `~/.keysat/filebrowser.env`
and `/data/keysat-license.txt` outside the repo.
## Memory references
Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-licensing-Licensing/memory/`
(scan before a major change): `keysat_release_workflow.md`,
`no_unauthorized_copy_changes.md`, `keysat_admin_ui_pill_convention.md`,
`startos_lxc.md`, `startos_registry_icon_unrenderable.md`, `keysat_open_threads.md`.
## Open TODOs
- Verify the universal multi-arch publish end-to-end: `publish.sh` now runs
`make universal` (one `keysat.s9pk`, both arches) instead of x86-only; the first
real publish must confirm the registry index lists both arches. `riscv` target
unverified (not in the manifest, so `make universal` excludes it).
- StartOS Community Registry submission criteria — Start9 hasn't published the
checklist; reach out directly when ready.
- Registry icon doesn't render in the StartOS marketplace (see `guides/startos-packaging.md`).
- Split `audit:read` out of the blanket `:read` scope into its own tier so a
Read-only scoped key can read dashboards/licenses but NOT the full audit log
(`api/api_keys.rs::Role::grants`). Deferred from the scoped-keys session.
- Build the admin SPA "API keys" management panel (create w/ role picker, list,
revoke) — backend is wired; UI deferred to a design-focused session.
## Current state (2026-06-13)
- **Live**: server `immense-voyage.local` runs daemon `0.2.0:54` (migrations
00200022). Registry `registry.keysat.xyz` publishes `:54`; four SDKs published;
`keysat.xyz` + `docs.keysat.xyz` deployed. **Prod is still `:54` — the prior
session's two P1 fixes are committed to source but NOT yet built/installed/
published. Next release builds `:55`.**
- **This session (UNCOMMITTED across 4 repos; docs + StartOS packaging, no daemon
logic changed)** — doc-auditor + start9-spec-checker + 2 reviewer passes, all
approved/no blockers; `tsc` + `bash -n` clean. By repo:
- *root* (Gitea): `testing.md` api 47→54; `payments.md` FK confirmed
(`db/mod.rs:29`); `startos-packaging.md` + this block updated for universal
publish; `licensing-tiers.md` gained the "no enforce mode / Creator caps" note.
- *keysat-docs* (Gitea): `integrate.html` phantom `GET /v1/licenses/{id}/status`
→ real `POST /v1/validate` w/ `key`. **Needs `deploy-sites.sh docs` to go live.**
- *keysat daemon* (GitHub+gitea): new `instructions.md` (Start9-required);
manifest `packageRepo` + `docsUrls[1]` dead-link fixes; `v0.2.0.ts` stale-header
removed; `activateLicense.ts`/`showCredentials.ts` enforce-mode drift cleaned
(enforce retired — `self_license.rs:15`).
- *go SDK* (Gitea): README v0.1→v0.2.
- *operator-local* `~/.keysat/publish.sh` (gitignored, NOT committed): x86-only →
`make universal` (one `keysat.s9pk`, both arches). **Pending a verification build.**
All 4 StartOS submission blockers now addressed. Left for operator decision:
`integrate.html` BTCPay-only prereq/refund copy (no Zaprite mention). Commit =
4 per-repo commits (root, keysat-docs, go SDK are Gitea-only; daemon is
GitHub+gitea — also `git push gitea main`).
- **`:52`/`:53` = multi-provider/merchant-profile model**: data model + backend
resolution shipped and audited sound; resolution/CRUD query surface has tests.
Both `:54` P0s (provider-injection test seam; Zaprite webhook-forgery re-confirm)
remain fixed; live purchase + settle paths sound.
- **Unshipped source work (awaiting `:55`)** — two P1s from the prior session:
1. **Settle-amount tripwire.** `get_invoice_status` now returns
`ProviderInvoiceSnapshot { status, amount }`; `audit_settle_amount` (shared by
webhook + reconcile issue paths) WARNs + writes an `invoice.amount_mismatch`
audit row on drift, then **issues anyway** (advisory, not a gate — a hard gate
would fight BTCPay payment tolerance). SAT-only: skips non-SAT (fiat sub
renewals) and `None`. Reviewed (caught + fixed a fiat-renewal false-positive).
See `docs/guides/payments.md`.
2. **Scoped API keys wired.** 58 admin endpoints migrated `require_admin`
`require_scope`; 12 sensitive ones stay master-only (issuer key, provider
connect/disconnect, set-password, api-key CRUD, db-info, operator-name,
per-license tier change). `require_scope` re-exported from `api::admin`. Role
boundary tests added. Boundary documented in `api/api_keys.rs` module doc.
- **GAP — multi-profile still non-functional end-to-end**: nothing writes
`products.merchant_profile_id` (INSERT in `create_product_with_currency` omits
it; `update_product_with_currency` has no field; `Product` in `models.rs` lacks
it). Resolver fully supports it; only the product→profile **write path** is
missing. **Gating piece for multi-profile.**
- **Work queue (next, in order)**:
1. **product→merchant-profile picker** (the GAP — add `merchant_profile_id` to
`Product` + `repo.rs` SELECT; `set_product_merchant_profile` writer mirroring
`set_product_entitlements_catalog`; field on `CreateProductReq`/
`UpdateProductReq` applied post-write; profile `<select>` from
`GET /v1/admin/merchant-profiles`, shown only when >1 profile; no migration).
2. 3 other deferred UIs (rail picker, per-profile SMTP, rail-pref editor);
`unlimited_merchant_profiles` on master Pro/Patron policies.
3. Deferred this session (now in Open TODOs): split `audit:read` out of the
blanket `:read` scope; build the admin "API keys" management SPA panel.
- **Known debt (P2 — schedule, not urgent)**: no rate-limit on `/v1/purchase` +
`/v1/redeem`; rate-limit bucket keys on spoofable `X-Forwarded-For` (bypass
conditional on whether the StartOS proxy rewrites XFF — unverified); `422`/`415`
errors return plain-text not JSON (breaks SDK `JSON.parse`); product `slug` has
no validation (empty/300-char/meta chars stored); `GET /v1/admin/products`
returns 405 though OpenAPI documents it; dep advisories (`sqlx`→≥0.8.1
RUSTSEC-2024-0363, `rustls-webpki`→≥0.103.12); **4 StartOS submission blockers**
(spec-checker-verified) all addressed and staged, pre-build — manifest
`packageRepo` (`…/keysat-startos``…/keysat`) and `docsUrls[1]`
(`docs/INTEGRATION.md``KEYSAT_INTEGRATION.md`, the real repo-root file) fixed;
`instructions.md` written (reviewer + doc-auditor signed off); aarch64 now shipped
via `publish.sh` `make universal` (one s9pk, both arches — pending a verification
build); no CI + fmt/clippy/prettier unenforced.
- **Deferred (P3+ — bulk or later decision)**: `/v1/purchase` 400 vs
`/v1/btcpay/webhook` 503 for the same no-provider cause; undocumented required
`kind` on discount-codes; field-naming drift (`license_id`/`id`, machines `key`
vs `license_key`, `redeem`/`purchase` `product` vs `validate` `product_slug`);
migration self-heal `_sqlx_migrations` allowlist foot-gun; 2 KB unauth Zaprite
payload WARN-log; outbound-webhook SSRF (operator-only); re-register the master
Zaprite webhook at the path-keyed URL; registry icon non-render (known platform limit);
optional fmt/prettier standalone commit.
- **Tests/build**: `cargo check` clean (1 intentional deprecation warning); full
suite green — api **54** (incl. new settle-tripwire + scoped-key role-boundary
tests), subscriptions 7, upgrades 9, worker 3, crosscheck 4, migrations 9. No new
clippy warnings. FK enforcement **confirmed** — sqlx pool sets `foreign_keys(true)`
per connection (`db/mod.rs`). CI/fmt status is in Known debt.