Files
keysat-root/AGENTS.md
T

11 KiB
Raw Blame History

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 building or changing any user-facing UI (landing, docs, admin SPA), read design/DESIGN.md and design/tokens.tokens.json and conform to them — the brand contract; pull colors/type/space/radii/shadows from the tokens, never hardcode off-scale values.
  • 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
  onboarding-harness/             docs-onboarding test rig → onboarding-harness/README.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
design/                           design contract (DESIGN.md + tokens.tokens.json) + brand/ assets; original Claude Design system archived in design/_imports/
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-keysat/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

  • riscv build target is unverified and not declared in the manifest (so make universal excludes it); revisit only if a riscv StartOS target appears.
  • 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.
  • Operator action (manual; needs the master admin key — a read-only key can't write): grant unlimited_merchant_profiles to the Pro and Patron tiers on the live master. Confirmed 2026-06-16 against licensing.keysat.xyz that the slug is absent from all three keysat policies (Creator/Pro/Patron), from the master's own Patron self-license, and from the product entitlements_catalog. Steps: add the slug to the keysat product entitlements_catalog, then to the Pro + Patron policy entitlements (admin UI), then re-issue the master self-license so it takes effect.

Current state (2026-06-16)

  • Live (registry/canonical): registry.keysat.xyz + files.keysat.xyz/keysat.s9pk publish 0.2.0:57 — universal multi-arch (x86_64 + aarch64), GitHub release v0.2.0-57. Migrations 00200023; four SDKs published; keysat.xyz + docs.keysat.xyz deployed. :57 shipped the merchant-onboard scoped role (catalog + license self-serve, no master key; src/api/api_keys.rs) — see git log for detail.

  • Live box immense-voyage.local: :57 was deployed this session via make install (start-cli package install returned clean; StartOS applies the swap async — not independently confirmed; verify the StartOS UI shows 0.2.0:57). publish.sh now runs make install as step 5, so future ships auto-deploy (best-effort, non-fatal).

  • Onboarding doc-harness — Stage 1 (no payments): completed-clean this session, committed + pushed. licensing-service-startos/onboarding-harness/ runs the global onboarding-tester agent docs-only against the SDK-integration journey (loop converged 5→1→0 stumbles over 3 runs). Doc fixes shipped to keysat-docs (integrate/agent/wire-format) + the served openapi.rs spec; the publishable walkthrough is harvested to agent.html #worked-example. The openapi.rs fixes reach the live spec only on the next daemon release; keysat-docs deploys independently. Full detail: onboarding-harness/STAGE1-RESULT.md. Stage 2 (regtest buyer-pays) is gated on agent-payment-connect slices 35 below.

  • In progress — agent-payment-connect (phase 2). Approved spec: plans/agent-payment-connect-scope.md. Lets a scoped key connect a BTCPay provider, but ONLY on a sandbox daemon and ONLY for a non-mainnet network — never folded into a role (a key that can repoint settlement is a fund-redirection key).

    • Foundation committed this session (3afac07, origin+gitea; NOT version-bumped) — slices 12 of 5: Config.sandbox_mode (env KEYSAT_SANDBOX_MODE, never API-settable; surfaced in /v1/admin/tier); migration 0024 scoped_api_keys.extra_scopes; per-key à-la-carte scopes (GRANTABLE_EXTRA_SCOPES=["payment_providers:write"], granted via extra_scopes, in NO role — grants() carves it out of full-admin's wildcard; fail-closed parsing). Reviewer pass clean after fixing the full-admin-wildcard P1.
    • Pending — slices 35: require_provider_connect gate (master→any; scoped+ payment_providers:write→only if sandbox_mode AND non-mainnet); BTCPay OAuth wiring (record scoped_initiator in btcpay_authorize_state at start_connect, network-check at finish_connect, migration 0025); Zaprite stays master-only. The BTCPay on-chain address network detection MUST be validated against a live regtest box before shipping (classify address prefix bc1/tb1/bcrt1, fail-closed to mainnet; the payment-method id is BTC-CHAIN vs BTC by version).
  • Work queue (next, in order):

    1. Build gate slices 35 (above) — validate the BTCPay address fetch on regtest.
    2. Confirm :57 is live on immense-voyage.local (StartOS UI).
    3. Operator data action (master key): grant unlimited_merchant_profiles to Pro/Patron.
    4. 3 multi-profile UIs + split audit:read (ROADMAP / Open TODOs).
  • P2/P3 debt (unchanged): set_product_entitlements_catalog missing rows_affected guard; no rate-limit on purchase/redeem (spoofable XFF); 422/415 plain-text not JSON; slug unvalidated; GET /v1/admin/products 405 vs OpenAPI; dep advisories (sqlx→≥0.8.1, rustls-webpki→≥0.103.12); no CI / fmt-clippy unenforced; field-naming drift; outbound webhook SSRF; design-contract conformance (see ROADMAP).

  • Tests/build: cargo check + npm run check clean (1 intentional deprecation warning); full suite green — lib unit 13, api 59, subscriptions 7, upgrades 9, worker 3, crosscheck 4, migrations 9 (through 0024). No new clippy warnings. FK enforcement confirmed — sqlx pool sets foreign_keys(true) per connection.