feat(evidence): compose BReg lookups as native Evidence sources - #865
Conversation
Bundles name the connections a source may use so admission, single-flight and credentials stay scoped per connection, and questions declare selector profiles so a governed BReg lookup composes without a caller value becoming Evidence authority. The extract/3 adapter ABI, the source contract and the invariant matrix change together; the authoring model and the language server index accept profiles. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… targets `source import|diff|update|detach` accepts a bregctl export, compares it three ways against the previously imported revision and local edits, and replaces files atomically under a project lock with a rollback journal. `target new|explain` scaffolds and explains deployment targets; builds prune unused source alternatives and verify target-owned connection slots. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`evidence_source` turns a reviewed lookup grant into Evidence source, selector, schema and adapter files with a behaviour fingerprint, refusing profiles that do not grant lookup and selector types Evidence cannot represent exactly. Record reads join only the derived relations a request reaches, pinned by a test-only query-plan probe and dependency coverage. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`bregctl dev start|stop` runs a digest-pinned PostgreSQL container and a loopback registry with private state under `.breg/dev`, role separation over TLS and a control socket; `bregctl generate evidence-source` wraps the BReg exporter. Six workspace dependency edges back the supervisor. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Two draft tutorials cover composing and deploying an Evidence project from a BReg export; generated CLI reference pages, configuration data and the sidebar follow the new commands; a generator publishes the starter project tarball; the breg tutorial job builds the Evidence toolset and verifies the retained dev lifecycle and the composition. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A named `sourceConnections` entry carried none of the conditional constraints an inline `http-json-source` carries, so the published Version 1 contract described bundles the runtime refuses to load: an unauthenticated connection at an `https://` origin, an unauthenticated loopback connection also naming a private-CA trust profile, and loose loopback spellings such as a default, zero, out-of-range, or padded port. Adopters read the contract before they meet the runtime, and a contract that publishes a shape only the runtime denies teaches the wrong lesson about where the unauthenticated exception ends. Mirror the `http-json-source` conditional onto `source-connection` so both documents describe the same narrow exception, and say in the two affected descriptions what that exception costs. Review notes: - Threat: an operator follows the published contract, points an unauthenticated source connection at a public origin or pairs one with a private-CA trust profile, and believes the contract sanctions it. Two documents disagreeing is also how a later relaxation of one side passes review unnoticed. - Enforcement point: `SourceConnectionConfig::validate` and `validate_local_unauthenticated_source_origin` in `crates/registry-evidence/src/config.rs` remain the enforcement point for a loaded bundle. The schema now refuses the same documents ahead of it, for every tool that validates against the contract alone. - Focused negative test: `source_connections_are_constrained_alike_by_the_bundle_schema_and_the_runtime` in `crates/registry-evidence/src/config.rs` asserts that the runtime parser and `bundle_contract_validator()` both refuse each of the seven denied connection shapes, and that both accept an authenticated connection and the local loopback exception. The generated Evidence configuration reference data is regenerated with the owning generator so it carries the narrowed connection rules. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…t point Matrix rows V1-I08 and V1-I11 and the adapter reference described the returned-record identity check as if the runtime performed it. It does not. Rust enforces the `extract` entry-point arity, the refusal to pair `extract/3` with `batch`, the fresh authorized selector subset handed to the script, and the fixed FactSet validation of what comes back, and it inspects nothing inside the script. The identity comparison lives in the extraction adapter, which a source exporter generates for every alternative it exports and which a hand-written adapter must carry itself. Say that plainly in both rows and in the reference, and state the obligation an author writing `extract/3` by hand takes on. Add a negative test that runs an exporter-shaped adapter against source responses whose returned identity differs from the selected profile by value and by scalar type, so the enforcement point the matrix names has an executable proof, and map it from both rows. Headings in `ADAPTER-API.md` are unchanged because the documentation site pins anchors into them. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
CI denies two nonminimal_bool clippy warnings in target.rs's hand-rolled settings validator (lines 201 and 223). That validator also accepted any nonempty assuranceProfile and any unknown governance field, while every consumer (target explain, build --target, fixtures run --target) opens the same governance.yaml through the closed TargetGovernance type, which denies unknown fields and restricts assuranceProfile to local, production, or evidence-grade. Because a target directory is create-only, a settings file that `target new` accepted but a consumer later refused left behind a directory with no in-place repair: the operator had to remove it and start over. Fix `target new` to validate governance by deserializing through the same build::TargetGovernance type the consumers use (bumped to pub(crate) for this), before creating anything, so it refuses exactly what consumers refuse and a refusal leaves nothing on disk. This also removes the two hand-rolled boolean checks the clippy lint was flagging; the one remaining nonminimal_bool site (runtime.bundleDirectory, which has no closed consumer type to delegate to) is fixed in place with is_none_or. Security invariant review: - threat: an operator-supplied settings file that target new's validator accepts but every reader of the same file (target explain, build, fixtures run) refuses, producing a create-only directory that cannot be repaired in place; - Rust enforcement point: target::validate_settings_documents in target.rs, now delegating governance validation to build::TargetGovernance::into_bundle, the same closed type build::read_target_documents deserializes governance.yaml with; - focused negative tests: target_new_rejects_unknown_governance_field and target_new_rejects_assurance_profile_outside_enum in target.rs assert new(..) is refused and the target directory is never created; target_new_accepts_a_valid_settings_file pins that a settings file matching the closed shape is still accepted. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…efusal mod.rs and files.rs enforce the SOURCE-EXPORT.md bounds on untrusted export material (manifest and per-artifact size, artifact count, aggregate size, provenance shape, manifest-is-a-mapping, exactly one sources/<id>.yaml, and lowercase bounded artifact stems), but none of the existing import tests exercised any of these refusals. Add one focused test per bound: - import_refuses_manifest_over_one_mebibyte - import_refuses_more_than_256_artifacts - import_refuses_artifact_over_one_mebibyte - import_refuses_export_over_sixteen_mebibytes_aggregate - import_refuses_provenance_outside_its_bounds - import_refuses_yaml_artifact_that_is_not_a_mapping - import_refuses_export_without_exactly_one_sources_artifact - import_refuses_artifact_stem_outside_its_bounds Each asserts the exact refusal message load_export emits, not a paraphrase. Also add deleting_obsolete_artifact_with_a_local_edit_requires_an_explicit_resolution: the existing test for this conflict path (customization_conflicts_finish_with_keep_adopt_and_resolved_file, deleting_obsolete_artifacts_preserves_customization_and_authored_references) always supplies a resolution for the edited, upstream-obsolete artifact, so the unresolved-conflict refusal in Candidate::validate was never exercised. The new test edits a file that the next export drops, gives no resolution, and asserts source update refuses with its documented message, the edited file is untouched, and no journal or partial state replacement remains. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The deployment tutorial told the reader to run `evidence bundle-check`, a hidden internal seam that carries no compatibility promise, and described output it does not produce. The public `evidence check` reads the candidate through its runtime file, compiles the bundle and validates the mounted secret material exactly as startup does. That command resolves the runtime file's absolute bundleDirectory and secret root, which this page sets to the final deployment location, so the step now states the precondition instead of implying any build host can run it. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The page carried three warnings as ordinary sentences a reader skims past: private Transit key custody, development material reaching the operated host, and a migration that constrains reversal. Each one loses a secret or forecloses a return to the previous state, which is what the style guide asks a caution or danger block to mark. Each block now also says what the loss costs, since the sentence it replaced gave the instruction without the consequence. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The bregctl development supervisor always detaches, and the flag is being removed from the binary. Both `bregctl dev` and `bregctl dev start` in this page carried it. No sentence explained the flag, so the surrounding prose already describes the behavior that remains. The evidencectl invocations keep their own flag: that is a separate binary with its own argument. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`npm run generate` rebuilds docs/site/public/examples/breg-evidence-starter.tar.gz on every run, so the tracked copy was a build artifact that reappeared in `git status` after any generate and invited a hand-edited commit of generated bytes. The generator and its unit test stay; only the output leaves the index. The ignore entry names that one path, so the reviewed public/examples/evidence/opencrvs-events-search.openapi.yaml fixture beside it stays tracked. The archive path also leaves the tutorial gate's CI input list with the gate scope revert. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
products/breg/evidence/tests/verify-composition.py drives bregctl, evidencectl and the Evidence runtime over the reviewed teaching inputs and starts no container, so it does not need the Docker-backed tutorial replay it was riding in. Running it there also widened the replay to build and route two Evidence crates the tutorial never executes, which selected the whole Docker job for changes it cannot prove. The proof now has a non-Docker ubuntu-24.04 job that builds only the three binaries it passes on the command line, and a breg_evidence_composition classifier flag that selects it from registry-breg, registry-bregctl, registry-evidence, registry-evidencectl and their reverse dependencies, or from products/breg/evidence/**. The flag is wired through the changes-job outputs and into the ci-result aggregate, whose check is generic over its needs list. breg-tutorial returns to its previous package set and inputs and keeps the dev_lifecycle test, which owns a PostgreSQL container of its own. Every step this commit adds or touches sets shell: bash with set -euo pipefail, and the PyYAML pin follows the one the repository already used, 6.0.2. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The root AGENTS.md routes registry-breg, bregctl and BREG configuration and PostgreSQL behavior to products/breg/AGENTS.md, which did not exist, so a contributor following the routing table landed nowhere and the product's own gates had to be rediscovered from CI each time. The entry states the boundary, including that the Evidence export is a governed lookup export and never Evidence authority, points at the contracts, the configuration references, DEV.md and EVIDENCE.md, and lists the checks per change kind with the commands and environment the CI jobs use. It closes with the generated outputs and their owning generators and the security review-note expectation from CONTRIBUTING.md. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The README described the scripted quickstart and the pilot lifecycle but never linked DEV.md, so the retained `bregctl dev` loop, the loop the tutorials and the composition proof use, was reachable only by listing the directory. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Three entries added to the two exclusion lists ended a column short of the comment alignment shfmt keeps in those arrays, so `shfmt -d` reported them. Only those three lines change; the pre-existing diffs elsewhere in check-evidence-tutorials.sh are left as they are on main. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The pinned surface list did not include the dev command, so the explicit surface test failed as soon as dev shipped. Record dev in the pin at its enum position instead of relaxing the assertion, keeping the list the authority on which commands are public. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
dev start parsed --detach and never read it: the supervisor detaches unconditionally, so the flag promised a choice the command does not offer. Remove it from the CLI, from the documented invocations and from the installed-binary proof, and say in the guide what detaching actually means. The generated CLI reference pages and docs data are regenerated with the owning generator so they stop documenting the removed flag. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
dev printed pretty JSON on success whatever --format asked for, so the default human output of every other command became machine output as soon as a reader ran dev. Render the report the way sibling commands do: keyed lines for a human, the same pretty JSON document for --format json. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Threat: dev start read any doctor failure as "the package was never activated" and answered it with apply --initial. A refused package, a rejected audit profile or an unreachable database therefore reached the activation authority with a hidden diagnostic, and the operator saw an apply failure instead of the prerequisite that actually broke. Enforcement point: activation() in the dev supervisor, a pure classifier over the doctor report that runs before apply is spawned. Only a refusal whose diagnostics are all startup.database.unready means not activated; every other refusal aborts the start carrying doctor's own code, path and message, and a report that is unparseable or names no diagnostic aborts too. startup.database.unready remains broader than "no package activated" because BReg reports several unready conditions under that one code, but it is the only signal doctor exposes and it no longer admits unrelated failures. Focused negative test: only_an_unready_database_classifies_a_doctor_refusal _as_not_activated in dev/tests.rs covers activated, not activated, an unrelated refusal whose message must reach the error, a malformed report and an empty diagnostic list. command() now wraps a new output() that returns the exit outcome with the captured stdout, so the classifier reads the report doctor printed. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Threat: dev interpolates the migration and runtime role passwords into CREATE ROLE statements sent to psql, while every owned prerequisite pumps its child's stderr straight into a retained 0600 log and copies a failed child's stdout into a second log. A refused statement makes the database client echo what it could not run, so the plaintext password reached persistent local files, the returned bytes and any message built from them. The client reports a truncated window around the error position, so a whole-value match is not enough. Enforcement point: the child boundary in dev's command runner. Standard input now carries the secret substring it contains, and for such a call the runner captures the child's stderr instead of streaming it, redacts both that capture and the child's stdout, and only then writes the log, the failure report and the returned bytes. Calls that carry no secret keep streaming as before. redact() hides every run of at least eight bytes that also occurs in the secret, which covers truncated echoes as well as whole occurrences, and leaves the surrounding diagnostics readable. A client-side SCRAM-SHA-256 verifier would avoid sending the plaintext at all, but neither an HMAC nor a PBKDF2 implementation is available to this crate without adding a dependency. Focused negative test: role_password_bytes_cannot_reach_diagnostics_or _errors in dev/tests.rs drives the real runner with a child that echoes its statement whole and as a 30 byte window, then asserts no log file and no error text contains the password or that window, while both logs still carry their surrounding text. redaction_hides_whole_and_truncated_secret _runs pins the pure function, including that unrelated text is untouched. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The local lifecycle created a Docker container with an anonymous volume and never removed either, so every project that was started once left storage behind with no name a person could recognize or reclaim. Bind the database to a named volume derived from the ownership identifier and add `dev stop --remove`, which stops as before, then removes the owned container with its volumes and the named volume itself, tolerating whatever an earlier reclamation already took. Reclamation clears the container facts from the private state so the next start builds an empty database from the retained keys, credentials, ports, clients and package. Plain `dev stop` still keeps everything. The live lifecycle test reclaims from `Drop`, so a panicking assertion cannot leak the container or the volume it created either. The generated CLI reference pages and docs data are regenerated with the owning generator so they document the flag. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…istry The live lifecycle test started a registry and exercised it through hand written requests, so nothing proved that `generate evidence-source` describes the registry a person actually runs. A wrong route, access profile, identity field or unresolved-problem shape would have shipped unnoticed. Author one exact selector and one narrow request-origin lookup profile in the test project, export it, then drive the exported route and access profile twice: a seeded identity echoes its own value with the scalar type the response schema declares, and an unknown identity answers exactly the status, type and code the export names for an unresolved lookup. Both assertions were confirmed to bite by mutating the expected seed value and by looking up the seeded identity in place of the absent one. The added work is two loopback requests and one offline export. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The Evidence source exporter refuses five input shapes it must never export: change-request lifecycle entities, a profile that grants no lookup, a lookup grant without a compiled lookup route, selector names that push the Evidence profile name past 64 bytes, and a composite selector whose union crosses the 8192-byte selector bound while each field stays inside its own. Each refusal now has a test asserting the diagnostic code and the exact message an author sees. The extraction adapter the exporter emits is the only place the returned-record identity is compared with the selector. Pin the emitted comparison verbatim for a single-field and a multi-field selector, so a change to that script cannot land unnoticed. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The exported Evidence prepare script carries the access profile as a query parameter, so a replaced adapter can name any profile on the lookup route. Selection is a request, never an authority: BReg resolves the named profile against the route's compiled profile list, the token's scopes, purposes and row-boundary claims, and the profile's lookup grants before any read runs. Threat: an Evidence deployment with a substituted adapter names a profile the token does not satisfy, a profile that grants no lookup, or a profile that does not exist, and learns from the answer whether the entity or profile exists or reads through it. Enforcement point: authorize_direct_route_base in the read API refuses a profile outside the route's compiled list or unsatisfied by the token, and resolve_lookup_selector refuses a profile without a lookup grant for the requested selector; every refusal is the concealed not-found problem. Negative test: lookup_route_conceals_a_substituted_access_profile_selection sends the exporter's lookup request with each of the three profile kinds, asserts the identical concealed problem the unknown-entity route returns, that the record service is never called, and that the profile lacking lookup still serves the read it is granted. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
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: 16a230a5d6
ℹ️ 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".
…assurance in the schema The published bundle contract and the runtime must refuse the same documents: an unused `sourceConnections` entry using `authentication.kind: none` at a canonical loopback origin previously passed schema validation under `production` and `evidence-grade` assurance even though `SourceConnectionConfig::validate` already refuses it outside local assurance. Extend the assurance-profile conditional to constrain `sourceConnections.*.authentication` the same way it already constrains `sources.*.authentication`, pinned by `source_connections_are_constrained_alike_by_the_bundle_schema_and_the_runtime`. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The catalog tests pin the artifact kinds `bregctl generate` accepts and the authoring inputs `evidencectl new` requires exactly one of. Both shapes changed with the Evidence source exporter and the offline starter, and the regenerated reference data already carries them; the pins now hold the published shape so the developer-tools test job proves it. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The role password test binds a fixed hex value to a local named `password`, which the secret scan reads as a leaked credential and refuses. The value is a canary the test plants to prove redaction, so the local now says so; the assertions and the redaction path are unchanged. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`dev stop --remove` is the documented recovery for a retained database container already taken by hand: `reclaim` verifies ownership of whatever Docker still lists and forgets the rest. But `stop` inspected the container unconditionally before reaching `reclaim`, and that inspection refuses an absent listing whenever the journal still names a container. Remove mode never got past that refusal, so a container removed by hand could not be reclaimed even though its named volume was still there to clean up. Skip the inspection (and the stop it guards) in remove mode when nothing is listed under the container's name, and let `reclaim` handle it as documented. Plain `dev stop` without `--remove` keeps refusing a missing retained container exactly as before, since that refusal protects retained data. Pinned by installed_dev_preserves_edits_and_recovers_failed_start_without_reseeding in crates/registry-bregctl/tests/dev_lifecycle.rs, extended to remove a recreated container by hand and assert both that plain `dev stop` still refuses it and that `dev stop --remove` reclaims the remaining volume and resets the retained container identity. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…value BReg accepts an empty lookup value on a string field whose `minLength` is zero, whether declared or defaulted, but the Evidence source exporter always emitted `minimumBytes: 1` for a string selector. A caller relying on the exported selector schema could never express the empty value BReg itself still accepts, silently narrowing the field's real domain the same way the existing unrestricted `int64` refusal already guards against. Refuse this shape in `selector_schema` with the same style of message as the int64 case, naming the fix: declare `minLength` of at least 1 on the field, or use a custom adapter. Pinned by refuses_a_string_selector_field_that_accepts_the_empty_value in crates/registry-breg/src/evidence_source/tests.rs, both for the schema function directly and through a compiled project whose `code` field omits `minLength`. The live dev lifecycle test authors its own selector field at generate time via bregctl's init template, which does not declare `minLength` on `code` (a separate, intentionally unchanged follow-up); update the test's authored copy to declare `minLength: 1` so it keeps exporting under the new refusal. Documented the requirement in products/breg/EVIDENCE.md alongside the int64 refusal it mirrors. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b2b4f0b1c4
ℹ️ 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".
… checks An evidence-grade target passes governance parsing, which accepts local, production and evidence-grade, and then fell into the wildcard refusal in compile_target_project, which named only local and production. Every evidence-grade target was refused before compilation by build, target explanation and target-backed fixture validation, a regression from the unconditional production checks the function ran before the local profile was added. Treat evidence-grade as production: validate the deployment inputs under the production rules, reject disposable local identifiers and hold every source to the production transport conditions. The refusal for any other value names all three accepted profiles. Security review. Threat: an evidence-grade deployment compiled under the local rules would accept unauthenticated or plaintext sources. Enforcement point: the assurance match in compile_target_project selecting the production arm. Negative test: evidence_grade_target_is_held_to_the_production_checks pins that an unauthenticated source is refused under evidence-grade with the same message as under production; evidence_grade_target_compiles_through_the_production_path pins the positive path and an_unknown_assurance_profile_is_refused_by_name pins the refusal naming all three profiles. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e63373dffa
ℹ️ 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".
The dev supervisor answered its owner-only control socket from a single read into a fixed buffer and compared the bytes it happened to receive against the two known commands. A Unix stream socket may deliver a client's write in parts, in which case the partial command counted as unknown, the connection was dropped, and `dev stop` reported that the supervisor had not stopped. The supervisor now reads until the newline, the end of the stream, or the 16-byte bound, whichever comes first, and only then compares the command. The per-read timeout is unchanged, so a client that stalls mid-command can hold the accept loop for at most sixteen seconds on a socket only the owning user can open. Pinned by `a_control_command_split_across_writes_is_read_whole` (both commands split across two writes with a pause between them, both failing before the change) and `a_control_command_stops_at_the_newline_or_the_size_bound` (no read past the newline, the 16-byte cap, end of stream without a newline). No trust boundary changes: the socket's ownership and mode checks and the command set are the same. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
`evidencectl new --starter` always told the reader to run `fixtures run` next. The reviewed BReg starter ships questions, derivations, fixtures and target settings but no source on purpose: its README asks for a `bregctl generate evidence-source` export to be imported first, so the printed next command failed straight after creation. When the copied starter carries no file under `sources/`, the next step now points at the project's README and says a source has to be imported or authored before `fixtures run`; a starter that ships a source keeps the previous line. Pinned by `a_starter_without_sources_defers_the_fixture_run_to_its_readme` (a starter with a README, questions, derivations, schemas and fixtures but no source; failing before the change) alongside the unchanged `local_starter_creates_offline_project_and_target_settings_example` for a starter that ships one. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9b8516307
ℹ️ 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".
`target explain` compiled the project with the deployment target before it read the target documents and listed the public key files the governance references. The compile copies those keys from the target and fails on a missing one, so a deleted or never-copied active or published key ended the command inside the compile error and the `missingPublicKeyFiles` report, which exists for that case, was never printed. The command now parses governance and runtime, inventories the expected and missing public key files and collects the secret references first. When a key is missing it prints the report with no fixture paths and exits with a failure; otherwise it compiles and prints the full report as before. Pinned by `a_missing_target_public_key_is_reported_by_explain_before_compilation` (the target key removed; failing before the change inside the compile with no report) and `explain_reports_the_fixture_paths_when_every_public_key_is_present`. No trust boundary changes: the report carries paths and logical secret references only, as before. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 682733901b
ℹ️ 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".
Summary
Native BReg to Evidence composition: a governed BReg lookup becomes an Evidence source without hand-written adapters. Five single-area commits carry the feature (Evidence runtime and contracts, evidencectl, the BReg exporter, the bregctl dev lifecycle, docs and CI), followed by the fixes from the staff-engineer review of the original branch. Thirty-five linear commits, no merge commits, each signed off: twenty-six from the split, then five from the first review round (three Codex findings fixed, two first-run CI failures fixed, the answered findings recorded below), then one from the second round (evidence-grade targets were refused before compilation; fixed with its pinning tests), then two from the third round (the supervisor reads a whole control command before matching it; a starter that ships no source has its next step deferred to its README), then one from the fourth round (
target explaininventories the public keys before it compiles, so a missing key reaches the report instead of ending inside the compile error).http-json-source(thesource-connectionschema now carries the sameallOf, so the published contract and the loaded-bundle validator refuse the same documents).bregctl dev start | stop | status) on a Docker-owned PostgreSQL with a named data volume,dev stop --removeto reclaim container and volume, output-format aware reports, doctor classification that only treats an unready database as "needs activation", and role passwords redacted at the diagnostics boundary;bregctl generate evidence-sourcewrites the export.evidence checkcommand and admonitions at the irreversible steps, reference pages,products/breg/AGENTS.md, the README link to the native development guide, a starter archive generated by the docs pipeline instead of committed, and the composition proof in its own non-Dockerbreg-evidence-compositionjob sobreg-tutorialkeeps its previous scope.Generated outputs travel with their source change: the two commits that change the bregctl surface carry the regenerated CLI reference pages and
cli-reference.json; the schema commit carries the regeneratedevidence-configuration.json.Checks
Local, on the branch head, rustup cargo 1.95.0 with
CARGO_INCREMENTAL=0 CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0:cargo fmt --checkandcargo clippy --locked --profile ci --all-targets -- -D warningsfor registry-evidence, registry-evidence-authoring, registry-language-server, registry-evidencectl, registry-breg, registry-bregctl and registry-cli-docs: clean.cargo test --locked --profile cifor the same seven crates (registry-cli-docs and registry-language-server are the developer-tools job's pair): all passing (registry-breg lib 229, http_read_only 68, registry-bregctl lib 80 and cli 83 among them). One pre-existing flaky test,glibc_at_the_floor_installs_both_commandsin registry-breg (unchanged from main), fails when its two Linux fixtures are stamped in the same microsecond and one removes the other's assets; it passes alone and on rerun, and is listed in the follow-ups. One whole-crate run of registry-evidencectl during the second round failedinterrupted_replacement_and_baseline_advance_restore_the_complete_prior_state(a test this branch adds) on its final lock acquire, once; 0 failures in 50 reruns of the lib tests (20 alone, 30 as two concurrent loops), and the assertion hides the error it got, so it is listed in the follow-ups with that diagnostic gap.cargo test --locked --profile ci -p registry-bregctl --test dev_lifecycle -- --ignoredafter building breg (runtime), bregctl and mint in the ci profile: 1 passed in 25 s; the test authors a selector profile and a lookup access profile, exports the source, drives the live registry from the export (route, method, identity field, scalar type, projection, access profile, fixed headers), and reclaims container and volume.verify-composition.pyagainst the ci-profile bregctl, evidencectl and evidence: 8 export artifacts, 22 fixture cases, 2 questions, provenance-only revisions unchanged, consumed-change revisions both changed, native source update passed.products/breg/scripts/test-postgres.shagainst a throwaway PostGIS container at the digest CI pins: all 52 invocations passing.products/breg/scripts/check-contracts.sh(103 tests),check-client-contract.shandproducts/evidence/scripts/check-contracts.sh(generated contracts reproduce exactly): passing.npm run check(26245 links, check-llms, anchors, docset, archive lock, frontmatter, notary, Vale, SVG, SEO, accessibility),npm test(490),npm run check:cli-reference, andnpm run generateleaving no tracked drift.python3 .github/scripts/test_ci_changes.py: 75 OK, including the newbreg_evidence_compositionrouting test and the narrowedbreg_tutorialone.cargo check --lockedfor every one of the 35 commits in a detached worktree;gitleaks dir --config .gitleaks.tomlon the tree: no leaks found.test-postgres-tls.sh(needs the CI service container); nothing on the branch touches that path.Pinning tests per review item are named in each fix commit. The ones that pin a trust boundary:
source_connections_are_constrained_alike_by_the_bundle_schema_and_the_runtime(registry-evidence config),an_exported_adapter_refuses_a_returned_identity_that_is_not_the_selector(registry-evidence Rhai runtime),target_new_rejects_unknown_governance_fieldandtarget_new_rejects_assurance_profile_outside_enum(evidencectl),only_an_unready_database_classifies_a_doctor_refusal_as_not_activatedandrole_password_bytes_cannot_reach_diagnostics_or_errors(bregctl dev),lookup_route_conceals_a_substituted_access_profile_selection(registry-breg read API). The review round addsrefuses_a_string_selector_field_that_accepts_the_empty_value(registry-breg exporter), extends the liveinstalled_dev_preserves_edits_and_recovers_failed_start_without_reseedingwith a container removed by hand, and extends the source-connection schema pin with an unauthenticated loopback connection under both non-local assurance profiles. The second round addsevidence_grade_target_is_held_to_the_production_checks,evidence_grade_target_compiles_through_the_production_pathandan_unknown_assurance_profile_is_refused_by_name(evidencectl). The third round addsa_control_command_split_across_writes_is_read_wholeanda_control_command_stops_at_the_newline_or_the_size_bound(bregctl dev supervisor) anda_starter_without_sources_defers_the_fixture_run_to_its_readme(evidencectl scaffold). The fourth round addsa_missing_target_public_key_is_reported_by_explain_before_compilationandexplain_reports_the_fixture_paths_when_every_public_key_is_present(evidencectl target).Notes
Security review notes for the changed trust boundaries:
SourceConnectionConfig::validateandvalidate_local_unauthenticated_source_originfor a loaded bundle, with the schema now refusing the same documents ahead of it,authentication.kind: noneon a source connection under production or evidence-grade assurance included. Negative test:source_connections_are_constrained_alike_by_the_bundle_schema_and_the_runtime.an_exported_adapter_refuses_a_returned_identity_that_is_not_the_selector; the emitted comparison is pinned verbatim byemitted_extract_checks_the_returned_identity_by_exact_value_and_scalar_type.target newaccepts but every consumer refuses, leaving a create-only directory with no in-place repair. Enforcement point:target::validate_settings_documentsdelegating tobuild::TargetGovernance::into_bundle. Negative tests:target_new_rejects_unknown_governance_field,target_new_rejects_assurance_profile_outside_enum.dev startproceeds on a broken database. Enforcement point:activation()in the bregctl dev module, the single classification site. Negative test: the Unrelated arm ofonly_an_unready_database_classifies_a_doctor_refusal_as_not_activated.role_password_bytes_cannot_reach_diagnostics_or_errors.compile_target_projectinstead refused every evidence-grade target before compilation, a regression from main's unconditional production checks once the local profile was added. Enforcement point: the assurance match incompile_target_project, which selects the production arm forproductionandevidence-grade(input validation under the production rules, disposable local identifiers rejected, every source held to the production transport conditions) and refuses any other value naming all three profiles. Negative test:evidence_grade_target_is_held_to_the_production_checks(an unauthenticated source is refused under evidence-grade with the same message as under production);evidence_grade_target_compiles_through_the_production_pathpins the positive path.authorize_direct_route_base(route's compiled profile list, token scopes, purposes and row-boundary claims) andresolve_lookup_selector(lookup grant for the selector) in the BReg read API, both answering with the concealed not-found problem. Negative test:lookup_route_conceals_a_substituted_access_profile_selection.Compatibility: the
bregctl dev start --detachflag from the original branch is gone before it ever shipped;dev stopkeeps container and data unless--removeis given. No released surface changes.Follow-ups deferred from the review, none addressed here:
behaviorRevisioncarries no engine or protocol version.sec-source-request-immutabledoes not assert it; the V1-I09 no-dedup clause is pinned only indirectly.let _ =sites on cleanup and permission-sealing paths.question.schema.jsoncannot express selector/profiles exclusivity (answered in the review thread: the derived schema describes shape only and the compiler and language server refuse the combination).source diff --targetruns a not-yet-accepted export's adapter Rhai through fixtures.writereads up to 512 MiB to validate the destination.rolebecomes an empty string (unreachable today).p256hand-rolls ES256.localhostwhile the container publishes on 127.0.0.1.dev stopin a never-started directory reports stopped.registrationNumberwhile journeys useregistration-number, alias unexplained.verify_outputsafter a partial start is still untested.executable()keeps a non-canonical PATH hit with no version recorded in state.postgres:17.11digest is not stated in DEV.md.products/evidence/fixtures/instantiatessourceConnections; stale installedbreg/mintbinaries surface only as "package refused" becauseStartupError::PackageRefuseddiscards its cause;bregctl init's example project ships no selector profile or lookup grant, so it cannot feedgenerate evidence-sourceunmodified; the SCRAM verifier alternative for the password redaction needs crypto not in bregctl's dependency closure; nothing pinsci-result's needs list; registry-mint links registry-evidence so Evidence changes still selectbreg-tutorial; no gates-inventory marker for the new composition job; anastro checkts(80007) warning on the starter generator'sawait; 12 pre-existing redoclysecurity-definedwarnings on the Evidence OpenAPI document; pre-existing shfmt hunks incheck-evidence-tutorials.shandtest-postgres.sh.target newvalidates the runtime settings document for shape only, since evidencectl has no closed mirror of the runtime'sRuntimeConfigand does not link the runtime crate (the runtime refuses unknown keys at startup with the field named); a published runtime settings contract or an evidencectl-local closed mirror is the follow-up.bregctl init's examplecodefield declares nominLength, so adding a selector profile on it is refused by the exporter with the fix named; the template should declareminLength: 1. The registry-breg installer test names its fixture directory by process id and wall-clock nanoseconds, which macOS reports at microsecond resolution, so two tests building Linux fixtures in the same microsecond share a directory and the first to finish removes the other's assets (reproduced 5 times in 30 runs of the two tests together, 0 in 30 alone, 0 in 20 serial runs; file unchanged on this branch).interrupted_replacement_and_baseline_advance_restore_the_complete_prior_state(evidencectl source import, added by this branch) failed its finalProjectLock::acquireonce in a whole-crate run and not in 50 reruns; the assertion isis_ok(), so the error was not captured, and the next step is to make it report the error and rerun under contention. From the third round: the BReg exporter maps a UUID or reference field to a 36-byte bounded string selector and a vocabulary code to a bounded string, since Evidence's selector contract has no UUID lexical type andcontrolled-codeneeds a codelist artifact the export does not carry; a same-length value outside the domain passes Evidence's selector validation and BReg refuses it with the auditedrequest.invalidproblem before any read (the decision queued for the maintainers: refuse these field types as not losslessly representable, or carry a codelist artifact for vocabularies and document the bounded-string limit for UUID and reference). ThesourceConnections.*.baseUrlpatterns mirror main'ssources.*.baseUrlpatterns, which admit user information and an out-of-range loopback port that the runtime refuses at startup; the next step tightens both branches together, regenerates the JSON and extendssource_connections_are_constrained_alike_by_the_bundle_schema_and_the_runtimewith those denied shapes. From the fourth round: a profiled--subject role@profile:field=valueonevidence request prepareis parsed by the progressive selector rule (true,falseand integer literals become booleans and integers, a double-quoted literal stays a string), so a string selector field whose value looks like an integer is refused by the runtime; the unprofiledrole:field=valueform keeps its value lexical. The queued shape parses the value by the declared field type when the selector is compiled, which threads the field types through the persistedCompiledSelectorand the dev state; until then the double-quoted form is the escape. From the fifth round: a UUID or reference field exported as an output fact is a 36-character bounded string, since Evidence's Version 1 fact schema subset has nopatternand only thedateanddate-timestring formats (the same decision as the selector item, now covering facts; auuidformat in the Evidence subset is the queued option).bregctl dev startresolves Docker fromPATHfor the rehearsal database anddev stophas no--docker-bin, whiledatabaseandstop_databaseuse the resolved flag. The evidencectl workflow reference still saysbuildrejects local assurance inputs; on this branchbuildcompiles anassuranceProfile: localtarget under the local checks and the bundle records the profile, so thebuildrow and paragraph need rewriting. Client tokens are minted before the rehearsal, activation, BReg start and readiness with a 300 s lifetime, so a first start that exhausts its deadlines reaches seeding with expired tokens; minting after BReg readiness is the queued fix.DCO
Signed-off-bytrailer.