Files
confidence-engine/lib/fixtures/rto26b-branch-scoped.mjs
T

247 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 inferred questions across branches (flat list for post-Analyse surface). */
getAllInferredQuestions() {
const qA1 = this._qA1;
const qA2 = this._qA2;
const qB1 = this._qB1;
const qB2 = this._qB2;
if (!qA1 || !qA2 || !qB1 || !qB2) return [];
return [
{ id: qA1.id, text: qA1.text, branchId: "branch-competitor-development" },
{ id: qA2.id, text: qA2.text, branchId: "branch-competitor-development" },
{ id: qB1.id, text: qB1.text, branchId: "branch-customer-demand" },
{ id: qB2.id, text: qB2.text, branchId: "branch-customer-demand" },
];
},
/** 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 };