From 90472de766ed1859569e753f6fdeea9f6a10a3ad Mon Sep 17 00:00:00 2001 From: robbond Date: Fri, 7 Aug 2026 14:07:28 +0100 Subject: [PATCH] experiment: audit provenance in update prompt --- docs/current-handoff.md | 9 ++- docs/design-evolution-log.md | 148 ++++++++++++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 5 deletions(-) diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 91a6c18..c246f4d 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -18,6 +18,7 @@ - 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. - `docs/task-context-packs.md` chooses the minimum context documents for each work type. ## 3. Why Work Is Paused @@ -72,6 +73,8 @@ Experiment 52C separated free-language semantic understanding from enum normalis 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 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 gap is a schema deficiency, not a prompt-design problem. + ## 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; @@ -124,8 +127,8 @@ Answer before continuing: --- -*Created by Experiment 34. Updated by Experiments 38–53, 54A. Branch: `feature/user-workspace-ux-v0.7`.* +*Created by Experiment 34. Updated by Experiments 38–53, 54A–54D. Branch: `feature/user-workspace-ux-v0.7`.* -### Return-to-Work Note (Experiment 53 → 54A → 54B → 54C) +### Return-to-Work Note (Experiment 53 → 54A → 54B → 54C → 54D) -Experiment 53 proved semantic separation was possible; 54A confirmed the SituationGraph cannot recover provenance from graph state alone; 54B showed supplied-versus-inferred distinction exists upstream in evidenceRecordSchema but is lost at buildInitialGraph because the node schema has no provenance field and evidenceIds carries only bare ID references. 54C inspected the normal answer-update boundary and found that while the raw user answer and validated model proposal are explicitly separate parameters at applyValidatedProposal's call site (orchestrator.js:683), they cannot be deterministically mapped to individual graph nodes within the proposal — the LLM independently generates additions from answer context with no provenance mapping. First function/file to inspect when resuming: `lib/graph/orchestrator.js` line 633 (buildGraphUpdatePrompt) to assess whether prompt structure carries any origin markers. +Experiment 54C clarified that whole-input origin remains deterministically knowable before application (answer = user supplied, proposal = model produced) but per-node provenance is lost because the validated proposal schema carries no per-node origin markers. Experiment 54D audited the production update prompt in lib/graph/prompt-builder.js and confirmed the user answer is explicitly identifiable to the model via a distinct named section (## User Answer). However, the requested proposal output schema has no provenance fields on nodes or edges, so supplied-versus-inferred origin cannot be preserved in the output. No production code changed. Branch: feature/user-workspace-ux-v0.7. First file to inspect when resuming: lib/graph/schema.js (graphUpdateSchema and situationNodeSchema) to evaluate minimal schema additions for per-node provenance fields. diff --git a/docs/design-evolution-log.md b/docs/design-evolution-log.md index 0e7224a..c4dd1dc 100644 --- a/docs/design-evolution-log.md +++ b/docs/design-evolution-log.md @@ -5285,9 +5285,9 @@ b) At `applyGraphUpdate` calls (lines 2882, 2912): only `graphSnapshot` and `pro ### Experiment Conclusion -**Update origin is partially knowable but already merged before application.** +**Input origin remains explicit before application, but per-node supplied-versus-inferred provenance is not represented in the validated proposal and is lost before durable graph mutation.** -The raw user answer and validated model proposal are explicitly separate at the `applyValidatedProposal` function call boundary (orchestrator.js:683-688). This means deterministic code CAN identify "which argument is the user answer" and "which argument is the model proposal." However, this distinction does not translate to per-node provenance because: +The raw user answer and validated model proposal are explicitly separate at the `applyValidatedProposal` function call boundary (orchestrator.js:683-688). Whole-input origin is explicit: `answer` = user supplied; `proposal` = model produced. However, per-node origin inside the proposal is not deterministically recoverable from the validated proposal alone. This distinction does not translate to per-node provenance because: 1. The graphUpdateSchema / situationNodeSchema has no provenance field on nodes or edges. 2. The raw user answer text has no structural mapping to proposal node boundaries — the LLM consumes the answer as context and generates additions independently, so there is no way to deterministically say "this node contains user information" versus "this node contains model inference." @@ -5319,3 +5319,147 @@ Provenance loss occurs at the intersection of proposal schema (no provenance fie No test run required; Experiment 54C is a source-trace audit. +## Experiment 54D — Does the Update Prompt Already Preserve User-vs-Model Origin? (2026-08-07) + +### Objective + +Inspect the production graph-update prompt to determine whether it marks which content is the user's answer versus model-generated interpretation strongly enough that provenance could, in principle, be preserved downstream. This is a source-inspection experiment only. Do not implement provenance. + +Two distinct questions: +- **Prompt-level source identity:** Can the model tell "this text is the user's answer"? +- **Proposal-level provenance:** Can downstream code tell "this particular proposed node directly represents supplied content rather than model inference"? + +These may have different answers. The first may be explicit while the second is absent. + +### Hypothesis + +The existing update prompt may already contain clearly separated sections such as: previous graph/context; current question; user answer; instructions for graph changes. If that structure is explicit, the upstream information needed to distinguish user-supplied input from model-generated additions may already exist at prompt time. If the prompt blends everything into undifferentiated text, provenance is weaker even before proposal parsing. + +### Files Actually Inspected + +- `lib/graph/orchestrator.js` — lines 617-638 (caller passing answer to buildPrompt); line 20 (import statement) +- `lib/graph/prompt-builder.js` — full file (buildGraphUpdatePrompt function and its helpers: formatEnumValues, formatGraph, formatExampleAnswerBlock) + +Production files inspected: `lib/graph/orchestrator.js`, `lib/graph/prompt-builder.js`. + +### Prompt Builder Function Inspected + +Function: `buildGraphUpdatePrompt` in `lib/graph/prompt-builder.js`, exported as `buildGraphUpdatePrompt` (line 26), aliased as `buildUpdatePrompt` (line 134). + +### Arguments Passed Into Prompt Builder + +- `situationGraph` — full current SituationGraph object +- `previousQuestion` — string (the previously selected question) +- `answer` — string (raw user answer, min 1 char, max 5000 chars per updateCaseRequestSchema) +- `promptVersion` — string, defaults to "v0.4" + +The caller in orchestrator.js:633 passes these four arguments directly from function parameters and a config value. No provenance metadata is constructed or passed at the call site. + +### User Answer Source Identity in Prompt + +Status: **explicit** + +Section `## User Answer` (line 48-49 of prompt-builder.js) contains the raw user answer as its entire content, separated by a Markdown header from everything above and below. The section header unambiguously identifies the block as user-supplied text. No other section contains this exact string. + +### Previous Question Separation in Prompt + +Status: **explicit** + +Section `## Previous Selected Question` (line 45-46) contains only the previous question string, clearly separated by a Markdown header from both the graph above and the answer below. + +### Graph/Context Separation in Prompt + +Status: **explicit** + +Section `## Current Situation Graph` (line 42-43) contains the full situation graph as formatted JSON, clearly separated by a Markdown header from all other content. + +### Instruction Versus User-Content Separation + +Status: **explicit** + +All instruction blocks use Markdown headers (`## Proposal Rules`, `## Allowed Node Kinds`, `## Additional Guidance`, etc.). These headers create visual and structural boundaries between user-provided sections (graph, question, answer) and system instructions. The prompt does not interleave instructions within user-content blocks. + +### Does Prompt Explicitly Identify User-Supplied Content + +Status: **explicit** + +Yes. The `## User Answer` header unambiguously marks which text block is the user's contribution. Additionally, Proposal Rule 9 states "Every new unknown must be directly traceable to the user's answer," and Rule 13a references "the relevant answer-derived decision or context node" — both rules reinforce that the answer section represents the authoritative user-supplied source. + +### Does Prompt Explicitly Distinguish Supplied Meaning from Model Inference + +Status: **implicit** + +The prompt does not contain an explicit instruction telling the model to label or separate supplied meaning from inference in its output. However, implicit cues exist: Rule 9 requires traceability ("directly traceable to the user's answer"), Rule 9a requires a why-it-matters clause for new unknowns (implying the model must reason about what it derives versus what is given), and Rule 13a references "answer-derived" nodes. These create an expectation that the model should distinguish derived from supplied content, but there is no structural output mechanism to preserve that distinction in the JSON proposal. + +### Does Requested Proposal Output Contain Provenance + +Status: **absent** + +The `graphUpdateSchema` (defined in `lib/graph/schema.js`, lines 155-163) has no provenance or source fields on any of its node or edge schemas. The `addedNodes` schema uses `situationNodeSchema` which contains only structural fields (id, label, description, kind, status, confidence, value, unit, evidenceIds, dependsOn, affects, parentId, childIds). No field exists to tag content as "user-supplied," "model-inferred," or any equivalent origin marker. + +### Prompt-Level Source Identity Status + +**explicit** — The model can clearly identify which input text came from the user (the `## User Answer` section) and which sections contain context/instructions (the graph JSON, previous question, rules, guidance). + +### Proposal-Level Provenance Status + +**absent** — The proposed output schema has no provenance fields. Even if the model understands which inputs were user-supplied, it has no mechanism to annotate its output nodes/edges with origin information. + +### Trace from User Answer to Validated Proposal + +| Stage | Source Identity | Supplied-vs-Inferred Meaning Explicit? | +|-------|----------------|----------------------------------------| +| 1. `answer` parameter in orchestrator.js:636 | explicit (parameter name) | N/A — raw string | +| 2. `## User Answer` section in prompt (prompt-builder.js:49) | explicit (named Markdown section) | implicit — the answer is given; no instruction distinguishes parts of it as supplied vs inferred | +| 3. LLM processes prompt and generates proposal | explicit (model can see which text is user answer) | implicit — rules require traceability but do not provide output mechanism for provenance | +| 4. `parsedProposal.proposal` after parseGraphUpdateProposal | absent (no source metadata on nodes/edges) | absent — graphUpdateSchema has no provenance fields | + +### First Point Where Per-Node Provenance Becomes Unavailable + +The proposed output schema (`graphUpdateSchema` in `lib/graph/schema.js`) defines the JSON contract returned by the LLM. Since none of its node or edge schemas include any origin/provenance field, per-node provenance is unavailable at the **output definition** stage — i.e., the prompt's own requested format cannot carry provenance even if the model understands it internally. This is upstream of parsing and validation; even before `parseGraphUpdateProposal` runs, the schema itself forbids provenance encoding. + +### Could the Model Know Which Input Came from the User + +**Yes.** The `## User Answer` section makes the user's contribution unmistakably identifiable. Rules 9 and 13a further reinforce the distinction between answer-derived content and model-generated additions. + +### Could Downstream Deterministic Code Know Which Proposed Node Came from Supplied Meaning + +**No.** The `graphUpdateSchema` has no provenance field on addedNodes, updatedNodes, or addedEdges. The validated proposal is a plain JSON object with no origin metadata. There is no deterministic mechanism to recover per-node provenance from the proposal alone. + +### Experiment Conclusion + +**Prompt clearly preserves user-source identity but proposal schema loses per-node provenance.** + +The production update prompt (prompt-builder.js) already separates the user answer into a distinct named section (`## User Answer`) with clear visual and structural boundaries from graph context, instructions, and constraints. The model can unambiguously identify which text is user-supplied. Rules 9 and 13a reinforce traceability expectations. + +However, the requested output schema (graphUpdateSchema in lib/graph/schema.js) has no provenance fields on nodes or edges. Even if the model internally distinguishes derived from supplied content, the JSON output contract cannot encode that distinction. Provenance is lost at the output-definition stage — before any parsing or validation occurs. + +This means: +- Prompt-level source identity: explicit +- Proposal-level provenance: absent (structural limitation of schema) +- The gap is not a prompt-design problem; it is a schema-deficiency problem + +### Limitations + +- Source-inspection audit only; no live model call executed +- Inspected the production prompt-builder and its immediate caller only +- Did not inspect whether evidenceType in reconstruction can carry origin information for the answer field itself +- Did not evaluate whether a schema change would be sufficient or whether additional upstream markers are needed +- Conclusions apply to the current prompt version (v0.4); earlier versions may differ + +### Status + +**Pending Rob's review.** Source-inspection complete. No production code changed. Working tree clean before commit. + +### Production Unchanged + +- `lib/graph/orchestrator.js`: 0 lines changed +- `lib/graph/prompt-builder.js`: 0 lines changed +- `lib/graph/schema.js`: 0 lines changed +- No production files modified +- Working tree clean before commit + +### Tests / Validation Run + +No test run required; Experiment 54D is a prompt-source audit. +