test(ui): checkpoint branch scoped fixture
This commit is contained in:
@@ -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 1–5) ───────────────────
|
||||
|
||||
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 1–5)
|
||||
_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 };
|
||||
Reference in New Issue
Block a user