Files
keysat-root/AGENTS.md
T

168 lines
11 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 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.