Keep reassessment lineage scoped to its owning run

This commit is contained in:
Dotta 2026-09-11 15:54:22 -05:00
parent d66c5b6121
commit 3556fa25f1
2 changed files with 52 additions and 7 deletions

View File

@ -2135,14 +2135,39 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
]);
});
it("records superseding assessment lineage when a board transition has no native decision predecessor", async () => {
it.each(["none", "same_run", "other_run"])("records run-scoped superseding assessment lineage with %s predecessor", async (predecessor) => {
const fixture = corpus.fixtures.find((candidate) => candidate.mode === "native");
if (!fixture) throw new Error("native corpus fixture missing");
const seeded = await seedFixture(fixture);
let priorDecisionId: string | null = null;
if (predecessor !== "none") {
let priorRunId = seeded.runId;
let priorAssessmentId = seeded.assessmentId;
if (predecessor === "other_run") {
priorRunId = randomUUID();
const [run] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, seeded.runId));
await db.insert(heartbeatRuns).values({ ...run, id: priorRunId });
const [result] = await db.select().from(nativeRunResults).where(eq(nativeRunResults.id, seeded.resultId!));
const resultId = randomUUID();
await db.insert(nativeRunResults).values({ ...result, id: resultId, runId: priorRunId });
const [assessment] = await db.select().from(workAssessments).where(eq(workAssessments.id, seeded.assessmentId));
priorAssessmentId = randomUUID();
await db.insert(workAssessments).values({ ...assessment, id: priorAssessmentId, runId: priorRunId,
resultId, inputDigest: `later-run:${priorRunId}` });
}
const [prior] = await db.insert(statusDecisions).values({
companyId, issueId: seeded.issueId, runId: priorRunId, assessmentId: priorAssessmentId,
decisionVersion: 1, policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
fromStatus: "in_progress", toStatus: "blocked", reasonCode: "prior_authoritative_decision",
decisionJson: { statusAction: "blocked" }, decisionDigest: `prior:${seeded.issueId}`,
applicationState: "applied", appliedAt: new Date(),
}).returning();
priorDecisionId = prior.id;
}
await db.update(issues).set({
status: "blocked",
statusVersion: 1,
lastStatusDecisionId: null,
lastStatusDecisionId: priorDecisionId,
}).where(eq(issues.id, seeded.issueId));
const supersedingAssessmentId = randomUUID();
await db.insert(workAssessments).values({
@ -2156,7 +2181,7 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
triggerActorCompanyId: companyId,
priorIssueStatus: "blocked",
priorStatusVersion: 1,
priorDecisionId: null,
priorDecisionId,
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
assessmentJson: { reason: "board_transition_without_native_decision" },
inputDigest: `board-transition-assessment:${seeded.issueId}`,
@ -2171,7 +2196,7 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
assessmentId: supersedingAssessmentId,
priorStatus: "blocked",
priorStatusVersion: 1,
priorDecisionId: null,
priorDecisionId,
decision: {
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
statusAction: "preserve",
@ -2182,7 +2207,9 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
},
});
expect(committed.decision.supersedesDecisionId).toBeNull();
const [persistedDecision] = await db.select().from(statusDecisions)
.where(eq(statusDecisions.id, committed.decision.id));
expect(persistedDecision.supersedesDecisionId).toBe(priorDecisionId);
await expect(db.select({
supersedesAssessmentId: workAssessments.supersedesAssessmentId,
}).from(workAssessments).where(eq(workAssessments.id, supersedingAssessmentId))).resolves.toEqual([

View File

@ -747,13 +747,31 @@ async function materializeDecisionEffect(input: {
}
const prior = await input.tx.select({
assessmentId: statusDecisions.assessmentId,
runId: statusDecisions.runId,
}).from(statusDecisions).where(and(
eq(statusDecisions.id, input.issue.lastStatusDecisionId),
eq(statusDecisions.companyId, input.companyId),
eq(statusDecisions.issueId, input.issue.id),
)).limit(1).then((rows) => rows[0] ?? null);
if (!prior) throw new Error("native_superseded_assessment_missing");
// Decisions can supersede earlier issue decisions across runs, while
// evidence assessments are deliberately owned by one run. A historical
// run's reassessment already points at its own preceding assessment.
const existing = prior.runId !== input.runId
? await input.tx.select({ supersedesAssessmentId: workAssessments.supersedesAssessmentId })
.from(workAssessments).where(and(
eq(workAssessments.id, lineage.currentAssessmentId),
eq(workAssessments.companyId, input.companyId),
eq(workAssessments.issueId, input.issue.id),
eq(workAssessments.runId, input.runId),
)).limit(1).then((rows) => rows[0] ?? null)
: null;
const supersedesAssessmentId = prior.runId === input.runId
? prior.assessmentId
: existing?.supersedesAssessmentId;
if (!supersedesAssessmentId) throw new Error("native_superseding_assessment_not_linked");
const [assessment] = await input.tx.update(workAssessments).set({
supersedesAssessmentId: prior.assessmentId,
supersedesAssessmentId,
}).where(and(
eq(workAssessments.id, lineage.currentAssessmentId),
eq(workAssessments.companyId, input.companyId),
@ -774,7 +792,7 @@ async function materializeDecisionEffect(input: {
payload: {
supersedesDecisionId: input.issue.lastStatusDecisionId,
assessmentId: assessment.id,
supersedesAssessmentId: prior.assessmentId,
supersedesAssessmentId,
},
};
}