# MeshForge experimental offline release verification — version 1 Status: **implemented library with fixture tests**, not an installer, updater, production trust policy or independently audited release system. The package is `internal/releaseverify`. It uses only the Go standard library plus the existing local `internal/state` package. No network access, external dependencies, process execution, private key persistence or automatic discovery of trust roots occurs. No actual SteveCo-AI founder key is supplied or created. ## Local trust and public API `Create(Config)` initializes `release-checkpoint.json` in an existing trusted local directory and refuses an existing checkpoint. `Open(Config)` requires an existing valid checkpoint. It never falls back to Create when data is missing, malformed, oversized or bound to a different configuration. Both return a `Verifier`, which must be closed. Initialization is an administrative trust operation, not recovery; deleting state and deliberately creating a new trust store is outside the rollback guarantee. The caller supplies canonical base64url root public keys, a positive root signature threshold, exact product/channel/OS/architecture/protocol, a mandatory minimum risk class, and an artifact size ceiling. There are no root, risk or platform defaults. Every field except the directory pathname is included in a persisted local trust fingerprint. Roots are copied, sorted and deduplicated (duplicates are rejected); caller order does not change their identity. Open cannot silently change roots, threshold, risk floor, target or byte ceiling. An unknown download peer cannot configure any of these values. - `Observe(Bundle, time.Time)` verifies metadata signatures, scopes, current validity and the pinned policy, then durably records first local observation. Review/test quorums may still be incomplete. The returned `Observation` is **not authorization** and does not read an artifact. - `VerifyArtifact(Bundle, io.Reader, time.Time)` requires full release/review/test authorization, hashes the exact bounded byte stream, then reloads and updates the current checkpoint under its process lock. It returns `Authorization` only after successful durable state update. A valid but immature candidate returns `ErrReviewPending`, preserving its first observation. No filename is trusted or later executed by this API. - `DecodeBundle` and `EncodeBundle` implement the strict canonical wire format. Signing helpers return typed signatures for Policy, Manifest, ReviewStatement and TestEvidence; they reject an Ed25519 private key whose cached public half does not match its seed. Signatures do not imply any independent real identity. The public workflow is exercised by `TestWindowPersistsAndValidArtifactIsIdempotent`: initialize explicit fixture trust, Observe an incomplete signed candidate, obtain further approvals, reject before its local deadline, reopen state, then verify the artifact at/after the deadline. `TestTrustAndTargetArePinnedAcrossOpen` is the configuration contract. Callers must not mutate a Bundle concurrently with a call. File-based callers must retain control of the bytes they verified: the result does not authorize later execution of a mutable pathname. ## Canonical bytes, identifiers and signatures All signed messages are UTF-8 `context + NUL + body`, where `body` is `encoding/json.Marshal` of the declared typed body struct in `types.go`, in **declaration order**, without whitespace. This is a versioned typed encoding, not JCS or arbitrary-JSON normalization. Go's escaping of HTML-sensitive and line-separator characters is part of these bytes. Unknown/duplicate fields, noncanonical property order/escaping, whitespace, aliases and trailing data are rejected by strict decode and exact remarshal comparison. No map appears in a signed structure. The complete field declarations in `types.go` define order. | Body | Schema | Signature context | |---|---|---| | Policy | `mf.release-policy/1` | `meshforge/release/policy/v1` | | Manifest | `mf.release-manifest/1` | `meshforge/release/manifest/v1` | | ReviewStatement | `mf.release-review/1` | `meshforge/release/review/v1` | | TestEvidence | `mf.release-test/1` | `meshforge/release/test/v1` | | TransitionBody | `mf.policy-transition/1` | `meshforge/governance/policy-transition/v1` | | TransitionReview | `mf.policy-transition-review/1` | `meshforge/governance/policy-transition-review/v1` | | TransitionEvidence | `mf.policy-transition-test/1` | `meshforge/governance/policy-transition-test/v1` | Transition body declaration order is `schema`, `previous_policy_id`, `next_policy_id`, `next_sequence`, `rules_id`, `issued_at`, `expires_at`. Transition review order is `schema`, `candidate_id`, `decision`, `issued_at`, `expires_at`; evidence order is `schema`, `candidate_id`, `kind`, `report_sha256`, `passed`, `issued_at`, `expires_at`. Signed wrappers carry `body` then `signature` (or `signatures` for root co-signatures). The bundle schema is `mf.policy-transition-bundle/1`; its ordered fields are `schema`, `previous_policy`, `next_policy`, `transition`, `reviews`, `evidence`. Local TransitionRules are not a signed message: their ID is SHA-256 over `meshforge/governance/local-transition-rules/v1`, NUL, and the typed canonical JSON body. A transition ID uses its transition signature context with `/id`. A `Signature` is `{key,value}`: canonical unpadded base64url Ed25519 public key (32 bytes) and signature (64 bytes). Binary/source-provenance digests and IDs use lowercase 64-character SHA-256 hex. A body ID is SHA-256 of its signature context with `/id` appended, then NUL and canonical body bytes. Policy IDs exclude the root signatures; candidate IDs exclude manifest signatures, reviews and test statements. Adding a signature does not change the reviewed candidate; changing any manifest field, artifact digest or policy ID does. `testdata/manifest-v1.json` contains the complete canonical manifest bytes and an independently Python-`hashlib`-computed candidate vector: `f7847a88147336b88ae2b24ef93bd17d9db632603172c3219bec76d85eec2d40`. It is a **fixture**, not an authorized release. Tests also reject cross-context signature substitution and noncanonical base64 padding-bit aliases. `TestReviewTransitionWireVector` fixes the complete typed transition body bytes and the independently Python-hashlib-computed transition ID `69293892ac26a468e9722e54859e3fed9e887bdea1efd675ccfaf67226ebd104`. This is also a fixture, not an authorized governance change. ## Policy, delegation and risk A Policy binds product, channel, sequence/previous policy, declared governance phase, issue/expiry, explicit maintainer delegations and three ordered risk rules. Root signatures are verified against only the locally configured roots. Unknown signers are rejected, not counted; repeated signers are invalid. The threshold counts unique configured keys, not peers, organizations, stars or comments. Delegations are embedded in and signed by the complete root-signed Policy. They are not self-issued certificates. Each binds one canonical key, group label, explicit roles (`release`, `review`, `test`), explicit permitted risk classes and validity. Delegations are sorted by key, roles lexicographically, risks in order `documentation`, `normal`, `security`; duplicate entries fail. They may not outlive their parent policy. Every statement requires a currently valid delegate whose role/risk/time scope covers that statement. One valid delegated release signature on the manifest is mandatory, plus the separate review/test rules. There are exactly three ordered rules. Each defines minimum unique reviewers, minimum reviewer groups, locally enforced review seconds, minimum unique test signers and required test kinds. Quorums/windows cannot decrease from a lower to a higher risk class. Required test kinds are sorted and drawn from `platform`, `regression`, `reproducibility`, `security`. No built-in default quorum/window is silently selected: the independently trusted root policy chooses these values. Zero wait is allowed only when that explicit signed policy says so; fixtures use it for some tests. The governance proposal's 24h/72h/7-day values are policy starting points, not a cryptographic guarantee. The risk class is a signed human/local-policy assertion. This library cannot infer whether source changes contain cryptography. The caller's minimum risk floor prevents a bundle from selecting a weaker class than that local floor. Group labels are likewise root-authorized assertions; actual organizational independence, expertise and conflicts of interest need external review. **Policy transitions are opt-in and experimental.** If local `Config` omits `transition_rules` (nil), the initial policy must have sequence 1 and no predecessor, its first valid observation pins its ID, and every later call requires that exact ID and sequence. A release bundle never changes policy. Supplying valid `transition_rules` before `Create` enables the separate transition protocol described below. The optional rules are part of the local trust fingerprint: they cannot be added, removed or changed when opening an existing checkpoint, and omission preserves the pre-transition configuration fingerprint and checkpoint format. No migration is inferred from old state. The local root public-key set and ordinary root threshold remain pinned and unchanged through every policy transition. A transition cannot add, remove or rotate roots. Its configured root threshold must be strictly higher than the ordinary root threshold and no greater than the number of pinned roots. Both the old and new policy require the ordinary configured root quorum; the transition body additionally requires its stronger local root quorum. Root rotation, emergency bypass and recovery are not implemented. `TransitionRules` contains that stronger root threshold, minimum transition approval/group/test quorums and review seconds, required test kinds, and exactly three immutable policy floors ordered by `documentation`, `normal`, `security`. Each floor is a complete policy `Rule`. The active initial policy and every transition policy must meet every floor. Policy rules themselves must be monotone by risk and have 1–64 approvals/test signers, 1–approvals groups, 0–30 days review delay, and 1–4 sorted supported test kinds. A floor checks approvals, groups, delay, test signers and inclusion of every required test kind; policies may choose stricter values. Transition configuration requires at least two approvals, two groups and one test signer, and its sorted required test-kind list must include `security`. Quorum bounds are 64 approvals/test signers and 64 groups; the rules shape and cross-field constraints are checked when trust is opened. A transition bundle contains signed old and next policies, a signed transition body, transition reviews and test evidence. It binds both policy IDs, the exactly next sequence, a domain-separated ID of the exact local rules, and issue/expiry times. The next policy must name the old policy as predecessor; phases may stay equal or advance `founder` → `maintainer` → `distributed`. Both policies and the transition must be current, have matching product/channel, and fit the time bounds: transition lifetime is at most 90 days, its issue time cannot precede the next policy, and its expiry cannot exceed either policy. Each transition signature, review and evidence uses a distinct signature context. Review/evidence statements bind the transition ID and their validity must fit inside both transition and current delegation validity. Only currently valid `review`/`test` delegations with the `security` risk in the **old** policy count. The incoming policy cannot appoint its own transition reviewers. Transition approval/group/test requirements are the maximum of the local transition minima and the old policy's Security rule; required test kinds are their union. Reviews require unique signers and enough distinct group labels; tests require the required kinds and enough distinct test signers. Any authenticated reject review or failed test is a negative result and blocks activation. `ObserveTransition` requires the stronger root quorum and authenticates every supplied statement before updating state, but permits incomplete review/test quorums. It records the first local observation for the exact transition ID; changed signed fields make a new ID and a new window. The effective wait starts at `max(transition issued_at, first local observation)`. `ActivateTransition` requires all quorums, no authenticated negative, and the elapsed local wait; it rechecks current policy generation while holding the process lock. If the checkpoint has no policy yet, transition observation may pin only the fully authenticated sequence-1 predecessor. It never silently skips policy history. Activation changes only the policy ID/sequence. It preserves the accepted release head and sequence, known release negatives and quarantine; it drops positive release and transition observation windows from the old generation, retaining known negative transition records. Releases may advance while a transition waits, but releases under the superseded policy are rejected after activation. The activation result reports policy-rule weakenings and delegation changes; these are informational, not authorization by themselves. It installs no release or artifact. There are at most 16 live transition records. Authenticated negatives are never forgotten to stay within this bound. An overflow positive is rejected; an overflow authenticated negative extends a conservative transition quarantine to its expiry. The stronger-than-a-timestamp-afterward case is also explicit: a valid reject or failed test learned after the last transition was activated marks that activation permanently rejected. All future release authorization and policy transitions for this checkpoint then fail closed. **There is no safe recovery path for this terminal quarantine.** Do not delete/reset state as a workaround; a privileged rollback of the entire trusted filesystem is outside the verifier's protection. The verifier does not prove it has seen every review, nor revalidate an arbitrary superseded governance chain. ## Manifest, reviews and evidence A manifest binds the policy ID, product/channel, release sequence/predecessor, version label, source revision claim, exact OS/architecture, sorted compatible protocol list, artifact SHA-256 and exact byte size, provenance-report SHA-256, risk class and issue/expiry. The local platform/protocol must match. Version labels are display data; ordering uses the monotonic sequence and predecessor ID. The provenance/source fields authenticate claims and references; this package does not fetch a report, rebuild source or prove supply-chain provenance. Every review signs the candidate ID, `approve` or `reject`, and its issue/expiry. Every test statement signs that same ID, a test kind, report SHA-256, pass/fail and issue/expiry. Tests are **assertions by authorized signers**, not tests executed by the verifier. Review and evidence lifetimes must fit the manifest and their delegation. Review keys are unique; evidence is unique per key plus test kind. One key cannot become multiple reviewers or test signers through encoding aliases or repeated signatures. Multiple test kinds signed by one key remain one signer. The wait starts at `max(manifest.issued_at, first local observation)` for that exact candidate. Signer-controlled backdating cannot move the local observation backwards. Missing signatures may arrive later without restarting the same candidate's window. Changed manifest bytes produce a new ID and new observation window; old reviews cannot authorize it. ## Known negative results, state and recovery A valid authorized reject or failed test is recorded monotonically for that candidate until its signed manifest expires. Later omission, a replacement positive statement, a second open verifier or restart cannot remove this local negative. This also covers a rejection observed before its predecessor is accepted, and a rejection observed after prior acceptance. Forged negatives do not change state. A different valid candidate can be evaluated normally. There are at most 64 live pending/negative candidate records. Positive overflow is denied. A new authenticated negative beyond this capacity cannot simply be forgotten: it conservatively quarantines **all authorization** until that candidate's expiry, using one bounded timestamp. No live negative is evicted to make room. This favors fail-closed behavior over availability. Expired records can be pruned because the corresponding manifest is no longer valid; a persisted monotonic observation time rejects subsequent local clock rollback. The stored negative is a local deny decision, not an independently signed audit container. This is protection for **observed** negatives, not a completeness proof for unseen reviews held by other parties. `release-checkpoint.json` is bounded to 64 KiB and uses `internal/state`'s pinned regular-file access, Linux/Windows process lock, atomic replacement, file sync and Linux directory sync. State records local trust fingerprint, last observed time, pinned policy ID, accepted release sequence/ID and bounded candidate state. Views/updates reload it; instances never authorize from stale cached checkpoints. A new release must be exactly sequence N+1 and refer to accepted ID N. The same accepted candidate is idempotently re-verifiable only while its policy, statements and artifact are still valid and it has no known negative. Conflicting same-sequence IDs, gaps, rollback and unexpected policy changes are rejected. An artifact mismatch/read error never advances accepted state. Verification first checks bounded metadata and hashes at most declared size plus one byte; then it locks and reloads state again before committing. A competing writer can therefore make an otherwise valid artifact stale. On uncertain state durability, no Authorization is returned; reopening/retrying the same candidate can reconcile an already committed checkpoint. Missing/corrupt state is never implicitly reset. The local directory, administrator and system clock are trust assumptions. These checks do not detect a privileged actor rolling back the entire filesystem and clock together. Expiry bounds freeze attacks under a trustworthy clock; an offline verifier cannot discover an unknown newer release. Windows directory-powerloss, network filesystems, actual power interruption and external security audit remain unverified. No old/new writer mixing or lockfile deletion while running. ## Bounds and test evidence Maximum bundle: 1 MiB. Maximum roots: 16; delegates: 64; manifest signatures: 64; reviews: 64; test statements: 128. Strings and arrays have explicit per-field limits. Policy lifetime is at most 366 days, manifest lifetime at most 90 days, review delay at most 30 days. Artifacts are capped at 1 GiB **and** the caller's smaller ceiling, processed through a 64 KiB streaming buffer. No whole artifact allocation or unbounded reader is used. These are experiment resource limits, not production throughput claims. Transition bundles share the 1 MiB canonical bundle ceiling and allow at most 16 signatures on each old/new policy and transition, 64 reviews, 128 evidence statements, and 16 live transition checkpoint records. Transition lifetime is at most 90 days. TransitionRules require 2–16 roots in their stronger threshold, 2–64 approvals, 2–approvals groups, 1–64 test signers, a 0–30 day wait, one to four sorted supported test kinds including `security`, and exactly three valid policy floors. Each Policy Rule has 1–64 approvals and test signers, 1–approvals groups, a 0–30 day wait and one to four sorted test kinds. The three risk rules must be monotone from documentation through security. The configured transition root threshold must exceed the ordinary threshold and not exceed the pinned root count. These are input bounds, not recommended production settings. Tests cover canonical bytes/vector and context separation, unknown/duplicate JSON/base64 aliases, key consistency, trust/target pinning, root/review/group/test quorums, role/risk/expiry, changed candidates, local wait/restart, 1000 untrusted peer keys, artifact tamper/short/trailing/read failure/byte ceilings, two concurrent instances, stale sequence/clock/policy rejection, corruption/deletion, uncertain writes, negative omission/restart/prior/future acceptance and bounded overflow. The independent root review tests specifically reproduce and guard negative omission and forged-negative poisoning. Test keys/artifacts are fixtures only; none are installed, executed, published or represented as founder authorization. A production release still needs separately provisioned root fingerprints, reviewed transition/rotation/revocation/recovery and emergency rules, independent security review, reproducible build/provenance/SBOM integration and real platform runs. There is deliberately no installer or auto-update execution path here.