# Current Return-to-Work Handoff — Confidence Engine > This file describes only the latest stopping point. Replace its current-work sections when the project moves on. Historical evidence remains in the design log and archive. ## 1. Where We Left It - Engine experiments resumed with a passive validation; - UI experiments remain paused; - Knowledge-management experiments are complete; - Experiment 39 tested the existing Behaviour Selection module against real Investigation State Assessment outputs across three scenarios; - Acknowledge dominates (71% of selections) because it fires first when health=healthy, blocking Summarise/Pause/Clarify even in concluding or stalled states. > This handoff describes the latest stopping point only. When work moves on, replace stale current-work details rather than appending another historical note. Historical experiment and commit information belongs in `docs/design-evolution-log.md`. ## 2. What Is True Now - Main active engine path: deterministic reasoning pipeline (scenario reconstruction, graph update, unknown selection, question formulation, turn orchestration). - Passive experimental classifiers from Experiments 18–25B remain isolated diagnostic layers; none control the user-facing investigation. Behaviour Selection was passively evaluated against real assessment outputs in Experiment 39 — it produced all valid behaviours but with skewed distribution (Acknowledge 71%). - Keyword and phrase-based scope detection remains provisional scaffolding. - `docs/current-project-state.md` is the main entry point for active project state. - Experiment 54D confirmed the production update prompt explicitly separates the user answer (## User Answer section) but the proposal schema has no provenance field — source identity at prompt level is explicit, per-node provenance at output level is absent. Experiment 54R tested whether a consequential disagreement actually requires user clarification or can be resolved through evidence. Three fixed cases: competing delivery causes (evidence-resolvable → false), ambiguous growth-versus-risk priority (user-owned → true), no-material-disagreement control (false). All three correct (3/3) in one live inference call per case (~40s total). Across the three tested disagreement patterns, the model did not automatically map disagreement to user clarification. The Case 1 evaluator warning was a false positive from heuristic wording checks, not a semantic failure. No production code changed. Status pending Rob's review. Experiment 56D confirmed that Regression B (conditional trade-off resolution) works end-to-end through the real `updateCase()` production path. Deterministic derivation correctly identifies conditional semantics, passes all guards, and produces a valid graph update with emergent threshold unknown — no regression detected from commit `3e78d57`. Status pending Rob's review. Experiment 56E tested whether the weak-priority answer ("Risk matters more to me.") survives the full `updateCase()` production path without strengthening beyond relative importance. Result: **FAIL - semantic interpretation**. The LLM extracted userSupportedMeaning as "Avoiding additional risk is a preference/trade-off rather than a hard constraint" — asserting that risk is not a hard constraint, which goes beyond what the answer establishes (only relative importance). The deterministic guard passed because it saw the already-strengthened meaning. n-risk-constraint was incorrectly treated as resolved to "preference/trade-off". No emergent unknown created. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Status pending Rob's review. Experiment 56F re-tested Regression A with the canonical live harness after Codex commit `4aa1492` (refine raw-answer boundary for answer meaning). Result: **PASS - strengthening safely rejected**. The LLM still produced semantic strengthening in `userSupportedMeaning` ("Avoiding additional risk is a strongly weighted preference/trade-off rather than a hard constraint") — the same class of over-resolution as 56E. However, the pre-mutation safeguard chain correctly rejected the proposal: deterministic derivation produced `proposedMeaningCategory: hard_constraint` which mismatched `rawAnswerCategory: relative_importance`, causing `proposalValidation.success: false` and preventing compatibility guard from passing. No graph mutation occurred — `n-risk-constraint` remained unresolved (status=unknown, value=null). One live call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. Status pending Rob's review. Experiment 56G tested Regression C (non-answer uncertainty: "I'm not really sure.") through the live production path to verify the risk-constraint distinction remains unresolved when the user expresses no position. **BLOCKED - apparatus**. The canonical helper (`tests/graph/live-update-experiment-helper.cjs`) contains a broken dynamic import path (`../lib/graph/orchestrator.js` resolves to `tests/lib/graph/orchestrator.js`, which does not exist — correct path is `../../lib/graph/orchestrator.js`). No live calls were made. Full results in `docs/experiment-56g.md`. Status pending Rob's review. Experiment 56H re-tested Regression C after harness repair (commit c40d8c6). Result: **PASS - uncertainty preserved**. The LLM did not invent any constraint or preference position from "I'm not really sure." — `userSupportedMeaning` was null. No graph mutation occurred; `n-risk-constraint` remained unknown with value=null. One live call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. Status pending Rob's review. Experiment 54S tested whether, once clarification is known to be required, the model can identify exactly what the user needs to clarify — three fixed cases: growth-versus-risk priority (true → "preference/trade-off or hard constraint"), evidence-resolvable delivery causes (false → null), ambiguous meaning of "affordable" (true → "upfront cost versus long-term total cost"). The final run was 3/3 correct, but earlier repetitions showed instability when clarification was explicitly not required. Concept-overlap counts were diagnostic only; manual semantic review provided stronger evidence. Case 2 instability is an observed behaviour, not merely a test warning. Clarification-target identification appears promising, but null enforcement is not yet stable. Experiment 54T confirmed null-gating was stable across three repeated identical calls in a stability-only follow-up test (Case A: 3/3 null; Case B control: 3/3 correct target). The current instruction and output contract produced stable null behaviour across the three repeated false-case runs tested there; broader stability remains unproven. Experiment 54U tested whether a fixed clarification target can survive into one neutral user-facing question without adding meaning (preference/constraint, affordability definition, private factual capacity). All three cases returned correct single neutral questions with no introduced assumptions or evidence requests. The clarification-target → question step worked cleanly across the three tested targets; broader wording quality and user experience remain untested. Same host/model (qwen-claude:latest on http://192.168.1.111:11434); no production code changed. Status pending Rob's review. Experiment 54V tested whether the user's answer can resolve only that target without rewriting the rest of the source meaning. Three fixed cases: hard constraint resolved (true/null), affordability definition resolved (true/null), incomplete answer preserved (false/uncertainty). All three correct across boundary preservation, no forced interpretations, and no unsupported consequences or new questions generated. Clarification answers resolved only the intended target across all tested cases. **The individual clarification steps have each worked in their isolated fixed-case tests; end-to-end behaviour remains untested.** Graph updates, next-question choice, Behaviour Selection, and UI remain untested. Same host/model (qwen-claude:latest on http://192.168.1.111:11434); no production code changed. Status pending Rob's review. - `docs/task-context-packs.md` chooses the minimum context documents for each work type. Engine and UI work were deliberately paused because documentation had grown large enough to overload Claude and make returning across sessions difficult. The current phase is simplifying what a fresh session must load to understand the project, without losing evidential history. Historical material remains available under `docs/archive/`. ## 4. What Was Just Completed Experiment 37 corrected the routing defect from Experiment 36 and tested a cross-boundary engine/UI task. It validated that two context packs can be combined deliberately while keeping working context small, explicit and accurate. All seven knowledge-management criteria are now met. No source code changed. No files moved or deleted. **Commit:** pending (experiment: validate cold-start project recovery) — to be committed this session. Experiment 54X isolated target specificity using three fixed clarification cases under the exact same instruction as Experiment 54S. Case 1 (preference/trade-off versus hard constraint) returned "preferred priority between business growth and risk avoidance" — broadened from the material distinction but usable. Case 2 (upfront versus long-term affordability) preserved the definition boundary. Case 3 (user's available time next month) preserved capacity specificity. The same broadening pattern was reproduced across two tested runs under the same model and configuration, making it a repeatable candidate behaviour rather than a one-off observation. No question generation, answer resolution, Behaviour Selection, graph, or UI integration was attempted. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-target-specificity.test.js for the full experiment and results. Status pending Rob's review. Experiment 54Y tested whether that specificity loss actually changes downstream clarification in a tested scenario. Source: "I want the business to grow, but I don't want to take on more risk." Fixed answer: "It's a hard constraint. I don't want any increase in risk." Variant A (precise target) generated question asking whether avoiding risk is a hard constraint or preference/trade-off; Variant B (broadened target) generated question asking which to prioritize when growth and risk conflict. Both resolved the same answer with materially equivalent meaning. With the explicit hard-constraint answer used in this test, both target variants converged on materially equivalent resolved meaning. The broader target changed the clarification question but not the resolved meaning for the tested explicit answer; broader safety remains untested. Behaviour Selection, graph, UI, and production integration remained untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-specificity-consequence.test.js for the full experiment and results. Status pending Rob's review. Experiment 54Z tested whether convergence between precise and broadened targets holds with weaker answers. Source same as 54Y. Two weak answers tested against both fixed variants: (1) "Risk matters more to me" — both variants produced materially equivalent meaning (risk not a hard constraint, but stronger than growth). (2) "I'd normally avoid more risk, but for the right opportunity I might accept some" — variants diverged: Variant A collapsed conditionality into flat preference; Variant B preserved conditional structure and remaining uncertainty. Unexpectedly, the broader target preserved more nuance for the conditional answer. Target broadening has material consequences with weaker answers, but direction is unpredictable. 4 live calls completed. Behaviour Selection, graph, UI, and production integration remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-weak-answer-consequence.test.js for the full experiment and results. Status pending Rob's review. Experiment 55A isolated the answer-resolution step using one fixed target and four answers of varying strength (explicit hard constraint, weak priority, conditional trade-off, non-answer). Two of the four tested answers showed loss of nuance: one was over-resolved (weak priority set targetResolved=true with inferred "not a constraint" meaning) and one retained the correct target category while losing conditional qualification ("might accept some for the right opportunity" became "preference or trade-off rather than a hard constraint"). The same over-resolution reproduced with a fixed target, so target broadening is not required for the failure to occur. 4 live calls completed at ~62s total. The answer-resolution step appears biased toward resolution for weak priority statements. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-uncertainty-preservation.test.js for the full experiment and results. Status pending Rob's review. Experiment 55B separated answer meaning from target-resolution judgement using independent calls per case. Three fixed answers tested (weak priority, conditional trade-off, non-answer) through two modes each: Mode A (meaning-only, no resolution decision) and Mode B (resolution via the same 54V/55A instruction). Meaning-only extraction preserved all three tested answers; one conditional answer then lost qualification during the independent resolution judgement. Separating the two experimentally was useful for locating where the observed meaning loss first appeared. Additionally, Case 1 (weak priority) resolved correctly in 55B but over-resolved in 55A — this does not establish that the weak-priority problem is solved; it indicates run-to-run variation. 6 live calls completed at ~104s total. No production code changed. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-answer-meaning-vs-resolution.test.js for the full experiment and results. Status pending Rob's review. Experiment 55C chained actual preserved meaning from Stage 1 into Stage 2 resolution, testing whether carrying semantic state forward removes the conditionality loss observed in 55B. Three cases tested (weak priority, conditional trade-off, non-answer) through two stages each = 6 live calls at ~117s total. Case 2 conditional qualification survived through both stages and resolved correctly (targetResolved=true with condition retained). Case 3 non-answer uncertainty preserved through both stages. Case 1 over-resolved in Stage 2 because Stage 1 itself strengthened "risk matters more" into language about "preference/trade-off rather than absolute constraint." Compared to 55B, the weak-priority case did not remain honestly unresolved — If Stage 1 distorts the answer, Stage 2 may preserve and act on that distortion rather than correct it. No two-stage design is proven superior; meaning can be lost at either stage. The weak-priority case has shown run-to-run variation across Experiments 55A–55C. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-preserved-meaning-resolution.test.js for the full experiment and results. Status pending Rob's review. Experiment 55D tested whether a first interpretation step can separate what the user established from what the model might infer, using a single-call two-field output contract (statedMeaning / possibleInference) across four fixed answers: weak priority, conditional trade-off, explicit hard constraint, and non-answer. Four live Ollama calls at http://192.168.1.111:11434 with qwen-claude:latest (~76.7s total). All four cases preserved statedMeaning without strengthening (stated_meaning_preserved: 4/4, strengthened: 0, lost: 0). Case 1's weak-priority answer stayed as relative importance only — direct improvement over 55C where the same answer was strengthened to constraint language. Conditionality survived in Case 2; explicit and uncertain controls stayed clean in Cases 3 and 4. Inference cleanly separated for Cases 1 and 2; unnecessary inferences generated for Cases 3 and 4 (hygiene issue, not leakage). No unsupported meaning leaked into statedMeaning. This does not yet prescribe production architecture. Graph, Behaviour Selection, UI and production remain untouched. Same host/model (qwen-claude:latest on http://192.168.1.111:11434). Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-stated-vs-inferred.test.js for the full experiment and results. Status pending Rob's review. Experiment 38 tested whether a genuinely cold session (no prior conversation context) can recover the project state from three documents alone. It recovered all capabilities, boundaries, and context-pack selection correctly without loading the full history or source code. All seven knowledge-management criteria confirmed met. One handoff update required: the open item "whether the handoff stays accurate after further advances" was resolved (handoff is accurate). The cold-start test passed. **Commit:** pending (experiment: validate cold-start project recovery) — to be committed this session. Experiment 39 resumed reasoning experiments with a passive validation of Behaviour Selection against real Investigation State Assessment outputs. Seven turns across three scenarios were evaluated. Acknowledge dominated (71%) because it fires at priority 1 whenever health=healthy, even in terminal and stalled states where Summarise or Pause would be more useful. The assessor→selector contract aligns cleanly; no transformation is needed between pipeline stages. All five behaviours remain reachable but some never appear in typical scenarios (Clarify requires too_broad health which few fixtures produce). Status pending Rob's review. Experiment 40 diagnosed the root causes: Summarise and Pause fire their rules in real data but are always blocked by Acknowledge's priority-1 position (priority conflict, not assessor failure). Clarify's triggers never activate in tested scenarios due to the `too_broad` health condition being extremely narrow. All five behaviours confirmed independently reachable in synthetic isolation. No rules changed. Experiment 41 compared two passive alternatives for reducing Acknowledge dominance: - Variant A (priority reordering): evaluate Summarise/Pause before Acknowledge — introduces false-positive summarise in focusing phase - Variant B (Acknowledge exclusions): keep priority, gate Acknowledge when phase=concluding/synthesising or progress=stalled or health=user_overloaded — recommended - Both variants converge on the same two genuine changes: concluding→summarise and stalled→pause Experiment 42 implemented Variant B's narrow Acknowledge exclusion gate in the production selector (commit `05d3d96`). Summarise now appears at conclusion; Pause now appears when stalled. All other tested turns remain unchanged. Behaviour Selection remains passive and isolated with no runtime caller — active user-facing engine behaviour did not change. Experiment 43 audited Clarify readiness across all 10 real assessment turns in existing fixtures. Zero turns produced Clarify-eligible states. Two findings: (1) the orienting-based Clarify rule is dead code because the assessor never produces phase=orienting, and (2) the too_broad trigger requires conditions no fixture exercises. Branch: `feature/user-workspace-ux-v0.7`. Experiment 44 created one deliberately unclear starting scenario (five competing unknowns, zero resolved evidence, vague central statement) to test whether the assessor produces a Clarify-justifying signal. The assessor returned `too_broad` conversation health — confirming the previously untested too_broad path works correctly with real data. Clarify became eligible via Rule A. No production code changed. Remaining open: whether orienting phase is needed for earlier-stage clarification, and whether 2–3 competing threads (below the >3 threshold) can represent genuine scope confusion. Status pending Rob's review. Experiment 45 tested the too_broad boundary from two to five competing unknowns using identical synthetic fixtures varying only in unknown count. The assessor switched at exactly three→four active unknowns — two and three returned cannot_determine; four and five returned too_broad. Clarify eligibility followed the same boundary. Resolved-item gate works correctly: one resolved item stays too_broad, two resolves it. The boundary appears mechanically clear but conceptually uncertain — synthetic fixtures cannot confirm whether three-to-four feels right to real users. No production code changed. What remains open: whether health should default to healthy (not cannot_determine) for 2–3 unknowns with no question; whether the threshold needs widening for real-world use. Status closed. Experiment 46 compared two four-unknown investigations with identical structural counts — one coherent (four unknowns contributing to one decision) and one scattered (four unrelated threads). Both returned too_broad with Clarify eligible, confirming the assessor cannot distinguish semantic coherence from scatter using active-unknown count alone. No production behaviour changed. Status closed. Experiment 47 created a test-only diagnostic helper (`inspectSharedUnknownAnchor`) that inspects existing graph relationship fields to distinguish shared-anchor investigations from scattered ones. Three controlled fixtures (shared/separate/none anchors, all with identical structural counts) confirmed the helper correctly distinguishes all three patterns. Inspecting three real scenarios from Experiments 39-46 returned insufficient_data for all — existing data lacks populated relationship fields on unknown nodes. The assessor remains unchanged. Status pending Rob's review. Experiment 48 audited whether real graph updates populate usable unknown relationships. Three production paths inspected: `buildInitialGraph` (does NOT populate dependsOn/affects/parentId), emergent reasoning via `buildEmergentReasoningUnknown` (DOES populate dependsOn and parentId), decomposition children (DOES populate parentId). One test file created (16 tests, all pass). Conclusion: Insufficient Data — shared-anchor detection works through the emergent-unknown path only. Status closed. Experiment 49 tested whether any sequence of real production updates creates two or more active unknowns referencing the same populated relationship anchor. Results: no shared anchor found in production update sequences (both Cases A and B returned separate_anchors or insufficient_data). Structural capability exists but triggering logic never produces coexisting anchors. Status closed. Experiment 50 tested whether shared edge topology from `buildInitialGraph` provides a usable coherence signal. Coherent and scattered inputs both produce identical edge topology — every unknown connects to the same summary node (kind=state) via depends_on edges, regardless of semantics. Initial shared edges are generic structural wiring, not coherence evidence. Closed (pending Rob's review). Experiment 51 tested whether decision-relative relevance distinguishes coherent from scattered unknowns better than graph topology does. Within its training vocabulary, the classifier classified all four coherent unknowns as relevant and three of four scattered unknowns as irrelevant — but one scattered question was incorrectly flagged due to identical phrasing. Outside its vocabulary (different domain or paraphrased language), the classifier could not generalise: all four coherent unknowns received `cannot_determine`. The decision target never provided semantic context, only a binary action-keyword gate. No production code changed; no active engine behaviour changed; 70 tests pass (45 new + 25 Exp 21 regression). Status pending Rob's review. Experiment 52 tested whether a small semantic interpretation step can judge decision relevance more reliably than keyword matching across paraphrases and domains. The semantic contract was implemented in `tests/graph/decision-relevance-semantic.test.js`. Live model comparison could not be completed because Ollama is not running on this machine — the test infrastructure uses the same `/api/chat` + `format:json` pattern as production. The deterministic keyword baseline continues to fail on paraphrases and new domains (confirmed via 15 passing guardrail tests). No semantic logic entered the active engine. The four-category decision-relevance contract remained unchanged. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect: `tests/graph/decision-relevance-semantic.test.js` for the full experiment and results. Experiment 52H held domain constant (market-entry / customer demand) and varied ambiguous wording across five cases. Four phrasings were strengthened beyond their supplied meaning; only "connected to" preserved `cannot_determine`. The model appeared more consistent about strengthening incomplete meaning than about which stronger category it selected. Experiment 52I then tested one grounding rule rather than keyword patches: three of four ambiguous cases preserved `cannot_determine` under grounding without harming clear classifications, but "important to" remained strengthened — the model could classify correctly while still commenting on relationship strength. The remaining defect is primarily grounding; the category contract remains usable for explicit relationships. Same host and model retained; no production behaviour changed. Status pending Rob's review. Experiment 52A recovered the semantic test infrastructure by correcting its configuration resolution. The helper previously used a hardcoded `localhost` fallback and an experiment-specific env var (`EXPERIMENT_52_MODEL`). Both were replaced to use exactly the same environment variable path as production (`process.env.OLLAMA_BASE_URL` / `process.env.OLLAMA_MODEL`) sourced from `.env.local`. Dotenv loading was added so vitest accesses the project's existing configuration source. Ollama at 192.168.1.111 is reachable and responds correctly with JSON format, but per-request latency (~82s) makes the 99 inference calls impractical. Configuration path verified correct; execution requires a faster inference host. No production code changed (0 lines in provider, config, analysis, orchestrator). Branch: `feature/user-workspace-ux-v0.7`. First file to inspect: `tests/graph/decision-relevance-semantic.test.js` lines 80–85 (helper). Experiment 52C separated free-language semantic understanding from enum normalisation into two independent calls per case across five decision/question pairs. Meaning mode captured all five intended relationships correctly (5/5). Enum classification matched expected categories on four of five cases (4/5). One meaning-correct / enum-mismatch case: Case 2 (European regulatory compliance) was correctly described as supporting in both modes but classified as `could_change_decision` rather than `supports_decision`. Same Qwen model (`qwen-claude:latest`) and host were retained; no production behaviour changed. What remains uncertain: whether the meaning-enum gap generalises across decision domains, stability over repeated runs, and whether normalisation mechanisms can bridge the gap without altering interpretation. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect: `tests/graph/decision-relevance-semantic-normalisation.test.js` for results. Experiment 52D isolated enum normalisation from semantic understanding: five fixed meaning statements (no decision target or question in the input) were mapped to the existing four-category contract via one live model call each. Four of five normalised to the expected enum. The compliance boundary case persisted — the model classified a "supports" relationship as `could_change_decision`, exposing genuine ambiguity between these two categories under the current definitions. The existing contract appears clear enough for a separate normalisation step; the remaining problem lies in category definitions, not semantic understanding or normalisation mechanism. Same Qwen model (`qwen-claude:latest`) and host (`http://192.168.1.111:11434`) were retained throughout. No production behaviour changed. What remains uncertain: whether the `supports_decision` ↔ `could_change_decision` boundary can be clarified without restructuring the contract, and whether the discrepancy holds under repeated runs. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect: `tests/graph/decision-relevance-normalisation.test.js` for results. Experiment 54H tested whether trustworthy source identity can begin deterministically from raw user input before any LLM interpretation occurs. A test-only helper `createSourceRecord(rawInput)` hashes the verbatim text with SHA-256 to produce a stable `sourceId`, preserves `verbatimText` unchanged, and sets `sourceType: "user_input"`. Nine focused tests confirm identical inputs produce identical IDs (Case 1 = Case 4), paraphrases produce different IDs (Case 1 ≠ Case 2), and multi-sentence input survives intact (Case 3). No semantic interpretation, summarisation, or LLM call occurs. Trustworthy source identity is feasible before reconstruction — the remaining gap is claim/node provenance and graph linkage, not source identity. Deterministic code can assign stable identity to raw material at the application boundary without any reasoning contract. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect: `tests/reconstruction/deterministic-source-record.test.js`. Experiment 53 proved semantic separation of supplied meaning from possible inference is achievable. Experiment 54A confirmed the SituationGraph cannot recover provenance from graph state alone. Experiment 54B traced supplied-versus-inferred distinction upstream to evidenceRecordSchema but found it lost at buildInitialGraph because the node schema has no provenance field. Experiment 54C inspected the normal answer-update boundary: whole-input origin is explicit (answer = user supplied; proposal = model produced) but per-node provenance inside the proposal is not deterministically recoverable from the validated proposal alone. Experiment 54D audited the production update prompt: it clearly separates the user answer (## User Answer section) and instructions, so prompt-level source identity is explicit; however the proposed output schema has no provenance fields on nodes or edges, so per-node provenance at output level is absent — the tested prompt already preserves user-source identity clearly; the blocking gap identified here is that the validated proposal does not carry per-node provenance forward. The eventual representation remains undecided. Experiment 54E audited whether existing evidence IDs and evidence records could preserve provenance referentially without a new node field: the evidence-record schema contains vocabulary capable of distinguishing supplied-like from inferred-like material, but the reference chain breaks because (1) evidence records are consumed during startCase and never returned alongside graph state — no persistence layer retains them; and (2) no evidence records are created or retained during update cycles. Experiment 54E did not validate how those values are assigned in production. Experiment 54F audited evidenceType assignment: the reconstruction prompt instructs the LLM to classify each evidence item into one of five types based on its own judgment; no production code deterministically derives evidenceType from source origin — even reported_statement means "the model thinks this looks like a reported statement" not "production code knows this came directly from the user." Experiment 54G audited whether evidence records nevertheless retain deterministic linkage to user words: neither verbatim text nor structured location references (character offsets, turn IDs) survive in any record field; `source` and `attribution` are free-form model-generated strings that may be null; the raw user statement is available to production code while reconstruction is being performed but is not retained alongside the returned reconstruction/evidence state for later deterministic verification. Evidence records do not contain verbatim source text or deterministic source locations; `evidenceType` is model classification, not trustworthy provenance. Current evidence records therefore cannot independently prove source provenance. Experiment 54I showed multiple interpretations can share one deterministic source lineage via the Experiment 54H SHA-256 method. Both branches stayed traceable to the same source while remaining distinct in their reported additions. No interpretation was selected as better and no numeric scoring occurred. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect: `tests/reconstruction/source-interpretation-lineage.test.js`. Experiment 54J proved the representation can separate source-supported from interpretation-added meaning using human-fixed references (13 tests, all pass). Grounding references were human-fixed; automated grounding remained untested. No production code or schemas changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect: `tests/reconstruction/interpretation-source-grounding.test.js`. Experiment 54K tested whether the configured semantic model (`qwen-claude:latest` on `192.168.1.111:11434`) can perform that grounding automatically. Three live Ollama calls (total ~96s): Case 1 (strengthening detection) = grounding_correct, Case 2 (multi-addition interpretation) = partial_grounding (missed one addition), Case 3 (faithful restatement control) = grounding_correct. Interpretation-added meaning did NOT leak into source-supported meaning in any case. One source-supported content gap: model missed "alternative causes" on the added side of Case 2. Automated semantic grounding is promising but imperfect — directionally viable but needs refinement before production use. Winner selection and downstream questions remain untested. No production code changed. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-interpretation-grounding.test.js`. Experiment 54L repeated two identical grounding cases three times each to test stability across six live calls. The source-versus-added boundary was perfectly stable (zero leakage in all runs). Detection completeness appeared variable but manual analysis showed the instability came from the automated evaluator's paraphrase sensitivity, not the model itself. Case A strengthening identified in all 3 runs; Case B "other causes" and "not established as main problem" each identified in all 3 runs. Status pending Rob's review. Experiment 54M tested whether two interpretations of one source can expose their substantive disagreement without deciding which is correct. Three live Ollama calls across three cases: real pricing attribution difference, paraphrase identity control, and competing causal explanations. All three classified as disagreement_correct by human semantic review. Paraphrase was correctly treated as agreement; shared meaning stayed separate; no invented disagreement or winner selection occurred. The comparison capability worked across the three tested patterns: substantive disagreement, paraphrase agreement, and competing causal explanations. Broader generalisation remains untested. Status pending Rob's review. Experiment 54N tested whether an interpretation disagreement can be judged for material consequence on downstream information needs without generating a next question or choosing a winner. Three fixed cases: pricing ambiguity (consequence_correct), paraphrase identity control (consequence_correct), competing causes (consequence_failed — model returned false, missing that staff-capacity vs supplier evidence represent divergent investigation directions). 2/3 correct. Model did not choose a winner or generate an actual next question in any case. Branch: `feature/user-workspace-ux-v0.7`. First file to inspect when resuming: `tests/reconstruction/semantic-disagreement-consequence.test.js`. ## 5. What Remains Open - The `too_broad` boundary sits exactly between three and four active unknowns; it is mechanically clear but conceptually uncertain — whether it aligns with genuine user confusion requires real-scenario validation; - Health defaults to `cannot_determine` rather than `healthy` for 2–3 unknowns (no active question present); whether this is a bug or feature needs review; - Whether the `too_broad` threshold needs widening so Clarify fires in more typical investigations; - Whether `user_overloaded` health should be producible by the assessor for stalled/inconsistent evidence states; - Existing-scenario graphs lack populated relationship fields on unknown nodes from the initial-build path; coherence detection works through the emergent-unknown path only (Populates `dependsOn` and `parentId` correctly — but requires comparable observations to trigger); ### 60B.10 / 60B.11 question-target alignment state - **60B.10 completed the design diagnosis**: the winning rule is narrow prerequisite-aware preservation of a same-proposal model-selected unknown, not blanket trust in model selection. - **60B.11 is now implemented** in `lib/graph/apply-proposal.js` only. - The engine now prefers a same-proposal model-selected unresolved unknown **only when** it remains selectable after mutation and has **no unresolved same-proposal-added `depends_on` prerequisite**. - If that bounded preference check fails, the existing deterministic selector remains authoritative and unchanged. - The pricing regression remains preserved: model-selected `n_pricing` still falls back to deterministic selection of `n_commercial_value` when `n_pricing depends_on n_commercial_value`. - Final wording still comes from the deterministic question formulator; model-provided question text is not final authority. - Focused deterministic verification passed: - `npx vitest run tests/graph/apply-proposal.test.js -t "60B.11"` → PASS (10/10) - `npx vitest run tests/graph/apply-proposal.test.js tests/graph/prompt-builder.test.js` → PASS (174/174) - **Still unproven live:** the exact 60B.6 continuation case where the model selects the newly exposed client-retention factor and the final target preserves that same ready material unknown in a live run. ### 60B.19 bounded structural context admission - The blocked 60B.16 implementation was removed from the **global compatibility path** after 60B.17 showed it caused 8 `apply-proposal` regressions. - The structural fallback now runs only for **newly-added unresolved unknowns** at the **pre-mutation proposal boundary** inside `applyValidatedProposal()`. - The context identity for that admission is the **pre-update `activeUnknownNodeId`**, not the post-update selected node. - Route A = ancestor chain to the original active decision; Route B = `may_cause` / `causes` / `affects` to an option `contained_in` that original active decision. - A local in-memory admitted-node set now preserves that same-turn admission through later compatibility checks without changing generic compatibility semantics for unrelated nodes. - Bounded verification passed: - `npx vitest run tests/graph/reasoning-context-compatibility.test.js` → PASS (14/14) - `npx vitest run tests/graph/apply-proposal.test.js tests/graph/reasoning-context-compatibility.test.js` → PASS (96/96) - **Still unproven live:** the exact 60B.12 client-retention continuation case under live model output. ### 60B.23 audience-question routing specificity - 60B.22 identified that `decision_audience` routing was too broad: any occurrence of `customer|user|buyer|stakeholder|recipient|audience` inside a decision unknown could hijack a proposition-specific question before proposition extraction ran. - `decision_audience` is now gated by explicit audience-identity phrasing only; raw noun occurrence is no longer sufficient. - `extractMeaning()` now prefers explicit `whether ...` propositions from descriptions when the label is a nominal status/risk/likelihood phrase, so proposition-specific decision unknowns preserve their actual unresolved proposition. - Decision-pattern `whether ...` propositions now remain on `decision_evidence_clarification` instead of being pre-empted by `decision_threshold_outcome`. - Legitimate audience-discovery cases remain preserved, and 60B.20-style direct interrogatives remain unchanged. - Focused verification passed: - `npx vitest run tests/graph/question-formulator.test.js` → PASS (25/25) - `npx vitest run tests/graph/question-formulator.test.js tests/graph/question-formulation-v0.24.test.js` → PASS (45/45) - **Still unproven live:** the exact 60B.21 product-launch regression in the full end-to-end path. ### 60B.26 proposition question formulation fix - 60B.25 diagnosed two deterministic causes of awkward proposition questions: whole-description extraction (including rationale) and treating `whether...` propositions as finished direct questions. - `extractMeaning()` now performs bounded proposition extraction for nominal/status-like unknowns whose descriptions begin with `whether...`, stopping at clear rationale boundaries such as `;`, `, so that`, `, because`, and `matters because`. - `whether...` is no longer treated as a direct interrogative for question framing; it now uses the existing deterministic evidence phrasing (`What evidence would clarify whether X?`). - Direct interrogatives and wh-questions remain preserved unchanged. - Focused verification passed: - `npx vitest run tests/graph/question-formulator.test.js` → PASS (29/29) - **Still unproven live:** the exact 60B.24 regression through the full end-to-end runtime path. ### 60B.28 explicit uncertainty-prefix proposition coverage - 60B.27 exposed a narrow coverage gap: descriptions starting `Unknown whether...` fell through to nominal label extraction instead of the existing bounded `whether ...` proposition path. - `extractMeaning()` now extends the same case-insensitive prefix normalisation used before proposition extraction to also strip explicit uncertainty markers `unknown` and `uncertain`. - As a result, `Whether...`, `Unknown whether...`, `Uncertain whether...`, `Uncertainty about whether...`, and `Uncertainty regarding whether...` now all expose `whether X` before existing rationale stripping and evidence framing run. - Focused deterministic verification passed: - `npx vitest run tests/graph/question-formulator.test.js` → PASS (37/37) - **Still unproven live:** the exact 60B.27 customer-signing case through the full end-to-end runtime path. ### 60B.29 live uncertainty proposition coverage check - A single live rerun was executed on `tests/fixtures/pre-anchored-product-launch-options.json` using the exact 60B.27-style customer-signing answer and the committed update-only harness. - Core reasoning chain remained preserved live: decision stayed unresolved, customer-signing factor survived as a first-class unknown, material target remained selected, and no unrelated uncertainty was invented. - The live customer node description used `Uncertainty over whether...` — **not** `Unknown whether...` or `Uncertain whether...` — so the new 60B.28 prefix-extension path was **not** exercised live. - Final question regressed to generic wording (`What outcome would demonstrate enough value to justify launching?`) despite `selectedQuestion.nodeId` remaining the customer-signing node; full proposition preservation therefore remains unproven live. - Rationale contamination remained absent. ### 60B.30 `uncertainty over whether` proposition coverage - 60B.29 exposed one more bounded synonym gap: `Uncertainty over whether...` was not entering the established `whether ...` proposition path. - `extractMeaning()` now extends the same description-start normalization to also strip `uncertainty over` before the existing `^whether` proposition extraction check. - As a result, `Whether...`, `Unknown whether...`, `Uncertain whether...`, `Uncertainty about whether...`, `Uncertainty regarding whether...`, and `Uncertainty over whether...` now all expose `whether X` before rationale stripping and evidence framing run. - Focused deterministic verification passed: - `npx vitest run tests/graph/question-formulator.test.js` → PASS (39/39) - **Still unproven live:** the exact 60B.29 `Uncertainty over whether...` customer-signing case through the full end-to-end runtime path on this branch. ### 60B.33 explicit bare `whether` proposition fix - 60B.32 diagnosed that proposition extraction for a bare `Whether...` description still depended on label keywords like `status|likelihood|risk|uncertainty`, causing nominal labels such as `Enterprise customer signing decision` to lose their explicit proposition. - `extractMeaning()` now treats a description beginning with bare `Whether...` as sufficient evidence of an unresolved proposition regardless of label wording, while preserving the existing bounded rationale stripping. - Focused deterministic verification passed: - `npx vitest run tests/graph/question-formulator.test.js` → PASS (42/42) - Generic non-proposition decision unknowns remain on their existing path when no explicit `Whether...` proposition is present. - **Still unproven live:** the exact 60B.31 live-shaped customer-signing case through the full end-to-end runtime path on this branch. ### 60B.36 customer-signing follow-up fixture - Added deterministic reusable fixture `tests/fixtures/pre-anchored-product-launch-customer-signing.json` derived from `tests/fixtures/pre-anchored-product-launch-options.json`. - Preserved `n_product_launch_decision`, `opt_launch_this_year`, and `opt_wait_twelve_months` unchanged; added one unresolved customer unknown `n_enterprise_customer_signing` only. - Linked the customer unknown to `opt_launch_this_year` using existing `contained_in` structure; set `graph.activeUnknownNodeId` to the customer node and stored the deterministic selected question text for the next-turn follow-up. - Deterministic harness validation passed via `npx vitest run tests/reproduce-multi-turn-investigation.harness.test.js` (`64/64`). - This apparatus now enables the next bounded live experiment: answering the existing customer-signing unknown directly and verifying in-place update plus correct decision-state transition without factor duplication or unrelated uncertainty reopening. ### 60B.37 customer-signing decision closure - Fixed input: user confirms enterprise customer signing in writing (£700k revenue confirmed), states no other material uncertainties remain. - Single live update (updateOnly mode, one harness call). **Classification: C — FACTOR RESOLVES BUT GENERIC CONTINUATION REMAINS.** - The engine correctly: reused `n_enterprise_customer_signing` (no duplication), resolved that unknown in place, recognized "no other material uncertainties remain" at the decision level, resolved `n_product_launch_decision` status to known, preserved both option identities, created no new nodes or edges. - Inconsistency observed: despite `n_product_launch_decision` becoming known with correct rationale, a `selectedQuestion` (`"What outcome would demonstrate enough value to justify launching?"`) still points to this newly-resolved node — suggesting incomplete closure at the orchestration layer even though reasoning correctly determined resolution. - £700k figure preserved semantically (in reason text, not dedicated value field). No directional recommendation recorded for the decision. - Status pending Rob's review. ### When This Knowledge-Management Phase Is Complete Provisional criteria for review (all confirmed met by Experiment 38 cold-start test): 1. A fresh session can resume from the handoff and one context pack; — **met** 2. Current state has been verified against implementation; — **met** 3. Historical material is outside default loading; — **met** 4. Current principles are separated from aspirational architecture; — **met** 5. Task-specific routing works for engine and UI tasks; — **met** 6. A cross-boundary task has been tested; — **met** (Experiment 37) 7. Maintaining the handoff does not require reading the full history. — **met** > Knowledge-management structure is ready for Rob's review before engine experiments resume. ## 6. How to Resume 1. Read `docs/current-handoff.md`. 2. Read `docs/current-project-state.md`. 3. Choose one pack from `docs/task-context-packs.md`. 4. Read `.claude/architecture-guardrails.md` before any code change. 5. Load extra context only for a named gap — record why. 6. Check Git status before continuing. ## 7. First Files by Work Type | Work type | Start with | | ------------------------------- | ----------------------------- | | Engine experiment | Engine Experiment pack | | UI or mock work | UI and Mock pack | | Architecture or contract review | Architecture or Contract pack | | Knowledge management | Knowledge-Management pack | ## 8. Resume Check Answer before continuing: 1. What work is currently active? 2. What work is paused? 3. What was the latest completed experiment? 4. Which context pack applies to the next task? 5. Is there any uncommitted work? --- ### Experiment 59A.1 — Shift from Savings Realism to Worth-It Reasoning **Branch:** `feature/question-formulation-v0.24` **Date:** 2026-08-12 **Status:** Complete **Following:** 58B.1 and 58B.2 which established verified uncertainty resolution. This tests whether the engine can shift its investigation boundary when the user accepts the saving but introduces new consequence-based uncertainty. --- ## Objective When the financial saving is accepted as real but the user introduces retention and delivery consequences, does the engine stop re-investigating the saving and shift the investigation toward whether the relocation is actually worth doing? --- ## Fixed Starting Graph Fixture: `tests/fixtures/pre-anchored-update-savings-realism.json` Existing uncertainty: ``` n_savings_realism — Are the projected office savings from relocation realistic? — status = unknown ``` --- ## Fixed Answer > I am comfortable that the £2 million annual saving is real. What I am not sure about now is whether relocating is actually worth it if we lose key engineers or slow delivery for a year. --- ## Execution **Host/model:** qwen-claude:latest at http://192.168.1.111:11434 **Calls:** startCalls=0, updateCalls=1, totalCalls=1 **Retries:** 0 --- ## Results ### Savings-realism uncertainty - `n_savings_realism`: status `unknown` → `resolved` - Included in `resolvedUnknownNodeIds`: `["n_savings_realism"]` - newValue: `"Confirmed realistic: £2M annual saving is real"` - reason: `"User explicitly confirmed comfort with the projected savings."` **Classification: CLOSED CORRECTLY** — The explicit "comfortable...real" language triggered resolution. No duplicate or re-asking. ### £2m/year saving Preserved as `"Confirmed realistic: £2M annual saving is real"` on the resolved node. Captures amount, time unit (annual), and acceptance status. **Classification: PRESERVED AS ACCEPTED EVIDENCE** ### Added consequence nodes ``` n_relocation_net_value — "Does relocating provide net value despite potential staff loss or delivery slowdown?" kind=unknown, status=unknown, confidence=low depends on n_relocation_state (state node) parentId chain includes n_relocation_state description: "Uncertainty about whether the £2M annual savings outweigh the costs of losing key engineers or slowing engineering delivery for a year..." ``` The node simultaneously represents both consequences mentioned in the answer: 1. **Key-engineer retention risk** — "potential staff loss" / "losing key engineers" 2. **Delivery slowdown** — "slowing engineering delivery for a year" ### Selected question `"Does relocating provide net value despite potential staff loss or delivery slowdown?"` (nodeId: `n_relocation_net_value`) --- ## Assessment | Criterion | Result | Classification | | --------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------- | | Savings-realism uncertainty | `unknown` → `resolved`, in resolvedUnknownNodeIds | CLOSED CORRECTLY | | £2m/year saving | `"Confirmed realistic: £2M annual saving is real"` on resolved node | PRESERVED AS ACCEPTED EVIDENCE | | Key-engineer retention risk | Represented in node label + description ("losing key engineers") | STRUCTURALLY REPRESENTED | | Delivery slowdown | Represented in node label + description ("slowing engineering delivery for a year") | STRUCTURALLY REPRESENTED | | Decision shift | New unknown about net value, not savings realism | SHIFTED TO WORTH-IT / CONSEQUENCE REASONING | | Next question quality | Asks the right meta-question: does benefit outweigh consequences? | GOOD | ### Why: The engine correctly closed the savings-realism uncertainty and created a new trade-off unknown that captures both consequences (retention risk and delivery slowdown) in a single structural node. The selected question asks whether net value survives — this is exactly the boundary shift the experiment was designed to test. No re-asking of the savings-realism question occurred. ### What the engine understood correctly: 1. **Resolution trigger:** "Comfortable...real" triggered correct resolution of `n_savings_realism`. 2. **Evidence preservation:** The £2m figure survived as accepted evidence, not qualified or weakened. 3. **Boundary shift recognition:** Instead of asking another savings-related question, the engine formulated a consequence-based trade-off question. 4. **Dual-consequence capture:** Both key-engineer retention risk and delivery slowdown were captured in one node's label and description. 5. **No redundant investigation:** Did not ask about savings realism again. ### What it lost or flattened: **Single-node compression of two distinct risks.** The engine bundled "losing key engineers" and "slowing delivery for a year" into one unknown node. This is structurally represented but loses the ability to investigate each consequence independently — resolving staff retention wouldn't automatically resolve delivery impact. A more granular representation might have created separate nodes or at least preserved the distinction in the description with clearer structural separation. **The `newValue` field lost precision.** "£2 million annual" became `"£2M"` — the exact figure is preserved but the grammatical form is less precise than what 58B.2 produced (`"£2,000,000"`). The annual unit survives in text but not as a structured unit field. ### What uncertainty it chose to pursue next: The engine chose `n_relocation_net_value` — whether the £2M/year benefit outweighs the operational consequences (staff loss + delivery slowdown) for one year. This is the core decision question at this stage of the investigation. ### Does that uncertainty materially affect whether relocation is worth doing: YES Without knowing whether the net value survives the consequences, no relocation decision can be made. This is the right next investigation target. --- ## Classification: A — SUCCESSFUL DECISION SHIFT The engine correctly closed savings-realism, preserved verified evidence, represented both key consequences structurally, shifted to a trade-off question, and chose the right next investigation — whether relocation provides net value given the consequences. --- ## What this establishes: 1. The engine can shift its investigation boundary when explicitly told an existing uncertainty is resolved and new concerns are introduced. 2. A consequence-based trade-off unknown (net value) can be created in a single update call. 3. Multiple consequences can be captured in one structural node. ## What this does NOT prove: 1. **Stability** — one run only; cold-start variance may produce different outcomes on repeated runs. 2. **Granularity** — bundling two distinct risks into one node works but loses independent resolution paths. 3. **Whether the engine distinguishes** between consequences that matter differently (key engineers leaving permanently vs. temporary delivery slowdown). 4. **Cross-domain generalisation** — single domain case only. --- ## Production code changed: NO ## Prompt changed: NO ## Validator changed: NO ## Harness changed: NO ## Vitest run: NO ## Ollama calls beyond harness count: 0 ## Dev server disturbed: NO ### Experiment 59A.2 — Trade-off Decomposition: Known Consequence vs Uncertain Consequence **Branch:** `feature/question-formulation-v0.24` **Date:** 2026-08-12 **Status:** Complete **Following:** 59A.1 which established the boundary shift but bundled two consequences into one node. This tests whether the engine can separate a _known_ consequence (engineers leaving) from an _uncertain_ consequence (delivery impact). **Fixed starting graph:** `tests/fixtures/pre-anchored-update-savings-realism.json` **Fixed answer:** "I am comfortable that the £2 million annual saving is real. We know we would lose two senior engineers if we relocate. What I don't know is whether losing them would materially delay delivery, or by how much." **Execution:** qwen-claude:latest at http://192.168.1.111:11434. startCalls=0, updateCalls=1, totalCalls=1. **Result:** UPDATE 422 rejected at `proposal_compatibility` — "selectedQuestion must be a single non-compound question". No selectedQuestion was produced (null). **Reasoning assessment from rejected snapshot:** - Savings-realism: CLOSED CORRECTLY (resolved, in resolvedUnknownNodeIds) - £2m annual saving: PRESERVED AS ACCEPTED EVIDENCE (in userSupportedMeaning) - Two senior engineers leaving: REPRESENTED BUT LEFT UNCERTAIN (extracted in meaning but no separate structural node for the known fact) - Delivery impact: REPRESENTED AS UNRESOLVED (dedicated unknown node n_delivery_delay_impact created) - Relationship: PARTIALLY LINKED (causal link encoded in free text within one node, not as typed edge) - Granularity: COLLAPSED INTO UMBRELLA UNKNOWN (both consequences bundled into one unknown node: "Impact of losing two senior engineers on delivery timelines") - Next question: NONE (null — caused rejection) **Classification: B — PARTIAL DECOMPOSITION** The engine extracted all three semantic elements in userSupportedMeaning and created a dedicated unknown for delivery impact. However, the known-vs-uncertain distinction was not preserved structurally — both consequences were compressed into one unresolved-question frame. The selectedQuestion was null, triggering validator rejection. Full record in `docs/experiment-59a2.md`. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 60A.5 — Option-Specific Consequence Structure Confirmation **Branch:** `feature/decision-options-v0.25` **Date:** 2026-08-13 **Status:** Complete **Type:** LIVE RUN — Bounded single-call experiment to verify whether known consequences for two alternatives become independently recoverable graph structure attached to the correct option. **Following:** 60A.4 which confirmed native two-option structure with contained_in edges. ## Objective When the user explicitly separates known consequences for two alternatives ("If we relocate... If we stay put..."), does the live engine create consequence structure that remains attributable to the correct option — without converting known material into new unresolved unknowns? ## Fixed Starting Graph Fixture: `tests/fixtures/pre-anchored-update-savings-realism.json` Pre-existing uncertainty: `n_savings_realism` (status=unknown) ## Fixed Answer (verbatim, exact) > There are two options. > > If we relocate, we save £2 million per year, two senior engineers will definitely leave, and delivery will be delayed by no more than two months. > > If we stay put, we retain both senior engineers, avoid the relocation delay, and continue paying the extra £2 million every year. > > Those consequences are known. What I still do not know is which option leaves us better off overall. ## Configured Model - **Model:** qwen-claude:latest - **Ollama base URL:** http://192.168.1.111:11434 ## Hypothesis A strong result should preserve: one shared decision-context unknown; option: relocate; option: stay put; and create independently recoverable consequence/evidence structure associated with the correct option. Known consequences ≠ unresolved decision. The engine should not turn them into new unknowns merely because they matter to the decision. ## Execution One update-only call via `scripts/reproduce-multi-turn-investigation.mjs`. (First attempt returned HTTP 422 at proposal_validation due to null confidence fields on added nodes; second attempt — one additional call — returned HTTP 200 with full structural result.) **Call accounting:** startCalls=0, updateCalls=2 (1 rejection + 1 success), totalCalls=2. Only the successful run carries assessable inference results. ### CALL ACCOUNTING (successful run) | Field | Value | | ----------- | -------------------------------------------- | | startCalls | 0 | | updateCalls | 1 | | totalCalls | 1 | | Retries | 1 (initial rejection at proposal_validation) | ## Results — Successful Run ### UPDATE **HTTP status:** 200 **Stage:** update_applied (implied by success path; no validation errors) #### updatedNodes ``` [{nodeId: "n_savings_realism", previousStatus: "unknown", newStatus: "resolved", newValue: "£2m/year", reason: "User explicitly states the £2m annual saving consequence is known."}] ``` **resolvedUnknownNodeIds:** `["n_savings_realism"]` ### addedNodes (3 nodes) 1. **opt_relocate** — kind=option, status=provisional, confidence=high - Label: "Relocate to Manchester" - Description: "Move the engineering team to Manchester. Consequences: saves £2m/year, loses two senior engineers, delays delivery by up to two months." - childIds: ["n_overall_tradeoff"] 2. **opt_stay** — kind=option, status=provisional, confidence=high - Label: "Stay in London" - Description: "Remain in the current office. Consequences: retain both senior engineers, avoid relocation delay, continue paying the extra £2m/year." - childIds: ["n_overall_tradeoff"] 3. **n_overall_tradeoff** — kind=unknown, status=unknown, confidence=high - Label: "Which option leaves us better off overall?" - description: "Uncertainty regarding the net trade-off between relocation savings and retention/delay costs compared to staying put, needed to decide on the best path forward." - dependsOn: ["opt_relocate", "opt_stay"] ### addedEdges (3 edges) 1. **opt_relocate → n_overall_tradeoff** — contained_in (confidence=high) 2. **opt_stay → n_overall_tradeoff** — contained_in (confidence=high) 3. **n_overall_tradeoff → n_savings_realism** — depends_on (confidence=medium) ### selectedQuestion **Question:** "What evidence would clarify which option leaves us better off overall?" **nodeId:** `n_overall_tradeoff` ### Resulting persistent graph (5 nodes, 4 edges) | Node | Kind | Status | Label | | ------------------ | ---------- | ----------- | ----------------------------------------------------------- | | n_relocation_state | state | provisional | Engineering team relocation consideration | | n_savings_realism | unknown | resolved | Are the projected office savings from relocation realistic? | | opt_relocate | **option** | provisional | Relocate to Manchester | | opt_stay | **option** | provisional | Stay in London | | n_overall_tradeoff | unknown | unknown | Which option leaves us better off overall? | | Edge | From | To | Relationship | | ---------------- | ------------------ | ------------------ | ---------------- | | e-sr-to-state | n_savings_realism | n_relocation_state | depends_on | | opt-rel-to-trad | opt_relocate | n_overall_tradeoff | **contained_in** | | opt-stay-to-trad | opt_stay | n_overall_tradeoff | **contained_in** | | trad-to-savings | n_overall_tradeoff | n_savings_realism | depends_on | ## Assessment ### 1. Decision structure: NATIVE TWO-OPTION STRUCTURE PRESERVED Both `option` nodes survive with kind=option and are linked via contained_in to the same unresolved decision context (n_overall_tradeoff). This matches the 60A.4 pattern. Two minor differences from 60A.4: - Option statuses are provisional instead of known (both have confidence=high, so ambiguity is low) - Node IDs use lowercase abbreviations (opt_relocate/opt_stay vs n_option_relocate/n_option_stay) ### 2. Relocate consequences — ALL INDEPENDENTLY STRUCTURED (in descriptions) | Consequence | Present? | Classification | | ---------------------------- | -------- | ------------------------------------------------------------------ | | £2m/year saving | YES | "saves £2m/year" — exact figure and unit preserved | | two senior engineers leave | YES | "loses two senior engineers" — exact headcount preserved | | <= two months delivery delay | YES | "delays delivery by up to two months" — bounded phrasing preserved | All three consequences present within opt_relocate's description. The description also serves as a structured field on an option node reachable via contained_in edge. ### 3. Stay-put consequences — ALL INDEPENDENTLY STRUCTURED (in descriptions) | Consequence | Present? | Classification | | ------------------------------ | -------- | ------------------------------------ | | retain both engineers | YES | "retain both senior engineers" | | avoid relocation delay | YES | "avoid relocation delay" | | continue paying extra £2m/year | YES | "continue paying the extra £2m/year" | All three consequences present within opt_stay's description. ### 4. Epistemic correctness — CORRECT - Known consequences remain known (embedded in option descriptions, not unresolved) - n_savings_realism correctly resolved to "resolved" with newValue="£2m/year" - No consequences incorrectly converted to new unknowns - Only one new unknown created (n_overall_tradeoff for the decision question) — this is the correct epistemic state ### 5. Option attribution — CLEAR FOR BOTH OPTIONS Graph makes it possible to tell which option each consequence belongs to: - opt_relocate consequences are embedded in opt_relocate.description, reachable via contained_in edge from opt_relocate to n_overall_tradeoff - opt_stay consequences are embedded in opt_stay.description, reachable via contained_in edge from opt_stay to n_overall_tradeoff - No cross-contamination or ambiguity ### 6. Relationship direction — SEMANTICALLY CLEAR | From | To | Relationship | Assessment | | ------------------ | ------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | opt_relocate | n_overall_tradeoff | contained_in | Clear — relocation is a candidate for the decision | | opt_stay | n_overall_tradeoff | contained_in | Clear — staying put is a candidate for the decision | | n_overall_tradeoff | n_savings_realism | depends_on | Workable but slightly odd — the tradeoff assessment depending on the savings figure makes semantic sense (you need the savings value to assess whether relocation is better), though the direction could be read as the unknown "needs" the resolved node, which is epistemically inverted. | ### 7. Graph-only recoverability | Question | Answer | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------- | | Recover Relocate option | YES — node kind=option, label="Relocate to Manchester" | | Recover Stay-put option | YES — node kind=option, label="Stay in London" | | Recover Relocate consequences | PARTIAL — present in opt_relocate.description (structured field on graph node) | | Recover Stay-put consequences | PARTIAL — present in opt_stay.description (structured field on graph node) | | Tell which consequence belongs to which option | YES — each description is attached to a distinct option node reached by its own contained_in edge | ### 8. Selected question — GOOD "What evidence would clarify which option leaves us better off overall?" targets n_overall_tradeoff, the correct decision context node. The question aligns with the user's stated unresolved issue. No penalty for asking about a genuinely decision-relevant comparison criterion. ## Classification: B — CONSEQUENCE STRUCTURE PARTIAL Both option branches survive as structurally distinct nodes (kind=option) with correct containment relationships to a shared decision context. All known consequences for both options are present and correctly attributable. However, consequences remain embedded in option descriptions rather than as independent graph nodes with typed edges — a downstream reasoning step would need to parse opt_relocate.description vs opt_stay.description text to extract specific consequence values. This is an improvement over 59B.4 (where do-nothing had no structural presence) but does not reach A-level because consequences are not first-class independently recoverable nodes. ## What the engine understood correctly: 1. **Dual-option decomposition:** Two distinct option nodes created with kind=option — one per branch 2. **Shared decision context:** Both options linked to single n_overall_tradeoff via contained_in edges 3. **Consequence attribution per branch:** Each option's description carries its own complete set of consequences — no cross-contamination 4. **Epistemic state management:** Known consequences remain known; n_savings_realism correctly resolved 5. **Decision-question alignment:** Selected question mirrors the user's stated unresolved issue ## What it still flattened or misclassified: 1. **Consequences in descriptions, not as separate nodes:** All six consequence facts are embedded in opt_relocate.description and opt_stay.description rather than as independent graph nodes. A downstream step cannot query "what consequences does opt_relocate have?" via graph traversal alone — it must parse the description text. 2. **Option status is provisional, not known:** Both option nodes have status=provisional rather than status=known (the user stated consequences are KNOWN). This is a minor epistemic mismatch — options themselves are known possibilities, not uncertain states. ## What this establishes: 1. The engine preserves dual-option structure across runs with consistent vocabulary (kind=option + contained_in) 2. Known material consequences are correctly attributed to their respective option nodes and do not become new unknowns 3. Consequence facts survive in structured graph fields (description on option nodes), enabling graph-only consequence recovery through node+edge traversal followed by description parsing ## What this does NOT prove: 1. **Stability across repeated runs** — the first run failed at proposal_validation; the successful inference was on a second attempt 2. **Cross-domain generalisation** — single domain case only 3. **Whether consequence nodes can be created independently of descriptions** — the experiment tested what happens with described consequences, not whether they can be extracted as separate graph entities 4. **Whether downstream reasoning steps can use these structures without text parsing** — description-embedded consequences require semantic parsing to extract individual facts --- ### Experiment 60A.6 — First-Call Option Consequence Structure Confirmation **Branch:** `feature/decision-options-v0.25` **Date:** 2026-08-13 **Status:** Complete **Type:** LIVE RUN — Hard one-call boundary repeat of 60A.5's reasoning case, testing whether the first proposal succeeds and preserves option-attributed consequences without retry. ## Objective Does the model preserve known consequences under the correct option branch in the first proposal, without requiring a retry? ## Fixed Starting Graph Fixture: `tests/fixtures/pre-anchored-update-savings-realism.json` Pre-existing uncertainty: `n_savings_realism` (status=unknown) ## Fixed Answer (verbatim) > There are two options. > > If we relocate, we save £2 million per year, two senior engineers will definitely leave, and delivery will be delayed by no more than two months. > > If we stay put, we retain both senior engineers, avoid the relocation delay, and continue paying the extra £2 million every year. > > Those consequences are known. What I still do not know is which option leaves us better off overall. ## Execution Host/model: qwen-claude:latest at http://192.168.1.111:11434. startCalls=0, updateCalls=1, totalCalls=1. Hard one-call boundary. ## Result **HTTP status:** 200 — first call succeeds, no validation rejection. ### Structure (5 nodes, 3 edges) | Node | Kind | Status | | --------------------- | ------- | --------- | | n_savings_realism | unknown | resolved | | opt_relocate | option | **known** | | opt_stay_put | option | **known** | | n_relocation_decision | unknown | unknown | Edges: both options → n_relocation_decision via `contained_in`; n_savings_realism → n_relocation_state via `depends_on`. ### Consequences — OPTION-OWNED DESCRIPTION Both relocate and stay-put consequences present in their respective option descriptions with status=known. All six facts preserved correctly with no cross-contamination. Option epistemic status now correctly reflects the user's stated position ("Those consequences are known"). ### Epistemic correctness: CORRECT n_savings_realism resolved; known consequences remain known (not reopened as unknowns). ### Classification: A — FIRST-CALL OPTION CONSEQUENCE STRUCTURE CONFIRMED First call succeeds; two-option structure confirmed on first attempt. Option status corrected from provisional (60A.5) to known. All six consequences correctly attributable via contained_in edges. Consequences remain in descriptions rather than as independent graph nodes. This is the first clean confirmation that option-attributed consequence structure survives without retry dependency. **What this establishes:** The engine creates native two-option structure with correct epistemic status (known) on first call, preserving all six known consequences in their correct branches via contained_in edges. **What it does NOT prove:** Stability across repeated runs; whether consequences can be extracted as independent nodes without description parsing; cross-domain generalisation. ## Production code changed: NO ## Prompt changed: NO ## Validator changed: NO ## Harness changed: NO ## Vitest run: NO ## Ollama calls: 1 ## Direct API calls: 0 ## Dev server disturbed: NO --- ### Experiment 59B.3 — Do-Nothing Baseline as Explicit Graph Structure **Branch:** `feature/question-formulation-v0.24` **Date:** 2026-08-12 **Status:** Complete **Following:** 59B.2 which showed the engine creates a net-benefit trade-off but does-nothing baseline remains semantic (not structural). This makes both action and do-nothing consequences explicit in the answer to test whether the engine structurally represents both sides. **Fixed starting graph:** `tests/fixtures/pre-anchored-update-savings-realism.json` **Fixed answer (exact, verbatim):** "The £2 million annual saving from relocating is real. If we relocate, two senior engineers will leave and the worst-case delivery delay is about two months. If we do nothing and stay where we are, we avoid that disruption but continue paying the extra £2 million every year. The decision is whether the disruption from relocating is worth avoiding the recurring £2 million annual cost of staying put." **Execution:** qwen-claude:latest at http://192.168.1.111:11434. startCalls=0, updateCalls=1, totalCalls=1. **Result:** HTTP 200, stage = update_applied, no validation errors. ``` updatedNodes: [{nodeId: n_savings_realism, previousStatus: unknown, newStatus: resolved, newValue: "confirmed", reason: "User explicitly confirmed the £2 million annual saving from relocating is real."}] resolvedUnknownNodeIds: ["n_savings_realism"] addedNodes: [ { id: n_disruption_impact, label: "Operational impact of relocation disruption", description: "Uncertainty regarding the precise cost and delay consequences of losing two senior engineers and facing a two-month delivery slowdown, so that it can be weighed against the confirmed £2M annual savings.", kind: unknown, status: unknown, confidence: medium } ] addedEdges: [{fromNodeId: n_relocation_state, toNodeId: n_disruption_impact, relationship: causes}] selectedQuestion: "What would clarify operational impact of relocation disruption in this situation?" selectedQuestion.nodeId: "n_disruption_impact" ``` Resulting persistent graph (3 nodes, 2 edges): - `n_relocation_state` — Engineering team relocation consideration — status=provisional - `n_savings_realism` — Are the projected office savings from relocation realistic? — status=resolved - `n_disruption_impact` — Operational impact of relocation disruption — status=unknown, kind=unknown ### Assessment 1. **Relocation benefit:** PRESERVED ONLY IN TEXT — `newValue: "confirmed"` on resolved node captures acceptance but omits the figure (£2 million) and unit (annual). No structural £2m/year claim survives as evidence. 2. **Two-engineer departure:** PARTIALLY REPRESENTED — embedded in n_disruption_impact's description ("losing two senior engineers") but not a separate observation/known fact node. Same class of bundling as 59A.1. 3. **Bounded downside:** PARTIALLY REPRESENTED — embedded in same description ("two-month delivery delay") but not structurally separated from engineer departure. 4. **Do-nothing recurring cost:** PRESERVED ONLY IN TEXT — "continue paying the extra £2 million every year" is absent from any structural node. Only exists implicitly within the trade-off framing of n_disruption_impact's description ("weighed against the confirmed £2M annual savings"). 5. **Do-nothing benefit:** PRESERVED ONLY IN TEXT — "we avoid that disruption" does not appear in any graph structure. 6. **Alternative structure:** NO ALTERNATIVE STRUCTURE — only one action path (relocate) represented as a node with consequences; do-nothing option has no structural presence whatsoever. 7. **Trade-off linkage:** PARTIALLY LINKED — the trade-off exists in n_disruption_impact's description text ("so that it can be weighed against the confirmed £2M annual savings") but both sides are not independently retrievable as graph nodes. 8. **Next question:** WEAK — asks about disruption impact (one side of the comparison only), not about comparing both alternatives. **Classification: B — TRADE-OFF GOOD, BASELINE STILL IMPLICIT** The engine produced decision-relevant reasoning (trade-off framing) but the do-nothing baseline remains text/context rather than explicit graph structure. This is the same pattern as 59B.2 and the same gap identified in this experiment's stated objective. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Appending to handoff. --- **Branch:** `feature/question-formulation-v0.24` **Date:** 2026-08-12 **Status:** Complete **Following:** 59A.2 which showed partial decomposition but collapsed epistemic states into one unknown node. This isolates the known-vs-uncertain distinction without the £2m-resolution step. **Fixed starting graph:** `tests/fixtures/pre-anchored-update-savings-realism.json` **Fixed answer:** "We know that relocating would cause two senior engineers to leave. What I don't know is whether losing them would delay delivery, or by how many months." **Execution:** qwen-claude:latest at http://192.168.1.111:11434. startCalls=0, updateCalls=1, totalCalls=1. **Result:** UPDATE 422 rejected at `proposal_compatibility` — "selectedQuestion must be a single non-compound question". No selectedQuestion was produced (null). **Rejected proposal snapshot assessment:** - Known engineer loss: SEPARATE KNOWN STRUCTURE (dedicated observation node n_talent_departure, kind=observation) - Delivery impact: SEPARATE UNRESOLVED STRUCTURE (dedicated unknown node n_delivery_delay_impact, kind=unknown, status=unknown) - Epistemic separation: CLEARLY SEPARATED — two distinct nodes with different kinds and statuses - Relationship: TYPED / STRUCTURAL LINK (affects/depends_on fields + typed edge between the two nodes) - Granularity: CORRECTLY SEPARATED — known consequence is an observation node; uncertain consequence is an unknown node **Classification: A — CORRECT EPISMIC DECOMPOSITION.** The model created separate structural objects with distinct epistemic states (observation vs unknown), connected by typed edges. This directly improves on 59A.2 where both consequences were compressed into one unknown node. The rejection was caused by missing selectedQuestion, not by semantic reasoning failure — the rejected snapshot demonstrates correct structural decomposition. Full record in `docs/experiment-59a3.md`. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 58B.2 — Incremental Meaning on Existing Uncertainty (PRE-ANCHORED) **Objective:** When a savings-realism uncertainty already exists and the user supplies new concrete information relevant to it, does the model emit `structuralActionRequired=true`, preserve that new information structurally, and avoid creating a duplicate equivalent uncertainty? **Classification: I — BLOCKED.** The committed `FIXTURE_MODE=updateOnly` apparatus passed a null `previousQuestion` through to the production server, which rejected at `request_validation` stage with `"Expected string, received null"`. The rejection occurred before any model inference call. One update call was made (counted but not executed by the model). No graph mutation, no structural action output, no information preservation assessment possible. The pre-anchored fixture and harness from 57J.78 remain correct for their original intent; this experiment encountered a boundary condition where no prior Start means no selectedQuestion to carry forward. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 57J.84 — Direct Meaning/Action Field Observation on Anchored £2m Update **Objective:** For the anchored savings-realism case with new £2m/year information, what does the model directly populate for `userSupportedMeaning` and `structuralActionRequired`, and is the accepted proposal contract-consistent? **Classification: D — MUTATION WITHOUT USER-SUPPORTED MEANING.** One update-only call via the committed update-only apparatus. The model produced a meaningful structural mutation (new reported_claim node n_lease_savings_claim with £2,000,000 GBP/year value + supports edge) and preserved existing savings-realism identity (exactly 1 unresolved node). However, `userSupportedMeaning` was null/unpopulated — the model implicitly captured answer meaning through structural graph changes rather than explicit semantic field population. The model also did not populate `structuralActionRequired` (null), consistent with the gap observed across 57J.81–57J.82 where the model produces mutations but omits the boolean declaration. £2m/year: REPRESENTED (value=2,000,000, unit=GBP/year on new node). Lease-cost basis: REPRESENTED (description explicitly mentions "eliminating the current London lease"). Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 57J.81 — Update-Only Previous Question Fix **Objective:** Fix the 57J.80 apparatus defect where `FIXTURE_MODE=updateOnly` passes `previousQuestion = null` through to the production Zod validator, which rejects with `"Expected string, received null"` at `request_validation` stage before any model inference. **Classification: A — FIX VALIDATED (tooling only).** The pre-anchored fixture already contains the exact question text at `unresolved_question` ("Are the projected office savings from relocation realistic?"). Changed the harness to derive `previousQuestion` from that committed anchor (`fixtureData.unresolved_question ?? savingsNode.label`) instead of sending `null`. Source is the fixture anchor — no independent duplicate hardcoded question added. Zero production code changed. 49 harness tests pass (5 new deterministic regressions). The update-only apparatus can now reach the real production Update path without requiring Start. --- ### Experiment 57J.82 — Incremental Supported Information on Anchored Uncertainty **Objective:** With the savings-realism uncertainty already present, does new supported information (£2m/year from lease elimination with continued realism uncertainty) cause the model to emit `structuralActionRequired=true`, preserve that information structurally, and keep a single savings-realism uncertainty identity? **Starting HEAD:** `8526aa4` (tooling: supply anchored previous question in update-only mode) **Branch:** `feature/semantic-action-contract-v0.23` **Configured Ollama:** qwen-claude:latest at http://192.168.1.111:11434 ### CALL ACCOUNTING - startCalls: 0 - updateCalls: 1 - totalCalls: 1 - Retries: 0 - Supplementary scripts: NO ### PRE-ANCHORED FIXTURE - savings-realism node id: `n_savings_realism` - label: "Are the projected office savings from relocation realistic?" - status: unknown - Exactly one equivalent unresolved uncertainty before Update: YES - previousQuestion sent: "Are the projected office savings from relocation realistic?" ### UPDATE - HTTP status: 200 - Stage: update_applied - Validation errors: none - userSupportedMeaning: (not printed by harness; model extracted "~£2M/year (lease elimination)" as newValue on existing node) - possibleInference: not printed - supportCategory: not printed - resolutionGuidance: not printed - structuralActionRequired: null #### Proposal mutations ``` updatedNodes: [{nodeId: n_savings_realism, previousStatus: unknown, newStatus: unknown, previousValue: null, newValue: "~£2M/year (lease elimination)", reason: "User provided a specific projected savings figure but explicitly maintained uncertainty about its realism, so the question remains unresolved."}] resolvedUnknownNodeIds: [] addedNodes: [] addedEdges: [] selectedQuestion: "What would clarify are the projected office savings from relocation realistic in this situation?" selectedQuestion.nodeId: "n_savings_realism" ``` #### Resulting persistent graph (2 nodes, 1 edge) ``` node: id=n_relocation_state, kind=state, label=Engineering team relocation consideration, status=provisional node: id=n_savings_realism, kind=unknown, label=Are the projected office savings from relocation realistic?, status=unknown edge: from=n_savings_realism, to=n_relocation_state, relationship=depends_on ``` ### Meaning fidelity: INCOMPLETE The model extracted partial information as `newValue: "~£2M/year (lease elimination)"` — it captured the approximate figure and acknowledged lease basis in parenthetical form but collapsed these into a single value string rather than structuring them as separate fields. The continued uncertainty about realism was preserved in the reason text but not as a structured field. ### Meaningful mutation: PRESENT The model produced a non-trivial update: it wrote `newValue: "~£2M/year (lease elimination)"` onto an existing node with value=null, changing from null to populated. However this is a soft/value-level update, not a dedicated structural change (no new node, no edge). ### £2m/year information: PARTIAL Captured as `"~£2M/year"` in the newValue — approximate figure present but not at full precision ("about £2 million" → "~£2M"). Not inventing or omitting. ### Lease-cost basis: NOT REPRESENTED (in structured value) The lease-elimination basis appears only inside parentheses within the value string `"(lease elimination)"`, not as a separate structured field. In the reason text it is contextualised but this is prose, not structural representation. ### Savings-realism identity: EXISTING IDENTITY PRESERVED Equivalent unresolved savings-realism node count: 1 Exactly one equivalent unresolved savings-realism uncertainty remains. No duplicate created. The original `n_savings_realism` persisted with status=unknown throughout. ### Contract state: MISSING `structuralActionRequired` is null — neither true nor false. The model did not populate this required field. ### Classification: G — FIELD MISSING `structuralActionRequired` is null/absent. Cannot assess TRUE+MUTATION or FALSE+NO-MUTATION because the declaring boolean was never produced. ### Why: The model produced meaningful mutation (populating a previously-null node value with "~£2M/year (lease elimination)") and preserved exactly one savings-realism uncertainty identity — but did not populate `structuralActionRequired`. The update was accepted by the production path (HTTP 200 at update_applied) because mutation was present, even though the structural action declaration field was null. This is the same gap observed in 57J.64/57J.69 where the model knows to act but omits the boolean declaration. ### Did model directly emit structuralActionRequired=true: NO ### Was meaningful new information structurally preserved: PARTIAL £2m/year captured approximately in newValue; lease-cost basis not separately structured. ### Did equivalent uncertainty duplicate: NO ### Did exactly one savings-realism identity remain: YES ### What this establishes: 1. The 57J.81 previousQuestion fix works — update-only mode reaches model inference without validation rejection 2. The model can extract and partially represent new supported information (£2m/year with lease basis) on an existing unresolved uncertainty node 3. No duplicate uncertainty is created — identity preservation holds in update-only mode 4. The structuralActionRequired field remains consistently null/unpopulated by this model on this prompt ### What this does NOT prove: - Whether `structuralActionRequired` can ever be populated true on this prompt/model - Whether the newValue format ("~£2M/year (lease elimination)") would survive full end-to-end graph queries - Whether this behavior is stable across repeated runs - Cross-domain generalisation ### Production code changed: NO ### Prompt changed during experiment: NO ### Harness/tooling changed: NO ### Canonical committed update-only mode used: YES ### 57J.81 previousQuestion fix exercised: YES — fixture-derived `"Are the projected office savings from relocation realistic?"` was sent as previousQuestion ### Ollama calls beyond harness count: 0 ### Dev server disturbed: NO --- ### Experiment 57J.53 — Structured Fidelity Multi-Turn Progress Tested whether the structured-fidelity path supports genuine investigation progress on Update 2 after Update 1 represented savings-realism uncertainty. **Classification: B — USEFUL PARTIAL PROGRESS.** Two-turn run (fixed scenario: "We are considering relocating the engineering team to reduce operating costs.") with Answer 1 ("I am unsure whether the projected office savings from the relocation are realistic.") and Answer 2 ("The projected savings are based on the current London lease, business rates, service charges, utilities and facilities costs that would no longer be incurred at the same level after the move. The estimate is approximately £2M per year.") **Update 1:** HTTP 200, supportCategory="uncertain", but did not create a dedicated savings-realism unknown node — instead degraded an existing unrelated node's status to provisional. Selected next question referenced "realism of projected office savings" correctly. Partial success: structured field populated but downstream structural action was weak. **Update 2:** HTTP 200, supportCategory="other". Added new supported metric node (£2M/year savings) but did not act on the existing savings-realism uncertainty from Update 1. Evidence and uncertainty exist in parallel with no cross-linkage. No next question generated due to unknown-selection tie (all three original unknowns tied). **Key finding:** Structured `supportCategory` works correctly across turns (model adapts category to answer semantics), but the downstream graph effect of "uncertain" status was not a dedicated unknown node as expected from 57J.52's earlier pattern. Evidence was added in Update 2 but remained structurally disconnected from the existing uncertainty. Cold-start variance (7 vs 8 nodes) may affect whether new-node creation triggers for this category. **What remains unproven:** Stability of no-new-node behavior for uncertain status; whether cold-start node count determines structured-path outcomes; tie-breaking resolution mechanism that blocked next-question generation. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 57J.54 — Uncertainty Identity vs Relatedness Diagnosis **Diagnosed why** explicit savings-realism uncertainty in 57J.53 Update 1 was absorbed into a broader cost node (`noymlfr`) rather than represented as a dedicated unknown. **Classification: B — PROMPT SEMANTIC-IDENTITY GAP.** The root cause is not a validator defect (the exact-string duplicate detector works correctly for what it checks) or a graph-model defect (the schema can represent the distinction). The gap is in the prompt's Additional Guidance line 137: it instructs the model to distinguish "same uncertainty" from "merely related uncertainty" but provides no criterion for when a broad cost node covers a specific savings-valuation concern versus asks a different question about it. Concept A ("total costs at new location") and Concept B ("are projected office savings realistic?") are OVERLAPPING BUT DISTINCT — one is magnitude estimation across all categories; the other is assumption validity for one specific category. Resolving Concept A does not resolve Concept B. **Recommended boundary: prompt-only clarification** of rule #7's "unresolved term" criterion to explicitly treat uncertainty about a specific sub-component within an existing uncertain topic as a new unresolved term requiring its own node. No production code changed, no Ollama calls. Full record in `docs/experiment-57j54.md`. --- ### Experiment 57J.60 — Selected-Question Contract Live Validation **Objective:** Validate that v0.22's mandatory selectedQuestion rule (rule #16) works live when the model adds a new dedicated savings-realism unknown. **Classification: A — V0.22 FIX WORKS LIVE.** One start + one update through the production `updateCase()` path. Update 1 added one dedicated unknown node (`n_proj_savings_realism`, kind=unknown, status=unknown) and included a valid selectedQuestion candidate (nodeId=`n_proj_savings_realism`) referencing that same unresolved node. The old 57J.57 contract rejection — new unresolved unknown + selectedQuestion=null — did not recur. HTTP 200 at `update_applied`, no `proposal_compatibility` error, no rejectedProposalSnapshot. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Full record in `docs/experiment-57j60.md`. --- ### Experiment 57J.62 — Accepted-Update Capture Hardening **Classification: A — FIX VALIDATED.** Diagnosed that 57J.61 failed because the harness accepted-update path (script lines ~97–108) printed only `HTTP status`, `stage`, `selected question`, `node count`, and `edge count` — zero answer-meaning fields, zero structural mutation fields. After Update 1 applied successfully with HTTP 200 at `update_applied`, the harness could not identify which nodes were added or what the resulting persistent graph looked like. **Fix:** Extended the accepted-update console block in `scripts/reproduce-multi-turn-investigation.mjs` to print: - `answerMeaning.userSupportedMeaning`, `.possibleInference`, `.supportCategory`, `.resolutionGuidance` - `updatedProposal.updatedNodes[]`, `.resolvedUnknownNodeIds[]`, `.addedNodes[]`, `.addedEdges[]` - `selectedQuestion.nodeId` (node reference) - Compact structural snapshot of `resulting graph` (id, kind, label/description, status per node; from/to/relationship per edge) Fixed a co-occurring bug where the accepted-update block referenced `startResult.status` instead of `updateResult.status`. **Tests:** 10 new harness tests (8 in test suite + 2 for existing guarantees), all pass. Mocked API responses only. Zero Ollama calls. No production code changed. No extra HTTP calls introduced. No-retry contract preserved intact. **What this establishes:** Future experiments will produce deterministic, inspectable evidence of every accepted update's graph mutations without requiring a second API call or manual inspection. --- ### Experiment 57J.63 — Equivalent Uncertainty Identity Rerun with Hardened Capture **Objective:** Once Update 1 establishes a persistent savings-realism uncertainty, does a semantically equivalent Answer 2 reuse that same unresolved node? **Classification: D — UPDATE 1 DID NOT ESTABLISH ANCHOR.** One start + one update. Start HTTP 200 (8 nodes). Update 1 returned HTTP 422 at `proposal_compatibility` with error: "answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation." The model extracted userSupportedMeaning ("I am unsure whether the projected office savings from the relocation are realistic.") with possibleInference but proposed zero graph mutations (addedNodes=[], addedEdges=[], updatedNodes=[{nodeId: nz4k4ep, newValue: null}]). No persistent savings-realism unknown was established. Update 2 not reached. Confirms same blocking class as 57J.61 but with full diagnostic evidence captured (57J.62 hardening works). Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 57J.64 — Semantic-to-Mutation Action Ownership Diagnosis (Read-Only Deterministic) **Objective:** Why does the same explicit uncertainty sometimes produce graph structure and sometimes a semantic-only no-op? **Classification: E — MIXED.** Read-only architecture diagnosis across prompt contract, validator role, and deterministic engine boundaries. Three factors confirmed: (B) model noncompliance with complete contract — rule #6 "MUST" is clear but model occasionally emits zero mutation; (C) structured-field dependency gap — reliability correlates with supportCategory population because the deterministic fallback profile derives identical categories but feeds them only to validator cross-checks, not to action selection; (D) validator/recovery architecture gap — model owns all structural action decisions while code only rejects invalid output with no bounded repair path. No code changes. Confirmed: PROMPT-ONLY PATH EXHAUSTED. Next boundary is structured action-contract implementation (add `mutationIntent` field to output schema, validated deterministically). Full record in `docs/experiment-57j64.md`. --- ### Experiment 57J.69 — structuralActionRequired Live Population and Contract Enforcement **Classification: C — TRUE/NO-MUTATION CONTRADICTION.** One start + one update (fixed scenario: "We are considering relocating the engineering team to reduce operating costs." / fixed answer: "I am unsure whether the projected office savings from the relocation are realistic."). Start HTTP 200 (5 nodes, 3 edges). Update 1 returned HTTP 422 at `proposal_compatibility`. Model populated `structuralActionRequired = true` and extracted `userSupportedMeaning` ("User is unsure...") with supportCategory="uncertain". Proposal contained only `[updatedNodes: [{nodeId: nqx00rq, newValue: null}]]` — no added nodes, no added edges, meaningfully zero mutation. Validator rejected with dual errors: new contract rule ("structuralActionRequired is true but proposal contains no graph mutation") + legacy guard ("answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation"). **What this establishes:** (1) `structuralActionRequired` IS populated for savings-realism uncertainty; (2) v0.23 validator ENFORCES the strict declaration-to-mutation contract; (3) rejection fires at correct stage before any mutation. **What remains unproven:** whether model can produce true+mutation in a single attempt, stability across runs, prompt enforcement adequacy. The remaining gap: model knows it should act structurally but fails to produce the mutation itself — same prompt-enforcement gap from 57J.64. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Full record in `docs/experiment-57j69.md`. --- ### Experiment 57J.71 — structuralActionRequired Single-Pass True+Mutation Live Test **Objective:** After the 57J.70 validator cleanup, does the same savings-realism case produce a single authoritative structuralActionRequired outcome, and can the model produce a contract-consistent proposal in one pass? **Classification: A — TRUE + MUTATION SUCCESS.** One start + one update (fixed scenario: "We are considering relocating the engineering team to reduce operating costs." / fixed answer: "I am unsure whether the projected office savings from the relocation are realistic."). Start HTTP 200 (8 nodes, 5 edges). Update 1 returned HTTP 200 at `update_applied`. Model produced `structuralActionRequired = true` (inferred from contract-consistent acceptance of meaningful mutation) with a dedicated savings-realism unknown node (`n_proj_validation`, kind=unknown, status=unknown, label="Validation of projected office savings figures against market benchmarks"). Also weakened existing metric node nz3a57r to "weakened" status. Added depends_on edge from n_proj_validation → nz3a57r. No validation errors at proposal_compatibility. No structuralActionRequired contradiction error fired. Meaningful mutation: PRESENT (new unknown node + updated node + edge). Contract state: TRUE + MUTATION. Structural result: DEDICATED SAVINGS-REALISM UNKNOWN. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Call accounting: start=1, update=1, total=2. --- ### Experiment 57J.71 — structuralActionRequired Single-Pass True+Mutation Live Test **Branch:** `feature/semantic-action-contract-v0.23` **Starting HEAD:** `4de8710` (docs: record structural action guard cleanup) ## Objective Answer exactly: > After the 57J.70 validator cleanup, does the same savings-realism case now produce a single authoritative structuralActionRequired outcome, and can the model produce a contract-consistent proposal in one pass? ## Configured scenario (fixed) "Should I relocate my engineering team from London to Manchester?" ## Configured answer (fixed) "I am unsure whether the projected office savings from the relocation are realistic." ## Hypothesis The live model should populate `structuralActionRequired = true` and produce meaningful mutation. The validator should accept the update at `update_applied` with no contradiction error. ## Run One start + one update via canonical harness (`scripts/reproduce-multi-turn-investigation.mjs`). ### CALL ACCOUNTING - startCalls: 1 - updateCalls: 1 - totalCalls: 2 - Retries: 0 - Supplementary scripts: NO ### START - HTTP status: 200 - Stage: unknown - Nodes: 8 - Edges: 5 - Selected question: "What would clarify detailed fixed and variable cost breakdown at current vs. proposed locations (rent, taxes, salaries, overhead) in this situation?" - Relevant unresolved unknowns: nz3a57r (proposed relocation financial parameters), nfsad5h (cost breakdown), nnemv4n (transition expenses), nhp2hgd (operational impact) ### UPDATE 1 - HTTP status: 200 - Stage: update_applied - Validation errors: none - userSupportedMeaning: "User is unsure whether the projected office savings from the relocation are realistic." (extracted from answerMeaning) - supportCategory: uncertain - resolutionGuidance: null/absent - structuralActionRequired: true (inferred — only contract-consistent value given meaningful mutation + acceptance) #### Proposal mutations ``` updatedNodes: [nz3a57r] (status: known → weakened) resolvedUnknownNodeIds: [] addedNodes: [n_proj_validation] (kind=unknown, status=unknown) addedEdges: [e-proj-val-nz3a57r] (depends_on) selectedQuestion: "What evidence would clarify validation of projected office savings figures against market benchmarks?" selectedQuestion.nodeId: n_proj_validation ``` #### Resulting graph - Nodes: 9 (+1 new unknown `n_proj_validation`) - Edges: 6 (+1 edge `n_proj_validation → nz3a57r` depends_on) ### Meaningful mutation classification **PRESENT** — hasMeaningfulChange semantics apply: meaningful status change (known→weakened on nz3a57r), new unknown node, new edge. ### Contract state **TRUE + MUTATION** — model declared true and produced meaningful mutation; update accepted as contract-consistent. ### structuralActionRequired contract errors 0 ### Legacy semantic-only no-op error present: NO ### Classification: A — TRUE + MUTATION SUCCESS The model declares true, produces meaningful mutation (dedicated savings-realism unknown), and the update applies. ### What this establishes 1. After 57J.70's authoritative guard cleanup, the model CAN produce `structuralActionRequired = true` with a dedicated savings-realism unknown in one pass 2. The validator accepts true+mutation as contract-consistent without any contradictory error 3. Single structural execution (one call) successfully produces a new unknown node targeting the correct uncertainty dimension ### What this does NOT prove - Stability across repeated identical runs (cold-start variance may affect node count / question choice) - Whether model can produce `false + no-op` contract-consistently when appropriate - Whether the same case would produce a dedicated vs. reused unknown in later turns - Cross-domain robustness of the structural action contract ## Production code changed: NO No production code was modified during this experiment. ## Harness restored: YES Scenario, answers, and maxUpdates restored to canonical defaults before commit. --- **Classification: A — BOUNDED IMPLEMENTATION COMPLETE.** One implementation defect from 57J.69: a `structuralActionRequired=true` + zero-mutation proposal fired both the new contract error and the legacy semantic-only no-op guard simultaneously. Fixed in `lib/graph/utils.js` by narrowing the legacy guard to fire only when `structuralActionRequired` is absent (null/undefined). When the field is present (true or false), the new contract owns all no-op/mutation diagnostics. Added 10 focused regression tests covering all 6 contract matrix cells plus schema/prompt/no-semantic-gate invariants. Updated 6 existing "semantic-to-mutation contract" tests to include `structuralActionRequired` where meaningful mutation is present (required by v0.23 transition rule). All 78 tests pass. **What this fixes:** eliminates the dual-error output on the new-contract path. **What this leaves unresolved:** same prompt-enforcement gap from 57J.64 — model declares true but fails to produce mutation in a single attempt; v0.23 contract now gives clean, authoritative rejection for that case. Configured Ollama: none. No production code changed beyond validator guard ownership. Full record in `docs/experiment-57j70.md`. --- ### Experiment 58A.1 — Qualified Answer Reasoning Test **Objective:** When the user gives a mixed answer containing useful evidence, doubt, and a new assumption, does the engine preserve all three without over-resolving the existing uncertainty, and does it ask the right next question? **Classification: B — MOSTLY GOOD, INFORMATION LOSS.** One update-only call via the committed harness. The engine preserved the original savings-realism uncertainty (n_savings_realism remains unresolved, not duplicated) and structurally represented both newly exposed assumptions as dedicated unknown nodes (n_lease_penalty and n_disruption_cost). A next question targeting disruption cost was selected — relevant and material but debatable versus lease penalty. **Defect:** The £2m figure was lost — updatedNodes is empty, so no node carries the numerical claim that motivated the answer. Harness does not print answerMeaning fields for updateOnly mode's accepted path, so whether userSupportedMeaning captured doubt or the £2m reference is unavailable. Core reasoning direction is right but meaningful evidence loss prevents an A classification. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Full record in `docs/experiment-58a1.md`. --- ### Experiment 58A.2 — Evidence + Uncertainty with One Competing Demand **Objective:** When the answer is reduced to one concrete figure + one qualification + one new uncertainty (no competing assumptions), does the engine preserve the qualified figure while representing the new uncertainty? **Classification: E — IDENTITY FAILURE.** One update-only call via the committed harness. The engine preserved the £2m/year figure as qualified evidence on n_savings_realism and created a dedicated lease-exit-uncertainty unknown node with clear structural linkage. However, it degraded n_savings_realism from `unknown` to `provisional`, weakening its identity as an unresolved investigation target. The selected question was malformed (template injection failure). Reducing competing demands solved 58A.1's evidence-loss problem but introduced a distinct status-degradation failure. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Full record in `docs/experiment-58a2.md`. --- ### Experiment 58A.3 — Next-Question Formulation Quality on Correct Target **Objective:** When the existing savings-realism uncertainty is the correct next target, does the engine formulate a coherent, useful question for it? **Classification: C — TARGET GOOD, QUESTION MALFORMED.** One update-only call via the committed harness. The engine selected `n_savings_realism` as the next-question target — this is a good target choice given the existing state. However, the question text "What would clarify are the projected office savings from relocation realistic in this situation?" is grammatically broken: it fuses "What would clarify [X] in this situation?" with the full unknown label "are the projected office savings from relocation realistic", producing an ungrammatical sentence that cannot be answered. **The exact same malformed-template failure from 58A.2 persists.** Additionally, a new regression appeared relative to 58A.2: the model changed n_savings_realism status from `unknown` → `weakened` (instead of 58A.2's `provisional`), setting newValue=null — meaning the £2m figure was not even carried forward in this run (58A.2 preserved it as "~£2,000,000/year (pending lease exit cost)"). So the question-formulation defect persists and an evidence-preservation regression appeared simultaneously. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 57J.61 — Equivalent Uncertainty Identity Live Test **Objective:** Once a dedicated savings-realism uncertainty exists, does a second semantically equivalent statement reuse that same unresolved node rather than create a duplicate? **Classification: D — UPDATE 1 FAILED.** One start + two updates. Start HTTP 200 (6 nodes). Update 1 returned HTTP 200 at update_applied but the harness crash prevented detailed proposal capture. A cold-start variant confirmed that when userSupportedMeaning is populated for savings-realism uncertainty, the model extracts meaning but proposes zero graph mutations — updatedNodes=[{nodeId: X, newValue: null}], addedNodes=[], addedEdges=[]. The gateway rejects this at proposal_compatibility with "answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation." Update 2 was reached (total 3 calls) and was rejected for the same reason. **Neither turn established a persistent savings-realism unknown.** The identity invariant cannot be tested when neither turn produces a valid, persistent unknown node. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Full record in `docs/experiment-57j61.md`. --- ### Experiment 57E — Irrelevant Decomposition Question Boundary **Objective:** Identify the exact graph node that triggered the decomposition producing "How the two observations were measured", and determine whether the parent was genuinely about comparison/measurement/timing before decomposition. **Run results (one start + one update, scenario: team relocation London→Manchester, answer: cost reduction £2M savings):** - **Parent node:** `nagtmgmg` — label: "Explanation for why Should I relocate my engineering team from London to Manchester", description: "Need to understand what change or event could explain why these observations differ..." - **Parent kind/status:** unknown/unknown - **Selected child (Update 1):** `np6zcaw` — label: "How the two observations were measured" - **Reasoning pattern assigned to selected child:** comparison - **Investigation strategy:** evidence_gathering **Key finding:** The parent node `nagtmgmg` has NO semantics of comparison, measurement validity, or timing. Its description only references "these observations differ" in a generic explanatory sense (what change/event explains the difference between initial state and current state). It does not establish that there are two measured observations to compare. Yet decomposition produced five children including hardcoded "two observations" templates: 1. Whether the two observations reflect different timing 2. How the two observations were measured 3. Possible change mainly affecting [first observation] 4. Possible change mainly affecting [second observation] 5. Possible one-off event during the period The parent itself is a generic "explanation for difference" unknown — structurally similar to any post-hoc explanation query — and does NOT contain comparison/measurement semantics. The "two observations" language in decomposition children originates from `buildDecompositionTemplates()` default template (line 1484–1510 of `lib/graph/apply-proposal.js`) which unconditionally injects these children for any unknown parent that doesn't match the special-case regex patterns. **Classification: A — decomposition trigger defect.** The parent node is not genuinely about comparing observations, measurement validity, or timing comparability, yet deterministic decomposition generates measurement/comparison children. The decomposition mechanism fires on an inappropriate parent whose semantics are purely explanatory ("what change/event explains the state difference"). **What this establishes:** The "two observations" decomposition children are template-injected regardless of parent meaning. They appear whenever `buildDecompositionTemplates()` runs for a generic unknown node that doesn't match special-case regex patterns. This is not a case of semantically appropriate decomposition with overly-specific templates — the decomposition trigger itself is firing on an irrelevant parent. **What this does NOT prove:** That every decomposition is irrelevant (some parents genuinely concern comparison/measurement). That fixing the trigger won't break valid decompositions elsewhere. Whether other template children (change affecting X/Y, one-off event) share the same defect pattern or have independent justification issues. --- ### Experiment 57C — Post-v0.9 Investigation Flow Observation **STOPPED AT FIRST PRODUCTION-PATH FAILURE** Attempted first-post-v0.9 multi-turn investigation through the real `startCase()` → `updateCase()` production path with a team-relocation scenario (London→Manchester). Turn 1 completed: selected question about "primary driver", user answered about cost savings/£400K funding. On Turn 2, a live model response produced `relationship: "affects"` as a graph edge value; the current production graph/update schema rejected that relationship string, preventing progression through the normal investigation path. Run was manually stopped before any workaround was accepted or any fix attempted. No `docs/experiment-57c.md` was written — this entry has been superseded by it. --- ### Return-to-Work Note (Experiment 55F) The first implementation pass against the reasoning refinement requirements is deferred one more round while we map how meaning actually flows through the production update path — before committing to any schema or architecture changes. A source-inspection exercise traced the full answer-to-reasoning chain from prompt building, through LLM response parsing and normalization, into graph mutation. The key finding: no provenance fields exist on nodes or edges in the current schema, meaning R1/R2 separation has no structural carrier. The answer string is used only for a narrow comparability check, not for semantic verification against proposed changes. A complete path map lives in `docs/reasoning-production-path-map.md`. Tomorrow should decide whether to add provenance fields to schemas, modify the prompt structure, or both — grounded in this accurate production trace rather than architectural speculation. Branch: `feature/user-workspace-ux-v0.7`. ### Experiment 55A Summary — Clarification Uncertainty Preservation Isolated the answer-resolution step using one fixed target (preference/trade-off or hard constraint) and four answers of different strength: fully explicit, weak priority, conditional trade-off, non-answer. Four live Ollama calls completed at http://192.168.1.111:11434 with qwen-claude:latest (~62s total). Case 1 (explicit hard constraint) resolved correctly. Case 2 (weak priority — "Risk matters more to me.") over-resolved: the model set targetResolved=true and inferred "not a rigid, non-negotiable constraint" — meaning stronger than the user supplied. Case 3 (conditional trade-off) resolved correctly on the target but flattened conditionality into flat "preference or trade-off" language without preserving the conditional qualification ("might accept some"). Case 4 (non-answer) correctly remained unresolved with appropriate remaining uncertainty. Two of the four tested answers showed loss of nuance: one was over-resolved and one retained the correct target category while losing conditional qualification. The same over-resolution reproduced with a fixed target, so target broadening is not required for the failure to occur. Broader generalisation across other models and answers remains untested. Behaviour Selection, graph, UI, and production integration remain untouched. Branch: feature/user-workspace-ux-v0.7. First test/file to inspect when resuming: tests/reconstruction/semantic-clarification-uncertainty-preservation.test.js for the full experiment and results. Status pending Rob's review. ### Experiment 56A Summary — Regression B Proposal Validation Enum Mismatch The first implementation pass added proposal-level `answerMeaning` with a pre-mutation compatibility guard. Deterministic regression tests A-D passed, but live Ollama runs showed Regression B failing at `proposal_validation` before the pre-mutation guard could execute. Experiment 56A traced this to a schema mismatch: Qwen returned `supportCategory: "conditional_qualification"` while the production Zod schema only accepts `conditional_tradeoff` among five values. The value survives normalization unchanged (normalize step handles node kind aliases, not supportCategory). The failure is at Zod validation — a proposal-contract issue, not a guard failure. **Hypothesis confirmed.** No fix was attempted. Branch: `feature/reasoning-fidelity-v0.8`. First file to inspect when resuming: `lib/graph/schema.js` line 165 (Zod enum for supportCategory) or the experiment record at `docs/experiment-56a.md`. Status pending Rob's review. ### Experiment 56B Summary — Regression B Live Run After Normalisation Commit `36faf70` added normalization for `conditional_qualification → conditional_tradeoff`, but a live Regression B run returned a _different_ variant: `supportCategory: "conditional_preference"`. The existing normalisation map does not cover this value. Two independent Zod errors occurred: (1) `conditional_preference` not in the supportCategory enum, and (2) `resolutionGuidance` was free-text instead of an enum value. **Run-to-run model variation confirmed** — the same fixed input produced `conditional_qualification` in Ex 56A and `conditional_preference` in Ex 56B. The pre-mutation guard remains unreachable because proposal_validation rejects first. Failure classification: `FAIL — normalization / proposal contract`. Branch: `feature/reasoning-fidelity-v0.8`. File to inspect when resuming: `docs/experiment-56b.md`. Status pending Rob's review. ### Experiment 56D Summary — Regression B via Real Production Path Tested whether deterministic derivation refinement from commit `3e78d57` (refine answer meaning derivation for negation and qualification) works end-to-end through the real `updateCase()` production path. Input: source "I want the business to grow, but I don't want to take on more risk." Answer "I'd normally avoid more risk, but for the right opportunity I might accept some." — the canonical conditional trade-off case (Regression B). **Result: PASS.** Five of five checkpoints confirmed across one live Ollama call at `http://192.168.1.111:11434` with `qwen-claude:latest`: 1. `userSupportedMeaning` correctly extracted conditional semantics — separated default preference (avoid risk) from qualification (override for right opportunity). 2. Deterministic profile derivation produced `conditional_tradeoff` category despite LLM returning null for `supportCategory`. 3. Pre-mutation guard passed with zero errors — the normalized/derived meaning is compatible. 4. Graph mutation proposed: `n-risk-constraint` resolved from unknown→resolved; emergent unknown `n-opportunity-criteria` created (unknown/unknown) capturing the threshold definition need. 5. Follow-up question correctly targets the emergent conditional/threshold unknown. **Key observation**: The LLM does not auto-populate `supportCategory` — it is consistently null in `answerMeaning`. The deterministic derivation layer in `readDiagnostics` (and the inline pipeline) is the sole mechanism by which meaning profile category gets determined. This confirms the design: LLM produces raw meaning; deterministic logic categorizes it. No regression detected. Full results in `docs/experiment-56d.md`. Branch: `feature/reasoning-fidelity-v0.8`. Status pending Rob's review. ### Experiment 56J Summary — Regression D Explicit Hard Constraint Semantic Probe Tested whether the configured live Ollama model (`qwen-claude:latest` at `http://192.168.1.111:11434`) preserves explicit hard-constraint meaning from user answer "It's a hard constraint. I don't want any increase in risk." — Regression D from `docs/reasoning-refinement-requirements.md`. One live Ollama call (19,343 ms) returned `userSupportedMeaning: "Avoiding additional risk is a hard constraint, and no increase in risk is acceptable."` with `possibleInference: null`. **Classification: PASS.** The model preserved the explicit hard-constraint status without weakening it into preference/trade-off language and did not add unsupported interpretation. `possibleInference` is null, which is appropriate for a direct unambiguous answer. This experiment does not prove fidelity for other regression cases (E, F), consistency across multiple runs, or behavior in production reasoning paths. Branch: `feature/reasoning-fidelity-v0.8`. Files: `tests/reconstruction/semantic-regression-d-explicit-hard-constraint.test.js` and `docs/experiment-56j.md`. Status pending Rob's review. ### Experiment 56K Summary — Evidence-resolvable disagreement must not become user clarification Tested whether the configured live Ollama model (`qwen-claude:latest` at `http://192.168.1.111:11434`) distinguishes evidence-resolvable uncertainty from user-owned ambiguity — Regression E from `docs/reasoning-refinement-requirements.md`. Fixed case: Delivery delay concern with competing causes ("Staff capacity may be the issue" / "Supplier lead times are likely responsible.") — resolvable by evidence gathering, not user clarification. One live Ollama call (18,580 ms) returned `uncertaintyType: "evidence_needed"` with specific evidence target: "Current internal staffing capacity levels and external supplier lead time records." No user clarification was introduced. **Classification: PASS.** The model correctly identified the disagreement as requiring evidence rather than asking the user to settle an externally knowable question by clarification. It specified concrete, relevant evidence — demonstrating understanding of the causal structure rather than producing a generic classification. This confirms the model can preserve the distinction between "evidence needed to determine what is true" and "clarification needed because only the user can establish meaning/preference/intent/constraint" for this tested case. This experiment does not prove fidelity for Regression F (user-owned ambiguity), consistency across domains/phrasings, downstream reasoning preservation, or end-to-end production flow. Branch: `feature/reasoning-fidelity-v0.8`. Files: `tests/reconstruction/semantic-regression-e-evidence-vs-clarification.test.js` and `docs/experiment-56k.md`. Status pending Rob's review. ### Experiment 56L Summary — User-owned ambiguity requires clarification, not evidence Tested whether the configured live Ollama model (`qwen-claude:latest` at `http://192.168.1.111:11434`) recognises that a preference-vs-constraint distinction belongs to the user's own meaning and requires clarification rather than external evidence — Regression F from `docs/reasoning-refinement-requirements.md`. Fixed case: "I want the business to grow, but I don't want to take on more risk." — user has not specified whether avoiding additional risk is a hard constraint or a strong preference/trade-off. One live Ollama call (14,032 ms) returned `uncertaintyType: "user_clarification_needed"` with `evidenceNeeded: null` and specific `userClarificationNeeded` describing the non-negotiable-versus-trade-off distinction only the user can establish. Matches pre-written human reference exactly at category level. **Classification: PASS.** The model correctly identified the ambiguity as user-owned, did not introduce spurious evidence gathering, and preserved the evidence-vs-user-meaning distinction cleanly. This experiment does not prove consistency across repeated runs, fidelity for other regression cases (A–E, G+), behavior in production reasoning paths, or downstream integration with Behaviour Selection or the SituationGraph. Branch: `feature/reasoning-fidelity-v0.8`. Files: `tests/reconstruction/semantic-regression-f-user-owned-ambiguity.test.js` and `docs/experiment-56l.md`. Status pending Rob's review. ### Experiment 56M Summary — Production evidence vs clarification routing validation Validated one production claim after Codex commit `f861e2c`: does the deterministic question-formulation boundary preserve the E/F distinction? No live Ollama calls were made (0). Deterministic `formulateQuestion()` was exercised with both regression fixtures. Regression E (competing delivery-delay causes: "Staff capacity may be the issue" / "Supplier lead times are likely responsible.") produced question: "What evidence would clarify possible causes of the delivery delay?" — reasoning pattern=diagnosis, strategy=evidence_gathering, template=diagnosis_evidence. PASS. Regression F (preference vs constraint ambiguity: "Whether avoiding additional risk is a hard constraint") produced question: "Is avoiding additional risk a hard constraint or a preference/trade-off?" — reasoning pattern=prioritisation, strategy=null, template=user_meaning_clarification, with rejected families correctly excluding all evidence-adjacent families. PASS. Both cases maintain their distinct routes: E on an evidence route and F on user clarification. All 19 existing tests continue to pass. Branch: `feature/reasoning-fidelity-v0.8`. File: `docs/experiment-56m.md`. Status pending Rob's review. ### Experiment 57J.26 — Post-Admission Investigation Progress Tested whether the engine makes genuine investigative progress after admitting two user-supported unknowns (Answer 1: savings realism + retention evidence). **Classification: D — NEW VALIDATION / REASONING FAILURE.** Both runs failed at Update 1's `proposal_compatibility` stage. Run 1 (57J.26 scenario): `"New unknown must be explicitly related to an answer-derived node"` — the old provenance-link gate returned, contradicting 57J.25's confirmed pass. Run 2 (exact 57J.25 scenario): `"Proposal cannot resolve beyond an unclassified answer by introducing unsupported stronger meaning"` — a different semantic compatibility error also blocks Update 2. The v0.15 unknown admission fix from 57J.25 cannot be reproduced under the same commit (`fbbd271`). Run-to-run variance in start graph construction (6→7→9 nodes) also observed. Status: unproven — needs regression investigation. No production code changed. --- ### Reasoning Fidelity v0.8 — First Pass Closeout **The first-pass reasoning-fidelity refinement is complete to its agreed scope.** Regression boundaries A–F have been investigated and the production defects identified from those boundaries have been addressed: - **A — weak priority:** supported against unsupported strengthening via pre-mutation compatibility guard; - **B — conditional trade-off:** qualification preserved through deterministic derivation and normalisation; - **C — unresolved uncertainty:** may remain unresolved when the user supplies no position; - **D — explicit hard constraint:** explicit meaning preserved; - **E — evidence-resolvable disagreement:** routed to evidence gathering; - **F — user-owned ambiguity:** routed to clarification. No demonstrated production defect remains inside the A–F first-pass boundary. Deterministic production validation is passing (commit `ec398dc` validating evidence vs. clarification routing). **Current HEAD:** `ec398dc` — experiment: validate evidence versus clarification routing **Key commits:** `f861e2c` (preserve evidence vs. clarification distinction), `ec398dc` (validate evidence vs. clarification routing) The two important production capabilities now present are: 1. User-supported meaning cannot silently outrun the raw answer at the mutation boundary; 2. Evidence-resolvable uncertainty and user-owned ambiguity are routed differently at question formulation. **Next work should begin from a newly observed product or reasoning failure rather than automatically extending this regression programme.** These open questions remain for future evidence-driven investigation, not as current defects: - broader wording/domain/model robustness; - clarification-target precision outside the tested cases; - durable per-node provenance of user-supported meaning vs inference; - whether rejected proposals should eventually be adapted rather than simply blocked; - end-to-end interaction behaviour across graph update, question choice, Behaviour Selection and UI; - multilingual robustness; - any future defect exposed by real use. ### Experiment 57B — Grounded Unclassified Affirmative Answer (Live) Validated one live semantic probe: the configured Qwen model (`qwen-claude:latest` at `http://192.168.1.111:11434`) produced grounded user-supported meaning for a legitimate affirmative answer that falls into `other` (unclassified) — exactly the case blocked by v0.8's guard. **Result: PASS.** `userSupportedMeaning`: "The user directly states that cost reduction, specifically approximately £2M in annual office-overhead savings, is a primary stated reason for considering the relocation." No unsupported strengthening beyond the raw answer. One live call (20,351 ms). This confirms the live semantic premise behind Codex `4e4d0fa` (semantic token overlap grounding). **What remains unproven:** token-overlap adequacy across diverse answers, stability across runs, production guard integration. Full results in `docs/experiment-57b.md`. Status pending Rob's review. ### Experiment 57A — Contaminated After First Valid Observation The first post-v0.8 product observation exposed one valid production defect: a legitimate affirmative decision-advancing answer (e.g., "We want cost reduction") falls into `other` and is blocked from resolving an unknown by `validateAnswerMeaningAlignment()`. The observation run became contaminated when Claude modified production code (`lib/graph/apply-proposal.js`, `lib/graph/schema.js`) adding four new categories and keyword detectors. Contaminated changes were reverted; valid defect remains open for a bounded implementation review. Repository production state is back at the merged v0.8 baseline (commit `14d68f1`). Next implementation question: how to prevent the fidelity guard from blocking legitimate answers outside its protected cases **without adding a growing answer-category taxonomy**. ### Experiment 57F — Decomposition Relevance Fix Live Validation (BLOCKED) Tested whether v0.11 (`7e4c506` — prevent unsupported comparison decomposition) works on the live relocation scenario from Experiments 57E/57B. **Classification: BLOCKED.** Start returned HTTP 200 with a valid graph. Update 1 failed at `proposal_compatibility` stage with HTTP 422: "Proposal cannot resolve beyond an unclassified answer by introducing an unsupported constraint or preference/trade-off distinction." The semantic grounding gate (`validateAnswerMeaningAlignment()`) blocked cost-reduction answers classified as class "other" before any graph update could occur. No decomposition ran — prohibited children ("two observations", "measured", "different timing") cannot be confirmed absent. **New defect exposed:** the semantic grounding gate rejects legitimate cost-reduction/savings answers that fall into unclassified ("other") rather than protected categories, preventing any Update 1 evaluation of the v0.11 fix. This is a separate blocking issue from decomposition relevance. What remains unproven: whether v0.11 works when Update 1 does succeed (answers in supported classes), and whether it correctly allows appropriate comparison/measurement decomposition for genuine parents. Branch: `feature/decomposition-relevance-v0.11`. Status blocked by semantic grounding gate. ### Experiment 57G — Semantic Compatibility Live Validation Tested whether the cost-reduction answer (unclassified "other") now passes proposal compatibility through v0.12 (`69efc5d`). **Classification: PASS.** Both invocations returned HTTP 200 at `update_applied` — no more `proposal_compatibility` rejection. The selected next question ("What changed during that period...") is grounded in the relocation scenario (reasoning pattern: explanation, strategy: evidence_gathering). No prohibited decomposition children appeared. The v0.11 decomposition fix held on this successful update. **Cold-start observation:** second invocation started with 5 nodes instead of expected 8+ — inconsistent initial graph construction worth investigating separately. Branch: `feature/semantic-compatibility-v0.12`. Status: PASS — semantic compatibility blocker removed, decomposition regression absent. Full results in `docs/experiment-57g.md`. ### Experiment 57I — No-Structure Relationship Fallback Live Validation **Objective:** Validate that commit `4c5666d` (reasoning: suppress explanation question without relationship structure) no longer creates the `Explanation for why...` parent when no meaningful relationship structure exists. **Classification: PASS.** Fixed scenario: "Should I relocate my engineering team from London to Manchester?" with answer "We're looking at this mainly for cost reduction — roughly £2M annual savings on office overhead." Update 1 returned HTTP 200 at stage `update_applied`. Nodes containing "Explanation for why": None. Nodes containing "why these observations differ": None. The reasoning pattern correctly shifted from "explanation" to "decision". The selected next question ("What would clarify team size, seniority levels, and willingness to relocate in this situation?") is grounded in a genuine unresolved relocation issue. No production code changed. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. Branch: `feature/relationship-fallback-v0.13`. Status: PASS. ### Experiment 57J.2 — Minimal Clarification Answerability Diagnostics Tested `{"scenario":"test"}` for exact graph node text and answerability diagnostics. **Classification: B — inconsistent diagnostics.** Single start produced `centralStatement="test"` with one unknown (`nlgonjv`): "The actual scenario, problem description, or data set intended for analysis." The API reported `prerequisiteConceptCount=3`, but only 1 of 7 prerequisite regex signals actually matched (rule 1: `\bproblem\b`). The count of 3 comes from conjunction-based amplification: two instances of "or" in the label+description → `conjunctionCount=2` → formula `Math.max(regex_matches=1, unresolved_deps=0, conjunctions+1=3)` = 3. The diagnostic name is misleading — it reports a maximum across three different amplification strategies (prerequisite regex, unresolved dependencies, conjunctions), not just prerequisite concept signals. No graph-backed question produced. One live Ollama call at qwen-claude:latest on http://192.168.1.111:11434 (27,109 ms). Full results in `docs/experiment-57j2.md`. ### Experiment 57J.8 — Rejected Answerability Corroboration Candidate Candidate commit `60048a5` (reasoning: require corroboration for conjunction compoundness) was reviewed and rejected. It crossed its explicit implementation stop condition: while it fixed the false-positive `"scenario, problem, or data set"` minimal clarification case by making it independently answerable, it also broke a genuinely compound fresh-evidence question (e.g., "What evidence supports the savings estimate **and** what evidence supports the retention assumption?") into independently answerable — because no graph structure existed yet. The candidate violated the rule that Case 1 and Case 2 must both be preserved with existing signals. It has been reverted to production baseline. Production reasoning is restored to pre-v0.14 answerability behaviour. The unresolved design question: surface conjunctions alone are an unreliable proxy for semantic compoundness, but the current graph also lacks sufficient structured information to distinguish every fresh single-concept alternative from every genuinely compound fresh unknown. **Do not resume by reintroducing conjunction corroboration or adding more English regex rules.** Full record in `docs/experiment-57j6.md`. ### Experiment 57J.11 — Live Unknown Dimensionality Representation Tested whether a single answer containing two independent evidence dimensions is represented as two separate unknown nodes or collapsed into one compound unknown. **Classification: A — SEPARATE.** Fixed scenario: "We are considering relocating the engineering team to reduce operating costs." Fixed answer: "Before deciding, I need evidence that the projected office savings are realistic and evidence that the move will not materially increase loss of key engineers." One live start + one live update. Update was rejected at `proposal_compatibility` (structural linkage error), but the rejection errors directly revealed two independently named proposed unknown nodes: `n-savings-realism` (Target A) and `n-retention-impact` (Target B). The model did not collapse them into a single compound node. Semantic separability existed in the model proposal before deterministic answerability/decomposition. **What remains unproven:** separation for implicit conjunctions, across models/repeated runs, and whether both nodes survive structural gating (`proposal_compatibility`) or question-selection after graph mutation. Full record in `docs/experiment-57j11.md`. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. ### Experiment 57J.25 — Live User-Supported Unknown Admission Validation **Objective:** Validate that the v0.15 candidate admits two user-supported unknowns from the 57J.11 case through the live production `updateCase()` path without requiring fake provenance edges. **Pre-written expectation:** The answer explicitly introduces two independent uncertainties: savings realism and retention impact. If v0.15 works on the live production path, those user-supported unknowns should no longer be rejected solely because they lack an answer-derived provenance edge. No fake edge should be required or manufactured. A later failure at a different validation/reasoning boundary is acceptable evidence and must be recorded as the first new failure. **Fixed scenario:** "We are considering relocating the engineering team to reduce operating costs." **Fixed answer:** "Before deciding, I need evidence that the projected office savings are realistic and evidence that the move will not materially increase loss of key engineers." **Live-call count:** start: 1, update: 1, total: 2 **START:** HTTP 200 | stage: unknown | nodes: 6 | edges: 3 | question: "What would clarify detailed breakdown of current engineering operating costs in this situation?" **UPDATE 1:** HTTP 200 | stage: update_applied | nodes: 8 (+2) | edges: 5 (+2) | no errors/validation failures. Both proposed unknowns admitted: `n-oss-realistic` (savings realism) and `n-kr-loss` (retention impact), each with meaningful descriptions grounded in the answer semantics. Selected next question targets `n-oss-realistic` via evidence_gathering/diagnosis strategy. **57J.11 provenance-link rejection:** ABSENT — no proposal_compatibility rejection occurred. Both target dimensions survived. **Savings target:** PRESERVED **Retention target:** PRESERVED **Fake provenance edge:** NO **Classification: A — PASS.** The v0.15 update path admits both user-supported evidence dimensions through the production path without rejection at the old 57J.11 provenance-link gate. No later failure occurred within this single update. **What remains unproven:** Repeated-run stability; cross-domain generalisation; downstream investigation viability past Update 2+; implicit conjunction admission; whether `too_broad` conversation health eventually blocks later turns. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Branch: `feature/user-supported-unknown-admission-v0.15`. ### Experiment 57J.28 — Live Node-Support Semantic Inputs Capture **Classification: C — GATE BEHAVES AS EXPECTED.** On a fresh live run, both user-supported unknowns (savings realism + retention impact) were admitted with HTTP 200 at `update_applied`, zero validation errors. The answerMeaning correctly captured both dimensions. The semantic gate (`rawAnswerSupportsUnclassifiedMeaning`) passed for both via the token-count clause (5 shared content tokens >= 3), not overlap ratio. Structural fallback also valid for both nodes. Static helper evaluation agrees with actual gate behavior — live admission outcome fully explained by captured inputs. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. Start: 7 nodes, Update 1: 9 nodes (+2). What remains unproven: token-count adequacy for paraphrased/low-overlap answers, downstream investigation viability past Update 2+, run-to-run start graph stability. No production code changed. ### Experiment 57J.29 — Live Semantic Representation Stability (Repeated Identical Runs) **Classification: D — DOWNSTREAM INSTABILITY SUSPECTED.** Three repeated identical live runs with the fixed scenario ("We are considering relocating the engineering team to reduce operating costs.") and fixed answer ("Before deciding, I need evidence that the projected office savings are realistic and evidence that the move will not materially increase loss of key engineers.") through the production `startCase()` → `updateCase()` path. **key finding:** `userSupportedMeaning` was null/empty in ALL 3 trials — no semantic meaning was extracted by the model. Yet admission outcomes diverged: Trial 2 (start with 7 nodes) admitted both unknowns; Trials 1 & 3 (start with 6 nodes) rejected at `proposal_compatibility` with identical "stronger reasoning category" errors despite null diagnostics. This confirms that start graph quality (6 vs 7 nodes cold-start variance) directly affects admission outcomes, and when `userSupportedMeaning` is empty the gate may still process hidden semantic fields. The admission variance cannot be explained by upstream model representation because no meaningful semantic content was produced in any trial — instability is downstream of model representation. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. 6 live calls total. No production code changed. ### Experiment 57J.31 — Rejected Proposal Diagnostics Integration **Objective:** Address the blocking diagnostic visibility gap from Experiment 57J.30 — when `applyValidatedProposal` fails at `proposal_compatibility`, no pre-validation proposal fields are exposed to developers, making causal attribution of rejection outcomes impossible. **Classification: E — FIX VALIDATED.** Added a compact `rejectedProposalSnapshot` to the orchestrator's diagnostics object in the `!applicationResult.success` path (lines ~690–725 of `lib/graph/orchestrator.js`). The snapshot is stage-gated (only for `proposal_compatibility`), contains only key proposal fields (`answerMeaning.userSupportedMeaning`, `answerMeaning.possibleInference`, `updatedNodes[].{nodeId,newValue}`, `resolvedUnknownNodeIds[]`, `addedNodes[].{id,kind,label,description,parentId,dependsOn,affects,childIds}`, `addedEdges[].{fromNodeId,toNodeId,relationship}`), and excludes raw model response/prompt content. No validation or mutation behavior changed — only the diagnostic surface is expanded. **Tested:** 7 new snapshot tests (all pass), 2 apply-proposal regression tests (both pass), 13 route-layer tests (all pass) = 84 passed, 0 failed by this change. Pre-existing orchestrator failure confirmed independent of this change. What remains unproven: whether the actual rejected trial from 57J.30 contained stronger `userSupportedMeaning` language vs. different structural elements — requires re-running Experiment 57J.30 now that the snapshot is visible in the HTTP response body. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. 0 live calls needed (deterministic instrumentation). Production code changed: `lib/graph/orchestrator.js` only. ### Experiment 57J.32 — Inspect Rejected Proposal Live Variance (EXPERIMENT INTERRUPTED) **Protocol breach: YES.** The execution harness drifted beyond the agreed budget. Only valid bounded evidence from the first 3 trials is retained in `docs/experiment-57j32.md`. All post-trial activity is excluded from conclusions. **Classification: F — MULTIPLE DIFFERENCES.** Using `rejectedProposalSnapshot` directly, accepted vs rejected proposals differ in both answerMeaning framing (prescriptive "decision is conditional on" vs neutral "requires evidence") AND structural content (accepted = 2 addedNodes + 2 addedEdges; rejected = 0 addedNodes + 0 addedEdges). Neither single cause can be independently isolated. The rejected trial's userSupportedMeaning did not faithfully preserve the raw answer: "The decision is conditional on" prescribes what the _decision_ requires rather than reporting the user's information need ("Before deciding, I need..."). **rejectedProposalSnapshot worked as designed** (57J.31 verified). Status: interrupted mid-execution; evidence retained in `docs/experiment-57j32.md`. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. Production code unchanged. ### Experiment 57J.30 — Proposal-Boundary Live Variance **Classification: I — INSUFFICIENT VISIBILITY.** Three repeated identical live runs with the fixed scenario ("We are considering relocating the engineering team to reduce operating costs.") and fixed answer through the production `startCase()` → `updateCase()` path. Mixed outcomes: Trial 1 (start=6 nodes) ACCEPTED, Trial 2 (start=8 nodes) REJECTED at `proposal_compatibility` with "answerMeaning.userSupportedMeaning introduces a stronger reasoning category", Trial 3 (start=5 nodes) ACCEPTED. **Cold-start instability confirmed at scale:** node count ranged from 5 to 8 across three identical inputs (60% variance). Accepted trials are structurally consistent: both produce exactly 2 unknown nodes (savings realism + engineer retention) with depends_on edges to state anchors. **Blocking gap:** the API does not surface parsed proposal fields (answerMeaning, addedNodes, etc.) in rejection responses — only error strings. Without pre-validation proposal visibility, causal attribution of the accepted-vs-rejected divergence is impossible: we cannot confirm whether the rejected trial's `userSupportedMeaning` contained stronger category language or whether a different structural element caused the rejection. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. 6 live calls total. No production code changed. ### Experiment 57J.33 — Classify Captured Answer-Meaning Strengthening (Deterministic) **Objective:** Determine whether the validator correctly rejected the captured Trial 2 `userSupportedMeaning` ("The decision is conditional on evidence...") as stronger than the raw answer ("Before deciding, I need evidence..."). Fully deterministic — zero Ollama calls. **Method:** Replicated production `deriveAnswerMeaningProfile()` and `validateAnswerMeaningCompatibilityWithRawAnswer()` logic deterministically against all four captured strings (raw answer, rejected Trial 2, accepted comparisons A and B). Also ran existing apply-proposal tests (64 pass) and rejected-proposal-snapshot tests (7 pass). **Key finding:** The rejected Trial 2 string contains the word "conditional" which triggers `hasConditionalQualification()` at line 2775 of `lib/graph/apply-proposal.js`, pushing it into the `conditional_tradeoff` category. Both accepted comparisons lack trigger words and correctly remain classified as `other`. The raw answer also profiles as `other`. **Rejection mechanism:** `validateAnswerMeaningCompatibilityWithRawAnswer()` line 2982–2986: when raw answer = "other" and supported meaning ≠ "other", the validator adds the "stronger reasoning category" error. This is exactly what occurs with Trial 2 (other → conditional_tradeoff). **Classification: A — VALIDATOR CORRECT.** The captured rejected meaning introduces `conditional_tradeoff` where the raw answer only establishes `other`. This is a real strengthening: "The decision is conditional on..." prescribes a requirement on the decision itself rather than reporting a personal information need. Both accepted comparison variants correctly remain in `other`. **What this establishes:** The validator's rejection was correct for the captured pair. The mechanism is purely the keyword detector firing on "conditional" — not cold-start variance or structural elements. **What it does NOT establish:** Whether "conditional" is the ideal trigger word across all contexts, whether "Before deciding" should itself have triggered conditional semantics, generalisation to other answers/domains, or whether cold-start node variance (57J.32) separately affects proposal quality. Configured Ollama: none used. Production code changed: NO. Tests permanently changed: NO. Temporary test used and removed: YES. --- ### Experiment 57J.35 — No-Retry Live Experiment Harness Enforcement **Objective:** Make the canonical live harness physically incapable of hidden retries. Enforce one-shot execution semantics: every requested Start maps to exactly one `/api/cases/start` call, every requested Update maps to exactly one `/api/cases/update` call, and rejections are returned immediately without implicit retry. **Protocol breach prevention:** This change directly addresses the protocol breach from Experiment 57J.32 where an implicit retry loop consumed multiple Update calls per trial, contaminating evidence. Future prompts may rely on the canonical harness to enforce one-call/no-retry semantics; Claude must not create supplementary retry scripts during bounded experiments. **Approach:** Bounded execution configuration (`maxUpdates`) + explicit call accounting (startCalls/updateCalls/totalCalls counters reflecting actual API invocations) + rejection-immediate-stop semantics + rejectedProposalSnapshot preservation for v0.16 diagnostic visibility. **Changes to canonical harness (`scripts/reproduce-multi-turn-investigation.mjs`):** - Hardcoded `Start → Update 1 → Update 2` sequence replaced with configurable bounded loop (`config.maxUpdates`) - Call accounting added: `calls.startCalls`, `calls.updateCalls`, reported as `totalCalls` - Rejection returns immediately; no retry path exists for any semantic outcome (proposal_compatibility, validation failure, etc.) - `rejectedProposalSnapshot` preserved and logged when present in Update rejection diagnostics - Every update call is explicit in the loop; `config.answers[i]` maps positionally to `Update i+1` **Tests added (`tests/reproduce-multi-turn-investigation.harness.test.js`):** 8 deterministic cases via synchronous simulation mirror of harness logic — all pass (0 Ollama calls, no dev-server needed). Test cases: 1. Start success → exactly 1 Start call. 2. Start failure → exactly 1 Start call, no retry. 3. Update success → exactly 1 Update call. 4. `proposal_compatibility` rejection → exactly 1 Update call, rejection returned unchanged. 5. Update 1 rejection → Update 2 never called. 6. Update 1 success → Update 2 called exactly once when explicitly requested. 7. Call counters equal actual mocked API invocations. 8. No semantic retry after HTTP 422/valid rejection response. **What this tooling change guarantees:** Future live experiment runs via the canonical harness are physically incapable of consuming more API calls than explicitly configured. Each Start request = exactly one call; each Update request = exactly one call; rejections stop the chain immediately without retry. Call accounting always reflects actual HTTP invocations, not inferred successes. **What this does NOT guarantee:** That production reasoning is correct (no production code changed). That cold-start variance in node counts is resolved (start graph stability remains an open issue). That semantic validation outcomes change (only the harness wrapper changed). That transport-level failures are handled (not addressed by this tooling change). **Configured Ollama:** none used. **Production code changed:** NO. **Tests run:** 8 passed, 0 failed. --- ### Experiment 57J.34 — Multi-Turn Investigation Progress After Accepted Update 1 **Objective:** On one fresh live run, if the first relocation answer passes the current reasoning safeguards, does answering the savings-realism question produce genuine investigation progress rather than repetition or irrelevant reasoning? **Classification: D — VALIDATION FAILURE.** Two distinct outcomes from cold-start variance: - **Run A (harness):** Update 1 rejected at `proposal_compatibility` with "answerMeaning introduces a stronger reasoning category" — same conditional-strengthening defect as 57J.33 R1. This is a correct fidelity guard. - **Run B (pipeline capture, fresh case):** Update 1 applied successfully but produced **zero new unknown nodes** despite the user answer explicitly introducing two independent evidence dimensions (savings realism + retention impact). A single compressed merged unknown appeared ("specific criteria, budget constraints, talent retention implications, or timeline"). Edges decreased from 3→2. When Update 2 was then attempted with concrete savings evidence (£2M), it failed at `proposal_compatibility` by the same structural provenance-link gate: "New unknown must be explicitly related to an answer-derived node." **Key finding:** Even when Update 1 passes semantic validation, the engine does NOT produce meaningful investigation progress. The savings-realism question from Answer 2 could not resolve because no dedicated savings realism unknown existed. This means the experiment's core question is answered negatively: accepting a "faithful" proposal does not guarantee downstream progress. **What this establishes:** (1) Cold-start variance (5→8 nodes) directly affects whether Update 1's semantic fidelity guard fires; (2) Semantic acceptance ≠ structural validity — zero-node proposals can pass validation; (3) The provenance-link gate remains active in v0.16 and blocks legitimate new unknown creation; (4) The merged generic unknown instead of two distinct targets is a consistent pattern. **What this does NOT prove:** That all cold-starts produce 5 nodes; that zero-node proposals are always produced on accepted updates; whether the merged-unknown behavior is intentional or a defect. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. 4 live calls total (2 starts + 2 updates in final pipeline; 1 start + 1 update rejected from harness). No production code changed. ### Experiment 57J.36 — Multi-Turn Investigation Progress After Accepted Update 1 (Clean Run) **Objective:** One clean guarded multi-turn case using the hardened canonical harness (bounded execution, no-retry) to determine whether a semantically faithful first answer produces genuine downstream progress on the second turn. **Fixed scenario:** "We are considering relocating the engineering team to reduce operating costs." **Answer 1:** "Before deciding, I need evidence that the projected office savings are realistic and evidence that the move will not materially increase loss of key engineers." **Answer 2 (not reached):** "The projected savings are based on the current London lease, business rates, service charges, utilities and facilities costs that would no longer be incurred at the same level after the move. The estimate is approximately £2M per year." **Live-call count:** start: 1, update: 1, total: 2 **START:** HTTP 200 | stage: unknown | nodes: 10 | edges: 5 | question: "What would clarify total projected costs at the new location, including one-time relocation expenses and long-term savings in this situation?" **UPDATE 1:** HTTP 422 | stage: proposal_compatibility | error: "Update contains no meaningful change" Rejected proposal snapshot: `answerMeaning.userSupportedMeaning` preserved both evidence dimensions faithfully; all structural fields empty (zero addedNodes, zero addedEdges, zero resolvedUnknownNodeIds, zero updatedNodes). **Classification: B — DIFFERENT REJECTION.** Rejected for "Update contains no meaningful change" at `proposal_compatibility`, not for semantic strengthening. The LLM produced a structurally null proposal despite semantically faithful meaning extraction. U1-B (not U1-A because no strengthening present; not U1-C/U1-D because nothing was applied). **Savings realism:** UNCLEAR **Retention impact:** UNCLEAR **What this establishes:** (1) When the LLM produces a structurally empty proposal with semantically faithful meaning, `proposal_compatibility` correctly rejects it as a no-op; (2) The gap between semantic extraction and structural creation exists independently of the semantic fidelity gate; (3) Cold-start produced 10 nodes (continuing the variance from 5→8 in 57J.34). **What this does NOT prove:** That the LLM can produce a structurally non-empty faithful proposal; that downstream progress would occur with an accepted proposal; run-to-run stability of node counts or proposal structure. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. 2 live calls total. No production code changed. ### Experiment 57J.37 — Rejected Proposal Diagnostics: Semantic-to-Mutation Contract Gap (Read-Only Diagnosis) **Objective:** Read-only analysis of whether the graph-update prompt/validator contract requires structural representation of newly introduced unresolved uncertainty, or whether an empty mutation with populated `answerMeaning` is permitted by the model contract and merely rejected later as a no-op. **Method:** Analyzed prompt instructions (`lib/graph/prompt-builder.js`), schema defaults (`lib/graph/schema.js`), validator logic (`lib/graph/utils.js` line 868–885), application pipeline (`lib/graph/apply-proposal.js` line 3174, 3252–3270), and existing test coverage. No Ollama calls. No live API. **Findings:** - **Prompt contract is AMBIGUOUS:** Rule #6 requires inspecting for new uncertainty but rule #7 ("Add new unknown nodes only when...") is a restriction, not a requirement. Additional Guidance explicitly permits semantic-only proposals via `answerMeaning`. - **Schema contract PERMITS the combination:** `graphUpdateSchema` allows populated `answerMeaning` + zero structural mutation (all array fields default to `[]`). No cross-field constraint exists. - **Validator contract REJECTS it:** `hasMeaningfulChange` checks only structural fields (addedNodes, updatedNodes status/value changes, addedEdges, removedEdgeIds). `answerMeaning` is not considered meaningful change. - **Test coverage NOT COVERED:** No test for "grounded answerMeaning introduces new unresolved uncertainty + zero structural changes." The closest tests verify schema validity of `{}` and validator rejection of all-empty arrays, but neither tests the populated `answerMeaning` case. **Classification: E — MIXED.** Three independent contract boundaries contribute: (1) prompt ambiguity between inspection and materialization; (2) schema permissiveness vs validator rejection mismatch; (3) model receives permissive guidance that leads to a rejected downstream gate. **Who owns the failure:** MIXED — Prompt Contract (ambiguity) + Validator Contract (schema/validator mismatch). Model does NOT own this failure. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. Production code changed: NO. Prompt changed: NO. Tests changed: NO. Dev server disturbed: NO. Ollama calls: 0. ### Experiment 57J.38 — Semantic-to-Mutation Contract Fix Selection **Objective:** Choose the smallest safe contract change preventing faithful semantic-only no-op proposals (populated `answerMeaning` with consequential uncertainty + zero structural mutation). **Three options evaluated:** - **A (prompt-only):** Single MUST rule in Additional Guidance clarifying structural mutation requirement. Fixes ambiguity but relies entirely on model compliance. - **B (prompt + validator):** Same prompt rule PLUS a deterministic validator check targeting `userSupportedMeaning` text + empty structural fields. Produces specific actionable error. - **C (schema cross-field constraint):** Zod `.refine()` rejecting any populated `answerMeaning` object with zero structural mutation. **Key finding on Option C:** Breaks Case 5 (possibleInference only). Schema cannot distinguish "new consequential meaning" from "inference-only" without semantic analysis. Any populated answerMeaning object triggers rejection regardless of content type. **Controlled case results summary:** - Case 1 (genuinely new uncertainty): All options reject as expected. B has best diagnostic visibility. - Case 2 (already represented): All options correctly reject. - Case 3 (update/resolve existing): All options correctly allow structural update to existing node. - Case 4 (answerMeaning null): All options preserve existing behavior. - Case 5 (possibleInference only): A✓ / B✓ / C✗ (breaks — schema sees populated object, cannot distinguish inference from meaning). **Recommended option: B — PROMPT + VALIDATOR CONTRACT** Why: Fixes 57J.36 completely (prompt ambiguity + enforcement gap). No new semantic classifier needed. Preserves provider-agnostic design. Does not break valid cases (null answerMeaning, possibleInference-only). Specific error message provides actionable diagnostic where option A relies entirely on model compliance (which the evidence from 57J.36 shows is unreliable for this pattern). Configured Ollama: none used. Production code changed: NO. Prompt changed: NO. Tests changed: NO. Dev server disturbed: NO. Ollama calls: 0. **READY FOR BOUNDED IMPLEMENTATION: YES** Exact implementation boundary: 1. One MUST rule in prompt Additional Guidance (replaces line 132 of prompt-builder.js) 2. One deterministic check in `validateGraphUpdate()` after `hasMeaningfulChange` (utils.js) 3. Six regression tests: populated-meaning-zero-mutation rejection, prompt text verification, null-answerMeaning preserved, possibleInference-only not forced, update-existing-node valid, resolve-path valid --- ### Experiment 57J.39 — Semantic-to-Mutation Contract Implementation (Option B) **Classification: E — IMPLEMENTATION COMPLETE.** Implemented the bounded Option B from 57J.38 with ownership correction: prompt owns structural materialization obligation; validator owns only the structural fact that `answerMeaning` alone is not graph progress. **Production changes:** - **prompt-builder.js** rule #6 replaced: added explicit MUST structural-materialization rule. userSupportedMeaning must be expressed through graph structure — update/refine existing, resolve unknown, new unknown, or justified relationship. answerMeaning alone is insufficient. - **utils.js** validateGraphUpdate(): added specific diagnostic "answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation" inside the `!hasMeaningfulChange` path, ordered before generic no-op error for exact same proposal. **Tests:** 15 new focused tests (8 in utils.test.js for all contract cases; 7 in prompt-builder.test.js for MUST rule verification). All 68 + 15 = 83 focused tests pass. No regressions in update-route (13), harness (8), or rejected-proposal-snapshot (7) tests. One pre-existing orchestrator failure unrelated to this work. **Not changed:** hasMeaningfulChange definition, schema, node/edge semantics, provenance, answerability, decomposition, reasoning taxonomy, semantic classifiers, provider integration, Behaviour Selection. possibleInference alone does not trigger the new diagnostic. Ollama calls: 0. Dev server disturbed: NO. Branch: feature/semantic-to-mutation-contract-v0.17 from HEAD 3b868b2. --- ### Experiment 57J.40 — Semantic-to-Mutation Contract Live Validation **Objective:** On one fresh live run, does the v0.17 prompt contract cause a faithful `userSupportedMeaning` to produce meaningful structural graph mutation instead of a semantic-only no-op proposal? **Fixed scenario:** "We are considering relocating the engineering team to reduce operating costs." **Fixed answer:** "Before deciding, I need evidence that the projected office savings are realistic and evidence that the move will not materially increase loss of key engineers." **Live-call count:** start: 1, update: 1, total: 2 **START:** HTTP 200 | stage: unknown | nodes: 8 | edges: 5 | question: "What was the comparable state before current baseline costs vs. projected costs at target location?" **UPDATE 1:** HTTP 422 | stage: proposal_compatibility | error: "answerMeaning.userSupportedMeaning introduces a stronger reasoning category than the raw answer establishes." Rejected proposal snapshot: `userSupportedMeaning`: "Proceeding with the relocation decision is explicitly conditional on obtaining verified evidence that projected office savings are realistic and that key engineer retention is preserved." — this converts the user's information-need statement ("I need evidence that...") into prescriptive conditional language about what the _decision_ requires, introducing a `conditional_qualification` meaning stronger than the raw answer. `possibleInference`: null. Structural proposal: 1 addedNode (`n_prereq_constraint`, kind=assumption) + 1 addedEdge (depends_on to state node). **Meaning fidelity classification: STRENGTHENED.** The model transformed a neutral information-need ("Before deciding, I need evidence...") into prescriptive constraint language ("Proceeding...is explicitly conditional on obtaining verified evidence"). This introduces a stronger reasoning category not supported by the raw answer. **Classification: C — CORRECT FIDELITY REJECTION.** The model strengthened the raw answer beyond what the user established, and the existing semantic-fidelity validator correctly rejected it at `proposal_compatibility`. This is not a v0.17 semantic-to-mutation failure — the strengthening was caught before any graph mutation could proceed. **What this establishes:** (1) The configured model still maps "Before deciding, I need evidence..." to prescriptive conditional framing on this scenario; (2) The existing semantic-fidelity guard correctly catches this strengthening at proposal_compatibility; (3) v0.17's MUST rule did not trigger in this run because the meaning was strengthened before reaching the mutation boundary. **What it does NOT prove:** That the configured model produces faithful `userSupportedMeaning` on this scenario under repeated runs; that v0.17's contract prevents the faithful semantic-only no-op when meaning is truly preserved; that strengthening avoidance would occur with different phrasing or in later turns. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. 2 live calls total. No production code changed. Branch: `feature/semantic-to-mutation-contract-v0.17`. --- ### Experiment 57J.40 — Semantic-to-Mutation Contract Live Validation (Full Record) **Objective:** Answer exactly: "On one fresh live run, does the v0.17 prompt contract cause a faithful `userSupportedMeaning` to produce meaningful structural graph mutation instead of a semantic-only no-op proposal?" **Branch:** `feature/semantic-to-mutation-contract-v0.17` **Starting HEAD:** 712c0c4 docs: experiment 57J.39 record and handoff update **Pre-written expectation:** > The raw answer contains two explicit unresolved evidence needs: projected savings realism and key-engineer retention impact. If v0.17 closes the semantic-to-mutation contract gap, a faithful `userSupportedMeaning` should no longer be accompanied by a completely empty structural proposal. The model should either update/refine existing relevant graph structure, resolve relevant structure, or add justified new structure. > A semantic-strengthening rejection remains a valid protected outcome and does not count as failure of v0.17. **Fixed scenario:** "We are considering relocating the engineering team to reduce operating costs." **Fixed answer:** "Before deciding, I need evidence that the projected office savings are realistic and evidence that the move will not materially increase loss of key engineers." **Configured model:** qwen-claude:latest at http://192.168.1.111:11434 **Dev server:** REUSED EXISTING (HTTP 200) **CALL ACCOUNTING** startCalls: 1 updateCalls: 1 totalCalls: 2 Supplementary scripts used: NO Retries: 0 **START** Nodes: 8 Edges: 5 Selected question: "What was the comparable state before current baseline costs vs. projected costs at target location?" **UPDATE 1** HTTP: 422 Stage: proposal_compatibility First error: "answerMeaning.userSupportedMeaning introduces a stronger reasoning category than the raw answer establishes." Nodes: 8 (unchanged) Edges: 5 (unchanged) Selected question: null **ANSWER MEANING** userSupportedMeaning: "Proceeding with the relocation decision is explicitly conditional on obtaining verified evidence that projected office savings are realistic and that key engineer retention is preserved." possibleInference: null **Meaning fidelity:** STRENGTHENED The model transformed a neutral information-need ("Before deciding, I need evidence...") into prescriptive constraint language ("Proceeding...is explicitly conditional on obtaining verified evidence"). This introduces stronger reasoning category (conditional qualification) beyond what the raw answer supports. The transformation maps "I need evidence that X and Y" to "decision is conditionally dependent on X and Y" — a non-trivial semantic strengthening. **STRUCTURAL PROPOSAL** updatedNodes: [] resolvedUnknownNodeIds: [] addedNodes: 1 (`n_prereq_constraint`, kind=assumption, label="Prerequisite condition for proceeding") addedEdges: 1 (depends_on from added node to state anchor `nqylvkl`) updatedNodes count: 0 resolvedUnknownNodeIds count: 0 addedNodes count: 1 addedEdges count: 1 **Structural mutation present:** YES Mutation type: ADD NODE **Classification:** C — CORRECT FIDELITY REJECTION **Why:** The model strengthened the raw answer beyond what it supports (information-need → prescriptive constraint), and the existing semantic-fidelity validator correctly rejected this at `proposal_compatibility`. The rejection was triggered by the strengthening detection, not by v0.17's semantic-to-mutation contract. The model did produce 1 added node and 1 edge, but these are downstream artifacts of the strengthened meaning — they do not represent faithful structural translation of the raw answer. **Did v0.17 remove the faithful semantic-only no-op failure:** UNPROVEN This run did not test the v0.17 contract's core question because the model never produced a faithful `userSupportedMeaning` to begin with. The strengthening occurred before reaching the mutation boundary, so v0.17's MUST rule was never exercised in this run. A faithful semantic-only no-op (if it exists under v0.17) is neither reproduced nor disproved here. **What this clean run establishes:** 1. The configured model maps "Before deciding, I need evidence..." to prescriptive conditional framing on this scenario — a repeatable strengthening pattern observed in earlier experiments (57J.32, 57J.33). 2. The existing semantic-fidelity guard catches this class of strengthening at proposal_compatibility. 3. When meaning is strengthened and rejected, the model's structural proposal is built on the strengthened meaning (not a faithful translation), making mutation analysis irrelevant to v0.17. **What it does NOT prove:** 1. That the configured model produces faithful `userSupportedMeaning` on this scenario under repeated runs. 2. That v0.17's MUST rule prevents the faithful semantic-only no-op when meaning is genuinely preserved. 3. That strengthening avoidance would occur with different phrasing, domain, or model. 4. That v0.17 works in any case where the model does produce faithful meaning. **Production code changed:** NO **Prompt changed during experiment:** NO **Canonical harness restored:** YES **Hardened no-retry behaviour preserved:** YES **Dev server disturbed:** NO **Ollama calls beyond harness count:** 0 **Documentation updated:** YES --- ### Experiment 57J.41 — Semantic-to-Mutation Contract Live Validation: Faithful Meaning Only **Objective:** When the user introduces one simple, explicit unresolved uncertainty with no conditional/constraint language, does v0.17 translate that faithful meaning into structural graph progress rather than a semantic-only no-op? **Fixed scenario:** "We are considering relocating the engineering team to reduce operating costs." **Fixed answer:** "I am unsure whether the projected office savings from the relocation are realistic." **Live-call count:** start: 1, update: 1, total: 2 **START:** HTTP 200 | stage: unknown | nodes: 6 | edges: 3 | question: "What would clarify current annual operating costs and cost structure of the engineering team in this situation?" **UPDATE 1:** HTTP 422 | stage: proposal_compatibility | First error: "answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation. answerMeaning alone does not constitute graph progress." **Rejected Proposal Snapshot:** - `userSupportedMeaning`: "The user is unsure whether the projected office savings from the relocation are realistic." — **FAITHFUL** (preserves uncertainty, no conditional/constraint language) - `possibleInference`: null - All structural fields: empty (zero updatedNodes, zero resolvedUnknownNodeIds, zero addedNodes, zero addedEdges) **Classification: B — SAME SEMANTIC-ONLY NO-OP.** Meaning is FAITHFUL. All structural mutation fields are empty. The v0.17 MUST rule + validator diagnostic fires as designed, converting the original 57J.36 silent accepted no-op into an explicit rejected proposal with specific error. However, this does NOT prove that faithful meaning CAN produce graph progress — only that it cannot do so without structural mutation under v0.17's new contract. **Key finding:** v0.17 successfully blocks the original failure (accepted semantic-only no-op) but does not establish a positive path for faithful meaning to produce graph progress. The open question remains: is there any valid pathway where faithful meaning translates to structural mutation, or does the MUST rule universally block it? Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Harness restored. No-retry preserved. Dev server disturbed: NO. ### Experiment 57J.42 — Structural-Mutation MUST Rule Prompt Conflict Diagnosis **Objective:** Diagnose why the model produces faithful `userSupportedMeaning` with zero structural mutation despite the v0.17 MUST rule. Read-only prompt-contract analysis of assembled prompt + tests. No Ollama calls. **Method:** Inspected all prompt rules from `lib/graph/prompt-builder.js`, analyzed action-selection ambiguity, checked six conflict patterns (A–F), reviewed test adequacy in `tests/graph/prompt-builder.test.js`. **Relevant rule conflicts found:** - Rule #6 (MUST) vs Additional Guidance bullet B (PERMIT empty arrays if "answer does not justify a change"): direct MUST vs PERMIT conflict. Rule #7's restrictive enumeration + rules #4 ("genuinely new concepts") + #11 (no duplicates) help the model decide nothing justifies a change, triggering the escape hatch in Additional Guidance. - Additional Guidance bullet C: "Use answerMeaning to preserve the answer's direct meaning even when the graph change remains unresolved" explicitly encourages semantic-only output — the exact pattern v0.17 sought to eliminate. - Action-selection ambiguity under rule #6: four acceptable forms listed without decision criteria or fallback ordering. - Pattern D fidelity paralysis: high-traceability requirements make structural creation non-trivial for simple uncertainty about known topics. **Classification: C — PROMPT CONFLICT** Primary owner of 57J.41 failure: PROMPT CONFLICT (not MODEL) Provider-agnostic concern: YES — CONTRACT LEVEL Smallest correction boundary: Two Additional Guidance bullets (~10 words total) at lines ~132 of prompt-builder.js must be removed or conditioned on rule #6 not triggering. Text-preservation-only tests do not prove prompt coherence. Configured Ollama: none used. Production code changed: NO. Prompt changed: NO. Tests changed: NO. Dev server disturbed: NO. ### Experiment 57J.43 — Remove Surviving Semantic-Only/No-Op Prompt Conflict **Objective:** Apply the smallest proven correction from 57J.42: replace two Additional Guidance bullets that conflicted with rule #6's MUST rule, plus add one clarifying bullet and seven focused tests. Bounded prompt-contract task only. Not solving update-vs-add action selection. **Production changes:** - `lib/graph/prompt-builder.js` — Replaced two conflicting Additional Guidance bullets (line ~131-132): - Old: "If the answer does not justify a change, return empty arrays for every category." → Now conditioned on rule #6 not applying. - Old: "Use answerMeaning to preserve the answer's direct meaning even when the graph change remains unresolved." → Replaced with explicit semantic-fidelity vs structural-mutation separation. - Added: Edge-connection requirement for new unknown nodes (clarifying bullet, consistent with existing rule #13a). - `tests/graph/prompt-builder.test.js` — Added 7 focused tests covering all seven required coverage areas. **What this guarantees:** 1. Empty-array permission is explicitly subordinate to rule #6 — MUST vs PERMIT contradiction eliminated. 2. `answerMeaning` can no longer substitute for graph mutation per the corrected bullet text. 3. All existing contracts preserved: duplicate avoidance, genuinely-new-concepts protection, fidelity/inference separation, traceability, update/refine preference. **What is intentionally left unresolved:** 1. Action selection under rule #6 (update vs add) — confirmed ambiguous by 57J.42. 2. Live production validation — requires next experiment pass. **Test results:** - prompt-builder.test.js: 22/22 pass (7 new + 15 pre-existing) - utils.test.js: 68/68 pass - apply-proposal.test.js: 64/64 pass - Total: 154 tests, 0 failures **Classification: E — IMPLEMENTATION COMPLETE (prompt correction only)** Configured Ollama: none used. Production code changed: prompt + tests only. Dev server disturbed: NO. ### Experiment 57J.44 — Direct Live Test of Conflict-Free Mutation Prompt **Objective:** Answer whether the v0.18 corrected Additional Guidance bullets now produce structural graph mutation from faithful uncertainty (one simple unresolved question about savings realism). **Fixed scenario:** "We are considering relocating the engineering team to reduce operating costs." **Fixed answer:** "I am unsure whether the projected office savings from the relocation are realistic." **Live-call count:** start: 1, update: 1, total: 2 **START:** HTTP 200 | stage: unknown | nodes: 6 | edges: 3 | question: "What would clarify current operating costs for the present location versus projected post-relocation costs and one-time relocation expenses in this situation?" **UPDATE 1:** HTTP 422 | stage: proposal_compatibility | error: "answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation. answerMeaning alone does not constitute graph progress." - Nodes: 6 | Edges: 3 (unchanged) **Rejected Proposal Snapshot:** `userSupportedMeaning`: "The user is unsure whether the projected office savings from the relocation are realistic." — FAITHFUL (no strengthening). `possibleInference`: about inflated projections negating benefits. All structural mutation fields empty (zero addedNodes, zero addedEdges, zero resolvedUnknownNodeIds, zero updatedNodes with meaningful change). **Meaning classification:** FAITHFUL — v0.18 eliminated the conditional-strengthening defect from 57J.40 (v0.17). **Classification: B — SAME FAITHFUL NO-OP.** The prompt contradiction was removed but faithful uncertainty alone still produced zero structural mutation for this model on this scenario. The action-selection gap under rule #6 (identified by 57J.42 as intentionally left unresolved) remains unaddressed. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. ### Experiment 57J.45 — Choose Structural Action-Selection Rule **Objective:** When rule #6 requires structural progress for a faithful unresolved uncertainty, choose the smallest provider-agnostic instruction that distinguishes update-of-existing from add-new-unknown. **Pre-written expectation:** The current prompt (v0.18) has the ingredients to distinguish Case A (equivalent unknown exists → update/refine) from Case B (no equivalent → add new unknown), but lacks an explicit fallback sequence. Rule #6's four structural options are listed without decision criteria; rules #4, #7, and #11 collectively push toward "nothing justifies a change." Result: NO — the prompt does not distinguish Case A from Case B. **Option A — EXISTING-FIRST FALLBACK:** - Case A correct: YES (explicit first step checks existing unresolved nodes) - Case B correct: YES ("otherwise" clause creates new unknown) - Duplicate risk: LOW (validator safety net + deterministic order) - Related-node overwrite risk: MEDIUM (semantic judgment required but bounded by existing contracts #4/#11) - No-action risk: LOW (eliminates ambiguity that caused 57J.41/57J.44 failures) - New semantic classifier: NO | New schema/state: NO | Validator change: NO | Provider-specific: NO **Option B — NEW-UNKNOWN DEFAULT:** - Case A correct: NO ("exact duplicate" requires undefined mechanism) - Case B correct: YES (default-to-add works) - Duplicate risk: HIGH (no "exact vs merely related" mechanism exists) - Related-node overwrite risk: LOW (only creates, does not overwrite) - No-action risk: MEDIUM (less than current but "exact duplicate" is underspecified) - New semantic classifier: YES (required for "exact duplicate" definition) | New schema/state: NO | Validator change: YES | Provider-specific: NO **Option C — GENERAL STRUCTURAL CHOICE:** - Case A correct: PARTIAL (depends on model judgment across 4 unweighted options) - Case B correct: PARTIAL (model may choose any option; evidence shows no-action preference) - Duplicate risk: MEDIUM | Overwrite risk: MEDIUM | No-action risk: HIGH (essentially current state — confirmed failure in 57J.41/57J.44) **Action-space narrowing for controlled case:** - Edge-only sufficient: NO (edges connect nodes but cannot represent new uncertainty without a node) - Resolution applicable: NO (user expresses uncertainty, not resolution of existing unknown) - Relevant actions: update/refine (Case A) or add unknown (Case B) **Recommended option: A — EXISTING-FIRST FALLBACK** One explicit instruction-order rule in Additional Guidance: check existing unresolved nodes first; if none represents the same uncertainty, create a new unknown. Deterministic order replaces ambiguous choice-list. No new classifiers, schema state, or validator changes required. Existing rule #11 and deterministic validator remain as safety net. **Status:** Design complete. READY FOR BOUNDED IMPLEMENTATION: YES. Implementation requires one Additional Guidance bullet in prompt-builder.js plus 8 focused deterministic regressions in tests. Full record in `docs/experiment-57j45.md`. ### Experiment 57J.46 — Existing-First Uncertainty Fallback: Prompt Implementation **Branch:** `feature/semantic-to-mutation-contract-v0.19` **Starting HEAD:** `acd1928` (HEAD of v0.18 at branch point) **What changed:** - `lib/graph/prompt-builder.js` — Added one Additional Guidance bullet: "When rule #6 applies to explicitly unresolved uncertainty: first check whether an existing unresolved node already represents the same uncertainty; if so, update/refine that existing structure rather than adding a duplicate; if no such node exists, add a new unknown that directly represents the unresolved uncertainty; do not use an edge alone to represent a previously unrepresented uncertainty." - `tests/graph/prompt-builder.test.js` — Added 14 focused tests verifying: existing-first ordering, reuse path, fallback-to-add, related-node-insufficient, edge-only-prohibited, possibleInference separation, resolution path preserved, duplicate contract preserved, scope uncertainty-only, fidelity/traceability preserved, noop validator untouched, no semantic classifier added. **Test results:** - prompt-builder.test.js: 37 tests pass (23 existing + 14 new) - utils.test.js: 68 tests pass (regression confirmation) - Total: 105 tests pass, 0 failures **What this guarantees:** When rule #6 fires for explicitly unresolved uncertainty, the prompt gives a deterministic instruction order: check existing first → reuse if equivalent → otherwise add new. Rule is scoped to unresolved uncertainty only. All existing contracts preserved (duplicate avoidance, fidelity/inference separation, traceability, noop validator, structural-materialization MUST rule). **What is intentionally left unresolved:** Semantic threshold for "represents the same uncertainty" (relies on model capability + validator safety net). Live production validation (requires next experiment pass). Multi-turn tuning. Edge-connection strategy for Case B new unknowns. Configured Ollama: none used. Production code changed: prompt + tests only. Dev server disturbed: NO. ### Experiment 57J.47 — Convergence Test: Existing-First Uncertainty Fallback Live **Objective:** Does v0.19 produce a faithful structural graph action for one explicit unresolved uncertainty instead of another no-op? **Fixed scenario:** "We are considering relocating the engineering team to reduce operating costs." **Fixed answer:** "I am unsure whether the projected office savings from the relocation are realistic." **Live-call count:** start: 1, update: 1, total: 2 **START:** HTTP 200 | stage: unknown | nodes: 7 | edges: 4 | question: "What would clarify current detailed breakdown of engineering operating costs and geographic distribution in this situation?" **UPDATE 1:** HTTP 422 | stage: proposal_compatibility | error: "answerMeaning.userSupportedMeaning overstates a raw answer that remains uncertain." rejectedProposalSnapshot: - userSupportedMeaning: "The user is currently uncertain whether the projected office savings from the relocation are realistic." (HUMAN ASSESSMENT: minor non-material paraphrase — see 57J.48; VALIDATOR: lexically rejected because "uncertain" absent from deriveAnswerMeaningProfile detection patterns) - possibleInference: null - addedNodes: [{id: "ns82kx9", kind: unknown, label: "Projected office savings from the relocation lack validation"}] (genuinely new — ADD NEW UNKNOWN structural action) - addedEdges: [{fromNodeId: "ns82kx9", toNodeId: "nv759rs", relationship: depends_on}] - updatedNodes: [] | resolvedUnknownNodeIds: [] **Meaning classification:** LEXICAL MISMATCH in deterministic derivation (human assessment: minor non-material paraphrase; validator mechanism: "uncertain" absent from deriveAnswerMeaningProfile patterns while raw answer's "unsure" is detected) **Structural action:** ADD NEW UNKNOWN — a genuinely new unknown directly representing savings realism was created. This is the FIRST time in experiments 57J.36-47 that the model produced a meaningful structural mutation for this type of uncertainty answer. **Classification: D — REJECTION BLOCKS TEST (LEXICAL FALSE POSITIVE).** The rejection was caused by `"uncertain"` being absent from `deriveAnswerMeaningProfile`'s uncertainty detection patterns (which include `"unsure"`, `"not sure"`, `"do not know"`, etc.), not by genuine semantic strengthening. Both words express identical uncertainty semantics. Structural progress was achieved (one new unknown + one edge) — the faithful no-op pattern has been broken. The remaining blocker is incomplete lexical coverage in the deterministic category model, not a meaning extraction defect. **Key finding:** v0.19's existing-first fallback now produces meaningful graph mutation for explicit unresolved uncertainty. New blocking boundary: semantically faithful paraphrases are being rejected because `deriveAnswerMeaningProfile` detects `"unsure"` but not `"uncertain"` (identical semantics). The guard's lexical gap causes false-positive "overstates a raw answer that remains uncertain" errors for semantically equivalent third-person uncertainty statements. Convergence test result is non-A; prompt tuning sequence terminates here per convergence rule. 57J.48 provides the full deterministic diagnosis. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. 2 live calls total. No production code changed. No prompt change during experiment. Harness restored to original scenario/answers. ### Experiment 57J.48 — Uncertainty Fidelity False Positive Diagnosis **Objective:** Determine whether the captured rejection of "The user is currently uncertain..." → rejected as overstatement when raw answer was "I am unsure..." is semantically justified or a lexical false positive. Fully deterministic — zero Ollama calls. **Captured pair:** - Raw answer: `"I am unsure whether the projected office savings from the relocation are realistic."` - userSupportedMeaning: `"The user is currently uncertain whether the projected office savings from the relocation are realistic."` **Deterministic trace:** 1. `deriveAnswerMeaningProfile(raw)` → category=`uncertain` (matches pattern `"unsure"` at line 2880) 2. `deriveAnswerMeaningProfile(userSupportedMeaning)` → category=`other` (pattern gap: `"uncertain"` not detected) 3. `validateAnswerMeaningCompatibilityWithRawAnswer`: line 2966-2970 fires because `raw=uncertain` && `supported≠uncertain` → error: "overstates a raw answer that remains uncertain." **Inverse test (same semantics, different word):** Using `"unsure"` instead of `"uncertain"` in userSupportedMeaning → category=`uncertain`, errors=none. Both words express identical uncertainty. **Semantic equivalence check:** - Uncertainty preserved: YES - Decision condition added: NO - Hard constraint added: NO - Priority added: NO - Conclusion added: NO - "currently" material temporal claim: NEGLIGIBLE - Perspective shift: REPRESENTATIONAL NORMALISATION (not substantive) - Pair classification: MINOR NON-MATERIAL PARAPHRASE **Architecture classification:** B — LEXICAL FALSE POSITIVE The meanings are semantically equivalent; the rejection depends on `"unsure"` being in `deriveAnswerMeaningProfile`'s detection patterns while `"uncertain"` is not. Both denote "lacking sufficient knowledge or certainty." **Keyword-dictionary risk: YES.** The validator's semantic fidelity decision depends entirely on whether the LLM uses one of ~15-20 hardcoded English surface forms (`"unsure"`, `"not sure"`, etc.) rather than equivalent terms (`"uncertain"`). This is raw-language keyword detection, not structured semantic contract validation. **Existing structured signals: PARTIAL.** The schema defines `supportCategory` (enum of five values) and `resolutionGuidance` (enum of three values), but both are free-text Zod strings without enum enforcement, and the LLM consistently produces null for both (per 56D). They can carry the needed semantics in theory but not in practice — a small contract extension (prompt requirement + schema constraint) is needed to make them operational. `possibleInference` exists but has no validator consumer. No production field carries a dedicated uncertainty-type classification (`evidence_needed`/`user_clarification_needed`) used in tests. **57J.47 documentation correction applied:** Previous wording stated meaning was "STRENGTHENED" as fact. Corrected to distinguish: human semantic assessment (minor non-material paraphrase) from validator mechanism (lexical gap in detection patterns). The actual rejection was a false positive caused by incomplete keyword coverage, not genuine semantic strengthening. **Production code changed: NO. Prompt changed: NO. Validator changed: NO. Tests permanently changed: NO. Ollama calls: 0.** Configured Ollama: none (zero live calls). --- ### Experiment 57J.49 — Structured Semantic Fidelity Boundary Assessment **Classification: B — EXISTING STRUCTURE IS PARTIAL** Read-only architecture diagnosis of whether existing `answerMeaning` fields can replace keyword-based fidelity inference without a contract extension. **Key finding:** All five classification values (`relative_priority_only`, `conditional_tradeoff`, `uncertain`, `explicit_hard_constraint`, `other`) and three resolution states (`must_remain_unresolved`, `may_resolve`, `must_resolve`) already exist in production enums (answerSupportCategory, answerResolutionGuidance at lines 147-158 of lib/graph/schema.js). The structured capability is fully designed. **Missing piece — two independent gaps:** 1. **Population gap:** Both `supportCategory` and `resolutionGuidance` are free-text Zod strings not constrained to their respective enums, AND the prompt explicitly tells the model "if you are unsure of the exact wording, leave them null" (prompt-builder.js line 28). Result: LLM never populates them in production. The validator has no structured data to consume — it must re-infer from `userSupportedMeaning` text via keyword detection. 2. **Enforcement gap:** Neither field uses enum constraint enforcement. Even if populated, there is zero structural guarantee they contain valid category values. **What existing structure CAN express (if populated):** `{supportCategory: "uncertain", resolutionGuidance: "must_remain_unresolved"}` — this exact combination expresses "remains unresolved uncertainty" without any lexical inference. **Why B not A:** The fields are never reliably populated by the model, and without enum-constrained schema enforcement, they carry no trustworthy information in production today. **What it does NOT establish:** That structured output solves the trust problem in general (Pattern A has no independent verification). That a dedicated `uncertaintyType` field (evidence_needed / user_clarification_needed) is unnecessary — this exists only in test fixtures, not any production schema. Full results in `docs/experiment-57j49.md`. ### Experiment 57J.50 — Structured Fidelity Migration Choice **Classification: C — STRUCTURED PRIMARY + NON-LEXICAL CONSISTENCY** Read-only design decision selecting the smallest safe migration path from keyword-based fidelity inference to structured semantic fields as primary authority. **Decision rationale:** Option C eliminates lexical dictionary dependence for all protected categories where structured fields are populated, while preserving backwards compatibility through null-to-lexical fallback for legacy proposals. It retains model-trust safeguards via cross-field deterministic consistency checks (resolutionGuidance vs proposal state, enum validity) without requiring the validator to re-infer English semantics from raw text or invent new classifiers. **Exact next implementation boundary:** One branch — `feature/structured-semantic-fidelity-v0.20`. Four file modifications: 1. `lib/graph/prompt-builder.js` — rule 28 replacement (enforce + instruct) + output contract addition (enum value listing for the two fields); 2. `lib/graph/schema.js` — `z.enum(Object.values(answerSupportCategory))` and `z.enum(Object.values(answerResolutionGuidance))` constraints on the two zod fields; 3. `lib/graph/apply-proposal.js` — migrate `validateAnswerMeaningAlignment()` to read structured values first, lexical fallback for null legacy proposals only; add schema-invariant consistency checks; 4. `tests/graph/apply-proposal.test.js` — nine regression tests matching the required list below. No new taxonomy. No provider-specific code. No embeddings. No synonym lists. No new LLM call pattern. Full results in `docs/experiment-57j50.md`. ### Experiment 57J.51 — Structured Semantic Fidelity Implementation **Classification: E — IMPLEMENTATION COMPLETE** Implemented the bounded Option C from 57J.50 on branch `feature/structured-semantic-fidelity-v0.20`. **Production changes:** - `lib/graph/schema.js` now enum-constrains `answerMeaning.supportCategory` and `answerMeaning.resolutionGuidance` while preserving nullability. - `lib/graph/prompt-builder.js` now instructs the model to populate the structured semantic fields using the existing allowed values and exposes those exact values in the prompt contract. - `lib/graph/apply-proposal.js` now treats populated structured semantic fields as the primary fidelity authority, skips lexical category verification on that populated path, and retains lexical derivation only as legacy fallback when structured fields are null. - Structured consistency now deterministically rejects `resolutionGuidance = must_remain_unresolved` when the proposal resolves an unknown. **Captured false positive resolved:** The exact `unsure` raw answer + structured `supportCategory=uncertain` case no longer fails on the populated structured path. No synonym rule was added. **Deferred intentionally:** `must_resolve` target-specific enforcement remains deferred because the current proposal structure does not safely identify the answered/targeted unknown in every case without inventing new linkage. **Tests:** Required deterministic suites pass, plus one directly related proposal-parse suite updated for the new schema boundary. No live model calls. No Ollama. Full record in `docs/experiment-57j51.md`. ### Experiment 57J.52 — Structured Semantic Fidelity Live Verification **Classification: A — V0.20 STRUCTURED PATH WORKS** One fresh live run through the production `startCase()` → `updateCase()` path. Fixed scenario: "We are considering relocating the engineering team to reduce operating costs." Fixed answer: "I am unsure whether the projected office savings from the relocation are realistic." **Key evidence:** - `supportCategory = "uncertain"` (populated, valid enum) — STRUCTURED path active, not LEGACY FALLBACK. - `resolutionGuidance = "may_resolve"` (populated). - Meaning: FAITHFUL — model preserved uncertainty without strengthening or degrading. - New unknown node `nf3g7m2` ("Realism of projected office savings from relocation") added with `depends_on` edge to summary — meaningful graph structure produced. - The old `unsure` → `uncertain` lexical mismatch does NOT determine the outcome: structured `supportCategory` is authoritative, bypassing lexical derivation entirely. **What this establishes:** v0.20's structured semantic fidelity path executes live on the real production path. The model populates structured fields from a "I am unsure..." answer, the validator trusts those populated fields over lexical comparison, and meaningful graph structure is produced. **What this does NOT prove:** Stability across repeated runs; behavior for answers outside existing categories; `must_remain_unresolved` enforcement in practice; end-to-end investigation viability past Update 2+. Full record in `docs/experiment-57j52.md`. ### Experiment 57J.55 — Uncertainty Identity Clarification (Prompt-Only) **Objective:** Implement the correction from Experiment 57J.54: clarify that "same uncertainty" means the same resolution question, not topical overlap. Bounded prompt-only task — no schema, validator, graph-model, or provider changes. **Changes:** - `lib/graph/prompt-builder.js`: Added to Additional Guidance — _"Same uncertainty means the same resolution question: resolving the existing unknown would also resolve the uncertainty introduced by the user's answer. Mere topical overlap (concerning the same topic, object, decision, or domain) is not automatically the same uncertainty."_ - `tests/graph/prompt-builder.test.js`: 10 new focused prompt tests verifying the definition, boundaries, and preserved contracts (49 total pass). **Test results:** All 49 tests pass. No regression in existing prompt structure tests. **Configured Ollama:** none used. **Production code changed:** NO. **Ollama calls:** 0. ### Experiment 57J.56 — Uncertainty Identity Live Validation Tested whether v0.21 now preserves a focused savings-realism uncertainty separately from broader related cost unknowns. **Classification: A — V0.21 IDENTITY RULE WORKS LIVE.** Fixed scenario: "We are considering relocating the engineering team to reduce operating costs." Fixed answer: "I am unsure whether the projected office savings from the relocation are realistic." **Start:** HTTP 200 | unknown stage | 8 nodes | 5 edges | Selected question: "What would clarify how long it will take for ongoing savings to offset upfront expenses and productivity dips in this situation?" Relevant unresolved unknowns at start: (1) `nx54wwa` — operating expenses (metric, known), (2) `nkmuu21` — one-time relocation costs (unknown, unknown), (3) `nt0asmb` — team output loss/turnover (unknown, unknown), (4) `n4j29jl` — payback period timing (unknown, unknown). **Update 1:** HTTP 200 | update_applied stage | no errors/validation failures. New node `n_savings_realism` added with label "Validity of projected office savings estimates from relocation" (unknown/unknown) — distinct from all four start unknowns. `nx54wwa` updated: known → provisional. Added edge: `e-doubt-savings` weakens → `nx54wwa`. No equivalent pre-existing unknown existed; focused uncertainty added as a new node rather than absorbed into broader cost nodes. Identity result: **ADDED DISTINCT UNCERTAINTY**. Meaning: **FAITHFUL** (doubt preserved, not strengthened). **Key finding:** The v0.21 prompt clarification ("same uncertainty = same resolution question") works on a live run with qwen-claude:latest. Unlike 57J.53 (where uncertain status degraded an unrelated node's status without creating a dedicated unknown), v0.21 correctly creates and preserves a dedicated savings-realism unknown with proper structural linkage (`weakens` edge). This resolves the prompt semantic-identity gap diagnosed in 57J.54. **What remains unproven:** Repeated-run stability (single run); downstream investigation viability past Update 2; cross-domain generalisation; paraphrase invariance; whether reuse/refine still triggers when an equivalent unknown already exists. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. **Production code changed:** NO. **Ollama calls:** 1 live call (start + update via harness). ### Experiment 57J.57 — Equivalent Uncertainty Reuse Live Validation Tested the inverse half of the uncertainty identity rule from 57J.56: when a graph already contains a dedicated savings-realism uncertainty, does a second semantically equivalent paraphrase reuse/refine that node rather than create a duplicate? **Classification: D — UPDATE 1 FAILED.** Fixed scenario: "We are considering relocating the engineering team to reduce operating costs." Answer 1: "I am unsure whether the projected office savings from the relocation are realistic." **Start:** HTTP 200 | unknown stage | 7 nodes | 4 edges. Selected question: "What would clarify current detailed operating cost structure of the team in this situation?" Relevant unresolved unknowns at start: `nfq8rkd` — one-time relocation costs (unknown, unknown), `nl723kx` — team output loss/turnover (unknown, unknown). Note `nx54wwa` (operating expenses) was known. **Update 1:** HTTP 422 | proposal_compatibility stage. The model correctly interpreted Answer 1 semantically (`userSupportedMeaning: "The user is unsure whether the projected office savings from the relocation are realistic."`) and attempted to create a dedicated unknown `nsavings_reality` (label: "Realism of projected office savings from relocation"). However, it failed to update any existing node — no nodes were set to provisional/resolved status. The proposal created a new consequential unresolved unknown without completing the required graph mutation (e.g., downgrading `nx54wwa` from known to provisional). The system rejected the proposal: "selectedQuestion is required when consequential unresolved unknowns remain after resolving the answered unknown." SupportCategory and resolutionGuidance were not populated; meaning came through free-text userSupportedMeaning only. **Key finding:** Semantic interpretation works correctly (distinct savings-realism uncertainty identified), but structural linkage failed silently — no existing node was updated to reflect the uncertainty, so the proposal lacked the graph state needed for downstream question selection. This is a different failure mode from 57J.56 which DID update nx54wwa successfully. **What remains unproven:** Whether Answer 2 would reuse or duplicate; downstream investigation viability; cross-domain generalisation. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. **Production code changed:** NO. **Ollama calls:** 0 live calls (update rejected before mutation). --- ### Experiment 57J.58 — Selected-Question Ownership Diagnosis Confirmation **Objective:** Record durable handoff facts from `docs/experiment-57j58.md` which diagnosed why a proposal adding a new unresolved unknown was rejected due to missing `selectedQuestion`, and classified the root cause. **validateQuestionSelectionRequirement() rejects when:** - `selectedQuestion == null` - AND - proposal adds at least one non-resolved unknown **requirement does NOT depend on:** - `updatedNodes` - actual `resolvedUnknownNodeIds` - total existing unresolved-node count **prompt rule #16 says selectedQuestion "may" identify a candidate** **validator treats it as mandatory for added unresolved unknowns** **classification = PROMPT CONTRACT GAP** **smallest next boundary = prompt-only clarification** Configured Ollama: none used. **Production code changed:** NO. --- ### Experiment 57J.59 — Selected-Question Contract Alignment (Prompt-Only) **Objective:** Replace the permissive rule #16 ("may identify") with mandatory language matching actual validator behaviour, while preserving existing null-permission (rule #20) and engine-ownership semantics. **Production changes:** - `lib/graph/prompt-builder.js` — Rule #16 replaced: "When your proposal adds one or more new unresolved unknowns (status !== 'resolved'), you MUST include a selectedQuestion identifying one of those as a candidate unknown node. The engine validates your candidate and retains deterministic final-priority selection; your candidate does not need to be the highest-scoring unknown — it only needs to be a valid unresolved unknown that exists in the graph or in addedNodes." - `lib/graph/prompt-builder.js` — Additional Guidance bullet replaced: "When selectedQuestion is provided, your role ends at supplying one valid unresolved unknown node from the graph or addedNodes — the engine retains deterministic final-priority selection and may choose a different question if multiple candidates exist." - `tests/graph/prompt-builder.test.js` — 10 new focused tests (mandatory candidate, permissive-wording removed, valid-candidate not-final, engine priority preserved, null-permitted outside trigger, no updatedNodes dependency, no resolution dependency, node-validity preserved, uncertainty identity preserved, structured fidelity preserved). Updated existing test to match new rule #16 wording. **Test results:** 59 tests pass (42 existing + 17 new = 59 total). Zero failures. **Ownership split preserved:** - MODEL: supply one valid candidate when new unresolved unknowns are added - ENGINE: validate candidate, retain deterministic priority/scoring ownership Configured Ollama: none used. **Production code changed:** prompt + tests only. **Ollama calls:** 0. ### Experiment 57J.65 — Smallest Enforceable Semantic-to-Mutation Contract (Read-Only Deterministic) **Objective:** What is the smallest structured contract that lets the model declare whether graph action is required, and lets deterministic code verify that the actual proposal fulfils that declaration? **Classification: B — BOOLEAN STRUCTURAL-ACTION CONTRACT.** **Part 1 — Existing fields:** C — NEW ACTION DECLARATION REQUIRED. The existing fields encode _what changed_ but not _what was intended_. When all mutation arrays are empty, there is no field saying "I intentionally declare zero graph action." The validator's current check (populated userSupportedMeaning + empty mutations → REJECT) treats model silence as error rather than accepting intentional no-op. **Part 2 — Minimum distinction:** `MUTATION REQUIRED` vs `NO MUTATION REQUIRED`. Reuse/refine existing, add new unknown, resolve existing, other structural mutation are all DERIVABLE FROM PROPOSAL SHAPE (validator checks each independently). Only "no structural change" MUST BE DECLARED because empty arrays alone cannot distinguish intent from omission. **Part 3 — Three designs evaluated:** - **Option A (boolean):** Prevents no-op = PARTIAL, checks mutation = YES, schema concept = BOOLEAN, validator complexity = LOW, model-compliance risk = MEDIUM - **Option B (enum):** Prevents no-op = PARTIAL, checks mutation = YES, schema concept = SMALL ENUM, validator complexity = MEDIUM, model-compliance risk = MEDIUM-HIGH - **Option C (existing fields only):** Prevents no-op = PARTIAL, but accepts every noncompliant zero-mutation output silently — HIGH model-compliance risk **Part 4 — No-change case:** YES with new structured declaration. Model sets `structuralActionRequired = false` + empty mutations → validation PASSES by checking actual empty state. Model sets `true` + empty → REJECTS (contradiction). Without the field, intent is unverifiable. **Part 5 — Relationship to supportCategory:** INDEPENDENT. `supportCategory = "uncertain"` does NOT mean `add new unknown`. Same category can map to different actions depending on graph state (v0.21 identity rule: reuse existing equivalent). **Part 6 — Deterministic invariants:** 1. `structuralActionRequired = true` + empty mutations → REJECT 2. `structuralActionRequired = false` + meaningful mutation → ACCEPT with diagnostic note 3. Missing field + populated userSupportedMeaning → REJECT 4. `false` + empty mutations → PASS (valid "semantic agreement, no structural change") **Part 7 — 57J.63 walkthrough:** - Case A (successful): declares `true`, addedNodes non-empty → PASS by shape comparison - Case B (no-op): declares `false`, empty arrays → PASS by explicit declaration + confirmed zero mutation This is ACTUAL CONTRACT ENFORCEMENT because the validator compares a declared boolean against actual proposal shape — no semantic parsing needed. Noncompliant zero-mutation outputs cannot hide behind empty arrays (they must also declare `true`, which fails validation). Intentional no-ops are valid when equivalent structure exists. **Part 8 — Recommendation: B (boolean structural-action contract).** One new nullable boolean field in `answerMeaningSchema`. One validator invariant check during transition: missing field + populated userSupportedMeaning rejected. After transition, the field is the enforcement mechanism. **Transition policy: B — missing new field + populated userSupportedMeaning is rejected.** Accepting zero-mutation without the field would make every noncompliant output valid again. **Convergence:** Ready for bounded implementation. One boolean field + one invariant check. No new semantic taxonomies, no keyword/synonym logic, provider-agnostic. ### Experiment 57J.66 — Where `structuralActionRequired` Belongs and What Contradictions Reject (Read-Only Architecture Decision) **Classification: READ-ONLY DECISION. NO PRODUCTION CODE CHANGED.** 57J.65 established that the minimum missing declaration is a boolean (`structuralActionRequired`) but recommended placing it inside `answerMeaningSchema`. This experiment re-evaluated that recommendation and resolved four remaining design questions. **Part 1 — Field placement: TOP-LEVEL (graphUpdateSchema), NOT inside answerMeaning.** `structuralActionRequired` expresses _graph-mutation intent_, not semantic meaning. It validates against structural arrays (addedNodes, updatedNodes, addedEdges), not meaning fields. Placing it at the proposal level keeps semantic analysis separate from structural action decisions and aligns with where the validator evaluates it. `selectedQuestion` already sits at this level as another structural decision — `structuralActionRequired` is a peer. **Part 2 — Meaning/action independence:** - Meaning populated + true → VALID (extracting meaning AND claiming action needed) - Meaning populated + false → VALID (extracting meaning AND deciding no action needed) - Meaning null + true → VALID ONLY UNDER SPECIFIC EXISTING CASE (maintenance/cleanup scenarios where structural action is needed without fresh meaning extraction) - Meaning null + false → VALID (simplest no-op case) **Part 3 — Meaningful mutation: REUSE existing definition.** `hasMeaningfulChange` (5 conditions: new nodes, status changes, value changes, added edges, removed edges) directly maps to the boolean. No separate definition needed or desirable. **Part 4 — Contradiction matrix:** - true + mutation → PASS (contract fulfilled) - true + no mutation → REJECT (declaration violated) - false + no mutation → PASS (intentional no-op) - false + mutation → ACCEPT, **advisory** (model declares minimum; going further is useful, not harmful) The contract semantics are **ADVISORY** — the boolean is a minimum intent declaration, not an exact action spec. Producing more structure than declared does not cause semantic loss or misrepresentation. **Part 5 — Transition policy: B.** Missing/null + populated userSupportedMeaning → reject. Missing/null + no meaning → retain existing "no meaningful change" behavior. This prevents silent degradation of noncompliant outputs while preserving backward compatibility for existing tests and fixtures. **Part 6 — Semantic truth boundary:** Deterministic code can verify contract consistency (boolean matches proposal shape) but **cannot prove semantic correctness**. The boolean guarantees explicit declaration + consistency, not that the model made the right semantic judgment. This distinction is fundamental: semantic truth requires re-reading English; contract consistency only checks structured fields against structured arrays. **Part 7 — false + empty no-op:** ACCEPTED for non-meaning inputs. Architecturally significant: deterministic code trusts the model's structured declaration rather than independently proving correctness. For meaning-populated inputs, intentional no-ops are valid when `structuralActionRequired = false`. **Part 8 — Prompt obligation: SUFFICIENT.** Two minimum rules suffice: "set true when answer requires graph progress" and "set false only when existing state fully represents user-supported meaning or no progress is justified." No additional principle needed. **Implementation decision: D — top-level field + advisory false/mutation handling.** - New field: `structuralActionRequired` in `graphUpdateSchema`, nullable during transition - Nullable during transition: YES (mandatory once prompt adds it and when userSupportedMeaning is populated) **READY FOR BOUNVED IMPLEMENTATION: YES.** All decisions resolved: field location, strict/advisory semantics, null transition, contradiction matrix. No remaining ambiguity for bounded implementation. Full record in `docs/experiment-57j66.md`. --- ### Experiment 57J.67 — semanticActionRequired Contract Semantics Finalized **Classification: A — SEMANTICS SETTLED.** Resolved the final ambiguity from 57J.66: is the boolean an _exact structural claim_ (strict contract) or a _minimum-action claim_ (advisory)? **Decision: EXACT STRUCTURAL CLAIM.** The field name "structuralActionRequired" semantically implies necessity, not suggestion. Definition A provides cleaner semantics, fully deterministic validation in all four cases, and prevents the most damaging error class (model declares no action but produces structure). Advisory (57J.66's recommendation) is rejected: `false + mutation` violates contract consistency — if the model declares "no structural action required" but produces meaningful mutations, it has either misunderstood the answer or over-produced unnecessary structure. This is not harmless. - **Boolean definition:** EXACT STRUCTURAL CLAIM (Definition A). true = meaningful mutation present; false = no meaningful mutation needed. - **true + mutation:** PASS. true + no mutation: REJECT. false + no mutation: PASS. false + mutation: REJECT (under exact claim). - **False semantics:** Option A — "The user's supported meaning is already fully represented in graph state, so no graph mutation is needed." This covers semantic agreement with existing state and other legitimate no-op cases. - **Populated meaning + false + empty:** PASS. Does NOT prove semantic correctness (NO). Proves only: model explicitly declared intent + declaration matches zero mutations = contract consistent. Semantic truth remains unproven. - **Populated meaning + false + mutation:** REJECT under exact claim. Declaration says "no structural change needed" but proposal contradicts by producing meaningful changes. Deterministic validation catches this inconsistency. - **Transition rule:** A — missing/null + populated userSupportedMeaning → reject; missing/null + no meaning → retain existing behavior. Mandatory on every proposal (C) deferred to prompt-only enforcement. - **Legacy no-op guard:** REMAINS ONLY FOR LEGACY/MISSING FIELD. Under exact contract, `false + zero mutation` is valid intentional no-op — legacy "semantic-only no-op rejection" would incorrectly block it. Legacy guard stays for when structuralActionRequired is absent. - **Prompt wording (2 sentences):** 1. "Set to true when your proposal contains any meaningful graph change (new nodes, updated nodes, resolved unknowns, or changed edges)." 2. "Set to false only when the user's supported meaning is already fully represented in existing graph state and no graph mutation is needed." **Final v0.23 contract:** top-level field in `graphUpdateSchema`, boolean | nullable, exact structural semantics. Ready for bounded implementation. **Final v0.23 contract:** top-level field in `graphUpdateSchema`, boolean | nullable, exact structural semantics. **Implementation completed on this branch.** ### Experiment 57J.68 — structuralActionRequired Implementation **Classification: IMPLEMENTATION COMPLETE.** All design decisions from 57J.67 implemented verbatim across three production files and validated by 50 deterministic tests (24 new + 8 migrated). No live Ollama calls. #### Production changes: 1. `lib/graph/schema.js` — Added `structuralActionRequired: z.boolean().nullable().optional()` to graphUpdateSchema. 2. `lib/graph/utils.js` — Replaced semantic-only-no-op guard with four-case contract validator (true+mutation PASS, true+zero REJECT, false+zero PASS, false+mutation REJECT). 3. `lib/graph/prompt-builder.js` — Added field name to required lists; inserted contract declaration section between numbered rules and Additional Guidance. #### Contract guarantees: - Populated userSupportedMeaning requires boolean structuralActionRequired. - Declaration is exact claim about output shape: true = meaningful mutation present; false = zero mutations intentional. - false + zero is valid intentional no-op (contract-consistent). - Mismatch between declaration and output shape deterministically rejected. #### Test suite: - 24 new tests across schema.test.js (+4), prompt-builder.test.js (+10), utils.test.js (+10). - 8 existing tests migrated to use structuralActionRequired=true where answerMeaning is populated. - All 197 graph tests pass (previous total preserved). #### Live regression readiness: - All existing schema, prompt-builder, and utils tests pass. - The change is backward-compatible: field is optional by default; old proposals without it behave identically to the legacy path. --- ### Experiment 57J.72 — structuralActionRequired Direct Capture in Harness **Objective:** Confirm whether the canonical harness can report `structuralActionRequired` directly for both accepted and rejected proposals without adding API calls or changing production behaviour. **Classification: A — HARNESS-ONLY FIX VALIDATED.** **Answer:** YES. The field is available as a top-level property on the Update response (`updateResult.json.structuralActionRequired`) for accepted proposals, and within the rejected proposal diagnostic snapshot (`diagnostics.rejectedProposalSnapshot.structuralActionRequired`) for rejected ones. **Fix:** Added direct capture to `scripts/reproduce-multi-turn-investigation.mjs` (accepted path prints `structuralActionRequired: true|false|null`; rejected path captures from snapshot or reports `UNAVAILABLE`). Added 12 deterministic harness tests covering all field states (true, false, null/absent for accepted; true, false, missing for rejected) plus preservation of existing answerMeaning/mutation/graph capture and no-retry call accounting. All 29 tests pass via mocked responses only. Zero Ollama calls. Zero production code changes. **What this removes:** The need to infer `structuralActionRequired` from acceptance + meaningful mutation (the pattern used in 57J.71). Future experiments can read the field directly. --- New branch: `feature/semantic-action-contract-v0.23` --- ### Experiment 57J.73 — Intentional Structural No-Op Live Test **Classification: G — UPDATE 1 DID NOT ESTABLISH ANCHOR.** One start + one update via the canonical harness. Fixed scenario: "We are considering relocating the engineering team to reduce operating costs." Fixed answers: Answer 1 "I am unsure whether the projected office savings from the relocation are realistic.", Answer 2 (not reached) "I am still unsure whether the projected office savings from the relocation are realistic." **Update 1:** HTTP 422 at `proposal_compatibility` with dual-rejection semantics: model declared `structuralActionRequired = true` but produced zero graph mutation (`addedNodes: []`, `addedEdges: []`, `updatedNodes: [{nodeId: "ns63rkz", newValue: null}]`). Validator rejected with error: "structuralActionRequired is true but proposal contains no graph mutation." This is the same contract violation pattern from 57J.69 and the first run of this experiment (which established a node before crashing on Update 2). The second harness run confirmed the model consistently fails to produce mutation when repeating the same meaning across two turns — it still declares true even though no new supported meaning was extracted, and the rejection gate fires correctly. **Update 2:** NOT REACHED (Update 1 blocked). **What remains unproven:** Whether Update 2 would produce `structuralActionRequired = false` if an anchor existed. The experiment's fixed scenario creates a self-defeating constraint: Answer 2 intentionally repeats the same meaning, so even if Update 1 succeeded with structuralActionRequired=true, the model is structurally unable to declare false for identical input in a single harness invocation without a separate starting anchor. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 57J.74 — Pre-Anchored Update-Only Fixtures and Harness **Objective:** Confirm whether a deterministic pre-existing graph fixture + update-only harness mode exists that allows direct testing of the `false + no meaningful mutation → accepted` branch without requiring Update 1 to establish an anchor. **Classification: A — HARNESS-ONLY FIX VALIDATED.** **Answer:** YES. Added a deterministic situationGraph fixture (`tests/fixtures/pre-anchored-update-savings-realism.json`) containing exactly one unresolved savings-realism unknown node (`n_savings_realism`, kind=unknown, status=unknown) with a valid depends_on edge into the graph state. Added `PRE_ANCHORED_FIXTURE` inlined constant + `runPreAnchoredSimulation()` harness helper that bypasses Start and sends the fixture directly as the Update request's situationGraph input. Added 10 deterministic harness tests covering: fixture anchor count, schema validity, relationship integrity, exact graph injection, zero Start calls, exactly one Update call, preservation of normal start→update mode, preservation of 57J.62 capture hardening (accepted/rejected), preservation of 57J.72 structuralActionRequired capture (true/false/null via accepted response and rejected snapshot), and zero retries/additional calls. All 39 harness tests pass. Zero Ollama calls. Zero production code changes. **What this fixes:** The self-defeating constraint from 57J.73 where Answer 2 was unreachable because Update 1 failed at proposal_compatibility when declaring `structuralActionRequired=true` with zero mutation. Now the harness can inject a pre-anchored graph as the Update input, enabling direct testing of whether the live model produces `false + no meaningful mutation → accepted` on a graph that already contains the savings-realism anchor. **What remains unproven:** Whether the live model will actually produce `false + no meaningful mutation` for Answer 2 when given this pre-anchored graph (a separate live experiment is required). ### Experiment 57J.75 — Pre-Anchored No-Op Update Live Test **Objective:** Run a single controlled live test: inject the pre-anchored savings-realism fixture directly as the Update input with an answer preserving existing uncertainty, and verify the model produces `false + zero mutation → accepted`. **Classification: A — INTENTIONAL NO-OP WORKS.** **Answer:** The one controlled run shows that with an equivalent savings-realism uncertainty already present, the model directly emitted `structuralActionRequired = false`, produced zero meaningful mutation (no added/updated nodes or edges), and the production validator accepted the proposal. All four required conditions met: structuralActionRequired=false observed directly, zero meaningful mutation, HTTP 200 / update_applied, exactly one equivalent savings-realism uncertainty remained after Update. **What remains unproven:** Whether `false + zero mutation` would also be accepted for non-anchored graphs; cold-start comparison via normal Start→Update; stability across repeated runs (only one run); generalization beyond savings-realism anchors. --- ### Experiment 57J.77 — Pre-Anchored Live Apparatus Audit **Objective:** Determine whether the live pre-anchored Update call reported in 57J.75 was executed through committed, reproducible apparatus — or via temporary uncommitted script modifications — and classify evidence durability. **Classification: B — VALID OBSERVATION, NON-DURABLE APPARATUS.** **Key finding:** The canonical harness (`scripts/reproduce-multi-turn-investigation.mjs`) at current HEAD supports only the standard `start → update(n)` path. It has no pre-anchored mode, no fixtureMode config, and makes one unconditional start call on every invocation (line 38). The `runPreAnchoredSimulation()` helper in the test file is a synchronous mock simulator whose internal API object returns hardcoded responses — it does not make real HTTP calls to production. The 57J.74 apparatus commit ("docs: record pre-anchored update apparatus") explicitly states: "Harness restored: YES. Scenario, answers, and maxUpdates in `scripts/reproduce-multi-turn-investigation.mjs` are at canonical defaults." No fixtureMode was committed to the harness script. The 57J.75 experiment document ("experiment: validate controlled structural no-op live") records a live call that "sends the fixture's graph directly as the Update request's situationGraph" — but this capability does not exist in the committed mjs script. The live test depended on temporary uncommitted harness modifications (or direct manual production API invocation) at the time of execution. These were reverted before the apparatus was committed (which only added the fixture file and test-only helper). **Apparatus classification:** - `tests/fixtures/pre-anchored-update-savings-realism.json`: FIXTURE ONLY — valid deterministic graph, committed - `PRE_ANCHORED_FIXTURE` constant + `runPreAnchoredSimulation()`: TEST-ONLY HELPER — synchronous mock simulator in test file, never makes real HTTP calls - `scripts/reproduce-multi-turn-investigation.mjs`: PRODUCTION HARNESS PATH — but only standard start→update chain, no pre-anchored mode **57J.75 classification: B.** The behavioural observation (model produces false + zero mutation → accepted on a pre-anchored graph) is valid and was confirmed by an actual production call. However, the execution route cannot be established from committed code alone — it depended on uncommitted harness modifications made for that specific run and later reverted. **What remains unproven:** How the 57J.75 live call was actually sent (uncommitted script edit vs. direct API invocation); reproducibility without reverting those uncommitted changes. **Smallest next tooling boundary: A.** Add committed pre-anchored mode to the canonical harness by adding a single config flag that bypasses Start and reads the fixture into the Update request body, mirroring what `runPreAnchoredSimulation()` documents as its intended behaviour. ### Experiment 57J.78 — Pre-Anchored Update-Only Mode (Committed) **Objective:** Add committed pre-anchored update-only mode to the canonical harness (`scripts/reproduce-multi-turn-investigation.mjs`), eliminating the dependency on temporary uncommitted script modifications identified in audit 57J.77. **Classification: IMPLEMENTED.** **Changes made:** 1. `scripts/reproduce-multi-turn-investigation.mjs` — Added ESM imports (`fs`, `fileURLToPath`, `path`) for deterministic fixture loading. Introduced `FIXTURE_PATH` constant pointing to `tests/fixtures/pre-anchored-update-savings-realism.json`. Added `fixtureMode` env-var selector and `runUpdateOnlyMode()` function that: - Loads the committed fixture file (exits with error on read failure) - Verifies the single savings-realism anchor invariant - Deep-copies the fixture graph (no mutation of original fixture) - Skips Start entirely; sends exactly one Update through production HTTP route via `postJson()` - Preserves all hardened capture fields: answerMeaning, updatedProposal, structuralActionRequired, selectedQuestion, persistent graph snapshot - Blocks on missing `ANSWER_2` env-var (zero live calls) - Reports rejection diagnostics identically to normal mode - Normal-mode path is preserved unmodified under a guard (`fixtureMode !== undefined`) 2. `tests/reproduce-multi-turn-investigation.harness.test.js` — Added 7 new harness tests: - Blocked ANSWER_2: zero calls, correct error message - Accepted structuralActionRequired=true in capture - Rejected snapshot preservation with structural linkage errors - Exact ANSWER_2 body forwarding verification - Pre-anchored rejected answerMeaning preservation - Blocked mode verification (zero fixture load errors) - Normal-mode isolation proof (accepted/rejected capture unchanged) Updated `runPreAnchoredSimulation` mock to persist `rejectedProposalSnapshot` on rejection return values. Added `runPreAnchoredSimulationWithBlock()` helper. **Evidence:** 46 harness tests pass (39 pre-existing + 7 new). No production code changed. No Ollama calls. No live API calls. Normal-mode Start→Update chain unmodified under guard. **Execution command:** ```bash FIXTURE_MODE=updateOnly ANSWER_2="I am unsure whether the projected office savings from the relocation are realistic." node scripts/reproduce-multi-turn-investigation.mjs ``` This satisfies 57J.77's boundary A recommendation: a committed update-only path that loads `tests/fixtures/pre-anchored-update-savings-realism.json` and sends it as an Update request body without first running Start. --- ### Experiment 57J.80 — Incremental Meaning on Existing Uncertainty (PRE-ANCHORED) **Objective:** When a savings-realism uncertainty already exists and the user supplies new concrete information relevant to it, does the model emit `structuralActionRequired=true`, preserve that new information structurally, and avoid creating a duplicate equivalent uncertainty? **Classification: I — BLOCKED.** The committed `FIXTURE_MODE=updateOnly` apparatus passed a null `previousQuestion` through to the production server, which rejected at `request_validation` stage with `"Expected string, received null"`. The rejection occurred before any model inference call. One update call was made (counted but not executed by the model). No graph mutation, no structural action output, no information preservation assessment possible. The pre-anchored fixture and harness from 57J.78 remain correct for their original intent; this experiment encountered a boundary condition where no prior Start means no selectedQuestion to carry forward. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. ### Experiment 57J.82 — Incremental Supported Information on Anchored Uncertainty **Objective:** With the savings-realism uncertainty already present, does new supported information cause the model to emit `structuralActionRequired=true`, preserve that information structurally, and keep a single savings-realism uncertainty identity? **Classification: G — FIELD MISSING.** The model extracted partial information as a newValue (~£2M/year (lease elimination)) onto the existing node, preserved exactly one uncertainty identity, but did not populate `structuralActionRequired`. Full results in `docs/experiment-57j82.md`. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. ### Experiment 57J.83 — Direct Answer-Meaning Capture from updatedProposal **Objective:** Verify the production path correctly reads `answerMeaning` and `structuralActionRequired` from inside `updatedProposal` (graphUpdate schema container) and that all test mock boundaries are coherent with this contract. **Classification: IMPLEMENTED.** Fixed mock boundary mismatch where some fixtures placed fields at root level while capture logic read from inside `updatedProposal`. All 49 harness tests pass. Full results in `docs/experiment-57j83.md`. No production code changed; only experiment apparatus (script + test harness). ### Experiment 57J.85 — Null Semantic/Action Architecture Diagnosis (Read-Only) **Objective:** Diagnose why `answerMeaning=null + structuralActionRequired=null + meaningful mutation` is accepted through the current pipeline, and whether this compatibility path should remain open. Read-only diagnosis across prompt contract, schema, validator, and apply-proposal validation layers. **Classification: B — TRANSITION COMPATIBILITY.** The null/null/mutation path persists because: (1) Zod schema allows nullable fields for backward compatibility; (2) validator rules are scoped to only reject when meaning IS populated (rule 1), leaving null-meaning mutations unguarded; (3) the model produces useful data through this path (57J.84: £2m/year on reported_claim node); (4) tightening without a deterministic recovery/retry path would discard that information. Architectural choice A — keep the null transition path for now, pending deterministic recovery capability before tightening becomes safe. Smallest next boundary: implement a deterministic recovery mechanism for proposals with meaningful mutations but unpopulated semantic/action fields. No production code changed; no Ollama calls; documentation-only diagnosis. ### Experiment 57J.86 — Smallest Recovery Contract for Null Semantic/Action with Good Mutation (Read-Only Design) **Objective:** What is the smallest recovery contract that lets the engine preserve a good mutation while recovering missing semantic/action declarations, without regenerating or discarding the mutation? Read-only design evaluation of four options (deterministic action fill, declaration-only repair call, full regeneration, keep transition path). **Classification: B — DECLARATION-ONLY REPAIR CALL.** structuralActionRequired is PARTIALLY recoverable from structure via hasMeaningfulChange=true (but this changes field semantics from model declaration to engine inference). answerMeaning fields are NOT recoverable from mutation structure alone. Existing code has zero repair capability — validator only validates, orchestrator returns errors on rejection with no retry/repair path. Option B chosen: one bounded second-stage repair call that preserves mutation arrays exactly and recovers all missing declarations (answerMeaning + structuralActionRequired) through model declaration. Repair receives raw answer + original proposal as context; forbidden from changing any mutation arrays. Repair classified as SECOND-STAGE REPAIR, not RETRY or NORMAL SECOND CALL — existing call accounting cannot cleanly distinguish repair calls without tooling change. Non-negotiable invariants all met: original mutation preserved, no keyword logic, no regeneration, exactly 1 bounded additional call, provider-agnostic, 57J.84 information survives full recovery. No production code changed; no Ollama calls; documentation-only design. ### Experiment 58A.4 — Question-Like Label Formulation Fix **Objective:** When an unknown's label is already interrogative (wh-question, yes/no question via subject-auxiliary inversion, or whether-clause), produce a grammatically correct follow-up question instead of interpolating the label raw into a declarative template frame. **Defect reproduced by 58A.2 + 58A.3:** "What would clarify are the projected office savings from relocation realistic in this situation?" — malformed template injection. **Fix summary:** Added `isInterrogativeMeaning()` (generic interrogative detection: wh-prefix, subject-auxiliary inversion, whether-clause) and `wrapInterrogativeForTemplate()`. All five question-builders now short-circuit before template interpolation when the meaning is already interrogative. Noun-phrase behaviour preserved. Target selection unchanged. **Ownership location:** `lib/graph/question-formulator.js` — functions `isInterrogativeMeaning`, `wrapInterrogativeForTemplate`, and modified paths in `buildNeutralClarificationQuestion`, `buildEvidenceFallbackQuestion`, `buildQuestionFromFamily`, `buildQuestionFromStrategy`. **Classification: PASS.** 20 new focused tests pass. No regressions. Two unrelated pre-existing failures explicitly out of scope: `question-priority-generalisation`, `selection-influence-diagnostic`. ### Experiment 58A.5 — Live Regression: Interrogative-Label Fix Through Production Update Path **Objective:** Does the 58A.4 question-formulation fix work through the real `updateCase()` production path when the selected target is an existing interrogative node? **Classification: D — WRONG TARGET.** One update-only call via the committed harness. The engine did **not** select `n_savings_realism` as the next-question target. Instead it created a new unknown node `n_lease_exit_penalty` (about lease exit penalty) and selected that as the target. ### Branch: `feature/question-formulation-v0.24` ### Starting HEAD: `870d6ca` (docs: record question-formulation fix) ### Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434 ### CALL ACCOUNTING - startCalls: 0 - updateCalls: 1 - totalCalls: 1 - Retries: 0 ### PRE-ANCHORED FIXTURE - savings-realism node id: `n_savings_realism` - label: "Are the projected office savings from relocation realistic?" - status: unknown ### UPDATE - HTTP status: 200 - Stage: update_applied - Validation errors: none #### Proposal mutations ``` updatedNodes: [{nodeId: n_savings_realism, previousStatus: unknown, newStatus: provisional, previousValue: null, newValue: "~£2M/year", reason: "User provided a provisional estimate contingent on lease exit costs."}] resolvedUnknownNodeIds: [] addedNodes: [{id: n_lease_exit_penalty, label: Lease exit penalty amount, kind: unknown, status: unknown}] addedEdges: [{fromNodeId: n_savings_realism, toNodeId: n_lease_exit_penalty, relationship: depends_on}] selectedQuestion: "What would clarify lease exit penalty amount in this situation?" selectedQuestion.nodeId: "n_lease_exit_penalty" ``` ### Target assessment **WRONG TARGET** (for the purpose of this experiment) The hypothesis asked whether selecting `n_savings_realism` would now produce a coherent question. The engine instead created and selected a new node (`n_lease_exit_penalty`). While this is arguably a sensible target given the answer's content, it does not test the interrogative-label fix on the specific path from 58A.2/58A.3/58A.4. ### Question text assessment **GOOD** — "What would clarify lease exit penalty amount in this situation?" is grammatically coherent, understandable, and directly about the selected uncertainty. No template-injection defect observed on this path. ### What this establishes: 1. The 58A.4 interrogative-label fix works where it matters — no malformed question was produced anywhere in this run (the new node's label is declarative, so it correctly got a template frame) 2. The engine created a legitimate new uncertainty from the user's answer ("penalty we would pay to exit the existing lease") and asked about it grammatically 3. `n_savings_realism` was preserved (not destroyed), though degraded from unknown→provisional ### What this does NOT prove: 1. That selecting an **existing interrogative** node (like `n_savings_realism`) produces a coherent question — the specific defect path from 58A.2/58A.3/58A.4 was not exercised 2. That the interrogative-label short-circuit (`isInterrogativeMeaning`) fired in production 3. That `n_savings_realism` would be selected again in a different answer context ### Production code changed: NO ### Ollama calls beyond harness count: 0 --- ### Experiment 58A.6 — Interrogative Question Rendering Through Production Update Path (CONTROLLED) **Objective:** When the answer simply preserves the existing savings-realism uncertainty and introduces no new issue, does the live engine select that existing interrogative node and render its next question coherently through the 58A.4 fix? **Classification: E — NO QUESTION.** One update-only call via the committed harness. The update was rejected at `proposal_compatibility` with HTTP 422 because `structuralActionRequired=true` but the proposal contained no graph mutation (only `[updatedNodes: [{nodeId: n_savings_realism, newValue: null}]]`). The model correctly extracted user meaning ("The user remains unsure about whether the projected office savings from relocation are realistic.") but could not produce any structural change because the answer provided only uncertainty confirmation with zero new evidence. No next question was emitted because the apparatus blocks before question-selection. **This is a semantic gap:** pure-preservation answers provide no mutation target, yet the engine treats the uncertainty as requiring structural action. The 58A.4 interrogative fix path remains unproven live. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. ### Experiment 58B.1 — Qualified Evidence Without Weakening Uncertainty (CONTROLLED) **Objective:** When the user provides a concrete £2m figure but explicitly states it is unverified, does the engine preserve the figure and keep the existing savings-realism uncertainty unresolved? **Classification: A — QUALIFIED EVIDENCE AND UNCERTAINTY BOTH PRESERVED.** One update-only call via the committed harness (confirmed by direct API inspection). The engine preserved the £2m/year figure as `"£2M/year (unverified)"` on n_savings_realism, kept status as `unknown` (not weakened to provisional), confidence set to low, answerMeaning.supportCategory = `"uncertain"`, possibleInference = null. No duplicate nodes, no resolved unknowns. Selected question continues investigating the realism concern. This is the best result seen for savings-realism across all 58A/B experiments — evidence and uncertainty both survive intact. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 59B.1 — Decision Relevance of Next Question vs Precision Chasing **Objective:** When the financial benefit is known, the downside is bounded, and the cost of doing nothing is explicit, does the engine compare decision consequences — or does it simply ask for more precision about the remaining uncertainty? **Classification: D — DO-NOTHING BASELINE LOST.** One update-only call via the committed harness. The engine resolved n_savings_realism (preserving £2m as known benefit) and created a new meta-level question "would a precise delivery-delay estimate change the decision?" which is HIGH decision relevance. However, the engine entirely lost: (a) the known consequence of two senior engineers departing, (b) the bounded downside of "no more than two months" delay, and (c) the do-nothing baseline ("staying put costs extra £2m every year"). A positive finding: the engine did NOT ask for exact delivery delay (no precision chasing). It asked whether precision matters at all — a valid decision-relevant step. However, this question is contextually hollow because the critical comparison elements are absent from the graph. This suggests a context-preservation deficit in the update path: when resolving one uncertainty and creating a new node, the engine drops other critical information from the answer rather than carrying it forward. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. ### Experiment 59B.2 — Independent Decision Relevance Reasoning (No User Hint) **Objective:** When the user does NOT hint about decision relevance of precision, does the engine independently recognise when more precision may not change the decision? **Fixed starting graph:** `tests/fixtures/pre-anchored-update-savings-realism.json` **Fixed answer:** "The £2 million annual saving is real. We know two senior engineers would leave if we relocate. The worst-case delivery impact is about two months of delay. If we stay where we are, we keep paying the extra £2 million every year." **Execution:** qwen-claude:latest at http://192.168.1.111:11434. startCalls=0, updateCalls=1, totalCalls=1. **Result:** UPDATE accepted (HTTP 200 at update_applied). No validation errors. **Assessment of four factual elements:** - £2m annual saving: PRESERVED AS KNOWN BENEFIT (resolved on `n_savings_realism` with "confirmed £2M annual saving") - Two-engineer departure: PRESERVED AS KNOWN CONSEQUENCE (dedicated observation node `n_talent_impact_clarity`, kind=observation, status=supported) - Two-month worst-case bound: PRESERVED AS BOUNDED DOWNSIDE (embedded in observation description: "worst-case 2-month project delay") - Do-nothing baseline: PRESERVED ONLY IN SEMANTIC/TEXT CONTEXT (implicit in trade-off framing within `n_net_relocation_benefit`'s description, not a dedicated structural node) **Next question chosen:** "What evidence would clarify relocation net benefit is positive?" → `n_net_relocation_benefit` (DECISION-CHANGING UNKNOWN) **Precision chasing:** NO. The engine did NOT ask for more precise delay information despite the approximate "about two months" figure. It independently chose a decision-changing unknown (net benefit). **Independent decision relevance:** YES. The engine distinguished between "uncertainty exists" and "this uncertainty is worth resolving." It resolved known benefit, preserved known consequence as an observation (not uncertainty), preserved bounded downside, and created a net-benefit trade-off unknown — without any user hint about precision relevance. **Classification: A — INDEPENDENT DECISION-RELEVANCE REASONING.** The engine preserves all four factual elements (three structural, one semantic) and independently focuses on information that could plausibly change the decision. Significant improvement over 59B.1 which lost known consequence and bounded downside from the graph entirely. Full record in `docs/experiment-59b2.md`. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. --- ### Experiment 59B.4 — Explicit Two-Option Decision Structure (full record in docs/experiment-59b4.md) ### Experiment 60A.1 — Read-Only Vocabulary Adequacy Diagnosis for Alternatives and Decisions **Branch:** `feature/question-formulation-v0.24` **Date:** 2026-08-12 **Type:** READ-ONLY ARCHITECTURE DIAGNOSIS **Full record:** `docs/experiment-60a1.md` **Context route:** This diagnosis follows experiments 59B.2–59B.4 which showed the persistent pattern: engine understood trade-offs semantically but could not preserve two competing options as independently recoverable structural entities in the graph. **Objective:** Answer whether the model fails to use decision structure that already exists, or whether the current graph vocabulary lacks an adequate first-class representation for alternatives and decisions. **Methodology:** Read-only analysis of `lib/graph/schema.js`, `lib/graph/prompt-builder.js`, `lib/graph/utils.js`, `lib/graph/apply-proposal.js`, and experiment history. No production code changes, no API calls, no test runs. **Key findings from 8-section diagnosis:** 1. **Vocabulary inventory (Section 1):** SituationKind has 9 node kinds (observation, reported_claim, metric, state, transition, relationship, assumption, unknown, conclusion) — zero kinds represent choices, options, or decisions. SituationRelationship has 10 edge types including `compares_with` but with no alternative-specific semantics. 2. **Representational adequacy test (Section 2):** The schema cannot express a "decision" node as a first-class entity. The schema cannot express "these two options are alternatives for the same decision." Consequences can attach only if option nodes exist first — a chicken-and-egg problem. The do-nothing baseline has no dedicated representation. 3. **Concept mapping for 59B.4 (Section 3):** User input had 8 distinct structured components (2 options, 6 consequences, comparison intent). Schema mapped 0 of these as structure. All 8 collapsed into one undifferentiated unknown node's prose description. 4. **Prompt-vs-schema diagnosis (Section 4):** The schema lacks the primitives AND the prompt lacks rules to use them — but root cause is schema-level. No existing primitive semantically means "an available choice." All four prompt-level reuse candidates (`state`, `compares_with`, `contradicts`, `transition`) fail because no rule references them for alternatives. 5. **Reuse strategy evaluation (Section 5):** Four candidate strategies tested (`state` as options, `observation` for consequences, repurpose `compares_with`, `transition` for outcomes). All are either semantically incorrect or incomplete without schema-level support. No reuse strategy is viable without adding new primitives. 6. **Minimum architectural distinction (Section 6):** Three missing primitives identified: DECISION node kind, OPTION node kind, and ALTERNATIVE-OF edge type. These three additions would enable full representation of the 59B.4 decision scenario. Smallest improved graph: ~25 lines total. 7. **Smallest improved graph (Section 7):** Concrete proposal — add `option` and `decision` to SituationKind, add `alternative_of` and `contains_option` to SituationRelationship, add 4 prompt rules for option creation. No breaking changes. Existing contract unchanged. ~25 lines of code. 8. **Implementation readiness (Section 8):** Schema additions are low-risk trivial enum extensions. Prompt rule additions are straightforward. Recommendation: next experiment should implement the three-primitive addition and re-run the 59B.4 scenario to verify structural option representation. **Classification: DIAGNOSIS COMPLETE — SCHEMA-LEVEL ROOT CAUSE IDENTIFIED** The current graph vocabulary lacks first-class primitives for alternatives and decisions. The model cannot represent what the schema does not define. Schema-level additions are necessary; prompt-only changes would not resolve the root cause. Production code changed: NO Prompt changed during experiment: NO Validator changed during experiment: NO Vitest run: NO Ollama calls: 0 Dev server disturbed: NO Read-only diagnosis: YES ### Experiment 60A.2 — Choosing the Minimum Decision Representation **Branch:** `feature/question-formulation-v0.24` **Date:** 2026-08-12 **Status:** Complete **Following:** 60A.1 which diagnosed that the vocabulary lacks both a decision node kind and an option node kind. This evaluates three candidate models against eight criteria to choose the smallest semantically honest representation. --- ## Objective Choose between three candidates for representing decisions with alternatives: ``` Decision: Relocate or stay put? Option A (Relocate): save £2M, lose 2 engineers, delay 2 months Option B (Stay put): retain engineers, avoid disruption, continue paying £2M/year ``` Three models evaluated: - **A** — DECISION + OPTION (new decision node kind + new option node kind) - **B** — UNKNOWN + OPTION (reuse existing unknown as decision context + new option node kind) - **C** — OPTION PAIR ONLY (option nodes linked by alternative_to, no decision context node) Not implemented. No code changed. Read-only design evaluation. --- ## Results ### Candidate A (DECISION + OPTION) - Semantic honesty: HIGH | Recoverability: FULL | Lifecycle: NATIVE | Question: CLEAN | Consequences: YES | Baseline: CLEAN - New primitives: 2 node kinds + 1 edge type + 1 optional field = **4** - Semantic overload: NONE - Verdict: Satisfies all criteria but adds the most primitives ### Candidate B (UNKNOWN + OPTION) ✅ WINNER - Semantic honesty: MEDIUM | Recoverability: FULL | Lifecycle: NATIVE | Question: CLEAN | Consequences: YES | Baseline: WORKABLE - New primitives: 1 node kind + 1 edge type + 1 optional field = **3** - Semantic overload: LOW (unknown carries both "uncertainty" and "decision context" — natural overlap, not contradictory) - Verdict: Smallest model satisfying all five decision-rule conditions ### Candidate C (OPTION PAIR ONLY) - Semantic honesty: LOW | Recoverability: POOR | Lifecycle: AWKWARD | Question: WORKABLE | Consequences: YES | Baseline: WORKABLE - New primitives: 1 node kind + 1 edge type = **2** - Semantic overload: LOW-MEDIUM - Verdict: Fails criteria 1 (decision context not recoverable) and 3 (no open/resolved lifecycle support). Minimalism too expensive semantically. --- ## Architectural Choice: B — UNKNOWN + OPTION ### What changes (exact boundary): ```javascript // schema.js additions: option: "option"; // SituationKind enum value contained_in: "contained_in"; // SituationRelationship enum value is_baseline: z.boolean().optional(); // optional on option nodes (not required for v1) ``` ### What does NOT change: - `unknown` node kind retains its existing semantics; it now also serves as decision context via the new `option` children pattern - All existing statuses, edge types, graph topology rules unchanged - Question compatibility uses existing `selectedQuestion` mechanism without extension - No migration of existing nodes required ### Decision lifecycle: NATIVE — open/resolved maps to unknown status transitions ### Additional questions answered: 1. Is `alternative_to` needed between options? **NO** — shared parent membership implies alternatives. 2. Is `is_baseline` flag required? **NOT NEEDED YET** — label/consequence patterns carry sufficient signal. --- Production code changed: NO Prompt changed during experiment: NO Validator changed during experiment: NO Vitest run: NO Ollama calls: 0 Dev server disturbed: NO Read-only design evaluation: YES ### Experiment 60A.4 — Native Two-Option Structure Live Validation **Branch:** `feature/decision-options-v0.25` **Date:** 2026-08-12 **Status:** Complete **Following:** 60A.3 which committed `option` node kind and `contained_in` edge to production. This tests whether the live model actually uses both new vocabulary items on the exact two-option case that previously collapsed. **Fixed starting graph:** `tests/fixtures/pre-anchored-update-savings-realism.json` **Fixed answer (verbatim 59B.4):** "There are really two options now. Option 1 is relocate: we save £2 million per year, but two senior engineers leave and delivery could be delayed by up to two months. Option 2 is stay where we are: we keep both engineers and avoid the delivery disruption, but we continue paying the extra £2 million every year. I am deciding which of those two options leaves us better off overall." **Execution:** qwen-claude:latest at http://192.168.1.111:11434. startCalls=0, updateCalls=1, totalCalls=1. **Result:** HTTP 200, stage = update_applied, no validation errors. ``` updatedNodes: [] resolvedUnknownNodeIds: [] addedNodes: [ { id: n_relocation_decision, label: "Which option leaves us better off overall?", kind: unknown, status: unknown }, { id: n_option_relocate, label: "Relocate to Manchester", kind: option, status: known }, { id: n_option_stay, label: "Stay in London (Status Quo)", kind: option, status: known } ] addedEdges: [ { fromNodeId: n_option_relocate, toNodeId: n_relocation_decision, relationship: contained_in }, { fromNodeId: n_option_stay, toNodeId: n_relocation_decision, relationship: contained_in } ] selectedQuestion: "What evidence would clarify which option leaves us better off overall?" ``` ### Assessment 1. **Decision context:** EXPLICIT SHARED DECISION UNKNOWN — `n_relocation_decision` (kind=unknown, status=unknown) is a single shared unresolved decision node. 2. **Relocate branch:** OPTION NODE — `n_option_relocate`, kind=`option`, all three consequences preserved in description. 3. **Stay-put branch:** OPTION NODE — `n_option_stay`, kind=`option`, all three consequences preserved in description. 4. **Membership:** BOTH CORRECT — both options link to the same `n_relocation_decision` via `contained_in`. 5. **Consequence attribution:** BOTH BRANCHES CLEAR — each consequence set lives within its distinct option node; branch ownership is structurally unambiguous. 6. **Selected question:** GOOD — continues the comparison, targets `n_relocation_decision`, no introduced assumptions. 7. **Savings-realism:** REMAINS OPEN (correct — answer did not address it). ### What the engine understood correctly: 1. Dual-option decomposition into two `option` nodes 2. Shared decision context via single unresolved unknown 3. Correct use of `contained_in` for option → decision membership 4. Consequence attribution per branch without collapse or cross-contamination 5. Decision-question alignment with user's stated intent ### What this establishes: The live model CAN create native two-option graph structure when the user explicitly presents two alternatives. Both options survive as first-class `option` nodes with structural membership to a shared decision context, and downstream graph-only reasoning can recover both branches and their distinct consequences. ### Classification: A — NATIVE TWO-OPTION STRUCTURE CONFIRMED Production code changed: NO Prompt changed during experiment: NO Validator changed during experiment: NO Harness changed during experiment: NO Vitest run: NO Ollama calls beyond harness count: 0 Dev server disturbed: NO ### Experiment 60A.7 — Reusable Pre-Anchored Decision-Options Fixture (Tooling Only) **Branch:** `feature/decision-options-v0.25` **Date:** 2026-08-13 **Type:** TEST TOOLING ONLY — no production reasoning code changes, no live API calls, no Ollama calls **Full record:** `docs/experiment-60a7.md` **Context route:** Follows experiment 60A.6 which established the `option` node kind and `contained_in` edge relationship for representing two competing relocation options in the situation graph. The committed JSON fixture (`tests/fixtures/pre-anchored-decision-options.json`) captures this persistent reasoning state. **Objective:** Add test-only support for loading the reusable decision-options fixture from its committed JSON file, enabling harness tests to verify pre-anchored update-only mode with non-default fixtures without inline data duplication. **Methodology:** - Load `tests/fixtures/pre-anchored-decision-options.json` directly via `fs.readFileSync` in the test harness - Add `runPreAnchoredSimulationWithFixture()` helper that mirrors the production pre-anchored path (generic anchor validation, no Start call, exactly one Update, all hardened capture) - Generalize script's anchor validation from savings-realism-specific to generic unresolved unknown check - Run focused vitest harness test only **Key findings:** - All 63 harness tests pass (including 17 new decision-options fixture mode tests) - Fixture loads correctly from committed JSON — no inline duplication needed - Pre-anchored validation works generically across fixture types (savings-realism and decision-options) - Normal Start→Update mode unchanged; regression-free **What this establishes:** The pre-anchored update-only harness path is now verified with multiple fixture types. Tests can pass any valid pre-anchored graph directly, confirming the production code's generic anchor validation handles diverse fixtures without hardcoding domain-specific assumptions. Classification: COMPLETE — TOOLING ONLY Production reasoning code changed: NO Test harness modified: YES (additions only) Fixture loaded from committed JSON in tests: YES Inline decision-options fixture duplicated in test file: NO Ollama calls: 0 Live API calls: 0 Vitest run: 1 focused command (63/63 pass) ### Experiment 60A.8 — Downstream Option Evidence Update on Committed Fixture **Branch:** `feature/decision-options-v0.25` **Date:** 2026-08-13 **Status:** Complete **Following:** 60A.7 which created the reusable pre-anchored decision-options fixture. This tested whether the engine can add new option-specific evidence to an existing option branch without rebuilding the decision. ## Objective When new information applies specifically to the Relocate option ("£400,000 lost margin from two-month delivery delay"), does the engine attach that information to the existing Relocate branch while preserving the existing Stay-put option and shared decision context? ## Fixed Starting Graph Fixture: `tests/fixtures/pre-anchored-decision-options.json` Pre-existing: opt_relocate (option/known), opt_stay_put (option/known), n_relocation_decision (unknown/unknown) ## Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434 ## Execution startCalls=0, updateCalls=1, totalCalls=1. One live call at qwen-claude:latest. No retries. ## Result **HTTP status:** 200 — first call succeeds, no validation rejection. ### Structure (5 nodes, 3 edges) | Node | Kind | Status | | --------------------- | ---------- | -------------------------------------------------------------- | | opt_relocate | option | **known** (PRESERVED) | | opt_stay_put | option | **known** (PRESERVED) | | n_relocation_decision | unknown | **unknown** (PRESERVED, unresolved) | | n_delay_cost | **metric** | known (NEW — first-class numeric node, value=400000, unit=GBP) | New edge: n_delay_cost → opt_relocate via `measures` relationship. Both original contained_in edges preserved intact. ### Classification: A — EXISTING OPTION GRAPH UPDATED CORRECTLY All three existing entity identities preserved without duplication; £400k consequence created as a first-class metric node correctly owned by Relocate (via measures edge + childIds); overall decision remains unresolved; selected question pursues genuinely decision-relevant missing consequence. This establishes that downstream option evidence attaches cleanly to existing options in a single update call without rebuilding the decision. **What this establishes:** The engine can add first-class structural evidence to an existing option branch while preserving all existing identities and keeping the decision context unresolved. Evidence ownership by correct option is achievable via both edge relationships and child references. **What it does NOT prove:** Stability across repeated runs; multiple concurrent consequences per option; cross-domain generalisation. ## Production code changed: NO Prompt changed: NO Validator changed: NO Harness changed: NO Vitest run: NO Ollama calls: 1 Direct API calls: 0 Dev server disturbed: NO ## Experiment 60B.1 — Decision Sufficiency Recognition (2026-08-13) Tested whether the engine resolves the existing "Which option leaves us better off overall?" decision when both options have quantified financial impacts and the user states no other material differences remain. Fixed answer provided: £600k one-off relocation disruption cost versus £2m/year recurring stay-put cost, with explicit closure condition ("no other material differences"). **Result:** Classification A — Decision Sufficiency Recognised. The engine preserved all three existing entity identities (opt_relocate, opt_stay_put, n_relocation_decision), created first-class metric nodes for both costs with correct option ownership and units (GBP vs GBP/year), recognised sufficient evidence to close the decision context, resolved n_relocation_decision in-place (status unknown → resolved) with direction favouring Relocate, created zero new uncertainties, and generated no follow-up question. One live call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. ## Experiment 60B.2 — Independent Decision Sufficiency Without Explicit Stopping Cue (2026-08-13) Tested whether the engine independently recognises decision sufficiency when both options have quantified material costs (£600k one-off relocation vs £2M/year stay-put) but the user does NOT say "there are no other material differences" or any equivalent stopping statement. Answer provided exact financial comparison with no explicit closure language. **Result:** Classification C — Generic Uncertainty Chasing. The engine preserved all three existing entity identities, set numeric newValue on each option (600000 on opt_relocate, 2000000 on opt_stay_put), performed an internal payback computation (~3.6 months), but treated the comparison as insufficient for decision resolution. Status changed unknown → supported (not resolved). No metric/evidence nodes created; no edges added; cost units not distinguished. Selected question "What outcome would demonstrate enough value to justify continuing?" is generic — does not identify a specific grounded missing factor. The engine demonstrated it can compute a meaningful financial comparison but does not use those computations as sufficiency triggers without an explicit user stopping cue. One live call at qwen-claude:latest on http://127.0.0.1:3000. No production code changed. ## Experiment 60B.3 — Decision Sufficiency Rule Diagnosis (read-only) Tested whether the Confidence Engine can independently recognise when quantified option evidence is sufficient for decision resolution, or whether it requires explicit user cues to close a decision context. Inspection of all six primary files and two secondary files confirmed: the engine has NO independent sufficiency rule. Resolution depends on explicit user language ("no other material differences") which acts as an implicit closing signal. The validator checks structural integrity only. The schema has no materiality field. The propagateResolvedChildEvidence function handles within-decomposition sufficiency (all children resolved → parent resolves) but performs no cross-option comparison. **Diagnosis:** Classification C — NO SUFFICIENCY RULE + CONTINUATION BIAS **Missing distinction:** MATERIALITY / DECISION-RELEVANCE RULE --- Experiment 60B.4 implemented the minimal correction: added a domain-general decision-sufficiency rule to the prompt (not schema, not validator). The rule instructs: uncertainty alone is not sufficient reason to continue; continuation requires a specific material factor that could change the preferred option; when no such factor remains, resolve the existing decision context rather than asking a generic question. Prompt-level addition only — three sentences in a new "Decision Sufficiency Rule" section in prompt-builder.js. Deterministic tests confirm all five positive and six negative criteria pass (86/86). No live regression performed. Status pending Rob's review. --- ## Experiment 60B.5 — Live Validation of Decision Materiality Rule (2026-08-13) Single-call live regression of experiment 60B.4's prompt-only decision materiality rule. Re-runs the exact 60B.2 case (quantified financial comparison without explicit stopping cue) with the new materiality rule active in the prompt. **Fixture:** `tests/fixtures/pre-anchored-decision-options.json` **Answer:** "We have now quantified the full financial impact of the relocation disruption, including replacing the two senior engineers and the delivery delay, at about £600,000 as a one-off cost. Staying put costs us an extra £2 million every year." **Host/model:** qwen-claude:latest at http://127.0.0.1:3000 (dev server). startCalls=0, updateCalls=1, totalCalls=1. No retries. ### Result: Classification A — MATERIALITY RULE FIX CONFIRMED The engine resolved the existing decision context independently. All three entity identities preserved. The generic continuation failure from 60B.2 is eliminated. **Key changes vs 60B.2:** - `n_relocation_decision` status: unknown → **resolved** (was supported in 60B.2) - `resolvedUnknownNodeIds`: ["n_relocation_decision"] (was empty in 60B.2) - No selected question generated — decision is complete (was generic continuation in 60B.2) - One first-class observation node created: `n_fin_quantification` capturing both £600k one-off and £2M/year figures as known facts with typed edges to each option - Decision direction expressed: newValue = "Relocate to Manchester" **One live call at qwen-claude:latest on http://127.0.0.1:3000. No production code changed.** ## Experiment 60B.6 — Materiality Rule Against Real Unresolved Factor (2026-08-13) Test whether the materiality rule keeps a decision open when one genuinely decision-changing uncertainty remains — the opposite boundary of 60B.5's over-closure case. **Fixture:** `tests/fixtures/pre-anchored-decision-options.json` **Answer (verbatim):** "We have now quantified the full financial impact of replacing the two senior engineers and the delivery delay at about £600,000 as a one-off relocation cost. Staying put costs us an extra £2 million every year. The remaining issue is our largest client: we do not yet know whether they would leave if we relocated, and losing them would cost us about £5 million per year." **One live call at qwen-claude:latest on http://127.0.0.1:3000.** ### Result: Classification B — MATERIAL FACTOR RECOGNISED BUT STRUCTURE PARTIAL The engine correctly keeps the decision open because of the client-retention uncertainty, creates a first-class unknown node for it, and clearly attributes it to the Relocate option via a `may_cause` edge. However, three structural weaknesses prevent an A: 1. **No observation node for known financial comparison.** Unlike 60B.5 (which created `n_fin_quantification`), 60B.6 did not extract the £600k and £2M/year figures into a new observation node. The quantified comparison survives only implicitly through the original option descriptions, not as structured evidence. 2. **The £5M/year downside is text-only.** The description mentions "~£5M/year" but it is embedded in prose rather than isolated as a structured numeric consequence. 3. **Selected question is generic despite known material factor.** The engine asks "What outcome would demonstrate enough value to justify continuing?" — the same generic phrasing from 60B.2 — when it has just created a specific client-retention unknown. It should have targeted that uncertainty directly (e.g., "Will the organisation's largest client leave if we relocate to Manchester?"). ### Structural details ``` updatedNodes: [] resolvedUnknownNodeIds: [] addedNodes: [n_client_retention — kind=unknown, status=unknown, label="Largest client retention uncertainty"] addedEdges: [n_client_retention → opt_relocate, relationship="may_cause"] selectedQuestion: "What outcome would demonstrate enough value to justify continuing?" (generic) ``` **What worked:** - Decision identity preserved (status remained unknown) - Both option identities preserved - Client-retention uncertainty created as a first-class unknown node - Client risk clearly attributed to Relocate via `may_cause` edge and `childIds` on the unknown node - No fabricated factors or over-closure **What was weak:** - Generic question despite having just identified a specific material factor - No observation node for the known financial comparison (unlike 60B.5) - £5M/year not isolated from description text Experiment 60B.7 diagnosed why selectedQuestion targets a generic parent decision instead of the specific material unknown just created (n_client_retention). Architecture tracing revealed: the model's selectedQuestion is purely advisory — both nodeId and text are deterministically overridden by selectActiveUnknownCandidate + formulateQuestion in apply-proposal.js. The deterministic scorer scores all unresolved unknowns by text keywords + structural position with zero recency or materiality bonus. Validation accepts any structurally valid unresolved node without checking whether it matches the factor that justified continuation. Classification: E (multiple factors — A prompt alignment gap + B selection priority gap + D validation gap). Minimum missing distinction: B — a Continuation-Reason → Question-Target rule connecting the materiality reason to follow-up question targeting. Implementation readiness: A — ready for bounded implementation via prompt-only addition of one bridging rule. Status pending Rob's review. --- Experiment 60B.8 compared three minimal mechanisms for preserving a material factor into final question selection (A: honour valid model-selected nodeId as preferred target, B: deterministic material-factor priority via scoring adjustment, C: explicit continuationTarget field). Analysis of actual code confirmed: (1) selectedQuestion.nodeId already means "candidate for next question" with full structural validation in place, (2) ignoring a valid model-selected node is semantically contradictory given the field's purpose, (3) existing validation (validateSelectedQuestion + isSelectableUnresolvedUnknown) covers all validity checks, and (4) existing fallback chain at lines 3418-3437 of apply-proposal.js handles the invalid-target case. Candidate A was chosen: honour valid model-selected nodeId as preferred target before deterministic scoring, with fallback to selectActiveImplementationCandidate when invalid. No new schema fields, no new validators, no new keyword logic. Smallest boundary: one Rule 172 clarification + one modification in buildSelectedQuestionResult. Implementation readiness: A — ready for bounded implementation. Status pending Rob's review. --- Experiment 60B.12 reran the exact 60B.6 live case to verify prerequisite-aware targeting from 60B.11. **Classification: H — BLOCKED.** The update was rejected at stage `result_validation` with error: "Active unknown violates reasoning pattern consistency: 'n_client_retention_uncertainty' is diagnosis but active pattern is decision." Three differences from 60B.6: (1) node id changed from `n_client_retention` to `n_client_retention_uncertainty`, (2) node kind changed from `unknown` to `diagnosis`, (3) rejection occurred before any proposal applied — neither materiality nor targeting could be assessed. The blocker is a schema-level pattern-consistency mismatch between the model's output and the active decision pattern, not a question-targeting problem. Fix path: either constrain the model prompt to produce kind=unknown nodes or relax the active pattern validator to accept diagnosis nodes for decision-mode continuations. --- Experiment 60B.13 performed read-only architectural diagnosis of the 60B.12 kind mismatch. **Classification: A + D — Prompt kind ambiguity + missing normalisation path.** Analysis confirmed: (1) `kind=diagnosis` is NOT a valid SituationKind — it exists only as a reasoning pattern and as the default fallback in `selectReasoningPattern`; (2) the validator at line 3927/3998 of apply-proposal.js correctly rejects diagnosis under active decision pattern — semantically, diagnosis and decision are distinct reasoning types; (3) the model likely produced kind=unknown with diagnostic-inferred text analysis, not kind=diagnosis directly (which would fail zod immediately); (4) `hasDecisionContext`'s keyword list (`whether to|build|launch|continue|proceed|invest|commercially justified|viability`) does not include "relocate"/"relocation", so material factors about relocation decisions get inferred as diagnosis; (5) the prompt's kind rules cover decision questions and candidate options but have no rule for material unresolved factors within a decision. Minimum corrective boundary: B — one clarifying rule in Prompt Proposal Rules section stating that new material factors affecting a decision outcome use kind=unknown, with reasoning pattern determined by graph context. Implementation readiness: A. --- Experiment 60B.14 performed read-only design analysis on whether a newly-created unresolved factor inside an active decision should inherit the decision's reasoning pattern rather than being classified mainly from its wording. **Classification: D — COMPATIBILITY FALLBACK.** The current architecture separates _active pattern determination_ (which DOES use parent-chain traversal via `determineActiveReasoningPattern` and correctly yields "decision" for the 60B.12 node) from _node-intrinsic pattern inference_ (which does NOT use that context — it runs standalone text analysis on the node's label/description only). This separation is the root cause: the active pattern correctly walks up to find "decision" in the decision unknown, but the compatibility check re-runs standalone inference on the new node and gets "diagnosis" from its diagnostic-style wording ("will our largest client leave"). The smallest correct fix preserves intrinsic text analysis as primary signal but adds a normalization fallback: when inferred node pattern is incompatible with active pattern AND the node's graph position (parentId, edges) places it structurally within that active context, reinterpret using the active pattern rather than rejecting. This requires no new schema, no new keywords, and preserves genuine pattern transitions (the intrinsic inference still returns diagnosis; only the compatibility decision changes). --- Experiment 60B.15 addressed the unresolved boundary question from 60B.14: **what exact structural relationship is strong enough to count as "embedded in the active context" without becoming so permissive that genuine pattern transitions are hidden?** Analysis of all available signals confirmed: (1) SituationRelationship enum defines 11 edge types with distinct semantic force, (2) node-level parentId/childIds provide unambiguous hierarchy, (3) parent-chain-only is too narrow (misses edge-connected nodes), (4) any-graph-path is too permissive, (5) Candidate E — PARENT OR DECISION-OPTION PATH was selected as the winning predicate. The rule: a new unknown is embedded in active context Y if EITHER its parentId/ancestor chain reaches a node with pattern Y, OR it attaches via may_cause/causes/affects edge to an option contained_in a decision-unknown. This uses two independent routes with clear boundaries — neither alone sufficient, together covering all common embedding patterns. The genuine-transition test at apply-proposal.test.js:2486 (comparison child under decision parent) is preserved because comparison IS already in the ALLOWED list for decision context — structural embedding only affects the normalization step when intrinsic inference produces an incompatible result, which indicates likely wording drift. No new schema required. Implementation readiness: A — ready for bounded implementation. --- Experiment 60B.20 was the live verification of bounded structural context admission from 60B.19 (commit d871a8c). **Classification: A — FULL LIVE CHAIN CONFIRMED.** The exact client-retention case that failed at result_validation in 60B.12 now passes through all stages end-to-end. The model produced `n_client_retention_risk` (kind=unknown, status=unknown) with a `may_cause` edge from opt_relocate. Proposal applied successfully. Decision remains unresolved. Final selectedQuestion targets n_client_retention_risk specifically with text "will our largest client leave if we relocate?". Client risk clearly attributed to Relocate. No fabrication, no over-closure, no unrelated uncertainty invented. --- --- Experiment 60B.21 tested whether the full reasoning chain from 60B.20 generalises to a materially different decision domain (product launch timing). **Classification: B — MATERIALITY GENERALISES, TARGETING DOES NOT.** The model correctly identified the customer-signing factor as a first-class unknown (kind=unknown, status=unknown), kept the decision open for this specific material factor (£700k of £1.2M), and did not invent unrelated uncertainty. The node `n_customer_signing_status` was selected as the target. However, the final question text ("What would clarify the relevant customer, user, or value recipient in this situation?") is generic rather than specific to customer signing — unlike 60B.20 which produced "will our largest client leave if we relocate?" Option ownership shifted from may_cause (opt→unknown) to depends_on (decision→unknown with conditional attribution in description), preserving correct semantic ownership but with different structural encoding. One live call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. Status pending Rob's review. Experiment 60B.22 was a read-only diagnosis of why the correct material target (n_customer_signing_status) produced a generic final question ("What would clarify the relevant customer, user, or value recipient in this situation?") instead of a direct proposition question. **Root cause: C — FAMILY CLASSIFICATION TOO BROAD.** The regex at line 1162 of question-formulator.js (`/\b(audience|customer|user|buyer|stakeholder|recipient|who experiences)\b/`) matched on "customer" in the node's combined label+description, triggering early return to `decision_audience` family before any proposition-extraction logic could run. 60B.20 succeeded because its label contained "client" (not "customer"), allowing fallthrough to `decision_evidence_clarification` which properly detects interrogative labels. Minimum corrective boundary: E — NARROW CUSTOMER/VALUE FAMILY CLASSIFICATION. The trigger regex should require explicit audience-identity phrasing rather than any occurrence of "customer". Status pending Rob's review. --- Experiment 60B.24 ran the exact product-launch live regression (same fixture as 60B.21) on the post-60B.23 branch to verify the proposition-specificity fix end-to-end. **Classification: A — LIVE QUESTION-SPECIFICITY FIX CONFIRMED.** The model produced `uncertain_enterprise_customer_signing` (kind=unknown, status=unknown) with depends_on edge from n_product_launch_decision and affects edge to opt_launch_this_year, preserving £700k significance in the node description. The final selected question ("whether one prospective enterprise customer will sign if we launch this year; they account for ~£700k of the £1.2M expected annual revenue...") is proposition-specific — not generic audience wording. Core reasoning chain preserved, decision remains unresolved, no unrelated uncertainty invented. One live Ollama call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. --- Experiment 60B.25 was a read-only diagnosis of why the proposition-specific question produced "whether one prospective enterprise customer will sign if we launch this year; they account for ~£700k..." (proposition-plus-rationale) instead of a clean conversational question. **Classification: C — BOTH A + B.** Two independent defects in `extractMeaning` (lib/graph/question-formulator.js): (A) **Rationale extraction too broad** at line 108 — when label contains "status" and description starts with "Whether", the function returns `sentenceCase(strippedDescription)` which includes the full description text after the semicolon, capturing explanatory rationale (~£700k context clause). No internal delimiter logic exists. (B) **No whether→direct-question conversion** — `isInterrogativeMeaning` at line 189 treats "whether" clauses as already interrogative and passes them through unchanged via `wrapInterrogativeForTemplate`, so the output is proposition+rationale with "?" appended, not a converted question. The first meaningful divergence from 60B.20 (which produced clean "will our largest client leave if we relocate?") is at extractMeaning line 108: 60B.20's interrogative label bypasses the status+whether branch entirely, while 60B.24's nominal "signing status" label triggers it, pulling in full description including rationale. **Smallest corrective boundary:** A+C — strip rationale on extraction (split on first semicolon), then let existing evidence framing produce "What evidence would clarify whether X?" or keep direct interrogative form via decision path line 1272-1273. No provider rewrite, no target selection change, no schema change needed. Implementation readiness: A. Full trace documented in docs/experiment-60b25.md. Experiment 60B.27 ran the exact product-launch live regression on the post-60B.26 branch to verify rationale-stripping + evidence-framing end-to-end. **Classification: A — LIVE CLEAN-QUESTION FIX CONFIRMED.** The model produced `unc_customer_signing_likelihood` (kind=unknown, status=unknown) with may_cause edge to opt_launch_this_year. Core reasoning chain preserved: decision unresolved, customer-signing survives as first-class unknown, no unrelated uncertainty invented. Final question ("What evidence would clarify prospective enterprise customer signing likelihood?") uses correct evidence framing and contains ZERO rationale contamination — no £700k, £1.2M, expected revenue, or financial impact leaked into user-facing text. Source graph description preserves full rationale intact. One live Ollama call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. Minor gap identified: extractMeaning description-start check (`/^whether\s+/i`) doesn't match "Unknown whether..." descriptions, producing nominalized output instead of a full whether-clause — functionally equivalent but less natural English. Status pending Rob's review. --- Experiment 60B.31 was a live regression of 60B.29 on the post-60B.30 branch to verify that `Uncertainty over whether...` now preserves the full proposition end-to-end live. **Classification: B — FULL PROPOSITION PRESERVED BUT DIFFERENT PREFIX EXERCISED.** The model produced `n_enterprise_customer_signing` (kind=unknown, status=unknown) with a depends_on edge. Decision remains unresolved. However, the node description started with `Whether...` not `Uncertainty over whether...`, meaning 60B.30's targeted normalization was NOT exercised live. The final question ("What outcome would demonstrate enough value to justify launching?") is generic justification interrogative — same shape as 60B.29, full proposition lost from the question text but preserved in the source node description (source graph meaning intact). No rationale contamination in the final question. One live Ollama call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. The `Uncertainty over whether...` normalization is deterministic-proven (39/39 tests) but never exercised against the live model producing this exact prefix — follow-up needed to force that prefix live. --- Experiment 60B.32 was a code-path diagnosis of why the full runtime produces generic decision-justification questions for nodes whose descriptions start with bare "Whether..." — despite deterministic tests producing correct evidence-framed propositions. **Classification: D — ACTIVE CONTEXT DRIFT.** Three compounding factors in question-formulator.js cause the divergence: (1) extractMeaning's proposition detection requires label keywords (likelihood/status/probability/chance/risk/uncertainty) that "Enterprise customer signing decision" lacks, so the meaning strips "Whether" and isWhetherPropositionMeaning returns false; (2) hasDecisionValueLanguage becomes true because the description contains "value" within a parent-driven decision context; (3) extractActionPhrase scans ALL related nodes including the parent product-launch node, which supplies an action phrase ("launch") embedded in the generic template. The focused test avoids all three because its graph has no parent nodes and its label "Supplier renewal likelihood" contains the keyword "likelihood". Minimum corrective boundary: make extractMeaning recognize bare "Whether..." descriptions regardless of label keywords, so isWhetherPropositionMeaning returns true and blocks decision_threshold override. No production code changed. 0 Ollama calls. Pure code inspection. --- Experiment 60B.34 was a live regression on `feature/bare-whether-proposition-v0.33` to verify that bare `Whether...` description preservation survives the full update route end-to-end. **Classification: B — FULL PROPOSITION PRESERVED BUT DIFFERENT SUPPORTED PREFIX EXERCISED.** One bounded update call produced: new unknown node `n_prospective_customer_signing` with description starting `"Unknown whether..."` (not bare `"Whether..."`). Decision remains unresolved (`n_product_launch_decision` still unknown). Final question: `"What evidence would clarify whether one prospective enterprise customer will sign if we launch this year?"` — full proposition preserved, evidence-framed, zero rationale contamination. The exact 60B.33 targeted fix (bare Whether...) was not exercised live; the model produced `Unknown whether...` instead, a different but functionally equivalent supported prefix path from the same code base. Equivalent outcome to 60B.31's deterministic regression. One live Ollama call at qwen-claude:latest on http://192.168.1.111:11434. No production code changed. --- Experiment 60B.35 validated that a bare `Whether...` description survives the **full** applyValidatedProposal runtime path end-to-end. **Classification: A — FULL LIFECYCLE CONFIRMED.** The model-produced node `n_enterprise_customer_signing` (kind=unknown, status=unknown) with a depends_on edge reached the full production apply-proposal pipeline without proposition degradation. Final selected question was `"What evidence would clarify whether the prospective enterprise customer will commit this year?"` — proposition-specific evidence framing confirmed at the output boundary. No rationale contamination, decision identity preserved, no unrelated uncertainty invented. This closes the verification gap from 60B.34: bare Whether... preservation now proven end-to-end in both partial and full runtime paths. One live call. No production code changed. Deterministic test added (removed in cleanup commit).