From b715b1ee379b18463dde3e9cf1048f016f263a45 Mon Sep 17 00:00:00 2001 From: Hare Date: Thu, 8 Oct 2026 16:34:37 +0900 Subject: [PATCH] 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. --- .../workspace/merge-request-status.test.ts | 30 ++++++++++++++++++- .../src/lib/workspace/merge-request-status.ts | 13 +++++++- .../tickets/[ticketId]/+page.svelte | 4 +-- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/web/workspace/src/lib/workspace/merge-request-status.test.ts b/web/workspace/src/lib/workspace/merge-request-status.test.ts index ffab4a28d..5e3ba1d44 100644 --- a/web/workspace/src/lib/workspace/merge-request-status.test.ts +++ b/web/workspace/src/lib/workspace/merge-request-status.test.ts @@ -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: {currentRequirementApprovalStatus(ticket.evidence)}", "ticket.evidence.missing", "summarySourceReviewStatus(mergeRequest)", "Immutable integration evidence", diff --git a/web/workspace/src/lib/workspace/merge-request-status.ts b/web/workspace/src/lib/workspace/merge-request-status.ts index d8860693e..d3b186bdd 100644 --- a/web/workspace/src/lib/workspace/merge-request-status.ts +++ b/web/workspace/src/lib/workspace/merge-request-status.ts @@ -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 { diff --git a/web/workspace/src/routes/w/[workspaceId]/tickets/[ticketId]/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/tickets/[ticketId]/+page.svelte index d395422c2..155a1fe9b 100644 --- a/web/workspace/src/routes/w/[workspaceId]/tickets/[ticketId]/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/tickets/[ticketId]/+page.svelte @@ -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 @@

Current requirement evidence

Current requirements: {ticket.evidence.complete_for_integration ? "satisfied for integration" : "not satisfied for integration"}.

-

Current requirement approval: {ticket.evidence.approved_current_subject ? "approved" : "not established"}.

+

Current requirement approval: {currentRequirementApprovalStatus(ticket.evidence)}.

{#if ticket.evidence.missing.length > 0}