← All reference docs

The team store: authenticated principals and repoint policy

The hosted layer of the three-layer model (local stdio → team store → public registry). Same kernel, same MCP tool surface, two differences that change the trust story:

  1. Principals are authenticated. oath serve --http <addr> --tokens <file> serves the MCP tools over HTTP (stateless streamable-HTTP: one JSON-RPC message per POST, Authorization: Bearer <token>). The journal author derives from the token's identity; a client-supplied author field is ignored. The server refuses to start without a tokens file — an unauthenticated network store would make every journal entry a lie.

    Tokens file (never commit one): {"<token>": {"principal": "name"}}, tokens ≥ 16 chars.

  2. Names move only through policy. <store>/policy.json holds repoint rules. Storage stays unconditional past the typecheck gate — objects are content-addressed facts — but a submission failing policy is journaled blocked and the name keeps pointing at its previous version. Nothing breaks; dependents pin hashes anyway.

{
  "rules": [
    {
      "names": ["sort", "max2"],
      "require_authorship_separation": true,
      "require_total": true,
      "forbid_falsified": true,
      "min_mutation_score": 0.8,
      "require_proven": true
    }
  ]
}

Authentication: signatures, not (just) tokens

oath serve authenticates a principal two ways, and a request needs one:

A present-but-invalid signature is rejected outright — it never falls through to token auth, so a forged key can't be laundered into a token principal. An unauthenticated store remains impossible: with no token file and no valid signature, every request 401s. (docs/registry-auth.md)

Registration gate (--authorized-keys, #66). Optionally restrict who may write: oath serve --authorized-keys <file> (or OATH_AUTHORIZED_KEYS), a JSON array of hex pubkeys. When set, only a listed key may write — an unlisted key still authenticates and READS (the store is public, re-verifiable), but its writes are refused. Absent/empty = open contribution (any signer writes), the default. This is the gate half of #66; delegated token minting (a key issuing scoped, expiring tokens for its agents) is the other half, still open.

Bearer tokens are capability-limited. A token is read-only by default: it can read, discover, and re-verify, but the state-changing tools — put (authors objects, moves names) and cross --record — are refused unless the token is granted "write": true. A signature always carries full capability (a key-holder authors as themselves). So a leaked read-only token can browse, never corrupt; writing needs either an explicitly write-scoped token or a signature. The tokens file:

{
  "<secret-token>": { "principal": "ci-bot", "write": true },
  "<other-token>":  { "principal": "readonly-agent" }
}

Semantics worth knowing

What this deliberately does not do yet

Tokens are static bearer secrets, not OAuth; there is no TLS termination (front it with a proxy); rules match names exactly (no globs beyond *); and policy is store-local — a public registry layer would need signed verdicts and verifier trust, which is issue #14's territory.


Rendered verbatim from docs/teamstore.md in the repository. The markdown is the single source; this page is a copy checked for drift in CI, so what you read here is what an implementer reads.