test(ui): checkpoint branch scoped fixture

This commit is contained in:
2026-08-20 07:43:18 +01:00
parent 09eeed5a9e
commit 49c4b904df
+231
View File
@@ -0,0 +1,231 @@
/**
* RTO.26B — Branch-scoped experimental fixture
*
* Replicates the RTO.26A branch record inside the UI layer.
* Membership is determined by explicit `branchId` provenance only.
*
* No semantic filtering, keyword matching, or whole-state reconstruction.
*/
// ── Deterministic ID generator (mirrors RTO.26A harness) ───────────────
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")}`;
}
// ── Record model (explicit provenance via `branchId`) ─────────────────
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,
};
}
// ── Fixed fixture data (mirrors RTO.26A stages 15) ───────────────────
function buildBranchScopedFixture() {
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);
// Late result scoped to Branch A / Contribution A1 (stage 5)
const lateResultA1 = createLateResult(
"branch-competitor-development",
cA1.id,
"This later interpretation relates to Contribution A1."
);
records.push(lateResultA1);
return {
// Records keyed by branch provenance
allRecords: records,
questions: {
a1: qA1, a2: qA2, b1: qB1, b2: qB2,
},
contributions: { a1: cA1, b1: cB1 },
lateResults: { a1: lateResultA1 },
};
}
// ── Branch-scoped retrieval (explicit provenance only) ─────────────────
/**
* getBranchReasoning(records, branchId)
* Returns ONLY records whose `branchId` matches. No semantic filtering.
*/
function getBranchReasoning(allRecords, branchId) {
return allRecords.filter(r => r.branchId === branchId);
}
// ── Branch-to-label mapping (UI layer only — not provenance logic) ─────
const BRANCH_META = {
"branch-competitor-development": {
id: "branch-a",
label: "Competitor development",
origin: "Whether competitors are developing similar products and when they might release them",
},
"branch-customer-demand": {
id: "branch-b",
label: "Customer demand",
origin: "How many customers will buy the product, and what revenue that represents",
},
};
// ── Exported interface for UI consumption ────────────────────────────────
export function useBranchScopedFixture() {
const fixture = buildBranchScopedFixture();
const allRecords = fixture.allRecords;
return {
/** Return branch-local question records as node-like objects. */
getBranchQuestions(branchId) {
const recs = getBranchReasoning(allRecords, branchId);
return recs
.filter(r => !r.questionId && !r.id.startsWith("late"))
.map(q => ({
id: q.id,
label: q.text,
kind: "unknown",
status: "unresolved",
_isFixtureQuestion: true,
contributions: q.contributions || [],
}));
},
/** Return branch-local contribution records scoped to active branch. */
getBranchContributions(branchId) {
const recs = getBranchReasoning(allRecords, branchId);
return recs.filter(r => r.questionId && !r.id.startsWith("late"));
},
/** Check if a late result exists for the given contribution. */
hasLateResult(contributionId) {
return Object.values(fixture.lateResults).some(lr => lr.contributionId === contributionId);
},
/** Return late results scoped to a specific branch (for display). */
getBranchLateResults(branchId) {
const recs = getBranchReasoning(allRecords, branchId);
return recs.filter(r => r.id.startsWith("late"));
},
/** Branch metadata for UI labels. */
getBranchMeta(id) {
// Reverse lookup: UI id → provenance branchId → meta
const entry = Object.entries(BRANCH_META).find(
([, m]) => m.id === id
);
return entry ? { ...entry[1] } : null;
},
/** Return the fixed two-branch list for the switcher. */
getBranches() {
return Object.values(BRANCH_META).map(m => ({ ...m }));
},
/** Return all records (for verification / debugging). */
getAllRecords() {
return allRecords;
},
/** Verify that a record's provenance matches its branch. */
verifyProvenance(record, expectedBranchId) {
return record?.branchId === expectedBranchId;
},
// ── RTO.26A cross-cutting invariants (deterministic checks) ──────
_crossCuttingInvariants() {
const branchAIds = new Set(getBranchReasoning(allRecords, "branch-competitor-development").map(r => r.id));
const branchBIds = new Set(getBranchReasoning(allRecords, "branch-customer-demand").map(r => r.id));
return {
SEMANTIC_FILTERING_REQUIRED: false,
KEYWORD_MATCHING_REQUIRED: false,
WHOLE_STATE_RECONSTRUCTION_REQUIRED: false,
GLOBAL_SELECTOR_REQUIRED: false,
CROSS_BRANCH_CONTAMINATION: [...branchAIds].some(id => branchBIds.has(id)),
NEXT_QUESTION_SELECTED: false,
};
},
// Expose fixture for inspection (RTO.26A stages 15)
_fixtureRecords: allRecords,
_qA1: fixture.questions.a1,
_qA2: fixture.questions.a2,
_qB1: fixture.questions.b1,
_qB2: fixture.questions.b2,
_cA1: fixture.contributions.a1,
_cB1: fixture.contributions.b1,
_lateResultA1: fixture.lateResults.a1,
};
}
export { buildBranchScopedFixture };