feat(bregctl): derive a registry project from PublicSchema with init --from - #918
Conversation
bregctl will offer a registry project drawn from the PublicSchema reference model. The model is LinkML, and the Base Registry Engine source-neutrality gate keeps domain vocabulary out of the breg crates, so the reader and the vendored snapshot live in their own crate: a bundle of LinkML files in, one resolved model out, with the LinkML features it does not model refused rather than dropped. The snapshot is pinned in PIN.yaml and refreshed by sync-snapshot.sh. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Embed named starter selections beside the PublicSchema snapshot so adopter tooling can derive a first project without asking the adopter to write a selection document. The first starter covers a person, a household, and the membership that links them. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Plain init keeps writing the domain-neutral example. With --from, init derives the project from the embedded PublicSchema snapshot instead: a selection document (--selection) or a shipped starter (--starter) names the concepts and properties, and the resolver turns them into entities, fields, closed vocabularies, references, and structured values, with sensitive properties classified restricted. The written project carries an operator and a reader profile, dependency-ordered journeys, dev clients, a README that explains each derivation and the findings check reports, and an echo of the selection to derive again from. The interactive path refuses without a terminal and is not written yet. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Generated by scripts/generate-cli-reference.mjs; the catalog digest changes in every page, and the init page gains the --from, --selection, and --starter options with their constraints. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Without a selection file or a starter, `init --from` now gathers the selection through prompts: registry identifier and title, the concepts, the properties of each, the target of any reference that fits several chosen concepts, the identifiers and routes, the code lists to list in full, and a final review of the document before anything is written. The prompts offer only what the resolver accepts and validate identifiers with its grammar, so an answer taken at a prompt is never refused after the project is derived. The resolver's range-fit, per-property support, and identifier rules are factored out so the wizard shares them instead of restating them. Every decision beneath the prompts is a pure function with tests, and a sweep test derives every concrete concept from the answers the wizard can produce. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The init --from publicschema path had no page a reader could follow. The tutorial derives the household starter, reads the attribution and the field tables init wrote, checks and starts the project, edits the selection and derives again, and describes the eight wizard prompts in the order the binary asks them. Every transcript comes from the binary. The page sits beside the module tutorial in the sidebar and in the adoption-path test, and is listed with the BReg tutorials the two replay gates exclude, with the reason, so the coverage check keeps passing. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
init --from <MODEL> is import only, PublicSchema is the first model, and the snapshot is a pinned external input; the decision log did not say so. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The README lists every vendored input and how it is refreshed; the PublicSchema snapshot under crates/registry-linkml was missing from it. 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: 5bacf3fcfb
ℹ️ 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 bregctl text rendering was one flat line per record, so a run of findings sharing a code repeated the same sentence against every path it was reported against, the artifact count sat between the findings and the steps, and neither the severity nor the count a reader has to act on appeared at all. relayctl already answers this in its own report module, so this brings the same discipline over: a lead sentence carrying the counts, aligned label and value detail, blank-line separated sections, and a closing count. It adds two things that discipline did not need. Findings sharing a severity and a code state their sentence once and list every path under it, because a registry project applies one access rule across every entity and so reports one mistake against six paths. And every line is written with the ANSI attributes anstyle names, which main_entry lets anstream keep for a terminal and strip for a pipe, a file, or a test harness, so a captured transcript stays the plain ASCII it was. Authored names are escaped before they are styled, so the only escape sequences in a finished rendering are the ones the renderer wrote. anstream and anstyle were already in the lockfile behind clap, so this adds two dependency edges and no new package. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ncing the snapshot The attribution CC-BY-4.0 asks for must carry a link to the licence, so PIN.yaml records it and Pin reads it. The sync script copies the working tree while recording HEAD, so a checkout with edits or untracked files under the copied paths is refused before anything is written. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A structured field carrying an unselected concept inline is classified for the most protected property it holds, so a reader profile no longer reads a protected value through it. A vocabulary override applies to a property holding many values too, as the echoed selection says it does. A bounded code is sized for the longest code of its enumeration, within the compiler's bound, so no code of the model is refused. The registry identifier leaves room for the names derived from it, so the refusal comes before the destination exists rather than from the compiler after. The wizard gate checks the streams the prompts use, standard input and standard error, so a piped standard output still runs it. The README carries the licence link and marks the project as a modified form of the model, and the identifier field is described as supplied by the caller, which is what the journeys do. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The remaining renderers still wrote flat `label: value` lines, so a reader had no severity, no counts, and no column to scan. They now build a report::Lines like the converted ones do. Two of them needed the module to grow a shape it did not have. The access explanation is a list of long sentences, which reads as one paragraph once the sentences wrap, so `listed` hangs a continuation the way `steps` already does. The synthetic admission preview exists to answer one question, so `verdict` puts the attribute on the answer rather than the heading, and a refusal reads as an answer rather than an error, because that is what it is. `counted` pluralized by appending an `s`, which named "8 dependencys" and "2 deliverys". It now applies the consonant-before-`y` rule, leaving "journeys" alone. 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: ca4db8b8b2
ℹ️ 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 renderer labelled the non-fatal severity `warning`, which is the word relayctl uses. bregctl's own word is `finding`: it is what the JSON `severity` carries and what the configuration reference defines, and a reader who has both open should not have to translate between them. A grouped code now counts the paths it collapsed rather than repeating the finding count the closing line already gives. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A reader scanning the dev report for one client's credential paths finds the block by its blank line above; the first client had none, so it ran into the session detail directly above it. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…at stays unlinked The model links its concepts through concepts of their own rather than through direct properties, so a selection of several concepts derives a registry of unrelated collections without anyone saying so. The resolver now finds the concepts not selected that refer to the selected ones at least twice, the wizard offers them after the concepts are chosen and ticks a reference to a chosen concept by default, and the entities no field connects are named in the review, in the README beside the concepts that would connect them, and in the report's next steps. A registry of unrelated collections stays a legitimate answer. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Every transcript on these pages quoted the flat `label: value` output the tool no longer writes, and the prose around them named lines that no longer exist: a `next:` line, an `artifacts:` count, a first line reading `synthetic profile admission: allowed (...)`. Each block here is the current rendering, captured from a real run where the command needs neither a database nor a network. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The tutorial showed the flat `label: value` transcript and named a `next:` line and an `artifacts` count that the report no longer prints, so a reader following it could not match what the terminal shows to what the page says. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Only the JSON rendering of `init --from publicschema` was asserted, so nothing held the derived project's text report to the shape the tutorial quotes: the lead sentence, the grouped finding line, the closing count, and the numbered next steps. 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: 4c3c74f5d2
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33c14bcf3d
ℹ️ 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".
…ommit The previous pin sat on a branch that was squash-merged upstream, so it was no longer reachable from main. The merged commit moves the CRVS and social-protection concept IRIs under their own namespaces and changes no definition the household starter uses. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The page explained the local clients file field by field, restated the project files the README already lists, and re-explained response mechanics at every step. The clients explanation now lives on the configured-registry page, each step keeps one line of context, and no paragraph runs past sixty words. Commands, checked output, and every linked anchor are unchanged. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Routing the renderers through the report module dropped the three migration bounds from the human explain output, closed a findings-only refusal with a claim of zero errors, rendered a rebaseline caveat as a step, and lost the entity and profile nouns of an access explanation. Each is restored and pinned by a test, labels are escaped like values, an unchanged project lock no longer reports zero artifacts, and a process test proves an authored claim name cannot forge a report line. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The change-review tutorial carried a transcript that was never captured: it claimed eighteen findings under two codes where the tool prints twenty-six under four. The refusal line and the access explanation blocks follow the renderer's current wording. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ules out The tutorial restated the derivation rules the generated README already carries and walked all nine wizard prompts inline, which made it read as reference. The prompts now have a reference page, the rules an explanation page, and the tutorial keeps the commands, the output the reader checks, and one line of context per step. The create request body is corrected to the camel-case names the API accepts, and the troubleshooting table names the runtime version mismatch that fails a local start. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
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: c77e263995
ℹ️ 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".
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…cal URIs through Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… before writing Inline vocabulary overrides above the codelist bound, identifier fields the compiler keeps for itself, field identifiers that normalize to one API or SQL name, and targets on properties that hold no records are refused during resolution so init leaves no destination behind. Connectors must reach two distinct selected concepts, a self-reference no longer counts as a link, and generated geometry and inlined temporal values keep their shapes. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…what elevates a field Split a generated journey that would exceed the fixture's step limit, keep step and capture identifiers within the fixture grammar, tell a field the model marks sensitive apart from one elevated only by its public entity in the README and the next steps, and state that a structured field's value is not projected to the manifest. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… reserved-name rule Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…nifest-core 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: 09bd3a967a
ℹ️ 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".
| let folded: String = name | ||
| .chars() | ||
| .map(|character| if character == '_' { '-' } else { character }) | ||
| .collect(); |
There was a problem hiding this comment.
Keep folded fixture identifiers unique
When a valid selection contains entity identifiers that differ only by _ versus -, such as person_record and person-record, this normalization produces identical create/get step and capture identifiers. Resolution considers the original IDs distinct, but fixture validation rejects the generated journey for duplicate identifiers when bregctl test or dev reads it. Detect collisions after fixture normalization or include the original identifier in a digest even when the folded value is within the length bound.
Useful? React with 👍 / 👎.
| if required.is_empty() { | ||
| if let Some((name, property)) = properties.iter().find(|(_, property)| { | ||
| property.get("type") == Some(&json!("string")) && property.get("enum").is_none() | ||
| }) { | ||
| object.insert(name.clone(), example_of_schema(property)); |
There was a problem hiding this comment.
Generate examples that satisfy temporal patterns
When an unselected concept is carried inline and its first eligible string property is temporal—for example, FunctioningProfile.respondent inlines Person, whose first such property is date_of_birth—this branch selects that property and example_of_schema writes "example". The newly added date pattern rejects that value, so the generated tests/journeys.yaml cannot be validated by bregctl test or dev. Either leave the optional object empty or generate a value that honors pattern and the rest of the selected property's schema.
Useful? React with 👍 / 👎.
| "fields: {{{}: {}}}", | ||
| scalar(&entity.identifier.id), | ||
| scalar(&identifier_value) | ||
| ), |
There was a problem hiding this comment.
Keep expected identifiers equal to truncated request values
When a hand-written selection uses an entity ID longer than 62 characters together with a short custom identifierField, identifier_value exceeds the generated field's 64-character bound. example_value truncates the create request, but this expectation still uses the untruncated value, so the generated journey is rejected or fails when the returned record is compared. Derive one bounded identifier value and reuse it in both the request and expectation.
Useful? React with 👍 / 👎.
| ResolvedKind::internal(FieldKind::Reference { | ||
| target: entry.id.clone(), | ||
| }) |
There was a problem hiding this comment.
Raise references to restricted target classifications
When a selection classifies a referenced entity as restricted but leaves the carrying entity readable—for example, a restricted Person targeted by an internal FunctioningProfile.respondent—this assigns the reference only its ordinary internal floor. The generated reader profile filters on the reference field's classification, not the target entity's, so it can return the restricted record's identifier and disclose the relationship even though it cannot read the target collection. Promote a reference to at least its selected target's classification and add a focused negative access test.
AGENTS.md reference: products/breg/AGENTS.md:L150-L157
Useful? React with 👍 / 👎.
Summary
bregctl init DEST --from publicschemaderives a registry project from a selection over the PublicSchema reference model instead of writing the domain-neutral example. Plainbregctl init DESTis unchanged. The selection comes from one of three sources that converge on a single document (kind: ModelSelection, echoed into the project atmodel/selection.yaml):--starter household, a selection shipped with the model (Person, Household, GroupMembership);--selection FILE, a selection document the adopter edits and passes back;init.selection.missing.The derived project is ordinary source that
check,dev, andbuildread like any other. The derivation is import only. Locked rules: an enumeration of at most 300 values becomes a closed vocabulary, a larger one a bounded code; a property whose value is another selected concept becomes areference; a many-valued reference is refused with guidance to model it as its own entity; a property the model marks sensitive becomes arestrictedfield, so derived projects reportaccess.profile.higher_classificationand the README explains why rather than hiding it.Commits are one per owning area:
crates/: theregistry-linkmlcrate (a LinkML reader and the vendored PublicSchema 0.3.0 snapshot withPIN.yamlandsync-snapshot.sh), the household starter,init --frominregistry-bregctl, and the wizard;docs/site/: the regenerated CLI reference and the tutorialtutorials/derive-a-registry-from-publicschema;products/breg/: a decision-log bullet;external/: the snapshot recorded as a vendored input.Checks
cargo fmt --check,cargo clippy --locked --profile ci --all-targets -p registry-bregctl -- -D warnings,cargo test --locked --profile ci -p registry-bregctl(213 lib tests + 87 CLI tests) and-p registry-linkml.products/breg/scripts/check_source_neutrality.pyandproducts/breg/scripts/check-contracts.sh.cargo deny check: the duplicate-version warnings are the ones already onmain.docs/site:npm test(545) and the fullnpm run checkchain (26227 internal links).bregctl devon PostgreSQL 17, its schema-test rehearsal replayed thefirst-recordsjourney, live reads with a reader token answered, session removed withdev stop --remove.expect; the result passedbregctl check.registry-linkml; selection parsing, resolver, renderer, and wizard unit tests inregistry-bregctl; CLI tests for--starter,--selection(symlink and size refusals), the model mismatch, and the no-terminal refusal; docs adoption-path and replay-gate coverage updated for the tutorial.Notes
ca9861a13391aa8c20f8b2a1d4b470a99d01ee7d(2026-05-19, version 0.3.0, CC-BY-4.0). That commit is not yet on the upstream repository'smain.external/README.mdrecords it as a current vendored input; please review it as a product and dependency decision.inquireis added toregistry-bregctlfor the wizard;Cargo.lockchanges accordingly.access.profile.higher_classificationfor sensitive properties read by theoperatorprofile. The README of a derived project explains the finding and how to narrow it.status: current, matching the other BReg tutorials that describe unreleasedbregctlbehaviour.DCO
Signed-off-bytrailer.