Compare commits

..
Author SHA1 Message Date
robbond 06f3f501d2 docs(confidence-engine): record restored workspace findings 2026-08-28 12:03:13 +01:00
robbond 10cbcbdd05 fix(confidence-engine): preserve investigation activity across turns
ThreadContributionsBadge, PriorContributionsSummary, and
SecondaryPreviousLearning all filtered contributions via
c.targetNodeId === nodeId. Multi-turn follow-up Contributions carry a
different immediate targetNodeId while the canonical origin remains on
Findings (originatingTargetNodeId).

Repaired: all contribution filters now match on EITHER
c.targetNodeId === nodeId || c.originatingTargetNodeId === nodeId.
handleDeconstructSubmit carries originatingTargetNodeId from
focusedPresentationItemId as provenance for cold-return recovery.
2026-08-28 11:56:31 +01:00
robbond b9a54589f0 docs(confidence-engine): record Phase 6 UNCLEAR + INVESTIGATING cue results
- Document live Playwright verification of amber INVESTIGATING indicator
  on Open Questions cards with matching contribution targetNodeId
- Document 11 new deterministic tests in focused-investigation-history describe block
- Confirm UNCLEAR and INVESTIGATING coexist independently (epistemic vs activity)
- Note that cue only renders inside OpenQuestionsPanel, not initial reflection surface
2026-08-28 11:26:57 +01:00
robbond 556acfb156 feat(confidence-engine): render INVESTIGATING cue on Open Question cards with focused history
- ThreadContributionsBadge (rendered per-node on OpenQuestionsPanel
  cards and Done-for-now cards) now shows an amber INVESTIGATING
  indicator when the node has matching contributions via targetNodeId
  identity match.
- UNCLEAR and INVESTIGATING cues coexist independently on the same
  card — UNCLEAR is epistemic state, INVESTIGATING is activity cue.
- Deterministic test suite added: focused-investigation-history (11
  tests) covering identity matching, zero-contrib edge cases,
  multiple-contrib coalescing, done-for-now retention, and uncoupling
  from UNCLEAR state.
- All 57 tests pass.
2026-08-28 11:25:40 +01:00
robbond f1bd91faf8 feat(confidence-engine): promote focused learning on done 2026-08-28 10:48:45 +01:00
robbond e221bd3bf8 feat(confidence-engine): isolate finding-informed understanding 2026-08-28 08:16:37 +01:00
robbond c45b703b3a docs(confidence-engine): record v0.48 storage closure and next boundary handoff
Record: v0.48 persistence objective complete; Finding eligibility resolved;
next boundary is isolated Finding-informed Current Understanding (feature/
finding-informed-understanding-v0.49); broader Finding-system questions
intentionally deferred. No production or test changes.
2026-08-28 07:59:50 +01:00
robbond b215846478 docs(confidence-engine): reconcile v0.48 persistence evidence 2026-08-28 07:06:10 +01:00
robbond 3e9123fe0e chore: scope experiment artifact ignores 2026-08-27 19:08:03 +01:00
robbond 3c5257cbd7 docs(confidence-engine): update v0.48 storage handoff 2026-08-27 19:05:30 +01:00
robbond d55f179d37 refactor(confidence-engine): remove legacy workspace persistence 2026-08-27 16:52:55 +01:00
robbond 166ee91698 feat(confidence-engine): persist canonical investigation state 2026-08-27 16:21:41 +01:00
robbond bc35e05253 refactor(confidence-engine): migrate scenario persistence to storage provider 2026-08-27 15:44:58 +01:00
robbond ba956eeb3a refactor(confidence-engine): add investigation storage provider 2026-08-27 15:24:38 +01:00
robbond 22e1d7484b feat(confidence-engine): support finding corrections 2026-08-27 13:43:23 +01:00
robbond 5c926154cd feat(confidence-engine): support not-relevant findings 2026-08-27 12:08:11 +01:00
robbond bf6c4241a5 chore: ignore local playwright mcp artifacts 2026-08-27 10:42:31 +01:00
robbond 0f4e49fc6c feat(confidence-engine): render focused findings from canonical state 2026-08-27 10:38:50 +01:00
robbond 7858650334 feat(confidence-engine): expose correlated findings to focused presentation 2026-08-27 09:23:51 +01:00
robbond ca7d8e5384 feat(confidence-engine): correlate focused result with contribution 2026-08-27 08:37:23 +01:00
robbond 0659599795 feat(confidence-engine): wire focused contribution finding derivation 2026-08-27 07:45:15 +01:00
robbond dc558e9c37 docs(confidence-engine): verify findings derivation gap in production chain
Verified: deriveFindingsFromContributions() exists with passing tests but
is never called in production. The contributions → findings seam is un-wired:

1. handleDeconstructSubmit() sends contribution to ScenarioForm
2. appendFocusedContribution() stores it in focusedContributions[]
3. findings state stays [] — no derivation ever runs
4. empty findings sent to /api/cases/update (which only echoes them back)
5. nothing renders from the findings surface

Fix: call deriveFindingsFromContributions after contribution is appended.
2026-08-27 07:40:50 +01:00
robbond 061ea364b3 feat(confidence-engine): derive findings from focused contributions 2026-08-27 06:44:07 +01:00
robbond 6a5cb43a30 docs(confidence-engine): checkpoint focused investigation workspace 2026-08-26 19:26:52 +01:00
robbond abeb3fcb03 feat(confidence-engine): add focused investigation overlay workspace 2026-08-26 19:22:54 +01:00
robbond 990b51aecf docs(confidence-engine): define progressive investigation workspace model 2026-08-26 17:26:04 +01:00
robbond 48b7185176 docs(confidence-engine): record current understanding isolation blocker 2026-08-26 16:30:32 +01:00
robbond 3235c35cf0 feat(confidence-engine): add focused finding handoff plumbing 2026-08-26 16:07:21 +01:00
robbond d3015f63d8 docs(confidence-engine): define minimum finding handoff slice 2026-08-26 15:07:57 +01:00
robbond 854160726e docs(confidence-engine): define focused finding handoff contract 2026-08-26 14:50:15 +01:00
robbond 10aa18d367 docs(confidence-engine): define finding graph reasoning contract 2026-08-26 14:28:33 +01:00
robbond ac5fbe7896 fix(confidence-engine): preserve focused learning across turns 2026-08-26 14:07:16 +01:00
robbond b26ea7d0ba docs(confidence-engine): establish contributions and findings distinction 2026-08-26 14:07:05 +01:00
robbond cb707c0192 test(confidence-engine): capture tentative mapping regression 2026-08-26 14:06:51 +01:00
robbond 787c8114ad docs(confidence-engine): record focused progression walkthrough findings 2026-08-26 12:40:37 +01:00
robbond fdb173d0e9 feat(confidence-engine): anchor focused frontier to investigation relevance 2026-08-26 12:16:41 +01:00
robbond cfd463d8b3 test(confidence-engine): capture structural frontier priority regression 2026-08-26 12:10:07 +01:00
robbond fd02be0f29 fix(confidence-engine): render focused investigation in current presentation 2026-08-26 11:52:18 +01:00
robbond 6288ef1031 fix(confidence-engine): keep focused investigation in current presentation 2026-08-26 11:30:59 +01:00
robbond 81dda77392 fix(confidence-engine): reopen completed focused investigation 2026-08-26 10:20:45 +01:00
robbond 42a7e82d88 feat(confidence-engine): tighten focused relationship attribution 2026-08-26 07:56:27 +01:00
robbond 3ca37b0918 test(confidence-engine): checkpoint focused deconstruction regression cases 2026-08-26 07:34:22 +01:00
robbond 112739b8e5 feat(confidence-engine): checkpoint focused deconstruction reasoning 2026-08-25 15:10:09 +01:00
robbond 9d670822a3 feat(confidence-engine): preserve focused deconstruction semantic fidelity 2026-08-24 10:11:06 +01:00
robbond 2c108df5a9 checkpoint: preserve latest live run graph output json 2026-08-23 19:56:07 +01:00
robbond c134b5cb04 feat(confidence-engine): stabilize investigation workspace with semantic decomposition and deterministic presentation anchors 2026-08-23 16:59:55 +01:00
robbond 01c57788ee feat(confidence-engine): stabilize user-directed investigation flow
Intentional changes in this checkpoint:
- Deconstruct route: use body.targetNodeId (client identity) over raw.model-invented ID
- ThreadContributionsBadge: compact per-thread contribution indicator with expandable history
- Reopen continuation: resume from accumulated contributions instead of reformulating
- showEvidenceLimit gate: hide evidence-limit card during active investigation paths
- Evidence-limit visibility correction in rendering pipeline
- Section ordering: assumptions and connections after 'Still unclear' in focused result
- Prompt v0.3: preserve user-stated alternatives as separate unknowns; no count inflation
- 3 durable regression tests (target identity, contribution persistence, reopen state)
- evidence-limit card visibility gate test suite

Temporary residue removed:
- test-analysis.mjs (scratch diagnostic)
- 5 diagnostic console.log blocks from reasoning-workspace.jsx
2026-08-23 12:05:51 +01:00
robbond 96ad0e7915 refine(ui): restore visual hierarchy and Situation context in initial workspace
- Enhance Current Understanding prominence with subtle teal/teal border
  gradient, stronger heading, larger body text, more internal spacing
- Restore Situation panel as right-hand column in initial reflection view;
  uses OriginalSituation when graph exists, scenario text fallback otherwise
- Stacks layout on narrow screens via grid-cols-1/gap-6/lg:grid-cols-3
- Apply teal styling to normal-state CurrentUnderstandingCard and
  PlainLanguageCard (was flat gray border with bg-transparent)
- Surface assumption nodes alongside unknowns in Open Questions; add
  Unclear / Plausible interpretation tags
- Wire up follow-up question buttons in deconstructed results
2026-08-22 19:01:45 +01:00
robbond 517d780e2c checkpoint: preserve semantic decomposition investigation state 2026-08-22 08:21:29 +01:00
robbond 68be2344c6 fix(rto): persist focused contributions immediately after deconstruct success
The successful focused deconstruct calls onFocusedContribution which
updates parent state, but never persisted the new collection to
sessionStorage. This meant an immediate reload would lose the
contribution.

Fix: add a useEffect in ReasoningWorkspace that watches the
focusedContributions prop for changes and saves via the existing
saveSession mechanism. A ref guard prevents double-save alongside the
existing updateStatus-success effect.
2026-08-21 18:33:20 +01:00
robbond fce68a050f feat(ui): RTO.31 ownership of focused contributions flows to scenario form
- Add focusedContributions state + appendFocusedContribution callback in ScenarioForm
- Contributions persist through session lifecycle (save/restore/restart)
- Pass onFocusedContribution and focusedContributions to ReasoningWorkspace
- Call onFocusedContribution on successful deconstruct with full result shape
- Test: contribution sequence, field preservation, same/different target coexistence
2026-08-21 18:03:49 +01:00
robbond 41afd9b49f checkpoint: preserve reflection and response-contract work 2026-08-21 14:29:39 +01:00
robbond c9335cf850 fix(ui): make initial reflection surface exclusive to post-Analyse state
Add three exclusivity guards that suppress legacy surfaces during the
initial post-Analyse reflection state (postAnalyseStatus === 'success'):

- CurrentInvestigationCard: suppressed because its selectedQuestion
  from startCase was leaking into the initial reflection view
- OpenQuestionsPanel: suppressed because it rendered whenever hasGraph
  was true, regardless of initial reflection state
- Terminal state cards (EvidenceLimitCard / CompletionCard): suppressed
  because they fired on status='success' && !hasSelectedQuestion

Transition out of initial reflection happens when user clicks a proposed
finding, which sets formulationStep='active' and triggers the existing
deactivation useEffect.

No reasoning changes. No startCase changes. No mock changes.
2026-08-21 11:51:33 +01:00
robbond 86287bebe8 feat(ui): surface initial semantic reconstruction 2026-08-21 10:34:22 +01:00
robbond 412551c968 test(ui): restore deconstruction before question choice 2026-08-20 15:58:23 +01:00
robbond 4b264c5681 test(ui): make inferred questions originate branches 2026-08-20 14:29:42 +01:00
robbond 65ced2e406 test(ui): make initial branch selection user owned 2026-08-20 14:11:37 +01:00
robbond 4761d07a76 test(ui): stabilize fresh start hydration 2026-08-20 10:00:18 +01:00
robbond 173240d76c test(ui): restore fresh start scenario entry 2026-08-20 09:42:32 +01:00
robbond cef8f46bd6 test(ui): restore entry lifecycle and notebook rendering 2026-08-20 09:33:06 +01:00
robbond 86bb3426ef test(ui): explore provisional branch pause and reopen 2026-08-20 09:00:01 +01:00
robbond df0e3b5a9b test(ui): explore branch notebook composition 2026-08-20 08:42:43 +01:00
robbond e7a1bc689c test(ui): consolidate branch scoped workspace 2026-08-20 08:01:40 +01:00
robbond 36060faf16 test(ui): verify branch scoped workspace data path 2026-08-20 07:56:26 +01:00
robbond 49c4b904df test(ui): checkpoint branch scoped fixture 2026-08-20 07:43:18 +01:00
robbond 09eeed5a9e test(ui): isolate branch scoped workspace experiment 2026-08-20 07:35:08 +01:00
robbond 47cd0c7d7b test(experiment): checkpoint branch scoped reasoning retrieval 2026-08-20 06:48:44 +01:00
robbond 6bf9e7e710 test(ui): explore branch as workspace context 2026-08-20 06:26:35 +01:00
robbond 8163c5d014 test(ui): clarify branch provenance and hierarchy 2026-08-20 06:15:44 +01:00
robbond 0ac2e05c40 test(ui): clarify branch focus and passive updates 2026-08-20 06:01:32 +01:00
robbond ad42f67800 test(ui): explore passive branch result indication 2026-08-20 05:55:25 +01:00
robbond 058ad2326f test(experiment): checkpoint nonlinear branch continuity 2026-08-20 05:45:29 +01:00
robbond 9fb9735c6e test(experiment): checkpoint semantic relationship inference result 2026-08-19 19:14:46 +01:00
robbond a00adfabf4 test(experiment): simplify relationship inference apparatus 2026-08-19 18:53:10 +01:00
robbond 601e46e4b7 docs: preserve domain-independent facilitator principles 2026-08-19 18:39:35 +01:00
robbond 85204f96ac test(experiment): checkpoint borderline relationship control 2026-08-19 17:35:51 +01:00
robbond e1a18e27e7 test(experiment): checkpoint relationship negative-control result 2026-08-19 17:27:52 +01:00
robbond a73f125f8d test(experiment): checkpoint relationship false-positive apparatus 2026-08-19 17:21:13 +01:00
robbond c97f07ba65 test(experiment): checkpoint fragment relationship discovery result 2026-08-19 16:59:22 +01:00
robbond 67103fa8d4 test(experiment): repair relationship discovery env loading 2026-08-19 16:35:58 +01:00
robbond 4687226bd8 test(experiment): repair relationship discovery result handling 2026-08-19 16:14:50 +01:00
robbond 8fb284c374 test(experiment): checkpoint fragment relationship discovery apparatus 2026-08-19 15:43:06 +01:00
robbond 8c52939c02 test(experiment): checkpoint derived focused current-view result 2026-08-19 15:34:41 +01:00
robbond 644108db71 test(experiment): checkpoint derived focused current-view apparatus 2026-08-19 15:27:14 +01:00
robbond 0b0d5594fe test(experiment): checkpoint granular answer fragment result 2026-08-19 14:41:57 +01:00
robbond fbeaf01f90 docs: archive verified historical experiment families 2026-08-19 14:25:43 +01:00
robbond e6d0327641 docs: archive historical Confidence Engine evidence 2026-08-19 12:07:17 +01:00
robbond a12f9555af docs: clarify Confidence Engine context authority 2026-08-19 11:49:30 +01:00
robbond 5b43c1b8f9 docs: preserve Confidence Engine methodology continuity 2026-08-19 10:46:05 +01:00
robbond e1b54e4073 test(experiment): checkpoint granular answer fragment apparatus 2026-08-19 10:14:46 +01:00
robbond 6ed3415220 test(experiment): checkpoint three-turn separated reasoning result 2026-08-19 09:51:31 +01:00
robbond 98889039c2 test(experiment): checkpoint three-turn separated reasoning apparatus 2026-08-19 09:35:48 +01:00
robbond 9c715161b0 test(experiment): checkpoint separated reasoning layers result 2026-08-19 09:15:25 +01:00
robbond 56de4a7ef3 test(experiment): checkpoint separated reasoning layers apparatus 2026-08-19 08:53:33 +01:00
robbond b20707c447 test(experiment): checkpoint focused context boundary result 2026-08-19 08:33:41 +01:00
robbond 6ed4d60029 test(experiment): checkpoint focused context boundary apparatus 2026-08-19 07:59:41 +01:00
robbond 153bbee85e test(experiment): checkpoint two-turn focused refinement result 2026-08-19 07:53:26 +01:00
robbond 8520f2195d test(experiment): expose two-turn live refinement route 2026-08-19 07:44:55 +01:00
robbond ebcf1d9306 test(experiment): checkpoint two-turn focused refinement apparatus 2026-08-19 07:35:32 +01:00
robbond dafc020f66 feat(experiment): checkpoint one-turn focused investigation UI 2026-08-19 07:07:10 +01:00
robbond 7add85d8d2 feat(experiment): checkpoint focused investigation boundaries 2026-08-19 05:41:32 +01:00
robbond c0b963973f test(experiment): checkpoint focused vs global result 2026-08-19 05:15:25 +01:00
robbond 913dfec507 test(experiment): checkpoint comparison observability 2026-08-18 19:28:30 +01:00
robbond 952cb442b5 test(experiment): checkpoint focused vs global comparison apparatus 2026-08-18 18:34:08 +01:00
robbond 2f6c90b027 test(experiment): checkpoint focused answer deconstruction 2026-08-18 18:25:35 +01:00
robbond 648e1c7a29 test(experiment): checkpoint explicit-node formulation 2026-08-18 17:29:07 +01:00
robbond fd98cda8ba feat(experiment): checkpoint RTO question lifecycle 2026-08-18 16:38:15 +01:00
robbond 6b25100f9a feat(experiment): checkpoint RTO open-question workspace 2026-08-18 15:25:27 +01:00
robbond db5016c138 feat(experiment): checkpoint RTO case workspace lifecycle 2026-08-18 14:59:52 +01:00
robbond 4a34dcc361 test(experiment): ground RTO apparatus in real fixture 2026-08-18 12:39:19 +01:00
robbond 25a88c5fc3 feat: multi-thread experimental apparatus (RTO.A1)
Add fixture-only apparatus for representing multiple concurrent open
investigation items within a fixed case context.

New scenario 'multi-thread' exposes:
- A fixed central situation statement and case summary (product-launch
  timing decision, drawn from existing pre-anchored-product-launch
  data)
- Three open investigation items — none compulsory: enterprise customer
  signing probability, competitor timing, financial viability comparison
- One engine recommendation (mt-ent-customer-signing, ordered first)
- User selection of any item; chosen item becomes visually primary while
  others remain visible as context
- Experimental state isolated in _experimental / _experimentalState —
  never aliases production graph fields
2026-08-18 10:39:16 +01:00
277 changed files with 23326 additions and 296 deletions
+37 -10
View File
@@ -19,6 +19,18 @@ It:
6. updates the graph from the answer;
7. repeats until action is justified or the remaining uncertainty is clear.
> **NOTE:** The flow above describes historical/current implementation mechanics.
> It does not represent current Confidence Engine methodology direction.
> See `docs/Confidence_Engine_Return_to_Origin_Methodology_Context_2026-08-18.md`
> for the current working hypothesis (granular answer-fragment inquiry).
The linear selector-led flow described above is a **historical capability**, not
an automatic architecture to continue. Under Return-to-Origin:
- The Engine facilitates inquiry; it does not compel a single-question route.
- The user owns which unresolved investigation/question to pursue.
- Accumulated reasoning memory does not necessarily belong inside repeated LLM calls.
A chatbot remembers the conversation.
The Confidence Engine preserves the state of the reasoning.
@@ -50,15 +62,23 @@ The engine should help a user reach one of these states:
## Current development stage
The deterministic reasoning architecture reached a stable alpha checkpoint.
> **Version lineage note:** The Confidence Engine uses two distinct version
> lineages that must not be conflated:
> - **Reasoning-engine experimental lineage** (v0.8+): reasoning-fidelity,
> investigation-state assessment, semantic selectors — under RTO pause.
> - **UX/product development lineage** (v0.7): workspace layout, user views,
> loading feedback — also paused.
> These are independent tracks; do not assume they describe one product version.
Current work is primarily improving:
The deterministic reasoning architecture reached a stable alpha checkpoint
(reasoning-engine v0.8). UI/product work reached v0.7 staging. Both have
paused under Return to Origin while the granular answer-fragment hypothesis
is evaluated as working methodology context.
- usability;
- presentation;
- loading feedback;
- plain-language explanations;
- separation of user and developer views.
Current work is paused. The next step begins from the methodology question:
given the useful investigation structure the Engine can already derive, how
should that structure be surfaced so a person can see, choose, defer, and
return to open questions while the Engine continues to guide their thinking?
Do not resume broad reasoning architecture work unless a repeated observed
failure clearly requires it.
@@ -93,7 +113,11 @@ The interface should minimise cognitive load by presenting the current state fir
The engine may contain hundreds of reasoning nodes; the user should only see the information required to take the next meaningful action.
## Why workspace layout matters (v0.7)
## Why workspace layout matters (v0.7 — UX/product lineage)
> **This section documents paused UX design intent.** It belongs to the v0.7
> product development lineage, not the reasoning-engine lineage. UI work is
> currently paused under Return to Origin.
This phase optimises for simultaneous visibility instead of sequential scrolling.
Related panels — Understanding alongside Investigation Map, Situation alongside History — can appear side-by-side on wide screens while mobile continues to stack everything vertically. The reasoning engine is completely unaware of these changes; only the presentation layer is affected.
@@ -104,7 +128,10 @@ Read `docs/current-working-principles.md` for current guidance. Treat `docs/arch
For UI mock work, read `docs/ui-mock-reference.md`. Do not load
`docs/archive/deferred-ux-backlog.md` unless a named past UX idea is being reviewed.
Engine and UI experiments are paused. First file to inspect when resuming:
`docs/current-project-state.md`, then `docs/project-knowledge-inventory.md`.
Engine and UI experiments are paused under Return to Origin. First file to inspect when resuming:
**`docs/current-handoff.md`** (methodology continuity anchor), then `docs/current-project-state.md`, then `docs/project-knowledge-inventory.md`.
> After reading `docs/current-project-state.md`, choose the relevant minimal pack from `docs/task-context-packs.md`. Do not combine packs unless a specific task genuinely crosses boundaries.
>
> **Historical experiment families are evidence to load only when a specific question requires them; they are not default architecture context.**
+7
View File
@@ -39,3 +39,10 @@ yarn-error.log*
evaluation-results/
provider-debug-results/
tests-results/
# Local Playwright MCP runtime output
.playwright-mcp/
# Evidence/temp directories from live experiments
.evidence-temp/
@@ -0,0 +1,89 @@
import { getProvider } from "@/lib/llm/provider";
import { buildFocusedDeconstructPrompt, validateFocusedDeconstructSchema } from "@/lib/graph/focused-investigation";
export async function POST(request) {
try {
const body = await request.json();
if (!body.targetNodeId || typeof body.targetNodeId !== "string") {
return Response.json(
{ error: "Request must include a 'targetNodeId' string field" },
{ status: 400 },
);
}
if (!body.targetLabel || typeof body.targetLabel !== "string") {
return Response.json(
{ error: "Request must include a 'targetLabel' string field" },
{ status: 400 },
);
}
if (!body.targetDescription || typeof body.targetDescription !== "string") {
return Response.json(
{ error: "Request must include a 'targetDescription' string field" },
{ status: 400 },
);
}
if (!body.centralStatement || typeof body.centralStatement !== "string") {
return Response.json(
{ error: "Request must include a 'centralStatement' string field" },
{ status: 400 },
);
}
if (!body.question || typeof body.question !== "string") {
return Response.json(
{ error: "Request must include a 'question' string field" },
{ status: 400 },
);
}
if (!body.answer || typeof body.answer !== "string") {
return Response.json(
{ error: "Request must include an 'answer' string field" },
{ status: 400 },
);
}
const prompt = buildFocusedDeconstructPrompt({
targetLabel: body.targetLabel,
targetDescription: body.targetDescription,
centralStatement: body.centralStatement,
question: body.question,
answer: body.answer,
});
const provider = getProvider();
const startedAt = Date.now();
const raw = await provider.generateReconstruction(prompt, process.env.OLLAMA_MODEL);
const elapsedMs = Date.now() - startedAt;
// Validate schema (required fields present, no graph-mutation fields)
const validationErrors = validateFocusedDeconstructSchema(raw);
if (validationErrors.length > 0) {
return Response.json(
{
success: false,
error: "Focused deconstruction result did not match expected schema",
validationErrors,
targetNodeId: body.targetNodeId,
elapsedMs,
},
{ status: 502 },
);
}
return Response.json({
success: true,
targetNodeId: body.targetNodeId,
observations: raw.observations,
uncertainties: raw.uncertainties,
assumptions: raw.assumptions,
relationships: raw.relationships,
possibleFollowUpQuestions: raw.possibleFollowUpQuestions,
elapsedMs,
});
} catch (e) {
return Response.json(
{ error: e.message || "Unknown server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,52 @@
import { formulateQuestionForTarget } from "@/lib/graph/focused-investigation";
export async function POST(request) {
try {
const body = await request.json();
if (!body.targetNodeId || typeof body.targetNodeId !== "string") {
return Response.json(
{ error: "Request must include a 'targetNodeId' string field" },
{ status: 400 },
);
}
if (!body.situationGraph || typeof body.situationGraph !== "object") {
return Response.json(
{ error: "Request must include a 'situationGraph' object field" },
{ status: 400 },
);
}
const result = formulateQuestionForTarget({
situationGraph: body.situationGraph,
targetNodeId: body.targetNodeId,
});
if (!result.success) {
return Response.json(
{ success: false, error: result.error },
{ status: 400 },
);
}
return Response.json({
success: true,
targetNodeId: result.targetNodeId,
question: result.question,
strategy: result.strategy,
reasoningPattern: result.reasoningPattern,
reasoningPatternReason: result.reasoningPatternReason,
reason: result.reason,
questionFamily: result.questionFamily,
selectedQuestionTemplate: result.selectedQuestionTemplate,
allowedQuestionFamilies: result.allowedQuestionFamilies,
rejectedQuestionFamilies: result.rejectedQuestionFamilies,
});
} catch (e) {
return Response.json(
{ error: e.message || "Unknown server error" },
{ status: 500 },
);
}
}
+143
View File
@@ -0,0 +1,143 @@
/**
* Experimental branch switcher — RTO.25A
*
* Smallest branch representation needed to test passive late-result indication.
* Does NOT replace production branch navigation. Temporary fixture only.
*/
"use client";
import React, { useState, useEffect } from "react";
/* ── Keyframes (injected once via <style> at render) ───── */
const PulseStyle = () => (
<style>{`
@keyframes rto-pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
`}</style>
);
/* ── Status dot (passive new-result indicator) ─────────── */
function NewIndicator({ visible }) {
if (!visible) return null;
return (
<span
className="ml-2 inline-flex items-center"
title="Something new is available here"
aria-label="New result available"
>
<span
className="relative inline-block h-[8px] w-[8px]"
style={{ animation: "rto-pulse 3s ease-in-out infinite" }}
>
<span
className="absolute inset-0 rounded-full bg-blue-400/70"
aria-hidden="true"
/>
</span>
</span>
);
}
/* ── Single branch row ─────────────────────────────────── */
function BranchRow({ id, label, active, isNew, isPaused, origin, onClick }) {
const isActive = Boolean(active);
return (
<button
onClick={onClick}
disabled={isActive}
aria-current={isActive ? "page" : undefined}
className={`w-full flex items-start gap-2 rounded-md px-3 py-2 text-left transition text-sm ${
isActive
? "bg-blue-50/80 border border-blue-200/60 text-blue-900 font-medium"
: "text-gray-600 hover:bg-gray-100/70 hover:text-gray-800 border border-transparent"
} ${!isActive ? "cursor-pointer" : "cursor-default"}`}
>
{/* Active indicator — ● vs ○ */}
<span
className={`flex-none leading-none text-base ${
isActive ? "text-blue-500" : "text-gray-400"
}`}
aria-hidden="true"
>
{isActive ? "●" : "○"}
</span>
{/* Branch label + origin */}
<span className="flex-1 min-w-0">
<span className="truncate block">{label}</span>
{origin && (
<span className="block text-[11px] leading-tight text-gray-500/80 truncate" title={origin}>
{origin}
</span>
)}
</span>
{/* Passive indicators: pause + new */}
<span className="flex items-center gap-1.5 flex-none">
{!isActive && isPaused && (
<span
className="text-[10px] text-gray-400"
title="Done for now"
>
Paused
</span>
)}
{!isActive && <NewIndicator visible={isNew} />}
</span>
</button>
);
}
/* ── Card wrapper ────────────────────────────────────────── */
export default function ExperimentalBranchSwitcher({
branches = [],
activeBranchId,
branchNewResults = {},
branchPauseState = [],
onBranchSelect,
}) {
if (!branches.length) return null;
return (
<div
className="rounded-lg border border-gray-200/60 bg-gray-50/30 p-4"
role="radiogroup"
aria-label="Experimental branch switcher — RTO.25A"
>
{/* Label — clearly experimental */}
<h2 className="mb-1 text-[10px] font-semibold tracking-widest uppercase text-gray-600">
Branches{" "}
<span className="font-normal text-gray-500">(exp)</span>
</h2>
<p className="mb-3 text-[11px] font-medium leading-tight text-gray-500/80">
Browse branches. Current focus is preserved.
</p>
<div className="space-y-1" role="list" aria-label="Available branches">
{branches.map((branch) => (
<BranchRow
key={branch.id}
id={branch.id}
label={branch.label}
active={activeBranchId === branch.id}
isNew={Boolean(branchNewResults[branch.id])}
isPaused={branchPauseState.includes(branch.id)}
origin={branch.origin}
onClick={() => onBranchSelect?.(branch.id)}
/>
))}
</div>
</div>
);
}
export { PulseStyle };
+2 -2
View File
@@ -84,10 +84,10 @@ export default function InvestigationMap({ turnCount = 0 }) {
return (
<div className="rounded-lg border border-gray-200/60 bg-gray-50/30 p-4" role="region" aria-label="Investigation map preview">
<h2 className="mb-1 text-[11px] font-medium tracking-widest uppercase text-gray-300">
<h2 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Investigation Map
</h2>
<p className="mb-3 text-xs text-gray-400/70">
<p className="mb-3 text-xs font-medium leading-tight text-gray-500/80">
Active investigation topics and their status.
</p>
@@ -196,7 +196,7 @@ function InvestigationSummaryPanelV2({ graph, selectedQuestion, result, updateSt
<div>
{stillInvestigating.length > 1 ? (
<>
<h3 className="mb-2 text-xs font-medium text-gray-400">Still investigating</h3>
<h3 className="mb-2 text-xs font-medium text-gray-500">Still investigating</h3>
<ul className="space-y-1.5">
{Object.entries(investigatingByGroup).map(([group, items]) => (
<li key={group}>
@@ -227,7 +227,7 @@ function InvestigationSummaryPanelV2({ graph, selectedQuestion, result, updateSt
{/* ── What we have learned ────────────────────────── */}
{known.length > 0 && (
<div>
<h3 className="mb-2 text-xs font-medium text-gray-400">What we know</h3>
<h3 className="mb-2 text-xs font-medium text-gray-500">What we know</h3>
<ul className="space-y-1.5">
{known.map((item, i) => (
<li key={i} className="flex items-start gap-2">
@@ -243,8 +243,8 @@ function InvestigationSummaryPanelV2({ graph, selectedQuestion, result, updateSt
{/* ── Quiet reasoning summary — secondary ─────────── */}
<div className="pt-2 border-t border-gray-200/40">
<p className="text-[10px] font-medium tracking-widest uppercase text-gray-300 mb-1.5">Reasoning</p>
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-gray-400">
<p className="text-[10px] font-semibold tracking-widest uppercase text-gray-500 mb-1.5">Reasoning</p>
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-gray-500">
{reasonEntries.map(([label, count]) => (
<span key={label}>
{count} {label}
@@ -47,7 +47,7 @@ function KnownSection({ title, items }) {
return (
<div>
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-400">
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-500">
{title}
</h3>
<ul className="space-y-1.5">
@@ -67,7 +67,7 @@ function InvestigatingSection({ title, items }) {
return (
<div>
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-400">
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-500">
{title}
</h3>
<ul className="space-y-1.5">
@@ -87,7 +87,7 @@ function ExplanationSection({ items }) {
return (
<div>
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-400">
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-500">
Possible explanations
</h3>
<ul className="space-y-1.5">
@@ -102,10 +102,10 @@ function QuietSummary({ text }) {
return (
<div className="pt-2 border-t border-gray-200/40">
<p className="text-[10px] font-medium tracking-widest uppercase text-gray-300 mb-1.5">
<p className="text-[10px] font-semibold tracking-widest uppercase text-gray-500 mb-1.5">
Investigation state
</p>
<p className="text-xs text-gray-400">{text}</p>
<p className="text-xs text-gray-500">{text}</p>
</div>
);
}
+1 -1
View File
@@ -127,7 +127,7 @@ function InvestigationSummaryPanel({ graph, selectedQuestion, result, updateStat
{/* Current understanding */}
{currentUnderstanding && (
<div>
<h3 className="mb-1 text-[11px] font-medium tracking-widest uppercase text-gray-400/70">
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
What we understand so far
</h3>
<p className="text-sm leading-relaxed text-gray-600">{currentUnderstanding}</p>
File diff suppressed because it is too large Load Diff
+231 -60
View File
@@ -5,6 +5,8 @@ import { useState, useRef, useMemo } from "react";
import DiagnosticsView from "@/components/diagnostics-view";
import ReasoningWorkspace, { LoadingOverlay, ContinueLaterBanner } from "@/components/reasoning-workspace";
import { mockFetch, AVAILABLE_SCENARIOS } from "@/lib/mocks/confidence-engine/mock-client";
import { deriveFindingsFromContributions, normalizeFindings, produceFindingInformedSummary } from "@/lib/graph/finding-helpers";
import { loadInvestigation, saveInvestigation, clearInvestigation } from "@/lib/storage/investigation-storage";
/* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */
const MOCK_ENABLED = process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS === "true";
@@ -33,7 +35,7 @@ export async function submitScenarioForStartCase(fetchImpl, scenario) {
export async function submitAnswerForUpdateCase(
fetchImpl,
{ situationGraph, previousQuestion, answer },
{ situationGraph, previousQuestion, answer, findings },
) {
if (!answer?.trim()) {
return {
@@ -47,10 +49,15 @@ export async function submitAnswerForUpdateCase(
};
}
const body = { situationGraph, previousQuestion, answer };
if (findings && findings.length > 0) {
body.findings = findings;
}
const response = await fetchImpl("/api/cases/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ situationGraph, previousQuestion, answer }),
body: JSON.stringify(body),
});
return {
@@ -186,25 +193,37 @@ export function UpdateErrorPanel({ updateError }) {
export { INITIAL_MESSAGES, UPDATE_MESSAGES, useLoadingStatus };
// ── Session key ────────────────────────────────────────────────
const SESSION_KEY = "confidence-engine-session";
function getSession() {
if (typeof sessionStorage === "undefined") return null;
try {
const raw = sessionStorage.getItem(SESSION_KEY);
return raw ? JSON.parse(raw) : null;
} catch (_) { return null; }
/**
* Derives whether the current component state represents a valid investigation
* context sufficient to render a workspace surface.
*
* Valid only when:
* - result carries a situationGraph (renderable graph), OR
* - status is "success" AND there is a non-empty scenario
* (from session restoration with real data).
*
* This predicate is the single source of truth for all render-gate decisions.
* showExperimentView, fixture availability, or sessionStorage keys alone are
* NOT sufficient to constitute valid context.
*/
export function hasValidInvestigationContext(result, status, scenario) {
return Boolean(result?.situationGraph) ||
(status === "success" && Boolean(scenario?.trim()));
}
function saveSession(state) {
if (typeof sessionStorage === "undefined") return;
try { sessionStorage.setItem(SESSION_KEY, JSON.stringify(state)); } catch (_) {}
}
/**
* Derives the primary surface that must render for the given state tuple.
* Enforces exactly-one-primary-surface invariant: no zero, no two.
*/
export function derivePrimarySurface(result, status, _showExperimentView, scenario, activeBranchId) {
if (status === "loading") return "LOADING";
if (status === "error") return "ERROR_SURFACE";
function clearSession() {
if (typeof sessionStorage === "undefined") return;
try { sessionStorage.removeItem(SESSION_KEY); } catch (_) {}
const valid = hasValidInvestigationContext(result, status, scenario);
if (valid) return "NORMAL_WORKSPACE";
return "SCENARIO_ENTRY";
}
export default function ScenarioForm() {
@@ -219,19 +238,145 @@ export default function ScenarioForm() {
const [currentUnderstanding, setCurrentUnderstanding] = useState(null);
const [mockScenario, setMockScenario] = useState("");
const [hideFacilitatorOnLanding, setHideFacilitatorOnLanding] = useState(false);
/* ── RTO.31: focused contributions ownership ─────────────── */
const [focusedContributions, setFocusedContributions] = useState([]);
/* ── v2 findings from focused contributions ─────────────── */
const [findings, setFindings] = useState([]);
function appendFinding(finding) {
setFindings((prev) => {
return [...prev, finding];
});
}
function updateFindingDisposition(findingId, newDisposition) {
setFindings((prev) =>
prev.map((f) => (f.id === findingId ? { ...f, userDisposition: newDisposition } : f)),
);
}
function updateFindingProposition(findingId, newProposition) {
setFindings((prev) =>
prev.map((f) =>
f.id === findingId
? { ...f, proposition: newProposition, userDisposition: null }
: f,
),
);
}
/**
* v0.49 promotion seam — deterministic Current Understanding update
* triggered by "Done for now" activity boundary (no case/update, no LLM).
*/
function handleDoneForNowPromotion(targetNodeId) {
if (!targetNodeId || !findings?.length) return;
// Filter eligible findings for this specific target only.
const eligible = findings.filter(
(f) => f.originatingTargetNodeId === targetNodeId && (f.userDisposition === null || f.userDisposition === "agree"),
);
if (eligible.length === 0) return;
// Determine the base: use currentUnderstanding if available, else empty string.
const baseSummary = currentUnderstanding ?? "";
// Deterministic producer — no LLM, no API.
const newSummary = produceFindingInformedSummary(baseSummary, eligible);
// Idempotence guard: skip if summary is unchanged (no new eligible findings
// beyond what's already in the current Evidence block).
if (newSummary === baseSummary) return;
// Avoid duplicate evidence propositions from repeated promotion.
const existingEvidenceMatch = baseSummary.match(/Evidence:\s*\[([^\]]+)\]/);
let isDuplicate = false;
if (existingEvidenceMatch) {
const existingTexts = existingEvidenceMatch[1].split("; ").map((t) => t.trim());
isDuplicate = eligible.every((f) => existingTexts.includes(f.proposition));
}
if (isDuplicate) return;
// Mutate the SAME summary/state that autosave already persists.
setCurrentUnderstanding(newSummary);
}
function appendFocusedContribution(contribution) {
// Derive a single stored contribution object and use it for BOTH
// contribution storage AND Finding derivation so the same identity
// appears in focusedContributions[] and Finding.contributionId.
setFocusedContributions((prev) => {
const seq = prev.length + 1;
const storedContribution = { ...contribution, sequence: seq, id: `contrib-${String(seq).padStart(4, "0")}` };
// Derive Findings from the exact stored Contribution (not a separate approximation)
setFindings((prevFindings) => {
const newFindings = deriveFindingsFromContributions([storedContribution]).findings;
return normalizeFindings([...prevFindings, ...newFindings]);
});
return [...prev, storedContribution];
});
}
const textareaRef = useRef(null);
/* Restore persisted session on mount (Phase 3) ─────────── */
/* ── Valid investigation predicate ─────────────────────── */
// Delegated to the exported utility below.
const validCtx = hasValidInvestigationContext(result, status, scenario);
/* Restore persisted session on mount ─────────── */
useEffect(() => {
if (typeof window === "undefined") return;
const saved = getSession();
const saved = loadInvestigation();
if (!saved) return;
const hasGraph = Boolean(saved.situationGraph);
setScenario(saved.scenario || "");
setResult(saved.situationGraph ? { ...saved, situationGraph: saved.situationGraph } : null);
setResult(hasGraph ? { ...saved, situationGraph: saved.situationGraph } : null);
setCurrentUnderstanding(saved.summary || null);
setStatus("success");
setFocusedContributions(saved.focusedContributions || []);
setFindings(saved.findings || []);
// Partial sessions (present but no graph) must NOT suppress the
// scenario-entry form. Only promote to success when there is actual
// investigation data to render.
if (hasGraph) {
setStatus("success");
}
}, []);
/* ── Canonical autosave — persist whenever state changes (Phase 2) ── */
useEffect(() => {
if (typeof window === "undefined") return;
// Guard: no valid investigation yet → skip autosave during idle/start flows.
// Also prevents overwriting an existing saved investigation with the initial
// empty state of a fresh ScenarioForm instance (hydration race guard).
if (!result?.situationGraph) return;
void saveInvestigation({
scenario,
situationGraph: result.situationGraph,
selectedQuestion: result.selectedQuestion,
summary: currentUnderstanding,
updatedAt: new Date().toISOString(),
focusedContributions,
findings,
});
}, [
scenario,
result?.situationGraph,
result?.selectedQuestion,
currentUnderstanding,
focusedContributions,
findings,
]);
/* Restore facilitator dismiss preference (Experiment 05) ─── */
useEffect(() => {
if (typeof window === "undefined") return;
@@ -307,7 +452,7 @@ export default function ScenarioForm() {
setCurrentUnderstanding(data.summary ?? null);
const normalised = normaliseStartResult(data);
setResult(normalised);
saveSession({ scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString() });
saveInvestigation({ scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString(), focusedContributions, findings: [] });
} else {
setStatus("error");
setCurrentUnderstanding(data.summary ?? null);
@@ -340,6 +485,7 @@ export default function ScenarioForm() {
situationGraph: result?.situationGraph,
previousQuestion: result?.selectedQuestion,
answer,
findings,
});
if (submission.skipped) {
@@ -352,6 +498,12 @@ export default function ScenarioForm() {
const outcome = submission.data;
if (submission.ok && outcome.success) {
// Merge server-returned findings with local state
let newFindings = [...findings];
if (outcome.appendedFindings && Array.isArray(outcome.appendedFindings)) {
newFindings = [...newFindings, ...outcome.appendedFindings];
}
setUpdateStatus("success");
setCurrentUnderstanding(
outcome.summary ? outcome.summary : currentUnderstanding,
@@ -372,8 +524,8 @@ export default function ScenarioForm() {
diagnostics: outcome.diagnostics,
}));
setAnswer("");
// Persist after successful update turn
saveSession({ scenario, situationGraph: outcome.updatedSituationGraph, selectedQuestion: normaliseUpdateSelectedQuestion(outcome.selectedQuestion), summary: outcome.summary ?? currentUnderstanding, updatedAt: new Date().toISOString() });
// Persist after successful update turn — include findings
saveInvestigation({ scenario, situationGraph: outcome.updatedSituationGraph, selectedQuestion: normaliseUpdateSelectedQuestion(outcome.selectedQuestion), summary: outcome.summary ?? currentUnderstanding, updatedAt: new Date().toISOString(), focusedContributions, findings: newFindings });
} else {
setUpdateStatus("error");
setUpdateError(outcome);
@@ -386,7 +538,8 @@ export default function ScenarioForm() {
return (
<div className="space-y-6">
{status === "idle" && (
{/* ── Idle form for scenario input ─ */}
{!result?.situationGraph && status === "idle" && (
<form onSubmit={handleSubmit} className="space-y-6">
{/* Two-column landing workspace */}
@@ -419,7 +572,7 @@ export default function ScenarioForm() {
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<label htmlFor="dismiss-facilitator" className="text-xs text-gray-500">
Don't show this introduction again
{`Dismiss this introduction permanently`}
</label>
</div>
</div>
@@ -428,7 +581,7 @@ export default function ScenarioForm() {
{/* Right panel — Workspace (2/3 on desktop) */}
<div className={hideFacilitatorOnLanding ? "md:col-span-3" : "md:col-span-2"}>
<h2 className="mb-4 text-xs font-bold tracking-widest uppercase text-gray-400">Tell me what's happening</h2>
<h2 className="mb-4 text-xs font-bold tracking-widest uppercase text-gray-400">What&#39;s the situation</h2>
<textarea
ref={textareaRef}
value={scenario}
@@ -505,42 +658,58 @@ export default function ScenarioForm() {
/>
)}
{/* ── Main result workspace ─────────────────────── */}
{((status === "success" || status === "error") && status !== "loading") && (
<ReasoningWorkspace
scenario={scenario}
status={status}
updateStatus={updateStatus}
currentUnderstanding={currentUnderstanding}
result={{
...(result || {}),
situationGraph: updateResult?.updatedSituationGraph ?? result?.situationGraph,
selectedQuestion: updateResult?.selectedQuestion ?? result?.selectedQuestion,
newlySurfacedNodeIds: result?.newlySurfacedNodeIds || [],
diagnostics: result?.diagnostics || null,
updateError,
}}
answer={answer}
setAnswer={setAnswer}
onAnswerSubmit={handleUpdate}
lastSubmittedAnswer={lastSubmittedAnswer}
onRestart={() => {
clearSession();
setStatus("idle");
setResult(null);
setAnswer("");
setUpdateStatus("idle");
setUpdateResult(null);
setLastSubmittedAnswer("");
setCurrentUnderstanding(null);
setUpdateError(null);
}}
/>
{/* ── Main result workspace ─── */}
{(status === "success" || status === "error") && (
<>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* Workspace — uses result from Analyse or Update only */}
<div className="lg:col-span-2">
<ReasoningWorkspace
scenario={scenario}
status={status}
updateStatus={updateStatus}
currentUnderstanding={currentUnderstanding}
result={{
...(result || {}),
situationGraph: updateResult?.updatedSituationGraph ?? result?.situationGraph,
selectedQuestion: updateResult?.selectedQuestion ?? result?.selectedQuestion,
newlySurfacedNodeIds: result?.newlySurfacedNodeIds || [],
diagnostics: result?.diagnostics || null,
updateError,
}}
answer={answer}
setAnswer={setAnswer}
onAnswerSubmit={handleUpdate}
lastSubmittedAnswer={lastSubmittedAnswer}
focusedContributions={focusedContributions}
onFocusedContribution={appendFocusedContribution}
findings={findings}
onUpdateFindingDisposition={updateFindingDisposition}
onUpdateFindingProposition={updateFindingProposition}
/* v0.49 done-for-now promotion seam */
onSummaryUpdate={handleDoneForNowPromotion}
onRestart={() => {
clearInvestigation();
setStatus("idle");
setResult(null);
setAnswer("");
setUpdateStatus("idle");
setUpdateResult(null);
setLastSubmittedAnswer("");
setCurrentUnderstanding(null);
setUpdateError(null);
setFocusedContributions([]);
setFindings([]);
}}
/>
</div>
</div>
</>
)}
{/* ── Continue later banner when session was restored ── */}
{status === "success" && result?.updatedAt && (
<ContinueLaterBanner onRestart={() => { clearSession(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); }} />
<ContinueLaterBanner onRestart={() => { clearInvestigation(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); setFocusedContributions([]); setFindings([]); }} />
)}
{/* Reset button after successful analysis */}
@@ -548,7 +717,7 @@ export default function ScenarioForm() {
<div className="text-center">
<button
onClick={() => {
clearSession();
clearInvestigation();
setScenario("");
setStatus("idle");
setResult(null);
@@ -558,6 +727,8 @@ export default function ScenarioForm() {
setLastSubmittedAnswer("");
setCurrentUnderstanding(null);
setUpdateError(null);
setFocusedContributions([]);
setFindings([]);
}}
className="rounded-lg border border-gray-200/60 px-4 py-2 text-sm font-medium text-gray-500 transition hover:bg-gray-50/80"
>
@@ -211,6 +211,160 @@ Given the useful investigation structure the Engine can already derive, how shou
The next phase should begin from this methodology question, not from a preselected technical solution.
## Granular Answer-Fragment Learning (RTO.1417)
Recent experiments explored what happens when further answers are made inside the same focused investigation (RTO.1417).
### What RTO.1417 proved
The experiments demonstrated that an LLM can:
- Retain prior focused knowledge across turns
- Revise uncertainty in response to new information
- Separate focused understanding from decision significance
- Carry coherent reasoning across several turns inside a single investigation
This learning was valuable and should be preserved as experimental evidence. The apparatus created during RTO.1417 remains available and relevant.
### What RTO.1417 began recreating
Pushing that design further exposed that we had reproduced the original structural assumption at a lower level:
- **Original global pattern:**
```text
whole case state + new answer → LLM rewrites whole case state
```
- **Focused version (RTO.1417):**
```text
whole focused-investigation state + new answer → LLM rewrites whole focused-investigation state
```
The second version is much smaller and technically better, but it is still the same cumulative reconstruction pattern — just at a lower scope. Prompt growth from later RTO experiments helped expose this.
**Learning:** Do not immediately respond by optimising or compressing the cumulative focused-state implementation. Reconsider whether accumulated state needs to be sent back through the LLM at all.
### The granular answer-fragment hypothesis (working hypothesis — not yet architecture)
The natural reasoning unit appears to be:
> **one question → one answer → one interpretation/capture**
Granularity's purpose is not merely token or latency optimisation. The small cycle is how the methodology makes a large problem manageable for the user. A difficult scenario is progressively decomposed into pieces small enough to reason about confidently.
The working hypothesis is:
```text
user chooses a question
→ user provides an answer
→ Engine deconstructs that answer
→ Engine captures the granular contribution
→ resulting uncertainties/questions are exposed
→ user chooses what to investigate next
→ repeat
```
Each accepted answer can produce a small evidence-bearing reasoning fragment. Those fragments are remembered outside the LLM call. The larger investigation understanding and eventual graph emerge from composing those pieces over time. Only directly relevant prior knowledge may need to be supplied when a specific earlier fragment is being qualified, contradicted or refined.
A software implementation may eventually represent granular contributions as things such as:
- observations
- uncertainties
- assumptions
- relationships
- questions raised
linked to the question/investigation that produced them. This illustrative list is not a production schema — it exists here only as a design hint.
### Memory / graph principle
The LLM does not necessarily need to own accumulated reasoning memory. The graph/state/notebook layer can remember the reasoning fragments. The LLM may be used to interpret a new answer, but a software implementation should not assume every new answer requires sending all accumulated investigation state back through the model and asking it to regenerate the whole current understanding.
### Optional capability: "Help me answer" / "Answer for me"
A software implementation may optionally offer something like:
> **Help me answer** or **Answer for me**
where the LLM proposes an answer. This is an optional application capability — not part of the core method. The methodology works without it.
**Ownership rule:** A generated answer is a proposal, not gospel and not automatically evidence. The user must be able to accept it, edit it or reject it. Only an accepted contribution enters the normal reasoning/deconstruction flow. Where practical, provenance should remain distinguishable between:
- user-supplied answer
- LLM-proposed answer accepted/edited by user
### Development principle reaffirmed: BUILD → BREAK → LEARN → STOP
When an experiment exposes that an architectural assumption is breaking:
```text
do not immediately optimise the broken assumption
do not add complexity to preserve it
capture what was learned
return to the methodology
design the next smallest experiment from that learning
```
RTO.1417 should therefore remain valuable evidence, not be deleted or described as mistakes. They helped reveal the next underlying assumption.
## Methodology test for future development
> **Could this reasoning operation be described in the Confidence Engine methodology and performed by a trained human facilitator without an LLM?**
- If YES: the application may use an LLM to automate, accelerate or scale it
- If NO: stop and ask whether the work is developing the Confidence Engine methodology or merely exploiting an LLM capability
This does not apply to implementation mechanics such as JSON, APIs or databases. It applies to the underlying reasoning behaviour.
## Recent experimental evidence supporting methodological principles (2026-08-18/19)
The following experiments provide specific evidence for the durable methodology principles
documented in `docs/current-working-principles.md`. Each is recorded as one data point, not generalisation.
### RTO.18 — Independent granular question/answer deconstruction (without accumulated state)
Independent per-turn question and answer deconstruction worked when each turn received only its own
question + answer, without any accumulated focused state from previous turns. This supports:
- **A3** (reasoning on meaning, not accumulated vocabulary)
- **A6** (non-linear investigation via independent fragments)
- **The granular answer-fragment hypothesis** as a working direction
### RTO.20 — Narrow derived current view from selected fragments + known relationship
A narrow, derived current understanding state worked when computed from selected fragments combined with known structural relationships rather than full-graph reconstruction. This supports:
- **A5** (deterministic structure for identity/storage; semantic interpretation only where needed)
- **A10** (progressive disclosure of relevant reasoning to the user)
### RTO.21 — Semantic relationship discovery: one genuine positive case
Semantic interpretation found one genuine cross-fragment relationship from two fragments alone. The operation correctly identified that two contributions meaningfully related without prior keyword dictionary matching. This supports:
- **A4** (semantic interpretation as a suitable facilitation capability)
- **A3** (meaning-based over vocabulary-based reasoning)
### RTO.22 — Semantic relationship discovery: one obvious negative case (control)
The same semantic operation correctly returned no relationship for one obviously unrelated pair of contributions. This supports:
- **A4** (semantic interpretation is useful but produces proposals, not decisions)
- **A3** (meaning-based reasoning does not produce false positives at high rates on obvious cases)
### RTO.23 — Current apparatus work
RTO.23 apparatus development is ongoing. No live experimental evidence exists for RTO.23 yet.
---
## Methodology principles reinforced by this evidence
The experiments above support (without proving) the following durable methodology boundaries:
- **Delivery-platform independence** (A1): all results were observed through a software delivery path, but the reasoning operations described (question deconstruction, relationship inference, fragment composition) are equally performable by a human facilitator.
- **Meaning over dictionary** (A3/A4): RTO.21 and RTO.22 together suggest semantic interpretation can produce both true-positive and true-negative relationship proposals without keyword scoring — but two data points do not establish reliability. The guardrail remains: treat all inferred relationships as proposals until handled per the delivery method.
- **Non-linear investigation** (A6/A7): independent fragment processing validates that reasoning can proceed asynchronously across branches without blocking the user.
- **Progressive disclosure** (A10): RTO.20 demonstrates that a derived narrow view from relevant fragments is more useful to the user than a full-graph reconstruction of everything known.
## Source basis
- `01_Confidence_Engine_Founding_Principles`
@@ -224,4 +378,11 @@ The next phase should begin from this methodology question, not from a preselect
- `Confidence_Engine_Project_Context_Update_2026-08-17`
- `Confidence_Engine_Current_Handoff_2026-08-17`
This context update distinguishes established project principles from current implementation learning. The workspace/user-directed investigation model is recorded as the current hypothesis to test, not as a completed replacement architecture.
> **Provenance note:** Some source-basis documents listed above were external
> project/session context supplied during the methodology work and are not
> repository-managed files. They informed this document's content but cannot be
> verified as originating from the Git history of this repository. Their role
> is to document where the methodology context came from, not to assert Git
> provenance for those external documents.
This context update distinguishes established project principles from current implementation learning. The workspace/user-directed investigation model is recorded as the current hypothesis to test, not as a completed replacement architecture. The granular answer-fragment hypothesis (RTO.1417) is recorded as working hypothesis, not yet accepted architecture.
+24
View File
@@ -15,6 +15,30 @@ All files below were moved from `docs/` on 2026-08-06 by Experiment 29 to reduce
| `docs/v0.7-observation-report.md` (136 lines) | `docs/archive/v0.7-observation-report.md` | Experimental observation snapshot from v0.7 UX work. | Useful as a reference but not a current working document. UX work is paused. | When reviewing past UX observations that may inform future interface design decisions. |
| `docs/archive/deferred-ux-backlog.md` (376 lines) | `docs/archive/deferred-ux-backlog.md` | Deferred and exploratory UX ideas from original `docs/backlog info.md` (lines 21390). Retained for historical reference. Not commitments, priorities or active tasks. | Superseded `docs/backlog info.md`. Deferred UX planning separated from mock reference in Experiment 31. | When a named past UX idea from the deferred backlog is being reviewed; not loaded by default. |
## Phase 2B Experiment Archives (2026-08-19)
All files below were classified `HISTORICAL_EVIDENCE + SAFE` during the Phase 1B/2B context audit and moved to reduce default reading burden while preserving full traceability. They are preserved evidence — not discarded, obsolete, or invalidated. Load only when a specific historical question requires them.
| Subdirectory | What Was Moved | Count |
|---|---|---|
| `docs/archive/experiments/reasoning-fidelity-v0.8/` | Experiment 56 family (reasoning-fidelity v0.8 pass) | 11 files (experiment-56am, excluding c) |
| `docs/archive/experiments/semantic-action-contract/` | Experiment 58 family (semantic action contract) | 8 files (experiment-58a1a6, b1b2) |
| `docs/archive/experiments/question-formulation/` | Experiment 59 family (question formulation) | 7 files (experiment-59a1a3, b1b4) |
| `docs/archive/experiments/decision-options/` | Experiment 60A family (decision options analysis) | 7 files (experiment-60a18, excluding a3) |
| `docs/archive/experiments/decision-closure-integration/` | Experiment 60B subfamilies {1015}, {5582}, {95,97,100} | 35 files (experiment-60b{10-15}, {55-56,58-82}, {95,97,100}) |
| `docs/archive/experiments/knowledge-mgmt/` | Cold-start validation historical evidence | 1 file (cold-start-validation.md) |
| `docs/archive/experiments/context-routing/` | Document-role review (classification/routing analysis) | 1 file (document-role-review.md) |
| `docs/archive/experiments/pre-RTO/` | Pre-Return-to-Origin experiments and version-specific docs: v0.5v0.7 | 7 files (pre-RTO experiments + release notes/UX pass) |
**Not moved in Phase 2B:** checkpoint-60b93.md, docs/design-evolution-log.md, docs/investigation-state-assessment*.md, architectural-principles.md, v0.6-reasoning-architecture.md, success-signals.md, failure-modes.md, investigation-narrative.md, behaviour-selection.md, orchestrator-contract.md, reasoning-contract-backlog.md, reasoning-refinement-requirements.md, reasoning-production-path-map.md.
**Phase 2D experiment archives (2026-08-19):** After Phase 2C carry-forward verification confirmed all three families SAFE for archival:
| Subdirectory | What Was Moved | Count |
|---|---|---|
| `docs/archive/experiments/post-v0.8-investigation/` | Experiment 57 family (post-v0.8 investigation) | 69 files (experiment-57* family) |
| `docs/archive/experiments/decision-closure-integration/` | Experiment 60B subfamilies {18}, {1948} | 37 files (experiment-60b{1-8}, experiment-60b{19-48}) |
## Superseded Files
The following files were superseded by a structured split in Experiment 31 and are no longer in use. Their contents remain fully represented in the documents below.

Some files were not shown because too many files have changed in this diff Show More