release: implement site registry v0.4 trust pipeline

This commit is contained in:
Nic Weyand 2026-09-13 12:22:05 -04:00
commit e26efc19fa
Signed by: nicweyand
SSH key fingerprint: SHA256:2te+ycJIQON/Wo/dH6+ZkFSQ4HnHWpetV2azx9E65dQ
67 changed files with 10698 additions and 640 deletions

View file

@ -1,82 +1,114 @@
# Dataset publisher runbook
This runbook creates a reviewable candidate and a signed activation. Source update,
review and release keys are separate authorities. Scheduled jobs may acquire,
import and build candidates; they do not approve, sign or activate destinations.
Scheduled acquisition and observation jobs create evidence and candidates. Human
reviewers vote on exact bundles. A separate publisher signs and activates an
accepted generation.
## Trust roots and local state
## Prepare trust and state
Keep cache, mutable store, immutable generations, reviewer keys, release keys and
consumer trust files outside the checkout. An OpenSSH reviewer trust file contains
one accepted principal and public key per line:
Keep source caches, captures, writer database, generations, reviewer keys,
publisher keys and consumer trust files outside the checkout. A reviewer
allowed-signers file contains one principal and public key per line. Add OpenSSH
validity options when rotating keys and preserve old keys for retained history.
```text
operator@example.org ssh-ed25519 REVIEWER_PUBLIC_KEY
```
Create a reviewed policy JSON when the reference policy is not appropriate. The
reference policy requires two independent identities, groups and physical keys
for every name, edge and equivalence; sticky revocations and publisher separation
are mandatory. Record actual group membership in `reviewer_groups`. Optional
`risk_thresholds` can require a larger quorum for `source_conflict` or
`dangerous_drift`; blocking those risks remains separately configurable.
For rotation, retain an old public key with an OpenSSH `valid-before` option
covering its signed decision times. New decisions are checked at append time, so
an expired key cannot submit backdated reviews; historical release verification
uses each authenticated `reviewed_at`. Remove a retired key only after no retained
generation or review log depends on it.
## Build and inspect a candidate
Distribute the release publisher public key to consumers through an independent
authenticated channel. Do not put private keys, production trust files or source
datasets in Git or CI. The isolated source CI runner has none of these files.
1. Import only manifests whose source, format, license and typed coverage were
checked. Keep the object and acquisition receipt.
2. Build with `--reviewer-trust`; record the returned receipt pin.
3. Run `verify`, `stats`, `diff`, `export-audit` and the fixed evaluation corpus.
4. Inspect `review-queue`, `evidence`, exact entity/domain reverse lookups and
source-separated popularity.
5. Run candidate observations on a bounded schedule and import their manifests.
Review `drift` and `revocation-candidates`; rebuild after imports.
## Candidate acceptance
Queue viewing is read-only. Observation import changes evidence bundles but never
route state.
Run `update` or the explicit download/import/build commands from the operator guide.
For every candidate generation:
## Create authenticated votes
1. Verify its externally recorded receipt pin with `verify`.
2. Run `stats` and compare source snapshots, selected sources, facts, rejections,
reviews, database bytes and upcoming expiry with the prior accepted generation.
3. Run `diff` against the prior pin. Investigate every source-selection, identity,
name, property, edge, popularity, review and equivalence change.
4. Use `lookup`, `entity`, `lookup-web`, `popularity` and `category` to inspect exact
source evidence and conflicts. Popularity never proves ownership.
5. Replay the maintained evaluation corpus with `evaluate`; require zero judgment
mismatches and compare latency with a documented hardware/process baseline.
## Authenticated decisions
Create a bounded review JSON from the exact candidate fingerprint and evidence.
Sign its exact bytes and append it through the CLI:
Use `prepare-vote` for a name or edge, or `prepare-equivalence-vote` for an exact
entity pair. The command writes canonical JSON containing the current evidence
and policy digests. Review those exact bytes, then sign them:
```bash
ssh-keygen -Y sign -n argand-site-registry-review \
-f /secure/reviewer-key review.json
argand-site-registry review --database /data/registry/import.sqlite \
ssh-keygen -Y sign -n argand-site-registry-vote \
-f /secure/reviewer-one vote.json
argand-site-registry verify-vote \
--generation /data/registry/candidate --pin "$CANDIDATE_PIN" \
--decision review.json --signature review.json.sig \
--decision vote.json --signature vote.json.sig \
--allowed-reviewers /secure/reviewer-allowed-signers \
--identity operator@example.org
--identity reviewer-one
argand-site-registry vote \
--database /data/registry/import.sqlite \
--generation /data/registry/candidate --pin "$CANDIDATE_PIN" \
--decision vote.json --signature vote.json.sig \
--allowed-reviewers /secure/reviewer-allowed-signers \
--identity reviewer-one
```
Identity equivalence decisions use the same signed JSON and reviewer namespace.
Rebuild after appending decisions, then repeat the complete diff and evaluation.
The release command re-verifies every retained reviewer signature against the
current reviewer trust file. Missing, altered or no-longer-trusted proofs stop it.
Repeat with enough independently controlled identities, groups and keys. Use
`equivalence-vote` to append equivalence votes after
`verify-equivalence-vote`. Rebuild and check the compiled decision. Never share
one private key under several reviewer names.
An approval expires within 90 days and begins no earlier than writer acceptance.
A revocation has no expiry. To restore a revoked subject, every new approval in a
complete quorum must list every active revocation ID in `supersedes`.
## Sign and activate
Run all offline gates and the separate network-enabled dependency audit. The
publisher identity and physical key must not have supplied any reviewer vote.
```bash
argand-site-registry sign --generation /data/registry/reviewed \
--pin "$REVIEWED_PIN" --key /secure/release-key \
--allowed-reviewers /secure/reviewer-allowed-signers
--pin "$REVIEWED_PIN" --key /secure/publisher-key \
--allowed-reviewers /secure/reviewer-allowed-signers \
--identity registry-publisher
argand-site-registry activate --generation /data/registry/reviewed \
--current /data/registry/current.json \
--allowed-signers /secure/release-allowed-signers \
--allowed-signers /secure/publisher-allowed-signers \
--allowed-reviewers /secure/reviewer-allowed-signers \
--identity registry-publisher
```
Record the source commit, candidate and accepted receipt pins, typed diff, evaluation
report, reviewer trust-file digest, release signer identity and activation receipt
in an immutable operator log. Activation refuses a rollback that drops a distributed
revocation. Deliver a new current pointer/pin to every consumer and bound their caches.
Record the source-code revision, provider manifest IDs, candidate and reviewed
pins, policy and reviewer-trust digests, diff, evaluation report, publisher
identity, and activation result in an immutable operator log outside Git.
Distribute the complete generation, signature, and independently authenticated
publisher trust root. Confirm every consumer received the new revocation state.
For an incident, append a signed revocation, rebuild with full history, inspect,
evaluate, sign and activate. Preserve the suspect source bytes, generation and proofs.
Follow [SECURITY.md](../SECURITY.md) for private reporting and key compromise.
For an incident, preserve the suspect evidence, append a signed revocation,
rebuild with full history, evaluate, sign and activate. Use
`revocation-candidates` only as review input; it never signs or appends a vote.
To protect pinned consumers before the full generation arrives, publish a small
cumulative feed under the distinct publisher namespace:
```bash
argand-site-registry export-revocations \
--generation /data/registry/revoked --pin "$REVOKED_PIN" \
--effective-at 2026-09-13T00:00:00Z --output /data/revocations.json
argand-site-registry sign-revocations \
--generation /data/registry/revoked --pin "$REVOKED_PIN" \
--input /data/revocations.json --output /data/revocations.json.sig \
--key /secure/publisher-key \
--allowed-reviewers /secure/reviewer-allowed-signers \
--identity registry-publisher
```
Signing recomputes the feed from the pinned generation and re-verifies every
reviewer vote. Distribute the exact feed, signature, and publisher trust root.
When replacing a feed, verify it with the prior feed and signature so a mirror
cannot discard a revocation. Refresh feeds before their seven-day deadline. A
newer compatible feed can block an older pinned generation, but clearing that
block requires installing its exact full generation so the consumer can recompute
the authenticated superseding quorum.
Follow [SECURITY.md](../SECURITY.md) for private disclosure and key compromise.