feat: converge first-adopter runtime configuration and package verification - #1632
Conversation
A missing socket, a refused request, a key version Transit has not created or has retired, and a public key that is not the governed one all reported the same generic signing failure, so an operator could not tell a proxy to restart from a keyVersion to change. TransitSigner::initialize now returns a typed, value-free TransitInitializationError, and Evidence reports the cause after "runtime signing initialization failed:" in check and serve. The prefix is unchanged, so evidencectl still classifies the refusal as a dependency failure. BREAKING: registry-platform-crypto TransitSigner::initialize returns TransitInitializationError instead of SigningError. Refs #1459 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ts audit lock `evidencectl doctor --runtime-config` could not pre-check a candidate while the instance it replaces was serving: the dependency check opens the audit destination, and the running writer holds its lock, so the check always refused. `evidence check --require-runtime-dependencies --without-audit-lock` and `evidencectl doctor --without-audit-lock` now prove every runtime dependency the plain form proves except the lock itself. The audit destination is checked with the platform `check_writable` preflight, which validates the directory, any existing active file and lock companion, and a complete final entry the way a writer would, without taking the lock, after the hash key is checked as startup checks it. The lock-free JSON report states that a second writer is not detected. The plain form still refuses a held lock, and its refusal now names the flag. Startup and the lock-free check share one assembly sequence and differ only in the audit step, so the two cannot drift apart. Refs #1491 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…h a named profile An issuer that serves its key set under a private certificate authority could only be reached by pointing the whole process at another trust store, which also widened every source connection. The bundle's authentication block may now name a tlsTrustProfile, bound in runtime.yaml exactly like a source profile. The CA is trusted beside the system roots for the jwksUri connection alone; hostname verification stays on and a local HTTP issuer cannot name a profile. The platform gains ValidatedFetchUrl::immediate_get_with_additional_roots and JwksFetcher::new_trusting_additional_roots to carry the roots. Refs #1455 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… not declare A registry field rename passed every authoring gate: the source update installed a fact schema without the old name, the candidate built, and every request then failed at evaluation with a missing-fact refusal. The operator learned from production traffic rather than from review. The authoring library now reads the parsed answer function, without running it, and names each literal fact read on its first parameter that the question's source does not declare. evidencectl applies the check wherever it reads a project, so check, fixtures run, build, package, source diff, and source update refuse the rename before anything is installed. The declared set is an inline operation's facts or the properties of a referenced source's closed fact schema; an open schema, a computed key, and a read outside answer are left to the fixtures. The check is source-product neutral. BREAKING: a project whose derivation reads an undeclared fact is now refused as evidence.authoring.derivation-fact-undeclared. Refs #1423 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`bregctl test --baseline-runtime-config` now rebuilds the predecessor schema from the verified active package's signed sources on the disposable test database, requires it to reproduce the recorded predecessor fingerprint, and runs the successor migration in apply order: pre-assertions, compiler statements, reviewed steps, deferred constraints and views, runtime ACL reconciliation, and post-assertions. It requires the candidate schema fingerprint and rolls everything back in one transaction. `package` needs the test receipt, so a plan the rehearsal refuses can no longer be signed and fail only at `apply`. The rehearsal runs over empty tables: it proves SQL validity, ordering, and reachability of the target schema, not data-dependent failures. Field-encryption backfill steps are skipped and reviewed fixture files are not loaded. The operator docs now describe the reviewed-migration file layout, the backup binding `apply` checks, and what `test` does and does not rehearse. Security: a PostgreSQL error message can quote row values. The rehearsal failure reports carry only the SQLSTATE, its class, and the schema, table, column, and constraint names, enforced where `postgres/rehearsal.rs` maps the database error. The negative test `real_postgres_rehearsal_refuses_a_reviewed_plan_activation_would_refuse` plants a canary in a failing step and asserts it never reaches the report. Refs #1424 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`history rebaseline` refused any registry holding more than 1,000 live rows, so a larger registry could never regain snapshot coverage after an erasure. The verification now reads each entity's live rows, their journal heads, and the retained heads in the same record-identifier range in pages of 1,000 inside the same transaction, proves each entity holds exactly one retained journal head per live row, and keeps memory and every per-entity statement flat as the registry grows. The run is a read and write outage for every entity, as it was under the old limit, and it now lasts as long as the registry is large. The migration role lifts forced row security on every entity table before the first page with ALTER TABLE, which holds each table in ACCESS EXCLUSIVE mode until the transaction ends, so API reads wait as well as writes. The migration role deliberately holds no BYPASSRLS authority, which is the only way to read the rows without that lock, so the operator docs state the outage and give a sizing and maintenance-window rule instead: time the command against a restored copy, allow at least twice that, drain or announce API traffic, and size the statement timeout for one scan of the revision journal (the unindexed-head check) and the lock timeout for the in-flight requests at the start. BREAKING: `HistoryRebaselineError::LiveRowBudgetExceeded` and `MAX_REBASELINE_LIVE_ROWS` are removed, and bregctl no longer emits `history.rebaseline.live_rows.budget_exceeded` or `field_encryption.erase_history.rebaseline.live_rows_budget_exceeded`. Security: the threat is a baseline that vouches for a live row its journal does not reproduce, or for a journal head whose live row is gone. `verify_every_live_row_matches_its_journal_head` in `history_migration.rs` compares every page's live rows with their heads and the retained heads in the page's key range, then refuses any head past the last live row, before the baseline commit is written. The negative tests `rebaseline_refuses_a_mismatch_on_a_later_page`, `rebaseline_refuses_a_journal_head_with_no_live_row`, `rebaseline_refuses_a_journal_head_before_the_first_live_row` and `rebaseline_refuses_a_journal_head_past_the_last_live_row` prove the refusals past the first page and at both ends of the key range. Row security is not weakened: the force is lifted only inside the migration transaction and restored before it commits. Refs #1428 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A reviewed `chunked_backfill` step changed live rows without appending a history revision, so snapshot and as-of reads kept serving the values the backfill had replaced. Each chunk now captures the rows it selects, runs its UPDATE, requires the changed row count to equal the selected count, and appends one revision per changed row in one history commit, all inside the chunk's own transaction. The field-encryption backfill already journaled its chunks this way; both now share the page capture. `bregctl test` classifies every step `apply` journals with the same check, so a step the journal would refuse fails the rehearsal as `migration.rehearsal.history_step_refused` instead of failing `apply`. BREAKING: `chunkSize` for a reviewed chunked backfill is capped at 1,000 (it was 10,000), the history commit's member budget. Its SQL must be one UPDATE of the declared entity that writes no record metadata and uses no refused statement word outside comments and plain string literals. A plan outside those limits that `test` and `package` accepted before is now refused. The lexical first check that journaled steps share sets aside comments and plain string literals, accepts a line break after UPDATE, and matches record metadata columns and statement words as whole words, so a leading licence header or a member named `created_at_source` no longer refuses a step. It reads a dollar-quoted body, a literal holding a backslash, or an unterminated comment or literal as written, so a statement word inside one still refuses the step: the scan never guesses where such a construct ends. The parsed-statement validator and the journal's metadata check stay behind it. Security: the threat is retained history that diverges from the live rows a reviewed backfill rewrote, so an as-of read or export serves values the registry no longer holds and the journal cannot account for the change. `execute_reviewed_chunk` in `postgres/interlock.rs` journals every chunk through `prepare_reviewed_page_capture` and `finish_reviewed_page_update`, and `check_reviewed_history_step` in `history_migration.rs` refuses a step the journal cannot record, both at apply and in the rehearsal. The negative tests `reviewed_chunked_backfill_refuses_a_chunk_size_beyond_the_commit_budget`, `chunked_backfill_refuses_the_statement_shapes_the_journal_cannot_hold`, the `rank-dollar-quoted` case of `real_postgres_rehearsal_refuses_a_reviewed_plan_activation_would_refuse`, and the history assertions in `real_postgres_backfill_and_destructive_recovery_are_bounded_resumable_and_activation_closed` cover the refusals and the per-chunk revisions. Refs #1480 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Removing a field or an entity from a registry package drops the live column or table, but every revision snapshot recorded before the change still holds the removed values, and so does every database backup. An operator reviewing the diff could read the removal as erasure. `bregctl diff` now reports each removed field or entity as the finding `diff.history.removed_values_retained`, against the compiled diff, with a message that names `bregctl history erase` as the only command that removes the values, whole revisions of one record at a time. The change and retention guides say the same. Security: the threat is an operator treating a package removal as data minimization or erasure while the values stay readable in retained history and backups. `removed_value_findings` in `crates/registry-bregctl/src/lib.rs` raises the finding for every `field_removed` and `entity_removed` change, and `a_removed_field_is_reported_as_retained_in_history_not_erased` proves the finding for a removed field and its absence for an unchanged candidate. The diff is offline, so it does not count the affected snapshots, and there is still no command that removes one field from history. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Raising a `text` field's `maxLength` was a `field_type_changed` change that needed a reviewed migration, although every stored value already meets the higher limit. It is now the compatible additive change `field_length_widened`: the compiler replaces the column's inline length check under the same lock and row validation a vocabulary code addition uses, keeping the constraint name a fresh install gives it. A revision recorded under the lower limit stays readable, and an action that sets or requires the entity reports `action_target_fields_widened` and stays additive instead of becoming a reviewed `action_changed`. A `string` field's `maxLength` is its `varchar` column type, and a column type change needs the dependent views and row policies rebuilt, so raising it stays a reviewed `field_type_changed`. Lowering any limit stays destructive. Security: the threat is a live check swap that quietly admits values the successor does not declare, or drops a second check on the column. `replace_inline_field_check_statement` in `generated_ddl.rs` replaces only the single unnamed check over the column and now also excludes the field pattern's named check, and `FieldTypeSource::widens_text_length_of` in `contract.rs` admits only a `text` to `text` change with a higher limit. `text_length_widening_replaces_the_length_check_and_keeps_existing_rows` proves the pattern still refuses and the higher limit still bounds, and `text_length_widening_is_additive_and_replaces_the_length_check` and `a_raised_text_limit_on_a_targeted_entity_keeps_the_action_contracts` prove that narrowing and a `string` limit stay reviewed. Refs #1427 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The successor rehearsal in `bregctl test` and the rebaseline without a live-row limit both change what an operator sees, and both remove or tighten something a pipeline may rely on, so each gets a BREAKING entry that links the operator guide describing it. Refs #1424 Refs #1428 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
When PostgreSQL refused a compiler statement or a reviewed step after `apply` began maintenance, the report said only `apply.migration.failed`, so an operator had nothing to act on before reaching for `migration reconcile`. A server refusal now keeps its SQLSTATE and the table, column, and constraint names PostgreSQL reported, and `apply` reports it as `apply.migration.statement_failed` with that SQLSTATE and its class. A lost connection still reports `apply.migration.failed`, and the target stays pinned either way. `PostgresFailure` moves out of the rehearsal module so `apply` and the rehearsal share one value-free error shape; it is now exported from `registry_breg::postgres` under every feature set. A reviewed chunk's refusal is classified like a transactional step's, so a chunk that breaks a field pattern names the field. BREAKING: a statement refusal after maintenance began reports `apply.migration.statement_failed`, not `apply.migration.failed`, and `registry-breg` reports it as the new `MigrationError::StatementFailed` variant. Automation that matches the old code or matches `MigrationError` exhaustively must add the new case. Security: the threat is a PostgreSQL error message, detail, or hint that echoes a stored or computed row value (a failed cast repeats its input, a unique violation repeats the key) and would reach the apply report, logs, or CI output. `PostgresFailure::from_error` in `postgres/failure.rs` retains only the SQLSTATE and object names, and `PostgresKernelError::from_statement_error` in `postgres/mod.rs` is the only path that carries a refusal to `MigrationError::StatementFailed`, whose Display is built from those fields alone. The negative test `refused_step_reports_its_sqlstate` in `tests/postgres_migration.rs` applies a step whose constant PostgreSQL refuses and asserts the refusal names `SQLSTATE 22P02 (data exception)` and carries neither the constant nor the managed schema name, and `apply_reports_a_refused_statement_with_its_sqlstate_and_objects` pins the report. Refs #1424 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A lower `minLength` on a `string` field under the same `maxLength` only relaxes a column check, but the planner classified it as `field_type_changed` and demanded a reviewed migration. It is now `field_length_widened`, applied live by the same constraint swap a raised `text` `maxLength` uses: the successor replaces the single inline check over the column with the lower minimum, or drops it when the minimum falls to 0, since a fresh install of the candidate declares none. The catalog then matches a fresh install. Revisions recorded under the higher minimum stay readable, and an action whose only change is the lower minimum stays additive as `action_target_fields_widened`. `text` has no `minLength`, so the live lowering applies to `string`. Changing a `string` field's `maxLength` (its `varchar` column type) and widening a `decimal` stay reviewed migrations: both alter a column type that the generated views and row policies depend on, and the reviewed path is where that dependency is handled. Security: the threat is a live check swap that loosens a different constraint than the one the change relaxes, or leaves the catalog diverged from a fresh install so that the fingerprint no longer proves the schema. `FieldTypeSource::lowers_string_min_length_of` in `contract.rs` accepts only the same `maxLength` and a strictly lower minimum, and `replace_inline_field_check_statement` in `generated_ddl.rs` selects exactly one single-column check with `INTO STRICT`, excluding compiler-named, temporal-order and pattern constraints, and fails closed otherwise. Encrypted fields get no swap. The negative cases in `string_minimum_lowering_is_additive_and_replaces_or_drops_the_length_check` (a raised minimum and a changed maximum stay destructive), `a_lowered_string_minimum_keeps_recorded_values_readable`, `a_lowered_string_minimum_on_a_targeted_entity_keeps_the_action_contracts`, and the refusals and fingerprint equality in `string_minimum_lowering_replaces_or_drops_the_length_check_and_keeps_existing_rows` cover it. Refs #1427 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ck reconciliation A reconciliation that failed every pass left /ready green and doctor reported a generic dependency failure, so an operator could not tell a wedged source from a missing secret. Readiness now fails after five consecutive failed passes for a source and recovers on the next successful pass. Reconciliation and synchronization continue past a failing subject. doctor names the check that refused (casework.doctor.check-failed, doctor:/checks/<check>) with a value-free cause, including the audit destination check, reports per-source reconciliation health, and prints the package digest. An unmigrated database is named as a schema that is not current. BREAKING: caseworkctl JSON reports move to v1alpha2; migration 18 must be applied before serving. Refs #1448 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…rop on the audit writer migration Two branches each adding migration 17 would collide silently; the test fails when a version is missing, duplicated, or when AUDIT_OUTBOX_DROP_VERSION stops naming 0017_audit_writer.sql. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… work A package that removes a queue, profile or source, edits a pinned review kind without a new version, or changes what a source read discloses used to activate silently, leaving in-flight reviews and open work items that no caller could finish. The runtime now compares the pinned work with the package before any activation write and refuses by name, with counts, unless package.acknowledgeStrandedWork names that exact package digest. caseworkctl doctor reports the same comparison as pinnedWork and fails the pinnedWork check by name. BREAKING: casework serve refuses such a package; DoctorReport gains pinnedWork. Refs #1416 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Casework and the Base Registry Engine ship in lock-step, but nothing told an operator when a rolling upgrade left them on different releases: the adapter kept talking and failures surfaced later as opaque source errors. BReg now reports its release on GET /v1/registry in the Registry-Engine-Version header, the BReg client exposes it on the response metadata, and the Casework adapter treats a different release as the source being unavailable, naming both versions. caseworkctl doctor reports the mismatch with the upgrade step to take. BREAKING: a Casework deployment whose BReg runs another release is now refused as unavailable until both run the same release. Refs #1419 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
An operator had no way to scrape the runtime's health. The runtime gains an optional metricsListener: a second, operator-private socket serving /metrics and /version, refused unless it is loopback or private and distinct from the API listener. Absent by default, so no new socket opens. Refs #980 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…irst A review authority restored from an older backup or replaced answers an accepted review's result lookup with an empty 404. BReg left such a review looking healthy, let it compete with live reviews for every poll batch, and offered no supported way to submit it again or stop waiting. The lookup now records result-unknown-to-authority, the poller serves a review with a pending webhook completion first and an unknown one last, and a pending completion makes its review due at once. bregctl review-recovery resubmit submits the exact retained request again under its original idempotency key, and close stops waiting with operator-closed. Both run behind the verified operator boundary request retention uses, refuse by a closed reason that names the state and code, and write an audit request entry before the transaction and its response after the commit, naming the request only by its keyed reference. Refs #1454 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A session the server ends mid-step (a terminated backend, a shutdown, a failover, an idle-in-transaction timeout) arrives as a FATAL database error, and it was reported as a refused statement with ReconcileFailedMigration guidance. PostgresKernelError::from_statement_error now maps SQLSTATE class 08, 57P01 to 57P05, and 25P03 to Connection, so apply reports apply.migration.failed as it does for a client-side loss. The verified-DDL path now classifies its errors through the same function, so a statement timeout is Statement(57014) on both DDL paths. BREAKING: the changelog entry now names the PostgresKernelError::Statement variant, which breaks an exhaustive match on that enum as well as on MigrationError. Security note: threat is an operator acting on the wrong recovery guidance after a server-ended session; both classifications keep the target pinned, so this is a reporting fix, not a safety change. Enforcement is sqlstate_ends_the_session in crates/registry-breg/src/postgres/mod.rs. Negative tests are a_server_ended_session_is_a_connection_failure_not_a_refused_statement and failed_resume_and_ddl_timeout_are_fail_closed_on_real_postgres. Refs #1424 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The lexical check on a journaled reviewed step masked a line comment through the next line feed only, while PostgreSQL also ends it at a carriage return, so an assignment after `--\r` was hidden from the scan. It also read a Unicode-escape identifier such as U&"created\005Fat" as words other than the column it decodes to. The mask now ends a line comment at either newline character, and any U& identifier or string and the UESCAPE keyword are refused anywhere in the statement. The runtime metadata-equality check in journal_captured_changes now also compares created_at and updated_at, captured as text in the same transaction, so a step that changes either is refused even if a spelling escaped both the lexical and the syntax-tree checks. Docs: the reviewed-migration page says metadata may not be named anywhere in the statement, even in a read; the retention page and the erase-history lifecycle state that a rebaseline blocks reads as well as writes, and that a paged verification statement is bounded by one page of records and their retained revisions rather than a fixed cost. Security note: threat is a reviewed step that rewrites record metadata so the history journal no longer matches the live row. Enforcement is reviewed_update_words and mask_comments_and_literals in crates/registry-breg/src/history_migration.rs, with CapturedEntityRow::keeps_record_metadata_of at journal time. Negative tests are chunked_backfill_refuses_the_statement_shapes_the_journal_cannot_hold (carriage-return comment, both U& spellings) and a_reviewed_step_that_changes_any_record_metadata_is_detected. Refs #1480 Refs #1428 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Every runtime reads its runtime.yaml under the same file, parsing, and substitution rules, and spells secret providers, database references, the JWKS source, the package pin, and the listener the same way. The loader refuses unsafe files, removed keys with their replacement, a wrong envelope, and substitution inside secret references; the blocks carry a canonical JSON Schema the conformance gate compares products against. Ed25519 and ES256 key generation join ES384 so adopter tooling can mint the key types the runtimes verify. BREAKING: reject_deprecated_config_fields is removed; runtimes declare removed keys on the loader. Refs #1408 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… loader
Scheduling now reads its runtime configuration through the shared
registry-platform-config loader and blocks, so its envelope, secret
providers, database, package, listener, and JWKS source share one
implementation and one schema with the rest of the stack.
BREAKING CHANGE: authentication.oidc.jwksUri is removed and refused with a
diagnostic naming jwksSource kind: uri; listener.bind is required; the
runtime configuration and every configured path are refused when they pass
through a symbolic link; an environment expression in the authored policy,
records, or fixtures is refused with the field that holds it.
Adds ${VAR} substitution in runtime.yaml string values outside *Ref fields
and the optional package.expectedDigest pin checked against the verified
package's policy digest.
Refs #1408
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Registry Render loads its runtime file with the platform loader and the shared package, listener, and secret provider blocks, so the file follows the same envelope, size, ownership, and substitution rules as the other runtimes. BREAKING CHANGE: runtime.yaml must declare apiVersion registry.registrystack.org/render-runtime/v1alpha1 and kind RenderRuntimeConfig. server.bind is listener.bind and is required, server.shutdownGraceSeconds is listener.shutdownGraceSeconds, bundle.path is package.root, audit.path must be absolute, and secret providers are declared under secretProviders with an absolute file root. Removed keys, among them audit.directory, audit.integrityKeyRef, and audit.maxSegmentBytes, are refused with their replacement named. serve, healthcheck, and check require --runtime-config FILE; the default runtime path and the REGISTRY_RENDER_RUNTIME variable are gone. package.expectedDigest, when set, must equal the sealed bundle hash reported by /health. Refs #1408 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A socket address parser accepts any number of leading zeroes in the port, so an unbounded listener.bind text is refused before it is parsed. The shared schema carries the same maxLength, and the Scheduling runtime schema that embeds the block is regenerated. Refs #1408 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…loader The Discovery runtime loads its runtime file with the platform loader and the shared listener block, replacing its own YAML reader, so the file follows the same envelope, path, size, and substitution rules as the other runtimes. A loader refusal now names the file and field in the startup error instead of reporting an opaque invalid configuration. BREAKING CHANGE: runtime.yaml declares apiVersion registry.registrystack.org/discovery-runtime/v1alpha1 and kind DiscoveryRuntimeConfig instead of schemaVersion, and listener.address is listener.bind. Both removed keys are refused with their replacement named. The discovery binary takes --runtime-config FILE instead of --runtime; the path must be absolute and free of symbolic links. The container image passes the new flag in its default command. Refs #1408 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A runtime that renders its effective configuration, as Relay's authoring tooling does, needs the blocks to write back the key names they were read from. Optional fields are omitted rather than written as null, so the generated schemas no longer claim a null default for them. Refs #1408 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Relay now reads its deployment binding through the registry-platform-config loader and shared blocks, so its envelope, secret providers, package, listener, and JWKS source share one implementation and one refusal vocabulary with the rest of the stack. Secrets resolve only through the providers the file declares, and a file secret no longer resolves beside whatever directory the runtime file happens to sit in. BREAKING CHANGE: runtime.yaml opens with apiVersion registry.registrystack.org/relay-runtime/v1alpha1 and kind RelayRuntimeConfig. server.bind is listener.bind, packagePath is an absolute package.root with an optional package.expectedDigest pin checked against the package revision, and authentication.issuer is authentication.oidc with issuer and a jwksSource of kind discovery or uri; each removed key is refused with its replacement. The audit block keeps the shared destination shape; audit.sink and audit.integrityKeyRef are refused with a diagnostic naming audit.path. secret:file/ resolves under secretProviders.file.root and secret:env/ needs secretProviders.environment. relay check and relay serve take a required absolute --runtime-config; the --runtime flag, the RELAY_RUNTIME variable, and the default path are gone, and the container image passes the flag in its default command. The site changelog also gains the Scheduling entry and uses the file's BREAKING marker for the Discovery and Render entries. Refs #1408 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Environment substitution belongs to runtime.yaml. An authored file that
carries a ${VAR} expression would read as a literal and hide the
operator's intent, so a Relay registry.yaml and a Render bundle
manifest.yaml now refuse one with the field that holds it. Relay reports
contract.environment_expression; Render reports a manifest problem.
BREAKING CHANGE: a registry.yaml or bundle manifest.yaml holding a
${...} expression no longer loads.
Refs #1408
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Replace bundle and runtime revisions with the verified package digest while preserving package-derived requirement revisions. Package production outputs, dev staging, committed fixtures, frozen contracts, and operator guidance now use the shared deterministic envelope. Refs #1409 Refs #1352 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Replace manifest sealing with deterministic Registry Stack packages, bind verified captured bytes through rendering, and update Render contracts and examples. Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Migrate discoveryctl and runtime startup to the shared package envelope, preserve exact verified index bytes, and update the maintained operator journey. Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Require all seven adopters to prove the common package pin refusal, migrate maintained Evidence and Discovery references, and keep local BReg-Evidence development on generated verified packages. Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Remove the obsolete runtime digest contract and direct maintained checks through an editable project, deployment target, and installed package. Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Republish positive test packages after authored mutations, keep deliberate tamper cases negative, and align migrated package CLI expectations. Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ma head Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…be read back Closes #1621 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…nces An import run's input digest is computed by the client over the file it reads and recorded on the run; the server never receives the file and does not recompute it. The guides, the API reference, the ingestion-run contract, the security matrix, and `import-authority open --input-sha256` help now say so, instead of implying the pin binds what the chunks write. The item volume stays the bound the server enforces. The import guide also states that import is create only, so re-running lines that already committed creates their records again. Refs #1420 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
AGENTS.md requires tracked review notes for changes to authorization, audit integrity, and release provenance. The notes cover import authorities, the instance claim, audit retention, review recovery, and package building and byte binding: the threat, the enforcement point and defaults, the negative tests, and the residuals each change accepts, including client-announced import digests, create-only re-runs, and physical restores the instance claim does not detect. Refs #1420 Refs #1454 Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…and runtime config Review the catalog change against the clap definitions: the --runtime-config flag across breg, evidence, relay, and registry-render; the package --revision and --dry-run options; bregctl import-authority, instance-claim, and review-recovery; registry-render package replacing seal; evidencectl build removed; and --without-audit-lock on evidence check and evidencectl doctor. Refs #1409 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
ce57942 to
4945694
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4945694b7a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| finish_reviewed_page_update(&transaction, registry, target_package_revision, capture) | ||
| .await | ||
| .map_err(|_| PostgresKernelError::RegistryUnavailable)?; |
There was a problem hiding this comment.
Verify the journaled rows match the executed chunk
When reviewed chunk SQL accidentally updates a different but equally sized set of records from the UUIDs in $1, affected == selected passes, while finish_reviewed_page_update rereads only the originally captured IDs and may journal fewer or zero changes. Its returned change count is discarded, so the transaction then commits the unjournaled updates and advances the checkpoint, corrupting revision history. Compare the journaled count with affected and refuse any mismatch.
AGENTS.md reference: products/breg/AGENTS.md:L10-L15
Useful? React with 👍 / 👎.
| transaction | ||
| .commit() | ||
| .await | ||
| .map_err(|_| ImportAuthorityError::Unavailable)?; | ||
| append_transitions(&self.audit, pending).await?; |
There was a problem hiding this comment.
Make authority transitions and audit records recoverable
If the audit writer fails after the database commit—for example because the audit volume fills—the command returns unavailable but the authority transition remains committed. Retrying open now gets AlreadyOpen, while retrying close sees a terminal row, and neither path recreates the missing transition record; partial failure while appending several records has the same permanent gap. Persist an audit outbox with the transition or otherwise make the committed record replayable.
AGENTS.md reference: products/breg/AGENTS.md:L10-L13
Useful? React with 👍 / 👎.
| root: &Path, | ||
| context: &PredecessorPackageContext<'_>, | ||
| ) -> Result<VerifiedPredecessorPackage> { | ||
| let shared = verify_shared_package(root)?; | ||
| load_predecessor_package_with_verified_envelope(root, context, &shared) |
There was a problem hiding this comment.
Accept the previous release's package during upgrade
An existing v0.34.0 registry's active package predates SHA256SUMS, but predecessor loading now unconditionally runs the new shared-envelope verifier. Consequently bregctl test/apply cannot load the active predecessor and the required forward upgrade rehearsal fails before any migration can run. Add a bounded compatibility path for the immediately previous signed package format, or an executable conversion step that works before successor apply.
AGENTS.md reference: products/breg/AGENTS.md:L10-L15
Useful? React with 👍 / 👎.
| let conflicts = crate::stranded_pinned_work(store, project, &adapters).await?; | ||
| match crate::pinned_work_verdict(&conflicts, package_digest, acknowledged) { |
There was a problem hiding this comment.
Serialize the pinned-work check with policy activation
During a rolling deployment, the old Casework process can admit a review or create an open work item after this standalone inventory read but before the new process begins serving. The candidate can therefore pass the check, remove a queue, profile, source, or pinned policy dependency needed by that newly admitted work, and then activate with the work hidden or orphaned. Coordinate the inventory check and activation with an admission fence or database lock that every work-creation path also observes.
Useful? React with 👍 / 👎.
| side.run("evidencectl", "build", "--project", str(self.project), "--target", | ||
| str(self.target), "--output", str(upgraded)) |
There was a problem hiding this comment.
Invoke the replacement Evidence packaging command
When the predecessor target needs the grammar migration, this path invokes the current evidencectl build, but the command added in this commit is a hidden retired spelling that unconditionally returns evidence.build.retired and instructs callers to use package. Thus the required Evidence upgrade rehearsal always aborts before the successor runtime is checked or served. Invoke evidencectl package here instead.
Useful? React with 👍 / 👎.
| side.run("evidencectl", "build", "--project", str(self.project), "--target", | ||
| str(self.target), "--output", str(upgraded)) | ||
| runtime = migrate_evidence_runtime(load_yaml(self.runtime)) | ||
| runtime["package"]["root"] = str(upgraded / "bundle") |
There was a problem hiding this comment.
Point the upgraded runtime at the package root
For the current evidencectl package format, SHA256SUMS, evidence.yaml, and the governed artifacts are written directly under the output directory, with no bundle child. Pointing the operative runtime at candidate-upgraded/bundle therefore makes the subsequent evidence check refuse the package as missing, so the rehearsal still cannot exercise the upgraded service even after using the correct packaging command. Set package.root to upgraded itself.
Useful? React with 👍 / 👎.
Supersedes #1607.
Summary
This PR carries the first two config convergence work packages and the
first-adopter lifecycle fixes, rebased onto the audit simplification that
landed in #1560:
runtime.yamlthrough one loader inregistry-platform-config. The loadershares blocks for listeners, secrets, the audit key, and the OIDC issuer
and clients. A conformance gate proves each product refuses malformed
configuration in the shared shape.
Evidence, Relay, Scheduling, Render, and Discovery all produce and verify
one package envelope. The envelope is a
SHA256SUMSfile listing everyother file, plus an optional
REVISION, and the package digest is thesha256:ofSHA256SUMS. Consumers are bound to the bytes thatverification checked.
listed per product below.
The platform loader and the runtime adoptions cannot land separately without
a broken intermediate state, which is why one PR spans
crates/,products/,docs/site/, andrelease/. Each commit stays scoped to oneproduct.
Out of scope, and not claimed here:
Per product
Platform
Runtime configuration.
registry-platform-configadds:listener.bindbounded to 128 characters, secret providers, the audithashKeyRefblock, the OIDC issuer, OIDC client admission, and static JWKS.Loader refusals. The loader refuses:
jwksSource;Refusals withhold configured text.
Conformance gate.
release/scripts/check-config-conformance.pycovers seven products.Package writer and verifier.
package.rootinstead of its directory.package.expectedDigestpins the digest.Hooks. A lease commit that cannot be read back is recorded as an unknown disposition rather than a retryable one.
Base Registry Engine
bregandbregctlread--runtime-configthrough the shared loader.
bregctl packagewrites the shared envelope and takes--revision. Startup,apply, and operator tooling verify the envelopebefore any database authority is used, and the active and predecessor
package loads are bound to the verified bytes. Package signing is
unchanged.
importoperation, which change controldoes not count as a direct write, and operator-opened import authorities:
bregctl import-authority open|close|close-expired|list. An authoritybounds a load by window, item volume, and package revision. Pinned input
digests are labels the client announces, not server-side verification; the
docs and
--input-sha256help say so.an operator runs
bregctl instance-claim adopt --acknowledge-original-retired. An existing registry adopts once after itsfirst apply on this release.
bregctl review-recovery resubmit|closehandles areview its authority lost, and the result poller orders live reviews first.
bregctl test --baseline-runtime-configrehearses successor migrations.maxLengthcan be raised, andminLengthlowered, on live data.Registry Casework
fails on a stuck or failing reconciliation. The health state is migration
0018_source_reconciliation_health.sql, which follows refactor(audit)!: replace chained journals with shared JSONL streams #1560's0017_audit_writer.sql.metricsListenerserves operator metrics,and
/versionreports the package digest. Metrics scrapes and deaddatabase connections are bounded.
Named
allowedClientsare required behind an operator-controlled TLSterminator.
files to package identity, and validates consumers from one package load.
Evidence
evidence check --without-audit-lockandevidencectl doctor --without-audit-lockcheck a candidate beside therunning writer without taking its single-writer audit lock.
through a named
tlsTrustProfile.refused at authoring time.
--runtime-configthrough theshared loader and serves the shared package format.
evidencectl packagereplaces
evidencectl build.Relay, Scheduling, Render, Discovery
--runtime-configthrough the shared loader.relayctl packagegains--dry-runand--revision.schedulingctl packagenow writes a verified package directory (--output,--dry-run,--revision).--runtime-configthrough the shared loader.registry-render packagereplacesseal.Breaking changes and upgrade notes
--configand--runtimebecome--runtime-configonbreg,evidence,relay, andregistry-render.SHA256SUMS, and the product changelogs describe rebuilding with eachproduct's
packagecommand.bregctl instance-claim adoptonceafter its first apply on this release.
successor over a package built by the previous
bregctlrelease does notwork yet; see Known break.
CLI surface changes
The CLI reference record in
docs/site/src/data/cli-reference.yamlisre-stamped. Each changed catalog entry was checked against its clap
definition.
--runtime-configreplaces older flags.breg: replaces--config.evidence: replaces the global--runtime; now oncheck,evaluate, andserve.relay check|serve: replaces--runtime.registry-render check|healthcheck|serve: replaces--runtime.bregctlcommands.import-authority open|close|close-expired|listinstance-claim status|adoptreview-recovery resubmit|closebregctl package --revision <TEXT>caseworkctl package --revision <TEXT>;--dry-runnow reportspackageDigest.evidencectl package --revision <REVISION>relayctl package --dry-run --revision <TEXT>;--outputmust not exist yet, and is optional with--dry-run.schedulingctl package --output <DIRECTORY> --dry-run --revision <TEXT>; it now writes a new package directory.evidencectl buildis removed; useevidencectl package.registry-render sealis replaced byregistry-render package.registry-render check --sealis removed.registry-render check|compile|validate --bundleaccepts an authored bundle or a package directory.evidencectl artifact inspect <PROJECT>names a deployment target whoseruntime.yamlnames the installed package.evidence check --without-audit-lockrequires--require-runtime-dependencies.evidencectl doctor --without-audit-lockrequires--runtime-config.Security review notes
Every change below is security-sensitive under
AGENTS.md:products/breg/SECURITY-REVIEW-NOTES.mdcovers importauthorities (BREG-SEC-109..113), the instance claim (BREG-SEC-114..117),
audit retention, review recovery, and package byte binding. For each it
gives the threat, defaults, enforcement point, tests, and accepted
residuals. Accepted residuals:
committed_items;review-recovery closedoes not withdraw the review at the authority;httpOIDC issuer is still accepted in every environment.errors, and it refuses environment expressions in authored files and
substitution into
*Reffields.SHA256SUMS, not an authenticity proof.operator-controlled terminator.
--without-audit-lockstill proves the audit destination's modes, its write access, and a complete final entry, but not the single-writer lock. A second writer is not detected, soservestill refuses to start while another process holds the lock.Issues
Closes #1416
Closes #1448
Closes #1454
Closes #1480
Closes #1491
Closes #1621
Refs #1408: WP1 is in place. BReg still reports configuration errors as
"document invalid" without the field, and only Relay refuses a config file
another local user can write (follow-up tickets).
Refs #1409: WP2 is in place for all seven products. The offline BReg
identity report absorbed from #1262 still needs confirming against the
ticket.
Refs #1410: WP3, the database-backed config ledger, is not started.
Refs #1418: D2, environment-neutral package with ledger-recorded activation,
is not started. BReg packages stay signed and environment-bound here.
Refs #1419: the peer mismatch is refused by name. BReg does not log
Casework's version, and an absent
effectsis still refused by decision.Refs #1420: the import authority is in place. The authority-open doctor
advisory and an end-to-end script remain, plus the follow-ups in the
security notes.
Refs #1422:
/versionand the metrics listener are in place. Audit export isreplaced by shipping the JSONL audit stream (#1560), so no
caseworkctl audit exportexists.Refs #1423: the static fact-read check is in place. Runtime detection, the
fixture gate by default, the bregctl export-field warning, and
language-server findings remain.
Refs #1424: rehearsal is in place. It runs over empty tables, has no
scaffold command, and blocks engine upgrades (#1633).
Refs #1427: enum additions and type widening are in place. Narrowing with
generated validation remains.
Refs #1428: rebaseline has no row limit. Progress reporting remains.
Refs #1455:
tlsTrustProfileis in place. Naming the failing dependency andhost in the generic unavailable message remains.
Refs #1352
Not closed by this PR:
caseworkctl audit verifywas built for the hash chain, which refactor(audit)!: replace chained journals with shared JSONL streams #1560removed. It is superseded rather than delivered; close it as not planned.
Follow-ups
and serve test for BReg and Casework.
migration_locks_hosted_work_before_counting_it_for_the_dropcan miss its5-second lock-poll window on a loaded host.
Known break
and it blocks the next release. "Upgrade rehearsal from the previous
release" refuses because the v0.34.0 package has no
SHA256SUMS(
package.baseline.package.integrity_refused). Behind that refusal, therehearsal recompiles the predecessor with this branch's compiler and
refuses with
migration.rehearsal.baseline_not_reproducibleon a catalogfingerprint drift this branch introduces. With both gates bypassed in a
local prototype,
apply,instance-claim adopt,verify, andbregreadiness all pass over a v0.34.0 database.
Verification
Run with the
ci:fulllabel: pull requests skip the heavy integration tier(PostgreSQL, tutorial, upgrade rehearsal, and composition jobs), and this PR
touches every product's configuration loading, so those jobs are part of the
review evidence.
Local runs on macOS (Apple Silicon). CI job names are given where the local
step reproduces one.
On the final head:
--no-fail-fastinstall_script(#1363, macOS only)cargo deny checkOn an earlier head of this branch (before the last fixes, which touch only
BReg package error mapping and BReg tests):
cargo fmt --check,cargo check --workspace --all-targets, clippy-D warningscargo test --workspaceinstall_script(macOS, #1363) and the BReg targets fixed since; an Evidence client run that failed during a local disk-full episode passes in CInpm testandnpm run checkNot run locally: the upgrade rehearsal (see Known break), the Node.js native
binding tests (the macOS FIPS dylib does not load), and the
immediate-action examples (the same dylib). CI covers all three.
DCO
Signed-off-bytrailer.