Keep reassessment lineage scoped to its owning run
This commit is contained in:
parent
d66c5b6121
commit
3556fa25f1
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue