diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..2e9d841 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,50 @@ +# Ownership here means protected review, not notification: a change to a +# surface below cannot merge without an owner's approval once the branch +# ruleset in docs/GOVERNANCE.md is applied. The policy — what each surface is +# and why it needs an owner — lives in docs/GOVERNANCE.md, which this file +# must stay consistent with. +# +# Accountable owner per OpenCoven/psyche#14: @BunsDev. Team-scoped entries may +# refine this file; until teams exist, ownership is the named maintainers. + +# Default: everything requires maintainer review. +* @BunsDev @CompleteDotTech + +# Core contracts: canonical records, digests, IDs, the closed v1 registry, and +# the error vocabulary every other crate and consumer inherits. +/crates/psyche-core/ @BunsDev @CompleteDotTech + +# Store: durable records, append-only transitions, quarantine, retention, and +# the forward-only migrations that decide what older binaries can still open. +/crates/psyche-store/ @BunsDev @CompleteDotTech +/crates/psyche-store/migrations/ @BunsDev @CompleteDotTech + +# Coven boundary: the typed port contracts downstream repositories bind to. +/crates/psyche-coven/ @BunsDev @CompleteDotTech + +# Surfaces: bounded adapter ports; the wall against payload widening. +/crates/psyche-surfaces/ @BunsDev @CompleteDotTech + +# Conformance: the reusable C-S suites, fakes, and state-machine evidence. +/crates/psyche-test-support/ @BunsDev @CompleteDotTech + +# Evidence: the attestations and the checkers that verify they stay true. +/docs/G2-EVIDENCE.md @BunsDev @CompleteDotTech +/scripts/ @BunsDev @CompleteDotTech + +# Runtime, configuration, and CLI: startup, shutdown, and operator surface. +/crates/psyche-runtime/ @BunsDev @CompleteDotTech +/crates/psyche-config/ @BunsDev @CompleteDotTech +/crates/psyche-cli/ @BunsDev @CompleteDotTech + +# CI: the checks every other surface is gated by. Changes here can weaken +# every guarantee at once and are reviewed like contract changes. +/.github/ @BunsDev @CompleteDotTech + +# Release: what actually ships, and the manifests that make it verifiable. +/packages/ @BunsDev @CompleteDotTech +/docs/RELEASE.md @BunsDev @CompleteDotTech + +# Docs: what consumers and operators rely on being true. +/docs/ @BunsDev @CompleteDotTech +/*.md @BunsDev @CompleteDotTech diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c40f26d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/OpenCoven/psyche/security/advisories/new + about: Private disclosure route. Never a public issue — see SECURITY.md. + - name: Support and reporting guide + url: https://github.com/OpenCoven/psyche/blob/main/SUPPORT.md + about: What to include in a report, and what never to include. diff --git a/.github/ISSUE_TEMPLATE/contract-and-conformance.yml b/.github/ISSUE_TEMPLATE/contract-and-conformance.yml new file mode 100644 index 0000000..132cb91 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/contract-and-conformance.yml @@ -0,0 +1,56 @@ +name: Contract and conformance +description: Propose a schema/profile change or report a conformance gap against the frozen contracts. +labels: ["contract"] +body: + - type: markdown + attributes: + value: | + This issue is public and permanent. Never include secrets or credential + values, raw prompts or private conversation content, unrestricted + terminal output, private repository data, environment dumps, or full + personal paths. + + The v1 registry is closed: an unknown kind, unknown major version, or + unknown enum value is a strict decode failure and quarantines, never + dispatches. A proposal here is a proposal to revise that registry + deliberately, through review — not a bug in the refusal. + + - type: dropdown + id: kind + attributes: + label: Kind + options: + - Schema or profile change proposal + - Conformance gap (a suite accepts or rejects wrongly) + - Error vocabulary gap + - Digest or canonicalization concern + validations: + required: true + + - type: textarea + id: affected + attributes: + label: Affected schema kinds + description: The exact identifiers, e.g. psyche.delivery.v1, psyche.error.v1. The closed registry is listed in docs/SCHEMAS.md. + validations: + required: true + + - type: textarea + id: behavior + attributes: + label: Expected vs actual behavior + description: For a conformance gap, name the suite (C-S1 through C-S12, docs/TESTING.md) and the exact step. + validations: + required: true + + - type: textarea + id: evidence + attributes: + label: Evidence links + description: Immutable URLs to source, runs, or fixtures — with the SHA-256 where one exists, as docs/G2-EVIDENCE.md does. Do not paste private repository data; link what is public or describe it abstractly. + + - type: textarea + id: compatibility + attributes: + label: Compatibility consequence + description: What a consumer on the current registry would see after this change, and what migration or quarantine behavior follows. diff --git a/.github/ISSUE_TEMPLATE/defect.yml b/.github/ISSUE_TEMPLATE/defect.yml new file mode 100644 index 0000000..fb34841 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/defect.yml @@ -0,0 +1,67 @@ +name: Defect report +description: Report a defect or regression in Psyche. This form exists to keep protected data out of public issues. +labels: ["defect"] +body: + - type: markdown + attributes: + value: | + This issue is public and permanent. Never include secrets or credential + values, raw prompts or private conversation content, unrestricted + terminal output, private repository data, environment dumps (`env`, + `set`, process listings), or full personal paths. Replace home + directories with `~` (or `%USERPROFILE%`) everywhere, including in JSON. + + If you believe you have found a **vulnerability**, do not use this + form: follow the private route in SECURITY.md + (https://github.com/OpenCoven/psyche/security/advisories/new). + + - type: textarea + id: observed + attributes: + label: What happened + description: The observed behavior, trimmed to what matters. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What you expected + description: And why that expectation matches the documented behavior — docs/CLI.md, docs/CONFIGURATION.md, docs/SCHEMAS.md, or a conformance suite. + + - type: textarea + id: doctor + attributes: + label: Doctor output + description: | + `psyche doctor --json`. Safe to paste by design: it never reads a + credential and never contacts the network. Still redact personal paths. + render: json + + - type: textarea + id: terminal + attributes: + label: Failing command and output + description: The one command that failed and its error output only — not a transcript of the session. + + - type: input + id: version + attributes: + label: Version or commit + description: The commit if built from source, or the package version once packages exist. + validations: + required: true + + - type: input + id: platform + attributes: + label: Platform + description: OS and architecture, e.g. linux-x64, darwin-arm64, win32-x64. + validations: + required: true + + - type: input + id: schema-version + attributes: + label: Configured schema_version + description: The declared value, e.g. psyche.config.v1. Do not paste the configuration file itself. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..be54576 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,51 @@ + + +## Summary + + + +## Scope + + + +## Risk + + + +## Contract impact + + + +## Migration + + + +## Rollback + + + +## Generated artifacts + + + +## Tests + + + +- [ ] `cargo fmt --all -- --check` +- [ ] `cargo clippy --workspace --all-targets -- -D warnings` +- [ ] `cargo test --workspace --locked` + +## Affected consumers + + + +## Issue + +Refs OpenCoven/psyche# diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..831ea9b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,97 @@ +# Security policy + +This file states what security support exists today, where to report a +vulnerability privately, what happens after a report, and where ordinary +support ends. It describes the repository as it is; a release that cannot keep +these promises cannot claim them. + +## Supported versions and surfaces + +Psyche has no released versions. The npm packages are not published +(publication is gated at G12), the Rust workspace version is `0.0.0`, and no +tag exists. The only supported version is the `main` branch: + +| Surface | Status | Security fixes | +|---|---|---| +| `psyche` / `psyched` built from `main` | development build | fixed on `main` | +| SQLite store under the configured `data_dir` | development build | fixed on `main` | +| Coven socket boundary (typed port contracts) | development build | fixed on `main` | +| `@opencoven/psyche` npm wrapper | **not published** | none until first publish | +| Tagged or packaged releases | none exist | not applicable | + +Until the first tagged release there is nothing to backport to: a security fix +is a commit on `main`. The backport and support-window policy in +[docs/RELEASE.md](docs/RELEASE.md) begins operating with the first stable +release, not before. + +## The surfaces, concretely + +A security report should name one of these. Claims here are enforced by code +and pinned by tests; where this build does not yet enforce something, the +enforcement gap is stated in the same breath. + +- **Configuration.** The root config declares `psyche.config.v1` exactly; + unknown fields and unknown versions are errors. Secrets are named by + reference (`op://VAULT/ITEM/token`), never as literals — the `SecretRef` type + rejects a literal at parse time. In this release no field is typed as + `SecretRef` yet, so that rejection has nothing to act on; the gap is + recorded in [docs/CONFIGURATION.md](docs/CONFIGURATION.md) rather than + hidden here. +- **Data directory.** The store refuses to run with unsafe filesystem + permissions and fails closed rather than auto-relaxing them; + `psyche doctor` reports the same conditions without auto-fixing them. +- **Coven socket.** The daemon binds the socket configured at `coven.socket`. + The typed port contracts in `psyche-coven` and `psyche-surfaces` bound what + crosses it; adapters cannot widen payloads or add fields. +- **Logs and documents.** Logs are JSON on stderr, `--json` documents on + stdout. Extension-table values are never printed, and a `Config` is never + rendered with `{:?}` — both pinned by tests — because a future extension may + carry a credential. +- **npm wrapper.** The wrapper verifies the platform binary's SHA-256 against + a recorded manifest before exec and fails closed: the shipped digests are + placeholders that match no artifact, so the wrapper is inert until a release + replaces them. Its limits are stated in the package README — it is a + time-of-check/time-of-use check that authenticates the manifest's claim, not + the publisher. + +## Reporting a vulnerability + +Report privately through GitHub's private vulnerability reporting: +**https://github.com/OpenCoven/psyche/security/advisories/new** + +Never report a security problem in a public issue, a pull request, or any +other channel — a public report discloses the vulnerability before a fix can +exist. + +A report is usable with: the commit or package version, the surface above, +a minimal reproduction, and the impact you observed. A report must not +contain secrets or credential values, raw prompts or private conversation +content, unrestricted terminal output, private repository data, environment +dumps, or full personal paths — redact home directories to `~` (or +`%USERPROFILE%`). [SUPPORT.md](SUPPORT.md) explains how to trim a +report without trimming what matters. + +## What happens after a report + +| Step | Target | +|---|---| +| Acknowledgement | within 5 business days | +| Status update | every 14 days until resolution | +| Fix | on `main` (backports begin with the first stable release) | +| Coordinated disclosure | with the fix release, or within 90 days of confirmation, whichever comes first | + +The advisory is published when affected users can act on it, not before. +Credit is given at the reporter's discretion. No bounty program exists. + +## What is not security support + +Security reporting is not a support channel. Installation help, configuration +questions, and defect reports belong in the ordinary support routes in +[SUPPORT.md](SUPPORT.md). Out of scope for the security process: + +- a compromised operator account or host (fix the host; the daemon cannot + defend an attacker who can edit its configuration file and data directory), +- defects in the Coven daemon on the other end of the socket, or in any + messaging surface behind it — report those to their own projects, +- anything that requires a listening network service beyond the configured + Coven socket or a global guarantee the wrapper's README does not make. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..e05cec8 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,66 @@ +# Support + +Support is public, best-effort, and free. There is no paid tier, no private +channel, and no SLA. Before the first stable release (see +[docs/RELEASE.md](docs/RELEASE.md) for the support-window policy), "supported" +means the `main` branch — asking about anything else is asking about code that +no longer exists. + +## Where each kind of question goes + +| Question | Where | +|---|---| +| Defect or regression in Psyche | GitHub issue, using the **Defect report** form | +| Schema, contract, or conformance gap | GitHub issue, using the **Contract and conformance** form | +| Vulnerability | **Not an issue.** The private route in [SECURITY.md](SECURITY.md) | +| "How do I configure X" | Open a Defect report only if the documentation is wrong; otherwise an issue with the defect form is the only intake that exists | + +Blank issues are disabled because unstructured intake is where protected data +leaks: a pasted shell transcript or environment dump is exactly the artifact +the forms exist to prevent. The forms carry the same redaction contract as +this page. + +## What a usable report contains + +- The exact build: the commit if built from source, the package version once + packages exist. +- `psyche doctor --json`. It is designed to be safe to share: it never reads a + credential and never contacts the network. The one thing it cannot know is + which parts of a path you consider private, so still trim it (below). +- The configuration's declared `schema_version` (the string, not the file). +- The exit code, and which binary produced it. Exit codes are shared across + both binaries and documented in [docs/CLI.md](docs/CLI.md). +- The failing command and its error output — not an unrestricted transcript. + +## What never goes into an issue + +This is not politeness; it is the reporting contract. An issue is public and +permanent. + +- **Secrets and credential values.** The configuration cannot hold secret + literals (`SecretRef` rejects them at parse time), but a pasted file can + still carry material from other systems on the host. +- **Raw prompts and private conversation content** from any surface. +- **Unrestricted terminal output.** Trim to the failing command and its + error. A full scrollback is an environment dump by another name. +- **Private repository data** from your own projects, including names, + digests, and payloads you would not publish. +- **Environment dumps** — `env`, `set`, `printenv`, process listings, shell + histories. +- **Full personal paths.** `/home/name/...`, `C:\Users\name\...`, + `/Users/name/...` are replaced with `~` or `%USERPROFILE%` throughout the + report, including in JSON output. + +Redaction does not make a report less usable. The exact personal path under +which a daemon failed is never the diagnostic — the configuration it read, the +exit code, and the doctor report are. + +## What support is not + +- **Not indefinite data custody.** Internal and experimental records are + governed by the retention contract in [docs/SCHEMAS.md](docs/SCHEMAS.md); + retention is a feature, not a promise to keep every record forever. +- **Not downstream debugging.** Defects in the Coven daemon, a messaging + surface, or an adapter belong to their own projects. +- **Not a security intake.** Anything you believe is a vulnerability follows + the private route in [SECURITY.md](SECURITY.md) instead of an issue. diff --git a/docs/GOVERNANCE.md b/docs/GOVERNANCE.md new file mode 100644 index 0000000..c70f2c0 --- /dev/null +++ b/docs/GOVERNANCE.md @@ -0,0 +1,118 @@ +# Governance + +This document is the written policy behind the repository's governance +controls: who owns what, what review a change must pass, how the branch +ruleset enforces it, what happens in an emergency, and how dependencies, +secrets, and provenance are controlled. It is paired with +[docs/RELEASE.md](RELEASE.md), which covers what ships and how. + +Presence of these files is not readiness. Each control below is marked as +**current** (enforced today) or **applied** (the configuration to apply). +"Applied" controls are listed here precisely so that applying them is a +reviewable change with a known target state, and so that nothing can be +claimed ready by quoting this document instead of checking the repository. + +## Ownership + +Protected review means a change to a surface cannot merge without an owner's +approval. The mapping lives in [.github/CODEOWNERS](../.github/CODEOWNERS); the +reasoning lives here. + +| Surface | Why it has an owner | +|---|---| +| `psyche-core` contracts | The closed v1 registry, digests, IDs, and error vocabulary are inherited by every consumer; a silent change here is a breaking change everywhere. | +| `psyche-store` and its migrations | Durability and retention decisions; the forward-only migration rule decides what older binaries can still open. | +| `psyche-coven` boundary | The typed port contracts downstream repositories bind to. | +| `psyche-surfaces` | The wall against payload widening; adapters cannot add fields. | +| `psyche-test-support` | The reusable conformance suites — evidence that downstream adapters are tested against. | +| Evidence (`docs/G2-EVIDENCE.md`, `scripts/`) | The attestations and their checkers; editing either can falsify what "passed" means. | +| CI (`.github/`) | The checks every other surface is gated by; a CI change can weaken every guarantee at once and is reviewed like a contract change. | +| Release (`packages/`, `docs/RELEASE.md`) | What actually ships, and the manifests that make it verifiable. | +| Docs (`docs/`, root `*.md`) | What consumers and operators rely on being true. | + +The accountable owner is @BunsDev. Until organization teams exist, ownership +is the named maintainers in CODEOWNERS; team-scoped entries may refine it +without changing this document. + +## Branch and ruleset policy + +The policy for `main`, to be applied as a repository ruleset: + +- **Pull requests required.** No direct pushes, by anyone. +- **Applies to administrators.** There is no role that bypasses review; this + is the point of the ruleset — a direct or bypass change cannot silently skip + required checks. +- **Code-owner review required** for the paths in CODEOWNERS. +- **Required checks** (the check-run names CI produces): + - `Rust checks (ubuntu-latest)`, `Rust checks (macos-latest)`, + `Rust checks (windows-latest)` + - `Dependency audit` + - `Secret guard` + - `npm distribution (Node 20)`, `npm distribution (Node 22)` +- **No force pushes, no deletions.** History on `main` is append-only, like + the transitions it records. +- **Squash merges, one PR per merge commit.** CI's evidence checker verifies + that each squash commit on `main` belongs to exactly one pull request; this + policy keeps that check true. + +As of this document, no ruleset exists on the repository yet; the required +checks above already run on every pull request, and the ruleset is what makes +them required and unbypassable. + +## Emergency changes + +The ruleset has no standing bypass. When an emergency requires one — a +security fix that cannot wait for review, a CI outage blocking every change — +the bypass is a **grant**, not a right: + +1. **Named.** The grant names the PR, the approver, and the reason, before the + bypass is used. +2. **Time-bounded.** The grant expires; the change must pass full review + within the window or be reverted. +3. **Auditable.** The grant record lives in the PR description, and the bypass + event is visible in the repository's audit log. +4. **Followed by restoration and review.** After the emergency, the ruleset is + restored to no-bypass and the change receives a normal review; the review + resolution is linked from the PR. + +An emergency change that is not followed by restoration and review is a +control failure, not a precedent. + +## Dependency review, secret scanning, and provenance + +The controls below are **current**; they run on every pull request and every +push to `main`: + +- **Dependency review** — `cargo deny check licenses advisories bans sources` + ([deny.toml](../deny.toml)): security advisories and yanked crates denied, + license allowlist enforced, wildcard dependencies denied, unknown registries + and git sources denied. The binary is installed pinned, the same command an + engineer runs locally — there is no CI-only path. +- **Secret scanning** — gitleaks over the full history of every ref + (`detect --no-banner --redact --log-opts="--all"`): a secret that was + committed and reverted is still a leak, and `--redact` keeps the finding out + of the public log. +- **Artifact verification** — the npm wrapper refuses to exec a platform + binary whose SHA-256 does not match the recorded manifest, and the shipped + placeholders fail closed until a release replaces them. + +What arrives with the first stable release, and is specified in +[docs/RELEASE.md](RELEASE.md): an SBOM for every packed artifact, build +provenance, and signed immutable release metadata binding source commit to +artifact digests. + +## Reporting hygiene + +Nothing in the intake path may encourage protected-data disclosure. The +public reporting routes are [SECURITY.md](../SECURITY.md) (private, for +vulnerabilities) and [SUPPORT.md](../SUPPORT.md) (public, for everything else); +the issue forms carry the same redaction contract, and blank issues are +disabled because unstructured intake is where protected data leaks. A report +that cannot be written without pasting an environment dump is the repository +failing the reporter, not the other way around. + +## Change review for this document + +Changes to this file, to CODEOWNERS, or to CI are contract changes and +require code-owner review; weakening a control here follows the same review +path as weakening one in code. diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000..705d1e2 --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,181 @@ +# Release + +This document is the release contract: what Psyche ships, through which +channels, how each version domain is numbered, what evidence a stable release +must carry, and how releases are supported, patched, deprecated, and rolled +back. It is paired with [docs/GOVERNANCE.md](GOVERNANCE.md), which covers +who approves changes and how the ruleset enforces review. + +Nothing has been released yet. The npm packages are unpublished (publication +is gated at G12), the workspace version is `0.0.0`, and the wrapper's +checksum manifest is all-zero placeholders that fail closed. This document +distinguishes, for every control, whether it is **current** (exercised today) +or **first exercised at the release rehearsal** — the checklist at the end — +so readiness can be verified rather than claimed. + +## Artifacts and channels + +| Artifact | Channel | Notes | +|---|---|---| +| `@opencoven/psyche` (npm wrapper) | npm registry — the only distribution channel | Contains `bin/`, `scripts/`, `README.md`, `package.json` only; tests are not shipped. | +| `@opencoven/psyche-` (five companion packages) | npm registry via `optionalDependencies` | Carry the compiled `psyche`/`psyched` binaries; must declare `os`/`cpu` and must not hide `package.json` behind `exports` — the wrapper's resolution depends on both. | +| Rust binaries | **only** inside the companion packages | The workspace is `publish = false`; crates.io is not a channel. | +| Conformance suites and evidence | The repository at a pinned commit, with immutable URLs and SHA-256 digests as [docs/G2-EVIDENCE.md](G2-EVIDENCE.md) does | Not packaged; consumers run the suites from a reviewed source commit. | +| Docs | The repository, at the same pinned commit | A release that changes behavior changes docs in the same commit. | + +One channel exists deliberately: every artifact a consumer runs arrives +through npm, so integrity checks, provenance, and deprecation notices have one +place to live. + +## Versioning + +Four domains move independently and are reconciled by the compatibility +matrix, not by a shared version number: + +| Domain | Where it lives | Scheme | +|---|---|---| +| Protocol schema/profile | The closed `psyche.*.v1` registry ([docs/SCHEMAS.md](SCHEMAS.md)) | The profile is the set of accepted schema majors. New kinds or majors are a new profile version; unknown kinds, majors, and enums quarantine, never dispatch — so a profile bump is always deliberate. | +| Rust implementation | `workspace.package.version` | Semver; `0.y.z` carries no stability promise. | +| npm wrapper | `packages/psyche-npm` `version` | Semver, independent of the Rust version: the wrapper's contract is resolve → verify → exec, and it can change without the daemon changing. | +| Conformance runner | The C-S suite matrix plus the manifest/checker scripts | Indexed by the evidence profile they attest (G2 today), pinned by commit and digest. | + +Two rules bind the domains together: + +- **A wrapper release must record real checksums.** `psyche.checksums` entries + are placeholders (all zeros) until the companion packages exist; the + placeholders fail closed, and the shipped test asserts they remain + placeholders until a real artifact replaces them. +- **Companion package versions move with the Rust release they package.** A + companion that does not contain the binary the wrapper's digest names is a + defect, not a versioning choice. + +## Immutable release metadata + +Every stable release resolves to immutable source and artifact digests. The +release record binds: + +- the source commit (an immutable URL, in the G2-EVIDENCE style), +- the SHA-256 of every published artifact — the values the wrapper's + `psyche.checksums` carries, replacing the placeholders, +- an SBOM (CycloneDX) for the workspace and for each packed artifact, +- build provenance (the npm publish is provenance-attested), and +- a release manifest carrying all of the above, **signed**, published as a + release asset and never mutated after publication. + +The signing mechanism is chosen at the release workstream; this document +records the requirement and its verification, not private signing details. +"Signed" means a consumer can verify who produced the manifest and that it has +not changed; a manifest that can be edited after publication is not release +metadata, it is a suggestion. + +## Verify packed artifacts, not source + +A release is verified from a clean install of the packed artifact, never from +source-relative imports — running `bin/psyche.js` out of a checkout proves +nothing about the tarball a consumer receives, and skips the resolution and +checksum path that is the wrapper's entire job: + +1. `npm pack ./packages/psyche-npm` (positional — `pack` reads + `package.json` from the working directory and ignores `--prefix`). +2. In a clean prefix, `npm install ` with the companion packages + reachable. +3. Run `psyche doctor --json` and `psyche status --json` from the installed + wrapper: resolution, digest verification, and exec all happened through + the shipped code path. +4. Repeat the pack dry-run on both supported Node majors (20, 22); the + `files` allowlist must resolve to the same five files. + +## Release stages + +**Release candidate.** A tagged build whose full CI run is green and whose +conformance evidence is recorded at a pinned commit. An RC is installable and +digest-verified; it is not stable and makes no promise about support. + +**Stable.** An RC becomes stable when **all** of the following hold: + +- the compatibility matrix row for the release is complete (below), +- every required downstream consumer canary is green — **a release cannot be + called stable while a required consumer canary is red or absent**. Absent + is not better than red: a canary that did not run proves nothing, and an + absent canary blocks stability exactly like a failed one, +- the signed release manifest, SBOM, and provenance are published, +- a clean-install verification (above) has passed for the packed artifacts. + +**Deprecation.** A deprecated release keeps its support window but stops +receiving fixes at its end. Deprecation is announced in the release notes of +the successor and on the deprecation date; the npm dist-tag for `latest` +moves, and the old version stays installable and digest-verifiable — +deprecation changes what is promised, not what exists. + +**Emergency patch.** An emergency fix to a stable release is cut from the +stable tag, carries the patch number, and is subject to the named, +time-bounded, auditable bypass procedure in [docs/GOVERNANCE.md](GOVERNANCE.md) +if review cannot complete first. The bypass is followed by restoration and a +normal review; the patch release notes name the emergency and link the +resolution. + +**Rollback.** npm versions are immutable: a bad release is **not** republished +under its own version and is **not** unpublished. Rollback means: + +- publish the corrected release as the next version, move `latest` to it, and + record the defective version as deprecated in the compatibility matrix; +- the wrapper's digest manifest is the rollback safety net — any binary whose + digest does not match the manifest the wrapper shipped with is refused, so + a defective companion cannot be silently substituted; +- for store state: schema is forward-only (below), so rolling back the binary + against an upgraded store is refused with + `UnsupportedDatabaseVersion`; rollback of data means restoring the + pre-upgrade backup taken in the upgrade procedure. + +## Support windows and upgrade/downgrade + +- **Pre-1.0 / `main`:** no support window; development builds are supported + by the next commit, nothing more. +- **Stable N:** supported until **N+1 stable plus 90 days**. Security fixes + apply to the latest stable only ([SECURITY.md](../SECURITY.md)); other fixes + ship in the next stable. +- **Upgrade:** apply the new binary; missing store migrations apply + atomically at open, forward only. Take the pre-upgrade backup the rollback + procedure relies on. Configuration is strict — an untouched config that + named `psyche.config.v1` keeps loading, and a config the new build rejects + is reported as such, not silently migrated. +- **Downgrade:** the store refuses it rather than guessing — a binary older + than the store's `user_version` fails closed with + `UnsupportedDatabaseVersion`. The only downgrade path for data is the + pre-upgrade backup; binaries alone can be downgraded freely against a + store they can still open. + +## Compatibility matrix and canary evidence + +Every stable release gets one row, published with the release and never +edited after publication — a correction is a new row: + +| Field | Meaning | +|---|---| +| Release | Wrapper version, Rust version, source commit | +| Schema profile | The registry majors this release accepts (e.g. v1) | +| Store schema | The `user_version` this release opens (1 today) | +| Conformance evidence | Immutable link to the suite run at the pinned commit | +| Canary: coven | Link to coven's consumer run against this release | +| Canary: other consumers | One row per required consumer canary | + +Canary evidence follows the evidence discipline of +[docs/G2-EVIDENCE.md](G2-EVIDENCE.md): immutable URLs, pinned digests, a +named run. A canary row that links a rerun-after-the-fact is a canary that +did not gate anything. + +## Release rehearsal + +One rehearsal proves the whole path end to end before the first stable +release claims it. The rehearsal is recorded as evidence — checklist, +commands, links, digests — in the G2-EVIDENCE style: + +- [ ] **Build:** workspace builds from a clean checkout at the pinned commit; `--locked` respected. +- [ ] **Conformance:** the C-S suites and state-machine runs pass at that commit, evidence recorded. +- [ ] **Pack/install:** packed tarball installs into a clean prefix and runs `psyche doctor --json` through the shipped wrapper (the packed-artifact procedure above). +- [ ] **Provenance:** SBOM, provenance, and the signed release manifest are produced, and the manifest verifies. +- [ ] **Downstream canary:** every required consumer canary runs against the packed artifacts and is green. +- [ ] **Rollback:** the downgrade/rollback procedure is executed — a defective-version scenario is simulated, `latest` moves, and the store-refusal path for downgrade is demonstrated. + +A stable release whose rehearsal is missing is not a stable release; it is an +RC with confidence.