fix: complete dynamic engine cleanup

This commit is contained in:
Jesse_Chen
2026-07-19 02:08:42 +08:00
parent f90a3bd1cb
commit 437d50fe1e
6 changed files with 240 additions and 238 deletions
+57 -76
View File
@@ -1,103 +1,84 @@
# Task 3 — TypeScript Engine Adapter and Trust Boundary
## Implementation
## Final design
- Added snake-case serializers for dynamic opportunity and deterministic choice-scoring requests. Only server-resolved `ServerChoiceEvidence` becomes `partition_id` / `candidate_scores`; client option IDs and confidence fields are never serialized.
- Added strict dynamic response adapters. Opportunity responses are versioned, endpoint-bound, exact-field parsed, and split into a model-safe `packet`, private `candidateModel`, and private `scoringPartitions`.
- Candidate score maps must contain finite nonnegative values keyed by exactly every minute in the submitted range, including cross-midnight ranges. Duplicate opportunity/partition IDs and unexpected response fields are rejected.
- Dynamic scoring responses require `birth-time-choice-scoring-v2`, `dynamic_choice` evidence mode, empty public evidence, matching compatibility/effective counts, and the existing deterministic candidate safety gates.
- Added authenticated server-only engine calls for both dynamic Python endpoints. They fail before fetch when `JYOTISH_DYNAMIC_RECTIFICATION_TOKEN` is absent and send the configured value only as a bearer header. Legacy scan, questionnaire score, and dated-event score calls remain unauthenticated.
- Extended the legacy-compatible engine contract with optional dynamic methods and added `DynamicBirthTimeJourneyEngine`, where both methods are required. The production factory returns the required dynamic subtype while existing legacy-only test doubles remain source-compatible.
- Raised only the shared candidate-result compatibility cap from 6 to 10 and changed the high-gate message to “effective evidence items.” The dated-event request contract remains capped at 6.
- Added source-contract coverage preventing snake-case or camel-case candidate model, partition, score, and token identifiers from entering client, request, response, component, or hook modules.
- `BirthTimeJourneyEngine` requires both dynamic operations: `buildDifferencePacket` and `scoreChoices`. Existing scan/score consumers depend on the explicit `LegacyBirthTimeJourneyEngine` pick instead of weakening the primary interface.
- The server-only engine factory owns `JYOTISH_DYNAMIC_RECTIFICATION_TOKEN`. Both dynamic endpoints use an authenticated POST and a 45-second abort signal; all three legacy endpoints remain unauthenticated.
- Request serializers expose only server-resolved choice evidence. Client option IDs, confidence, applicability, and model-controlled safety gates are never sent as scoring authority.
- Dynamic v2 responses have a dedicated strict adapter. Root objects, ranges, opportunities, partitions, winning segments, score-map keys, counts, versions, modes, and duplicate identifiers are validated before mapping.
- Public difference packets omit private candidate score vectors. Private scoring partitions retain the exact server vector used by the later deterministic scoring call.
- Legacy response parsing remains compatibility-oriented: unknown server metadata is accepted and stripped. Only the shared result representation supports up to ten effective items; the legacy dated-event request remains capped at six.
- The HTTP wire accepts injected fetch and timeout-signal factories for executable contract tests. Production still defaults to `AbortSignal.timeout`.
## Files changed
Production:
- `frontend/src/lib/birth-time-evidence.ts`
- `frontend/src/lib/birth-time-journey-service.ts`
- `frontend/src/lib/birth-time-journey-engine.ts`
- `frontend/src/lib/birth-time-journey-adapters.ts`
- `frontend/src/lib/birth-time-journey-engine-model.ts`
- `frontend/src/lib/birth-time-evidence.ts`
- `frontend/src/lib/birth-time-journey-adapters.ts`
- `frontend/src/lib/birth-time-journey-dynamic-adapters.ts`
- `frontend/src/lib/birth-time-journey-assessment.ts`
Tests and support:
- `frontend/tests/birth-time-journey-engine.test.ts`
- `frontend/tests/birth-time-journey-adapters.test.ts`
- `frontend/tests/birth-time-journey-dynamic-adapters.test.ts`
- `frontend/tests/birth-time-journey-memory-store.ts`
- `frontend/tests/birth-time-journey-test-support.ts`
- `frontend/tests/birth-time-journey-service.test.ts`
- `frontend/tests/birth-time-agent-flow-test-support.ts`
Documentation:
- `docs/superpowers/plans/2026-07-18-dynamic-choice-birth-time-rectification.md`
- `.superpowers/sdd/task-3-report.md`
## RED
## RED evidence
1. Bundled Node focused run:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time-journey-engine.test.ts tests/birth-time-journey-adapters.test.ts`
- Failed during module instantiation because `parseCandidateDifferenceBuild` and `parseDynamicChoiceScoring` did not exist. Baseline result: 1 pass, 1 file-load failure.
2. After the first adapter implementation, the malformed score-key regression failed because a response containing `candidate_scores: { "not-a-time": 1 }` was accepted.
3. The exact-range regression then failed because an otherwise valid `05:34` score key could be added outside the `05:30—05:33` range.
4. The initial source-level legacy-auth assertion was too broad and matched the next dynamic method. It was narrowed to the exact legacy method section; production behavior did not change for this test-only correction.
1. The initial focused run failed to load because the dynamic response parsers did not exist.
2. Adapter regressions then exposed acceptance of malformed score keys, keys outside the submitted range, duplicate opportunity/partition identifiers, nested extra fields, and legacy evidence metadata incompatibility.
3. Interface and wire review probes exposed optional primary dynamic methods, source-regex authentication assertions, and a missing executable proof for exact URLs, bodies, authorization, and timeout behavior.
4. The final wire cleanup test injected a timeout factory and failed with `[]` instead of `[45000, 45000]`, proving that the seam was initially ignored.
## GREEN
## Final verification
1. Focused Task 3 suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time-journey-engine.test.ts tests/birth-time-journey-adapters.test.ts`
- 21 passed, 0 failed.
2. Complete birth-time frontend regression suite:
1. Focused adapter, wire, and evidence suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time-journey-engine.test.ts tests/birth-time-journey-adapters.test.ts tests/birth-time-journey-dynamic-adapters.test.ts tests/birth-time-evidence.test.ts`
- 29 passed, 0 failed.
2. Complete birth-time frontend suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time*.test.ts`
- 207 passed, 0 failed.
3. Focused ESLint over all Task 3 source and test files:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node node_modules/eslint/bin/eslint.js ...`
- 208 passed, 0 failed.
3. Full frontend suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/*.test.ts`
- 283 passed, 0 failed.
4. ESLint across every changed production/test TypeScript module:
- Passed with no diagnostics.
4. TypeScript diagnostic:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node node_modules/typescript/bin/tsc --noEmit --pretty false`
- No Task 3 diagnostics. It reproduces only the existing baseline `TS1501` in `tests/profile-persistence.test.ts:7` because the project targets ES2017 while that test uses an ES2018 regex flag.
5. `git diff --check`
5. TypeScript diagnostic:
- No Task 3 diagnostics. The only result is the known baseline `tests/profile-persistence.test.ts:7 TS1501`, caused by an ES2018 regex flag under the project's ES2017 target.
6. Pure-LOC audit:
- Every changed TypeScript file is at or below 250 pure LOC. The largest is `frontend/src/lib/birth-time-journey-service.ts` at 239; the split test-support modules are 171 and 111.
7. `git diff --check`:
- Passed with no whitespace errors.
## Pre-work gate
- Ran `/Users/jesse/Downloads/Copse/astrology/yinduzhanxing/.venv/bin/python scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` after reading both required sweep documents and the error ledger.
- The gate remained red only on the unrelated known fragment-governance baseline: `candidate_count` expected `0`, observed `1`. Remote visibility was blocked, so no cloud-sync claim is made.
- `/Users/jesse/Downloads/Copse/astrology/yinduzhanxing/.venv/bin/python scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` remained red only on the unrelated known fragment-governance baseline: `candidate_count` expected `0`, observed `1`.
- Remote visibility was blocked, so no cloud-sync claim is made.
## Self-review
- Dynamic secrets and score vectors stay behind the existing server-only engine entrypoint. The public dynamic packet deliberately drops `candidateScores`, while the private partition map retains the exact server vector for later binding.
- Both response adapters are strict at their dynamic endpoint roots; model-controlled extra fields cannot be silently retained.
- The dynamic scorer cannot elevate confidence by mismatching effective counts, dimensions, evidence mode, evidence contents, algorithm version, or the existing high-confidence candidate gates.
- Cross-midnight ranges enumerate minutes modulo 24 hours, preventing a valid `23:59—00:00` result from being rejected or split.
- Legacy request payloads, endpoints, timeout, parsing behavior, and authentication remain unchanged. The event request schema remains at 6 even though the shared result compatibility schema can represent the v2 cap of 10.
- No dependency, logging, model prompt field, client response field, or persistence write was added.
- Dynamic secrets and candidate score vectors remain behind the server boundary.
- Wire tests use independent literal request bodies rather than production serializers and directly assert two `45_000` timeout calls and the exact injected signals.
- Missing-token tests prove both dynamic operations fail before fetch. Executable legacy tests prove no Authorization header reaches any legacy endpoint.
- Dynamic parsing is fail-closed; legacy parsing preserves its prior accept-and-strip behavior.
- Cross-midnight ranges enumerate minutes modulo 24 hours and bind score keys to the exact submitted interval.
- The extracted memory store has no dependency on the fixture module, so its re-export does not create a runtime cycle.
- No dependency, logging field, client response field, or persistence write was added.
## Concerns
## Known unrelated baseline
- The repository still lacks a standalone runtime `server-only` package for direct Node imports. Per the approved module-boundary decision, verification uses the production file's existing `import "server-only"` plus strict projection and source-contract tests rather than adding an unavailable dependency.
- The complete TypeScript diagnostic remains blocked by the unrelated ES2017/ES2018 regex baseline described above.
## Review fixes
- Restored byte-compatible legacy candidate parsing. Unknown nested evidence metadata is accepted and stripped exactly as before Task 3; dynamic response parsing no longer changes the legacy schema.
- Moved every v2 response schema, invariant, and mapping into `birth-time-journey-dynamic-adapters.ts`. Root responses, ranges, opportunities, partitions, and winning segments are strict; empty dynamic evidence is enforced before candidate construction.
- Split dynamic adapter regressions into `birth-time-journey-dynamic-adapters.test.ts`. Added duplicate opportunity/partition attacks, nested extra-field attacks, exact-range keys, cross-midnight mapping, and independent expected-value assertions.
- Made `buildDifferencePacket` and `scoreChoices` required on the primary `BirthTimeJourneyEngine`. Existing services use the explicit `LegacyBirthTimeJourneyEngine` pick, and legacy-only test doubles were narrowed without runtime behavior changes.
- Refactored HTTP execution into `createJourneyEngineWire`, whose `post` accepts one typed request object. The server-only factory remains the environment owner; engine assembly is injectable for executable fake-fetch verification without adding the unavailable `server-only` runtime dependency.
- Replaced greedy source-regex authentication tests with executable coverage for both exact dynamic URLs, serialized bodies, POST method, bearer header, 45-second abort signal, and missing-token zero-fetch behavior. All three legacy engine endpoints execute without an Authorization header.
- Removed newly introduced non-null assertions and narrowed the ownership scan to the client/request/component/hook boundary named by the plan.
- Kept every modified production and test TypeScript module at or below 250 pure LOC. The largest is `birth-time-journey-test-support.ts` at 249; Task 3 production modules range from 19 to 239.
### Review RED
1. The legacy compatibility regression failed with `unrecognized_keys` when an existing engine evidence item contained extra server metadata.
2. The new dynamic adapter suite failed to load because the protocol-specific module did not yet exist.
3. Independent review probes had shown nested dynamic `winning_segment` extras were silently stripped, optional primary engine methods weakened consumers, and the source-regex auth proof could remain green after removing authentication from one endpoint.
### Review GREEN
1. Focused legacy/dynamic/wire suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time-journey-engine.test.ts tests/birth-time-journey-adapters.test.ts tests/birth-time-journey-dynamic-adapters.test.ts`
- 24 passed, 0 failed.
2. Complete birth-time suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time*.test.ts`
- 210 passed, 0 failed.
3. Full frontend suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/*.test.ts`
- 285 passed, 0 failed.
4. Focused ESLint across all changed production/test TypeScript modules:
- Passed with no diagnostics.
5. TypeScript diagnostic:
- No Task 3 diagnostics; only the known `tests/profile-persistence.test.ts:7 TS1501` baseline remains.
6. Pure-LOC audit and `git diff --check`:
- Every changed TypeScript file is at or below 250 pure LOC; no whitespace errors.
- A clean TypeScript run is still blocked by `tests/profile-persistence.test.ts:7 TS1501`; Task 3 introduces no additional diagnostic.