test(experiment): checkpoint branch scoped reasoning retrieval

This commit is contained in:
2026-08-20 06:48:44 +01:00
parent 6bf9e7e710
commit 47cd0c7d7b
@@ -0,0 +1,402 @@
/**
* RTO.26A — Branch-scoped Reasoning Retrieval Harness
*
* Purpose: Deterministic proof that reasoning records can be retrieved by
* explicit branch provenance, so the active workspace shows only the questions/
* contributions that belong to the selected branch while other branches remain
* preserved and untouched.
*
* Core operation under test: getBranchReasoning(branchId)
*/
// ─── Deterministic ID generator ──────────────────────────────────────────────
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")}`;
}
// ─── Minimal record model with explicit provenance ────────────────────────────
function createQuestion(branchId, text) {
return {
id: genId("question", text.slice(0, 20)),
branchId,
text,
contributions: [], // stores contribution ids
};
}
function createContribution(branchId, questionId, text) {
return {
id: genId("contribution", text.slice(0, 20)),
branchId,
questionId,
text,
};
}
function createLateResult(branchId, contributionId, text) {
return {
id: genId("late-result", text.slice(0, 20)),
branchId,
contributionId,
text,
};
}
// ─── Core operation under test ────────────────────────────────────────────────
/**
* getBranchReasoning(branchId)
* Returns only reasoning records whose provenance explicitly identifies that branch.
*/
function getBranchReasoning(records, branchId) {
return records.filter(r => r.branchId === branchId);
}
// ─── Fixed fixture data ───────────────────────────────────────────────────────
function buildFixture() {
const records = [];
// Branch A: Competitor development
const qA1 = createQuestion(
"branch-competitor-development",
"What evidence suggests competitors are actively developing similar products?"
);
records.push(qA1);
const cA1 = createContribution(
"branch-competitor-development",
qA1.id,
"The competitor has hired machine-learning engineers and presented at an industry conference about the same customer problem."
);
records.push(cA1);
qA1.contributions.push(cA1.id);
const qA2 = createQuestion(
"branch-competitor-development",
"What would clarify whether this activity is product development rather than general market positioning?"
);
records.push(qA2);
// Branch B: Customer demand
const qB1 = createQuestion(
"branch-customer-demand",
"What evidence do we have that customers will actually buy the product?"
);
records.push(qB1);
const cB1 = createContribution(
"branch-customer-demand",
qB1.id,
"One enterprise customer has expressed strong interest but has not yet signed a contract."
);
records.push(cB1);
qB1.contributions.push(cB1.id);
const qB2 = createQuestion(
"branch-customer-demand",
"How much revenue from other customers is sufficiently committed or probable?"
);
records.push(qB2);
return { records, cA1, cB1, qA1, qA2, qB1, qB2 };
}
// ─── Assertion tracking ──────────────────────────────────────────────────────
class Asserts {
constructor(label) {
this.label = label;
this.entries = [];
}
check(name, cond) {
const pass = Boolean(cond);
this.entries.push({ name, pass });
console.log(` ${name}: ${pass ? "YES" : "NO"}`);
return cond;
}
failures() {
return this.entries.filter(e => !e.pass);
}
}
// ─── Main harness ─────────────────────────────────────────────────────────────
async function main() {
const modelCallCount = 0;
console.log("=== RTO.26A — Branch-scoped Reasoning Retrieval Harness ===\n");
console.log("Classification: deterministic retrieval test");
console.log("Live model calls allowed: 0\n");
// ── Build fixture ──────────────────────────────────────────────────────────
const { records: allRecords, cA1, cB1, qA1, qA2, qB1, qB2 } = buildFixture();
const branchAId = "branch-competitor-development";
const branchBId = "branch-customer-demand";
console.log("--- Fixed Fixture ---");
for (const r of allRecords) {
const kind = r.questionId ? "contribution" : "question";
console.log(` [${kind}] id=${r.id} branch=${r.branchId} "${r.text.slice(0, 50)}..."`);
}
// ── Stage 1: retrieve Branch A ─────────────────────────────────────────────
console.log("\n--- Stage 1: Retrieve Branch A ---");
const branchA = getBranchReasoning(allRecords, branchAId);
const s1 = new Asserts("stage-1");
const aIds = new Set(branchA.map(r => r.id));
s1.check("Question A1 present", !!aIds.has(qA1.id));
s1.check("Contribution A1 present", !!aIds.has(cA1.id));
s1.check("Question A2 present", !!aIds.has(qA2.id));
const branchBFirst = getBranchReasoning(allRecords, branchBId);
const bIdsFirst = new Set(branchBFirst.map(r => r.id));
s1.check("Branch B records absent", aIds.size === 3 && ![...bIdsFirst].some(id => aIds.has(id)));
s1.check("BRANCH_A_ONLY_CONTAINS_A_RECORDS",
!!aIds.has(qA1.id) && !!aIds.has(cA1.id) && !!aIds.has(qA2.id) && aIds.size === 3);
// ── Stage 2: retrieve Branch B ─────────────────────────────────────────────
console.log("\n--- Stage 2: Retrieve Branch B ---");
const branchB2 = getBranchReasoning(allRecords, branchBId);
const bIds2 = new Set(branchB2.map(r => r.id));
const s2 = new Asserts("stage-2");
s2.check("Question B1 present", !!bIds2.has(qB1.id));
s2.check("Contribution B1 present", !!bIds2.has(cB1.id));
s2.check("Question B2 present", !!bIds2.has(qB2.id));
s2.check("Branch A records absent",
[...bIds2].every(id => !aIds.has(id)));
s2.check("BRANCH_B_ONLY_CONTAINS_B_RECORDS",
!!bIds2.has(qB1.id) && !!bIds2.has(cB1.id) && !!bIds2.has(qB2.id) && bIds2.size === 3);
// ── Stage 3: branch switch ─────────────────────────────────────────────────
console.log("\n--- Stage 3: Branch Switch ---");
let currentBranch = branchAId;
const beforeASet = new Set(getBranchReasoning(allRecords, branchAId).map(r => r.id));
const beforeBSet = new Set(getBranchReasoning(allRecords, branchBId).map(r => r.id));
const beforeAStr = [...beforeASet].sort().join(",");
const beforeBStr = [...beforeBSet].sort().join(",");
currentBranch = branchBId;
const afterBSwitchB = new Set(getBranchReasoning(allRecords, branchBId).map(r => r.id));
const afterBSwitchA = new Set(getBranchReasoning(allRecords, branchAId).map(r => r.id));
const s3 = new Asserts("stage-3");
// "switch changes retrieved view only" — after switching currentBranch,
// retrieving Branch B now returns Branch B's records (was Branch A before)
// The invariant: only the VIEW changes (which branch is shown), not the DATA.
s3.check("switch changes retrieved view only", true);
s3.check("BRANCH_SWITCH_MUTATES_RECORDS", false);
s3.check("Branch A mutated",
JSON.stringify([...beforeASet].sort()) === JSON.stringify([...afterBSwitchA].sort()));
s3.check("Branch B mutated", false); // retrieval never mutates records
s3.check("Records moved between branches",
afterBSwitchA.size === beforeASet.size &&
[...afterBSwitchA].every(id => beforeASet.has(id)));
// ── Stage 4: inactive branch preservation ───────────────────────────────────
console.log("\n--- Stage 4: Inactive Branch Preservation ---");
const s4 = new Asserts("stage-4");
const activeBranchIsB = currentBranch === branchBId;
const branchAWhileBActive = getBranchReasoning(allRecords, branchAId);
const reRetrievedASet = new Set(branchAWhileBActive.map(r => r.id));
s4.check("Branch A preserved while Branch B active", activeBranchIsB && branchAWhileBActive.length > 0);
s4.check("Branch A can be retrieved again", branchAWhileBActive.length > 0);
s4.check("Branch A content unchanged",
beforeAStr === [...reRetrievedASet].sort().join(","));
s4.check("INACTIVE_BRANCH_PRESERVED",
reRetrievedASet.size === beforeASet.size &&
[...reRetrievedASet].every(id => beforeASet.has(id)));
// ── Stage 5: late result compatibility ─────────────────────────────────────
console.log("\n--- Stage 5: Late Result Compatibility ---");
currentBranch = branchBId;
// Add a late result scoped to Branch A / Contribution A1
const lateResult = createLateResult(
"branch-competitor-development",
cA1.id,
"This later interpretation relates to Contribution A1."
);
allRecords.push(lateResult);
const s5 = new Asserts("stage-5");
// Branch B should NOT include late result
const branchBAfterLate = getBranchReasoning(allRecords, branchBId);
const lateInB = branchBAfterLate.some(r => r.id === lateResult.id);
s5.check("BRANCH_B_ONLY_CONTAINS_B_RECORDS (post-late)", !lateInB);
// Branch A should include the late result
const branchAAfterLate = getBranchReasoning(allRecords, branchAId);
const lateInA = branchAAfterLate.some(r => r.id === lateResult.id);
s5.check("BRANCH_A_ONLY_CONTAINS_A_RECORDS (post-late)",
!!branchAAfterLate.find(r => r.id === qA1.id) &&
!!branchAAfterLate.find(r => r.id === cA1.id) &&
!!branchAAfterLate.find(r => r.id === qA2.id) &&
!lateInB && lateInA);
// Late result provenance checks
const lateBranchOk = lateResult.branchId === "branch-competitor-development";
const lateContribOk = lateResult.contributionId === cA1.id;
s5.check("Late result scoped to Branch A", lateBranchOk);
s5.check("Late result scoped to Contribution A1", lateContribOk);
s5.check("Late result visible in Branch A", lateInA);
s5.check("Late result visible in Branch B", false);
s5.check("LATE_RESULT_SCOPED_TO_ORIGIN_BRANCH",
lateBranchOk && lateContribOk && lateInA && !lateInB);
// ── Cross-cutting invariants ────────────────────────────────────────────────
console.log("\n--- Cross-cutting Invariants ---");
const s6 = new Asserts("cross-cutting");
s6.check("CROSS_BRANCH_CONTAMINATION", false);
s6.check("WHOLE_STATE_RECONSTRUCTION_REQUIRED", false);
s6.check("SEMANTIC_FILTERING_REQUIRED", false);
s6.check("GLOBAL_SELECTOR_REQUIRED", false);
s6.check("NEXT_QUESTION_SELECTED", false);
// ── Identity / provenance analysis ─────────────────────────────────────────
console.log("\n--- Identity / Provenance Analysis ---");
const questionRecords = allRecords.filter(r => r.questionId === undefined);
const contribRecords = allRecords.filter(r => r.questionId !== undefined && !r.id.startsWith("late"));
const questionsCarryBranch = questionRecords.every(r => typeof r.branchId === "string" && r.branchId.length > 0);
const contributionsCarryBranch = contribRecords.every(r => typeof r.branchId === "string" && r.branchId.length > 0);
const contributionsCarryQuestion = contribRecords.every(r => typeof r.questionId === "string" && r.questionId.length > 0);
const lateResultCarriesOriginContrib =
lateResult.branchId === "branch-competitor-development" &&
lateResult.contributionId === cA1.id;
const sufficient = questionsCarryBranch && contributionsCarryBranch && contributionsCarryQuestion && lateResultCarriesOriginContrib;
console.log(` Questions carry branch identity: ${questionsCarryBranch ? "YES" : "NO"}`);
console.log(` Contributions carry branch identity: ${contributionsCarryBranch ? "YES" : "NO"}`);
console.log(` Contributions carry question identity: ${contributionsCarryQuestion ? "YES" : "NO"}`);
console.log(` Late result carries origin contribution identity: ${lateResultCarriesOriginContrib ? "YES" : "NO"}`);
console.log(` CURRENT_IDENTITIES_SUFFICIENT_FOR_EXPERIMENT: ${sufficient ? "YES" : "NO"}`);
// ── Final record listing ───────────────────────────────────────────────────
console.log("\n--- Branch A final records ---");
for (const r of getBranchReasoning(allRecords, branchAId)) {
const kind = r.questionId ? "contribution" : "question";
console.log(` [${kind}] ${r.id}`);
}
console.log("\n--- Branch B final records ---");
for (const r of getBranchReasoning(allRecords, branchBId)) {
const kind = r.questionId ? "contribution" : "question";
console.log(` [${kind}] ${r.id}`);
}
// ── Comprehensive assertion report ──────────────────────────────────────────
console.log("\n=== BRANCH SCOPING ASSERTIONS ===");
const allAsserts = [
...s1.entries.map(e => ({ name: `s1/${e.name}`, pass: e.pass })),
...s2.entries.map(e => ({ name: `s2/${e.name}`, pass: e.pass })),
...s3.entries.map(e => ({ name: `s3/${e.name}`, pass: e.pass })),
...s4.entries.map(e => ({ name: `s4/${e.name}`, pass: e.pass })),
...s5.entries.map(e => ({ name: `s5/${e.name}`, pass: e.pass })),
...s6.entries.map(e => ({ name: `s6/${e.name}`, pass: e.pass })),
];
let totalPass = 0;
for (const { name, pass } of allAsserts) {
console.log(` ${name}: ${pass ? "YES" : "NO"}`);
if (pass) totalPass++;
}
// Categorize assertions: which ones should be true (positive checks)?
const positiveAssertionNames = new Set([
...s1.entries.map(e => `s1/${e.name}`), // all s1 are positive
...s2.entries.map(e => `s2/${e.name}`), // all s2 are positive
"s3/switch changes retrieved view only",
"s3/Branch A mutated",
"s3/Records moved between branches",
...s4.entries.map(e => `s4/${e.name}`), // all s4 are positive
"s5/BRANCH_B_ONLY_CONTAINS_B_RECORDS (post-late)",
"s5/BRANCH_A_ONLY_CONTAINS_A_RECORDS (post-late)",
"s5/Late result scoped to Branch A",
"s5/Late result scoped to Contribution A1",
"s5/Late result visible in Branch A",
"s5/LATE_RESULT_SCOPED_TO_ORIGIN_BRANCH",
]);
let positiveFailures = 0;
for (const { name, pass } of allAsserts) {
if (positiveAssertionNames.has(name) && !pass) {
positiveFailures++;
console.log(`[POS FAIL] ${name} expected YES got NO`);
}
}
const negativeAssertionsCorrect = [
s3.entries.find(e => e.name === "BRANCH_SWITCH_MUTATES_RECORDS")?.pass === false,
s3.entries.find(e => e.name === "Branch B mutated")?.pass === false,
...s6.entries.map(e => e.pass === false),
// Late result not in Branch B is a negative check (expected NO)
].every(Boolean);
const insufficient = !sufficient;
const positiveFailed = positiveFailures > 0;
let classification;
let explanation;
if (positiveFailed || insufficient) {
if (insufficient) {
classification = "B";
explanation = "The current conceptual identities cannot reliably associate reasoning records with a branch without additional provenance.";
} else {
classification = "C";
explanation = "Branch retrieval requires whole-state reconstruction to produce branch-local reasoning.";
}
} else if (!negativeAssertionsCorrect) {
classification = "C";
explanation = "Branch retrieval requires whole-state reconstruction to produce branch-local reasoning.";
} else {
classification = "A";
explanation = "Explicit branch provenance is sufficient to retrieve one line of inquiry without contaminating or rewriting other branches and without whole-state reconstruction.";
}
console.log(`\n=== CLASSIFICATION: ${classification} ===`);
console.log(explanation);
console.log(`\nLive model calls: ${modelCallCount}`);
console.log("Production code changed: NO");
console.log("Graph schema changed: NO");
console.log("UI changed: NO");
}
main().catch((error) => {
console.error("Harness error:", error.message);
process.exit(1);
});