Skip to content

Commit 1c4f251

Browse files
authored
Merge pull request #14 from DeepBlueCLtd/claude/waypoint-hexcell
Waypoint→HexCell migration: restore schema ≡ code, empty the adherence DRIFT map
2 parents 660aa4e + f192bd8 commit 1c4f251

12 files changed

Lines changed: 199 additions & 104 deletions

File tree

app/js/main.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,9 @@ function shareSteering() {
461461
/** @type {import('../../schema/gen/remit').SteeringDelta} */
462462
const delta = {
463463
scope: 'steering',
464-
// Schema drift (DEC-57): the app moved to hex H3 ids, but the generated
465-
// Constraint.cells is still square-grid Waypoint{x,y}. Cast until the LinkML
466-
// schema grows a hex cell type and is regenerated.
467-
constraints: cells.length
468-
? [{ type: 'no-go', cells: /** @type {import('../../schema/gen/remit').Waypoint[]} */ (/** @type {unknown} */ (cells)) }]
469-
: [],
464+
// The generated Constraint.cells is HexCell[] (h3) since the Waypoint→HexCell
465+
// migration (ADR-0030), so the app's hex no-go cells fit directly — no cast.
466+
constraints: cells.length ? [{ type: 'no-go', cells }] : [],
470467
by: 'operator',
471468
role: 'duty-officer-plans',
472469
at: new Date().toISOString(),

docs/project_notes/bugs.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ Each entry records: date, symptom, root cause, fix, and how to prevent recurrenc
249249
- **Prevention / real fix:** update the LinkML source (a hex cell type, or `Waypoint.h3`) and
250250
re-run `schema/generate.sh`, then drop the cast. Enforced type-checking (ADR-0024) now
251251
catches this class of schema/code drift at build time instead of silently.
252+
- **Resolved (2026-06-14, ADR-0030):** done — `Constraint.cells` repointed to `HexCell`, regenerated, and the
253+
`unknown``Waypoint[]` cast deleted from `main.js` (typecheck stays green without it).
252254

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

@@ -268,5 +270,10 @@ Each entry records: date, symptom, root cause, fix, and how to prevent recurrenc
268270
`Constraint.cells`, `StartState`, `TrajectoryPoint`), reconcile `appetites``Appetite[]` and `TideDecision`,
269271
re-run `schema/generate.sh`, then empty the test's `DRIFT` map. The regen-no-diff + adherence checks (ADR-0029)
270272
now make this class of drift impossible to reintroduce silently.
273+
- **Resolved (2026-06-14, ADR-0030):** done — `Asset.position`/`Constraint.cells`/`StartState`/`TrajectoryPoint`
274+
repointed to hex (`HexCell` / `h3`), `TideDecision` gained `rv_min`, and `appetites` is now modelled as an
275+
`axis→setting` map (LinkML inlined dict, `Appetite.axis` identifier) — so it matches the runtime *without*
276+
changing the kernel (golden plan ids unchanged, NF3). The adherence test's `DRIFT` map is now **empty**: it
277+
validates the instances whole.
271278

272279
<!-- Add new entries above this line. -->

docs/project_notes/decisions.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,3 +798,32 @@ consequences. Link evidence (e.g. `specs/<feature>/evidence/`) where relevant.
798798
- **Consequences:** Principle I is now enforced, not just stated — generated files are labelled, drift fails CI
799799
two ways (regen + adherence), and the schema's real gaps are visible and tracked. Scope: tooling/CI/test only;
800800
the sole schema-output change is the banner text. No `app/`/kernel code changed.
801+
802+
## ADR-0030 (2026-06-14) — Waypoint→HexCell migration: restore schema ≡ code, empty the adherence DRIFT map
803+
804+
- **Context:** the ADR-0029 adherence guard surfaced that the LinkML schema still modelled several persisted
805+
shapes as the **square-grid `Waypoint{x,y}`** even though the app went hex (H3) at ADR-0016 — `Asset.position`,
806+
`Constraint.cells`, `StartState`, `Materialisation.trajectory` — plus two non-hex drifts: `appetites` is a
807+
runtime `{axis:setting}` map vs the schema's `Appetite[]`, and `TideDecision` carried a runtime `rv_min` the
808+
schema lacked. The guard had to *strip* these (its `DRIFT` map). This closes them so the schema describes reality.
809+
- **Decision:** migrate the schema source (the modules, then regenerate) to match the real shapes — the DEC-57
810+
direction is *schema follows the skeleton's real code* (the runtime is authoritative for v1):
811+
- **Hex coordinates →** repoint `Asset.position` (orbat) and `Constraint.cells` (plan) to the existing
812+
**`HexCell`** (`{h3, lat?, lng?}`, the ADR-0016 successor to `Waypoint`); rebuild `StartState` and
813+
`TrajectoryPoint` on `h3`(+`lat`/`lng`) instead of `x`/`y`.
814+
- **`TideDecision`** gains `rv_min` (the chosen route's RV arrival the runtime publishes).
815+
- **`appetites`** is modelled as an **`axis→setting` map**`Appetite.axis` made the LinkML `identifier` and
816+
`Stamp.appetites` `inlined` (not `inlined_as_list`), so gen-json-schema emits the compact dict form that
817+
validates `{tempo:'balanced', exposure:'balanced'}` directly. Chosen over changing the kernel to emit
818+
`Appetite[]`, which would have altered the Stamp's canonical bytes → **moved every golden plan id** (NF3).
819+
- **Payoff:** the documented interim **cast is deleted**`main.js`'s `SteeringDelta` write no longer casts hex
820+
cells `as unknown as Waypoint[]` (bugs.md); they're `HexCell`s now. The adherence test drops its `DRIFT`/strip
821+
machinery and **validates the instances whole**; an undeclared-field assertion still proves it catches new drift.
822+
- **Verification:** regenerate is idempotent + byte-reproducible (the ADR-0029 regen-no-diff check passes); 32 unit
823+
tests green; **golden plan ids unchanged** (the schema is validation-only — it never touches the runtime canonical
824+
form, so NF3 holds); 0 typecheck errors. `Waypoint` itself is retained in the schema (still the generic grid
825+
location type) but is no longer referenced by these persisted shapes.
826+
- **Consequences:** Principle I's "schema ≡ code" is restored for the serialisable core; the adherence guard is now
827+
strict (nothing stripped). Out of scope (unchanged): the broader "app imports the generated TS" migration
828+
(ADR-0012, its own spec) — the app still hand-writes most shapes; only the `SteeringDelta` binding consumes
829+
generated types today.

docs/project_notes/issues.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,5 @@ evidence (e.g. `specs/<feature>/evidence/`).
6262
| 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) |
6363

6464
| 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 |
65+
66+
| 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 |

docs/project_notes/key_facts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ need a value.
4141
| 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). |
4242
| 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. |
4343
| 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. |
44-
| 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). |
44+
| 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). |
4545

4646
_Pages URLs resolve once GitHub Pages is enabled (served from `gh-pages`). Add
4747
anything else worth remembering (service URLs, IDs, constants) as it comes up._

docs/remit-data-model.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ Stamp {
174174
baseline_version, excursions: [excursion_version]
175175
config_core_hash // DEC-48: world-defining config core (medium/channels/
176176
// movement-model/providers/vocabulary); instance shell excluded
177-
profile_version, start: { x, y, clock_min } // DEC-62: own-force profile (DEC-19) + start state — the plan
178-
// depends on both, so both are identity inputs (NF3)
177+
profile_version, start: { h3, clock_min } // DEC-62: own-force profile (DEC-19) + start state (H3 hex,
178+
// ADR-0030) — the plan depends on both, so both are identity inputs (NF3)
179179
appetites: { axis → setting } // implementer's, DEC-6
180180
steering: [Constraint] // interpreted gestures, DEC-24
181181
kernel_version, strategy_seed // DEC-29: part of identity

0 commit comments

Comments
 (0)