docs: centralize dynamic stop decisions

This commit is contained in:
Jesse_Chen
2026-07-19 00:13:00 +08:00
parent cc74caf9da
commit ab0351ad0f
@@ -264,6 +264,8 @@ test("unknown answers do not advance plateau or the effective safety count", ()
});
test("terminal conditions are deterministic", () => {
assert.equal(decisionFor({ result: null, forcedReason: "user_finished" }).reason, "user_finished");
assert.equal(decisionFor({ result: null, forcedReason: "generation_unavailable" }).reason, "generation_unavailable");
assert.equal(decisionFor({ confidence: "high" }).reason, "high_confidence");
assert.equal(decisionFor({ usefulOpportunityCount: 0 }).reason, "no_information_gain");
assert.equal(decisionFor({ repeatedOnly: true }).reason, "repeated_partition");
@@ -279,14 +281,15 @@ Expected: FAIL because `decideDynamicStop` does not exist.
- [ ] **Step 6: Implement deterministic stop ordering**
Use this decision order so a high-confidence result cannot be hidden by a secondary reason:
`DynamicStopInput.result` is nullable so generation can stop safely before a first score. Add `forcedReason: "user_finished" | "generation_unavailable" | null`; these explicit terminal events are checked before score-derived conditions. Use this decision order:
```ts
export function decideDynamicStop(input: DynamicStopInput): DynamicStopDecision {
const plateauCount = input.effectiveAnswer
const plateauCount = input.effectiveAnswer && input.result
? materiallyChanged(input.previousResult, input.result) ? 0 : input.priorPlateauCount + 1
: input.priorPlateauCount;
if (input.result.confidence === "high") return { kind: "finish", reason: "high_confidence", plateauCount };
if (input.forcedReason) return { kind: "finish", reason: input.forcedReason, plateauCount };
if (input.result?.confidence === "high") return { kind: "finish", reason: "high_confidence", plateauCount };
if (input.effectiveAnswerCount >= 10) return { kind: "finish", reason: "safety_cap", plateauCount };
if (plateauCount >= 2) return { kind: "finish", reason: "plateau", plateauCount };
if (input.usefulOpportunityCount === 0) return { kind: "finish", reason: "no_information_gain", plateauCount };
@@ -327,6 +330,8 @@ type DynamicJourneyProgress = {
Do not expose the hidden safety count or a maximum question count in either schema.
Add `dynamicJourneyTurnStateSchema` with `journeyProtocol: z.literal("dynamic-choice-v2")`, nonnegative `turnVersion`, `dynamicNextActionSchema`, `dynamicJourneyProgressSchema`, and the existing derived permissions schema. Keep `journeyTurnStateSchema` unchanged as the legacy-guided-v1 compatibility contract. Terminal resume behavior is implemented by Task 6 transitions, but every v2 public response must parse through this explicit dynamic turn-state discriminator.
- [ ] **Step 8: Run focused tests and commit**
Run: `cd frontend && node --test tests/birth-time-dynamic-choice.test.ts tests/birth-time-dynamic-stop-policy.test.ts tests/birth-time-journey-turn.test.ts`