docs(03): plan structural gap closure

This commit is contained in:
unanmed 2026-09-11 10:15:19 +08:00
parent c0444510d1
commit 39d5e366a4
6 changed files with 754 additions and 0 deletions

View File

@ -0,0 +1,123 @@
---
phase: 03-data-completion
plan: 10
type: execute
wave: 6
depends_on: [03-07]
files_modified:
- packages-user/data-state/src/core.ts
- packages-user/data-state/src/legacy/dependencies.ts
- packages-user/data-state/src/legacy/events.ts
- packages-user/data-state/test/fixtures/closed-loop.ts
- packages-user/data-state/test/coreSerializedEvents.test.ts
autonomous: true
requirements: [DATA-01]
gap_closure: true
gap_ids: [CORR-03-01]
estimate:
tokens: 26000
raw_tokens: 26000
tasks: 2
confidence: low
must_haves:
truths:
- "CORR-03-01: Phase 3 keeps legacy code as the existing compatibility path and adds no new legacy system or loading boundary (user-authoritative requirement 1)."
- "CoreState uses the pre-Phase-3 compatibility loading path rather than an internally injected legacy architecture, while current replay/event assembly remains intact (D-09, D-10, D-12)."
- "The fixed replay fixture is data-side setup and reaches the existing event store/map APIs without depending on a Phase-3 legacy loader."
artifacts:
- path: "packages-user/data-state/src/core.ts"
provides: "CoreState construction with the existing compatibility loading path and no new legacy dependency seam"
- path: "packages-user/data-state/test/fixtures/closed-loop.ts"
provides: "Direct explicit data fixture for the closed replay path"
key_links:
- "CoreState -> existing legacy loading callbacks and converters"
- "closed-loop fixture -> existing GameEventStore and MapState data APIs"
- "CoreState replay import -> @user/data-common barrel, preserving the user's current import cleanup"
---
<objective>
Close CORR-03-01 by removing the Phase-3 legacy dependency and serialized-loading additions, restoring CoreState to the existing compatibility-only loading path, and changing the replay fixture back to explicit data-side setup.
Purpose: The structural review rejects new legacy systems and loading boundaries. This correction removes only the Phase-3 architecture that caused that drift; it does not begin the Phase-5 legacy migration.
Output: CoreState without the new legacy boundary, removal of the two Phase-3 legacy adapter files, a direct closed-loop fixture, and removal of the serialized-loading regression that only exercised the rejected boundary.
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
@.planning/phases/03-data-completion/03-CONTEXT.md
@.planning/phases/03-data-completion/03-RESEARCH.md
@.planning/phases/03-data-completion/03-VERIFICATION.md
@.planning/phases/03-data-completion/03-07-SUMMARY.md
@packages-user/data-state/src/core.ts
@packages-user/data-state/test/fixtures/closed-loop.ts
@packages-user/data-state/src/legacy/dependencies.ts
@packages-user/data-state/src/legacy/events.ts
@dev.md
</context>
<tasks>
<task type="tracer">
<name>Task 1: CORR-03-01 restore the existing compatibility loading path</name>
<gap_id>CORR-03-01</gap_id>
<files>packages-user/data-state/src/core.ts, packages-user/data-state/src/legacy/dependencies.ts, packages-user/data-state/src/legacy/events.ts</files>
<action>Remove the Phase-3-only dependency factory, serialized-event adapter, symbol-keyed load entry, serialized branch, and raw-map loader added for that boundary. Restore CoreState's existing compatibility construction and loading callbacks using the repository's pre-Phase-3 legacy converters, `loading`, and browser-host data source; keep the existing `SaveSystem` assignment in this restoration, with the save-specific deletion and regression assertions completed by Plan 03-11. Preserve the current event built-in and replay assembly, preserve the no-argument `createCoreState()` shape, and retain the user's current `ReplaySystem` import from `@user/data-common` rather than reintroducing a relative package-internal import. Do not add another adapter, public registration API, or new legacy abstraction; legacy remains compatibility-only under user-authoritative requirements 1 and 2 (D-09, D-10, D-12, D-19, D-23, D-28).</action>
<verify>
<automated>$paths = @('packages-user/data-state/src/legacy/dependencies.ts','packages-user/data-state/src/legacy/events.ts'); foreach ($path in $paths) { if (Test-Path -LiteralPath $path) { throw "Rejected Phase-3 legacy artifact still exists: $path" } }; $core = Get-Content -Raw 'packages-user/data-state/src/core.ts'; if ($core -match 'createLegacyDependencies|LOAD_SERIALIZED_DATA|registerSerializedEvents') { throw 'CoreState still references the rejected loading boundary' }; if ($core -notmatch "from '@user/data-common'") { throw 'CoreState data-common barrel import is missing' }; if ($core -match "from '../../data-common/src/replay/system'") { throw 'CoreState reverted the user import cleanup' }; pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; const state = createCoreState(); if (state.saveSystem.constructor.name !== 'SaveSystem') throw new Error('existing SaveSystem path is not active');"
</automated>
<fails_when>Either rejected legacy artifact remains, CoreState still reaches the Phase-3 loading boundary, the current data-common ReplaySystem import cleanup is reversed, CoreState cannot be constructed, or the existing SaveSystem is not selected.</fails_when>
</verify>
<done>CoreState no longer depends on the Phase-3 legacy architecture and again uses the existing compatibility loading and save construction path without changing the current replay import cleanup.</done>
</task>
<task type="auto">
<name>Task 2: CORR-03-01 restore direct replay fixture coverage</name>
<gap_id>CORR-03-01</gap_id>
<files>packages-user/data-state/test/fixtures/closed-loop.ts, packages-user/data-state/test/coreSerializedEvents.test.ts</files>
<action>Replace the fixture's symbol-keyed serialized load payload with the established explicit data-side fixture shape: create the maps through the existing raw/map APIs, register the fixture event directly in the existing `GameEventStore`, and keep the map/tile event reference and awaited event mutation assertions intact. Delete the regression file whose only purpose was to validate the removed serialized legacy loading boundary. Keep the route, final snapshot, and Node replay entry behavior deterministic, and do not move event bodies into map/save data or introduce a replacement loader (D-03, D-04, D-11, D-13, D-22, D-24, D-27; user-authoritative requirement 1).</action>
<verify>
<automated>if (Test-Path -LiteralPath 'packages-user/data-state/test/coreSerializedEvents.test.ts') { throw 'Rejected serialized-loading regression still exists' }; $fixture = Get-Content -Raw 'packages-user/data-state/test/fixtures/closed-loop.ts'; if ($fixture -match 'LOAD_SERIALIZED_DATA|ILegacySerializedLoadData|serialized') { throw 'Closed-loop fixture still uses the rejected loader' }; if ($fixture -notmatch 'eventStore\.addEvent') { throw 'Closed-loop fixture no longer registers its explicit event' }; pnpm exec vitest run packages-user/data-state/test/nodeReplay.test.ts; if ($?) { pnpm test:data-node }
</automated>
<fails_when>The fixture still imports or invokes the removed loader, the obsolete regression file remains, the direct event fixture is absent, the replay regression fails, or the dedicated Node replay command exits non-zero.</fails_when>
</verify>
<done>The closed-loop replay proves the existing data-side event path directly, with no Phase-3 legacy loading system or boundary.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|---|---|
| compatibility host -> CoreState | Existing browser/legacy globals can supply compatibility data and must remain behind the existing path only. |
| explicit fixture -> data stores | Test data becomes map, tile, and event state without a new loader. |
| replay route -> event mutation | A fixed route invokes the existing command/event path. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-24 | Tampering | compatibility loading boundary | high | mitigate | Restore the existing compatibility callback path and remove the Phase-3 dependency/serialized adapter instead of adding another legacy entry point. |
| T-03-25 | Tampering | explicit replay fixture | medium | mitigate | Use fixed typed map/event data and retain the existing event-store and map validation paths. |
| T-03-SC | Tampering | npm/pip/cargo installs | low | accept | No package installation is planned. |
</threat_model>
<verification>
- `pnpm exec vitest run packages-user/data-state/test/nodeReplay.test.ts`
- `pnpm test:data-node`
- Source checks confirm the Phase-3 legacy boundary files and references are absent while the compatibility callback path remains.
</verification>
<success_criteria>
CORR-03-01 is closed when legacy additions from Phase 3 are removed, CoreState uses the existing compatibility-only loading path, the current data-common import cleanup remains, and the fixed replay still passes without a replacement legacy loader.
</success_criteria>
<output>
Create `.planning/phases/03-data-completion/03-10-SUMMARY.md` when done
</output>

View File

@ -0,0 +1,124 @@
---
phase: 03-data-completion
plan: 11
type: execute
wave: 7
depends_on: [03-10]
files_modified:
- packages-user/data-common/src/save/memory.ts
- packages-user/data-common/src/save/index.ts
- packages-user/data-state/test/coreNode.test.ts
- packages-user/data-state/test/nodeTracer.test.ts
autonomous: true
requirements: [DATA-01]
gap_closure: true
gap_ids: [CORR-03-02]
estimate:
tokens: 16000
raw_tokens: 16000
tasks: 2
confidence: low
must_haves:
truths:
- "CORR-03-02: CoreState uses the existing SaveSystem and does not add a Node-specific save adapter (user-authoritative requirement 2)."
- "Save initialization remains on the existing compatibility loading path; this correction does not move save into rendering or redesign save architecture."
- "Node/data tests verify independent CoreState objects without asserting or importing a Phase-3 save implementation."
artifacts:
- path: "packages-user/data-common/src/save/index.ts"
provides: "Existing save exports without the Phase-3 adapter"
- path: "packages-user/data-state/test/coreNode.test.ts"
provides: "Regression proof for independent CoreState construction and existing SaveSystem selection"
- path: "packages-user/data-state/test/nodeTracer.test.ts"
provides: "Replay tracer regression without a Node-specific save expectation"
key_links:
- "CoreState -> existing SaveSystem"
- "existing loading callback -> SaveSystem.init"
- "Node replay fixture -> CoreState without a parallel save architecture"
---
<objective>
Close CORR-03-02 by removing the Phase-3 in-memory save adapter and updating Node regressions to preserve the existing SaveSystem and compatibility initialization architecture.
Purpose: Save restructuring is outside this correction and outside the rendering refactor boundary. The fix must remove the new adapter, not replace it with another Node-only backend.
Output: No Phase-3 save adapter source/export and focused CoreState/tracer assertions for the existing SaveSystem path.
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
@.planning/phases/03-data-completion/03-CONTEXT.md
@.planning/phases/03-data-completion/03-VERIFICATION.md
@.planning/phases/03-data-completion/03-10-PLAN.md
@.planning/phases/03-data-completion/03-01-SUMMARY.md
@packages-user/data-common/src/save/system.ts
@packages-user/data-common/src/save/index.ts
@packages-user/data-state/src/core.ts
@packages-user/data-state/test/coreNode.test.ts
@packages-user/data-state/test/nodeTracer.test.ts
</context>
<tasks>
<task type="tracer">
<name>Task 1: CORR-03-02 remove the Phase-3 save adapter</name>
<gap_id>CORR-03-02</gap_id>
<files>packages-user/data-common/src/save/memory.ts, packages-user/data-common/src/save/index.ts</files>
<action>Delete the Phase-3 in-memory save implementation and remove only its save-barrel export. Do not add a replacement adapter, alter `ISaveSystem`, alter `SaveSystem`, move save into rendering, or change the existing compatibility initialization callback restored by Plan 03-10. Keep the ordinary save package exports and all existing save compression/registration behavior unchanged (D-18, D-23, D-28 are superseded here by user-authoritative requirement 2 for the save architecture).</action>
<verify>
<automated>if (Test-Path -LiteralPath 'packages-user/data-common/src/save/memory.ts') { throw 'Phase-3 save adapter still exists' }; $barrel = Get-Content -Raw 'packages-user/data-common/src/save/index.ts'; if ($barrel -match 'memory') { throw 'Save barrel still exports the Phase-3 adapter' }; if ((Get-Content -Raw 'packages-user/data-common/src/save/system.ts') -notmatch 'class SaveSystem') { throw 'Existing SaveSystem is missing' }; pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; const state = createCoreState(); if (state.saveSystem.constructor.name !== 'SaveSystem') throw new Error('CoreState is not using SaveSystem');"
</automated>
<fails_when>The adapter file or export remains, SaveSystem is missing/changed as the active implementation, CoreState construction does not select SaveSystem, or the source check exits non-zero.</fails_when>
</verify>
<done>The Phase-3 save adapter is removed and the existing SaveSystem remains the sole CoreState save implementation.</done>
</task>
<task type="auto">
<name>Task 2: CORR-03-02 align CoreState and tracer regressions with existing save behavior</name>
<gap_id>CORR-03-02</gap_id>
<files>packages-user/data-state/test/coreNode.test.ts, packages-user/data-state/test/nodeTracer.test.ts</files>
<action>Replace assertions that identify the removed adapter with assertions that two factory-created states have distinct save-system instances of the existing SaveSystem implementation, while retaining the independent hero/map/event-state checks and the no-browser-global construction check. Keep tests focused on construction and replay behavior; do not call save methods before the existing compatibility initialization path has configured the browser database, and do not add a test-only save substitute. Place the required Chinese coverage comment immediately before every `it` call (D-03, D-04, D-09, D-12, D-23; user-authoritative requirement 2).</action>
<verify>
<automated>$files = @('packages-user/data-state/test/coreNode.test.ts','packages-user/data-state/test/nodeTracer.test.ts'); foreach ($file in $files) { $text = Get-Content -Raw $file; if ($text -match 'MemorySaveSystem|save/memory') { throw "Removed save adapter referenced by $file" } }; pnpm exec vitest run packages-user/data-state/test/coreNode.test.ts packages-user/data-state/test/nodeTracer.test.ts; if ($?) { pnpm test:data-node }
</automated>
<fails_when>Either test references the removed adapter, a required Chinese test comment is missing, CoreState independence/tracer behavior fails, or the independent Node command exits non-zero.</fails_when>
</verify>
<done>Focused construction and replay tests prove independent states while preserving the existing SaveSystem and compatibility save architecture.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|---|---|
| CoreState -> SaveSystem | State registration reaches the existing persistence implementation. |
| compatibility loading -> database initialization | Browser-host loading controls when the existing database is initialized. |
| test fixture -> CoreState | Node tests construct state without introducing a second persistence boundary. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-26 | Tampering | save-system selection | high | mitigate | Remove the parallel adapter and assert CoreState selects the existing SaveSystem. |
| T-03-27 | Denial of service | uninitialized compatibility database | medium | mitigate | Keep tests on construction/tracer paths and leave database initialization to the existing host callback. |
| T-03-SC | Tampering | npm/pip/cargo installs | low | accept | No package installation is planned. |
</threat_model>
<verification>
- `pnpm exec vitest run packages-user/data-state/test/coreNode.test.ts packages-user/data-state/test/nodeTracer.test.ts`
- `pnpm test:data-node`
- Source checks confirm the existing SaveSystem is selected and no Phase-3 adapter is exported.
</verification>
<success_criteria>
CORR-03-02 is closed when the Phase-3 save adapter is absent, CoreState uses the existing SaveSystem and loading path, and focused Node construction/replay tests pass without a Node-specific persistence substitute.
</success_criteria>
<output>
Create `.planning/phases/03-data-completion/03-11-SUMMARY.md` when done
</output>

View File

@ -0,0 +1,130 @@
---
phase: 03-data-completion
plan: 12
type: execute
wave: 4
depends_on: [03-08]
files_modified:
- packages-user/data-state/src/event/map.ts
- packages-user/data-state/src/event/hero.ts
- packages-user/data-state/src/event/event.ts
- packages-user/data-state/src/event/index.ts
- packages-user/data-state/src/event/event.test.ts
autonomous: true
requirements: [DATA-01]
gap_closure: true
gap_ids: [CORR-03-03]
estimate:
tokens: 22000
raw_tokens: 22000
tasks: 2
confidence: low
must_haves:
truths:
- "CORR-03-03: data-state/src/event/index.ts contains only registration assembly and exports; behavior and parameter construction live in the event modules (user-authoritative requirement 3)."
- "Event invocation does not run a generic repeated runtime parameter-shape/type-check pipeline; handlers retain only their necessary target guards and compile-time parameter contracts."
- "The eight approved registrations remain stable and valid event behavior remains covered."
artifacts:
- path: "packages-user/data-state/src/event/index.ts"
provides: "Registration assembly and module exports only"
- path: "packages-user/data-state/src/event/map.ts"
provides: "Map event implementations and map registration construction"
- path: "packages-user/data-state/src/event/hero.ts"
provides: "Hero event implementations and hero registration construction"
- path: "packages-user/data-state/src/event/event.ts"
provides: "Event-control implementations and event registration construction"
- path: "packages-user/data-state/src/event/event.test.ts"
provides: "Real registration and valid/missing-target behavior regression coverage"
key_links:
- "module-owned registration builders -> index registration assembly -> GameEventSystem"
- "AnonTokyo typed built-in parameters -> event module handlers"
- "event handlers -> existing map/hero/event APIs with minimal safe target behavior"
---
<objective>
Close CORR-03-03 by moving event registration adapters and parameter construction out of the event barrel, leaving that barrel as exports/registration assembly only and removing the repeated generic runtime validation path.
Purpose: The event system is performance-sensitive and its implementation ownership must match the three event modules. This is a structural correction, not an expansion of the built-in catalog.
Output: Module-owned registration builders, a thin event barrel, and focused tests for the eight approved valid registrations and safe target behavior.
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
@.planning/phases/03-data-completion/03-CONTEXT.md
@.planning/phases/03-data-completion/03-VERIFICATION.md
@.planning/phases/03-data-completion/03-08-SUMMARY.md
@packages-user/data-state/src/event/index.ts
@packages-user/data-state/src/event/map.ts
@packages-user/data-state/src/event/hero.ts
@packages-user/data-state/src/event/event.ts
@packages-user/data-state/src/event/types.ts
@packages-user/data-state/src/event/event.test.ts
@packages-user/data-system/src/event/types.ts
@dev.md
</context>
<tasks>
<task type="tracer">
<name>Task 1: CORR-03-03 move registration construction into event modules</name>
<gap_id>CORR-03-03</gap_id>
<files>packages-user/data-state/src/event/map.ts, packages-user/data-state/src/event/hero.ts, packages-user/data-state/src/event/event.ts, packages-user/data-state/src/event/index.ts</files>
<action>Move each event group's `BuiltInFunction` construction and parameter adaptation beside its implementation: map registration entries in `map.ts`, hero entries in `hero.ts`, and event-control entries in `event.ts`. Preserve the existing eight names, parameter interfaces, `(param, env)` contract, and approved handler behavior. Make `index.ts` import the three module-owned registration collections, concatenate them in the existing stable order, and re-export the modules/types; it must not own parser helpers, environment-shape helpers, or implementation bodies. Remove the generic invocation-time object/property descriptor validation pipeline; rely on the typed AnonTokyo built-in contract and the handlers' existing target-existence/no-op safeguards, without adding another per-call validation layer (D-10, D-11, D-24, D-27; user-authoritative requirement 3).</action>
<verify>
<automated>$index = Get-Content -Raw 'packages-user/data-state/src/event/index.ts'; if ($index -match 'Object\.getOwnPropertyDescriptor|function parse|function read|isBuiltinParameter|isBlockEventEnv') { throw 'Event barrel still owns runtime parsing/validation helpers' }; if ($index -notmatch 'create.*Builtin') { throw 'Event barrel does not assemble module registrations' }; pnpm exec tsx -e "import { createEventBuiltinRegistrations } from './packages-user/data-state/src/event/index.ts'; const names = createEventBuiltinRegistrations().map(item => item.name); if (names.length !== 8) throw new Error('registration count drift');"
</automated>
<fails_when>The event barrel still contains parser/environment/runtime-validation helpers, module registration assembly is missing, or the real registration factory no longer returns exactly eight entries.</fails_when>
</verify>
<done>All event behavior and parameter construction are owned by event modules while the barrel only assembles the approved registration list and exports.</done>
</task>
<task type="auto">
<name>Task 2: CORR-03-03 update performance-sensitive event regression coverage</name>
<gap_id>CORR-03-03</gap_id>
<files>packages-user/data-state/src/event/event.test.ts</files>
<action>Update the event tests to exercise the real module-owned registration table for all eight approved names, valid parameter construction, awaited map/hero/event behavior, and missing target safe returns. Remove the broad nullish matrix whose purpose was to validate the rejected generic invocation guard; do not weaken the typed contracts or add a replacement repeated runtime-shape test. Keep a Chinese single-line coverage comment immediately before every `it` call and preserve the existing event dispatch regression invocation (D-01, D-03, D-04, D-11, D-24, D-27; user-authoritative requirement 3).</action>
<verify>
<automated>$lines = Get-Content -LiteralPath 'packages-user/data-state/src/event/event.test.ts'; for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*it(?:\.[A-Za-z]+)*\s*\(') { if ($i -eq 0 -or $lines[$i - 1] -notmatch '^\s*//\s*[\u4e00-\u9fff]') { throw "Missing Chinese coverage comment before it at line $($i + 1)" } } }; pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-system/src/event/eventDispatch.test.ts; if ($?) { pnpm exec prettier --check packages-user/data-state/src/event/index.ts packages-user/data-state/src/event/map.ts packages-user/data-state/src/event/hero.ts packages-user/data-state/src/event/event.ts packages-user/data-state/src/event/event.test.ts }
</automated>
<fails_when>A test lacks its required Chinese comment, any approved registration/valid handler/awaited behavior fails, the event dispatch regression fails, or formatting is not clean.</fails_when>
</verify>
<done>The event barrel is thin, module-owned registration behavior is covered through the real table, and no repeated generic runtime parameter validation remains in the invocation path.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|---|---|
| AnonTokyo built-in call -> event module | Script parameters reach the approved event handlers. |
| event environment -> map/hero state | Event code can address absent or invalid runtime targets. |
| registration assembly -> interpreter | Registration order and names determine callable built-ins. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-28 | Tampering | event registration assembly | medium | mitigate | Keep the eight-name order in one assembly function and test the real table. |
| T-03-29 | Denial of service | event target lookup | medium | mitigate | Retain handler-level target guards and safe no-op behavior without a repeated generic parser pipeline. |
| T-03-SC | Tampering | npm/pip/cargo installs | low | accept | No package installation is planned. |
</threat_model>
<verification>
- `pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-system/src/event/eventDispatch.test.ts`
- `pnpm exec prettier --check packages-user/data-state/src/event/index.ts packages-user/data-state/src/event/map.ts packages-user/data-state/src/event/hero.ts packages-user/data-state/src/event/event.ts packages-user/data-state/src/event/event.test.ts`
</verification>
<success_criteria>
CORR-03-03 is closed when event/index.ts is limited to registration assembly/exports, event modules own implementation and parameter construction, the generic per-invocation runtime validation pipeline is gone, and all approved event behavior remains green.
</success_criteria>
<output>
Create `.planning/phases/03-data-completion/03-12-SUMMARY.md` when done
</output>

View File

@ -0,0 +1,125 @@
---
phase: 03-data-completion
plan: 13
type: execute
wave: 5
depends_on: [03-12]
files_modified:
- packages-user/data-state/src/event/event.ts
- packages-user/data-state/src/event/types.ts
- packages-user/data-state/src/event/event.test.ts
- .planning/phases/03-data-completion/03-EVENT-CONTRACT.md
autonomous: true
requirements: [DATA-01]
gap_closure: true
gap_ids: [CORR-03-04]
estimate:
tokens: 18000
raw_tokens: 18000
tasks: 2
confidence: low
must_haves:
truths:
- "CORR-03-04: eventInsertEvent accepts a Statement[] body and executes that body directly through the existing interpreter (user-authoritative requirement 4)."
- "eventInsertEvent does not resolve an event id, read the event store, or delegate to the id-sequence helper."
- "eventInsertEvents retains its existing id-sequence contract, while the single-body path has explicit direct-execution regression coverage."
artifacts:
- path: "packages-user/data-state/src/event/event.ts"
provides: "Direct Statement[] event-body execution"
- path: "packages-user/data-state/src/event/types.ts"
provides: "Statement[] parameter contract for eventInsertEvent"
- path: "packages-user/data-state/src/event/event.test.ts"
provides: "Direct-body execution and no-id-resolution regression coverage"
- path: ".planning/phases/03-data-completion/03-EVENT-CONTRACT.md"
provides: "Corrected single-event body contract"
key_links:
- "Statement[] built-in parameter -> eventInsertEvent -> existing AnonTokyo interpreter"
- "direct body -> eventSetBlock registration -> map mutation"
- "eventInsertEvents ids -> existing event-store sequence path"
---
<objective>
Close CORR-03-04 by changing the single temporary event operation from an event-id wrapper to direct execution of a Statement[] body, while preserving the existing multi-id operation.
Purpose: The user-owned event contract requires a single event body to be executable without an event-store lookup. This is a focused contract correction, not a second event model.
Output: Updated event parameter contract, direct interpreter execution, corrected contract record, and focused tests.
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
@.planning/phases/03-data-completion/03-CONTEXT.md
@.planning/phases/03-data-completion/03-VERIFICATION.md
@.planning/phases/03-data-completion/03-12-PLAN.md
@.planning/phases/03-data-completion/03-EVENT-CONTRACT.md
@packages-user/data-state/src/event/event.ts
@packages-user/data-state/src/event/types.ts
@packages-user/data-state/src/event/event.test.ts
@packages-user/data-common/src/event/event.ts
@packages-user/data-system/src/event/types.ts
</context>
<tasks>
<task type="tracer">
<name>Task 1: CORR-03-04 execute a Statement[] body directly</name>
<gap_id>CORR-03-04</gap_id>
<files>packages-user/data-state/src/event/event.ts, packages-user/data-state/src/event/types.ts</files>
<action>Replace the single-event parameter contract with the existing `anon-tokyo` `Statement[]` type. Implement `eventInsertEvent(param, env)` by sending that body directly to the already assembled event interpreter with the existing block-event parameter/environment shape, awaiting the interpreter Promise and retaining the existing bounded nested-insertion safety guard. Do not call `eventInsertEvents`, do not resolve an id from `IGameEventStore`, and do not create a persistent event or new public event system. Keep `eventInsertEvents` unchanged as the existing ordered id-based operation (D-11, D-24, D-27; user-authoritative requirement 4).</action>
<verify>
<automated>$event = Get-Content -Raw 'packages-user/data-state/src/event/event.ts'; if ($event -match 'eventInsertEvent[\s\S]*eventInsertEvents\(') { throw 'Single-body event path delegates to id sequence' }; if ($event -match 'eventInsertEvent[\s\S]*store\.getEvent') { throw 'Single-body event path resolves an event id' }; if ((Get-Content -Raw 'packages-user/data-state/src/event/types.ts') -notmatch "from 'anon-tokyo'") { throw 'Statement type contract is missing' }; pnpm exec prettier --check packages-user/data-state/src/event/event.ts packages-user/data-state/src/event/types.ts
</automated>
<fails_when>The single-event implementation still delegates to the id helper or event store, the Statement[] contract is absent, or the data-state type check fails.</fails_when>
</verify>
<done>eventInsertEvent receives and directly awaits a Statement[] body through the existing interpreter without event-id resolution.</done>
</task>
<task type="auto">
<name>Task 2: CORR-03-04 prove direct-body execution and preserve id sequences</name>
<gap_id>CORR-03-04</gap_id>
<files>packages-user/data-state/src/event/event.test.ts, .planning/phases/03-data-completion/03-EVENT-CONTRACT.md</files>
<action>Update the contract record so `eventInsertEvent` documents a Statement[] body, direct interpreter execution, awaited completion, and the absence of id lookup. Replace the old single-id test call with a statement-body fixture that invokes an approved built-in and mutates the event layer; use a body whose execution would fail or remain unobserved if the event store were consulted, and assert the mutation occurs without registering that body under an id. Retain the existing `eventInsertEvents` ordered-id assertions and required Chinese coverage comments (D-01, D-03, D-04, D-11, D-24, D-27; user-authoritative requirement 4).</action>
<verify>
<automated>$contract = Get-Content -Raw '.planning/phases/03-data-completion/03-EVENT-CONTRACT.md'; if ($contract -notmatch 'Statement\[\]') { throw 'Event contract does not document Statement[]' }; if ($contract -match 'eventInsertEvent[\s\S]*id 对应|single id|单 id') { throw 'Event contract still describes eventInsertEvent as id-based' }; pnpm exec vitest run packages-user/data-state/src/event/event.test.ts; if ($?) { pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-system/src/event/eventDispatch.test.ts }
</automated>
<fails_when>The contract remains id-based, direct Statement[] execution does not mutate through the registered built-in, the body still requires an event-store id, or focused event tests fail.</fails_when>
</verify>
<done>Tests and contract prove that eventInsertEvent executes a Statement[] body directly while eventInsertEvents continues to resolve its explicit id list.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|---|---|
| Statement[] body -> AnonTokyo interpreter | A temporary event body becomes executable event logic. |
| interpreter -> approved built-ins | The body can request only the registered event operations. |
| id-sequence helper -> event store | The separate multi-id path resolves only its explicit stored ids. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-30 | Code execution | direct temporary Statement[] body | high | mitigate | Reuse the existing interpreter and bounded nested insertion guard; do not introduce dynamic compilation or a second executor. |
| T-03-31 | Tampering | accidental id resolution in single-body path | medium | mitigate | Add direct mutation coverage and source-level checks that distinguish the two operations. |
| T-03-SC | Tampering | npm/pip/cargo installs | low | accept | No package installation is planned. |
</threat_model>
<verification>
- `pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-system/src/event/eventDispatch.test.ts`
- `pnpm exec tsc --noEmit --pretty false --project packages-user/data-state/tsconfig.json`
</verification>
<success_criteria>
CORR-03-04 is closed when eventInsertEvent accepts Statement[] and directly executes it through the existing interpreter, while eventInsertEvents remains the only id-list operation and all focused event tests pass.
</success_criteria>
<output>
Create `.planning/phases/03-data-completion/03-13-SUMMARY.md` when done
</output>

View File

@ -0,0 +1,129 @@
---
phase: 03-data-completion
plan: 14
type: execute
wave: 5
depends_on: [03-09]
files_modified:
- packages-user/data-common/src/replay/func.ts
- packages-user/data-state/src/replay/commands.ts
- packages-user/data-state/src/replay/commands.test.ts
- .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md
autonomous: true
requirements: [DATA-01]
gap_closure: true
gap_ids: [CORR-03-05]
estimate:
tokens: 24000
raw_tokens: 24000
tasks: 2
confidence: low
must_haves:
truths:
- "CORR-03-05: New replay command handling is synchronous and minimally changes the existing replay system (user-authoritative requirement 5)."
- "Replay commands do not await movement/equipment Promises or add asynchronous command sequencing; the existing replay command/sandbox interfaces remain unchanged."
- "Replay-safety context is restored synchronously at the decorated method boundary; no asynchronous restoration mechanism is added."
- "No @shouldReplay() placement is changed on HeroAttribute.set, HeroAttribute.mul, or any other user-owned state method."
artifacts:
- path: "packages-user/data-state/src/replay/commands.ts"
provides: "Synchronous state-changing command bodies behind the existing replay command interface"
- path: "packages-user/data-common/src/replay/func.ts"
provides: "Existing synchronous replay-safety context restoration"
- path: "packages-user/data-state/src/replay/commands.test.ts"
provides: "Synchronous command and safety-boundary regression coverage"
- path: ".planning/phases/03-data-completion/03-REPLAY-CONTRACT.md"
provides: "Corrected synchronous replay contract"
key_links:
- "existing ReplaySandbox -> existing IReplayCommand interface -> synchronous command action"
- "shouldReplay wrapper -> immediate context restoration"
- "production command registry -> state APIs without added async sequencing"
---
<objective>
Close CORR-03-05 by removing the Phase-3 asynchronous command and replay-safety lifecycle additions, restoring synchronous command bodies while preserving the existing replay system interfaces.
Purpose: The structural review requires the replay correction to be minimal. This plan does not move decorator placement onto user-owned state methods and does not redesign ReplaySandbox or the public command contract.
Output: Synchronous command implementations, synchronous replay-safety restoration, corrected replay contract documentation, and focused tests.
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
@.planning/phases/03-data-completion/03-CONTEXT.md
@.planning/phases/03-data-completion/03-VERIFICATION.md
@.planning/phases/03-data-completion/03-09-SUMMARY.md
@.planning/phases/03-data-completion/03-REPLAY-CONTRACT.md
@packages-user/data-common/src/replay/types.ts
@packages-user/data-common/src/replay/func.ts
@packages-user/data-common/src/replay/sandbox.ts
@packages-user/data-state/src/replay/commands.ts
@packages-user/data-state/src/replay/commands.test.ts
@packages-user/data-base/src/hero/attribute.ts
</context>
<tasks>
<task type="tracer">
<name>Task 1: CORR-03-05 restore synchronous replay command and decorator behavior</name>
<gap_id>CORR-03-05</gap_id>
<files>packages-user/data-common/src/replay/func.ts, packages-user/data-state/src/replay/commands.ts, .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md</files>
<action>Remove the Phase-3 Promise-settlement branch from the replay-safety decorator so the existing collection context is restored immediately after the wrapped method returns. Remove `async`/`await` command bodies and controller waiting from the new data-state command implementations; invoke the existing state APIs synchronously and adapt their immediate results only as required by the already-existing `IReplayCommand.execute(): Promise<boolean>` boundary, without changing that interface or `ReplaySandbox`. Keep stable command codes/order, parameter validation, failure booleans, and production registry ownership. Update the replay contract to state the synchronous correction. Explicitly do not touch decorator placement on `HeroAttribute.set`, `HeroAttribute.mul`, or any other user-owned state method; this plan implements user-authoritative requirement 5 only (D-05, D-07, D-08, D-25).</action>
<verify>
<automated>if ((Get-Content -Raw 'packages-user/data-common/src/replay/func.ts') -match 'isPromiseLike|Promise\.resolve\(result\)') { throw 'Replay safety still has asynchronous restoration logic' }; $commands = Get-Content -Raw 'packages-user/data-state/src/replay/commands.ts'; if ($commands -match 'async (moveHero|moveToPoint|useItem|equip|unequip)|await controller\.onEnd') { throw 'Replay command bodies still add asynchronous execution' }; if ((Get-Content -Raw 'packages-user/data-base/src/hero/attribute.ts') -match 'shouldReplay') { throw 'User-owned HeroAttribute decorator placement was modified' }; pnpm exec eslint packages-user/data-common/src/replay/func.ts packages-user/data-state/src/replay/commands.ts; if ($?) { pnpm exec prettier --check packages-user/data-common/src/replay/func.ts packages-user/data-state/src/replay/commands.ts }
</automated>
<fails_when>Promise-aware safety restoration or awaited command bodies remain, existing replay interfaces are changed, user-owned state-method decorator placement is modified, or lint/format checks fail.</fails_when>
</verify>
<done>Replay command bodies and safety context handling are synchronous, the existing public replay interfaces remain intact, and user-owned decorator placement is untouched.</done>
</task>
<task type="auto">
<name>Task 2: CORR-03-05 replace async replay assertions with synchronous regressions</name>
<gap_id>CORR-03-05</gap_id>
<files>packages-user/data-state/src/replay/commands.test.ts</files>
<action>Rewrite replay command tests to assert immediate state API invocation and immediate command completion behavior under the existing command interface, rather than deferred controller settlement. Replace the Promise-lifecycle safety test with a synchronous nested-collection assertion that the context is restored when the decorated method returns. Preserve stable eight-command order, invalid-parameter false results, production registry calls, pure-path exclusion, and Chinese coverage comments. Add an explicit test guard that the correction does not alter any user-owned state method decorator placement, and do not add tests or code for the deferred user-owned placement follow-up (D-01, D-03, D-04, D-05, D-07, D-08; user-authoritative requirements 5 and 6).</action>
<verify>
<automated>$lines = Get-Content -LiteralPath 'packages-user/data-state/src/replay/commands.test.ts'; for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*it(?:\.[A-Za-z]+)*\s*\(') { if ($i -eq 0 -or $lines[$i - 1] -notmatch '^\s*//\s*[\u4e00-\u9fff]') { throw "Missing Chinese coverage comment before it at line $($i + 1)" } } }; pnpm exec vitest run packages-user/data-state/src/replay/commands.test.ts; if ($?) { pnpm test:data-node }
</automated>
<fails_when>Tests still require deferred controller completion, asynchronous safety restoration is observed, stable registry/pure-path behavior regresses, a Chinese test comment is missing, or Node replay exits non-zero.</fails_when>
</verify>
<done>Replay tests prove synchronous command handling and immediate safety-context restoration without changing the existing replay system or user-owned decorator placement.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|---|---|
| replay route -> command implementation | Encoded route parameters select state-changing instructions. |
| command implementation -> existing state API | Commands invoke movement/item/equipment APIs without adding a new async scheduler. |
| decorator -> safety collection | Collection context must not leak across synchronous command calls. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-32 | Denial of service | replay command sequencing | medium | mitigate | Remove added asynchronous waits and preserve the existing sandbox/interface boundary. |
| T-03-33 | Tampering | replay-safety context | medium | mitigate | Restore context immediately and test nested synchronous calls plus pure-path exclusion. |
| T-03-34 | Tampering | user-owned decorator methods | high | mitigate | Scope files to replay implementation/tests and assert HeroAttribute state-method placement is untouched. |
| T-03-SC | Tampering | npm/pip/cargo installs | low | accept | No package installation is planned. |
</threat_model>
<verification>
- `pnpm exec vitest run packages-user/data-state/src/replay/commands.test.ts`
- `pnpm test:data-node`
- `pnpm exec eslint packages-user/data-common/src/replay/func.ts packages-user/data-state/src/replay/commands.ts`
</verification>
<success_criteria>
CORR-03-05 is closed when command bodies and replay-safety restoration are synchronous, the existing replay interfaces remain unchanged, the data replay tests pass, and no user-owned state-method decorator placement is modified.
</success_criteria>
<output>
Create `.planning/phases/03-data-completion/03-14-SUMMARY.md` when done
</output>

View File

@ -0,0 +1,123 @@
---
phase: 03-data-completion
plan: 15
type: execute
wave: 6
depends_on: [03-14]
files_modified:
- packages-user/data-state/src/replay/commands.ts
- packages-user/data-state/src/replay/commands.test.ts
- .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md
autonomous: true
requirements: [DATA-01]
gap_closure: true
gap_ids: [CORR-03-06]
estimate:
tokens: 20000
raw_tokens: 20000
tasks: 2
confidence: low
must_haves:
truths:
- "CORR-03-06: Each replay instruction is an independent class in one replay command file, with no shared command-entrance object or inter-command dependency (user-authoritative requirement 6)."
- "Stable command registration order and the existing IReplayCommand/IReplayCommandItem extension points remain intact."
- "The synchronous command behavior and explicit exclusion of user-owned decorator placement from Plan 03-14 remain true."
artifacts:
- path: "packages-user/data-state/src/replay/commands.ts"
provides: "Eight independent replay instruction classes and stable top-level registration"
- path: "packages-user/data-state/src/replay/commands.test.ts"
provides: "Independent-class, order, behavior, and extensibility regression coverage"
- path: ".planning/phases/03-data-completion/03-REPLAY-CONTRACT.md"
provides: "Replay ownership note describing independent command classes"
key_links:
- "each command class -> its own existing state API"
- "top-level stable enum order -> one command instance per registration entry"
- "IReplayCommandItem -> existing user-extensible registration boundary"
---
<objective>
Close CORR-03-06 by replacing the shared replay command-entrance object and closures with independent instruction classes in the existing replay command file, while preserving stable registration order and existing extension interfaces.
Purpose: Each replay instruction must have an isolated implementation boundary so commands do not depend on shared mutable entrances or one another. This is a local ownership refactor, not a new replay API.
Output: Independent command classes, stable registration assembly, corrected contract note, and regression coverage for class isolation/order/extensibility.
</objective>
<execution_context>
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
@.planning/phases/03-data-completion/03-CONTEXT.md
@.planning/phases/03-data-completion/03-VERIFICATION.md
@.planning/phases/03-data-completion/03-14-PLAN.md
@.planning/phases/03-data-completion/03-REPLAY-CONTRACT.md
@packages-user/data-state/src/replay/commands.ts
@packages-user/data-state/src/replay/types.ts
@packages-user/data-state/src/replay/commands.test.ts
@packages-user/data-common/src/replay/system.ts
</context>
<tasks>
<task type="tracer">
<name>Task 1: CORR-03-06 split replay instructions into independent classes</name>
<gap_id>CORR-03-06</gap_id>
<files>packages-user/data-state/src/replay/commands.ts, .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md</files>
<action>Replace the shared command-entrance object and closure factory with one independent class per approved instruction in `commands.ts`: up, right, down, left, auto-pathfind-to-point, use-item, equip, and unequip. Each class must implement the existing `IReplayCommand` behavior, own only its needed state reference, perform its own parameter validation/action call, and never call another instruction class or rely on shared mutable entrance state. Keep pure validation helpers only when they do not create inter-command coupling. Make `createReplayCommandItems()` instantiate one independent class per stable enum entry in the existing order, keep `registerReplayCommandItems()` and the `IReplayCommandItem` extension point unchanged, and document the ownership correction. Do not alter the synchronous policy from Plan 03-14 or any user-owned `@shouldReplay()` placement (D-08, D-09, D-10, D-25; user-authoritative requirements 5 and 6).</action>
<verify>
<automated>$commands = Get-Content -Raw 'packages-user/data-state/src/replay/commands.ts'; if ($commands -match 'ReplayCommandEntrances|createMoveCommand') { throw 'Shared replay entrance/factory remains' }; pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; import { createReplayCommandItems } from './packages-user/data-state/src/replay/commands.ts'; const items = createReplayCommandItems(createCoreState()); const names = items.map(item => item.command.constructor.name); if (new Set(names).size !== 8) throw new Error('replay instructions are not independent classes');"
</automated>
<fails_when>The shared entrance object/factory remains, fewer than eight distinct command classes are registered, command classes depend on one another, or the structural probe exits non-zero.</fails_when>
</verify>
<done>All eight replay instructions are independent classes in one command file and the stable registry still creates one class instance per ordered code.</done>
</task>
<task type="auto">
<name>Task 2: CORR-03-06 verify isolation, order, and user extensibility</name>
<gap_id>CORR-03-06</gap_id>
<files>packages-user/data-state/src/replay/commands.test.ts, .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md</files>
<action>Add regression coverage that checks eight distinct command constructors, stable enum order, independent CoreState-bound command instances, preserved invalid-parameter false results, and registration of an existing custom `IReplayCommandItem` through the current registry interface. Retain synchronous behavior assertions from Plan 03-14, required Chinese comments, and the explicit non-scope assertion that no user-owned state-method decorator placement is changed. Do not reintroduce a shared entrance object, cross-command delegation, a second code owner, or a new public registry interface (D-01, D-03, D-04, D-08, D-09, D-25; user-authoritative requirements 5 and 6).</action>
<verify>
<automated>$lines = Get-Content -LiteralPath 'packages-user/data-state/src/replay/commands.test.ts'; for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*it(?:\.[A-Za-z]+)*\s*\(') { if ($i -eq 0 -or $lines[$i - 1] -notmatch '^\s*//\s*[\u4e00-\u9fff]') { throw "Missing Chinese coverage comment before it at line $($i + 1)" } } }; pnpm exec vitest run packages-user/data-state/src/replay/commands.test.ts; if ($?) { pnpm test:ci packages-user/data-common packages-user/data-base packages-user/data-system packages-user/data-state }; if ($?) { pnpm test:data-node }
</automated>
<fails_when>Class-isolation/order/extensibility tests or existing synchronous replay tests fail, a required Chinese comment is missing, the full data suite or Node replay gate fails, or user-owned decorator placement changes.</fails_when>
</verify>
<done>Independent command classes, stable registration order, existing extension interfaces, synchronous behavior, and user-owned decorator boundaries are all regression-tested.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|---|---|
| replay code/params -> independent command class | Route input selects one instruction implementation. |
| command class -> CoreState action | Each instruction reaches only its own existing state API. |
| custom command item -> ReplaySystem registry | Existing user-extensible command registration remains available. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-35 | Tampering | command ownership/order | high | mitigate | Instantiate eight distinct classes from the stable enum order and reject shared entrance coupling with structural tests. |
| T-03-36 | Tampering | custom command registration | medium | mitigate | Reuse the existing command-item/registry interfaces and test a custom item without creating a second registry. |
| T-03-SC | Tampering | npm/pip/cargo installs | low | accept | No package installation is planned. |
</threat_model>
<verification>
- `pnpm exec vitest run packages-user/data-state/src/replay/commands.test.ts`
- `pnpm test:ci packages-user/data-common packages-user/data-base packages-user/data-system packages-user/data-state`
- `pnpm test:data-node`
- Structural probe confirms eight distinct command constructors and no shared entrance object.
</verification>
<success_criteria>
CORR-03-06 is closed when each approved replay instruction is an independent class in commands.ts, stable registration and user extensibility remain intact, and all replay/data gates pass without touching user-owned decorator placement.
</success_criteria>
<output>
Create `.planning/phases/03-data-completion/03-15-SUMMARY.md` when done
</output>