From 987a5264b6e27fee6f29b5f7aaaced20c7a5053a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 10:45:26 +0000 Subject: [PATCH] =?UTF-8?q?feat(schema):=20LinkML=20guardrails=20=E2=80=94?= =?UTF-8?q?=20GENERATED=20banners,=20regen-no-diff=20CI,=20adherence=20tes?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes Principle I (LinkML = source of truth, DEC-57) enforceable rather than aspirational — the three deferred ADR-0011/0012 follow-ups: - GENERATED banners on every derived artefact (generate.sh stamps remit.ts / remit.schema.json / index.html) + .gitattributes linguist-generated. - regen-no-diff CI (schema-regen.yml): regenerates from the schema and fails on any schema/gen + site/data-model drift; toolchain pinned (linkml / linkml-runtime==1.11.1, Python 3.11), byte-reproducible. - schema-adherence test (test/schema-adherence.test.mjs, ajv dev-only): validates a committed Orbat + a kernel Plan against the generated JSON Schema; wired into a new unit.yml CI job (the unit suite never ran in CI before — only e2e + typecheck did). The adherence guard immediately surfaced the full extent of the documented Waypoint hex/square drift (Asset.position, Stamp.start, Materialisation.trajectory) plus appetites map-vs-list and TideDecision (bugs.md) — stripped and tracked via the test's DRIFT map; the Waypoint->HexCell migration is the surfaced follow-up. ajv added as a dev-only dependency (ADR-0014-approved, test-only — never imported by app/ or the kernel). 32 unit tests (+2) green; 0 typecheck errors. ADR-0029. https://claude.ai/code/session_01EhtBoKXg6bHnKdquacyknf --- .gitattributes | 5 ++ .github/workflows/schema-regen.yml | 39 ++++++++++ .github/workflows/unit.yml | 33 +++++++++ docs/project_notes/bugs.md | 19 +++++ docs/project_notes/decisions.md | 38 ++++++++++ docs/project_notes/issues.md | 2 + docs/project_notes/key_facts.md | 1 + package-lock.json | 59 +++++++++++++++ package.json | 1 + schema/gen/remit.schema.json | 1 + schema/gen/remit.ts | 5 ++ schema/generate.sh | 35 ++++++++- site/data-model/index.html | 1 + test/schema-adherence.test.mjs | 115 +++++++++++++++++++++++++++++ 14 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/schema-regen.yml create mode 100644 .github/workflows/unit.yml create mode 100644 test/schema-adherence.test.mjs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dadb33d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Generated artefacts (DEC-57 / ADR-0011): produced by schema/generate.sh from the +# LinkML schema and enforced by the schema-regen CI check — never hand-edited. +# Marked generated so GitHub collapses them in diffs and omits them from language stats. +schema/gen/** linguist-generated=true +site/data-model/index.html linguist-generated=true diff --git a/.github/workflows/schema-regen.yml b/.github/workflows/schema-regen.yml new file mode 100644 index 0000000..26862f9 --- /dev/null +++ b/.github/workflows/schema-regen.yml @@ -0,0 +1,39 @@ +name: schema-regen + +# Guards Principle I (LinkML is the one source of truth, DEC-57/ADR-0011): the +# committed artefacts under schema/gen/ and site/data-model/ are GENERATED, never +# hand-edited. This regenerates them from the schema and fails if anything drifts, +# so the generated files can never silently fall out of step with schema/*.yaml. + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: schema-regen-${{ github.ref }} + cancel-in-progress: true + +jobs: + regen: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Match the toolchain generate.sh pins (linkml==1.11.1) was verified against, + # so regeneration is byte-for-byte reproducible. + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Regenerate every derived artefact from the LinkML schema + run: bash schema/generate.sh + + - name: Fail if the generated artefacts are stale (schema ≡ generated) + run: | + if ! git diff --exit-code -- schema/gen site/data-model; then + echo "::error::Generated artefacts are out of date. Run 'bash schema/generate.sh' and commit schema/gen/ + site/data-model/index.html." + exit 1 + fi diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 0000000..021ec4b --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,33 @@ +name: unit + +# Runs the node --test unit suite in CI. Until now only e2e + typecheck ran on PRs, +# so the golden kernel fixtures (NF3), ORBAT model, routing/hexgrid and the new +# schema-adherence guard (ADR-0029) were verified locally only. Build-free. + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: unit-${{ github.ref }} + cancel-in-progress: true + +jobs: + unit: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Unit tests + run: npm run test:unit diff --git a/docs/project_notes/bugs.md b/docs/project_notes/bugs.md index 1af6c79..a1cbc16 100644 --- a/docs/project_notes/bugs.md +++ b/docs/project_notes/bugs.md @@ -250,4 +250,23 @@ Each entry records: date, symptom, root cause, fix, and how to prevent recurrenc re-run `schema/generate.sh`, then drop the cast. Enforced type-checking (ADR-0024) now catches this class of schema/code drift at build time instead of silently. +## Schema-adherence guard surfaces the full extent of the schema↔code drift (2026-06-14) + +- **Symptom:** the new schema-adherence test (ADR-0029, `test/schema-adherence.test.mjs`) validates real + instances against the generated JSON Schema and found `Asset.position`, `Stamp.start` and + `Materialisation.trajectory` fail `additionalProperties:false` — runtime hex `{h3,lat,lng}` vs the schema's + square-grid `Waypoint`/`StartState`/`TrajectoryPoint` `{x,y}`. It also flagged two non-hex drifts: the kernel's + `appetites` is a `{axis:setting}` map where the schema models `Appetite[] {axis,setting}`, and `TideDecision` + carries runtime fields beyond the schema's. +- **Root cause:** the same drift as the `SteeringDelta`/`Constraint.cells` entry above — the app moved to H3 hex + (ADR-0016) and grew kernel shapes that the LinkML source was never updated to match; the DEC-57 "schema ≡ code" + invariant has drifted on these classes too, not just `SteeringDelta`. +- **Fix (interim):** the adherence test strips these documented fields (its `DRIFT` map) before strict validation, + so the guard is green and still catches any NEW drift. The drift stays visible (here + the test's comments), + never hidden. +- **Real fix:** the Waypoint→HexCell migration (add a hex cell type / `Waypoint.h3`; repoint `Asset.position`, + `Constraint.cells`, `StartState`, `TrajectoryPoint`), reconcile `appetites`→`Appetite[]` and `TideDecision`, + re-run `schema/generate.sh`, then empty the test's `DRIFT` map. The regen-no-diff + adherence checks (ADR-0029) + now make this class of drift impossible to reintroduce silently. + diff --git a/docs/project_notes/decisions.md b/docs/project_notes/decisions.md index 38e59b2..affa94e 100644 --- a/docs/project_notes/decisions.md +++ b/docs/project_notes/decisions.md @@ -760,3 +760,41 @@ consequences. Link evidence (e.g. `specs//evidence/`) where relevant. - **Remaining skeleton notes:** World-before-Capture tool-order, mock band-calibration, and the `entities/` vs `views/projection/` module placement were reviewed and **held as-is** (no register change); the mid-stream coincidence H2→H1-lite edit (ADR-0009/DEC-53) was already reconciled. Closes issue #3. + +## ADR-0029 (2026-06-14) — LinkML guardrails: GENERATED banners, regen-no-diff CI, schema-adherence test + +- **Context:** ADR-0011/0012 adopted LinkML as the one source of truth (DEC-57) and logged three deferred + guardrails to make Principle I *enforceable* rather than aspirational: GENERATED banners on the derived + artefacts, a regen-no-diff CI check, and a golden-fixtures adherence test (skeleton instances validate against + the generated JSON Schema). This lands all three. (The fourth ADR-0012 note — migrating the app's inline shapes + onto the generated TS — remains its own spec.) +- **Decision:** + - **GENERATED banners.** `schema/generate.sh` now stamps every derived file `@generated — DO NOT EDIT` (a `//` + block on `remit.ts`, a `$comment` first-key on `remit.schema.json` — textual insert, no reformat — and an HTML + comment on `index.html`), plus a `.gitattributes` marking them `linguist-generated`. The banner is re-applied + each run, so it survives regeneration. + - **regen-no-diff CI** (`.github/workflows/schema-regen.yml`): regenerates from the schema and fails on any diff + under `schema/gen/` + `site/data-model/`, so the committed artefacts can never silently fall out of step with + `schema/*.yaml`. Reproducibility pinned — `generate.sh` installs `linkml==1.11.1` + `linkml-runtime==1.11.1`, + CI uses Python 3.11; verified to reproduce the committed bytes exactly (empty `git diff`). + - **Schema-adherence test** (`test/schema-adherence.test.mjs`, `node --test`): builds real instances — a + committed `Orbat` (red/green/own-force) and a kernel `Plan` (Stamp/Scores/Materialisation) — and validates + them against `schema/gen/remit.schema.json` with **ajv** (dev-only; draft-2019-09). Documented pre-existing + drifts are stripped per class (its `DRIFT` map) and the rest validated strictly, so the guard is green yet + still fails on any NEW drift (proven by an undeclared-field assertion). Wired into a new **`unit.yml`** CI job + (`npm run test:unit`) — which also closes the gap that the unit suite (golden fixtures, ORBAT, routing) had + never run in CI, only e2e + typecheck did, so the adherence guard (and all the others) now actually fire on PRs. +- **The guard earned its keep immediately:** it confirmed the documented `Waypoint` square-vs-hex drift (bugs.md) + extends beyond `SteeringDelta` to `Asset.position`, `Stamp.start` and `Materialisation.trajectory`, and surfaced + two more — the kernel carries `appetites` as a `{axis:setting}` map where the schema models `Appetite[]`, and + `TideDecision` shapes differently. Recorded (bugs.md) and stripped via `DRIFT`; the Waypoint→HexCell migration + + appetites/tide reconciliation (then emptying `DRIFT`) is the concrete next follow-up this guard makes visible. +- **Dependency (ADR-0014):** adds `ajv` as a **dev**-only dependency (test-only; never imported by `app/` or the + kernel), maintainer-approved for this guard. No runtime dependency added. +- **Options considered:** (a) ajv vs a hand-rolled validator — ajv, for faithful draft-2019-09 validation (a + hand-rolled check can't honestly resolve `$ref`/`anyOf`/`additionalProperties`); (b) banner via reserialisation + vs textual insert — textual, to leave gen-json-schema's bytes untouched (zero churn); (c) fixing the surfaced + drift now vs strip-and-track — tracked, to keep this PR a pure guardrail, not a schema migration. +- **Consequences:** Principle I is now enforced, not just stated — generated files are labelled, drift fails CI + two ways (regen + adherence), and the schema's real gaps are visible and tracked. Scope: tooling/CI/test only; + the sole schema-output change is the banner text. No `app/`/kernel code changed. diff --git a/docs/project_notes/issues.md b/docs/project_notes/issues.md index b8f49ae..73978df 100644 --- a/docs/project_notes/issues.md +++ b/docs/project_notes/issues.md @@ -60,3 +60,5 @@ evidence (e.g. `specs//evidence/`). | 2026-06-14 | `claude/spec-04-implement-0u0s9y` | **ORBAT asset enrichment (spec 005, ADR-0027).** Display-only, additive enrichment of the SME-Int ORBAT (NF9): shared `kind` (`PlatformKind` enum) → allegiance-framed map **symbols** (deck.gl `TextLayer` glyphs via a `SYMBOLS` lookup, no icon atlas) with a per-asset `symbol` override; intel `confidence` (reusing `ConfidenceLevel`) → marker **opacity**; red **dual range rings** (`detection_range_m`/`engagement_range_m`, `engagement ≤ detection`) replacing the single extent for red; descriptive `strength`/`notes` + red `threat_type` / green `category` (`GreenCategory` enum) / blue `role`. All schema-defined + regenerated (Principle I); model/panel/map extended in place. Backward-compatible: idempotent `normalize()` migrates spec-004 red drafts (`extent_m` → detection). 12 new unit (30 total) + 4 new e2e (9 total) green; 0 typecheck errors. Deferred: place-on-map + NATO frame shapes (later slice); routing influence (DEC-51). | ADR-0027 · `specs/005-orbat-asset-enrichment/` | | 2026-06-14 | issue [#3](https://github.com/DeepBlueCLtd/REMIT/issues/3) | **Walking-skeleton gate reconciliation (DEC-47 → register DEC-62).** Closed the three held skeleton deviations at the skeleton-complete gate: (A) the stamp gains `profile_version`+`start` identity axes (refines DEC-29/35); (B) `Plan.id = hash(Stamp ⊕ strategy)` within-handful discriminator (clarifies DEC-29); (C) the no-build `// @ts-check`+JSDoc approach ratified as DEC-41's TypeScript realisation (ADR-0024 typecheck + DEC-57 generated TS), a caveat not a reversal. All three were already baked into the LinkML schema (DEC-57); here recorded in the Doc-owned register (DEC-62, v28) + prose spine §6/§7 + skeleton spec gate note. Remaining notes (tool-order, band-calibration, module placement) held as-is. **Docs/governance only — no schema or code change.** | ADR-0028 · DEC-62 · [#3](https://github.com/DeepBlueCLtd/REMIT/issues/3) | + +| 2026-06-14 | `claude/linkml-guardrails` | **LinkML guardrails — ADR-0011/0012 deferred follow-ups (ADR-0029).** Made Principle I (LinkML = source of truth, DEC-57) *enforceable*: (1) **GENERATED banners** on every derived artefact via `schema/generate.sh` (`remit.ts` `//` block, `remit.schema.json` `$comment` first-key, `index.html` HTML comment) + `.gitattributes linguist-generated`; (2) **regen-no-diff CI** (`.github/workflows/schema-regen.yml`) — regenerates from the schema (pinned `linkml`/`linkml-runtime==1.11.1`, Python 3.11, byte-reproducible) and fails on any `schema/gen/`+`site/data-model/` diff; (3) **schema-adherence test** (`test/schema-adherence.test.mjs`, `ajv` dev-only, draft-2019-09) validating a committed `Orbat` + a kernel `Plan` against the generated JSON Schema, wired into a new **`unit.yml`** CI job (also closing the gap that `test:unit` had never run in CI — only e2e + typecheck did). The guard immediately surfaced the full extent of the Waypoint hex/square drift (`Asset.position`/`Stamp.start`/`Materialisation.trajectory`) + appetites map-vs-list + `TideDecision` (bugs.md) — stripped+tracked via its `DRIFT` map; the Waypoint→HexCell migration is the surfaced follow-up. 32 unit (+2) green; 0 typecheck errors. Dev-dep `ajv` (ADR-0014-approved, test-only). | ADR-0029 | diff --git a/docs/project_notes/key_facts.md b/docs/project_notes/key_facts.md index cb6f897..921a8c9 100644 --- a/docs/project_notes/key_facts.md +++ b/docs/project_notes/key_facts.md @@ -41,6 +41,7 @@ need a value. | ORBAT allegiance palette (004) | blue (own force) `#4493f8` · red (hostile) `#ff7b72` · green (neutral) `#38d39f` (`ALLEGIANCE_COLOR` in `orbat.js`; mirrored in `map.js` markers + Sync-Matrix tracks). | | ORBAT bounds / persistence (004) | `extent_m` 100..20000 m · `severity`/`sensitivity` 1..5 · `protection` ∈ {`keep_out`,`minimise_effect`}. Working draft mirrors to `localStorage['remit.orbat.M-001']` (canonical JSON, survives reload); commit mints an immutable content-addressed `Orbat` in the `ObjectStore` with lineage. | | ORBAT enrichment (005) | Display-only, additive (ADR-0027): `Asset.kind` (`PlatformKind`: infantry/vehicle/aircraft/vessel/sensor/emplacement/structure) → map **symbol** (`SYMBOLS` glyph lookup in `orbat.js`, deck.gl `TextLayer`, no icon atlas) + per-asset `symbol` override; `Asset.confidence` (`ConfidenceLevel`) → marker **opacity** `{high:1, medium:0.6, low:0.35}` (absent ⇒ 1); `Asset.strength`/`notes` (free text); red `RedParams.detection_range_m`/`engagement_range_m` (dual rings, `engagement ≤ detection`) + `threat_type`; green `GreenParams.category` (`GreenCategory`: hospital/school/utility/place_of_worship/residential/other); blue `BlueParams.role`. `normalize()` (in `loadDraft`) migrates spec-004 red drafts `extent_m`→`detection_range_m`. Vocab fields ignore invalid values; free-text trims + drops-empty. | +| Schema guardrails (ADR-0029) | Generated artefacts are enforced, not just labelled: `schema/generate.sh` stamps `@generated` banners on `schema/gen/*` + `site/data-model/index.html` (+ `.gitattributes linguist-generated`) and pins `linkml`/`linkml-runtime==1.11.1`; **regen-no-diff CI** (`.github/workflows/schema-regen.yml`, Python 3.11) fails on any `schema/gen/`+`site/data-model/` drift; **schema-adherence test** (`test/schema-adherence.test.mjs`, `ajv` dev-only, draft-2019-09) validates a committed `Orbat` + a kernel `Plan` against `remit.schema.json`. Known drifts stripped via the test's `DRIFT` map (Waypoint→HexCell; appetites map/list; `TideDecision` — bugs.md). The whole `test:unit` suite now runs in CI via **`.github/workflows/unit.yml`** (previously only e2e + typecheck ran). | _Pages URLs resolve once GitHub Pages is enabled (served from `gh-pages`). Add anything else worth remembering (service URLs, IDs, constants) as it comes up._ diff --git a/package-lock.json b/package-lock.json index 3668def..ee6525d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "devDependencies": { "@playwright/test": "^1.60.0", "@sparticuz/chromium": "^149.0.0", + "ajv": "^8.20.0", "typescript": "^5.7.2", "vite": "^8.0.16" } @@ -1273,6 +1274,23 @@ "gl-matrix": "^3.4.3" } }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1683,6 +1701,13 @@ "bare-events": "^2.7.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -1690,6 +1715,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-builder": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", @@ -1886,6 +1928,13 @@ "node": ">=0.8" } }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stringify-pretty-compact": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", @@ -2503,6 +2552,16 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-protobuf-schema": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", diff --git a/package.json b/package.json index 7cc7573..adbd718 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "devDependencies": { "@playwright/test": "^1.60.0", "@sparticuz/chromium": "^149.0.0", + "ajv": "^8.20.0", "typescript": "^5.7.2", "vite": "^8.0.16" }, diff --git a/schema/gen/remit.schema.json b/schema/gen/remit.schema.json index e881d43..393b702 100644 --- a/schema/gen/remit.schema.json +++ b/schema/gen/remit.schema.json @@ -1,4 +1,5 @@ { + "$comment": "@generated — DO NOT EDIT. Generated by schema/generate.sh from the LinkML schema (schema/remit.yaml, DEC-57); edit the schema and re-run it. Drift is caught by the schema-regen CI check (ADR-0011).", "$defs": { "AOPackage": { "additionalProperties": false, diff --git a/schema/gen/remit.ts b/schema/gen/remit.ts index 71ce42d..2bda767 100644 --- a/schema/gen/remit.ts +++ b/schema/gen/remit.ts @@ -1,3 +1,8 @@ +// @generated — DO NOT EDIT. +// Generated by schema/generate.sh from the LinkML schema (schema/remit.yaml, DEC-57). +// Edit the schema modules under schema/ and re-run `bash schema/generate.sh`. +// Drift from the schema is caught by the schema-regen CI check (ADR-0011). + export type RequirementId = string; export type CommitmentId = string; export type BaselineId = string; diff --git a/schema/generate.sh b/schema/generate.sh index b4a8651..b4659bc 100755 --- a/schema/generate.sh +++ b/schema/generate.sh @@ -17,7 +17,10 @@ if [ ! -x "$VENV/bin/linkml" ] && [ ! -x "$VENV/bin/gen-json-schema" ]; then echo "Bootstrapping LinkML toolchain in $VENV ..." python3 -m venv "$VENV" "$VENV/bin/pip" install --quiet --upgrade pip setuptools wheel - "$VENV/bin/pip" install --quiet linkml + # Pinned so generation is reproducible — the schema-regen CI check (ADR-0011) + # regenerates and fails on any diff, so the toolchain version must be fixed + # (linkml-runtime too: it drives the generators' output). + "$VENV/bin/pip" install --quiet "linkml==1.11.1" "linkml-runtime==1.11.1" fi V="$VENV/bin" @@ -28,4 +31,34 @@ echo "→ TypeScript schema/gen/remit.ts" "$V/gen-typescript" "$SCHEMA" > schema/gen/remit.ts echo "→ HTML ref site/data-model/index.html" "$V/python" schema/build-reference.py + +# --- GENERATED banners (ADR-0011) ---------------------------------------------- +# Stamp every derived artefact "do not edit", added here so the marker survives +# every regeneration (the generators above rewrite each file from scratch, so the +# banner is re-applied once per run — idempotent). The schema-regen CI check then +# enforces that the committed files match this script's output byte-for-byte. +echo "→ banners schema/gen/*, site/data-model/index.html" + +TS=schema/gen/remit.ts +{ cat <<'BANNER' +// @generated — DO NOT EDIT. +// Generated by schema/generate.sh from the LinkML schema (schema/remit.yaml, DEC-57). +// Edit the schema modules under schema/ and re-run `bash schema/generate.sh`. +// Drift from the schema is caught by the schema-regen CI check (ADR-0011). + +BANNER + cat "$TS"; } > "$TS.tmp" && mv "$TS.tmp" "$TS" + +# JSON has no comments — insert a "$comment" as the first key (textual, so the rest +# of gen-json-schema's output is left byte-for-byte unchanged). +JSON=schema/gen/remit.schema.json +JSON_MSG='@generated — DO NOT EDIT. Generated by schema/generate.sh from the LinkML schema (schema/remit.yaml, DEC-57); edit the schema and re-run it. Drift is caught by the schema-regen CI check (ADR-0011).' +{ head -n 1 "$JSON" + printf ' "$comment": "%s",\n' "$JSON_MSG" + tail -n +2 "$JSON"; } > "$JSON.tmp" && mv "$JSON.tmp" "$JSON" + +HTML=site/data-model/index.html +{ printf '%s\n' '' + cat "$HTML"; } > "$HTML.tmp" && mv "$HTML.tmp" "$HTML" + echo "Done." diff --git a/site/data-model/index.html b/site/data-model/index.html index 28df6d1..26650de 100644 --- a/site/data-model/index.html +++ b/site/data-model/index.html @@ -1,3 +1,4 @@ + REMIT — v1 Data Model