Skip to content

feat(stac): prefix-aware STAC typing + write-path safety (#256) - #663

Merged
IanMayo merged 16 commits into
mainfrom
claude/item-256-spec-status-JCx2R
Jun 2, 2026
Merged

IanMayo merged 16 commits into
mainfrom
claude/item-256-spec-status-JCx2R

Conversation

@IanMayo

@IanMayo IanMayo commented Jun 1, 2026 •

Copy link
Copy Markdown
Member

Summary

Makes the spec-240–deferred promise true: modelled debrief:* STAC fields now flow automatically to the writers' typed surface (read and write). LinkML's gen-typescript strips the debrief: prefix, so a naive StacItem.properties: StacExtensionProperties intersection matched none of the writers' real, prefixed access sites — they went through an untyped Record bag with as casts.

  • One schema-driven generator step (shared/schemas/scripts/generate.py, pure fn prefix_extension_slots) rewrites modelled slot keys to their LinkML slot_uri verbatim across three classes: StacExtensionProperties (5), StacSummaries (3), StacAsset (2 new).
  • Write-path typing — both hosts' mutation-path props re-typed from Record<string, unknown> to StacItemProperties, so write-side typos fail the build (the half a naive fix misses; ADR-033 / Article IV.5).
  • Models debrief:toolId + debrief:snapshotTimestamp as additive StacAsset slots; removes the hand-cast asset as StacAsset & { 'debrief:toolId'?: string }.
  • Closes the Implement tool parameter context menus (Feature 091) #240 Article II.1 audit deferral. On-disk JSON byte-for-byte unchanged; no new dependency; reuses the existing src/generated drift gate.

Changes

Schema + generator (foundational)

  • stac.yaml: two additive optional StacAsset slots (tool_id→debrief:toolId, snapshot_timestamp→debrief:snapshotTimestamp).
  • generate.py: prefix_extension_slots() + _load_extension_slot_uri_map(); one wired step over the three classes with RuntimeError self-guards. Regenerated TS + Pydantic (additive); deterministic.

Writer hosts

  • apps/vscode/src/services/stacService.ts: drop read-site casts; re-type write-path props (removes the Record widening + ADR-011 eslint-disable); remove the StacAsset debrief:toolId hand-cast.
  • apps/web-shell/src/services/stacWriterIdb.ts: re-type write-path props; drop the props as StacItem['properties'] cast.
  • Typing debrief:provenance_log surfaced a latent Implement tool parameter context menus (Feature 091) #240 divergence (generated wide vs component narrowed-hybrid PropertiesProvenanceEntry) that the as-cast had masked — resolved with an explicit typed persistence→domain narrowing bridge on the read.

Decision: debrief:label excluded

Investigation showed it's a GeoJSON feature property / MCP annotation, not a STAC item/asset key; modelling it would re-create the mismatch this feature removes. Left for a future BaseFeatureProperties change.

Evidence

Test Results

Metric Value
Python 2169 passed / 2 skipped / 1 xfailed
Generator transform (new) 7 passed
Type-level TS (read/write/asset) 6 passed
Writer unit suites (unchanged) 111 vscode + 54 web-shell passed
Lint / pyright / tsc (schemas + both apps) clean

Verified: FR-002 (new slot flows with no generator edit — pure-fn unit test); FR-004/FR-012 (read and write typos fail the build); FR-008/SC-004 (byte-stable round-trip incl. new asset keys); SC-007 (asset hand-cast removed); deterministic regen + drift-clean.

Generated diff (before → after)

  export interface StacExtensionProperties {
-     platforms?: PlatformRecord[],            +     'debrief:platforms'?: PlatformRecord[],
-     ...                                      +     'debrief:...'
  }
  export interface StacSummaries {
-     debrief_platforms?: PlatformRecord[],    +     'debrief:platforms'?: PlatformRecord[],
  }
  export interface StacAsset {
      href: string,            // untouched (no extension slot_uri)
+     'debrief:toolId'?: string,
+     'debrief:snapshotTimestamp'?: string,
  }

Full artefacts: specs/256-prefix-aware-stac-typing/evidence/ (test-summary.md, round-trip-evidence.md, generated-diff.md, usage-example.md).

Test Plan

  • uv run pytest (incl. new test_stac_prefix_transform.py, test_stac_roundtrip.py)
  • pnpm --filter @debrief/schemas typecheck && test + tsc --noEmit on both apps
  • writer unit suites unchanged (behaviour-preserving)
  • ruff check + ruff format --check + pyright + eslint clean
  • generator deterministic; src/generated drift-clean

Related


📖 Review in Spec Navigator: https://debrief.github.io/debrief-future/spec-navigator/?pr=663

Generated by Claude Code

Specifies prefix-aware TypeScript typing for StacExtensionProperties so
the writer's debrief:* property access sites gain named-slot typing
flowing from LinkML. Closes the #240-deferred Article II.1 audit finding.

Marks backlog item 256 as specified and links to
specs/256-prefix-aware-stac-typing/spec.md.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@github-actions

github-actions Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

📋 Spec Navigator

Review this PR's specs interactively:
👉 Open in Spec Navigator
Raw spec directories changed in this PR:

@github-actions

github-actions Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

📋 Backlog Navigator

Review this PR's BACKLOG.md edits interactively:

👉 Open in Backlog Navigator

Updated for 89a4551 — the navigator reads BACKLOG.md live from this PR's head branch, so no rebuild is needed.

@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 1, 2026 20:41 Inactive
Adds plan.md, research.md, data-model.md, contracts/, quickstart.md, and the
cached blog opener for the prefix-aware StacExtensionProperties typing feature.

Implementation route confirmed: extend the gen-typescript post-processor
(generate.py) to emit debrief:-prefixed slot keys derived from LinkML
slot_uri, so the STAC writers' props['debrief:*'] access sites gain typed
slots with no call-site rewrites. Typing-only; reuses the existing
src/generated drift gate. Constitution check passes (strengthens Article II.1).

Marks backlog item 256 as planned.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 1, 2026 21:23 Inactive
Re-plan after /speckit.review folded follow-ups into this PR:
- Prefix-aware transform now covers StacExtensionProperties, StacSummaries,
  and StacAsset (schema-driven via slot_uri — handles the divergent
  name->slot_uri conventions a per-class text rule cannot).
- Model debrief:toolId + debrief:snapshotTimestamp on StacAsset (additive
  LinkML slots; removes the stacService.ts:674 hand-cast). Drop debrief:label
  (a GeoJSON feature/MCP annotation key, not a STAC property).
- Re-type both hosts' write-path props from Record<string,unknown> to
  StacItemProperties so write-side typos fail the build (ADR-033 / Art IV.5).
- FR-002 covered by a pure-function transform unit test (synthetic slot).

Updates spec/plan/research/data-model/contracts/quickstart/opening-context
and refreshes agent context. No code changed yet.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 05:55 Inactive
@IanMayo IanMayo changed the title Spec: prefix-aware TypeScript typing for STAC extension properties (item 256) Plan: prefix-aware STAC typing across 3 classes + write-path safety (item 256) Jun 2, 2026
… typing)

31 tasks across 6 phases:
- Phase 2 (foundational): StacAsset slots + schema-driven prefix transform
  (pure fn + slot_uri loader + wiring) + regen — blocks all stories.
- US1 (P1): FR-002 proven by pure-function unit test + convention guard.
- US2 (P2): remove read casts, re-type both hosts' write-path props to
  StacItemProperties, remove StacAsset hand-cast; type-level read+write+asset
  assertions.
- US3 (P3): byte-invariance golden + StacAsset round-trip + #240 audit closure.
- Polish: evidence (round-trip proof), shipped post, /speckit.pr (T031 last).

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 06:05 Inactive
claude added 5 commits June 2, 2026 12:04
…ots (#256)

Phase 2 (foundational):
- Add prefix_extension_slots() pure fn + _load_extension_slot_uri_map() loader
  to generate.py; wire one schema-driven step into generate_typescript() that
  rewrites modelled slot keys to their slot_uri across StacExtensionProperties,
  StacSummaries, and StacAsset (RuntimeError self-guards per existing convention).
- Model debrief:toolId + debrief:snapshotTimestamp as optional StacAsset slots.
- Regenerate TS + Pydantic (additive); deterministic (byte-identical re-run);
  180 STAC/idempotency schema tests pass.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
Phase 3 (US1): test_stac_prefix_transform.py (7 tests) proves the transform is
a pure schema-driven function (new debrief:* slot flows through with no
generator edit), the loader resolves 3 classes, and the committed types.ts
emits every modelled slot under its colon key (convention guard) + structural
counts. Worked FR-002 demo recorded in evidence/usage-example.md.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
Phase 4 (US2):
- vscode stacService: drop read-site casts; re-type write-path props to
  StacItemProperties (remove the Record<string,unknown> widening + ADR-011
  eslint-disable); remove the StacAsset hand-cast for debrief:toolId.
- web-shell stacWriterIdb: re-type props to StacItemProperties; drop the
  props-as-StacItem['properties'] cast.
- Typing the debrief:provenance_log slot surfaced the #240 generated-vs-hybrid
  PropertiesProvenanceEntry divergence (previously masked by the as-cast);
  resolved with an explicit typed persistence->domain narrowing bridge on the
  read (write is narrow<:wide, no cast).
- Add tests/ts/stac-prefix-typing-256.test.ts (6 type-level read/write/asset
  assertions). Both apps typecheck clean; 111 vscode + 54 web-shell unit tests
  pass.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:27 Inactive
Phase 5 (US3):
- Byte-stable round-trip test for the new StacAsset debrief:toolId /
  debrief:snapshotTimestamp keys (FR-008/SC-004).
- ADR-040 records the schema-driven mechanism, write-path re-typing, label
  exclusion, the provenance_log hybrid-divergence discovery, and the #240
  Article II.1 deferral closure; issues.md logged.
- Fix ruff ANN202 on the prefix-transform test helper (clears CI Ruff Lint).

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@github-actions

github-actions Bot commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

🚀 Preview Deployments

Code Server (Full VS Code Extension)

🖥️ Open Code Server

Browser-based VS Code with the Debrief extension and sample data pre-installed.

Web Shell (Standalone App)

📱 Open Web Shell

Use this for Playwright testing and demos - runs outside Storybook.

Storybook (Component Library)

📚 Open Storybook

Browse all components, stories, and documentation.

Briefing Renderer (Air-gapped Storyboard Player — spec #264)

🎬 Open Briefing Renderer

Standalone SPA that plays a Storyboard from a file://-origin zip. Loaded here on HTTPS as a static demo with the dev-fixture Storyboard (Channel Crossing — Demo Briefing).


All Links
Environment Code Server Web Shell Storybook Briefing Renderer
This PR Open IDE Open App Open Storybook Open Briefing
Main branch — Open App Open Storybook Open Briefing

Updated on commit dc07362

@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:30 Inactive
@github-actions

github-actions Bot commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Schema Docs Preview

Interactive site (clickable Mermaid, search, navigation)

Open HTML preview

Markdown sources (textual review on GitHub)

Browse Markdown on gh-pages


All Links
Surface This PR Main branch
HTML Preview Persistent
Markdown Preview On gh-pages

Updated on commit dc07362

…ed post (#256)

Phase 6: full gate verified locally (ruff/eslint/pyright/tsc clean, 2204
pytest pass, schemas+vscode+web-shell vitest pass). Evidence: test-summary.md
(YAML front matter), round-trip-evidence.md, generated-diff.md, usage-example.md;
media/shipped-post.md (Building Prefix-Aware STAC Typing).

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:35 Inactive
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:36 Inactive
CI 'Ruff Lint' runs 'ruff format --check'; my two new Python files needed
formatting (ruff check already passed). Cosmetic only — 36 tests pass,
generated artefacts drift-clean.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:37 Inactive
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:38 Inactive
@IanMayo IanMayo changed the title Plan: prefix-aware STAC typing across 3 classes + write-path safety (item 256) feat(stac): prefix-aware STAC typing + write-path safety (#256) Jun 2, 2026
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:44 Inactive
The liveBacklog round-trip test (apps/backlog-navigator) parses the live
BACKLOG.md and asserts byte-identical parse->serialize. My manual ~~-wrap +
leftover 'implementing' status was non-canonical (the serializer strikes a
row iff status==='complete'). Set status=complete and re-serialised via the
navigator's own serializer. Fixes the 'Test & Lint' failure.

https://claude.ai/code/session_01JZ63MUkLs28LsSStBDbrh2
@IanMayo
IanMayo temporarily deployed to debrief-preview-pr-663 June 2, 2026 12:55 Inactive
@IanMayo
IanMayo merged commit 5f9671c into main Jun 2, 2026
17 checks passed
@IanMayo
IanMayo deleted the claude/item-256-spec-status-JCx2R branch June 2, 2026 17:11
github-actions Bot pushed a commit that referenced this pull request Jun 2, 2026

This branch was previously deployed

1 inactive deployment
debrief-preview-pr-663 — 89a45510 Deployed Jun 2, 2026 by IanMayo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants