Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions app/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,9 @@ function shareSteering() {
/** @type {import('../../schema/gen/remit').SteeringDelta} */
const delta = {
scope: 'steering',
// Schema drift (DEC-57): the app moved to hex H3 ids, but the generated
// Constraint.cells is still square-grid Waypoint{x,y}. Cast until the LinkML
// schema grows a hex cell type and is regenerated.
constraints: cells.length
? [{ type: 'no-go', cells: /** @type {import('../../schema/gen/remit').Waypoint[]} */ (/** @type {unknown} */ (cells)) }]
: [],
// The generated Constraint.cells is HexCell[] (h3) since the Waypoint→HexCell
// migration (ADR-0030), so the app's hex no-go cells fit directly — no cast.
constraints: cells.length ? [{ type: 'no-go', cells }] : [],
by: 'operator',
role: 'duty-officer-plans',
at: new Date().toISOString(),
Expand Down
7 changes: 7 additions & 0 deletions docs/project_notes/bugs.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ Each entry records: date, symptom, root cause, fix, and how to prevent recurrenc
- **Prevention / real fix:** update the LinkML source (a hex cell type, or `Waypoint.h3`) and
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.
- **Resolved (2026-06-14, ADR-0030):** done — `Constraint.cells` repointed to `HexCell`, regenerated, and the
`unknown`→`Waypoint[]` cast deleted from `main.js` (typecheck stays green without it).

## Schema-adherence guard surfaces the full extent of the schema↔code drift (2026-06-14)

Expand All @@ -268,5 +270,10 @@ Each entry records: date, symptom, root cause, fix, and how to prevent recurrenc
`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.
- **Resolved (2026-06-14, ADR-0030):** done — `Asset.position`/`Constraint.cells`/`StartState`/`TrajectoryPoint`
repointed to hex (`HexCell` / `h3`), `TideDecision` gained `rv_min`, and `appetites` is now modelled as an
`axis→setting` map (LinkML inlined dict, `Appetite.axis` identifier) — so it matches the runtime *without*
changing the kernel (golden plan ids unchanged, NF3). The adherence test's `DRIFT` map is now **empty**: it
validates the instances whole.

<!-- Add new entries above this line. -->
29 changes: 29 additions & 0 deletions docs/project_notes/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,32 @@ consequences. Link evidence (e.g. `specs/<feature>/evidence/`) where relevant.
- **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.

## ADR-0030 (2026-06-14) — Waypoint→HexCell migration: restore schema ≡ code, empty the adherence DRIFT map

- **Context:** the ADR-0029 adherence guard surfaced that the LinkML schema still modelled several persisted
shapes as the **square-grid `Waypoint{x,y}`** even though the app went hex (H3) at ADR-0016 — `Asset.position`,
`Constraint.cells`, `StartState`, `Materialisation.trajectory` — plus two non-hex drifts: `appetites` is a
runtime `{axis:setting}` map vs the schema's `Appetite[]`, and `TideDecision` carried a runtime `rv_min` the
schema lacked. The guard had to *strip* these (its `DRIFT` map). This closes them so the schema describes reality.
- **Decision:** migrate the schema source (the modules, then regenerate) to match the real shapes — the DEC-57
direction is *schema follows the skeleton's real code* (the runtime is authoritative for v1):
- **Hex coordinates →** repoint `Asset.position` (orbat) and `Constraint.cells` (plan) to the existing
**`HexCell`** (`{h3, lat?, lng?}`, the ADR-0016 successor to `Waypoint`); rebuild `StartState` and
`TrajectoryPoint` on `h3`(+`lat`/`lng`) instead of `x`/`y`.
- **`TideDecision`** gains `rv_min` (the chosen route's RV arrival the runtime publishes).
- **`appetites`** is modelled as an **`axis→setting` map** — `Appetite.axis` made the LinkML `identifier` and
`Stamp.appetites` `inlined` (not `inlined_as_list`), so gen-json-schema emits the compact dict form that
validates `{tempo:'balanced', exposure:'balanced'}` directly. Chosen over changing the kernel to emit
`Appetite[]`, which would have altered the Stamp's canonical bytes → **moved every golden plan id** (NF3).
- **Payoff:** the documented interim **cast is deleted** — `main.js`'s `SteeringDelta` write no longer casts hex
cells `as unknown as Waypoint[]` (bugs.md); they're `HexCell`s now. The adherence test drops its `DRIFT`/strip
machinery and **validates the instances whole**; an undeclared-field assertion still proves it catches new drift.
- **Verification:** regenerate is idempotent + byte-reproducible (the ADR-0029 regen-no-diff check passes); 32 unit
tests green; **golden plan ids unchanged** (the schema is validation-only — it never touches the runtime canonical
form, so NF3 holds); 0 typecheck errors. `Waypoint` itself is retained in the schema (still the generic grid
location type) but is no longer referenced by these persisted shapes.
- **Consequences:** Principle I's "schema ≡ code" is restored for the serialisable core; the adherence guard is now
strict (nothing stripped). Out of scope (unchanged): the broader "app imports the generated TS" migration
(ADR-0012, its own spec) — the app still hand-writes most shapes; only the `SteeringDelta` binding consumes
generated types today.
2 changes: 2 additions & 0 deletions docs/project_notes/issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ evidence (e.g. `specs/<feature>/evidence/`).
| 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 |

| 2026-06-14 | `claude/waypoint-hexcell` | **Waypoint→HexCell migration — restore schema ≡ code (ADR-0030).** Closed the drift the ADR-0029 adherence guard surfaced: repointed `Asset.position` / `Constraint.cells` / `StartState` / `TrajectoryPoint` onto hex (`HexCell` / `h3`, the ADR-0016 successor to `Waypoint`), added `TideDecision.rv_min`, and modelled `Stamp.appetites` as an `axis→setting` map (LinkML inlined dict, `Appetite.axis` identifier) — matching the runtime *without* changing the kernel, so **golden plan ids are unchanged** (NF3; the schema is validation-only). Deleted the documented interim `unknown`→`Waypoint[]` cast in `main.js` (bugs.md resolved). The adherence test's `DRIFT` map is now **empty** — it validates ORBAT + Plan instances whole. Regenerated (idempotent, regen-no-diff green); 32 unit green; 0 typecheck errors. Stacked on #13. | ADR-0030 |
2 changes: 1 addition & 1 deletion docs/project_notes/key_facts.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +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). |
| 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`. Validates ORBAT + Plan instances **whole** — no stripping (the Waypoint→HexCell migration, ADR-0030, closed the earlier drifts: `HexCell` positions/cells/start/trajectory, `TideDecision.rv_min`, `appetites` as an `axis→setting` map). The whole `test:unit` suite 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._
4 changes: 2 additions & 2 deletions docs/remit-data-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ Stamp {
baseline_version, excursions: [excursion_version]
config_core_hash // DEC-48: world-defining config core (medium/channels/
// movement-model/providers/vocabulary); instance shell excluded
profile_version, start: { x, y, clock_min } // DEC-62: own-force profile (DEC-19) + start state — the plan
// depends on both, so both are identity inputs (NF3)
profile_version, start: { h3, clock_min } // DEC-62: own-force profile (DEC-19) + start state (H3 hex,
// ADR-0030) — the plan depends on both, so both are identity inputs (NF3)
appetites: { axis → setting } // implementer's, DEC-6
steering: [Constraint] // interpreted gestures, DEC-24
kernel_version, strategy_seed // DEC-29: part of identity
Expand Down
Loading
Loading