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