test(experiment): checkpoint nonlinear branch continuity

This commit is contained in:
2026-08-20 05:45:29 +01:00
parent 9fb9735c6e
commit 058ad2326f
@@ -0,0 +1,395 @@
/**
* RTO.24A — Non-linear Branch Continuity Harness
*
* Purpose: Deterministic proof that a late semantic result for Branch A
* attaches to Branch A without:
* - moving the user's current focus
* - selecting a next question
* - contaminating Branch B
* - rebuilding any whole-investigation state
*
* Design boundary:
* - Standalone experimental runner.
* - Zero production code changes.
* - Zero live model calls.
* - Pure in-memory deterministic simulation.
* - No graph schema changes. No workers, queues, events, or UI.
*
* Flow tested:
* 1. User works in Branch A (contribution captured).
* 2. Semantic task is created for Contribution A (pending).
* 3. User switches to Branch B and creates Contribution B.
* 4. Late result for Contribution A arrives.
* 5. Result attaches to Branch A / Contribution A only.
* 6. User stays in Branch B. No forced navigation. No global rebuild.
*/
// ─── Deterministic ID generator (no LLM dependency) ────────────────────────
function genId(prefix, label) {
let h = 0;
for (let i = 0; i < label.length; i++) {
h = (Math.imul(31, h) + label.charCodeAt(i)) | 0;
}
return `${prefix}-${(Math.abs(h) % 1e6).toString(16).padStart(6, "0")}`;
}
// ─── Branch / Contribution state model (minimal, no production schema) ──────
function createBranch(id, label, centralStatement) {
return {
id,
label,
centralStatement,
contributions: [],
incomingResults: [], // semantic results that arrived late
currentSummary: "",
};
}
function createContribution(branchId, text, question) {
const contrib = {
id: genId("contrib", text.slice(0, 30)),
branchId,
text,
question: question || null,
status: "captured", // captured -> pending_result -> result_attached
createdAt: Date.now(),
originalText: text, // preserved for mutation check
};
return contrib;
}
function createPendingSemanticTask(contribId, branchId) {
return {
id: genId("task", `${branchId}-${contribId}`),
targetContributionId: contribId,
targetBranchId: branchId,
status: "pending", // pending -> completed -> delivered
createdAt: Date.now(),
};
}
function createLateResult(contribId, branchId, resultText) {
return {
id: genId("result", resultText.slice(0, 30)),
targetContributionId: contribId,
targetBranchId: branchId,
text: resultText,
attachedTo: null, // will be set by routing logic
arrivedAt: Date.now(),
};
}
// ─── Routing logic under test (the core invariant) ──────────────────────────
/**
* Route a late semantic result to its target branch and contribution.
*
* This is the minimal deterministic function that embodies the "branch continuity"
* invariant: results travel by explicit provenance links, not by global state.
*/
function routeLateResult(lateResult, branches) {
const targetBranch = branches.find(b => b.id === lateResult.targetBranchId);
if (!targetBranch) {
throw new Error(`Route fail: branch ${lateResult.targetBranchId} not found`);
}
const targetContrib = targetBranch.contributions.find(
c => c.id === lateResult.targetContributionId
);
if (!targetContrib) {
throw new Error(
`Route fail: contribution ${lateResult.targetContributionId} not found in branch ${targetBranch.id}`
);
}
// Attach result to the target branch's incoming results and its contribution
lateResult.attachedTo = {
branchId: targetBranch.id,
contributionId: targetContrib.id,
};
if (!targetBranch.incomingResults.find(r => r.id === lateResult.id)) {
targetBranch.incomingResults.push(lateResult);
}
if (!targetContrib.receivedResults) {
targetContrib.receivedResults = [];
}
targetContrib.receivedResults.push(lateResult);
// Update status flags on the contribution (non-mutating to original text)
targetContrib.status = "result_attached";
targetContrib.pendingTaskStatus = "delivered";
return { attachedTo: lateResult.attachedTo, branchId: targetBranch.id };
}
/**
* Simulate user switching active branch.
* This is the operation that MUST NOT be triggered by a late result arrival.
*/
function setActiveFocus(allState, branchId) {
const exists = allState.branches.some(b => b.id === branchId);
if (!exists) {
throw new Error(`Switch fail: branch ${branchId} not found`);
}
allState.activeBranchId = branchId;
return branchId;
}
// ─── Harness: Fixed Scenario Setup ──────────────────────────────────────────
function buildFixedScenario() {
const allState = {
activeBranchId: null, // will be set by the flow
branches: [],
pendingTasks: [],
lateResultsQueue: [], // results that have completed processing and are "arriving"
};
// ── Branch A: Competitor development ────────────────────────────────
const branchA = createBranch(
"branch-a",
"Competitor development",
"We need to assess whether a competitor is actively developing a solution to our core customer problem."
);
const contributionA = createContribution(
branchA.id,
"The competitor has hired machine-learning engineers and presented at an industry conference about the same customer problem.",
"What does the competitor's hiring and public presentation indicate about their product development timeline?"
);
branchA.contributions.push(contributionA);
allState.branches.push(branchA);
// ── Pending semantic task for Contribution A ────────────────────────
const pendingTaskA = createPendingSemanticTask(
contributionA.id,
branchA.id
);
contributionA.pendingTaskStatus = "pending";
allState.pendingTasks.push(pendingTaskA);
// ── Create Branch B (must exist before switching active focus) ───────
const branchB = createBranch(
"branch-b",
"Customer demand",
"We need to understand whether there is sufficient enterprise customer demand to justify a product launch."
);
allState.branches.push(branchB); // must exist before setActiveFocus validates
// ── Switch active focus to Branch B ─────────────────────────────────
setActiveFocus(allState, branchB.id);
const contributionB = createContribution(
branchB.id,
"One enterprise customer has expressed strong interest but has not yet signed a contract.",
"What would the enterprise customer's decision timeline look like if we requested a pilot program?"
);
branchB.contributions.push(contributionB);
allState.branches.push(branchB);
branchB.currentSummary = `Contribution captured for "${branchB.label}". Awaiting semantic analysis.`;
return allState;
}
// ─── Simulation: Late Result Arrival ────────────────────────────────────────
function simulateLateResultArrival(allState) {
// Get the pending task for Contribution A
const targetTask = allState.pendingTasks.find(
t => t.status === "pending"
);
if (!targetTask) {
throw new Error("No pending semantic task to complete");
}
// Mark task as completed (semantic analysis finished)
targetTask.status = "completed";
// Create the late result with the simulated semantic inference output
const lateResult = createLateResult(
targetTask.targetContributionId,
targetTask.targetBranchId,
"The competitor activity makes active competing-product development more plausible, but does not confirm a product or launch."
);
// Deliver the result (this simulates what the routing layer does)
routeLateResult(lateResult, allState.branches);
// Mark task as delivered
targetTask.status = "delivered";
targetTask.deliveredResultId = lateResult.id;
// Enqueue for branch notification
allState.lateResultsQueue.push(lateResult);
return lateResult;
}
// ─── Assertions ──────────────────────────────────────────────────────────────
function runAssertions(allState, lateResult) {
const errors = [];
let passCount = 0;
function assert(condition, message) {
if (!condition) {
errors.push(message);
console.log(` FAIL: ${message}`);
} else {
passCount++;
console.log(` PASS: ${message}`);
}
}
// ── Retrieve state references ───────────────────────────────────────
const branchA = allState.branches.find(b => b.id === "branch-a");
const branchB = allState.branches.find(b => b.id === "branch-b");
const contribA = branchA?.contributions?.[0];
const contribB = branchB?.contributions?.[0];
assert(!!branchA, "Branch A exists in state");
assert(!!branchB, "Branch B exists in state");
assert(!!contribA, "Contribution A exists in Branch A");
assert(!!contribB, "Contribution B exists in Branch B");
// ── Contribution ownership ──────────────────────────────────────────
assert(contribA.branchId === "branch-a", `Contrib A branchId = ${contribA?.branchId}`);
assert(contribB.branchId === "branch-b", `Contrib B branchId = ${contribB?.branchId}`);
// ── Late result attribution ─────────────────────────────────────────
assert(lateResult.targetBranchId === "branch-a", `Late result targetBranchId = ${lateResult?.targetBranchId}`);
assert(lateResult.targetContributionId === contribA?.id, `Late result targetContributionId = ${lateResult?.targetContributionId} (expected contrib A)`);
// ── Attachment correctness ──────────────────────────────────────────
assert(lateResult.attachedTo !== null && lateResult.attachedTo.branchId === "branch-a",
`Late result attached to branch A (${lateResult?.attachedTo?.branchId})`);
assert(lateResult.attachedTo !== null && lateResult.attachedTo.contributionId === contribA?.id,
`Late result attached to contribution A (${lateResult?.attachedTo?.contributedResultId || lateResult?.attachedTo?.contributionId})`);
// Verify it is NOT attached to Branch B
const branchBResults = branchB?.incomingResults || [];
assert(branchBResults.length === 0, "Late result does NOT attach to Branch B");
// ── User focus unchanged ────────────────────────────────────────────
assert(allState.activeBranchId === "branch-b",
`Active branch after late result = ${allState.activeBranchId} (expected branch-b)`);
// ── No forced navigation / no next-question selection ────────────────
// Neither contribution should have been auto-modified
assert(contribA.status === "result_attached", "Contrib A status updated to result_attached (not replaced)");
assert(contribB.status === "captured", `Contrib B status = ${contribB?.status} (should be captured, not modified)`);
// Original text preserved for both
assert(contribA.text === contribA.originalText,
`Contribution A text unchanged: "${contribA.text}"`);
assert(contribB.text === contribB.originalText,
`Contribution B text unchanged: "${contribB.text}"`);
// ── Branch A new-result available indicator ──────────────────────────
const branchANewResultAvailable = (branchA.incomingResults?.length || 0) > 0;
assert(branchANewResultAvailable, "Branch A can show 'new result available'");
// ── Late result queue integrity ─────────────────────────────────────
assert(allState.lateResultsQueue.length === 1, `Late results queue length = ${allState.lateResultsQueue.length}`);
assert(allState.lateResultsQueue[0].id === lateResult.id, "Queue item matches arrived result");
// ── No whole-state reconstruction flag ──────────────────────────────
// If any branch was rebuilt, it would have a rebuild flag set to true.
// Our routing only touches pending task and incoming results — no branches array recreation.
const branchARebuilt = branchA !== allState.branches.find(b => b.id === "branch-a");
assert(!branchARebuilt, "Branch A was NOT rebuilt (object identity preserved)");
// ── Pending tasks cleanup ───────────────────────────────────────────
const remainingPending = allState.pendingTasks.filter(t => t.status === "pending");
assert(remainingPending.length === 0, "No pending semantic tasks remain after delivery");
return { errors, passCount };
}
// ─── Main harness ────────────────────────────────────────────────────────────
async function main() {
const modelCallCount = 0; // deterministic — no provider import, no fetch
console.log("=== RTO.24A — Non-linear Branch Continuity Harness ===\n");
console.log("Classification: flow test (deterministic simulation)");
console.log("Live model calls allowed: 0\n");
// ── Phase 1: Build fixed scenario ────────────────────────────────────
console.log("--- Phase 1: Fixed Scenario Setup ---");
const state = buildFixedScenario();
const branchA = state.branches.find(b => b.id === "branch-a");
const branchB = state.branches.find(b => b.id === "branch-b");
const contribA = branchA.contributions[0];
const contribB = branchB.contributions[0];
console.log(` Branch A: ${branchA.label} (${branchA.id})`);
console.log(` Contribution A: "${contribA.text.slice(0, 60)}..." (${contribA.id})`);
console.log(` Pending semantic task: ${state.pendingTasks[0]?.id} (status: ${state.pendingTasks[0]?.status})`);
console.log(` Branch B: ${branchB.label} (${branchB.id})`);
console.log(` Contribution B: "${contribB.text.slice(0, 60)}..." (${contribB.id})`);
console.log(` Active focus: ${state.activeBranchId}`);
// ── Phase 2: Simulate late result arrival ────────────────────────────
console.log("\n--- Phase 2: Late Result Arrives ---");
const lateResult = simulateLateResultArrival(state);
console.log(` Late result ID: ${lateResult.id}`);
console.log(` Result text: "${lateResult.text}"`);
console.log(` Attached to: branch=${lateResult.attachedTo?.branchId}, contrib=${lateResult.attachedTo?.contributionId}`);
console.log(` Active focus after arrival: ${state.activeBranchId}`);
// ── Phase 3: Run assertions ──────────────────────────────────────────
console.log("\n--- Phase 3: Assertions ---");
const result = runAssertions(state, lateResult);
// ── Final report ─────────────────────────────────────────────────────
console.log("\n=== RESULT ===");
console.log(`Passes: ${result.passCount}`);
if (result.errors.length > 0) {
console.log(`Failures: ${result.errors.length}`);
for (const err of result.errors) {
console.log(`${err}`);
}
console.log("\nHarness: FAIL");
process.exit(1);
}
console.log(`\nHarness: PASS (${result.passCount} assertions)`);
console.log(`Live model calls: ${modelCallCount}`);
console.log(`Production code changed: NO`);
console.log(`Graph schema changed: NO`);
console.log(`UI changed: NO`);
return { state, lateResult, assertions: result };
}
main().catch((error) => {
console.error("Harness error:", error.message);
process.exit(1);
});