Files
yvgude--lean-ctx/docs/contracts/ctxpkg-registry-v1.md
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

9.0 KiB
Raw Blame History

Contract: ctxpkg Registry v1 (GL #406)

Status: ACTIVE Consumers: lean-ctx pack publish/install (CLI), ctxpkg.com storefront, leanctx.com account pages (token management), control plane (server)

The hosted registry for .ctxpkg context packages, served via ctxpkg.com. v1 is deliberately minimal: publish, index, download, yank — no payouts, no search API, no WASM plugins (see Out of scope).

Identity & trust model

Concern Who enforces it How
Authenticity Registry (publish gate) mandatory ed25519 signature, verified server-side against the engine's exact signing message
Name binding Registry (publish gate) manifest.name must equal @{namespace}/{name} from the URL; version likewise
Transport integrity Client (install gate) artifact SHA-256 from the index compared against downloaded bytes
Content integrity Client (install gate) engine verify_integrity (content_hash, composite sha256, byte_size) + local ed25519 re-verification
Immutability Registry (DB constraint) (package, version) unique; re-publish → 409; yank flips a flag, never deletes

The signing message is the engine's ctxpkg-sign-v1 format (rust/src/core/context_package/signing.rs):

message = hex(sha256("ctxpkg-sign-v1:{name}:{version}:{integrity.sha256}"))
signature = ed25519_sign(signing_key, message_bytes)

Namespaces

  • One namespace per account, claimed once, permanent in v1.
  • Org-owned namespaces (GL #524): claim with org_id — the claimer stays the anchor (FK target for tokens/domains); org owners/admins manage (mint publish tokens, domains), members mint read tokens and install private packages. Requires owner/admin role in the org to claim.
  • Format: [a-z0-9-], 232 chars, no leading/trailing dash.
  • Reserved (never self-serve): leanctx, lean-ctx, ctxpkg, official, verified, registry, api, www, admin, internal, system, root, staff, support.
  • Package names: [a-z0-9-], 164 chars. Versions: SemVer MAJOR.MINOR.PATCH with optional pre-release/build suffix.

Visibility (GL #524)

  • manifest.visibility: public (default) or private.
  • Private packages never appear in catalog/search/badge; index, manifest and download answer 404 unless the request carries a bearer token of the owning namespace (any scope). Authenticated private responses are Cache-Control: private, no-store.
  • Set at export: lean-ctx pack export <name> --sign --private.

Paid packs (GL #529, v0 first-party)

  • Price is registry metadata (registry_packages.price_cents/currency), not part of the signed manifest — repricing never forces a release. Set via PUT /api/account/registry/price {name, price_cents} (owner/admin; 0/null clears; v0 cap 50 000 cents).
  • Catalog/index expose price: {amount_cents, currency} | null. Discovery of paid packs stays public — only the download is gated.
  • Download gate: no purchase and no owning-namespace token → 402 with an actionable {"error": …} (where to buy). Purchased or owner → 200 with Cache-Control: private, no-store.
  • Buy flow: POST /api/account/registry/buy {namespace, name} (session auth on leanctx.com) → Stripe Checkout (mode=payment, metadata kind=ctxpkg_purchase, user_id, package_id) → webhook checkout.session.completed records the purchase (idempotent on the session id). Purchases bind to the account; buyers mint account install tokens (ctxr_…, no namespace required) to install.
  • Refunds: manual in v0 (Stripe dashboard; delete the purchase row to revoke access).

Public surface (ctxpkg.com → control plane /registry/v1/*)

ctxpkg.com proxies /api/* → control-plane /registry/*. All bodies JSON unless noted.

Method & path (public form) Auth Returns
GET /api/v1/index.json global catalog (public packages only): {schema:"ctxpkg-registry-index-v1", packages:[{namespace,name,scoped_name,description,latest_version,versions,downloads,updated_at,tags,verified,visibility,quality}]} (Cache-Control 300 s)
GET /api/v1/packages/{ns}/{name}/index.json token if private {schema:"ctxpkg-registry-package-v1", latest, visibility, versions:[{version,artifact_sha256,size_bytes,signer_public_key,yanked,downloads,published_at}]} (Cache-Control 60 s public / no-store private)
GET /api/v1/packages/{ns}/{name}/{version}/download token if private artifact bytes (application/octet-stream), x-ctxpkg-sha256 header, x-ctxpkg-yanked: true when yanked. Yanked stays downloadable (reproducibility) but is excluded from latest.
PUT /api/v1/packages/{ns}/{name}/{version} Bearer ctxp_… (publish scope) 201 {published, artifact_sha256, size_bytes, trust_report}; 400 invalid/unsigned/read-token; 401 bad token; 409 version exists; 413/400 too large
DELETE /api/v1/packages/{ns}/{name}/{version} Bearer ctxp_… (publish scope) 200 {yanked} — yank, not delete; owner namespace only

Publish-time checks (trust_report, persisted per release): schema:"ok", signature:"verified", name_version_match, size_within_limit (default cap 8 MiB), and an explicit skipped list (wasm_capability_audit, malware_heuristics) — the report never claims more than was checked.

Publisher self-service (leanctx.com account → edge → control plane)

Edge routes (session auth, open cloud_server):

Edge route Forwards to (internal-key auth)
GET /api/account/registry GET /api/billing/registry/{user_id}
PUT /api/account/registry/namespace PUT /api/billing/registry/{user_id}/namespace
POST /api/account/registry/tokens POST /api/billing/registry/{user_id}/tokens
DELETE /api/account/registry/tokens/{id} DELETE /api/billing/registry/{user_id}/tokens/{id}
PUT /api/account/registry/price PUT /api/billing/registry/{user_id}/price
POST /api/account/registry/buy POST /api/billing/registry/{user_id}/buy

Tokens (GL #524 scopes): 256-bit, plaintext shown exactly once at mint; only the SHA-256 is stored. Max 10 active per publisher; revocation is immediate.

Scope Prefix May Minted by
publish (default) ctxp_ publish, yank, install (incl. private) anchor / org owner / org admin
read ctxr_ install only (incl. private + purchased) — CI-safe any org member; any account (buyer install tokens, GL #529)

POST …/tokens body: {label?, scope?: "publish"\|"read"}. The claim body accepts {namespace, org_id?}; org claims need owner/admin in that org. Accounts without a namespace may mint read tokens only (GET /api/account/registry then returns namespace: null plus their tokens and purchases).

CLI surface

lean-ctx pack export <name>[@version] --sign     # ed25519-signed bundle
                                                 # key: ~/.lean-ctx/keys/ctxpkg-ed25519.key
                                                 # (auto-generated, 0600 — back it up)
lean-ctx pack export <name> --sign --private     # private on the hosted registry
lean-ctx pack publish <file.ctxpkg> [--registry <url>] [--token <ctxp_…>]
                                                 # token also via CTXPKG_TOKEN
                                                 # ctxr_ tokens are rejected locally
lean-ctx pack install <ns>/<name>[@version] [--registry <url>]
                                                 # registry also via CTXPKG_REGISTRY
                                                 # CTXPKG_TOKEN (ctxp_/ctxr_) unlocks
                                                 # private packages

Install resolves latest (newest non-yanked) unless pinned; a pinned yanked version installs with a loud warning. Every remote install is recorded in the project's .lean-ctx/ctxpkg.lock:

[[package]]
name = "@acme/auth-context"
version = "1.2.0"
artifact_sha256 = "…"
registry = "https://ctxpkg.com/api"

Out of scope for v1 (deliberate)

  • WASM plugins / policy packs as registry artifacts — blocked on the capability-audit pipeline (#403 signing story); v1 hosts signed .ctxpkg context packages only.
  • Publisher payouts (Stripe Connect rev-share, GL #532), namespace transfer, multiple keys per publisher (key rotation = new publisher identity in v1).
  • Server-side search → shipped (GL #514). Org-owned namespaces, private packages, read tokens → shipped (GL #524, P2). First-party paid packs → shipped (GL #529, P3 v0).

Module map

Concern Where
Server: routes + validation control plane src/routes/registry.rs, src/routes/registry_tokens.rs, src/routes/registry_purchases.rs
Server: queries / blobs / verify control plane src/registry_db.rs, src/registry_store.rs, src/registry_verify.rs
Client: remote calls rust/src/core/context_package/remote.rs
Client: signing keys rust/src/core/context_package/keys.rs
Client: lockfile rust/src/core/context_package/lockfile.rs
Edge proxies rust/src/cloud_server/billing_edge.rs
CLI rust/src/cli/pack_cmd.rs