fix(web): label current Ticket approval from requirement attestation

T-716 MR seq3 CURRENT_REQUIREMENT_APPROVAL_USES_INTEGRATION_PROOF: derive the requirement approval label from review_after_rescope rather than approved_current_subject. Preserve separate persisted integration/source approval display. Strengthen the Web regression for approved integration with stale current requirements and recovery by exact current attestation; require the Ticket route to render that tested status projection.

Validation: 28 focused Deno decoder/status/resource/route-reuse tests; deno task check (0 errors/warnings); root cargo check; cargo fmt --all -- --check; git diff --check HEAD. Rust contracts unchanged from validated source14fea520.
This commit is contained in:
2026-10-08 16:34:37 +09:00
parent 14fea520df
commit b715b1ee37
3 changed files with 43 additions and 4 deletions
@@ -7,6 +7,7 @@ import type { TicketMergeRequestSummary } from "#lib/generated/ticket-api.ts";
import { fixtureDetail } from "./home/dashboard.test-fixtures.ts";
import { parseTicketDetail } from "./api/ticket-browser.ts";
import {
currentRequirementApprovalStatus,
sourceReviewFreshness,
summarySourceReviewStatus,
} from "./merge-request-status.ts";
@@ -91,6 +92,13 @@ Deno.test("done Ticket can retain immutable approval while current requirement e
state: "done",
evidence: {
...fixtureDetail().evidence,
has_merge_request: true,
has_current_subject_ref: true,
has_commit: true,
has_review_request: true,
review_status: "pending",
approved_current_subject: true,
review_after_rescope: false,
missing: ["review_after_rescope"],
},
merge_requests: [{
@@ -101,6 +109,26 @@ Deno.test("done Ticket can retain immutable approval while current requirement e
});
assertEquals(ticket.state, "done");
assertEquals(ticket.evidence.complete_for_integration, false);
assertEquals(
currentRequirementApprovalStatus(ticket.evidence),
"not established",
);
const recovered = parseTicketDetail({
...ticket,
evidence: {
...ticket.evidence,
review_after_rescope: true,
review_status: "approved",
complete_for_integration: true,
missing: [],
},
});
assertEquals(recovered.evidence.approved_current_subject, true);
assertEquals(
currentRequirementApprovalStatus(recovered.evidence),
"approved",
);
assertEquals(recovered.state, "done");
assertEquals(
summarySourceReviewStatus(ticket.merge_requests[0]!),
"Integration approval recorded for immutable merged source source-1.",
@@ -118,7 +146,7 @@ Deno.test("Ticket route separates current requirement evidence from immutable in
const required of [
"Current requirement evidence",
"ticket.evidence.complete_for_integration",
"ticket.evidence.approved_current_subject",
"Current requirement approval:</strong> {currentRequirementApprovalStatus(ticket.evidence)}",
"ticket.evidence.missing",
"summarySourceReviewStatus(mergeRequest)",
"Immutable integration evidence",
@@ -1,4 +1,7 @@
import type { TicketMergeRequestSummary } from "#lib/generated/ticket-api.ts";
import type {
TicketEvidenceSummary,
TicketMergeRequestSummary,
} from "#lib/generated/ticket-api.ts";
import type {
MergeRequestDetail,
MergeRequestThreadEvent,
@@ -89,6 +92,14 @@ export function sourceReviewFreshness(
return `Fresh source review required: no effective verdict exists for ${source}.`;
}
// Source/integration approval can survive rescope; only this exact revision
// and linked-result attestation approves the current Ticket requirements.
export function currentRequirementApprovalStatus(
evidence: TicketEvidenceSummary,
): string {
return evidence.review_after_rescope ? "approved" : "not established";
}
export function summarySourceReviewStatus(
mergeRequest: TicketMergeRequestSummary,
): string {
@@ -13,7 +13,7 @@
parseTicketRoleAssignmentMutationResponse,
} from "#lib/workspace/api/ticket-browser.ts";
import { mergeRequestPagePath } from "#lib/workspace/api/merge-requests.ts";
import { summarySourceReviewStatus } from "#lib/workspace/merge-request-status.ts";
import { currentRequirementApprovalStatus, summarySourceReviewStatus } from "#lib/workspace/merge-request-status.ts";
import {
relationLabel,
TICKET_STATES,
@@ -686,7 +686,7 @@
<section class="ticket-control-card">
<header><h2>Current requirement evidence</h2></header>
<p><strong>Current requirements:</strong> {ticket.evidence.complete_for_integration ? "satisfied for integration" : "not satisfied for integration"}.</p>
<p><strong>Current requirement approval:</strong> {ticket.evidence.approved_current_subject ? "approved" : "not established"}.</p>
<p><strong>Current requirement approval:</strong> {currentRequirementApprovalStatus(ticket.evidence)}.</p>
{#if ticket.evidence.missing.length > 0}
<ul>
{#each ticket.evidence.missing as missing}<li>{missing}</li>{/each}