Skip to content

feat(hydra-gates): a schema can declare that it carries no demo data - #781

Merged
rubenvdlinde merged 1 commit into
mainfrom
feat/mock-generator-excludes-control-plane
Sep 18, 2026
Merged

rubenvdlinde merged 1 commit into
mainfrom
feat/mock-generator-excludes-control-plane

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What this changes

A schema can now say that it carries no demo data:

"Application": { "x-openregister-demo-data": false, ... }

A string is accepted in place of false and is printed as the reason. true and absence both mean generate, so nothing changes for any app that adds no marker.

Why

buildiq's register holds the schemas the app keeps about itself: the apps it has built, their versions, the slug route index, the template store, the export jobs it has run. ADR-111 rule 1 asks for three demo objects per schema, so the generator wrote three of each.

On the demo instance that produced three apps in the Apps list and on the dashboard that cannot be opened. Their applicationVersion rows carry the placeholder a format: uuid property gets, 00000000-0000-4000-8000-000000000000, and a manifest with no pages, so the detail page renders empty and /apps/buildiq/builder/<slug>/ never resolves. Three more sat in the template store beside the four real built-in templates and would clone into an equally empty app. Three export-job rows read as finished exports nobody ran. Measured 2026-09-18 on app c5a2e155-432b-4ea8-a965-56661b991463, slug ccdc.

Every one of those objects satisfies its schema. That is why --check was green on the dataset that broke the demo: conformance is about an object's shape, and nothing in a schema says whether its rows are content somebody authors or bookkeeping the app writes. So the app states it, the way a catalogue already does.

Three places honour the marker

  • build() generates nothing for the schema, and it does so before --keep carries objects forward. An app adopting the marker regenerates an existing descriptor, and the objects it wants gone are exactly the ones --keep preserves.
  • check() prints SKIP instead of demanding three, so gate 101 cannot ask for the rows the app just removed.
  • check() prints FAIL when a mock descriptor still carries objects for the schema. Skipping alone would leave the broken rows in place for ever, and those rows are the defect.

How this was verified

python3 hydra-gates/scripts/lib/test_generate_mock_register.py: 25 tests, exit 0.

Six of them are new. Reverting generate_mock_register.py and rerunning turns five of the six red:

FAILED (failures=5)
  test_a_schema_declaring_false_gets_no_objects
  test_a_string_is_accepted_in_place_of_false
  test_check_skips_an_excluded_schema_instead_of_demanding_three
  test_check_fails_when_the_excluded_schema_still_carries_objects
  test_keep_does_not_carry_forward_the_objects_being_removed

The sixth, test_true_and_absence_both_still_generate, is the control. It is green with and without the change on purpose: it is what separates "the exclusion works" from "the generator stopped generating".

Blast radius

The fleet's apps run gate 101 from ConductionNL/.github@main, so this lands everywhere the minute it merges. No app declares the marker today, so the SKIP arm and the FAIL arm are both unreachable until one does. The only behaviour change for an app that adds no marker is none.

What merges after this

ConductionNL/buildiq marks its five control-plane schemas and regenerates its dataset. That PR's gate 101 needs this one on main first, otherwise the gate asks buildiq for the three built-app rows the PR removes.

🤖 Generated with Claude Code

buildiq's register holds the schemas the app keeps about itself: the apps it
has built, their versions, the slug route index, the template store and the
export jobs it has run. ADR-111 rule 1 asks for three demo objects per schema,
so the generator wrote three of each, and on a demo instance that put three
apps in the Apps list and on the dashboard that cannot be opened. Their
applicationVersion rows carry the placeholder a format: uuid property gets
(00000000-0000-4000-8000-000000000000) and a manifest with no pages, so the
detail page renders empty and /apps/buildiq/builder/<slug>/ never resolves.
Three more sat in the template store beside the four real built-ins, and three
export-job rows read as finished exports nobody ran.

Every one of those objects satisfies its schema, which is why --check was green
on the dataset that broke the demo: conformance is about an object's shape, and
nothing in a schema says whether its rows are content somebody authors or
bookkeeping the app writes. So the app states it, the way a catalogue already
does:

    "Application": { "x-openregister-demo-data": false, ... }

A string is accepted in place of false and printed as the reason. true and
absence both mean generate, which is what every schema in the fleet does today,
so nothing changes for an app that adds no marker.

Three places honour it:

- build() generates nothing for the schema, and does so BEFORE --keep carries
  objects forward. An app adopting the marker regenerates an existing
  descriptor, and the objects it wants gone are exactly the ones --keep
  preserves.
- check() SKIPs it instead of demanding three, so the gate cannot ask for the
  rows the app just removed.
- check() FAILs when a mock descriptor still carries objects for it. Skipping
  alone would leave the broken rows in place for ever, and those rows are the
  defect.

Six arms in test_generate_mock_register.py. Five go red with the generator
reverted (5 failures); the sixth is the control that true and absence still
generate, and is green both ways on purpose.
@rubenvdlinde
rubenvdlinde merged commit 39daa76 into main Sep 18, 2026
43 checks passed
rubenvdlinde added a commit to ConductionNL/buildiq that referenced this pull request Sep 18, 2026
…e opened (#853)

* fix(demo-data): buildiq's own control-plane schemas carry no demo data

The setup walkthrough's example data imported three objects for every schema
in the buildiq register. That register holds the schemas buildiq keeps about
itself, so the import created three apps that appear in the Apps list and on
the dashboard and cannot be opened: their applicationVersion rows carry
application: 00000000-0000-4000-8000-000000000000, the placeholder a
format: uuid property gets, and a manifest with no pages, so the detail page
renders empty and /apps/buildiq/builder/<slug>/ never resolves. Three more
stood in the template store beside the four real built-ins and would clone into
an equally empty app, and three export-job rows read as finished exports nobody
ran. Seen on the demo instance on 2026-09-18, app
c5a2e155-432b-4ea8-a965-56661b991463, slug ccdc.

Every one of those objects satisfies its schema, which is why the generator and
its --check were both green. Conformance is about an object's shape; nothing in
a schema says whether its rows are content somebody authors or bookkeeping the
app writes. Eight schemas now say so with x-openregister-demo-data, the marker
ConductionNL/.github#781 adds:

  built-app             the register of apps buildiq has built
  applicationVersion    written by publish and promote
  built-app-route       an index the Application lifecycle maintains
  application-template  the store's starter packs
  export-job            written by the export pipeline
  rule-execution-log    written by the rule engine, a GDPR art. 22 record
  agent-run             written by AgentRunLogger
  formDraft             one person's half-filled form, owner-only

hello-message keeps its three objects: it is the app's content schema and the
reason the dataset exists. So do the schemas an administrator authors by hand
(automation, buildAgent, component-block, pageLayout, registrationForm and the
rule-set family): their rows are content, not bookkeeping, and none of them
generates today anyway.

Regenerated with the patched generator. The dataset goes from 18 objects to 3,
and --check exits 0 with five SKIP lines that each say why.

* fix(demo-data): the import refuses a dataset carrying buildiq's own bookkeeping

The dataset is generated, so it can regress without anybody editing it: a
generator run against a schema that has lost its declaration writes the
control-plane objects straight back. They then import in silence, because they
satisfy their schemas, OpenRegister has no opinion about what they mean, and
the wizard reports a cheerful count over three apps that cannot be opened.

DemoDataService now reads the x-openregister-demo-data declarations from the
app's own descriptors, the same file the generator reads, and refuses the
import before anything is written when the dataset addresses one of them. It
throws rather than filtering: half a dataset is not what the operator asked
for, and SetupController already surfaces the message.

Three arms cover it, plus a guard on the shipped file:

- DemoDataServiceTest: the refusal, that the importer is never called, and the
  control that an ordinary content schema still imports.
- DemoDatasetExcludesControlPlaneTest: the shipped dataset carries no object
  for a declared schema, the five control-plane schemas still declare it with a
  reason, and hello-message still carries its three objects.

Verified by mutation. Against the dataset shipped before this change the guard
test reddens naming all five schemas and three objects each; with the refusal
removed from install(), two of the three service arms redden and the control
stays green.

* style(demo-data): split the declaration reader so phpmd and phpcs are clean

The first version put path discovery, the mock filter and the per-schema
decision in one method: cyclomatic complexity 15 against a threshold of 10, an
NPath of 1188 against 200, and five phpcs errors from the `?:` on glob and an
unnamed argument. All NEW findings on lines this change wrote, so they are this
PR's to fix.

No behaviour change. phpcs, phpmd and phpmd-unusedparams all exit 0 on the
file, and the sixteen demo-data tests still pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant