{
  "findings": [
    {
      "title": "Replacement engagements inherit an earlier firm’s verified status",
      "file": "apps/api/src/member-verification-work.ts",
      "line": 17,
      "severity": "major",
      "claim": "In relational mode, firm A can be verified while the case remains open, then be replaced by firm B. Reassignment and acceptance preserve the case-wide verification_status. The new member reader presents A’s outcome as B’s current verification, and the mobile hero suppresses the pending verification action. The new advertised admin action also accepts the stale value; the underlying unrestricted closure mutation predates this branch.",
      "evidence": "New reader: 'verification_status',coalesce(c.verification_status,'pending'),'work',( ...; only work is scoped by c.runtime_data->>'firmId'=f.api_firm_id and c.assigned_at=vt.assigned_at. Mobile activeCaseHero.ts:138: if (read.data.verification_status === \"verified\") ... headline: \"Lawyer contact is verified.\". server.ts:28479 spreads storedCase and clears only firmReportedResolvedAt; the relational writer does not reset verification_status. PostgreSQL17 reproduction returned {state:\"assigned\",verification_status:\"verified\",work:null} after B accepted; subsequent mark_verified closed without B verification.",
      "fix": "Derive current verification from the current accepted firm and acceptance timestamp, or reset/recompute it atomically on engagement change. Preserve A’s evidence as historical. Require current-engagement verification for closure and add a regression for verified A -> reassigned B -> B pending.",
      "lens": "dispatch",
      "refutation": "KEEP MAJOR. Independent codex exec -s danger-full-access -c model_reasoning_effort=high inspected callers, task cancellation, SQL status triggers and closure guards. New stale submissions are prevented, but existing case-wide status is not invalidated. Narrowed: closure mutation gap itself is pre-existing. See refutations/refute-verification-carry.log (exit 0); reproduced in logs/reassignment-probe-prepared.log."
    },
    {
      "title": "A previous member-end timestamp prevents the replacement firm’s resolution report",
      "file": "db/verification-durable-002/001_canonical_verification_tasks.sql",
      "line": 77,
      "severity": "major",
      "claim": "After the member ends A’s engagement, ops can reassign B and B can accept. Force assignment retains memberEndRequestedAt from A. When B reports resolution, the new trigger selects A’s earlier timestamp as the current task due time, violating due_at >= assigned_at and rolling back the report. Repeated firm reports cannot recover; a fresh authorized member-end request can replace the stale time.",
      "evidence": "select min(value::timestamptz) into entry_at from (values (current_case.runtime_data->>'firmReportedResolvedAt'), (current_case.runtime_data->>'memberEndRequestedAt')) entries(value) where value is not null; task_due_at:=entry_at;. Lines29–31 require (due_reason='case_entry' and due_at>=assigned_at). server.ts:30539 records the member end; :28479 keeps it on replacement. PostgreSQL17 reproduced ERROR: new row for relation \"verification_tasks\" violates check constraint \"verification_tasks_due_reason_time_check\" after B’s report.",
      "fix": "Add a new forward migration that derives verification-entry timestamps only from the current accepted engagement, and clear current engagement end fields during reassignment while retaining original audit evidence. Test member-end A -> B accepts -> B reports resolution. Do not edit this historically applied SQL file.",
      "lens": "dispatch, data",
      "refutation": "KEEP MAJOR with narrowed recovery statement. Independent Codex checked state guards, first acceptance, serialization, task cancellation, ON CONFLICT behavior and later migrations. Retaining the timestamp predates the diff; the atomic CHECK failure is new. See refutations/refute-member-end-reassignment.log (exit 0); reproduced in logs/reassignment-probe-prepared.log."
    },
    {
      "title": "Valid dollar-delimiter text can block relational emergency commands",
      "file": "apps/api/src/case-relational-command-sql.ts",
      "line": 114,
      "severity": "major",
      "claim": "The new case and pre-arm builders interpolate member-derived JSON inside a fixed DO $identity$ body. Accepted idempotency keys or pre-arm addresses containing $identity$ terminate that body and abort persistence. Failed new writes roll back, but a valid retained legacy row can survive checked adoption and then break commands for unrelated cases because each command serializes all cases. Ordinary production dispatch remains gated; enabled relational development/reviewer paths are affected.",
      "evidence": "const rows = `jsonb_to_recordset(${jsonSql(split.cases)}) as incoming(...)`; then `do $identity$ begin ... select 1 from cases existing left join ${rows} ... end $identity$;`. textSql only doubles apostrophes. prearm-command-store.ts:166,189 repeats this with input.rows. The schemas accept request_$identity$_123 and address_input='Near $identity$ square'. Actual generated pre-arm SQL failed in PostgreSQL17 with syntax error at or near \"square\" (logs/sql-delimiter-probe.log). Arbitrary SQL execution is not established.",
      "fix": "Stage request data outside procedural dollar-quoted bodies and reference staged rows inside them, or use consistently delimiter-safe encoding. Cover new input and adopted legacy case/arm payloads, including unrelated subsequent commands.",
      "lens": "dispatch, data, security",
      "refutation": "KEEP MAJOR, narrowed to configured relational paths and persistent legacy-data availability risk. Independent Codex verified that client_arm_id rejects dollar signs but dispatch idempotency_key and pre-arm address permit them; adoption preserves such values outside its own DO body. Fresh failures do not persist poison. See refutations/refute-sql-delimiter.log (exit 0)."
    },
    {
      "title": "Required browser journeys still read v1 after the v2 case-read cutover",
      "file": "apps/api/src/server.ts",
      "line": 53475,
      "severity": "major",
      "claim": "The branch correctly rejects v1 case facts that cannot represent missing location/deadline data, but leaves required browser test helpers on v1. Three brief/access journeys independently seed no-location cases and then fail before UI assertions; two close-case journeys fail when the shared queue contains such a case. CI unconditionally runs this suite.",
      "evidence": "New guard: if (v1 && (v1Location === null || repositoryDetail.live_case.verification.due_at === null)) { sendCaseReadVersionRequired(response); return; }. apps/admin/tests/visual/portal-routes.spec.ts:1769 still calls requestApiJson(`/v1/admin/cases/${encodeURIComponent(caseId)}`); :489 reads /v1/admin/cases/live. Fresh Linux run recorded five case_read_version_required failures among 19 failures/9 passes; the three brief/access seed calls are at1845,1897,1941. The live portal already routes its case reads to v2.",
      "fix": "Migrate browser helper reads and response parsing to v2, keeping the existing behavioral assertions and truthful no-location fixtures. Update canonical location setup only where the journey requires a point. Do not remove the compatibility guard or blindly replace screenshot baselines.",
      "lens": "quality, evidence",
      "refutation": "KEEP MAJOR. Independent Codex confirmed new guard versus unchanged callers, valid no-signal seeds, direct API calls bypassing the portal’s v2 mapper, and unconditional CI inclusion. Qualified close-case failures as shared-state dependent; separated screenshot/font failures. See refutations/refute-visual-v2.log (exit 0) and logs/admin-visual-freeports.log."
    },
    {
      "title": "The new contact-reveal operation omits its bearer security declaration",
      "file": "openapi/v1.yaml",
      "line": 11,
      "severity": "minor",
      "claim": "The new contact-reveal operation requires a bearer session in prose and server enforcement, but declares no OpenAPI security requirement or bearer scheme. Generated auth-aware clients cannot infer the operation’s credential requirement. This is acknowledged release debt, not an authentication bypass.",
      "evidence": "operationId: revealGeneralHelpContact; description: Requires an admin bearer session with help_thread_reply permission and MFA verified within ten minutes. No operation/root security or bearer security scheme exists in v1; openapi/baseline/v1.yaml mirrors the omission. server.ts:51664 enforces the session, permission and fresh MFA before audited disclosure.",
      "fix": "Define the bearer scheme and reference it on this operation, update the contract baseline deliberately, and verify the generated operation declares authenticated access.",
      "lens": "security, quality",
      "refutation": "Minor; mandatory per-finding CLI refutation applies only to blockers/majors. Independent evidence/contract review identified the omission; primary confirmed the complete operation and server enforcement. See refutations/evidence-review.log."
    },
    {
      "title": "New DSAR truth tests still require fulfilment that the safety contract forbids",
      "file": "apps/api/src/dsar-fulfilment-truth.integration.test.ts",
      "line": 89,
      "severity": "minor",
      "claim": "Both newly committed export/delete truth subtests expect HTTP200 and a fulfilled request from the uncomposed durable DSAR repository. HEAD deliberately returns503 before any erasure, and the neighboring safety integration test correctly requires that behavior. The opt-in suite is therefore internally contradictory and reliably red.",
      "evidence": "assert.equal(response.status, 200); then wait until request status === \"fulfilled\". The repository is createPostgresPersistentDsarRepository({ execSql }); server.ts:49520 checks fulfilmentMode === \"unavailable\" and returns dsar_fulfilment_unavailable. Fresh serial PostgreSQL run fails both \"export cannot return fixture counts for an empty member\" and \"delete cannot return fixture counts for an empty member\" with503 !==200.",
      "fix": "Reconcile the obsolete component-truth tests with the unavailable fulfilment contract. Keep and strengthen assertions that the request/deadline stay unchanged and no erasure starts; move actual-count assertions to a real composed fulfilment service when implemented. Preserve the production safety guard.",
      "lens": "quality, evidence",
      "refutation": "Minor: mandatory independent CLI refutation does not apply. Primary verified the test setup, current guard, adjacent dsar-fulfilment-safety integration tests, new-file commit and repeated PostgreSQL failures. This is a test-contract defect, not a request to enable DSAR fulfilment."
    }
  ]
}
