# HAIDAA admission and publication — pilot-publication-v1

**ADMITTED means canonical storage with a signed admission receipt. PUBLICLY AVAILABLE requires a separate, independently authorized serving decision. Neither implies scientific endorsement.** Runtime discovery at `/v0/capabilities` reports `shared_contribution.enabled`. Sandbox enrollment remains a different policy.

## Lifecycle and scope

Sandbox qualification → operator-issued `shared-contributor-v1` grant → contributor signs directly in shared namespace `550e8400-e29b-41d4-a716-446655440000` → normal admission, initially `quarantined` → operator reviews exact records/dependencies → signed `content.release` → `available` public view.

A grant binds one key-derived actor and signing key, exact namespace, fixed append and own-record-read verbs, explicit schema allowlist, start/expiry, lifetime quota (at most 100) and per-minute budget (at most 5). Maximum interval is one hour. Issuance requires an approved, unrevoked sandbox identity with at least one sandbox admission, plus a separately signed operator qualification decision. An expired sandbox write window does not erase historical qualification. There is no automatic promotion or renewal. A root moderation key cannot receive a shared contributor grant through this mechanism.

The operator communicates the grant ID to the contributor; it is not a bearer secret. The contributor fetches `/v0/shared/grants/{grant_id}` with its key proof to inspect issuance and current write activity. Retained issuance includes the operator-signed command and server-signed control record. Verify against separately pinned operator and host public keys; do not trust an embedded key alone. The signing key must match the sandbox identity named by the issuance command. Revocation blocks future writes at HTTP authentication and again inside the atomic D1 admission transaction. Old signatures and receipts remain valid historical evidence.

Shared grants use:

```text
Authorization: HAIDAA-SHARED <grant_id>
X-HAIDAA-Time: <integer Unix seconds>
X-HAIDAA-Signature: <unpadded base64url Ed25519 signature>
```

Sign UTF-8(`HAIDAA-SHARED-REQUEST-V1\n` + RFC 8785 JCS(object)), with exactly:

```json
{"host_id":"https://api.haidaa.com","grant_id":"UUID","method":"POST","target":"/v0/namespaces/UUID/events","timestamp":1788685554,"event_id":"sha256:..."}
```

The target is the exact pathname plus query. LF is byte 0A, not two literal characters. Timestamp tolerance is 60 seconds. Use null event_id for GET. The proof binds the event; an independent event signature is also required. Save signed envelopes before POST and reuse their nonces/bytes on retries, refreshing only the HTTP proof. Identical retries consume no new quota. Inactive grants cannot retry writes. This is the pilot application proof, not RFC 9421 or full V1 request-nonce semantics.

Shared contributors can read their own quarantined/available events by exact authenticated lookup, including after write expiry/revocation. Other contributors' nonpublic content, suppressed/erased content and private shared inventories are unavailable. Use the anonymous public graph for other authors' available work. Legacy operator bearer reads retain their privileged namespace-wide scope. Shared grants confer no moderation, infrastructure, database, signing-server, or capability-management authority.

## Operator control

`POST /v0/control` accepts only fixed-schema, Ed25519-signed operator commands. There is no anonymous admin API, arbitrary SQL interface or CORS grant. `PUBLICATION_ADMIN_KEY` is an out-of-band pinned **public** root key; the private key stays on the operator machine. `SHARED_INTAKE_ENABLED=true` enables shared writes/issuance independently from sandbox enrollment. Existing operator bearer tokens and enrollment revocation tokens cannot authorize these commands.

Supported operations: `control.inspect` (private review and paginated audit), `grant.issue`, `grant.revoke`, `content.quarantine`, `content.release`, `content.suppress`. No contributed prose is interpreted as a command. Commands use `haidaa-control-command`, version 1; bind official host origin, exact shared namespace, key-derived administrative actor, signing key, UUID nonce, current expected audit head, creation time and fixed operation arguments. Domain: `HAIDAA-CONTROL-COMMAND-V1\n`. Command ID hashes the domain-prefixed canonical body. Unknown fields fail closed.

Content arguments include 1–50 unique exact targets, each with its expected prior action ID, a bounded reason, policy identifier and optional private administrative provenance mappings. The mapping records source namespace/event and destination event; it is not a scientific cross-namespace link. Inspect returns retained canonical event and receipt bodies, signatures, current state and prior action IDs for review.

The append-only D1 control log retains the complete command/signature and a separately host-signed record with sequence, previous hash, command ID, envelope digest, acceptance time and profile. Record domain: `HAIDAA-CONTROL-RECORD-V1\n`. Expected-head checks, command nonce uniqueness, log append, grant/state effects and head advancement commit in one D1 batch. A competing admin command returns `409 control_head_conflict`; never silently rebase a signed command. Retrying the exact committed command returns its retained record even after its timestamp expires. Fresh uncommitted commands expire after 60 seconds. `control.inspect` is an authenticated read and has no mutation/log effect; repeat reads are safe.

Operator CLI (Node 22, installed project dependencies):

```sh
npx tsx scripts/publication-control.ts init
npx tsx scripts/publication-control.ts inspect --events=sha256:ID,sha256:ID
npx tsx scripts/publication-control.ts prepare --action=.local/reviewed-action.json --output=.local/release-command.json
npx tsx scripts/publication-control.ts submit --command=.local/release-command.json
```

`init` exclusively creates a 0600 key in a 0700 directory and prints only its public key for trusted configuration. Existing keys are never replaced. `prepare` saves the signed command before mutation; `submit` sends exactly that file, saves the response and verifies the pinned host signature and command binding. Supply `--server-key` when using a separately trusted host key; defaults pin this pilot's retained key. The operator must review the exact content and action file before preparation. Failed review makes no serving change.

Example content action file (replace IDs and prior action from inspect):

```json
{"operation":"content.release","arguments":{"targets":[{"event_id":"sha256:...","prior_action":"admission-default"}],"reason":"reviewed-reproducible-fixture","policy":"scientific-seed-review-v1","provenance":[]}}
```

Example grant action: `operation: grant.issue`, with arguments `grant_id`, `source_grant_id`, integer `not_before`/`expires_at`, `max_events`, `per_minute`, `schema_ids`, `reason`, `policy`. Destination, actor and key are server-derived, not chosen by the contributor. Revocation arguments are `grant_id`, `reason`, `policy`.

## Release, suppression and public snapshots

Before release, load and verify each canonical event, require current retained serving state and exact prior action, reconstruct protocol dependencies, and require each dependency already public or explicitly included and eligible. Dependencies are traversed in deterministic order; no implicit publication expansion occurs. Missing/private/cross-namespace dependencies fail. A suppressed dependency must first be restored by a separate explicit release before dependent records can be released; bundling it cannot bypass that check.

Transitions: quarantined → available; suppressed → available with exact suppression prior action; available → quarantined; available/quarantined → suppressed. Erased is terminal and not produced by this slice. A duplicate operation with a new command against the same state is invalid; retry the original command ID instead. Suppression and quarantine block transitive dependents in ordinary serving. Restoring a dependency makes already-available dependents eligible again; separately quarantined/suppressed dependents still need their own review.

Scientific retraction remains an immutable author assertion. It does not suppress or erase its target. A released retraction may be public while its target remains public. Moderation changes neither scientific bodies, actor IDs, event IDs, event signatures nor admission receipt bytes.

Public graph, exact-event lookup and network summary use the same transitive dependency-closed shared-namespace view. Sandbox records never enter it. Unknown, quarantined, suppressed, erased and dependency-blocked direct lookups return the same nonrevealing 404. Public counts/activity omit those records. Public responses are no-store; the old network cache is no longer read. Already downloaded copies cannot be recalled. Receipt sequence gaps can exist because the unchanged scientific receipt chain also covers unpublished admissions; no private event bodies/IDs are exposed by the summary.

`publication-v1-N` is a monotonically increasing serving revision, advanced atomically on every content control action. It changes without deployment. Graph pagination retains public offsets and snapshot checks; stale snapshots return `409 publication_changed_restart`. It is an operational cursor, not proof of complete or nonforking host history. Grant changes advance the private audit head, not the public revision. Readers check the serving revision again after loading/signing their page.

## Migration and compatibility

Migration `0003_publication.sql` adds state/control/grant tables and immutable-log guards; it does not update canonical scientific events or receipts. Existing rows start quarantined except exact records in the former `v0.2-conformance-1` publication manifest. Those already-public decisions are imported as a canonical retained bootstrap descriptor, reason and domain-separated digest. The first new operator record links to that digest. **This is an out-of-band deployment bootstrap import, not a fabricated historical operator signature.** Only matching already-admitted IDs are imported; nonexistent IDs cannot authorize later admissions. All new admissions, including legacy bearer writes, default to quarantine.

`src/publication.ts` is now a historical fixture only. `scripts/seed-public.ts --submit` is retired. Ordinary contributions/releases do not edit it or deploy code. Back up D1 before migration; migrate before deploying the new reader. The preexisting public corpus stays available through the bootstrap import. Roll back by disabling shared intake and retaining the new reader/control code. Do not roll back to the old static reader after suppressions: that would discard current serving decisions. Preserve the D1 history and private operator key.

## Deliberate V1 limits

This implements the §7/§8.1 lifecycle without changing V0 scientific crypto. It does not claim full §7.1 replay: earlier enrollment/revocation history is separate; there is no full principal recovery/rotation system, all-namespace scientific-head barrier, administrative state digest/interpreter replay, receipt authorization-audit-head fields, PostgreSQL RLS, key ranges, external blobs, erasure implementation, or independent checkpoints. One pinned root is the current administrative authority. Host-signed control records attribute this pilot's operations but do not prove the host disclosed all history or that reviewed science is true. Cryptographic roles can be separate keys while the same human/agent operates both; that is not independent scientific replication.

## Seed client recovery

Admission proof survives publication/quarantine transitions. Seed clients now
reconcile exact saved envelopes and verified receipts before retrying writes;
`available` is a valid current state for an already-admitted event. Explicit
`prepare` precedes first `submit`; `resume` never regenerates missing signed
requests. A private SQLite journal retains recovery state and supports consistent
backup and export repair. See [the failure analysis and recovery runbook](docs/testing/CLIENT-RECOVERY.md).
