From 27aa0e54d7c9f9e6a5f99ff1efa1eb712a8cbd1a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 20:36:56 +0000 Subject: [PATCH 1/4] Add ORBAT red/green assets spec (004) Spec-kit specification for authoring and tuning multiple red (hostile) and green (neutral) asset instances on the ORBAT. Scoped to display-only authoring scaffolding per the DEC-56 horizon split and NF9 honest floor; reactive adversary and constraint/objective emission noted as deferred. https://claude.ai/code/session_01T3ZsVp7G1wAq6Dsfvh3eob --- .../checklists/requirements.md | 40 +++ specs/004-orbat-red-green-assets/spec.md | 240 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 specs/004-orbat-red-green-assets/checklists/requirements.md create mode 100644 specs/004-orbat-red-green-assets/spec.md diff --git a/specs/004-orbat-red-green-assets/checklists/requirements.md b/specs/004-orbat-red-green-assets/checklists/requirements.md new file mode 100644 index 0000000..6684dbb --- /dev/null +++ b/specs/004-orbat-red-green-assets/checklists/requirements.md @@ -0,0 +1,40 @@ +# Specification Quality Checklist: ORBAT — add & tune red and green assets + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-06-12 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Scope is deliberately bounded to the **red and green** sides of the ORBAT and to **display-only** + authoring scaffolding (DEC-56 horizon split, NF9 honest floor); reactive-adversary and + constraint/objective emission are called out as deferred, not in scope. +- The exact per-allegiance parameter field list is left to planning/design (the spec fixes the + shape, not field names) — a candidate for `/speckit-clarify` if the maintainer wants it pinned + before planning. +- All items pass on first iteration. Ready for `/speckit-clarify` (optional) or `/speckit-plan`. diff --git a/specs/004-orbat-red-green-assets/spec.md b/specs/004-orbat-red-green-assets/spec.md new file mode 100644 index 0000000..e7006df --- /dev/null +++ b/specs/004-orbat-red-green-assets/spec.md @@ -0,0 +1,240 @@ +# Feature Specification: ORBAT — add & tune red and green assets + +**Feature Branch**: `claude/fervent-feynman-5g4dpb` (cloud session; spec dir `004-orbat-red-green-assets`) + +**Created**: 2026-06-12 + +**Status**: Draft + +**Input**: User description: "Add red and green assets in the ORBAT (order of battle). Provide the ability to add and tune multiple instances of each asset." + +## Context — why + +The ORBAT (Order of Battle) is the **authoring root** of the command-post stretch (DEC-60): it +catalogues the **participants and potential participants** of an operation before requirement +and plan authoring. Entities on the ORBAT are typed by **allegiance** — **blue** (own force), +**red** (hostile), **green** (neutral / host-nation / civilian) — under *one ontology, three +stances*: the kernel **plans-for** blue, **avoids-assesses** red, and **respects** green. + +Today the entity catalogue is fixed in config and seeds a single own-force scenario. This feature +adds the ability for a planner to **populate the red and green sides of the ORBAT themselves** — +placing as many hostile and neutral assets as the scenario needs, and **tuning each instance's +parameters independently**. Per the horizon-split guard (DEC-56): v1 ships the **authoring and +display scaffolding** (instances are first-class entities, projected across the views); the +*reactive* adversary and capability-matched allocation remain designed-for, not claimed (NF9 — +the honest floor: no fabricated adversary reasoning until that discipline exists, DEC-51). + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Add and tune hostile (red) asset instances (Priority: P1) + +A planner opens the ORBAT and adds one or more **red** assets — a threat such as a patrol, a +sensor/observation post, or a weapon position. Each asset is placed in the area of operations and +its parameters are tuned independently: its label, its location, the extent/reach of the threat it +represents, and its severity. The planner can add several distinct red assets (e.g. two patrols +and one sensor), each tuned to a different position and reach, and see every one of them reflected +on the map. + +**Why this priority**: Red assets are the primary thing a mission plans *around*. Without the +ability to place and shape the threat picture, the planner cannot express the adversary side of a +scenario at all. This story alone is a viable slice: it delivers an authorable, visible red ORBAT. + +**Independent Test**: Open the ORBAT with an empty red side, add two red assets with different +positions and threat extents, tune one of them, and confirm both appear distinctly on the map with +the tuned values reflected — without touching the green side or own force. + +**Acceptance Scenarios**: + +1. **Given** an ORBAT with no red assets, **When** the planner adds a red asset and sets its + location and threat extent, **Then** a hostile-typed entity appears on the ORBAT roster and is + rendered in the red allegiance style on the map at that location. +2. **Given** one red asset already on the ORBAT, **When** the planner adds a second red asset with a + different location, **Then** both instances persist as independent entries with independent + parameters (changing one does not change the other). +3. **Given** a red asset on the ORBAT, **When** the planner tunes its threat extent or severity, + **Then** the change is reflected in its map depiction without affecting any other asset. +4. **Given** a red asset on the ORBAT, **When** the scenario is viewed, **Then** the system shows + the asset's threat as **display-only** context and performs **no** fabricated adversary + movement or reactive reasoning (honest floor). + +--- + +### User Story 2 - Add and tune neutral (green) asset instances (Priority: P1) + +A planner adds one or more **green** assets — a neutral or protected entity such as a host-nation +village, a civilian vessel, a hospital, or a no-strike area. Each green asset is placed and tuned +independently: its label, its location/extent, and its sensitivity (how much it must be respected / +the collateral weight). The planner can maintain several green assets at once, each with its own +parameters. + +**Why this priority**: Green assets carry the rules-of-engagement and collateral picture — the +constraints a defensible plan must respect. They are co-equal with red in expressing a scenario, so +they share P1. This story is independently viable: an authorable, visible green ORBAT. + +**Independent Test**: Open the ORBAT with an empty green side, add two green assets with different +locations and sensitivities, tune one, and confirm both render in the green allegiance style with +the tuned values — independent of red and own force. + +**Acceptance Scenarios**: + +1. **Given** an ORBAT with no green assets, **When** the planner adds a green asset and sets its + location and sensitivity, **Then** a neutral-typed entity appears on the roster and renders in + the green allegiance style on the map. +2. **Given** one green asset already present, **When** the planner adds a second with different + parameters, **Then** both persist as independent, separately-tunable entries. +3. **Given** a green asset, **When** the planner tunes its sensitivity or extent, **Then** the + change is reflected in its depiction without affecting any other asset. + +--- + +### User Story 3 - Manage the ORBAT roster (Priority: P2) + +The planner manages the full set of red and green instances as a roster: rename an asset, duplicate +an existing asset as a starting point for a similar one, and remove an asset that is no longer +relevant. The roster — and every instance's tuned parameters — persists so the scenario can be +revisited across sessions. + +**Why this priority**: Roster management makes authoring *practical* at the scale of a real +scenario (many similar threats), but the core value (add + tune + see) is already delivered by P1. +It builds on P1/P2 rather than standing alone. + +**Independent Test**: With several red and green assets present, duplicate one, rename the copy, +remove a different asset, reload the scenario, and confirm the roster and all tuned values are +exactly as left. + +**Acceptance Scenarios**: + +1. **Given** several assets on the ORBAT, **When** the planner duplicates one, **Then** an + independent copy is created carrying the source's parameters, which can then be tuned separately. +2. **Given** an asset on the ORBAT, **When** the planner removes it, **Then** it disappears from the + roster and from all views, and the remaining assets are unaffected. +3. **Given** an authored ORBAT, **When** the scenario is reloaded, **Then** every red and green + instance and its tuned parameters are restored exactly. + +--- + +### User Story 4 - ORBAT instances appear across the planning views (Priority: P3) + +Every red and green instance is a first-class **entity** (DEC-52), so it appears not only on the map +but wherever the views project entities — including the Sync Matrix when an instance carries a +time-varying aspect (e.g. a patrol that is only active in certain windows). The depiction is +display-only and synchronised with the shared playhead and selection. + +**Why this priority**: Cross-view projection is the pay-off of modelling assets as entities, but the +map depiction in P1/P2 already delivers the essential authoring feedback. This is enrichment. + +**Independent Test**: Add a red asset with an active-time window, scrub the shared timeline, and +confirm its track appears on the Sync Matrix and stays aligned with the map depiction. + +**Acceptance Scenarios**: + +1. **Given** an asset with a time-varying aspect, **When** the planner scrubs the playhead, **Then** + the asset's track on the Sync Matrix and its map depiction update together. +2. **Given** any ORBAT asset, **When** the planner selects it in one view, **Then** the selection is + reflected in the other views. + +--- + +### Edge Cases + +- **Empty ORBAT.** A scenario with no red and/or no green assets is valid; views simply show none of + that allegiance, and planning proceeds. +- **Many instances.** The roster remains legible and usable with a realistic count of similar assets + (e.g. a dozen patrols); instances stay individually identifiable and tunable. +- **Out-of-bounds placement.** An asset placed outside the area of operations is rejected or clamped + with clear feedback rather than silently lost. +- **Out-of-range tuning.** A parameter tuned beyond its allowed bounds is rejected or clamped with + feedback; the asset is never left in an invalid state. +- **Duplicate labels.** Two assets may share a human label; the system still tracks them as distinct + instances (identity is not the label). +- **Removal of a referenced asset.** Removing an asset that another part of the scenario refers to is + handled predictably (blocked with explanation, or cascaded with notice) rather than leaving a + dangling reference. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The ORBAT MUST let a planner add a **red (hostile)** asset and a **green (neutral)** + asset, each typed by allegiance, as a first-class entity in the scenario's entity catalogue. +- **FR-002**: The system MUST support **multiple independent instances** of each allegiance; adding, + editing, or removing one instance MUST NOT affect any other. +- **FR-003**: Each instance MUST expose a set of **tunable parameters** appropriate to its + allegiance, including at minimum a human label, a location within the area of operations, an + extent/reach, and a severity/sensitivity. Tuning a parameter MUST update only that instance. +- **FR-004**: The system MUST **validate** tuned parameters against their allowed bounds and reject + or clamp out-of-range values with clear feedback, never leaving an instance in an invalid state. +- **FR-005**: Each instance MUST be rendered in its **allegiance style** (red for hostile, green for + neutral) on the map at its authored location, visually distinct from own force and from the other + allegiance. +- **FR-006**: The planner MUST be able to **duplicate** an existing instance (creating an independent + copy carrying the source's parameters) and **remove** an instance from the roster. +- **FR-007**: The ORBAT roster and every instance's tuned parameters MUST **persist** so the scenario + can be revisited across sessions with all values restored. +- **FR-008**: Red and green instances MUST be **display-only context** in this release: the system + MUST NOT generate adversary movement, reactive behaviour, or any fabricated assessment beyond the + parameters the planner authored (NF9 honest floor; reactive adversary deferred per DEC-51/56). +- **FR-009**: The feature MUST NOT change the determinism of planning (NF3): authored ORBAT + parameters are inputs; identical inputs MUST continue to yield identical plans and projections. +- **FR-010**: Instances that carry a time-varying aspect MUST project onto the Sync Matrix as a + track, synchronised with the shared playhead and selection alongside the existing entities. +- **FR-011**: The feature MUST reuse the existing **entity / allegiance** vocabulary and the + config-declared catalogue shape rather than introducing a parallel asset model, so the ORBAT + authoring path is *additive* to the data model (allegiance attribute + asset parameters only). + +### Key Entities *(include if feature involves data)* + +- **ORBAT**: The roster of participants and potential participants for a scenario — the authoring + root. Holds the collection of allegiance-typed asset instances and is the entry point that seeds + downstream planning. +- **Asset instance**: A first-class **entity** (DEC-52) with identity, an **allegiance** (red / + green here; blue is own force), a human label, a location/extent in the area of operations, and a + set of allegiance-appropriate tunable parameters. Projected display-only across the views. +- **Red (hostile) parameters**: The threat picture an instance represents — location, threat + extent/reach, severity, and optionally the time window(s) in which it is active. (Threat *source* + only in v1; reactive behaviour deferred.) +- **Green (neutral) parameters**: The respect/collateral picture — location/extent, sensitivity + (collateral weight), and the nature of the protection it represents (e.g. keep-out vs. + minimise-effect). (Constraint/objective *emission* is designed-for, deferred; display-only in v1.) +- **Allegiance**: The blue / red / green typing on an entity that selects the kernel stance + (plan-for / avoid-assess / respect) and the rendering style. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: A planner can add a new red or green asset, place it, and see it on the map in **under + 30 seconds**, with no prior training beyond the on-screen affordances. +- **SC-002**: A planner can maintain **at least 10 instances each** of red and green on one ORBAT, + with every instance remaining individually identifiable and independently tunable. +- **SC-003**: Tuning any single instance's parameter changes **only that instance** in **100%** of + cases — no other instance or own force is affected. +- **SC-004**: After authoring an ORBAT and reloading the scenario, **100%** of instances and their + tuned parameter values are restored exactly. +- **SC-005**: Across the feature, the system produces **zero** fabricated adversary behaviour or + assessment — every depicted red/green effect traces directly to a planner-authored parameter + (honest-floor audit). +- **SC-006**: Re-planning the same scenario with an unchanged ORBAT yields **identical** plans and + projections (determinism preserved). + +## Assumptions + +- **Display-only scope (v1).** Red assets are passive threat *sources* and green assets are passive + collateral/ROE *markers*; neither yet emits live kernel constraints/objectives nor reacts. The + authoring and display scaffolding ships now; capability (capability-matched allocation, reactive + adversary, constraint/objective emission) is designed-for and deferred under the DEC-56 freeze + guard (DEC-59/60/61). Blue/own-force authoring is **out of scope** for this feature — it is the + existing own-force entity; this feature adds only the red and green sides. +- **Reuse the entity model.** Assets are modelled as existing allegiance-typed **entities** (DEC-52) + in the config-declared catalogue (DEC-48…50); the only data-model additions are the allegiance + attribute and the per-asset tunable parameters (DEC-60), not a new object family. +- **Area of operations.** Assets are placed within the current H3-hex area of operations and located + by hex/lat-lon consistent with the existing map and routing (spec 003). +- **Parameter set.** The concrete per-allegiance parameter list (e.g. exact red threat fields and + green sensitivity scale) is a reasonable starter set chosen at planning/design time; the spec + fixes the *shape* (label + location + extent + severity/sensitivity, per allegiance), not the + exact field names. +- **Persistence.** The existing scenario/store mechanism is reused to persist the ORBAT; no new + storage system is introduced. +- **Single planner, sequential.** Authoring is by a single user in v1 (multi-user role distribution + and stamped-delta concurrency remain the DEC-61 deferred seam). From af7e315ec4b8405d58d01bf813b8393ac71863b3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 20:42:34 +0000 Subject: [PATCH 2/4] Plan ORBAT red/green assets (004): research, data-model, contracts, quickstart Design artifacts for the ORBAT red/green asset feature. Models assets as allegiance-typed Entities (DEC-52/60), schema-defined and regenerated (Principle I), display-only under the DEC-56 guard / NF9 honest floor. Adds an Allegiance enum + a new orbat schema module, an ORBAT model + a roster authoring surface in app/js, projected via the existing map and Sync-Matrix. Blog post sketched for /speckit-implement. https://claude.ai/code/session_01T3ZsVp7G1wAq6Dsfvh3eob --- CLAUDE.md | 3 +- .../contracts/orbat-store.md | 47 ++++++ .../contracts/orbat-ui.md | 39 +++++ .../004-orbat-red-green-assets/data-model.md | 130 ++++++++++++++++ specs/004-orbat-red-green-assets/plan.md | 141 ++++++++++++++++++ .../004-orbat-red-green-assets/quickstart.md | 59 ++++++++ specs/004-orbat-red-green-assets/research.md | 97 ++++++++++++ 7 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 specs/004-orbat-red-green-assets/contracts/orbat-store.md create mode 100644 specs/004-orbat-red-green-assets/contracts/orbat-ui.md create mode 100644 specs/004-orbat-red-green-assets/data-model.md create mode 100644 specs/004-orbat-red-green-assets/plan.md create mode 100644 specs/004-orbat-red-green-assets/quickstart.md create mode 100644 specs/004-orbat-red-green-assets/research.md diff --git a/CLAUDE.md b/CLAUDE.md index 9f707cd..b994209 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -277,5 +277,6 @@ branch; allow workflows to write). For additional context about technologies to be used, project structure, -shell commands, and other important information, read the current plan +shell commands, and other important information, read the current plan: +`specs/004-orbat-red-green-assets/plan.md` diff --git a/specs/004-orbat-red-green-assets/contracts/orbat-store.md b/specs/004-orbat-red-green-assets/contracts/orbat-store.md new file mode 100644 index 0000000..dd0da9b --- /dev/null +++ b/specs/004-orbat-red-green-assets/contracts/orbat-store.md @@ -0,0 +1,47 @@ +# Contract — ORBAT model & persistence (`app/js/orbat/orbat.js`) + +The ORBAT model is the only writer of the authored roster. It is a **pure, deterministic** module +over the LinkML-generated `Orbat`/`Asset` types (imported from `schema/gen/remit.ts`); UI and +rendering read through it. All operations return a **new** draft (no in-place mutation), keeping +identity stable and rendering reproducible (NF3). + +## Types + +Imported from the generated schema (do not redeclare): +`Orbat`, `Asset`, `RedParams`, `GreenParams`, `TimeWindow`, `Allegiance`, `Protection`. + +## Operations + +| Function | Signature (conceptual) | Guarantees | +|---|---|---| +| `emptyOrbat(name)` | `(string) → Orbat` | version 1, `assets: []`. | +| `addAsset(orbat, { allegiance, position })` | `(Orbat, seed) → { orbat, id }` | mints a fresh unique `id`; seeds default params for the allegiance; rejects `allegiance='blue'` (out of scope) and out-of-AO `position`. | +| `duplicateAsset(orbat, id)` | `(Orbat, id) → { orbat, id }` | deep-copies the source's params under a **new** `id`; source unchanged (FR-002/006). | +| `tuneAsset(orbat, id, patch)` | `(Orbat, id, Partial) → Orbat` | applies `patch` to **only** that asset; **clamps/validates** bounds (FR-004); other assets byte-identical. | +| `removeAsset(orbat, id)` | `(Orbat, id) → Orbat` | drops the asset; remaining assets unaffected (FR-006). | +| `validate(asset)` | `(Asset) → { ok, issues[] }` | bounds + window `start ≤ end` + position-in-AO + allegiance/param-group match. | +| `canonical(orbat)` | `(Orbat) → string` | assets sorted by `id`, canonical JSON (DEC-35) — the persistence & identity form. | + +## Persistence + +- **Draft**: `saveDraft(orbat)` writes `canonical(orbat)` to `localStorage['remit.orbat.M-001']`; + `loadDraft()` restores it (returns `emptyOrbat` if absent). Mirrored on every mutating op so reload + restores exactly (SC-004). +- **Commit**: `commit(orbat, objects)` PUTs an immutable, content-addressed `Orbat` into the shared + `ObjectStore` with `lineage.previous_version` → the prior committed id (Principle V; idempotent + re-PUT per DEC-35). + +## Invariants + +- **Determinism (NF3)**: `canonical` is a pure function of asset content (sorted by `id`); equal + rosters ⇒ equal bytes ⇒ equal content id. No timestamps/insertion-order leak in. +- **Honest floor (NF9)**: the module exposes **no** function that derives adversary behaviour or + mutates a plan; output is consumed display-only. +- **Isolation (FR-002/SC-003)**: every op returns a new draft touching only the targeted asset. + +## Display adapter + +`assetToEntity(asset) → Entity` (display-only; the hand-written carve-out) produces the +`buildEntities()`-shaped entity: allegiance-typed, `provenance.kind='actor'`, a `position` for the +map marker, and — for red assets with `active_windows` — a `window`-type aspect for the Sync Matrix +(reusing the satellite-pass render path). Contains no kernel reference. diff --git a/specs/004-orbat-red-green-assets/contracts/orbat-ui.md b/specs/004-orbat-red-green-assets/contracts/orbat-ui.md new file mode 100644 index 0000000..868fb79 --- /dev/null +++ b/specs/004-orbat-red-green-assets/contracts/orbat-ui.md @@ -0,0 +1,39 @@ +# Contract — ORBAT authoring surface (`app/js/shell/orbat-panel.js`) + +A config-declared role-tab (DEC-61 shell seed; natural home: the `sme-int` role — "red/green +entities and threat"). It mounts with the shared context `{ objects, world, playhead, … }` and reads +/ writes the roster **only** through `app/js/orbat/orbat.js` (contract: `orbat-store.md`). + +## Affordances (testable contract) + +| Affordance | Behaviour | Backing requirement | +|---|---|---| +| **Roster** | Two groups, **Red (hostile)** and **Green (neutral)**, listing each asset by label + key params. Empty groups show an explicit "none" state. | FR-001, edge: empty ORBAT | +| **Add red / Add green** | Adds a default asset of that allegiance; placement via the map's click-to-pick-hex (spec 003 F5) or a default AO-centre position. Appears immediately in the roster and on the map. | FR-001/005, US1/US2 | +| **Per-row tuners** | Numeric/range inputs for `label`, `extent_m`, `severity` (red) / `sensitivity` (green), `protection` (green), and `active_windows` (red). Editing re-renders map + Sync-Matrix live. | FR-003, US1/US2 | +| **Duplicate** | One click clones the row under a new id; the copy is independently tunable. | FR-006, US3 | +| **Remove** | Removes the row from roster and all views; others unaffected. | FR-006, US3 | +| **Validation feedback** | Out-of-range tunes and out-of-AO placements are clamped/rejected with an inline message; the asset never enters an invalid state. | FR-004, edges | +| **Selection** | Selecting a row highlights the asset on the map / Sync-Matrix and vice-versa (shared selection). | US4 | + +## Projection contract (display-only) + +- **Map** (`map.render`): each asset draws an allegiance-coloured point + faint extent ring + + label, visually distinct from own force and the other allegiance (FR-005). Red `#ff7b72`-family, + green `#38d39f`-family (palette in `key_facts.md`). +- **Sync Matrix** (`sync-matrix`): a red asset with `active_windows` contributes a `window`-render + track via a catalogue row; it stays aligned with the shared playhead and selection (FR-010/US4). +- **Honest floor**: nothing the panel shows implies adversary motion or assessment beyond authored + params (FR-008/NF9). + +## Persistence contract + +- Every mutating affordance calls `saveDraft` (mirror to localStorage). On mount, `loadDraft` + restores the roster and all tuned values exactly (FR-007/SC-004). +- A **Commit ORBAT** action mints an immutable version in the `ObjectStore` (lineage preserved). + +## Non-goals (deferred, asserted by tests where cheap) + +- No routing/kernel influence from any asset (NF9/NF3). +- No blue/own-force authoring here (existing entity). +- No live ROE constraint / collateral objective emission (DEC-60 J3 capability — H2/H3). diff --git a/specs/004-orbat-red-green-assets/data-model.md b/specs/004-orbat-red-green-assets/data-model.md new file mode 100644 index 0000000..5c83462 --- /dev/null +++ b/specs/004-orbat-red-green-assets/data-model.md @@ -0,0 +1,130 @@ +# Phase 1 — Data Model: ORBAT red/green assets + +**Source of truth = LinkML** (Principle I). The shapes below are authored in the schema modules and +**regenerated** into `schema/gen/remit.schema.json` + `schema/gen/remit.ts` via `schema/generate.sh`. +The app imports the generated TS; it does not hand-author these shapes. Render closures (aspect +time-functions) are the documented behaviour/UI carve-out and stay in `app/js`. + +## Schema additions + +### `common.yaml` — new enum + +```yaml +enums: + Allegiance: + description: >- + Side typing on an entity (DEC-60). Selects the kernel STANCE — plan-for (blue) / + avoid-assess (red) / respect (green). v1 is display-only (NF9 honest floor). + permissible_values: + blue: { description: own force } + red: { description: hostile / adversary (threat source; passive in v1) } + green: { description: neutral / host-nation / civilian (ROE & collateral; inert in v1) } +``` + +### `entities.yaml` — one attribute on `Entity` + +```yaml + attributes: + # ...existing id/label/kind/provenance/aspects... + allegiance: + range: Allegiance + description: side typing (DEC-60); absent ⇒ unaligned/own-context as today +``` + +> Additive and optional, so existing entities (self/tide/sat) remain valid. + +### `orbat.yaml` — NEW module (imported by `remit.yaml`) + +```yaml +classes: + Orbat: + description: >- + The roster of participants & potential participants for a scenario — the authoring root + (DEC-60). Versioned & immutable when committed (lineage); the editable working draft mirrors + to localStorage. v1 authors the red & green sides; blue is the existing own force. + attributes: + id: { identifier: true, description: content id of the canonical form (DEC-35) } + name: {} + version: { range: integer } + assets: { range: Asset, multivalued: true, inlined_as_list: true } + lineage: { range: Lineage, inlined: true } + + Asset: + description: >- + One ORBAT entry — a first-class located thing (DEC-52) typed by allegiance, with + independently-tunable parameters. Display-only in v1. + attributes: + id: { identifier: true, description: stable per-instance identity (not the label) } + allegiance: { range: Allegiance, required: true } + label: { description: human label; need not be unique } + position: { range: Waypoint, inlined: true, description: AO location (H3 cell / lat-lon) } + extent_m: { range: float, description: reach/footprint radius in metres } + red: { range: RedParams, inlined: true, description: present iff allegiance = red } + green: { range: GreenParams, inlined: true, description: present iff allegiance = green } + + RedParams: + description: Hostile threat picture (threat SOURCE only in v1; reactive behaviour deferred, DEC-51). + attributes: + severity: { range: integer, description: graded threat severity (e.g. 1..5) } + active_windows: { range: TimeWindow, multivalued: true, inlined_as_list: true, + description: mission-minute windows the threat is active (Sync-Matrix track) } + + GreenParams: + description: Neutral / collateral picture (ROE & collateral emission deferred; inert in v1, DEC-60 J3). + attributes: + sensitivity: { range: integer, description: graded collateral weight (e.g. 1..5) } + protection: { range: Protection, description: nature of the rule (tagged for the future hard/soft split) } + + TimeWindow: + description: A mission-minute interval [start,end]. + attributes: + start_min: { range: integer, required: true } + end_min: { range: integer, required: true } + +enums: + Protection: + permissible_values: + keep_out: { description: no-go / no-strike area (future HARD constraint) } + minimise_effect: { description: collateral to be minimised (future SOFT objective) } +``` + +> `Waypoint` and `Lineage` already exist in the schema (reused, not redefined). + +## Entities (summary) + +| Entity | Key fields | Relationships | Notes | +|---|---|---|---| +| **Orbat** | `id`, `version`, `assets[]`, `lineage` | contains `Asset[]` | versioned/immutable when committed; working draft in localStorage | +| **Asset** | `id`, `allegiance`, `label`, `position`, `extent_m` | `red` xor `green` params | first-class Entity (DEC-52); display-only | +| **RedParams** | `severity`, `active_windows[]` | `TimeWindow[]` | threat source only (NF9) | +| **GreenParams** | `sensitivity`, `protection` | `Protection` enum | inert in v1 (DEC-60 J3) | + +## Validation rules (from the spec) + +- **FR-001/003**: an Asset MUST have `allegiance ∈ {red, green}` (blue is out of scope here), + a `position` inside the AO, and the parameter group matching its allegiance. +- **FR-002**: `id` is unique per instance and never reused; duplicating mints a fresh `id`. +- **FR-004**: `extent_m`, `severity`, `sensitivity` are clamped to their declared bounds; an + `active_window` MUST satisfy `start_min ≤ end_min` (rejected/clamped with feedback otherwise). +- **Edge — out-of-bounds position**: rejected or clamped to the AO with feedback (never silently lost). +- **Edge — duplicate labels**: permitted; identity is `id`, not `label`. +- **FR-008/NF9**: no field drives kernel behaviour; all are display inputs only. + +## Lifecycle / state + +```text +(draft) add ──► tune ──► duplicate / remove ──► tune … ┐ mirrored to localStorage each change + │ + commit ──► Orbat v_n (immutable, content-addressed, lineage→v_{n-1}) +``` + +- The editable surface is the **draft**; **commit** mints an immutable `Orbat` version in the + `ObjectStore` (Principle V). Reload restores the draft from localStorage (SC-004). +- Canonical/sorted-by-`id` serialisation underpins determinism (NF3) and stable content ids (DEC-35). + +## App adapter (display-only, hand-written — carve-out) + +`app/js/orbat/orbat.js` maps each committed/draft `Asset` → an `Entity` for `buildEntities()`: +`{ id, label, allegiance, provenance:{kind:'actor'}, aspects }`. Red assets with `active_windows` +expose a `window`-type aspect (reusing the satellite-pass render path) so they appear as Sync-Matrix +tracks; position feeds the map marker. No `at()` closure references the kernel or alters a plan. diff --git a/specs/004-orbat-red-green-assets/plan.md b/specs/004-orbat-red-green-assets/plan.md new file mode 100644 index 0000000..da7428b --- /dev/null +++ b/specs/004-orbat-red-green-assets/plan.md @@ -0,0 +1,141 @@ +# Implementation Plan: ORBAT — add & tune red and green assets + +**Branch**: `claude/fervent-feynman-5g4dpb` (spec dir `004-orbat-red-green-assets`) | **Date**: 2026-06-12 | **Spec**: [spec.md](./spec.md) + +**Input**: Feature specification from `specs/004-orbat-red-green-assets/spec.md` + +## Summary + +Bring the **red** and **green** sides of the ORBAT (DEC-60) forward into the app as +**display-only authoring scaffolding** (DEC-56 horizon split, NF9 honest floor). A planner can add, +duplicate, tune, and remove **multiple independent instances** of hostile (red) and neutral (green) +assets; each is a first-class **Entity** (DEC-52) carrying an **allegiance**, projected onto the map +(allegiance-coloured markers) and onto the Sync Matrix (a track when it carries a time-varying +aspect). The serialisable shape — the `Allegiance` enum, the asset parameters, and the `Orbat` +container — is added to the **LinkML schema** (Principle I, non-negotiable) and regenerated; the +app imports the generated types and reuses the existing entity/projection plumbing +(`buildEntities` → map + `sync-matrix`). The authored ORBAT persists across sessions. + +## Technical Context + +**Language/Version**: JavaScript (ES modules, `// @ts-check` + JSDoc); Node ≥ 20 for tooling. + +**Primary Dependencies**: existing app stack — `h3-js`, `maplibre-gl`, `@deck.gl/*`, Vite (ADR-0014); +LinkML toolchain (`schema/generate.sh`) for the data model. **No new runtime dependencies** (ADR-0014: +deps minimised and maintainer-approved). + +**Storage**: in-browser. Authored ORBAT persists via the browser (localStorage of the canonical +ORBAT JSON) so the scenario survives reload (FR-007); the content-addressed `ObjectStore` +(`app/js/stores/stores.js`) holds committed immutable versions with lineage. + +**Testing**: `npm run test:unit` (`node --test`) for the ORBAT model (canonical/deterministic +add/duplicate/tune/remove, validation/clamping); `npm run test:e2e` (Playwright cloud wrapper) for +the authoring surface + map/Sync-Matrix projection; evidence screenshots under +`specs/004-orbat-red-green-assets/evidence/`. + +**Target Platform**: modern browser (the static app served from `app/`); cloud + local dev. + +**Project Type**: single static web app (no backend) — Option 1 layout below. + +**Performance Goals**: authoring feedback is immediate (map/Sync-Matrix re-render on tune within one +frame); legible roster at ≥ 10 instances per allegiance (SC-002). + +**Constraints**: determinism (NF3) — authored params are canonical inputs, identical inputs ⇒ +identical plans/projections; honest floor (NF9) — no fabricated adversary behaviour; additive to the +data model (allegiance attribute + asset params only, DEC-60); no new build machinery. + +**Scale/Scope**: one ORBAT per scenario; tens of asset instances; two new allegiances authorable +(red, green) — blue/own-force is the existing entity, out of scope. + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Principle | Status | Notes | +|---|---|---| +| **I. LinkML is the data-model source of truth** (NON-NEGOTIABLE) | ✅ PASS (gated) | The persisted ORBAT/asset shape is serialisable object-core, so it is **schema-defined and regenerated**, never hand-authored: add `Allegiance` enum + `Asset`/`Orbat` to the schema modules, run `schema/generate.sh`, import the generated TS in `app/js`. **Display-only render closures** (aspect `at()` functions, the catalogue rows) stay hand-written — the documented carve-out for behaviour + UI-only shapes. | +| **II. No-build static app** | ✅ PASS | Stays in `app/js` ES modules + `// @ts-check`; **no new build machinery**. (Vite already adopted under ADR-0014 — a recorded deviation; this feature adds nothing new.) | +| **III. Spec-driven workflow + blog** | ✅ PASS | Following spec → plan → tasks → implement; blog post sketched in Phase 2 and authored at implement. | +| **IV. Durable project memory** | ✅ PASS | New ADR for "ORBAT red/green authoring scaffolding" in `decisions.md`; `issues.md` work-log entry; `key_facts.md` for the allegiance palette/persistence key — recorded at implement. | +| **V. Repo canonical + immutability** | ✅ PASS | Committed ORBAT versions are immutable with lineage (the working draft is the editable surface; commit mints a new version). | + +**No violations** → Complexity Tracking left empty. + +## Project Structure + +### Documentation (this feature) + +```text +specs/004-orbat-red-green-assets/ +├── plan.md # This file +├── spec.md # Feature spec +├── research.md # Phase 0 — decisions resolved +├── data-model.md # Phase 1 — schema additions (allegiance, asset, orbat) +├── contracts/ +│ ├── orbat-store.md # Persisted ORBAT object + add/duplicate/tune/remove operations +│ └── orbat-ui.md # ORBAT authoring surface contract (affordances, validation, projection) +├── quickstart.md # Phase 1 — runnable validation guide +├── checklists/ +│ └── requirements.md # Spec quality checklist (done at /speckit-specify) +├── blog/ # Authored at /speckit-implement (post.md + screenshots/) +└── evidence/ # Playwright screenshots captured at implement +``` + +### Source Code (repository root) + +```text +schema/ +├── common.yaml # + Allegiance enum (blue|red|green) +├── entities.yaml # + allegiance attribute on Entity +├── force.yaml # (reference; Profile/State unchanged) +├── orbat.yaml # NEW module: Orbat container + Asset (+ RedParams/GreenParams) +├── remit.yaml # entry schema: import the new orbat module +└── gen/ # REGENERATED (remit.schema.json, remit.ts) — do not hand-edit + +app/js/ +├── orbat/ +│ └── orbat.js # NEW: ORBAT model — add/duplicate/tune/remove, validation/clamp, +│ # canonicalisation; asset → Entity adapter (allegiance-typed) +├── entities/entities.js # buildEntities() folds in authored ORBAT assets as display-only entities +├── views/map.js # render() draws allegiance-coloured asset markers (extent ring + label) +├── views/sync-matrix.js # (unchanged plumbing) shows asset tracks via the catalogue +├── shell/orbat-panel.js # NEW: the authoring surface (role-tab/panel) — roster + per-instance tuners +└── main.js # wires the panel + feeds authored assets into map.render / entities + +tests/ (node --test) and e2e/ (Playwright) +├── e2e/orbat.spec.js # NEW: author → tune → see on map/matrix → persist across reload +└── app/js/orbat/*.test.js # NEW: model unit tests (deterministic, validation/clamp) +``` + +**Structure Decision**: Single static web app (Option 1). The data core extends the LinkML schema +(new `orbat.yaml` module + two small edits to `common.yaml`/`entities.yaml`), regenerated into +`schema/gen/`. The app gains one model module (`app/js/orbat/orbat.js`) and one UI surface +(`app/js/shell/orbat-panel.js`), then reuses the existing entity → map/Sync-Matrix projection rather +than adding a parallel rendering path. + +## Complexity Tracking + +> No constitution violations — section intentionally empty. + +## Phase 2 — Blog post plan (REMIT) + +Planning only (authored at `/speckit-implement` into `specs/004-orbat-red-green-assets/blog/post.md`, +from `docs/blog-post-template.md`): + +- **At a glance**: **"The ORBAT grows a red and green side — drop in as many threats and protected + places as a scenario needs, and tune each one."** Featured screenshot: the map with several + red threat rings and green no-strike markers placed, plus the roster panel open. +- **The problem**: the entity catalogue was fixed in config and seeded only own force; a planner + could not express the adversary/neutral picture of a scenario. +- **Options**: (a) bespoke per-allegiance asset objects vs (b) reuse the allegiance-typed Entity + + config catalogue; (c) free-form map drawing vs (d) a tunable parameter roster; (e) where the data + lives — hand-written app type vs LinkML-generated. +- **The strategy**: reuse Entity + allegiance (DEC-60), schema-define the serialisable shape and + regenerate (Principle I), keep it display-only under the DEC-56 guard / NF9 honest floor, project + through the existing map + Sync-Matrix. +- **The results**: add/duplicate/tune/remove multiple instances; allegiance-coloured map markers; + Sync-Matrix tracks for time-windowed assets; persistence across reload; determinism preserved. +- **Screenshots to capture** (Playwright → `evidence/screenshots/*.png`): empty ORBAT → one red asset + placed → multiple red+green assets with extent rings → a tuner adjusting threat extent (before/after) + → a time-windowed red asset's track on the Sync Matrix → roster after duplicate/remove → scenario + restored after reload. diff --git a/specs/004-orbat-red-green-assets/quickstart.md b/specs/004-orbat-red-green-assets/quickstart.md new file mode 100644 index 0000000..d2048f6 --- /dev/null +++ b/specs/004-orbat-red-green-assets/quickstart.md @@ -0,0 +1,59 @@ +# Quickstart — validate ORBAT red/green assets + +A runnable validation guide proving the feature end-to-end. Details live in +[data-model.md](./data-model.md) and [contracts/](./contracts/); this is the run/verify path. + +## Prerequisites + +```bash +npm install +# Data-model changes require regenerating the schema artefacts (bootstraps a LinkML venv): +bash schema/generate.sh # → schema/gen/remit.schema.json, schema/gen/remit.ts +``` + +## Run the app + +```bash +npm run dev # Vite dev server; open the ORBAT (SME-Intel) tab +# or: npm run build && npm run preview +``` + +## Validation scenarios (map to user stories) + +### US1 — Add & tune red assets (P1) +1. Open the ORBAT with an empty red side → the **Red (hostile)** group shows "none". +2. **Add red**, place it on the map → a red point + extent ring + label appears; a row appears in the roster. +3. **Add red** again at a different cell → two independent red rows; both visible on the map. +4. Tune the first asset's **extent** and **severity** → only its ring/label changes; the second is untouched. +5. Confirm **no** adversary motion or assessment is shown beyond the authored params (honest floor). + +### US2 — Add & tune green assets (P1) +1. With an empty green side, **Add green**, set location + **sensitivity** + **protection** → a green + marker renders, distinct from red and own force. +2. **Add green** again with different params → two independent green rows; tuning one leaves the other unchanged. + +### US3 — Manage the roster (P2) +1. **Duplicate** a red asset → an independent copy (new id) carrying the source's params; tune it separately. +2. **Remove** a green asset → it disappears from roster and map; the rest are unaffected. +3. **Reload the page** → the full roster and every tuned value are restored exactly (localStorage draft). + +### US4 — Cross-view projection (P3) +1. Give a red asset an **active window** (e.g. H+30..H+60) → a track appears on the Sync Matrix. +2. Scrub the shared playhead → the track and the map marker stay aligned; selecting the row highlights it in both views. + +## Automated checks + +```bash +npm run test:unit # orbat model: deterministic add/duplicate/tune/remove, validation/clamp, canonical identity +npm run test:e2e # cloud Playwright: author → tune → see on map/matrix → persist across reload +# (local: npm run test:e2e:local) +``` + +Capture evidence screenshots into `specs/004-orbat-red-green-assets/evidence/screenshots/` during the +e2e run (empty → one red → many red+green → tune before/after → Sync-Matrix track → after reload). + +## Expected outcomes (success criteria) + +- New asset placed and visible in **< 30 s** (SC-001); ≥ 10 instances per allegiance stay legible/tunable (SC-002). +- Tuning affects **only** the targeted asset (SC-003); reload restores **100%** of instances/values (SC-004). +- **Zero** fabricated adversary behaviour (SC-005); re-planning an unchanged ORBAT is **identical** (SC-006). diff --git a/specs/004-orbat-red-green-assets/research.md b/specs/004-orbat-red-green-assets/research.md new file mode 100644 index 0000000..dd53956 --- /dev/null +++ b/specs/004-orbat-red-green-assets/research.md @@ -0,0 +1,97 @@ +# Phase 0 — Research & Decisions: ORBAT red/green assets + +All decisions resolve the spec's assumptions into concrete design choices, grounded in the existing +codebase and the register (DEC-52/56/60, NF3/NF9). No open `NEEDS CLARIFICATION` remain. + +## D1 — Model assets as allegiance-typed Entities, not a new object family + +- **Decision**: Each red/green asset is a first-class **Entity** (DEC-52) carrying an **allegiance**. + Reuse the existing entity → projection plumbing (`buildEntities` → `map` + `sync-matrix`). +- **Rationale**: DEC-60 mandates "reuses entities/providers/commitments wholesale; adds only the + asset-capability vocabulary + allegiance attribute." A parallel asset type would duplicate the + projection machinery and violate the additive-only guard. +- **Alternatives considered**: a standalone `ThreatMarker`/`RoeZone` type (rejected — duplicates + Entity, breaks one-ontology/three-stances symmetry); piggy-backing on `Channel`/excursions + (rejected — that is the *capability* path, deferred to H2/H3, and would imply kernel coupling). + +## D2 — Schema-define the serialisable shape and regenerate (Principle I) + +- **Decision**: Add `Allegiance` enum (`blue|red|green`) to `schema/common.yaml`; add an optional + `allegiance` attribute to `Entity` in `schema/entities.yaml`; add a new `schema/orbat.yaml` module + with `Orbat` (the roster container) and `Asset` (+ red/green parameter groups), imported by + `schema/remit.yaml`. Run `schema/generate.sh`; the app imports the generated TS. +- **Rationale**: the persisted ORBAT is serialisable object-core → it is exactly what LinkML owns + (Principle I, NON-NEGOTIABLE). Hand-authoring it in `app/js` would be the re-listing anti-pattern. +- **Alternatives considered**: hand-written app typedef (rejected — Principle I); deferring schema + work to a later capability spec (rejected — persistence is in-scope now, so the shape must be + authored now). The **render closures** (aspect `at()` time-functions) stay hand-written — the + documented behaviour/UI carve-out. +- **Note**: `schema/generate.sh` bootstraps a LinkML venv (distro `pip` can't install LinkML — see + `bugs.md`); regenerated files in `schema/gen/` are outputs, never hand-edited. + +## D3 — Per-allegiance parameter set (the starter shape) + +- **Decision**: every Asset carries `id` (stable identity), `allegiance`, `label`, `position` + (an AO location — H3 cell / lat-lon, consistent with spec 003), and an `extent` (reach in metres / + hex radius). Allegiance-specific groups: + - **Red (hostile)**: `severity` (graded scale, e.g. 1–5), and optional `active_windows` + (mission-minute `[start,end]` list) for a time-varying threat (the Sync-Matrix track). + - **Green (neutral)**: `sensitivity` (collateral weight, graded scale), and `protection` + (nature of the rule — e.g. `keep_out` vs `minimise_effect`), tagged for the future + hard-constraint / soft-objective split (DEC-60 J3), but **inert** in v1. +- **Rationale**: fixes the *shape* the spec promised (label + location + extent + severity/sensitivity + per allegiance) while leaving exact scales tunable; the red `active_windows` is what makes an asset + project as a Sync-Matrix track, reusing the existing `window` aspect render type. +- **Alternatives considered**: a single flat param bag for both allegiances (rejected — loses the + differentiated-machinery intent of DEC-60); modelling green ROE as live commitments now (rejected — + that emission is the deferred capability, NF9). + +## D4 — Authoring surface: a roster panel with per-instance tuners + +- **Decision**: a new surface `app/js/shell/orbat-panel.js` (a config-declared role-tab in the + DEC-61 shell seed, `shell/roles.js`) presenting the roster (grouped red/green), an **Add** action + per allegiance, and per-row **duplicate / remove / tune** controls. Placement uses the existing + map click-to-pick-hex affordance (spec 003 F5); tuning uses numeric/range inputs bound to the + draft asset. +- **Rationale**: reuses the existing shell/role-tab pattern and the map's pick-hex interaction; + keeps authoring in one legible place (SC-001/002). The SME-Intel role (`sme-int`, already described + as "red/green entities and threat") is the natural home. +- **Alternatives considered**: inline map-only editing with no roster (rejected — poor legibility at + ≥ 10 instances, SC-002); a modal dialog per asset (rejected — slower to tune/compare many). + +## D5 — Persistence: canonical ORBAT JSON in localStorage; immutable versions in the store + +- **Decision**: the **working draft** ORBAT lives in app state and is mirrored to `localStorage` + under a fixed key (canonical JSON) so it survives reload (FR-007/SC-004). Committing mints an + immutable, content-addressed version in the existing `ObjectStore` with lineage (Principle V). +- **Rationale**: the in-memory `ObjectStore` is a mock that does not survive reload; localStorage is + the lightest durable store consistent with "no backend". Canonical JSON keeps identity stable + (DEC-35) and feeds determinism (D6). +- **Alternatives considered**: IndexedDB (rejected — overkill for one small object); URL-encoded + scenario (rejected — unwieldy at scale, though a nice future share-link); server persistence + (rejected — no backend). + +## D6 — Determinism (NF3) and the honest floor (NF9) + +- **Decision**: assets are sorted by `id` and canonicalised before they enter `buildEntities` / + `map.render`; rendering and any downstream plan identity key off the canonical form. Assets are + **display-only**: no asset influences routing/the kernel, and nothing is synthesised beyond + authored params. +- **Rationale**: NF3 requires identical inputs ⇒ identical outputs; canonical, sorted inputs + guarantee it. NF9 (honest floor) forbids fabricated adversary reasoning until the DEC-51 discipline + exists — so red is a passive threat *source* only. +- **Alternatives considered**: insertion-ordered lists (rejected — order would leak into identity); + letting red assets nudge `edgeCost` for a "plan-around" demo (rejected — that is the deferred + avoid-assess capability and would breach NF9 in v1). + +## D7 — Map depiction + +- **Decision**: extend `map.render` to draw authored assets: a `ScatterplotLayer` point at the + asset position in the allegiance colour, a faint extent ring (radius = `extent`), and a `TextLayer` + label — reusing the existing marker layers. Palette recorded in `key_facts.md`: red `#ff7b72`-family + (already the obstruction colour), green `#38d39f`-family (already a coincidence colour), distinct + from own-force markers. +- **Rationale**: reuses the existing deck.gl layers (`ScatterplotLayer`/`TextLayer`) already in + `map.js`; no new rendering dependency. +- **Alternatives considered**: deck.gl `IconLayer` with NATO-style symbols (deferred — nice polish, + more asset plumbing than v1 scaffolding needs). From 31928918539a1e1300c606bbeb7affff80f2b962 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 20:56:46 +0000 Subject: [PATCH 3/4] Tasks for ORBAT red/green assets (004) 39 tasks across setup (schema regen), foundational (model + persistence + projection + panel mount), and the four user stories (red MVP, green, roster management, Sync-Matrix projection), plus polish. Tests included per the project quality gates. https://claude.ai/code/session_01T3ZsVp7G1wAq6Dsfvh3eob --- specs/004-orbat-red-green-assets/tasks.md | 213 ++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 specs/004-orbat-red-green-assets/tasks.md diff --git a/specs/004-orbat-red-green-assets/tasks.md b/specs/004-orbat-red-green-assets/tasks.md new file mode 100644 index 0000000..53d4904 --- /dev/null +++ b/specs/004-orbat-red-green-assets/tasks.md @@ -0,0 +1,213 @@ +--- + +description: "Task list for ORBAT red/green assets implementation" +--- + +# Tasks: ORBAT — add & tune red and green assets + +**Input**: Design documents from `specs/004-orbat-red-green-assets/` + +**Prerequisites**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md), [data-model.md](./data-model.md), [contracts/](./contracts/) + +**Tests**: INCLUDED — the project's quality gates require `npm run test:unit` (`node --test`, `test/*.test.mjs`) and `npm run test:e2e` (Playwright cloud wrapper, `e2e/*.spec.ts`) on every PR (constitution §Development Workflow). Graphical work captures evidence screenshots. + +**Organization**: grouped by user story (US1–US4 from spec.md) for independent implementation and testing. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: can run in parallel (different files, no dependency on incomplete tasks) +- **[Story]**: US1 / US2 / US3 / US4 (setup, foundational, polish carry no story label) + +## Path Conventions + +Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `test/*.test.mjs`, e2e in `e2e/*.spec.ts`. + +--- + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Extend the LinkML data model (Principle I — source of truth) and scaffold the new module. Regenerated artefacts are outputs; never hand-edited. + +- [ ] T001 Add `Allegiance` enum (`blue|red|green`, with stance notes) to `schema/common.yaml` +- [ ] T002 Add optional `allegiance` attribute (range `Allegiance`) to `Entity` in `schema/entities.yaml` +- [ ] T003 Create `schema/orbat.yaml` module — `Orbat`, `Asset`, `RedParams`, `GreenParams`, `TimeWindow` classes + `Protection` enum (per [data-model.md](./data-model.md)); reuse existing `Waypoint`/`Lineage` +- [ ] T004 Import the `orbat` module in the entry schema `schema/remit.yaml` +- [ ] T005 Regenerate artefacts: `bash schema/generate.sh` → verify `schema/gen/remit.schema.json` + `schema/gen/remit.ts` include `Orbat`/`Asset`/`Allegiance`; do NOT hand-edit generated files +- [ ] T006 [P] Create `app/js/orbat/orbat.js` skeleton (`// @ts-check`) importing the generated types from `schema/gen/remit.ts` + +**Checkpoint**: schema regenerated, allegiance/asset shapes available as generated types. + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Shared model, persistence, projection plumbing, and panel mount that ALL stories build on. Not a demoable story on its own. + +**⚠️ CRITICAL**: No user-story work begins until this phase is complete. + +- [ ] T007 Implement `emptyOrbat(name)` + `canonical(orbat)` (assets sorted by `id`, canonical JSON, DEC-35) in `app/js/orbat/orbat.js` +- [ ] T008 Implement `validate(asset)` — bounds clamp for `extent_m`/`severity`/`sensitivity`, `start_min ≤ end_min`, position-in-AO, allegiance↔param-group match — in `app/js/orbat/orbat.js` +- [ ] T009 Implement persistence `saveDraft`/`loadDraft` (localStorage key `remit.orbat.M-001`, canonical JSON) in `app/js/orbat/orbat.js` +- [ ] T010 Implement display-only `assetToEntity(asset)` adapter (allegiance-typed Entity, `provenance.kind='actor'`, position; no kernel reference — NF9) in `app/js/orbat/orbat.js` +- [ ] T011 Extend `buildEntities()` to fold authored ORBAT assets into the entity set in `app/js/entities/entities.js` +- [ ] T012 Extend `map.render` to draw an allegiance-coloured asset marker layer (point + faint extent ring + label) in `app/js/views/map.js` +- [ ] T013 Register the ORBAT authoring panel as a config-declared role-tab (home: `sme-int`) in `app/js/shell/roles.js` and scaffold `app/js/shell/orbat-panel.js` with `mount(container, ctx)` +- [ ] T014 Wire the panel + authored assets into the render loop (feed assets to `map.render` and `buildEntities`) in `app/js/main.js` + +**Checkpoint**: an asset added in code renders on the map; the panel mounts. Stories can now begin. + +--- + +## Phase 3: User Story 1 — Add & tune red (hostile) assets (Priority: P1) 🎯 MVP + +**Goal**: A planner adds multiple independent red threat assets and tunes each (label, position, extent, severity); each appears in the red allegiance style on the map. + +**Independent Test**: With an empty red side, add two red assets at different cells, tune one's extent/severity, confirm both render distinctly and only the tuned one changed — without touching green or own force. + +### Tests for User Story 1 + +- [ ] T015 [P] [US1] Unit tests — deterministic `addAsset` (fresh unique id), `tuneAsset` red clamp (`extent_m`, `severity`), per-asset isolation, `canonical` stability — in `test/orbat.test.mjs` +- [ ] T016 [P] [US1] e2e — add two red assets, tune one, assert both visible + isolation + no fabricated adversary motion (honest floor) — in `e2e/orbat.spec.ts` + +### Implementation for User Story 1 + +- [ ] T017 [US1] Implement `addAsset(orbat, {allegiance:'red', position})` with red defaults + fresh id; reject `blue` and out-of-AO positions — in `app/js/orbat/orbat.js` +- [ ] T018 [US1] Implement `tuneAsset(orbat, id, patch)` applying clamped `RedParams` (severity) + `extent_m`/`label` to only the targeted asset — in `app/js/orbat/orbat.js` +- [ ] T019 [US1] Render the **Red (hostile)** roster group, **Add red**, and per-row tuners (label, extent, severity) in `app/js/shell/orbat-panel.js` +- [ ] T020 [US1] Red marker styling (`#ff7b72` family) + extent ring + click-to-pick-hex placement in `app/js/views/map.js` +- [ ] T021 [US1] Inline validation feedback (clamp/reject with message) in `app/js/shell/orbat-panel.js`; capture evidence screenshots → `specs/004-orbat-red-green-assets/evidence/screenshots/` + +**Checkpoint**: red ORBAT is authorable, tunable, and visible — the MVP. + +--- + +## Phase 4: User Story 2 — Add & tune green (neutral) assets (Priority: P1) + +**Goal**: A planner adds multiple independent green assets and tunes each (label, position, extent, sensitivity, protection); each appears in the green allegiance style, distinct from red and own force. + +**Independent Test**: With an empty green side, add two green assets with different sensitivities, tune one, confirm both render in green and only the tuned one changed. + +### Tests for User Story 2 + +- [ ] T022 [P] [US2] Unit tests — green defaults, `GreenParams` clamp (`sensitivity`, `protection`), isolation — in `test/orbat.test.mjs` +- [ ] T023 [P] [US2] e2e — add two green assets, tune one, assert distinct green styling vs red/own-force — in `e2e/orbat.spec.ts` + +### Implementation for User Story 2 + +- [ ] T024 [US2] Extend `addAsset`/`tuneAsset` with green defaults + `GreenParams` (`sensitivity`, `protection`) in `app/js/orbat/orbat.js` +- [ ] T025 [US2] Render the **Green (neutral)** roster group, **Add green**, and per-row tuners (label, extent, sensitivity, protection) in `app/js/shell/orbat-panel.js` +- [ ] T026 [US2] Green marker styling (`#38d39f` family) visually distinct from red and own-force in `app/js/views/map.js` + +**Checkpoint**: both red and green sides are independently authorable and visible. + +--- + +## Phase 5: User Story 3 — Manage the ORBAT roster (Priority: P2) + +**Goal**: Duplicate, remove, and persist assets; the roster and all tuned values survive reload; committing mints an immutable version. + +**Independent Test**: Duplicate an asset, rename the copy, remove a different asset, reload — confirm the roster and every tuned value are exactly as left. + +### Tests for User Story 3 + +- [ ] T027 [P] [US3] Unit tests — `duplicateAsset` (new id, independent copy), `removeAsset` (others unaffected), `commit` immutability + lineage — in `test/orbat.test.mjs` +- [ ] T028 [P] [US3] e2e — duplicate + remove, reload page, assert roster + tuned values restored — in `e2e/orbat.spec.ts` + +### Implementation for User Story 3 + +- [ ] T029 [US3] Implement `duplicateAsset(orbat, id)` (deep-copy under new id) + `removeAsset(orbat, id)` in `app/js/orbat/orbat.js`; wire duplicate/remove controls in `app/js/shell/orbat-panel.js` +- [ ] T030 [US3] Mirror `saveDraft` on every mutating op and `loadDraft` on panel mount in `app/js/shell/orbat-panel.js` +- [ ] T031 [US3] Implement `commit(orbat, objects)` — immutable content-addressed `Orbat` version with `lineage.previous_version` — in `app/js/orbat/orbat.js`; add a **Commit ORBAT** action to the panel + +**Checkpoint**: roster management + persistence across sessions work. + +--- + +## Phase 6: User Story 4 — ORBAT instances across the planning views (Priority: P3) + +**Goal**: A red asset with active time-windows projects as a Sync-Matrix track, synchronised with the shared playhead and selection. + +**Independent Test**: Add a red asset with an active window, scrub the timeline, confirm its track appears on the Sync Matrix and stays aligned with the map; selecting it highlights it in both views. + +### Tests for User Story 4 + +- [ ] T032 [P] [US4] e2e — red asset with `active_windows` shows a Sync-Matrix track aligned with the playhead; selection syncs across views — in `e2e/orbat.spec.ts` + +### Implementation for User Story 4 + +- [ ] T033 [US4] Add an `active_windows` (`TimeWindow[]`) tuner for red assets in `app/js/shell/orbat-panel.js` +- [ ] T034 [US4] Emit a `window`-type aspect from `assetToEntity` (reuse the satellite-pass render path) and add the catalogue row in `app/js/entities/entities.js` so the asset appears as a Sync-Matrix track +- [ ] T035 [US4] Bind shared selection across panel ↔ map ↔ Sync-Matrix in `app/js/main.js` + +**Checkpoint**: all four user stories are independently functional. + +--- + +## Phase 7: Polish & Cross-Cutting Concerns + +- [ ] T036 [P] Record project memory: new ADR (ORBAT red/green authoring scaffolding) in `docs/project_notes/decisions.md`; allegiance palette + localStorage key in `docs/project_notes/key_facts.md`; work-log entry in `docs/project_notes/issues.md` +- [ ] T037 [P] Author the blog post `specs/004-orbat-red-green-assets/blog/post.md` from `docs/blog-post-template.md` (problem/options/strategy/results/screenshots + "at a glance") and add `blog/screenshots/` +- [ ] T038 Run `quickstart.md` end-to-end and collect evidence screenshots into `specs/004-orbat-red-green-assets/evidence/screenshots/` +- [ ] T039 Run `npm run test:unit` + `npm run test:e2e`; ensure `// @ts-check` is clean across the new modules + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: schema first; T001→T002→T003→T004→T005 sequential (same generation), T006 after T005. +- **Foundational (Phase 2)**: depends on Setup; **blocks all user stories**. +- **User Stories (Phase 3–6)**: all depend on Foundational. US1 & US2 are both P1 and largely parallel; US3 builds on having assets to manage; US4 builds on the asset→entity path. +- **Polish (Phase 7)**: after the desired stories are complete. + +### User Story Dependencies + +- **US1 (P1)**: after Foundational — no dependency on other stories (MVP). +- **US2 (P1)**: after Foundational — independent of US1 (shares `addAsset`/panel/map but exercises the green path). +- **US3 (P2)**: after Foundational — easiest to validate once US1/US2 produce assets to duplicate/remove; the model ops are independent. +- **US4 (P3)**: after Foundational — depends on `assetToEntity` (T010); independently testable. + +### Within Each User Story + +- Tests written first and FAIL before implementation. +- Model ops (`orbat.js`) before panel wiring before map/matrix styling. +- Story complete and independently tested before moving on. + +### Parallel Opportunities + +- T006 [P] alongside late Setup. +- Unit-test and e2e-test tasks marked [P] within a story run together (different files: `test/orbat.test.mjs` vs `e2e/orbat.spec.ts`). +- US1 and US2 can be staffed in parallel after Foundational (note: both touch `orbat.js`, `orbat-panel.js`, `map.js` — coordinate or sequence those edits). +- Polish T036/T037 [P] are independent files. + +--- + +## Parallel Example: User Story 1 + +```bash +# Tests first (different files): +Task: "Unit tests for orbat model in test/orbat.test.mjs" # T015 +Task: "e2e author+tune red in e2e/orbat.spec.ts" # T016 +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 only) + +1. Phase 1 Setup → 2. Phase 2 Foundational → 3. Phase 3 US1 (red) → **STOP & validate** the red ORBAT end-to-end → demo via the PR preview. + +### Incremental Delivery + +Setup + Foundational → US1 (red, MVP) → US2 (green) → US3 (roster + persistence) → US4 (Sync-Matrix projection) → Polish. Each story adds value without breaking the previous ones. + +--- + +## Notes + +- [P] = different files, no dependency on incomplete work. +- LinkML is the source of truth (Principle I): asset/allegiance shapes are schema-defined and regenerated (T001–T005), never hand-authored; only display closures/UI stay in `app/js`. +- Honest floor (NF9) + determinism (NF3) are assertable invariants — exercised by T015/T016 and reaffirmed in T021. +- Commit after each task or logical group; capture evidence screenshots during e2e runs. From 96a9f3f862ade8290b61f873ffeef36fbe5a1b5c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 07:18:15 +0000 Subject: [PATCH 4/4] Add blue (own-force) as a third authorable allegiance (004) Per maintainer review: extend the ORBAT feature so blue assets are authorable and tunable alongside red/green, at the same display-only scaffolding level (one ontology, three stances). Blue tuning does not drive kernel routing in v1 (capability-matched allocation deferred); the existing planned own-force (ROVER-1) is reconciled as the canonical blue asset and is protected from removal. Propagated through spec, plan, data-model (BlueParams + canonical_own_force + reconcileOwnForce), contracts, quickstart, tasks (new US3 blue phase; roster/projection renumbered to US4/US5), and the quality checklist. https://claude.ai/code/session_01T3ZsVp7G1wAq6Dsfvh3eob --- .../checklists/requirements.md | 8 +- .../contracts/orbat-store.md | 22 +- .../contracts/orbat-ui.md | 31 +- .../004-orbat-red-green-assets/data-model.md | 39 ++- specs/004-orbat-red-green-assets/plan.md | 60 ++-- .../004-orbat-red-green-assets/quickstart.md | 19 +- specs/004-orbat-red-green-assets/spec.md | 277 ++++++++++-------- specs/004-orbat-red-green-assets/tasks.md | 144 +++++---- 8 files changed, 358 insertions(+), 242 deletions(-) diff --git a/specs/004-orbat-red-green-assets/checklists/requirements.md b/specs/004-orbat-red-green-assets/checklists/requirements.md index 6684dbb..a011384 100644 --- a/specs/004-orbat-red-green-assets/checklists/requirements.md +++ b/specs/004-orbat-red-green-assets/checklists/requirements.md @@ -1,4 +1,4 @@ -# Specification Quality Checklist: ORBAT — add & tune red and green assets +# Specification Quality Checklist: ORBAT — add & tune blue, red, and green assets **Purpose**: Validate specification completeness and quality before proceeding to planning **Created**: 2026-06-12 @@ -31,8 +31,10 @@ ## Notes -- Scope is deliberately bounded to the **red and green** sides of the ORBAT and to **display-only** - authoring scaffolding (DEC-56 horizon split, NF9 honest floor); reactive-adversary and +- Scope covers all three allegiances — **blue, red, and green** — at the **display-only** authoring + scaffolding level (DEC-56 horizon split, NF9 honest floor). Blue is included for *one ontology, + three stances* symmetry but does **not** drive routing (the existing planned own-force is + reconciled, not re-implemented); reactive-adversary, capability-matched allocation, and constraint/objective emission are called out as deferred, not in scope. - The exact per-allegiance parameter field list is left to planning/design (the spec fixes the shape, not field names) — a candidate for `/speckit-clarify` if the maintainer wants it pinned diff --git a/specs/004-orbat-red-green-assets/contracts/orbat-store.md b/specs/004-orbat-red-green-assets/contracts/orbat-store.md index dd0da9b..ba27eb9 100644 --- a/specs/004-orbat-red-green-assets/contracts/orbat-store.md +++ b/specs/004-orbat-red-green-assets/contracts/orbat-store.md @@ -8,17 +8,18 @@ identity stable and rendering reproducible (NF3). ## Types Imported from the generated schema (do not redeclare): -`Orbat`, `Asset`, `RedParams`, `GreenParams`, `TimeWindow`, `Allegiance`, `Protection`. +`Orbat`, `Asset`, `BlueParams`, `RedParams`, `GreenParams`, `TimeWindow`, `Allegiance`, `Protection`. ## Operations | Function | Signature (conceptual) | Guarantees | |---|---|---| | `emptyOrbat(name)` | `(string) → Orbat` | version 1, `assets: []`. | -| `addAsset(orbat, { allegiance, position })` | `(Orbat, seed) → { orbat, id }` | mints a fresh unique `id`; seeds default params for the allegiance; rejects `allegiance='blue'` (out of scope) and out-of-AO `position`. | -| `duplicateAsset(orbat, id)` | `(Orbat, id) → { orbat, id }` | deep-copies the source's params under a **new** `id`; source unchanged (FR-002/006). | +| `addAsset(orbat, { allegiance, position })` | `(Orbat, seed) → { orbat, id }` | mints a fresh unique `id`; seeds default params for the allegiance (`blue`/`red`/`green`); rejects out-of-AO `position`. | +| `duplicateAsset(orbat, id)` | `(Orbat, id) → { orbat, id }` | deep-copies the source's params under a **new** `id` (never copies `canonical_own_force`); source unchanged (FR-002/006). | | `tuneAsset(orbat, id, patch)` | `(Orbat, id, Partial) → Orbat` | applies `patch` to **only** that asset; **clamps/validates** bounds (FR-004); other assets byte-identical. | -| `removeAsset(orbat, id)` | `(Orbat, id) → Orbat` | drops the asset; remaining assets unaffected (FR-006). | +| `removeAsset(orbat, id)` | `(Orbat, id) → Orbat` | drops the asset; remaining assets unaffected (FR-006). The canonical own-force blue asset (`canonical_own_force`) is **protected** — removal is refused (FR-012). | +| `reconcileOwnForce(orbat, self)` | `(Orbat, Entity) → Orbat` | ensures exactly one blue asset mirrors the existing planned own-force (ROVER-1), `canonical_own_force = true`; idempotent. | | `validate(asset)` | `(Asset) → { ok, issues[] }` | bounds + window `start ≤ end` + position-in-AO + allegiance/param-group match. | | `canonical(orbat)` | `(Orbat) → string` | assets sorted by `id`, canonical JSON (DEC-35) — the persistence & identity form. | @@ -35,13 +36,16 @@ Imported from the generated schema (do not redeclare): - **Determinism (NF3)**: `canonical` is a pure function of asset content (sorted by `id`); equal rosters ⇒ equal bytes ⇒ equal content id. No timestamps/insertion-order leak in. -- **Honest floor (NF9)**: the module exposes **no** function that derives adversary behaviour or - mutates a plan; output is consumed display-only. +- **Honest floor (NF9) + display-only**: the module exposes **no** function that derives adversary + behaviour or mutates a plan; blue `availability`/`capabilities` do not feed routing. The only + kernel-wired own-force is the pre-existing planned `self`, which the canonical blue asset mirrors + (reconciled, not re-implemented). Output is consumed display-only. - **Isolation (FR-002/SC-003)**: every op returns a new draft touching only the targeted asset. ## Display adapter `assetToEntity(asset) → Entity` (display-only; the hand-written carve-out) produces the -`buildEntities()`-shaped entity: allegiance-typed, `provenance.kind='actor'`, a `position` for the -map marker, and — for red assets with `active_windows` — a `window`-type aspect for the Sync Matrix -(reusing the satellite-pass render path). Contains no kernel reference. +`buildEntities()`-shaped entity: allegiance-typed, `provenance.kind` = `self` for the canonical +own-force blue asset else `actor`, a `position` for the map marker, and — for any asset with a +time-varying aspect (red `active_windows`, a blue availability window) — a `window`-type aspect for +the Sync Matrix (reusing the satellite-pass render path). Contains no kernel reference. diff --git a/specs/004-orbat-red-green-assets/contracts/orbat-ui.md b/specs/004-orbat-red-green-assets/contracts/orbat-ui.md index 868fb79..d9aac16 100644 --- a/specs/004-orbat-red-green-assets/contracts/orbat-ui.md +++ b/specs/004-orbat-red-green-assets/contracts/orbat-ui.md @@ -8,23 +8,24 @@ entities and threat"). It mounts with the shared context `{ objects, world, play | Affordance | Behaviour | Backing requirement | |---|---|---| -| **Roster** | Two groups, **Red (hostile)** and **Green (neutral)**, listing each asset by label + key params. Empty groups show an explicit "none" state. | FR-001, edge: empty ORBAT | -| **Add red / Add green** | Adds a default asset of that allegiance; placement via the map's click-to-pick-hex (spec 003 F5) or a default AO-centre position. Appears immediately in the roster and on the map. | FR-001/005, US1/US2 | -| **Per-row tuners** | Numeric/range inputs for `label`, `extent_m`, `severity` (red) / `sensitivity` (green), `protection` (green), and `active_windows` (red). Editing re-renders map + Sync-Matrix live. | FR-003, US1/US2 | -| **Duplicate** | One click clones the row under a new id; the copy is independently tunable. | FR-006, US3 | -| **Remove** | Removes the row from roster and all views; others unaffected. | FR-006, US3 | +| **Roster** | Three groups, **Blue (own force)**, **Red (hostile)**, **Green (neutral)**, listing each asset by label + key params. Empty groups show an explicit "none" state. The canonical own-force (ROVER-1) is shown in Blue, marked and protected from removal. | FR-001/012, edge: empty ORBAT | +| **Add blue / Add red / Add green** | Adds a default asset of that allegiance; placement via the map's click-to-pick-hex (spec 003 F5) or a default AO-centre position. Appears immediately in the roster and on the map. | FR-001/005, US1/US2/US3 | +| **Per-row tuners** | Numeric/range/select inputs for `label`, `extent_m`, and the allegiance group: `severity` + `active_windows` (red); `sensitivity` + `protection` (green); `availability` + `capabilities` (blue). Editing re-renders map + Sync-Matrix live. | FR-003, US1/US2/US3 | +| **Duplicate** | One click clones the row under a new id (never the `canonical_own_force` flag); the copy is independently tunable. | FR-006, US4 | +| **Remove** | Removes the row from roster and all views; others unaffected. The canonical own-force asset's remove control is disabled. | FR-006/012, US4 | | **Validation feedback** | Out-of-range tunes and out-of-AO placements are clamped/rejected with an inline message; the asset never enters an invalid state. | FR-004, edges | -| **Selection** | Selecting a row highlights the asset on the map / Sync-Matrix and vice-versa (shared selection). | US4 | +| **Selection** | Selecting a row highlights the asset on the map / Sync-Matrix and vice-versa (shared selection). | US5 | ## Projection contract (display-only) - **Map** (`map.render`): each asset draws an allegiance-coloured point + faint extent ring + - label, visually distinct from own force and the other allegiance (FR-005). Red `#ff7b72`-family, - green `#38d39f`-family (palette in `key_facts.md`). -- **Sync Matrix** (`sync-matrix`): a red asset with `active_windows` contributes a `window`-render - track via a catalogue row; it stays aligned with the shared playhead and selection (FR-010/US4). -- **Honest floor**: nothing the panel shows implies adversary motion or assessment beyond authored - params (FR-008/NF9). + label, visually distinct across the three allegiances (FR-005). Blue own-force family, red + `#ff7b72`-family, green `#38d39f`-family (palette in `key_facts.md`). +- **Sync Matrix** (`sync-matrix`): any asset with a time-varying aspect (red `active_windows`, a blue + availability window) contributes a `window`-render track via a catalogue row; it stays aligned with + the shared playhead and selection (FR-010/US5). +- **Honest floor + display-only**: nothing the panel shows implies adversary motion/assessment, and + no tune (including blue availability/capability) changes the route or plan (FR-008/NF9). ## Persistence contract @@ -34,6 +35,8 @@ entities and threat"). It mounts with the shared context `{ objects, world, play ## Non-goals (deferred, asserted by tests where cheap) -- No routing/kernel influence from any asset (NF9/NF3). -- No blue/own-force authoring here (existing entity). +- No routing/kernel influence from any asset, including blue tuning (NF9/NF3) — the plan/route is + unchanged by authoring. +- No capability-matched allocation of the blue force-pool (DEC-60 capability — H2): blue is + display-only scaffolding; the existing planned own-force is reconciled, not re-implemented. - No live ROE constraint / collateral objective emission (DEC-60 J3 capability — H2/H3). diff --git a/specs/004-orbat-red-green-assets/data-model.md b/specs/004-orbat-red-green-assets/data-model.md index 5c83462..602e06a 100644 --- a/specs/004-orbat-red-green-assets/data-model.md +++ b/specs/004-orbat-red-green-assets/data-model.md @@ -59,8 +59,24 @@ classes: label: { description: human label; need not be unique } position: { range: Waypoint, inlined: true, description: AO location (H3 cell / lat-lon) } extent_m: { range: float, description: reach/footprint radius in metres } + blue: { range: BlueParams, inlined: true, description: present iff allegiance = blue } red: { range: RedParams, inlined: true, description: present iff allegiance = red } green: { range: GreenParams, inlined: true, description: present iff allegiance = green } + canonical_own_force: + range: boolean + description: >- + true on the single blue asset reconciled from the existing planned own-force (ROVER-1); + it drives the plan via the existing machinery and is protected from removal. + + BlueParams: + description: >- + Own-force pool member (capability-matched ALLOCATION deferred to H2; display-only in v1 — does + not drive routing). The capability vocabulary is the seam a future Scheme matches to a + requirement's activity needs (DEC-59/60). + attributes: + availability: { range: string, description: '"available | down" (own State mirror, DEC-52)' } + capabilities: { range: string, multivalued: true, + description: capability tags a future Scheme matches to activity needs (stub) } RedParams: description: Hostile threat picture (threat SOURCE only in v1; reactive behaviour deferred, DEC-51). @@ -95,20 +111,25 @@ enums: | Entity | Key fields | Relationships | Notes | |---|---|---|---| | **Orbat** | `id`, `version`, `assets[]`, `lineage` | contains `Asset[]` | versioned/immutable when committed; working draft in localStorage | -| **Asset** | `id`, `allegiance`, `label`, `position`, `extent_m` | `red` xor `green` params | first-class Entity (DEC-52); display-only | +| **Asset** | `id`, `allegiance`, `label`, `position`, `extent_m`, `canonical_own_force?` | one of `blue`/`red`/`green` params | first-class Entity (DEC-52); display-only | +| **BlueParams** | `availability`, `capabilities[]` | — | force-pool member; allocation deferred (H2); does not drive routing (NF9) | | **RedParams** | `severity`, `active_windows[]` | `TimeWindow[]` | threat source only (NF9) | | **GreenParams** | `sensitivity`, `protection` | `Protection` enum | inert in v1 (DEC-60 J3) | ## Validation rules (from the spec) -- **FR-001/003**: an Asset MUST have `allegiance ∈ {red, green}` (blue is out of scope here), - a `position` inside the AO, and the parameter group matching its allegiance. +- **FR-001/003**: an Asset MUST have `allegiance ∈ {blue, red, green}`, a `position` inside the AO, + and the parameter group matching its allegiance. - **FR-002**: `id` is unique per instance and never reused; duplicating mints a fresh `id`. - **FR-004**: `extent_m`, `severity`, `sensitivity` are clamped to their declared bounds; an `active_window` MUST satisfy `start_min ≤ end_min` (rejected/clamped with feedback otherwise). +- **FR-012 — own-force reconciliation**: exactly one blue asset may carry `canonical_own_force = true` + (the existing planned ROVER-1, surfaced not duplicated); it is protected from removal so the plan + stays valid. - **Edge — out-of-bounds position**: rejected or clamped to the AO with feedback (never silently lost). - **Edge — duplicate labels**: permitted; identity is `id`, not `label`. -- **FR-008/NF9**: no field drives kernel behaviour; all are display inputs only. +- **FR-008/NF9**: no field drives kernel behaviour — including blue `availability`/`capabilities`, + which do **not** alter routing in v1; all are display inputs only. ## Lifecycle / state @@ -125,6 +146,10 @@ enums: ## App adapter (display-only, hand-written — carve-out) `app/js/orbat/orbat.js` maps each committed/draft `Asset` → an `Entity` for `buildEntities()`: -`{ id, label, allegiance, provenance:{kind:'actor'}, aspects }`. Red assets with `active_windows` -expose a `window`-type aspect (reusing the satellite-pass render path) so they appear as Sync-Matrix -tracks; position feeds the map marker. No `at()` closure references the kernel or alters a plan. +`{ id, label, allegiance, provenance:{kind}, aspects }` (`kind` = `self` for the canonical own-force +blue asset, else `actor`). Red assets with `active_windows` — and any asset given a time-varying +aspect (e.g. a blue asset's availability window) — expose a `window`-type aspect (reusing the +satellite-pass render path) so they appear as Sync-Matrix tracks; position feeds the map marker. The +canonical own-force blue asset reuses the existing planned `self` entity (reconciled, not duplicated) +and is the *only* asset already wired to the kernel via the pre-existing machinery; no `assetToEntity` +closure references the kernel or alters a plan. diff --git a/specs/004-orbat-red-green-assets/plan.md b/specs/004-orbat-red-green-assets/plan.md index da7428b..412da0d 100644 --- a/specs/004-orbat-red-green-assets/plan.md +++ b/specs/004-orbat-red-green-assets/plan.md @@ -1,4 +1,4 @@ -# Implementation Plan: ORBAT — add & tune red and green assets +# Implementation Plan: ORBAT — add & tune blue, red, and green assets **Branch**: `claude/fervent-feynman-5g4dpb` (spec dir `004-orbat-red-green-assets`) | **Date**: 2026-06-12 | **Spec**: [spec.md](./spec.md) @@ -6,15 +6,18 @@ ## Summary -Bring the **red** and **green** sides of the ORBAT (DEC-60) forward into the app as -**display-only authoring scaffolding** (DEC-56 horizon split, NF9 honest floor). A planner can add, -duplicate, tune, and remove **multiple independent instances** of hostile (red) and neutral (green) -assets; each is a first-class **Entity** (DEC-52) carrying an **allegiance**, projected onto the map -(allegiance-coloured markers) and onto the Sync Matrix (a track when it carries a time-varying -aspect). The serialisable shape — the `Allegiance` enum, the asset parameters, and the `Orbat` -container — is added to the **LinkML schema** (Principle I, non-negotiable) and regenerated; the -app imports the generated types and reuses the existing entity/projection plumbing -(`buildEntities` → map + `sync-matrix`). The authored ORBAT persists across sessions. +Bring all three sides of the ORBAT (DEC-60) — **blue** (own force), **red** (hostile), **green** +(neutral) — forward into the app as **display-only authoring scaffolding** (DEC-56 horizon split, +NF9 honest floor). A planner can add, duplicate, tune, and remove **multiple independent instances** +of each allegiance; each is a first-class **Entity** (DEC-52) carrying an **allegiance**, projected +onto the map (allegiance-coloured markers) and onto the Sync Matrix (a track when it carries a +time-varying aspect). Authoring/tuning never changes kernel routing in v1: the existing planned +own-force (ROVER-1) is reconciled as the canonical blue asset and keeps driving the plan unchanged; +blue pool assets seed the future Scheme allocation (the deferred capability). The serialisable shape +— the `Allegiance` enum, the per-allegiance asset parameters, and the `Orbat` container — is added to +the **LinkML schema** (Principle I, non-negotiable) and regenerated; the app imports the generated +types and reuses the existing entity/projection plumbing (`buildEntities` → map + `sync-matrix`). The +authored ORBAT persists across sessions. ## Technical Context @@ -44,8 +47,9 @@ frame); legible roster at ≥ 10 instances per allegiance (SC-002). identical plans/projections; honest floor (NF9) — no fabricated adversary behaviour; additive to the data model (allegiance attribute + asset params only, DEC-60); no new build machinery. -**Scale/Scope**: one ORBAT per scenario; tens of asset instances; two new allegiances authorable -(red, green) — blue/own-force is the existing entity, out of scope. +**Scale/Scope**: one ORBAT per scenario; tens of asset instances; **three** allegiances authorable +(blue, red, green) at the display-only level — the existing planned own-force is reconciled as the +canonical blue asset and is unaffected by pool authoring. ## Constitution Check @@ -56,7 +60,7 @@ data model (allegiance attribute + asset params only, DEC-60); no new build mach | **I. LinkML is the data-model source of truth** (NON-NEGOTIABLE) | ✅ PASS (gated) | The persisted ORBAT/asset shape is serialisable object-core, so it is **schema-defined and regenerated**, never hand-authored: add `Allegiance` enum + `Asset`/`Orbat` to the schema modules, run `schema/generate.sh`, import the generated TS in `app/js`. **Display-only render closures** (aspect `at()` functions, the catalogue rows) stay hand-written — the documented carve-out for behaviour + UI-only shapes. | | **II. No-build static app** | ✅ PASS | Stays in `app/js` ES modules + `// @ts-check`; **no new build machinery**. (Vite already adopted under ADR-0014 — a recorded deviation; this feature adds nothing new.) | | **III. Spec-driven workflow + blog** | ✅ PASS | Following spec → plan → tasks → implement; blog post sketched in Phase 2 and authored at implement. | -| **IV. Durable project memory** | ✅ PASS | New ADR for "ORBAT red/green authoring scaffolding" in `decisions.md`; `issues.md` work-log entry; `key_facts.md` for the allegiance palette/persistence key — recorded at implement. | +| **IV. Durable project memory** | ✅ PASS | New ADR for "ORBAT blue/red/green authoring scaffolding" in `decisions.md`; `issues.md` work-log entry; `key_facts.md` for the allegiance palette/persistence key — recorded at implement. | | **V. Repo canonical + immutability** | ✅ PASS | Committed ORBAT versions are immutable with lineage (the working draft is the editable surface; commit mints a new version). | **No violations** → Complexity Tracking left empty. @@ -88,7 +92,7 @@ schema/ ├── common.yaml # + Allegiance enum (blue|red|green) ├── entities.yaml # + allegiance attribute on Entity ├── force.yaml # (reference; Profile/State unchanged) -├── orbat.yaml # NEW module: Orbat container + Asset (+ RedParams/GreenParams) +├── orbat.yaml # NEW module: Orbat container + Asset (+ Blue/Red/GreenParams) ├── remit.yaml # entry schema: import the new orbat module └── gen/ # REGENERATED (remit.schema.json, remit.ts) — do not hand-edit @@ -122,20 +126,22 @@ than adding a parallel rendering path. Planning only (authored at `/speckit-implement` into `specs/004-orbat-red-green-assets/blog/post.md`, from `docs/blog-post-template.md`): -- **At a glance**: **"The ORBAT grows a red and green side — drop in as many threats and protected - places as a scenario needs, and tune each one."** Featured screenshot: the map with several - red threat rings and green no-strike markers placed, plus the roster panel open. -- **The problem**: the entity catalogue was fixed in config and seeded only own force; a planner - could not express the adversary/neutral picture of a scenario. +- **At a glance**: **"The ORBAT grows all three sides — drop in as many own-force, threat, and + protected-place assets as a scenario needs, and tune each one."** Featured screenshot: the map with + blue own-force, red threat rings, and green no-strike markers placed, plus the roster panel open. +- **The problem**: the entity catalogue was fixed in config and seeded only a single own force; a + planner could not express the own-force pool, the adversary, or the neutral picture of a scenario. - **Options**: (a) bespoke per-allegiance asset objects vs (b) reuse the allegiance-typed Entity + config catalogue; (c) free-form map drawing vs (d) a tunable parameter roster; (e) where the data - lives — hand-written app type vs LinkML-generated. + lives — hand-written app type vs LinkML-generated; (f) blue display-only vs wired into the planner. - **The strategy**: reuse Entity + allegiance (DEC-60), schema-define the serialisable shape and - regenerate (Principle I), keep it display-only under the DEC-56 guard / NF9 honest floor, project - through the existing map + Sync-Matrix. -- **The results**: add/duplicate/tune/remove multiple instances; allegiance-coloured map markers; - Sync-Matrix tracks for time-windowed assets; persistence across reload; determinism preserved. + regenerate (Principle I), keep all three allegiances display-only under the DEC-56 guard / NF9 + honest floor (blue does not drive routing; own-force reconciled), project through the existing + map + Sync-Matrix. +- **The results**: add/duplicate/tune/remove multiple instances of each allegiance; allegiance-coloured + map markers; Sync-Matrix tracks for time-windowed assets; persistence across reload; determinism + + unchanged-plan preserved. - **Screenshots to capture** (Playwright → `evidence/screenshots/*.png`): empty ORBAT → one red asset - placed → multiple red+green assets with extent rings → a tuner adjusting threat extent (before/after) - → a time-windowed red asset's track on the Sync Matrix → roster after duplicate/remove → scenario - restored after reload. + placed → multiple blue+red+green assets with extent rings → a tuner adjusting threat extent + (before/after) → a blue tune leaving the route unchanged → a time-windowed asset's track on the + Sync Matrix → roster after duplicate/remove → scenario restored after reload. diff --git a/specs/004-orbat-red-green-assets/quickstart.md b/specs/004-orbat-red-green-assets/quickstart.md index d2048f6..340908d 100644 --- a/specs/004-orbat-red-green-assets/quickstart.md +++ b/specs/004-orbat-red-green-assets/quickstart.md @@ -1,4 +1,4 @@ -# Quickstart — validate ORBAT red/green assets +# Quickstart — validate ORBAT blue/red/green assets A runnable validation guide proving the feature end-to-end. Details live in [data-model.md](./data-model.md) and [contracts/](./contracts/); this is the run/verify path. @@ -32,12 +32,21 @@ npm run dev # Vite dev server; open the ORBAT (SME-Intel) tab marker renders, distinct from red and own force. 2. **Add green** again with different params → two independent green rows; tuning one leaves the other unchanged. -### US3 — Manage the roster (P2) +### US3 — Add & tune blue (own-force) assets (P2) +1. Open the ORBAT → the existing own-force (ROVER-1) appears in the **Blue** group, marked canonical + and with its remove control disabled. +2. **Add blue**, set location + **availability** + a **capability** stub → a blue marker renders, + distinct from red and green. +3. Tune the blue asset's capability/availability → the roster/depiction updates **and the selected + route/plan is unchanged** (display-only proof — no kernel coupling). + +### US4 — Manage the roster (P2) 1. **Duplicate** a red asset → an independent copy (new id) carrying the source's params; tune it separately. -2. **Remove** a green asset → it disappears from roster and map; the rest are unaffected. +2. **Remove** a green asset → it disappears from roster and map; the rest are unaffected. Confirm the + canonical own-force blue asset cannot be removed. 3. **Reload the page** → the full roster and every tuned value are restored exactly (localStorage draft). -### US4 — Cross-view projection (P3) +### US5 — Cross-view projection (P3) 1. Give a red asset an **active window** (e.g. H+30..H+60) → a track appears on the Sync Matrix. 2. Scrub the shared playhead → the track and the map marker stay aligned; selecting the row highlights it in both views. @@ -56,4 +65,4 @@ e2e run (empty → one red → many red+green → tune before/after → Sync-Mat - New asset placed and visible in **< 30 s** (SC-001); ≥ 10 instances per allegiance stay legible/tunable (SC-002). - Tuning affects **only** the targeted asset (SC-003); reload restores **100%** of instances/values (SC-004). -- **Zero** fabricated adversary behaviour (SC-005); re-planning an unchanged ORBAT is **identical** (SC-006). +- **Zero** fabricated adversary behaviour and the route/plan **unchanged** by any blue/red/green tune (SC-005); re-planning an unchanged ORBAT is **identical** (SC-006). diff --git a/specs/004-orbat-red-green-assets/spec.md b/specs/004-orbat-red-green-assets/spec.md index e7006df..d83239b 100644 --- a/specs/004-orbat-red-green-assets/spec.md +++ b/specs/004-orbat-red-green-assets/spec.md @@ -1,4 +1,4 @@ -# Feature Specification: ORBAT — add & tune red and green assets +# Feature Specification: ORBAT — add & tune blue, red, and green assets **Feature Branch**: `claude/fervent-feynman-5g4dpb` (cloud session; spec dir `004-orbat-red-green-assets`) @@ -6,23 +6,26 @@ **Status**: Draft -**Input**: User description: "Add red and green assets in the ORBAT (order of battle). Provide the ability to add and tune multiple instances of each asset." +**Input**: User description: "Add red and green assets in the ORBAT (order of battle). Provide the ability to add and tune multiple instances of each asset." — extended in review to include **blue (own-force)** assets as a third authorable allegiance at the same display-only level, for symmetry with *one ontology, three stances* (DEC-60). ## Context — why The ORBAT (Order of Battle) is the **authoring root** of the command-post stretch (DEC-60): it -catalogues the **participants and potential participants** of an operation before requirement -and plan authoring. Entities on the ORBAT are typed by **allegiance** — **blue** (own force), -**red** (hostile), **green** (neutral / host-nation / civilian) — under *one ontology, three -stances*: the kernel **plans-for** blue, **avoids-assesses** red, and **respects** green. +catalogues the **participants and potential participants** of an operation before requirement and +plan authoring. Entities on the ORBAT are typed by **allegiance** — **blue** (own force), **red** +(hostile), **green** (neutral / host-nation / civilian) — under *one ontology, three stances*: the +kernel **plans-for** blue, **avoids-assesses** red, and **respects** green. DEC-60 is explicit that +the ORBAT populates **both** the Entity catalogue **and the blue force-pool a Scheme allocates**. Today the entity catalogue is fixed in config and seeds a single own-force scenario. This feature -adds the ability for a planner to **populate the red and green sides of the ORBAT themselves** — -placing as many hostile and neutral assets as the scenario needs, and **tuning each instance's +lets a planner **author all three sides of the ORBAT themselves** — placing as many own-force (blue), +hostile (red), and neutral (green) assets as the scenario needs, and **tuning each instance's parameters independently**. Per the horizon-split guard (DEC-56): v1 ships the **authoring and -display scaffolding** (instances are first-class entities, projected across the views); the -*reactive* adversary and capability-matched allocation remain designed-for, not claimed (NF9 — -the honest floor: no fabricated adversary reasoning until that discipline exists, DEC-51). +display scaffolding** — instances are first-class entities projected across the views. The +*reactive* adversary, and the **capability-matched allocation** of blue assets, remain designed-for, +not claimed: authoring/tuning a blue pool asset does **not** change kernel routing in v1 (NF9 — the +honest floor; NF3 — determinism). The existing planned own-force (ROVER-1) is reconciled as the +canonical blue asset and continues to drive the plan through the existing machinery, unchanged. ## User Scenarios & Testing *(mandatory)* @@ -31,31 +34,29 @@ the honest floor: no fabricated adversary reasoning until that discipline exists A planner opens the ORBAT and adds one or more **red** assets — a threat such as a patrol, a sensor/observation post, or a weapon position. Each asset is placed in the area of operations and its parameters are tuned independently: its label, its location, the extent/reach of the threat it -represents, and its severity. The planner can add several distinct red assets (e.g. two patrols -and one sensor), each tuned to a different position and reach, and see every one of them reflected -on the map. +represents, and its severity. The planner can add several distinct red assets, each tuned to a +different position and reach, and see every one of them reflected on the map. -**Why this priority**: Red assets are the primary thing a mission plans *around*. Without the -ability to place and shape the threat picture, the planner cannot express the adversary side of a -scenario at all. This story alone is a viable slice: it delivers an authorable, visible red ORBAT. +**Why this priority**: Red assets are the primary thing a mission plans *around*. Without the ability +to place and shape the threat picture, the planner cannot express the adversary side of a scenario at +all. This story alone is a viable slice: an authorable, visible red ORBAT. **Independent Test**: Open the ORBAT with an empty red side, add two red assets with different -positions and threat extents, tune one of them, and confirm both appear distinctly on the map with -the tuned values reflected — without touching the green side or own force. +positions and threat extents, tune one, and confirm both appear distinctly on the map with the tuned +values — without touching the other allegiances. **Acceptance Scenarios**: -1. **Given** an ORBAT with no red assets, **When** the planner adds a red asset and sets its - location and threat extent, **Then** a hostile-typed entity appears on the ORBAT roster and is - rendered in the red allegiance style on the map at that location. -2. **Given** one red asset already on the ORBAT, **When** the planner adds a second red asset with a - different location, **Then** both instances persist as independent entries with independent - parameters (changing one does not change the other). -3. **Given** a red asset on the ORBAT, **When** the planner tunes its threat extent or severity, - **Then** the change is reflected in its map depiction without affecting any other asset. -4. **Given** a red asset on the ORBAT, **When** the scenario is viewed, **Then** the system shows - the asset's threat as **display-only** context and performs **no** fabricated adversary - movement or reactive reasoning (honest floor). +1. **Given** an ORBAT with no red assets, **When** the planner adds a red asset and sets its location + and threat extent, **Then** a hostile-typed entity appears on the roster and renders in the red + allegiance style on the map at that location. +2. **Given** one red asset already present, **When** the planner adds a second with a different + location, **Then** both persist as independent entries with independent parameters. +3. **Given** a red asset, **When** the planner tunes its threat extent or severity, **Then** the + change is reflected in its depiction without affecting any other asset. +4. **Given** a red asset, **When** the scenario is viewed, **Then** the system shows the threat as + **display-only** context and performs **no** fabricated adversary movement or reactive reasoning + (honest floor). --- @@ -63,65 +64,96 @@ the tuned values reflected — without touching the green side or own force. A planner adds one or more **green** assets — a neutral or protected entity such as a host-nation village, a civilian vessel, a hospital, or a no-strike area. Each green asset is placed and tuned -independently: its label, its location/extent, and its sensitivity (how much it must be respected / -the collateral weight). The planner can maintain several green assets at once, each with its own -parameters. +independently: its label, its location/extent, its sensitivity (collateral weight), and the nature of +the protection. The planner can maintain several green assets at once, each with its own parameters. **Why this priority**: Green assets carry the rules-of-engagement and collateral picture — the constraints a defensible plan must respect. They are co-equal with red in expressing a scenario, so -they share P1. This story is independently viable: an authorable, visible green ORBAT. +they share P1. Independently viable: an authorable, visible green ORBAT. **Independent Test**: Open the ORBAT with an empty green side, add two green assets with different -locations and sensitivities, tune one, and confirm both render in the green allegiance style with -the tuned values — independent of red and own force. +locations and sensitivities, tune one, and confirm both render in the green allegiance style with the +tuned values — independent of the other allegiances. **Acceptance Scenarios**: 1. **Given** an ORBAT with no green assets, **When** the planner adds a green asset and sets its - location and sensitivity, **Then** a neutral-typed entity appears on the roster and renders in - the green allegiance style on the map. + location and sensitivity, **Then** a neutral-typed entity appears on the roster and renders in the + green allegiance style on the map. 2. **Given** one green asset already present, **When** the planner adds a second with different parameters, **Then** both persist as independent, separately-tunable entries. -3. **Given** a green asset, **When** the planner tunes its sensitivity or extent, **Then** the - change is reflected in its depiction without affecting any other asset. +3. **Given** a green asset, **When** the planner tunes its sensitivity or extent, **Then** the change + is reflected in its depiction without affecting any other asset. + +--- + +### User Story 3 - Add and tune own-force (blue) asset instances (Priority: P2) + +A planner adds one or more **blue** assets to the own-force pool — additional platforms/units beyond +the single planned own-force. Each blue asset is placed and tuned independently: its label, its +location, its extent, and an availability + capability stub (the vocabulary a future Scheme matches +to a requirement's activity needs). The depiction is **display-only**: blue pool assets populate the +force-pool and appear on the map, but tuning them does **not** alter kernel routing in v1 (the +capability-matched allocation is the deferred capability). The existing planned own-force (ROVER-1) +appears as the canonical blue asset and continues to drive the plan unchanged. + +**Why this priority**: Authoring the blue force-pool completes the *one ontology, three stances* +symmetry and seeds the future Scheme allocation, but the essential v1 value (the threat + neutral +picture) is already delivered by P1, and own force is already planned by the existing machinery — +so this is completeness, at P2. It reuses the same add/tune pipeline as red/green. + +**Independent Test**: Add two blue pool assets with different availability/capability stubs, tune +one, and confirm both render in the blue allegiance style alongside the existing own force — and that +the selected plan/route is **unchanged** by the tuning (display-only proof). + +**Acceptance Scenarios**: + +1. **Given** the ORBAT, **When** the planner adds a blue asset and sets its location and + availability, **Then** an own-force-typed entity appears on the roster and renders in the blue + allegiance style, distinct from red and green. +2. **Given** a blue asset, **When** the planner tunes its capability stub or availability, **Then** + the change is reflected in the roster/depiction and the **plan and route are unchanged** (no + kernel coupling in v1). +3. **Given** the existing planned own-force, **When** the ORBAT is opened, **Then** ROVER-1 is shown + as the canonical blue asset (reconciled, not duplicated). --- -### User Story 3 - Manage the ORBAT roster (Priority: P2) +### User Story 4 - Manage the ORBAT roster (Priority: P2) -The planner manages the full set of red and green instances as a roster: rename an asset, duplicate -an existing asset as a starting point for a similar one, and remove an asset that is no longer -relevant. The roster — and every instance's tuned parameters — persists so the scenario can be +The planner manages the full set of blue, red, and green instances as a roster: rename an asset, +duplicate an existing asset as a starting point for a similar one, and remove an asset that is no +longer relevant. The roster — and every instance's tuned parameters — persists so the scenario can be revisited across sessions. -**Why this priority**: Roster management makes authoring *practical* at the scale of a real -scenario (many similar threats), but the core value (add + tune + see) is already delivered by P1. -It builds on P1/P2 rather than standing alone. +**Why this priority**: Roster management makes authoring practical at the scale of a real scenario, +but the core value (add + tune + see) is already delivered by P1/US3. It builds on the other stories. -**Independent Test**: With several red and green assets present, duplicate one, rename the copy, -remove a different asset, reload the scenario, and confirm the roster and all tuned values are -exactly as left. +**Independent Test**: With several assets of each allegiance present, duplicate one, rename the copy, +remove a different asset, reload the scenario, and confirm the roster and all tuned values are exactly +as left. **Acceptance Scenarios**: -1. **Given** several assets on the ORBAT, **When** the planner duplicates one, **Then** an - independent copy is created carrying the source's parameters, which can then be tuned separately. +1. **Given** several assets on the ORBAT, **When** the planner duplicates one, **Then** an independent + copy is created carrying the source's parameters, which can then be tuned separately. 2. **Given** an asset on the ORBAT, **When** the planner removes it, **Then** it disappears from the - roster and from all views, and the remaining assets are unaffected. -3. **Given** an authored ORBAT, **When** the scenario is reloaded, **Then** every red and green - instance and its tuned parameters are restored exactly. + roster and all views, and the remaining assets are unaffected. (The canonical planned own-force is + protected from removal, or removal is handled predictably.) +3. **Given** an authored ORBAT, **When** the scenario is reloaded, **Then** every instance and its + tuned parameters are restored exactly. --- -### User Story 4 - ORBAT instances appear across the planning views (Priority: P3) +### User Story 5 - ORBAT instances appear across the planning views (Priority: P3) -Every red and green instance is a first-class **entity** (DEC-52), so it appears not only on the map -but wherever the views project entities — including the Sync Matrix when an instance carries a -time-varying aspect (e.g. a patrol that is only active in certain windows). The depiction is -display-only and synchronised with the shared playhead and selection. +Every instance is a first-class **entity** (DEC-52), so it appears not only on the map but wherever +the views project entities — including the Sync Matrix when an instance carries a time-varying aspect +(e.g. a patrol active only in certain windows, or a blue asset available only for part of the +mission). The depiction is display-only and synchronised with the shared playhead and selection. **Why this priority**: Cross-view projection is the pay-off of modelling assets as entities, but the -map depiction in P1/P2 already delivers the essential authoring feedback. This is enrichment. +map depiction in P1/US3 already delivers the essential authoring feedback. This is enrichment. **Independent Test**: Add a red asset with an active-time window, scrub the shared timeline, and confirm its track appears on the Sync Matrix and stays aligned with the map depiction. @@ -129,7 +161,7 @@ confirm its track appears on the Sync Matrix and stays aligned with the map depi **Acceptance Scenarios**: 1. **Given** an asset with a time-varying aspect, **When** the planner scrubs the playhead, **Then** - the asset's track on the Sync Matrix and its map depiction update together. + the asset's Sync-Matrix track and its map depiction update together. 2. **Given** any ORBAT asset, **When** the planner selects it in one view, **Then** the selection is reflected in the other views. @@ -137,17 +169,19 @@ confirm its track appears on the Sync Matrix and stays aligned with the map depi ### Edge Cases -- **Empty ORBAT.** A scenario with no red and/or no green assets is valid; views simply show none of - that allegiance, and planning proceeds. -- **Many instances.** The roster remains legible and usable with a realistic count of similar assets - (e.g. a dozen patrols); instances stay individually identifiable and tunable. +- **Empty allegiance.** A scenario with no assets of a given allegiance is valid; views simply show + none of that colour, and planning proceeds. +- **Many instances.** The roster stays legible and usable with a realistic count of similar assets + across all three allegiances; instances stay individually identifiable and tunable. - **Out-of-bounds placement.** An asset placed outside the area of operations is rejected or clamped with clear feedback rather than silently lost. - **Out-of-range tuning.** A parameter tuned beyond its allowed bounds is rejected or clamped with feedback; the asset is never left in an invalid state. -- **Duplicate labels.** Two assets may share a human label; the system still tracks them as distinct - instances (identity is not the label). -- **Removal of a referenced asset.** Removing an asset that another part of the scenario refers to is +- **Duplicate labels.** Two assets may share a human label; identity is tracked separately (not by + the label). +- **The canonical own-force.** The existing planned own-force is reconciled as a blue asset, not + duplicated; removing or invalidating it is prevented or handled predictably so the plan stays valid. +- **Removal of a referenced asset.** Removing an asset another part of the scenario refers to is handled predictably (blocked with explanation, or cascaded with notice) rather than leaving a dangling reference. @@ -155,85 +189,94 @@ confirm its track appears on the Sync Matrix and stays aligned with the map depi ### Functional Requirements -- **FR-001**: The ORBAT MUST let a planner add a **red (hostile)** asset and a **green (neutral)** - asset, each typed by allegiance, as a first-class entity in the scenario's entity catalogue. +- **FR-001**: The ORBAT MUST let a planner add a **blue (own-force)**, **red (hostile)**, or **green + (neutral)** asset, each typed by allegiance, as a first-class entity in the scenario's entity + catalogue. - **FR-002**: The system MUST support **multiple independent instances** of each allegiance; adding, editing, or removing one instance MUST NOT affect any other. -- **FR-003**: Each instance MUST expose a set of **tunable parameters** appropriate to its - allegiance, including at minimum a human label, a location within the area of operations, an - extent/reach, and a severity/sensitivity. Tuning a parameter MUST update only that instance. +- **FR-003**: Each instance MUST expose **tunable parameters** appropriate to its allegiance, + including at minimum a human label, a location within the area of operations, an extent/reach, and + an allegiance-specific group (red: severity, optional active windows; green: sensitivity, + protection; blue: availability + a capability stub). Tuning a parameter MUST update only that + instance. - **FR-004**: The system MUST **validate** tuned parameters against their allowed bounds and reject or clamp out-of-range values with clear feedback, never leaving an instance in an invalid state. -- **FR-005**: Each instance MUST be rendered in its **allegiance style** (red for hostile, green for - neutral) on the map at its authored location, visually distinct from own force and from the other - allegiance. -- **FR-006**: The planner MUST be able to **duplicate** an existing instance (creating an independent - copy carrying the source's parameters) and **remove** an instance from the roster. +- **FR-005**: Each instance MUST be rendered in its **allegiance style** (blue / red / green) on the + map at its authored location, visually distinct across the three allegiances. +- **FR-006**: The planner MUST be able to **duplicate** an existing instance (an independent copy + carrying the source's parameters) and **remove** an instance from the roster. - **FR-007**: The ORBAT roster and every instance's tuned parameters MUST **persist** so the scenario can be revisited across sessions with all values restored. -- **FR-008**: Red and green instances MUST be **display-only context** in this release: the system - MUST NOT generate adversary movement, reactive behaviour, or any fabricated assessment beyond the - parameters the planner authored (NF9 honest floor; reactive adversary deferred per DEC-51/56). +- **FR-008**: All instances MUST be **display-only context** in this release: the system MUST NOT + generate adversary behaviour, **and MUST NOT change kernel routing or the generated plans in + response to authored blue/red/green parameters** (NF9 honest floor; reactive adversary and + capability-matched allocation deferred per DEC-51/56). - **FR-009**: The feature MUST NOT change the determinism of planning (NF3): authored ORBAT parameters are inputs; identical inputs MUST continue to yield identical plans and projections. - **FR-010**: Instances that carry a time-varying aspect MUST project onto the Sync Matrix as a - track, synchronised with the shared playhead and selection alongside the existing entities. + track, synchronised with the shared playhead and selection. - **FR-011**: The feature MUST reuse the existing **entity / allegiance** vocabulary and the - config-declared catalogue shape rather than introducing a parallel asset model, so the ORBAT - authoring path is *additive* to the data model (allegiance attribute + asset parameters only). + config-declared catalogue shape, so the ORBAT authoring path is *additive* to the data model + (allegiance attribute + asset parameters only). +- **FR-012**: The existing planned own-force MUST be **reconciled** as the canonical blue asset + (shown, not duplicated) and MUST continue to drive the plan through the existing machinery, + unaffected by blue-pool authoring. ### Key Entities *(include if feature involves data)* -- **ORBAT**: The roster of participants and potential participants for a scenario — the authoring - root. Holds the collection of allegiance-typed asset instances and is the entry point that seeds - downstream planning. -- **Asset instance**: A first-class **entity** (DEC-52) with identity, an **allegiance** (red / - green here; blue is own force), a human label, a location/extent in the area of operations, and a - set of allegiance-appropriate tunable parameters. Projected display-only across the views. -- **Red (hostile) parameters**: The threat picture an instance represents — location, threat - extent/reach, severity, and optionally the time window(s) in which it is active. (Threat *source* - only in v1; reactive behaviour deferred.) -- **Green (neutral) parameters**: The respect/collateral picture — location/extent, sensitivity - (collateral weight), and the nature of the protection it represents (e.g. keep-out vs. - minimise-effect). (Constraint/objective *emission* is designed-for, deferred; display-only in v1.) -- **Allegiance**: The blue / red / green typing on an entity that selects the kernel stance - (plan-for / avoid-assess / respect) and the rendering style. +- **ORBAT**: The roster of participants and potential participants — the authoring root. Holds the + collection of allegiance-typed asset instances and is the entry point that seeds downstream + planning (and the blue force-pool a future Scheme allocates). +- **Asset instance**: A first-class **entity** (DEC-52) with identity, an **allegiance** (blue / red + / green), a human label, a location/extent in the area of operations, and an allegiance-appropriate + parameter group. Projected display-only across the views. +- **Red (hostile) parameters**: location, threat extent/reach, severity, optional active window(s). + Threat *source* only in v1; reactive behaviour deferred. +- **Green (neutral) parameters**: location/extent, sensitivity (collateral weight), and protection + nature (e.g. keep-out vs minimise-effect). Constraint/objective *emission* deferred; display-only. +- **Blue (own-force) parameters**: location/extent, availability, and a capability stub (the + vocabulary a future Scheme matches to activity needs). Allocation *capability* deferred; + display-only — does not drive routing in v1. +- **Allegiance**: The blue / red / green typing that selects the kernel stance (plan-for / + avoid-assess / respect) and the rendering style. ## Success Criteria *(mandatory)* ### Measurable Outcomes -- **SC-001**: A planner can add a new red or green asset, place it, and see it on the map in **under - 30 seconds**, with no prior training beyond the on-screen affordances. -- **SC-002**: A planner can maintain **at least 10 instances each** of red and green on one ORBAT, - with every instance remaining individually identifiable and independently tunable. +- **SC-001**: A planner can add a new asset of any allegiance, place it, and see it on the map in + **under 30 seconds**, with no prior training beyond the on-screen affordances. +- **SC-002**: A planner can maintain **at least 10 instances of each allegiance** on one ORBAT, with + every instance remaining individually identifiable and independently tunable. - **SC-003**: Tuning any single instance's parameter changes **only that instance** in **100%** of - cases — no other instance or own force is affected. + cases — no other instance is affected. - **SC-004**: After authoring an ORBAT and reloading the scenario, **100%** of instances and their tuned parameter values are restored exactly. -- **SC-005**: Across the feature, the system produces **zero** fabricated adversary behaviour or - assessment — every depicted red/green effect traces directly to a planner-authored parameter - (honest-floor audit). +- **SC-005**: Across the feature, the system produces **zero** fabricated adversary behaviour and the + selected plan/route is **unchanged** by any blue/red/green tuning — every depicted effect traces + directly to a planner-authored parameter (honest-floor + display-only audit). - **SC-006**: Re-planning the same scenario with an unchanged ORBAT yields **identical** plans and projections (determinism preserved). ## Assumptions -- **Display-only scope (v1).** Red assets are passive threat *sources* and green assets are passive - collateral/ROE *markers*; neither yet emits live kernel constraints/objectives nor reacts. The - authoring and display scaffolding ships now; capability (capability-matched allocation, reactive - adversary, constraint/objective emission) is designed-for and deferred under the DEC-56 freeze - guard (DEC-59/60/61). Blue/own-force authoring is **out of scope** for this feature — it is the - existing own-force entity; this feature adds only the red and green sides. +- **Display-only scope (v1).** All three allegiances are authored at the same shape-scaffolding level + (DEC-56 freeze guard). Red assets are passive threat *sources*; green assets are passive + collateral/ROE *markers*; blue pool assets are *display-only force-pool members* — none yet emits + live kernel constraints/objectives, reacts, or alters routing. Capability (capability-matched + allocation, reactive adversary, constraint/objective emission) is designed-for and deferred + (DEC-59/60/61). +- **Own-force reconciliation.** The single existing planned own-force (ROVER-1) is surfaced as the + canonical blue asset and keeps driving the plan via the existing machinery; ORBAT blue authoring + adds *additional* pool members without changing how the plan is computed. - **Reuse the entity model.** Assets are modelled as existing allegiance-typed **entities** (DEC-52) in the config-declared catalogue (DEC-48…50); the only data-model additions are the allegiance attribute and the per-asset tunable parameters (DEC-60), not a new object family. - **Area of operations.** Assets are placed within the current H3-hex area of operations and located by hex/lat-lon consistent with the existing map and routing (spec 003). -- **Parameter set.** The concrete per-allegiance parameter list (e.g. exact red threat fields and - green sensitivity scale) is a reasonable starter set chosen at planning/design time; the spec - fixes the *shape* (label + location + extent + severity/sensitivity, per allegiance), not the - exact field names. +- **Parameter set.** The concrete per-allegiance parameter list is a reasonable starter set chosen at + design time; the spec fixes the *shape* (label + location + extent + allegiance-specific group), + not the exact field names or scales. - **Persistence.** The existing scenario/store mechanism is reused to persist the ORBAT; no new storage system is introduced. - **Single planner, sequential.** Authoring is by a single user in v1 (multi-user role distribution diff --git a/specs/004-orbat-red-green-assets/tasks.md b/specs/004-orbat-red-green-assets/tasks.md index 53d4904..e96c09a 100644 --- a/specs/004-orbat-red-green-assets/tasks.md +++ b/specs/004-orbat-red-green-assets/tasks.md @@ -1,9 +1,9 @@ --- -description: "Task list for ORBAT red/green assets implementation" +description: "Task list for ORBAT blue/red/green assets implementation" --- -# Tasks: ORBAT — add & tune red and green assets +# Tasks: ORBAT — add & tune blue, red, and green assets **Input**: Design documents from `specs/004-orbat-red-green-assets/` @@ -11,12 +11,12 @@ description: "Task list for ORBAT red/green assets implementation" **Tests**: INCLUDED — the project's quality gates require `npm run test:unit` (`node --test`, `test/*.test.mjs`) and `npm run test:e2e` (Playwright cloud wrapper, `e2e/*.spec.ts`) on every PR (constitution §Development Workflow). Graphical work captures evidence screenshots. -**Organization**: grouped by user story (US1–US4 from spec.md) for independent implementation and testing. +**Organization**: grouped by user story (US1–US5 from spec.md) for independent implementation and testing. ## Format: `[ID] [P?] [Story] Description` - **[P]**: can run in parallel (different files, no dependency on incomplete tasks) -- **[Story]**: US1 / US2 / US3 / US4 (setup, foundational, polish carry no story label) +- **[Story]**: US1 / US2 / US3 / US4 / US5 (setup, foundational, polish carry no story label) ## Path Conventions @@ -30,9 +30,9 @@ Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `tes - [ ] T001 Add `Allegiance` enum (`blue|red|green`, with stance notes) to `schema/common.yaml` - [ ] T002 Add optional `allegiance` attribute (range `Allegiance`) to `Entity` in `schema/entities.yaml` -- [ ] T003 Create `schema/orbat.yaml` module — `Orbat`, `Asset`, `RedParams`, `GreenParams`, `TimeWindow` classes + `Protection` enum (per [data-model.md](./data-model.md)); reuse existing `Waypoint`/`Lineage` +- [ ] T003 Create `schema/orbat.yaml` module — `Orbat`, `Asset` (+ `canonical_own_force`), `BlueParams`, `RedParams`, `GreenParams`, `TimeWindow` classes + `Protection` enum (per [data-model.md](./data-model.md)); reuse existing `Waypoint`/`Lineage` - [ ] T004 Import the `orbat` module in the entry schema `schema/remit.yaml` -- [ ] T005 Regenerate artefacts: `bash schema/generate.sh` → verify `schema/gen/remit.schema.json` + `schema/gen/remit.ts` include `Orbat`/`Asset`/`Allegiance`; do NOT hand-edit generated files +- [ ] T005 Regenerate artefacts: `bash schema/generate.sh` → verify `schema/gen/remit.schema.json` + `schema/gen/remit.ts` include `Orbat`/`Asset`/`Allegiance`/`BlueParams`; do NOT hand-edit generated files - [ ] T006 [P] Create `app/js/orbat/orbat.js` skeleton (`// @ts-check`) importing the generated types from `schema/gen/remit.ts` **Checkpoint**: schema regenerated, allegiance/asset shapes available as generated types. @@ -46,15 +46,16 @@ Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `tes **⚠️ CRITICAL**: No user-story work begins until this phase is complete. - [ ] T007 Implement `emptyOrbat(name)` + `canonical(orbat)` (assets sorted by `id`, canonical JSON, DEC-35) in `app/js/orbat/orbat.js` -- [ ] T008 Implement `validate(asset)` — bounds clamp for `extent_m`/`severity`/`sensitivity`, `start_min ≤ end_min`, position-in-AO, allegiance↔param-group match — in `app/js/orbat/orbat.js` +- [ ] T008 Implement `validate(asset)` — bounds clamp for `extent_m`/`severity`/`sensitivity`, `start_min ≤ end_min`, position-in-AO, allegiance↔param-group match for all three allegiances — in `app/js/orbat/orbat.js` - [ ] T009 Implement persistence `saveDraft`/`loadDraft` (localStorage key `remit.orbat.M-001`, canonical JSON) in `app/js/orbat/orbat.js` -- [ ] T010 Implement display-only `assetToEntity(asset)` adapter (allegiance-typed Entity, `provenance.kind='actor'`, position; no kernel reference — NF9) in `app/js/orbat/orbat.js` -- [ ] T011 Extend `buildEntities()` to fold authored ORBAT assets into the entity set in `app/js/entities/entities.js` -- [ ] T012 Extend `map.render` to draw an allegiance-coloured asset marker layer (point + faint extent ring + label) in `app/js/views/map.js` -- [ ] T013 Register the ORBAT authoring panel as a config-declared role-tab (home: `sme-int`) in `app/js/shell/roles.js` and scaffold `app/js/shell/orbat-panel.js` with `mount(container, ctx)` -- [ ] T014 Wire the panel + authored assets into the render loop (feed assets to `map.render` and `buildEntities`) in `app/js/main.js` +- [ ] T010 Implement display-only `assetToEntity(asset)` adapter (allegiance-typed Entity; `provenance.kind='self'` for the canonical own-force else `'actor'`; position; no kernel reference — NF9) in `app/js/orbat/orbat.js` +- [ ] T011 Implement `reconcileOwnForce(orbat, self)` — surface the existing planned own-force (ROVER-1) as the single `canonical_own_force` blue asset, idempotent — in `app/js/orbat/orbat.js` +- [ ] T012 Extend `buildEntities()` to fold authored ORBAT assets into the entity set in `app/js/entities/entities.js` +- [ ] T013 Extend `map.render` to draw an allegiance-coloured asset marker layer (point + faint extent ring + label) in `app/js/views/map.js` +- [ ] T014 Register the ORBAT authoring panel as a config-declared role-tab (home: `sme-int`) in `app/js/shell/roles.js` and scaffold `app/js/shell/orbat-panel.js` with `mount(container, ctx)` +- [ ] T015 Wire the panel + authored assets into the render loop (feed assets to `map.render` and `buildEntities`; reconcile own-force on load) in `app/js/main.js` -**Checkpoint**: an asset added in code renders on the map; the panel mounts. Stories can now begin. +**Checkpoint**: an asset added in code renders on the map; the panel mounts; ROVER-1 shows as the canonical blue asset. Stories can now begin. --- @@ -62,20 +63,20 @@ Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `tes **Goal**: A planner adds multiple independent red threat assets and tunes each (label, position, extent, severity); each appears in the red allegiance style on the map. -**Independent Test**: With an empty red side, add two red assets at different cells, tune one's extent/severity, confirm both render distinctly and only the tuned one changed — without touching green or own force. +**Independent Test**: With an empty red side, add two red assets at different cells, tune one's extent/severity, confirm both render distinctly and only the tuned one changed — without touching the other allegiances. ### Tests for User Story 1 -- [ ] T015 [P] [US1] Unit tests — deterministic `addAsset` (fresh unique id), `tuneAsset` red clamp (`extent_m`, `severity`), per-asset isolation, `canonical` stability — in `test/orbat.test.mjs` -- [ ] T016 [P] [US1] e2e — add two red assets, tune one, assert both visible + isolation + no fabricated adversary motion (honest floor) — in `e2e/orbat.spec.ts` +- [ ] T016 [P] [US1] Unit tests — deterministic `addAsset` (fresh unique id), `tuneAsset` red clamp (`extent_m`, `severity`), per-asset isolation, `canonical` stability — in `test/orbat.test.mjs` +- [ ] T017 [P] [US1] e2e — add two red assets, tune one, assert both visible + isolation + no fabricated adversary motion (honest floor) — in `e2e/orbat.spec.ts` ### Implementation for User Story 1 -- [ ] T017 [US1] Implement `addAsset(orbat, {allegiance:'red', position})` with red defaults + fresh id; reject `blue` and out-of-AO positions — in `app/js/orbat/orbat.js` -- [ ] T018 [US1] Implement `tuneAsset(orbat, id, patch)` applying clamped `RedParams` (severity) + `extent_m`/`label` to only the targeted asset — in `app/js/orbat/orbat.js` -- [ ] T019 [US1] Render the **Red (hostile)** roster group, **Add red**, and per-row tuners (label, extent, severity) in `app/js/shell/orbat-panel.js` -- [ ] T020 [US1] Red marker styling (`#ff7b72` family) + extent ring + click-to-pick-hex placement in `app/js/views/map.js` -- [ ] T021 [US1] Inline validation feedback (clamp/reject with message) in `app/js/shell/orbat-panel.js`; capture evidence screenshots → `specs/004-orbat-red-green-assets/evidence/screenshots/` +- [ ] T018 [US1] Implement `addAsset(orbat, {allegiance:'red', position})` with red defaults + fresh id; reject out-of-AO positions — in `app/js/orbat/orbat.js` +- [ ] T019 [US1] Implement `tuneAsset(orbat, id, patch)` applying clamped `RedParams` (severity) + `extent_m`/`label` to only the targeted asset — in `app/js/orbat/orbat.js` +- [ ] T020 [US1] Render the **Red (hostile)** roster group, **Add red**, and per-row tuners (label, extent, severity) in `app/js/shell/orbat-panel.js` +- [ ] T021 [US1] Red marker styling (`#ff7b72` family) + extent ring + click-to-pick-hex placement in `app/js/views/map.js` +- [ ] T022 [US1] Inline validation feedback (clamp/reject with message) in `app/js/shell/orbat-panel.js`; capture evidence screenshots → `specs/004-orbat-red-green-assets/evidence/screenshots/` **Checkpoint**: red ORBAT is authorable, tunable, and visible — the MVP. @@ -83,72 +84,93 @@ Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `tes ## Phase 4: User Story 2 — Add & tune green (neutral) assets (Priority: P1) -**Goal**: A planner adds multiple independent green assets and tunes each (label, position, extent, sensitivity, protection); each appears in the green allegiance style, distinct from red and own force. +**Goal**: A planner adds multiple independent green assets and tunes each (label, position, extent, sensitivity, protection); each appears in the green allegiance style, distinct from the others. **Independent Test**: With an empty green side, add two green assets with different sensitivities, tune one, confirm both render in green and only the tuned one changed. ### Tests for User Story 2 -- [ ] T022 [P] [US2] Unit tests — green defaults, `GreenParams` clamp (`sensitivity`, `protection`), isolation — in `test/orbat.test.mjs` -- [ ] T023 [P] [US2] e2e — add two green assets, tune one, assert distinct green styling vs red/own-force — in `e2e/orbat.spec.ts` +- [ ] T023 [P] [US2] Unit tests — green defaults, `GreenParams` clamp (`sensitivity`, `protection`), isolation — in `test/orbat.test.mjs` +- [ ] T024 [P] [US2] e2e — add two green assets, tune one, assert distinct green styling vs red/own-force — in `e2e/orbat.spec.ts` ### Implementation for User Story 2 -- [ ] T024 [US2] Extend `addAsset`/`tuneAsset` with green defaults + `GreenParams` (`sensitivity`, `protection`) in `app/js/orbat/orbat.js` -- [ ] T025 [US2] Render the **Green (neutral)** roster group, **Add green**, and per-row tuners (label, extent, sensitivity, protection) in `app/js/shell/orbat-panel.js` -- [ ] T026 [US2] Green marker styling (`#38d39f` family) visually distinct from red and own-force in `app/js/views/map.js` +- [ ] T025 [US2] Extend `addAsset`/`tuneAsset` with green defaults + `GreenParams` (`sensitivity`, `protection`) in `app/js/orbat/orbat.js` +- [ ] T026 [US2] Render the **Green (neutral)** roster group, **Add green**, and per-row tuners (label, extent, sensitivity, protection) in `app/js/shell/orbat-panel.js` +- [ ] T027 [US2] Green marker styling (`#38d39f` family) visually distinct from red and own-force in `app/js/views/map.js` -**Checkpoint**: both red and green sides are independently authorable and visible. +**Checkpoint**: red and green sides are independently authorable and visible. --- -## Phase 5: User Story 3 — Manage the ORBAT roster (Priority: P2) +## Phase 5: User Story 3 — Add & tune own-force (blue) assets (Priority: P2) -**Goal**: Duplicate, remove, and persist assets; the roster and all tuned values survive reload; committing mints an immutable version. +**Goal**: A planner adds multiple independent blue pool assets and tunes each (label, position, extent, availability, capability stub); each appears in the blue allegiance style. Tuning is **display-only** — it does not change kernel routing — and the existing planned own-force (ROVER-1) is reconciled as the canonical blue asset. -**Independent Test**: Duplicate an asset, rename the copy, remove a different asset, reload — confirm the roster and every tuned value are exactly as left. +**Independent Test**: Add two blue pool assets, tune one's availability/capability, confirm both render in blue alongside ROVER-1 and that the selected plan/route is **unchanged** by the tuning. ### Tests for User Story 3 -- [ ] T027 [P] [US3] Unit tests — `duplicateAsset` (new id, independent copy), `removeAsset` (others unaffected), `commit` immutability + lineage — in `test/orbat.test.mjs` -- [ ] T028 [P] [US3] e2e — duplicate + remove, reload page, assert roster + tuned values restored — in `e2e/orbat.spec.ts` +- [ ] T028 [P] [US3] Unit tests — blue defaults + `BlueParams` clamp, `reconcileOwnForce` idempotent + single `canonical_own_force`, duplicate never copies the canonical flag — in `test/orbat.test.mjs` +- [ ] T029 [P] [US3] e2e — add + tune blue, assert blue styling + **route/plan unchanged** by tuning (display-only proof) + ROVER-1 shown reconciled — in `e2e/orbat.spec.ts` ### Implementation for User Story 3 -- [ ] T029 [US3] Implement `duplicateAsset(orbat, id)` (deep-copy under new id) + `removeAsset(orbat, id)` in `app/js/orbat/orbat.js`; wire duplicate/remove controls in `app/js/shell/orbat-panel.js` -- [ ] T030 [US3] Mirror `saveDraft` on every mutating op and `loadDraft` on panel mount in `app/js/shell/orbat-panel.js` -- [ ] T031 [US3] Implement `commit(orbat, objects)` — immutable content-addressed `Orbat` version with `lineage.previous_version` — in `app/js/orbat/orbat.js`; add a **Commit ORBAT** action to the panel +- [ ] T030 [US3] Extend `addAsset`/`tuneAsset` with blue defaults + `BlueParams` (`availability`, `capabilities`) in `app/js/orbat/orbat.js` +- [ ] T031 [US3] Render the **Blue (own force)** roster group, **Add blue**, per-row tuners (label, extent, availability, capabilities), and show the canonical own-force row marked with its **remove disabled** in `app/js/shell/orbat-panel.js` +- [ ] T032 [US3] Blue marker styling distinct from red/green in `app/js/views/map.js`; assert in the e2e/honest-floor path that a blue tune leaves the route unchanged (no kernel coupling) -**Checkpoint**: roster management + persistence across sessions work. +**Checkpoint**: all three allegiances are independently authorable and visible; blue stays display-only. --- -## Phase 6: User Story 4 — ORBAT instances across the planning views (Priority: P3) +## Phase 6: User Story 4 — Manage the ORBAT roster (Priority: P2) -**Goal**: A red asset with active time-windows projects as a Sync-Matrix track, synchronised with the shared playhead and selection. +**Goal**: Duplicate, remove, and persist assets across all allegiances; the roster and tuned values survive reload; committing mints an immutable version. The canonical own-force is protected from removal. -**Independent Test**: Add a red asset with an active window, scrub the timeline, confirm its track appears on the Sync Matrix and stays aligned with the map; selecting it highlights it in both views. +**Independent Test**: Duplicate an asset, rename the copy, remove a different asset, reload — confirm the roster and every tuned value are exactly as left, and that the canonical own-force cannot be removed. ### Tests for User Story 4 -- [ ] T032 [P] [US4] e2e — red asset with `active_windows` shows a Sync-Matrix track aligned with the playhead; selection syncs across views — in `e2e/orbat.spec.ts` +- [ ] T033 [P] [US4] Unit tests — `duplicateAsset` (new id, independent copy, no canonical flag), `removeAsset` (others unaffected; canonical own-force protected), `commit` immutability + lineage — in `test/orbat.test.mjs` +- [ ] T034 [P] [US4] e2e — duplicate + remove, reload page, assert roster + tuned values restored; canonical own-force not removable — in `e2e/orbat.spec.ts` ### Implementation for User Story 4 -- [ ] T033 [US4] Add an `active_windows` (`TimeWindow[]`) tuner for red assets in `app/js/shell/orbat-panel.js` -- [ ] T034 [US4] Emit a `window`-type aspect from `assetToEntity` (reuse the satellite-pass render path) and add the catalogue row in `app/js/entities/entities.js` so the asset appears as a Sync-Matrix track -- [ ] T035 [US4] Bind shared selection across panel ↔ map ↔ Sync-Matrix in `app/js/main.js` +- [ ] T035 [US4] Implement `duplicateAsset(orbat, id)` (deep-copy under new id, drop canonical flag) + `removeAsset(orbat, id)` (refuse the canonical own-force) in `app/js/orbat/orbat.js`; wire duplicate/remove controls in `app/js/shell/orbat-panel.js` +- [ ] T036 [US4] Mirror `saveDraft` on every mutating op and `loadDraft` on panel mount in `app/js/shell/orbat-panel.js` +- [ ] T037 [US4] Implement `commit(orbat, objects)` — immutable content-addressed `Orbat` version with `lineage.previous_version` — in `app/js/orbat/orbat.js`; add a **Commit ORBAT** action to the panel + +**Checkpoint**: roster management + persistence across sessions work. + +--- + +## Phase 7: User Story 5 — ORBAT instances across the planning views (Priority: P3) + +**Goal**: An asset with active time-windows (a red patrol, a blue availability window) projects as a Sync-Matrix track, synchronised with the shared playhead and selection. + +**Independent Test**: Add a red asset with an active window, scrub the timeline, confirm its track appears on the Sync Matrix and stays aligned with the map; selecting it highlights it in both views. + +### Tests for User Story 5 + +- [ ] T038 [P] [US5] e2e — asset with `active_windows` shows a Sync-Matrix track aligned with the playhead; selection syncs across views — in `e2e/orbat.spec.ts` + +### Implementation for User Story 5 + +- [ ] T039 [US5] Add an `active_windows` (`TimeWindow[]`) tuner for red assets and an availability-window tuner for blue assets in `app/js/shell/orbat-panel.js` +- [ ] T040 [US5] Emit a `window`-type aspect from `assetToEntity` (reuse the satellite-pass render path) and add the catalogue row in `app/js/entities/entities.js` so the asset appears as a Sync-Matrix track +- [ ] T041 [US5] Bind shared selection across panel ↔ map ↔ Sync-Matrix in `app/js/main.js` -**Checkpoint**: all four user stories are independently functional. +**Checkpoint**: all five user stories are independently functional. --- -## Phase 7: Polish & Cross-Cutting Concerns +## Phase 8: Polish & Cross-Cutting Concerns -- [ ] T036 [P] Record project memory: new ADR (ORBAT red/green authoring scaffolding) in `docs/project_notes/decisions.md`; allegiance palette + localStorage key in `docs/project_notes/key_facts.md`; work-log entry in `docs/project_notes/issues.md` -- [ ] T037 [P] Author the blog post `specs/004-orbat-red-green-assets/blog/post.md` from `docs/blog-post-template.md` (problem/options/strategy/results/screenshots + "at a glance") and add `blog/screenshots/` -- [ ] T038 Run `quickstart.md` end-to-end and collect evidence screenshots into `specs/004-orbat-red-green-assets/evidence/screenshots/` -- [ ] T039 Run `npm run test:unit` + `npm run test:e2e`; ensure `// @ts-check` is clean across the new modules +- [ ] T042 [P] Record project memory: new ADR (ORBAT blue/red/green authoring scaffolding, blue display-only) in `docs/project_notes/decisions.md`; allegiance palette + localStorage key in `docs/project_notes/key_facts.md`; work-log entry in `docs/project_notes/issues.md` +- [ ] T043 [P] Author the blog post `specs/004-orbat-red-green-assets/blog/post.md` from `docs/blog-post-template.md` (problem/options/strategy/results/screenshots + "at a glance") and add `blog/screenshots/` +- [ ] T044 Run `quickstart.md` end-to-end and collect evidence screenshots into `specs/004-orbat-red-green-assets/evidence/screenshots/` +- [ ] T045 Run `npm run test:unit` + `npm run test:e2e`; ensure `// @ts-check` is clean across the new modules --- @@ -158,15 +180,16 @@ Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `tes - **Setup (Phase 1)**: schema first; T001→T002→T003→T004→T005 sequential (same generation), T006 after T005. - **Foundational (Phase 2)**: depends on Setup; **blocks all user stories**. -- **User Stories (Phase 3–6)**: all depend on Foundational. US1 & US2 are both P1 and largely parallel; US3 builds on having assets to manage; US4 builds on the asset→entity path. -- **Polish (Phase 7)**: after the desired stories are complete. +- **User Stories (Phase 3–7)**: all depend on Foundational. US1 & US2 (both P1) and US3 (P2) reuse the same add/tune pipeline; US4 builds on having assets to manage; US5 builds on the asset→entity path. +- **Polish (Phase 8)**: after the desired stories are complete. ### User Story Dependencies - **US1 (P1)**: after Foundational — no dependency on other stories (MVP). - **US2 (P1)**: after Foundational — independent of US1 (shares `addAsset`/panel/map but exercises the green path). -- **US3 (P2)**: after Foundational — easiest to validate once US1/US2 produce assets to duplicate/remove; the model ops are independent. -- **US4 (P3)**: after Foundational — depends on `assetToEntity` (T010); independently testable. +- **US3 (P2)**: after Foundational — independent; relies on `reconcileOwnForce` (T011) for the canonical blue asset; reuses the US1/US2 add/tune pipeline. +- **US4 (P2)**: after Foundational — easiest to validate once US1–US3 produce assets to duplicate/remove; the model ops are independent. +- **US5 (P3)**: after Foundational — depends on `assetToEntity` (T010); independently testable. ### Within Each User Story @@ -178,8 +201,8 @@ Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `tes - T006 [P] alongside late Setup. - Unit-test and e2e-test tasks marked [P] within a story run together (different files: `test/orbat.test.mjs` vs `e2e/orbat.spec.ts`). -- US1 and US2 can be staffed in parallel after Foundational (note: both touch `orbat.js`, `orbat-panel.js`, `map.js` — coordinate or sequence those edits). -- Polish T036/T037 [P] are independent files. +- US1, US2, US3 can be staffed in parallel after Foundational (note: all three touch `orbat.js`, `orbat-panel.js`, `map.js` — coordinate or sequence those edits). +- Polish T042/T043 [P] are independent files. --- @@ -187,8 +210,8 @@ Single static web app: schema in `schema/`, app in `app/js/`, unit tests in `tes ```bash # Tests first (different files): -Task: "Unit tests for orbat model in test/orbat.test.mjs" # T015 -Task: "e2e author+tune red in e2e/orbat.spec.ts" # T016 +Task: "Unit tests for orbat model in test/orbat.test.mjs" # T016 +Task: "e2e author+tune red in e2e/orbat.spec.ts" # T017 ``` --- @@ -201,7 +224,7 @@ Task: "e2e author+tune red in e2e/orbat.spec.ts" # T016 ### Incremental Delivery -Setup + Foundational → US1 (red, MVP) → US2 (green) → US3 (roster + persistence) → US4 (Sync-Matrix projection) → Polish. Each story adds value without breaking the previous ones. +Setup + Foundational → US1 (red, MVP) → US2 (green) → US3 (blue own-force pool) → US4 (roster + persistence) → US5 (Sync-Matrix projection) → Polish. Each story adds value without breaking the previous ones. --- @@ -209,5 +232,6 @@ Setup + Foundational → US1 (red, MVP) → US2 (green) → US3 (roster + persis - [P] = different files, no dependency on incomplete work. - LinkML is the source of truth (Principle I): asset/allegiance shapes are schema-defined and regenerated (T001–T005), never hand-authored; only display closures/UI stay in `app/js`. -- Honest floor (NF9) + determinism (NF3) are assertable invariants — exercised by T015/T016 and reaffirmed in T021. +- Honest floor (NF9) + determinism (NF3) + **display-only blue** are assertable invariants — exercised by T016/T017/T028/T029 (the blue route-unchanged proof) and reaffirmed in T022/T032. +- The existing planned own-force is reconciled (T011), not re-implemented; it stays the only kernel-wired own force. - Commit after each task or logical group; capture evidence screenshots during e2e runs.