diff --git a/app/js/wingman/wingman.js b/app/js/wingman/wingman.js index 1787c4a..ca58c1d 100644 --- a/app/js/wingman/wingman.js +++ b/app/js/wingman/wingman.js @@ -230,6 +230,25 @@ export function mountWingman(el, ctx) { `
≋ H+${Math.round(exec.simT)} — tide re-assessed: ${oldMode} → ${cur.mode} · ${cur.narrative}
`; } + // Capture an in-flight perturbation as a typed, content-addressed store object + // (issue #7), not just a prose log note: structured inputs that survive for the + // Data Analysis monitor and replay (NF3). Re-uses the DEC-61 attributed-delta + // write path (cf. SteeringDelta) — operator wearing the Ops hat. Non-fatal: the + // append-only log entry already records the event, so a failed share never + // breaks the live loop. + async function shareExecutionDelta(/** @type {Partial} */ fields) { + /** @type {import('../../../schema/gen/remit').ExecutionDelta} */ + const delta = { + scope: 'execution', by: 'operator', role: 'duty-officer-ops', + at: new Date().toISOString(), + event: 'obstruction', + ...fields, + }; + await ctx.seam.putObject('ExecutionDelta', delta).catch((/** @type {any} */ err) => + $('#wx-alerts').innerHTML += + `
could not share execution delta (logged locally): ${err?.message ?? err}
`); + } + async function addObstruction(/** @type {number} */ mins) { if (exec.complete || !world) return; const tau = exec.lastTau; @@ -274,6 +293,14 @@ export function mountWingman(el, ctx) { }, source: 'operator', confidence: 'reported', }); + await shareExecutionDelta({ + event: 'obstruction', + at_min: Math.round(exec.simT), + cell: { h3: cell.h3, lat: cell.lat, lng: cell.lng }, + delay_min: mins, + rv_min: missionEnd, + absorbed_min: absorbed > 0 ? absorbed : 0, + }); await maybeTideAlert(oldMode); await refreshLog(); await tick(0); @@ -318,6 +345,12 @@ export function mountWingman(el, ctx) { fact_delta: { note: `Cell ${shortH3(next.h3)} blocked — re-routed in flight`, tag: 'track-state' }, source: 'operator', confidence: 'reported', }); + await shareExecutionDelta({ + event: 'block', + at_min: Math.round(exec.simT), + cell: { h3: next.h3, lat: next.lat, lng: next.lng }, + rv_min: missionEnd, + }); await maybeTideAlert(oldMode); await refreshLog(); await tick(0); diff --git a/docs/project_notes/decisions.md b/docs/project_notes/decisions.md index b33035c..cb88086 100644 --- a/docs/project_notes/decisions.md +++ b/docs/project_notes/decisions.md @@ -631,3 +631,33 @@ consequences. Link evidence (e.g. `specs//evidence/`) where relevant. (new typedefs incl. `HexAO`, `Strat`/`Leg`/`RerouteOpts`, `Entity`/`Aspect`). `app/hexviz.js` (a standalone evidence page) is outside `include`; type-checking the Node-side tooling (`test`/`e2e`/`run-playwright.mjs`) is a possible follow-up under a second, Node-lib config. + +## ADR-0025 (2026-06-12) — Capture execution-phase perturbations as typed `ExecutionDelta` store objects (issue #7) + +- **Context:** the Execute phase lets the operator perturb a live run — an obstruction + (`+5`/`+25 min` hold) or a blocked cell forcing a re-route. Both appended a prose + `Observation` to the append-only execution log, but their *structured* inputs (cell, + minutes, resulting RV) lived only in transient wingman UI state. Nothing reached the + content-addressed object store, so — unlike `SteeringDelta` (the first DEC-61 write) — the + perturbations were invisible to the Data Analysis monitor and carried no structured inputs + for replay (NF3). The maintainer raised this as issue #7: *"they aren't captured in the + model. Should they be?"* +- **Decision:** **Yes.** Model the perturbation in LinkML and write it via the existing + DEC-61 attributed-delta path. Added `ExecutionEventKind` (`obstruction`|`block`) + a + `HexCell` value object (`h3` + optional `lat`/`lng`, the hex successor to `Waypoint`) to + `common`, and a concrete `ExecutionDelta (is_a: Delta)` to `records` (`event`, `at_min`, + `cell`, `delay_min`, `rv_min`, `absorbed_min`); regenerated `schema/gen/` + the HTML + reference. The wingman calls `seam.putObject('ExecutionDelta', …)` on each edit, attributed + to the operator wearing the **Ops** hat (`role: 'duty-officer-ops'`). +- **Options considered:** (a) pack the fields into the prose log entry — still invisible to + the store-backed monitor/replay; rejected. (b) a hand-written wingman UI-state object — + violates the LinkML one-source-of-truth rule (ADR-0012) the moment it crosses into the + shared store; rejected. (c) a generated typed delta mirroring `SteeringDelta` — **chosen**. +- **Consequences:** in-flight edits now appear in Data Analysis (with change-glow) and are + structurally preserved for replay. The prose log entry is **kept** (it reads well in the + after-action record) — the delta is its structured sibling, not a replacement; the share is + **non-fatal** (a failed write never breaks the live loop, since the log already recorded + the event). **Not done:** re-running a stamp does not yet *re-apply* the captured + perturbations (they're exogenous operator acts) — capturing the inputs is the prerequisite, + automatic re-application is the follow-up. `HexCell` is now the natural type to migrate the + documented square-vs-hex `Constraint.cells` drift in `SteeringDelta` onto (bugs.md). diff --git a/docs/project_notes/issues.md b/docs/project_notes/issues.md index 500527e..1a33085 100644 --- a/docs/project_notes/issues.md +++ b/docs/project_notes/issues.md @@ -51,4 +51,6 @@ evidence (e.g. `specs//evidence/`). | 2026-06-12 | PR #5 | Just-in-time departure (maintainer suggestion). A COA whose exfil would hold *at the exposed bank* for the tide now **leaves base late instead**, reaching the wath exactly at low water — same RV, no exposed wait. The tide-waiting (blue/`tracked`) COA departs ~51 min after the drive-the-road (`direct`) COA, so you watch **two vehicles move at different times** rather than one overlapping marker (`kernel.js materialise()`). Schedule becomes `[hold(delay), transit, visit, exfil]`; plan ids unchanged (they key off the decision inputs, not the materialisation — NF3), so only the golden's schedule/wait assertions moved. 12 unit + 12 e2e green. | ADR-0023 · [#5](https://github.com/DeepBlueCLtd/REMIT/pull/5) | +| 2026-06-12 | issue [#7](https://github.com/DeepBlueCLtd/REMIT/issues/7) | **Capture execution-phase edits in the model (ADR-0025).** In-flight obstructions/blocks during Execute appended a prose `Observation` to the log but never reached the content-addressed store, so their structured inputs were invisible to Data Analysis and absent for replay (NF3). Modelled them in LinkML — `ExecutionEventKind` + a `HexCell` value object (hex successor to `Waypoint`) in `common`, a concrete `ExecutionDelta(is_a Delta)` in `records` (`event`/`at_min`/`cell`/`delay_min`/`rv_min`/`absorbed_min`); regenerated `schema/gen/` + HTML ref (62→64 classes, 20→21 enums). The wingman now `seam.putObject('ExecutionDelta', …)` on each edit (operator wearing the Ops hat), mirroring `SteeringDelta`; prose log kept, share non-fatal. New e2e capture test; 0 typecheck errors, 12 unit green. Replay re-application deferred (follow-up). | ADR-0025 · `specs/004-capture-execution-deltas/` | + | 2026-06-12 | [#8](https://github.com/DeepBlueCLtd/REMIT/pull/8) | **Enforced the JSDoc type-checking the app already declared (ADR-0024).** Every `app/js` file carried `// @ts-check` + JSDoc but nothing ran it; added root `tsconfig.json` (checkJs+strict, noEmit, types:[], DOM/bundler), pinned `typescript`, added `npm run typecheck`, and a `typecheck.yml` CI gate (PR + push-to-main). Drove the app **442 → 0** type errors across 18 files — precise types for DOM/primitives/shared shapes (new typedefs `HexAO`, `Strat`/`Leg`/`RerouteOpts`, `Cell`/`Profile`/`TrajPoint`, `Entity`/`Aspect`…), `any` only for opaque serialisable blobs (plan/requirement/log/viz, per ADR-0012); **zero `@ts-ignore`/`@ts-nocheck`**. Fixed real drift it surfaced: stale pre-hex `wingman.js` annotations (`grid`→`ao`, `{x,y}`→`{h3}` callbacks), two `stateAt` null-guards, an `Entity.domain` tuple, a `@types/node` `setTimeout`→`Timeout` leak, and a documented **SteeringDelta hex-vs-square schema drift** (both in `bugs.md`). No source migrated to `.ts`, no runtime build step added; 12 unit green, e2e in CI. | ADR-0024 · [#8](https://github.com/DeepBlueCLtd/REMIT/pull/8) | diff --git a/e2e/skeleton.spec.ts b/e2e/skeleton.spec.ts index 31470b1..a568edb 100644 --- a/e2e/skeleton.spec.ts +++ b/e2e/skeleton.spec.ts @@ -152,6 +152,39 @@ test('the hex grid can be toggled off and on (basemap shows through)', async ({ await expect(map).toHaveAttribute('data-hexes', 'on'); // and back }); +test('execute: an obstruction is captured in the model as a typed ExecutionDelta (issue #7)', async ({ page }) => { + await page.goto('/'); + await walkToPlan(page); + await page.getByTestId('continue-compare').click(); + await page.getByTestId('pick-direct').check(); + await page.getByTestId('cmp-commit').click(); + await page.getByTestId('continue-execute').click(); + + // Step past the visit into the exfil drive (where an obstruction applies — the + // vehicle is on the move), then insert a +25 min obstruction. + for (let i = 0; i < 7; i++) await page.getByTestId('wx-step10').click(); + await page.getByTestId('wx-delay').click(); + + // The perturbation lands in the shared content store as a typed ExecutionDelta — + // structured inputs (event/cell/delay), not just a prose log note (issue #7). + await expect.poll(() => page.evaluate(() => { + const w = (window as any).__remit; + const rec = w.objects.list().reverse().find((o: any) => o.type === 'ExecutionDelta'); + if (!rec) return false; + const d = w.objects.get(rec.id).body; + return d.event === 'obstruction' && d.delay_min === 25 && typeof d.cell?.h3 === 'string'; + })).toBe(true); + + // It surfaces in the Data Analysis monitor like any other store object. + await page.getByTestId('tab-data-analysis').click(); + await expect(page.getByTestId('da-index')).toContainText('ExecutionDelta'); + await page.locator('.da-group[data-group="ExecutionDelta"] .da-row').first().click(); + await page.screenshot({ + path: 'specs/004-capture-execution-deltas/evidence/screenshots/01-execution-delta.png', + fullPage: true }); + await noFault(page); +}); + test('execute: blocking the next hex re-routes in flight', async ({ page }) => { await page.goto('/'); await walkToPlan(page); diff --git a/schema/common.yaml b/schema/common.yaml index 86ce76f..22cbc72 100644 --- a/schema/common.yaml +++ b/schema/common.yaml @@ -161,6 +161,13 @@ enums: Observation: {} Waiver: {} Replan: {} + ExecutionEventKind: + description: The kind of in-flight operator perturbation applied during Execute (issue #7). + permissible_values: + obstruction: + description: a +N min hold spliced at the vehicle's current cell, re-timed through the tide-aware chooser (ADR-0006/0007) + block: + description: a cell ahead declared impassable, forcing an in-flight re-route around it classes: Attribution: description: Who decided, under what authority, when. Stamped on every committing act — this is universal rule 2 (DEC-15, NF2). @@ -202,3 +209,15 @@ classes: required: true alias: description: a human-friendly name, e.g. "OP-21,3" + HexCell: + description: A single H3 hex location — the hex-grid successor to Waypoint (ADR-0014). Identified by its H3 index, with an optional lat/lng centre for rendering. + attributes: + h3: + required: true + description: the H3 cell index (res 9) + lat: + range: float + description: cell-centre latitude + lng: + range: float + description: cell-centre longitude diff --git a/schema/gen/remit.schema.json b/schema/gen/remit.schema.json index 0380832..a41faaa 100644 --- a/schema/gen/remit.schema.json +++ b/schema/gen/remit.schema.json @@ -1290,6 +1290,98 @@ "title": "Excursion", "type": "object" }, + "ExecutionDelta": { + "additionalProperties": false, + "description": "An in-flight operator perturbation during Execute (issue #7): an obstruction (a +N min hold at the vehicle's current cell) or a blocked cell forcing a re-route. Previously these survived only as a prose Observation note; capturing them as a typed, content-addressed store object preserves their structured inputs for inspection (the Data Analysis monitor) and replay (NF3), and re-uses the DEC-61 attributed-delta write path. Identity is content (DEC-35).", + "properties": { + "absorbed_min": { + "description": "minutes the downstream holds absorbed, so the RV slipped less than delay_min (ADR-0007)", + "type": [ + "number", + "null" + ] + }, + "at": { + "description": "when it was contributed (DEC-15, NF2)", + "format": "date-time", + "type": [ + "string", + "null" + ] + }, + "at_min": { + "description": "mission minutes when applied (sim-time \u2261 plan-time, ADR-0007)", + "type": [ + "integer", + "null" + ] + }, + "by": { + "description": "the contributing author", + "type": [ + "string", + "null" + ] + }, + "cell": { + "anyOf": [ + { + "$ref": "#/$defs/HexCell" + }, + { + "type": "null" + } + ], + "description": "where the perturbation bites \u2014 the vehicle's cell for an obstruction, the blocked cell for a block" + }, + "delay_min": { + "description": "the hold added by an obstruction (absent for a block)", + "type": [ + "number", + "null" + ] + }, + "event": { + "$ref": "#/$defs/ExecutionEventKind", + "description": "which perturbation \u2014 obstruction or block" + }, + "role": { + "description": "\"the contributing role-hat \u2014 e.g. duty-officer-plans\"", + "type": [ + "string", + "null" + ] + }, + "rv_min": { + "description": "the re-planned RV / mission-end after the perturbation", + "type": [ + "number", + "null" + ] + }, + "scope": { + "description": "\"the write-scope this delta falls under \u2014 e.g. steering\"", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "event" + ], + "title": "ExecutionDelta", + "type": "object" + }, + "ExecutionEventKind": { + "description": "The kind of in-flight operator perturbation applied during Execute (issue", + "enum": [ + "obstruction", + "block" + ], + "title": "ExecutionEventKind", + "type": "string" + }, "ExecutionLog": { "additionalProperties": false, "description": "The append-only live history of a mission (DEC-23/25/26): alerts when a band is crossed, observations that update truth, waivers, and replans \u2014 never overwritten. Together with rationales and stamps it gives a complete after-action record with perfect replay (F1).", @@ -1370,6 +1462,35 @@ "title": "Grid2D", "type": "object" }, + "HexCell": { + "additionalProperties": false, + "description": "A single H3 hex location \u2014 the hex-grid successor to Waypoint (ADR-0014). Identified by its H3 index, with an optional lat/lng centre for rendering.", + "properties": { + "h3": { + "description": "the H3 cell index (res 9)", + "type": "string" + }, + "lat": { + "description": "cell-centre latitude", + "type": [ + "number", + "null" + ] + }, + "lng": { + "description": "cell-centre longitude", + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "h3" + ], + "title": "HexCell", + "type": "object" + }, "Lineage": { "additionalProperties": false, "description": "Hash-link back to the version this one supersedes (universal rule 1).", diff --git a/schema/gen/remit.ts b/schema/gen/remit.ts index 18268b8..3e95522 100644 --- a/schema/gen/remit.ts +++ b/schema/gen/remit.ts @@ -213,6 +213,16 @@ export enum LogEntryKind { Waiver = "Waiver", Replan = "Replan", }; +/** +* The kind of in-flight operator perturbation applied during Execute (issue +*/ +export enum ExecutionEventKind { + + /** a +N min hold spliced at the vehicle's current cell, re-timed through the tide-aware chooser (ADR-0006/0007) */ + obstruction = "obstruction", + /** a cell ahead declared impassable, forcing an in-flight re-route around it */ + block = "block", +}; /** @@ -264,6 +274,19 @@ export interface Waypoint { } +/** + * A single H3 hex location — the hex-grid successor to Waypoint (ADR-0014). Identified by its H3 index, with an optional lat/lng centre for rendering. + */ +export interface HexCell { + /** the H3 cell index (res 9) */ + h3: string, + /** cell-centre latitude */ + lat?: number, + /** cell-centre longitude */ + lng?: number, +} + + /** * A commitment's ownership and waiver authority (B3). */ @@ -1011,4 +1034,23 @@ export interface SteeringDelta extends Delta { } +/** + * An in-flight operator perturbation during Execute (issue #7): an obstruction (a +N min hold at the vehicle's current cell) or a blocked cell forcing a re-route. Previously these survived only as a prose Observation note; capturing them as a typed, content-addressed store object preserves their structured inputs for inspection (the Data Analysis monitor) and replay (NF3), and re-uses the DEC-61 attributed-delta write path. Identity is content (DEC-35). + */ +export interface ExecutionDelta extends Delta { + /** which perturbation — obstruction or block */ + event: string, + /** mission minutes when applied (sim-time ≡ plan-time, ADR-0007) */ + at_min?: number, + /** where the perturbation bites — the vehicle's cell for an obstruction, the blocked cell for a block */ + cell?: HexCell, + /** the hold added by an obstruction (absent for a block) */ + delay_min?: number, + /** the re-planned RV / mission-end after the perturbation */ + rv_min?: number, + /** minutes the downstream holds absorbed, so the RV slipped less than delay_min (ADR-0007) */ + absorbed_min?: number, +} + + diff --git a/schema/records.yaml b/schema/records.yaml index 7832409..118fb8b 100644 --- a/schema/records.yaml +++ b/schema/records.yaml @@ -155,3 +155,27 @@ classes: multivalued: true inlined_as_list: true description: the interpreted steering gestures shared (DEC-24) — e.g. no-go regions + ExecutionDelta: + is_a: Delta + description: 'An in-flight operator perturbation during Execute (issue #7): an obstruction (a +N min hold at the vehicle''s current cell) or a blocked cell forcing a re-route. Previously these survived only as a prose Observation note; capturing them as a typed, content-addressed store object preserves their structured inputs for inspection (the Data Analysis monitor) and replay (NF3), and re-uses the DEC-61 attributed-delta write path. Identity is content (DEC-35).' + attributes: + event: + range: ExecutionEventKind + required: true + description: which perturbation — obstruction or block + at_min: + range: integer + description: mission minutes when applied (sim-time ≡ plan-time, ADR-0007) + cell: + range: HexCell + inlined: true + description: where the perturbation bites — the vehicle's cell for an obstruction, the blocked cell for a block + delay_min: + range: float + description: the hold added by an obstruction (absent for a block) + rv_min: + range: float + description: the re-planned RV / mission-end after the perturbation + absorbed_min: + range: float + description: minutes the downstream holds absorbed, so the RV slipped less than delay_min (ADR-0007) diff --git a/site/data-model/index.html b/site/data-model/index.html index fbd562f..8c51772 100644 --- a/site/data-model/index.html +++ b/site/data-model/index.html @@ -90,10 +90,10 @@

Generated reference · LinkML (DEC-57)

REMIT — v1 Data Model

The serialisable object core of REMIT's v1 mission-planning model — the shapes mission planning is built from. This LinkML schema is the Doc-owned source of truth (DEC-57): one schema generates JSON Schema, TypeScript types, and this hyperlinked reference + ER diagrams. `docs/remit-data-model.md` is the prose spine; the field names and structures here are reconciled against the objects the walking-skeleton app actually constructs (`app/js/**`), so the schema agrees with the running code.

-
7 modules62 classes -20 enumsJSON Schema & TypeScript generated alongside
-
-

The model at a glance

The serialisable object core, split into 7 modules.

Whole-model ER diagram

Every typed association across the modules, drawn in your browser (Mermaid). Scroll to zoom, drag to pan, click a box to jump to its class.

Show the 62-class diagram
erDiagram
+
7 modules64 classes +21 enumsJSON Schema & TypeScript generated alongside
+
+

The model at a glance

The serialisable object core, split into 7 modules.

Whole-model ER diagram

Every typed association across the modules, drawn in your browser (Mermaid). Scroll to zoom, drag to pan, click a box to jump to its class.

Show the 64-class diagram
erDiagram
   Attribution {
   }
   DataProvenance {
@@ -102,6 +102,8 @@ 

REMIT — v1 Data Model

The serialisable object core of } Waypoint { } + HexCell { + } CommitmentProvenance { } Requirement { @@ -218,6 +220,8 @@

REMIT — v1 Data Model

The serialisable object core of } SteeringDelta { } + ExecutionDelta { + } Requirement ||--o| Attribution : "provenance" Requirement ||--o{ Commitment : "commitments" Requirement ||--o| Lineage : "lineage" @@ -293,7 +297,8 @@

REMIT — v1 Data Model

The serialisable object core of Replan ||..o| Plan : "from" Replan ||..o| Plan : "to" Replan ||..o| SelectionRationale : "rationale_ref" - SteeringDelta ||--o{ Constraint : "constraints"

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

Common 4 classes · 20 enums

Shared value objects and the enum vocabulary used across the REMIT data model. Imported by every other module.

erDiagram
+  SteeringDelta ||--o{ Constraint : "constraints"
+  ExecutionDelta ||--o| HexCell : "cell"

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

Common 5 classes · 20 enums

Shared value objects and the enum vocabulary used across the REMIT data model. Imported by every other module.

erDiagram
   Attribution {
   }
   DataProvenance {
@@ -301,7 +306,9 @@ 

REMIT — v1 Data Model

The serialisable object core of Lineage { } Waypoint { - }

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

Attribution

Who decided, under what authority, when. Stamped on every committing act — this is universal rule 2 (DEC-15, NF2).

fieldtypecarddescription
issuing_rolestring0..1the role-hat the author wore
authoritystring0..1the authority the act was taken under
bystring0..1the author
atdatetime0..1ISO timestamp of the act

DataProvenance

The source and trust of a datum or entity (DEC-19). Trivial defaults in v1.

fieldtypecarddescription
kindstring1source-class — one of: self, planned, forecast, observed, provider. The prose model separates entity kind (self/actor/feature/phenomenon) from data provenance (planned/forecast/observed); the skeleton folds them, using self/forecast/provider. A documented string rather than an enum (see the AspectType note).
confidenceConfidenceLevel0..1
freshnessstring0..1how recent — e.g. "provisioned"

Lineage

Hash-link back to the version this one supersedes (universal rule 1).

fieldtypecarddescription
previous_versionstring0..1id of the prior Requirement version
amending_order_refstring0..1reference to the amending order, if any

Waypoint

A single grid location.

fieldtypecarddescription
xinteger1
yinteger1
aliasstring0..1a human-friendly name, e.g. "OP-21,3"

Requirement 12 classes

The requirement and the promises in it: Requirement -> Commitment -> Activity (DEC-5/16/17/18).

erDiagram
+  }
+  HexCell {
+  }

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

Attribution

Who decided, under what authority, when. Stamped on every committing act — this is universal rule 2 (DEC-15, NF2).

fieldtypecarddescription
issuing_rolestring0..1the role-hat the author wore
authoritystring0..1the authority the act was taken under
bystring0..1the author
atdatetime0..1ISO timestamp of the act

DataProvenance

The source and trust of a datum or entity (DEC-19). Trivial defaults in v1.

fieldtypecarddescription
kindstring1source-class — one of: self, planned, forecast, observed, provider. The prose model separates entity kind (self/actor/feature/phenomenon) from data provenance (planned/forecast/observed); the skeleton folds them, using self/forecast/provider. A documented string rather than an enum (see the AspectType note).
confidenceConfidenceLevel0..1
freshnessstring0..1how recent — e.g. "provisioned"

Lineage

Hash-link back to the version this one supersedes (universal rule 1).

fieldtypecarddescription
previous_versionstring0..1id of the prior Requirement version
amending_order_refstring0..1reference to the amending order, if any

Waypoint

A single grid location.

fieldtypecarddescription
xinteger1
yinteger1
aliasstring0..1a human-friendly name, e.g. "OP-21,3"

HexCell

A single H3 hex location — the hex-grid successor to Waypoint (ADR-0014). Identified by its H3 index, with an optional lat/lng centre for rendering.

fieldtypecarddescription
h3string1the H3 cell index (res 9)
latfloat0..1cell-centre latitude
lngfloat0..1cell-centre longitude

Requirement 12 classes

The requirement and the promises in it: Requirement -> Commitment -> Activity (DEC-5/16/17/18).

erDiagram
   CommitmentProvenance {
   }
   Requirement {
@@ -457,7 +464,7 @@ 

REMIT — v1 Data Model

The serialisable object core of ScheduleLeg ||..o| Commitment : "commitment_id" Scores ||--o{ Satisfaction : "satisfaction" Satisfaction ||..o| Commitment : "commitment_id" - Conflict ||..o{ Commitment : "parties"

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

Stamp

Every input that determines a plan, bundled into one identity (DEC-23/24/29): which requirement, which world, which config core, what appetites and steering, which kernel and seed. A plan's id IS the hash of its Stamp, so two plans are comparable only when their stamps share a basis (the comparability guard). Skeleton additions (DEC-47): `profile_version` and `start` are part of identity because the plan depends on the platform and the starting state.

fieldtypecarddescription
requirement_versionRequirement0..1id of the requirement version
baseline_versionBaseline0..1id of the baseline version
excursionsExcursion0..*ids of the excursion versions (empty in v1)
config_core_hashstring0..1hash of the world-defining config core — medium/channels/movement-model/providers/vocabulary; the instance shell is excluded (DEC-48)
profile_versionProfile0..1id of the profile version (skeleton addition, DEC-47)
startStartState0..1the starting state (skeleton addition, DEC-47)
appetitesAppetite0..*the implementer's risk dials (DEC-6)
steeringConstraint0..*interpreted operator gestures / no-go constraints (DEC-24)
kernel_versionstring0..1e.g. "mock-0.1" — part of identity (DEC-29)
strategy_seedinteger0..1RNG seed for strategy ordering — part of identity (DEC-29)

StartState

The starting position and clock baked into a Stamp (skeleton, DEC-47).

fieldtypecarddescription
xinteger0..1
yinteger0..1
clock_mininteger0..1

Appetite

One risk-appetite dial setting (DEC-6) — e.g. tempo = rapid, exposure = cautious.

fieldtypecarddescription
axisstring0..1"e.g. tempo, exposure"
settingstring0..1"e.g. deliberate/balanced/rapid, bold/balanced/cautious"

Constraint

One interpreted operator steering gesture (DEC-24) — e.g. a no-go region.

fieldtypecarddescription
typestring0..1"e.g. no-go"
cellsWaypoint0..*

Strategy

One candidate strategy in the plan handful (skeleton).

fieldtypecarddescription
keyStrategyKey1
labelstring0..1
axisstring0..1the axis it optimises, e.g. "time/speed"
blurbstring0..1a one-line description

Plan

identified

A candidate solution whose id IS the hash of its Stamp (DEC-5/22/29). Its materialisation (schedule, trajectory, state curves) is cached and regenerable; its scores and first-class conflicts make it comparable. The skeleton also carries the chosen `strategy` and the `tide_decision` weighing.

fieldtypecarddescription
ididstring1"= hash(Stamp, strategy)"
strategyStrategy0..1
stampStamp0..1authoritative — the plan's identity basis
materialisationMaterialisation0..1cached, regenerable; null when infeasible
scoresScores0..1
tide_decisionTideDecision0..1the exfil wait-vs-detour weighing (ADR-0006); null when no ford
conflictsConflict0..*first-class, named clashes (C1)

Materialisation

The cached, regenerable working-out of a plan; absent when the plan is infeasible.

fieldtypecarddescription
scheduleScheduleLeg0..*
trajectoryTrajectoryPoint0..*
state_curvesStateCurves0..1
tideTideDecision0..1the tide decision at plan time
verifiedboolean0..1
kernel_version_verifiedstring0..1the kernel version that verified the materialisation

ScheduleLeg

One leg of a plan's schedule. Exfil legs may carry a tide hold (ADR-0006).

fieldtypecarddescription
kindScheduleLegKind1
labelstring0..1
start_minfloat0..1
end_minfloat0..1
commitment_idCommitment0..1the commitment this leg serves (visit/exfil legs)

TrajectoryPoint

One sampled point on the platform's path; coordinates may be fractional, time/fuel rounded to 1 dp for IEEE stability.

fieldtypecarddescription
xfloat0..1
yfloat0..1
tfloat0..1mission minutes
fuel_pctfloat0..1

StateCurves

The end-state of the platform's curves over the plan (v1 carries fuel only).

fieldtypecarddescription
fuel_end_pctfloat0..1

Scores

A plan's comparable scores, read under the comparability guard (A2/C2/C6, NF10).

fieldtypecarddescription
satisfactionSatisfaction0..*
cost_bandBand0..1
robustness_bandBand0..1

Satisfaction

One commitment's verdict and slack within a plan.

fieldtypecarddescription
commitment_idCommitment0..1
labelstring0..1
margin_minfloat0..1slack in minutes; may be negative
margin_bandMarginBand0..1
verdictVerdict0..1

TideDecision

The result of the tidal-ford wait-vs-detour weighing (ADR-0006): the kernel materialises the exfil both ways — wait at the bank vs a ford-free detour — and commits to the earlier RV arrival, publishing the choice here.

fieldtypecarddescription
modeTideMode1
wait_minfloat0..1minutes held at the bank before crossing
ford_rvfloat0..1mission minutes the ford route reaches the RV
detour_rvfloat0..1mission minutes the detour route reaches the RV; null when no detour exists
narrativestring0..1the choice, in words

Conflict

identified

A first-class, named clash when commitments cannot all be kept (C1) — not a silent failure.

fieldtypecarddescription
ididstring1
kindConflictKind1
partiesCommitment0..*the commitments in tension
narrativestring0..1what the clash is, in words

Records 12 classes

The decision and execution record: SelectionRationale and the append-only ExecutionLog (DEC-23/25/26).

erDiagram
+  Conflict ||..o{ Commitment : "parties"

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

Stamp

Every input that determines a plan, bundled into one identity (DEC-23/24/29): which requirement, which world, which config core, what appetites and steering, which kernel and seed. A plan's id IS the hash of its Stamp, so two plans are comparable only when their stamps share a basis (the comparability guard). Skeleton additions (DEC-47): `profile_version` and `start` are part of identity because the plan depends on the platform and the starting state.

fieldtypecarddescription
requirement_versionRequirement0..1id of the requirement version
baseline_versionBaseline0..1id of the baseline version
excursionsExcursion0..*ids of the excursion versions (empty in v1)
config_core_hashstring0..1hash of the world-defining config core — medium/channels/movement-model/providers/vocabulary; the instance shell is excluded (DEC-48)
profile_versionProfile0..1id of the profile version (skeleton addition, DEC-47)
startStartState0..1the starting state (skeleton addition, DEC-47)
appetitesAppetite0..*the implementer's risk dials (DEC-6)
steeringConstraint0..*interpreted operator gestures / no-go constraints (DEC-24)
kernel_versionstring0..1e.g. "mock-0.1" — part of identity (DEC-29)
strategy_seedinteger0..1RNG seed for strategy ordering — part of identity (DEC-29)

StartState

The starting position and clock baked into a Stamp (skeleton, DEC-47).

fieldtypecarddescription
xinteger0..1
yinteger0..1
clock_mininteger0..1

Appetite

One risk-appetite dial setting (DEC-6) — e.g. tempo = rapid, exposure = cautious.

fieldtypecarddescription
axisstring0..1"e.g. tempo, exposure"
settingstring0..1"e.g. deliberate/balanced/rapid, bold/balanced/cautious"

Constraint

One interpreted operator steering gesture (DEC-24) — e.g. a no-go region.

fieldtypecarddescription
typestring0..1"e.g. no-go"
cellsWaypoint0..*

Strategy

One candidate strategy in the plan handful (skeleton).

fieldtypecarddescription
keyStrategyKey1
labelstring0..1
axisstring0..1the axis it optimises, e.g. "time/speed"
blurbstring0..1a one-line description

Plan

identified

A candidate solution whose id IS the hash of its Stamp (DEC-5/22/29). Its materialisation (schedule, trajectory, state curves) is cached and regenerable; its scores and first-class conflicts make it comparable. The skeleton also carries the chosen `strategy` and the `tide_decision` weighing.

fieldtypecarddescription
ididstring1"= hash(Stamp, strategy)"
strategyStrategy0..1
stampStamp0..1authoritative — the plan's identity basis
materialisationMaterialisation0..1cached, regenerable; null when infeasible
scoresScores0..1
tide_decisionTideDecision0..1the exfil wait-vs-detour weighing (ADR-0006); null when no ford
conflictsConflict0..*first-class, named clashes (C1)

Materialisation

The cached, regenerable working-out of a plan; absent when the plan is infeasible.

fieldtypecarddescription
scheduleScheduleLeg0..*
trajectoryTrajectoryPoint0..*
state_curvesStateCurves0..1
tideTideDecision0..1the tide decision at plan time
verifiedboolean0..1
kernel_version_verifiedstring0..1the kernel version that verified the materialisation

ScheduleLeg

One leg of a plan's schedule. Exfil legs may carry a tide hold (ADR-0006).

fieldtypecarddescription
kindScheduleLegKind1
labelstring0..1
start_minfloat0..1
end_minfloat0..1
commitment_idCommitment0..1the commitment this leg serves (visit/exfil legs)

TrajectoryPoint

One sampled point on the platform's path; coordinates may be fractional, time/fuel rounded to 1 dp for IEEE stability.

fieldtypecarddescription
xfloat0..1
yfloat0..1
tfloat0..1mission minutes
fuel_pctfloat0..1

StateCurves

The end-state of the platform's curves over the plan (v1 carries fuel only).

fieldtypecarddescription
fuel_end_pctfloat0..1

Scores

A plan's comparable scores, read under the comparability guard (A2/C2/C6, NF10).

fieldtypecarddescription
satisfactionSatisfaction0..*
cost_bandBand0..1
robustness_bandBand0..1

Satisfaction

One commitment's verdict and slack within a plan.

fieldtypecarddescription
commitment_idCommitment0..1
labelstring0..1
margin_minfloat0..1slack in minutes; may be negative
margin_bandMarginBand0..1
verdictVerdict0..1

TideDecision

The result of the tidal-ford wait-vs-detour weighing (ADR-0006): the kernel materialises the exfil both ways — wait at the bank vs a ford-free detour — and commits to the earlier RV arrival, publishing the choice here.

fieldtypecarddescription
modeTideMode1
wait_minfloat0..1minutes held at the bank before crossing
ford_rvfloat0..1mission minutes the ford route reaches the RV
detour_rvfloat0..1mission minutes the detour route reaches the RV; null when no detour exists
narrativestring0..1the choice, in words

Conflict

identified

A first-class, named clash when commitments cannot all be kept (C1) — not a silent failure.

fieldtypecarddescription
ididstring1
kindConflictKind1
partiesCommitment0..*the commitments in tension
narrativestring0..1what the clash is, in words

Records 13 classes

The decision and execution record: SelectionRationale and the append-only ExecutionLog (DEC-23/25/26).

erDiagram
   SelectionRationale {
   }
   ChosenBands {
@@ -482,6 +489,8 @@ 

REMIT — v1 Data Model

The serialisable object core of } SteeringDelta { } + ExecutionDelta { + } SelectionRationale ||..o| Plan : "chosen" SelectionRationale ||..o{ Plan : "beaten" SelectionRationale ||--o{ Appetite : "appetites" @@ -493,7 +502,8 @@

REMIT — v1 Data Model

The serialisable object core of Replan ||..o| Plan : "from" Replan ||..o| Plan : "to" Replan ||..o| SelectionRationale : "rationale_ref" - SteeringDelta ||--o{ Constraint : "constraints"

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

SelectionRationale

identified

The record of a choice (DEC-23): which plan won, which it beat, on which axis, with the after-mitigation bands, and who decided. Content-addressed.

fieldtypecarddescription
ididstring1content id of the canonical form (DEC-35)
chosenPlan0..1the winning plan
beatenPlan0..*the field it beat
deciding_axisstring0..1"what tipped it — e.g. time/speed, exposure, robustness, completeness"
notestring0..1the justification narrative
appetitesAppetite0..*the risk-appetite selections at the time
mitigationsstring0..*"e.g. armed-escort"
chosen_bandsChosenBands0..1cost/robustness bands after mitigations
bystring0..1
rolestring0..1"e.g. implementer"
atdatetime0..1

ChosenBands

fieldtypecarddescription
costBand0..1
robBand0..1

ExecutionLog

The append-only live history of a mission (DEC-23/25/26): alerts when a band is crossed, observations that update truth, waivers, and replans — never overwritten. Together with rationales and stamps it gives a complete after-action record with perfect replay (F1).

fieldtypecarddescription
entriesLogEntry0..*

LogEntry

abstract

Base of the append-only execution log; the store stamps each with a `seq`.

fieldtypecarddescription
kindLogEntryKind1
seqinteger0..10-indexed sequence, stamped on append
atinteger0..1mission minutes

Alert

is a LogEntry

A band crossing or hard-infeasibility raised during execution (E3).

fieldtypecarddescription
causeAlertCause0..1
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

AlertCause

fieldtypecarddescription
typeAlertCauseType1
commitmentstring0..1the phased commitment being tracked (e.g. "observe", "exfil")
fromstring0..1the prior band state
tostring0..1the new band state

Observation

is a LogEntry

A fact delta appended to the baseline's fact layer (E5).

fieldtypecarddescription
fact_deltaFactDelta0..1
sourcestring0..1"e.g. operator"
confidencestring0..1"e.g. reported"
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

FactDelta

fieldtypecarddescription
notestring0..1
tagstring0..1"e.g. track-state, sighting, weather"

Waiver

is a LogEntry

A relaxed commitment, attributed (B3). Not yet logged in the v1 skeleton.

fieldtypecarddescription
commitment_idCommitment0..1
bystring0..1
authoritystring0..1
rationalestring0..1
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

Replan

is a LogEntry

A move from one committed plan to another, with rationale (DEC-36). Not yet logged in the v1 skeleton.

fieldtypecarddescription
fromPlan0..1
toPlan0..1
rationale_refSelectionRationale0..1
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

Delta

abstract

A stamped cross-role write to the shared store (DEC-61): a role's contribution is an attributed delta — the same write path that serialises over `/sync` later (DEC-25), so going multi-node is transport, not re-architecture. Base of the concrete delta kinds; v1 emits SteeringDelta. Write-scope *enforcement* is designed-for — the delta is attributed (NF2) but not yet scope-checked.

fieldtypecarddescription
scopestring0..1"the write-scope this delta falls under — e.g. steering"
bystring0..1the contributing author
rolestring0..1"the contributing role-hat — e.g. duty-officer-plans"
atdatetime0..1when it was contributed (DEC-15, NF2)

SteeringDelta

is a Delta

The first DEC-61 write built in v1: an operator's *applied intel* — denied (no-go) cells — shared to the store as steering constraints, where every surface (e.g. the Data Analysis monitor, including a popped-out one) sees it land. Risk appetites, by contrast, stay local (a ranking lens). Identity is content (DEC-35): the store keys it by the hash of its canonical form.

fieldtypecarddescription
constraintsConstraint0..*the interpreted steering gestures shared (DEC-24) — e.g. no-go regions
scopeinheritedstring0..1"the write-scope this delta falls under — e.g. steering"
byinheritedstring0..1the contributing author
roleinheritedstring0..1"the contributing role-hat — e.g. duty-officer-plans"
atinheriteddatetime0..1when it was contributed (DEC-15, NF2)

Enums 20 value sets

The closed value vocabularies (in common) used by the fields above.

Criticality

enum

Whether a commitment must hold or is best-effort (DEC-22).

  • hard — must hold
  • soft — best-effort; carries a priority, with authority from provenance

ActivityType

enum

The verb of an activity (DEC-16); v1 set, pluggable registry (NF7).

  • visit
  • loiter
  • avoid
  • transit
  • maintain

CommitmentState

enum

The lifecycle state of a commitment (B4). `at-risk` is live-only (E2) and not stored.

  • proposed
  • negotiated
  • committed
  • satisfied
  • violated
  • waived
  • superseded

AnswerStatus

enum

  • confirmed — answered by the operator
  • defaulted — filled from a stamped default

AmbiguityStatus

enum

  • open
  • resolved

Domain

enum

The medium a baseline / profile / movement model operates in (DEC-20).

  • land
  • sea
  • air

Terrain

enum

Per-cell land-cover class used by the skeleton's mobility raster.

  • road
  • track
  • open
  • rough
  • forest
  • marsh
  • ford
  • water

Realisation

enum

How a channel or movement model is realised (DEC-49). `provider` = a computed service behind the seam.

  • raster
  • analytic
  • parametric
  • provider

Predictability

enum

How predictable a channel is over time (DEC-21) — drives sampling.

  • static
  • periodic
  • dynamic

ConfidenceLevel

enum

  • high
  • medium
  • low

AspectType

enum

The render-class of one time-varying facet of an entity (DEC-52/53).

  • cell — a position → map glyph / distance-along-track
  • scalar — a numeric value → a line (altitude, fuel, distance)
  • window — a time interval → a band
  • status — an up/down or phase value → ticks

StrategyKey

enum

The skeleton's three candidate strategies (the "handful" of plans).

  • direct — fastest by time/speed
  • tracked — keeps to tracked/road surfaces
  • covered — favours exposure-reducing cover

Band

enum

A coarse three-level qualitative band for cost / robustness (C2/C6, NF10).

  • robust
  • marginal
  • fragile

MarginBand

enum

The slack band on a single commitment's satisfaction (A2, NF10). `crossed` = violated.

  • robust
  • marginal
  • tight
  • crossed

Verdict

enum

A commitment's verdict in a plan's scores.

  • satisfied
  • violated
  • waived

ScheduleLegKind

enum

The kind of one leg in a plan's schedule.

  • transit
  • hold — a wait — e.g. "await low-tide window" at a tidal ford
  • visit
  • exfil

TideMode

enum

The outcome of the tidal-ford wait-vs-detour weighing (ADR-0006).

  • no-ford — no tidal ford on the route
  • open — ford open at the bank — cross now
  • wait — hold at the bank for the low-tide window, then cross
  • detour — ford-free detour reaches the RV sooner

ConflictKind

enum

Whether a clash is built into the request or emerges from the schedule (C1).

  • structural — e.g. no route exists
  • emergent — e.g. the schedule is infeasible

AlertCauseType

enum

Why the wingman raised an execution alert (E3).

  • hard_infeasible
  • band_crossing
  • tide_reassessment — a re-plan changed the tide decision's mode (ADR-0007)

LogEntryKind

enum

  • Alert
  • Observation
  • Waiver
  • Replan
+ SteeringDelta ||--o{ Constraint : "constraints" + ExecutionDelta ||--o| HexCell : "cell"

contains (inlined)  ·  references (by id)  ·  o{ many  || one  o| optional. Boxes outside the module are classes defined elsewhere.

SelectionRationale

identified

The record of a choice (DEC-23): which plan won, which it beat, on which axis, with the after-mitigation bands, and who decided. Content-addressed.

fieldtypecarddescription
ididstring1content id of the canonical form (DEC-35)
chosenPlan0..1the winning plan
beatenPlan0..*the field it beat
deciding_axisstring0..1"what tipped it — e.g. time/speed, exposure, robustness, completeness"
notestring0..1the justification narrative
appetitesAppetite0..*the risk-appetite selections at the time
mitigationsstring0..*"e.g. armed-escort"
chosen_bandsChosenBands0..1cost/robustness bands after mitigations
bystring0..1
rolestring0..1"e.g. implementer"
atdatetime0..1

ChosenBands

fieldtypecarddescription
costBand0..1
robBand0..1

ExecutionLog

The append-only live history of a mission (DEC-23/25/26): alerts when a band is crossed, observations that update truth, waivers, and replans — never overwritten. Together with rationales and stamps it gives a complete after-action record with perfect replay (F1).

fieldtypecarddescription
entriesLogEntry0..*

LogEntry

abstract

Base of the append-only execution log; the store stamps each with a `seq`.

fieldtypecarddescription
kindLogEntryKind1
seqinteger0..10-indexed sequence, stamped on append
atinteger0..1mission minutes

Alert

is a LogEntry

A band crossing or hard-infeasibility raised during execution (E3).

fieldtypecarddescription
causeAlertCause0..1
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

AlertCause

fieldtypecarddescription
typeAlertCauseType1
commitmentstring0..1the phased commitment being tracked (e.g. "observe", "exfil")
fromstring0..1the prior band state
tostring0..1the new band state

Observation

is a LogEntry

A fact delta appended to the baseline's fact layer (E5).

fieldtypecarddescription
fact_deltaFactDelta0..1
sourcestring0..1"e.g. operator"
confidencestring0..1"e.g. reported"
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

FactDelta

fieldtypecarddescription
notestring0..1
tagstring0..1"e.g. track-state, sighting, weather"

Waiver

is a LogEntry

A relaxed commitment, attributed (B3). Not yet logged in the v1 skeleton.

fieldtypecarddescription
commitment_idCommitment0..1
bystring0..1
authoritystring0..1
rationalestring0..1
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

Replan

is a LogEntry

A move from one committed plan to another, with rationale (DEC-36). Not yet logged in the v1 skeleton.

fieldtypecarddescription
fromPlan0..1
toPlan0..1
rationale_refSelectionRationale0..1
kindinheritedLogEntryKind1
seqinheritedinteger0..10-indexed sequence, stamped on append
atinheritedinteger0..1mission minutes

Delta

abstract

A stamped cross-role write to the shared store (DEC-61): a role's contribution is an attributed delta — the same write path that serialises over `/sync` later (DEC-25), so going multi-node is transport, not re-architecture. Base of the concrete delta kinds; v1 emits SteeringDelta. Write-scope *enforcement* is designed-for — the delta is attributed (NF2) but not yet scope-checked.

fieldtypecarddescription
scopestring0..1"the write-scope this delta falls under — e.g. steering"
bystring0..1the contributing author
rolestring0..1"the contributing role-hat — e.g. duty-officer-plans"
atdatetime0..1when it was contributed (DEC-15, NF2)

SteeringDelta

is a Delta

The first DEC-61 write built in v1: an operator's *applied intel* — denied (no-go) cells — shared to the store as steering constraints, where every surface (e.g. the Data Analysis monitor, including a popped-out one) sees it land. Risk appetites, by contrast, stay local (a ranking lens). Identity is content (DEC-35): the store keys it by the hash of its canonical form.

fieldtypecarddescription
constraintsConstraint0..*the interpreted steering gestures shared (DEC-24) — e.g. no-go regions
scopeinheritedstring0..1"the write-scope this delta falls under — e.g. steering"
byinheritedstring0..1the contributing author
roleinheritedstring0..1"the contributing role-hat — e.g. duty-officer-plans"
atinheriteddatetime0..1when it was contributed (DEC-15, NF2)

ExecutionDelta

is a Delta

An in-flight operator perturbation during Execute (issue #7): an obstruction (a +N min hold at the vehicle's current cell) or a blocked cell forcing a re-route. Previously these survived only as a prose Observation note; capturing them as a typed, content-addressed store object preserves their structured inputs for inspection (the Data Analysis monitor) and replay (NF3), and re-uses the DEC-61 attributed-delta write path. Identity is content (DEC-35).

fieldtypecarddescription
eventExecutionEventKind1which perturbation — obstruction or block
at_mininteger0..1mission minutes when applied (sim-time ≡ plan-time, ADR-0007)
cellHexCell0..1where the perturbation bites — the vehicle's cell for an obstruction, the blocked cell for a block
delay_minfloat0..1the hold added by an obstruction (absent for a block)
rv_minfloat0..1the re-planned RV / mission-end after the perturbation
absorbed_minfloat0..1minutes the downstream holds absorbed, so the RV slipped less than delay_min (ADR-0007)
scopeinheritedstring0..1"the write-scope this delta falls under — e.g. steering"
byinheritedstring0..1the contributing author
roleinheritedstring0..1"the contributing role-hat — e.g. duty-officer-plans"
atinheriteddatetime0..1when it was contributed (DEC-15, NF2)

Enums 21 value sets

The closed value vocabularies (in common) used by the fields above.

Criticality

enum

Whether a commitment must hold or is best-effort (DEC-22).

  • hard — must hold
  • soft — best-effort; carries a priority, with authority from provenance

ActivityType

enum

The verb of an activity (DEC-16); v1 set, pluggable registry (NF7).

  • visit
  • loiter
  • avoid
  • transit
  • maintain

CommitmentState

enum

The lifecycle state of a commitment (B4). `at-risk` is live-only (E2) and not stored.

  • proposed
  • negotiated
  • committed
  • satisfied
  • violated
  • waived
  • superseded

AnswerStatus

enum

  • confirmed — answered by the operator
  • defaulted — filled from a stamped default

AmbiguityStatus

enum

  • open
  • resolved

Domain

enum

The medium a baseline / profile / movement model operates in (DEC-20).

  • land
  • sea
  • air

Terrain

enum

Per-cell land-cover class used by the skeleton's mobility raster.

  • road
  • track
  • open
  • rough
  • forest
  • marsh
  • ford
  • water

Realisation

enum

How a channel or movement model is realised (DEC-49). `provider` = a computed service behind the seam.

  • raster
  • analytic
  • parametric
  • provider

Predictability

enum

How predictable a channel is over time (DEC-21) — drives sampling.

  • static
  • periodic
  • dynamic

ConfidenceLevel

enum

  • high
  • medium
  • low

AspectType

enum

The render-class of one time-varying facet of an entity (DEC-52/53).

  • cell — a position → map glyph / distance-along-track
  • scalar — a numeric value → a line (altitude, fuel, distance)
  • window — a time interval → a band
  • status — an up/down or phase value → ticks

StrategyKey

enum

The skeleton's three candidate strategies (the "handful" of plans).

  • direct — fastest by time/speed
  • tracked — keeps to tracked/road surfaces
  • covered — favours exposure-reducing cover

Band

enum

A coarse three-level qualitative band for cost / robustness (C2/C6, NF10).

  • robust
  • marginal
  • fragile

MarginBand

enum

The slack band on a single commitment's satisfaction (A2, NF10). `crossed` = violated.

  • robust
  • marginal
  • tight
  • crossed

Verdict

enum

A commitment's verdict in a plan's scores.

  • satisfied
  • violated
  • waived

ScheduleLegKind

enum

The kind of one leg in a plan's schedule.

  • transit
  • hold — a wait — e.g. "await low-tide window" at a tidal ford
  • visit
  • exfil

TideMode

enum

The outcome of the tidal-ford wait-vs-detour weighing (ADR-0006).

  • no-ford — no tidal ford on the route
  • open — ford open at the bank — cross now
  • wait — hold at the bank for the low-tide window, then cross
  • detour — ford-free detour reaches the RV sooner

ConflictKind

enum

Whether a clash is built into the request or emerges from the schedule (C1).

  • structural — e.g. no route exists
  • emergent — e.g. the schedule is infeasible

AlertCauseType

enum

Why the wingman raised an execution alert (E3).

  • hard_infeasible
  • band_crossing
  • tide_reassessment — a re-plan changed the tide decision's mode (ADR-0007)

LogEntryKind

enum

  • Alert
  • Observation
  • Waiver
  • Replan

ExecutionEventKind

enum

The kind of in-flight operator perturbation applied during Execute (issue

  • obstruction — a +N min hold spliced at the vehicle's current cell, re-timed through the tide-aware chooser (ADR-0006/0007)
  • block — a cell ahead declared impassable, forcing an in-flight re-route around it
diff --git a/specs/004-capture-execution-deltas/blog/post.md b/specs/004-capture-execution-deltas/blog/post.md new file mode 100644 index 0000000..fb8d0f9 --- /dev/null +++ b/specs/004-capture-execution-deltas/blog/post.md @@ -0,0 +1,68 @@ +--- +layout: default +title: "Execution edits, captured in the model" +--- + +> **At a glance —** Obstructions and blocks inserted during Execute now land in the shared +> object store as typed `ExecutionDelta` records — structured inputs, not just a prose log +> line — so they show up in the Data Analysis monitor and are kept for replay. + +![An obstruction captured as a typed ExecutionDelta in the Data Analysis monitor](screenshots/01-execution-delta.png) + +## The problem + +REMIT's Execute phase lets the operator perturb a live run: drop a `+25 min` **obstruction** +in the vehicle's path, or **block** the next cell and force an in-flight re-route. The +wingman handled both correctly — splicing a hold, re-timing the tail through the tide-aware +chooser, bending the route around the block. + +But the maintainer noticed something on the way home ([issue #7](https://github.com/DeepBlueCLtd/REMIT/issues/7)): +*"when I insert obstructions or delays they aren't captured in the model."* Each edit +appended a human-readable note to the execution log — but its **structured** facts (which +cell, how many minutes, the resulting RV) lived only in transient UI state. Nothing reached +the content-addressed **object store**. So unlike `SteeringDelta` — the operator's denied +cells, already a first-class shared write — the perturbations were invisible to the Data +Analysis monitor and carried no structured inputs for replay (NF3). + +## Options + +- **Richer log entries.** Pack the structured fields into the prose `Observation`. Cheap, + but the log is append-only history keyed by mission, not the content-addressed store the + monitor and replay read from — it would still be invisible there. +- **A bespoke UI-state object.** Hand-write a perturbation shape in the wingman. Fast, but + it violates the LinkML one-source-of-truth rule (ADR-0012) the moment it crosses a + boundary — and this one crosses straight into the shared store. +- **A typed delta, mirroring `SteeringDelta`** *(chosen)*. Model the perturbation in LinkML, + generate the type, and write it via the existing DEC-61 attributed-delta path. + +## The strategy + +Model the data, re-use the seam. Two additions to the schema: + +- a `HexCell` value object (`h3` + optional `lat`/`lng`) — the hex-grid successor to the + square-grid `Waypoint`, and +- an `ExecutionDelta` (an `is_a: Delta`) carrying `event` (`obstruction` | `block`), + `at_min`, `cell`, `delay_min`, `rv_min`, and `absorbed_min`. + +`schema/generate.sh` regenerates the JSON Schema, the TypeScript types, and the HTML +reference. The wingman then calls `seam.putObject('ExecutionDelta', …)` on each edit — the +same write path `SteeringDelta` uses, attributed to the operator wearing the **Ops** hat. +The prose log entry stays (it still reads well in the after-action record); the delta is its +structured sibling. The share is non-fatal — the append-only log already recorded the event, +so a failed write never breaks the live loop. + +## The results + +Insert a `+25 min` obstruction on the exfil drive and an `ExecutionDelta` lands in the store +with `event: 'obstruction'`, `delay_min: 25`, and the vehicle's `cell.h3` — and it appears +in the Data Analysis index alongside every other object, drilling down to its fields. A new +e2e test pins this; `npm run typecheck` stays at 0 errors (the imported type is generated, +not hand-shaped) and the 12 golden/unit tests are unchanged. + +One thing this does **not** do yet: re-running a stamp does not automatically *re-apply* the +captured perturbations — they're exogenous operator acts. Capturing their structured inputs +is the prerequisite; automatic re-application is the natural next step. + +## Screenshots + +![An obstruction captured as a typed ExecutionDelta, drilled down in the Data Analysis monitor](screenshots/01-execution-delta.png) diff --git a/specs/004-capture-execution-deltas/evidence/screenshots/01-execution-delta.png b/specs/004-capture-execution-deltas/evidence/screenshots/01-execution-delta.png new file mode 100644 index 0000000..60c51d5 Binary files /dev/null and b/specs/004-capture-execution-deltas/evidence/screenshots/01-execution-delta.png differ diff --git a/specs/004-capture-execution-deltas/spec.md b/specs/004-capture-execution-deltas/spec.md new file mode 100644 index 0000000..269a5b9 --- /dev/null +++ b/specs/004-capture-execution-deltas/spec.md @@ -0,0 +1,68 @@ +# 004 — Capture execution-phase edits in the model (issue #7) + +**Status:** implemented · **Source of truth:** [issue #7](https://github.com/DeepBlueCLtd/REMIT/issues/7) +(maintainer), `docs/remit-data-model.md` (object shapes), `docs/project_notes/decisions.md` +ADR-0011/0012 (LinkML source-of-truth + the attributed-delta write path, ADR-0022). + +> Maintainer-raised: *"in execute phase, when I insert obstructions or delays they aren't +> captured in the model. Should they be?"* — **Yes.** This spec makes each in-flight +> perturbation a first-class, content-addressed store object. + +## Problem + +During Execute, the operator can perturb the live run two ways: + +- **Obstruction** (`+5` / `+25 min`) — a hold spliced at the vehicle's current cell, the + remainder re-timed through the tide-aware chooser (ADR-0006/0007). +- **Block next cell** — a cell ahead declared impassable, forcing an in-flight re-route. + +Both already appended a prose `Observation` to the append-only **execution log** — but +their *structured* inputs (which cell, how many minutes, the resulting RV) lived only in +transient wingman UI state. Nothing landed in the **content-addressed object store**, so — +unlike `SteeringDelta` (the first DEC-61 write) — the perturbations: + +1. were **invisible** to the Data Analysis monitor (which browses store objects), and +2. carried **no structured inputs** for replay (NF3) — only a human-readable note. + +## Scope + +Model execution-phase perturbations as a typed delta and write them to the store. + +| Piece | What | Where | +|---|---|---| +| Schema — enum | `ExecutionEventKind` (`obstruction` \| `block`) | `schema/common.yaml` | +| Schema — value object | `HexCell` (`h3` + optional `lat`/`lng`) — the hex successor to `Waypoint` | `schema/common.yaml` | +| Schema — delta | `ExecutionDelta (is_a: Delta)` — `event`, `at_min`, `cell`, `delay_min`, `rv_min`, `absorbed_min` | `schema/records.yaml` | +| App write | `shareExecutionDelta()` → `seam.putObject('ExecutionDelta', …)` on each obstruction/block | `app/js/wingman/wingman.js` | + +Cross-cutting: re-uses the DEC-61 attributed-delta write path (`scope`/`by`/`role`/`at`, +operator wearing the **Ops** hat) exactly as `SteeringDelta` does; the prose log entry is +**kept** (it reads well in the after-action record) — the delta is the structured sibling, +not a replacement. The share is **non-fatal**: a failed write never breaks the live loop, +because the append-only log already recorded the event. + +## Out of scope + +- **Replay re-application** — re-running a stamp does not yet *re-apply* the captured + perturbations (they are exogenous operator acts). Capturing the structured inputs is the + prerequisite; automatic re-application is a noted follow-up. +- Resolving the documented square-vs-hex `Constraint.cells` drift in `SteeringDelta` + (bugs.md) — `HexCell` is introduced here and is the natural type to migrate it onto later. + +## Exit criteria → evidence + +Asserted by `e2e/skeleton.spec.ts` ("an obstruction is captured in the model as a typed +ExecutionDelta"), captured in `evidence/screenshots/`: + +1. **Captured** — after a `+25 min` obstruction on the exfil drive, an `ExecutionDelta` + lands in the store with `event: 'obstruction'`, `delay_min: 25`, and a `cell.h3`. +2. **Visible** — the object appears in the Data Analysis monitor index and drills down to + its structured fields. `01-execution-delta.png` +3. **Generated, not hand-shaped** — the TypeScript type the app imports comes from LinkML + (`schema/generate.sh` → `schema/gen/remit.ts`); no hand-authored shape (ADR-0012). + +## Verification + +- `npm run typecheck` — 0 errors (the imported `ExecutionDelta` type resolves from `gen/`). +- `npm run test:unit` — 12 golden/unit tests green (kernel set-pieces unchanged). +- `node run-playwright.mjs` — e2e lap + the new capture test green.