Repository navigation
feat(tasks/report): accept worker startedAt so AgentRun durations are real (#1120) - #1168
Conversation
… real (#1120) Accept an optional ISO 8601 startedAt in the report body. Store it as the run's start time when it carries a timezone, parses, is not in the future, and is within 24h of the report; missing/malformed/future/too-old values silently fall back to the report time (never a 400). The raw body value participates in the idempotency payload hash so an identical-body retry keeps duplicate-replay semantics across the 24h acceptance boundary.
|
Responses to the AI review findings:
On the |
…op hash cast (#1120) Independent review of #1168 found three minors: no non-zero-offset test (TZ conversion exercised only via +00:00), the 24h bound's inclusive > semantics unpinned at exactly 24h, and a lying `as string | undefined` cast on the raw hash input. Add the two tests (the boundary one under fake timers so it is not race-prone), widen reportPayloadHash to accept startedAt?: unknown so no cast is needed (canonicalJson already takes unknown; hash output byte-identical), and note UTC normalization of the stored/echoed startedAt in AGENTS.md.
|
Follow-up push Fixed
Not changed, rationale
Verification at |
|
Coordinator takeover — state verification (no new code changes needed). Branch state: Checks at current head: all green — Build, CI (Lint/Typecheck/Tests/Coverage/Database integration/Workflow lint/Database migrations), Docker Build (+MCP), smoke — except Reviews: both its-saffron reviews (advisory, approval withheld for check-coverage only) have no outstanding findings at this head. Round 1's two info findings were resolved in No further changes from this run; PR remains open and review-ready. |
|
Courier: already addressed (run courier-80398826ef09fa000dacfa6032019f9c) PR #1168 is already review-ready at head 068ec8b: branch current with main tip b0863de (diff = only the 3 intended files, no dependency files), all checks green except npm audit, which I reproduced locally and is byte-identically pre-existing on main (dev-only braces GHSA-vfj7-8cjw-p6xm chain; mergeable_state unstable = failing non-required check) and fixed by separate open PR #1169 per #1162. Both its-saffron advisory reviews addressed: round-1 info findings fixed in 068ec8b and answered in comments, round-2 zero formal findings with all 5 claims Held, no inline threads. Verification comment posted at #1168 (comment) |
…es .npmrc include=dev (#1162) npm gives include=dev precedence over --omit=dev even on the command line, so npm run audit evaluated the dev tree and failed on the dev-only, unfixable braces * advisory GHSA-vfj7-8cjw-p6xm via eslint-config-next. The CLI --include=optional replaces the project-level include list, restoring a prod+optional-only audit.
|
Coordinator takeover — Previous state: all checks green at What changed: single cherry-pick of Verified locally at Body updated: the prior "no dependency files are touched" verification claim is superseded; invariant 6 now states the audit-scoping rule and how the other paths (setup-node action, Dockerfile prod-deps, security-audit workflow) are kept. #1169 carries the identical commit; whichever merges second has an empty delta. PR remains open for human review (joryirving). |
… over cherry-picked --include=optional (#1162)
|
Coordinator takeover — synced with Merge ( Independent review pass over the merged tree: zero blockers/majors; single
Verification at merged tree: full |
joryirving
left a comment
There was a problem hiding this comment.
Re-reviewed current head 23f976f. Approve.
I independently traced the route and idempotency behavior against #1120:
- valid worker
startedAtvalues are normalized and persisted, while missing/malformed/non-string/future/>24h values fall back to the single report-timenowwithout introducing a new 400 path; startedAt <= finishedAtis guaranteed because validation and persistence share the same captured instant;- the 24h boundary is inclusive and pinned deterministically;
- the idempotency hash correctly uses the raw request value, so an identical-body retry does not turn into a 409 merely because re-validation crosses the 24h acceptance boundary;
- different raw
startedAtpayloads still produce different hashes; - offset timestamps are converted to the correct UTC instant;
- the duplicate-response echo semantics match the documentation and do not mutate the already-stored AgentRun.
All current-head CI is green: tests, coverage, lint, typecheck, build, DB integration/migrations, both Docker builds, npm audit, smoke, and AI review.
Only non-gating wording nit: AGENTS.md says “full ISO 8601 timestamp with timezone,” while the accepted grammar is deliberately the extended-offset form (Z / ±HH:MM). The PR body already documents that distinction precisely, so I would not hold the implementation for it.
I found no merge blocker.
joryirving
left a comment
There was a problem hiding this comment.
One nit before merge, since this PR already touches the contract documentation:
AGENTS.md: change “a full ISO 8601 timestamp with timezone” to wording that matches the actual accepted grammar, e.g. “an extended ISO 8601 timestamp with timezone (Z or ±HH:MM)”.
The implementation intentionally rejects basic-form offsets such as +0000, so the current wording is broader than the contract. No code change needed; everything else on 23f976f is approved.
…patch/issue-1120 # Conflicts: # .npmrc
…ormat grammar (#1120) Human review nit (joryirving) on PR #1168: 'a full ISO 8601 timestamp with timezone' is broader than the accepted grammar. ISO_8601_TIMESTAMP_PATTERN accepts extended format only with Z or ±HH:MM offsets; basic-form offsets (+0000) fall back silently. Reword to 'an extended ISO 8601 timestamp with timezone (Z or ±HH:MM)'. Docs-only; no behavior change.
|
Nit addressed in
The wording now matches Also synced the branch with |
|
Coordinator takeover — PR is review-ready; one residual terminology nit closed in State at takeover: head New in this push ( Verification: comment-only change — lint/typecheck/tests unchanged; CI is re-running at |
There was a problem hiding this comment.
AI Automated Review
Full PR review.
Analysis engine: MiniMax-M3 (anthropic) — primary route · pr-reviewer-action v3.3.0
Findings (1 info)
| Severity | Location | Finding |
|---|---|---|
| Info | AGENTS.md:320 |
AGENTS.md now correctly scopes the accepted grammar to extended ISO 8601 with timezone (Z or ±HH:MM); worth a CHANGELOG entry under release/ but not blocking. |
Recommendation
Approve. The PR delivers exactly what issue PR 1120 asks for: the optional startedAt is accepted, validated, stored as the run's start when valid, and silently falls back to now otherwise — no 400. The now shared between validation and finishedAt is the right construction (impossible for startedAt > finishedAt), the idempotency hash is over the raw body so an identical retry never 409s across the 24h boundary, and AGENTS.md matches the actual regex grammar (extended format, Z or ±HH:MM). 13 CI checks are green at head a0ce647, including npm audit, full vitest, lint, typecheck, build, DB integration, and migrations. The only .npmrc change is the deleted trailing newline; the audit supersession (merging origin/main over the cherry-pick) is correctly adopted verbatim and is invisible in the diff vs main for package.json, SECURITY-ACCEPTED-RISKS.md, and the audit workflow. The single outstanding human review nit (joryirving on 23f976f) — "extended ISO 8601 with timezone (Z or ±HH:MM)" — is satisfied.
Change-by-change findings
src/app/api/agents/[agentName]/tasks/report/route.ts
- New
TaskReportBody.startedAt?: stringfield — additive, optional, no breaking change. ✓ - New
MAX_STARTED_AT_AGE_MS = 24handISO_8601_TIMESTAMP_PATTERN(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/) — extended format only, basic-form+0000falls back silently as designed and documented. ✓ resolveReportedStartedAt(raw, now)gates in order: non-string → undefined, regex fail → undefined, NaN time → undefined, future (time > now) → undefined, older than 24h (now - time > MAX_STARTED_AT_AGE_MS) → undefined. The 24h comparison is strict-greater, so exactly-24h is accepted (inclusive bound) — pinned by the dedicated test using fake timers atT0minus 24h. ✓nowis computed once beforereportis assembled, and the samenowis used forfinishedAt, sostartedAt <= finishedAtis guaranteed by construction whenstartedAtis accepted (worker value <= now = finishedAt) and trivially when fallback fires (both equalnow). ✓report.startedAtis the normalized UTC ISO string (parsed and re-serialized viatoISOString()); echo reflects the canonical Z form. ✓runData.startedAtisnew Date(report.startedAt)when valid, elsenow— Date object for Prisma. ✓reportPayloadHashsignature widened to acceptstartedAt?: unknown(no cast); the call site explicitly passesraw.startedAtso the idempotency identity is the raw body, not the time-validated normalized value. This is what keeps an identical-body retry deduping instead of 409ing when the same string flips across the 24h boundary between attempts. ✓- The duplicate branch (
existing.payloadHash !== payloadHash→ 409) is unchanged and operates on the raw-hash, so behavior is consistent. ✓
src/app/api/agents/[agentName]/tasks/report/route.test.ts
17 new tests, all consistent with the corpus. Highlights:
- valid stored (duration > 0): asserts
startedAt.getTime() === startInstantandfinishedAt > startedAt. ✓ - missing →
startedAt === finishedAtand withinbefore/after. ✓ - malformed string, number, object, empty string, >24h, future → all collapse to
nowand return 200. ✓ ~23haccepted; exactly 24h accepted (fake timers atT0); non-zero offset+05:30stored at the UTC instant (TZ-conversion proof, not just accept); Python+00:00accepted; response echo normalized vs absent; bare-year/date-only/locale rejected. ✓- Differing only by
startedAt→ distinctpayloadHash(verifies raw-value participation). ✓ - Identical-body retry 25h later →
duplicate: true,agentRunId: 'run-1', status 200, not 409. ✓
The pre-existing route tests are intact; the helper setup (mockAgentRun.create, mockDedupe, Prisma.PrismaClientKnownRequestError) supports the new cases.
AGENTS.md
New paragraph at line 320 in the Report Outcomes section: optional startedAt, extended ISO 8601 with timezone (Z or ±HH:MM), 24h window, silent fallback (never a 400), UTC (Z) normalization on store/echo, raw value participates in idempotency payload identity, and the echo reflects re-validation of the current request so an idempotent replay may omit startedAt even when the original stored it. The wording matches the regex grammar and addresses the human review nit at 23f976f.
.npmrc
Only the trailing newline on include=dev is removed. Content is otherwise unchanged, including the CAUTION comment that now references #1166 (canonical --include=prod) instead of the obsolete --include=optional. Confirmed by impact scan: #1166 is the live audit-fix identifier across package.json, SECURITY-ACCEPTED-RISKS.md, package-audit.test.ts, the security-audit workflow, Dockerfile, and .npmrc itself. The PR's diff vs origin/main for the audit-fix files is empty (per the PR body's supersession note and the impact scan showing main-side wording in every hunk), so this PR does not re-litigate the audit gate.
Claim verification
- Claim 1 (
AgentRun.startedAtpreviously stamped with report time → zero duration for single-report runs). Held: the diff removesstartedAt: nowfromrunDataand replaces it withreport.startedAt ? new Date(report.startedAt) : now. No remaining counterexample. - Claim 2 (missing/malformed/non-string/future/too-old silently fall back, never 400). Held:
resolveReportedStartedAtreturnsundefinedon every rejection branch, and the fallback path runs inside the POST handler after all 400ing validators (taskType / outcome / numbers / string fields / evidence / idempotencyKey / prFixItem) have already passed. No new 400 was introduced on any listed or unlisted sibling code path. - Claim 3 (
resolveReportedStartedAtstrict grammar, not-future, inclusive 24h, singlenow, raw hash, etc.). Held: regex matches the documented grammar;time > nowrejects future;now - time > MAX_STARTED_AT_AGE_MSstrictly rejects >24h (so exactly-24h passes, verified by the dedicated fake-timer test); singlenowis computed once at line 241 and reused;reportPayloadHashis called with{ ...report, startedAt: raw.startedAt }so the raw value (not the normalized one) participates in the hash. - Claim 4 (17 new tests; cases enumerated). Held: count matches; each enumerated case is present in the diff (valid, missing, malformed string, number, object, empty string, future, >24h, ~23h accepted, exactly-24h accepted under fake timers,
+05:30stored at correct UTC instant, bare-year/date-only/locale rejected,+00:00accepted, echo normalized, echo absent on invalid, hash divergence for differing startedAt, identical-body retry across boundary → duplicate not 409). - Claim 5 (AGENTS.md documents extended ISO 8601 with timezone; UTC normalization; echo reflects current request). Held: AGENTS.md L320 reads "an extended ISO 8601 timestamp with timezone (
Zor±HH:MM), for example2026-10-03T04:20:58Zor2026-10-03T04:20:58.123456+00:00"; mentions "normalized to UTC (Z) form when stored and echoed back"; and explicitly notes the echo reflects re-validation of the current request. The human review nit at23f976fis satisfied.
Sources
src/app/api/agents/[agentName]/tasks/report/route.tsL35-72 (interface, constants, helper), L241 (nowonce), L252-262 (report.startedAt), L268-274 (runData.startedAt), L126-131 (reportPayloadHash), L291-294 (raw-body hash).src/app/api/agents/[agentName]/tasks/report/route.test.tsL733-1040 (17 new tests).AGENTS.mdL320 (new contract paragraph)..npmrc(no semantic change; CAUTION references#1166).package.json"//".auditcomment (impact scan) andpackage-audit.test.ts(impact scan) confirm the audit gate is canonical and guarded.- CI: 13/13 checks green at head
a0ce647. - Human review nit (
5407799789against23f976f): addressed atf43bcd7(AGENTS.md wording) anda0ce647(code comment).
Standards Compliance
- AGENTS.md — agent workflow contract updated to match the implementation; route documentation added;
DISPATCH_AGENT_TOKENbearer-auth path unchanged. - .github/ai-review-rules.md — direct, practical, flag real defects, prefer approve when reasonable: this PR meets that bar.
- Code Standards (AGENTS.md) — input validated before DB operations (
startedAtvalidated before being passed to Prisma); appropriate HTTP status (no spurious 400); no secrets logged or persisted. - Repository conventions — the
startedAtfield is additive (no breaking change to existing callers); the hash widening uses anunknownboundary (no cast) so anunknownbody still hashes correctly without type laundering.
Linked Issue Fit
Issue PR 1120's "Done when":
- "A report with a valid
startedAtstores it, and the duration isfinishedAt - startedAt." — Held:runData.startedAt = report.startedAt ? new Date(report.startedAt) : now; teststores a valid worker-reported startedAt…pinsfinishedAt - startedAt > 0and the exact stored instant. - "A missing, malformed, future or too-old
startedAtfalls back tonow." — Held: six dedicated tests cover missing, malformed string, future, and >24h; number/object/empty/bare-year/date-only/locale round it out. - "Route tests cover each case." — Held: 17 tests in the new
startedAt (#1120)describe block.
The issue's proposal text also asked for "not older than a sane bound (e.g. 24h)"; the implementation picks exactly 24h with an inclusive bound — consistent with the proposal.
Tool Harness Findings
8 tool calls executed (read_file on route.ts at three offsets, route.test.ts at two offsets, AGENTS.md, and .github/ai-review-rules.md; one access-denied on .npmrc which is fine because the diff already shows the change is only the trailing newline on include=dev and the content is otherwise identical). The harness confirmed:
now = new Date()is computed exactly once (L241) and the priorconst now = new Date()further down has been removed.reportPayloadHashis called with{ ...report, startedAt: raw.startedAt }— raw value, not the normalized one.- AGENTS.md L320 wording matches the regex grammar (extended format,
Zor±HH:MM) and explicitly addresses the human review nit. - The 17 new tests are co-located with the existing persistence/idempotency blocks; the existing
postRequesthelper and mock setup support the new cases without modification.
Unknowns or Needs Verification
None that block the verdict. The PR is internally consistent, the linked issue is satisfied, the human nit is addressed, and CI is green.
Human review dispositions
[
{
"review_id": "5407799789",
"disposition": "addressed",
"evidence": "AGENTS.md L320 now says 'an extended ISO 8601 timestamp with timezone (`Z` or `±HH:MM`)', matching the accepted regex grammar `ISO_8601_TIMESTAMP_PATTERN` (`Z` or `±HH:MM`, extended format only; basic-form `+0000` outside the grammar). The residual 'full ISO 8601' wording in the code comment was also reworded to 'extended ISO 8601' at head `a0ce647`."
}
]Requirement coverage
- req-e589bfe1ad7c (
Lint/typecheck blocks CI; must pass) — satisfied: Lint and Typecheck both showsuccessin CI at heada0ce647; PR body also statesnpm run audit(perpackage.json//.auditcomment) plusvitest3808 passed / 16 skipped at the prior verified headf43bcd7. - req-ad902b2a08da (
Tokens are secrets. DISPATCH_AGENT_TOKEN and GITHUB_TOKEN must never be logged, echoed, or persisted to disk.) — satisfied: the route never logs the bearer token, never echoes it instartedAt(which is only the report-body field), and never persists it viarunData(which contains onlyagentName,runType,status,startedAt,finishedAt,summary,errorMessage,touchedIssueUrls,issueId). No token is written to the AgentRun row.
Outstanding Human Change Requests
- @joryirving's change request (23f976f, head moved since) is not shown addressed at this head; it needs the reviewer's own re-review.
Reason: PR review: CHANGES_REQUESTED Total attempts: 192 Attempts by lane:
Failing run(s):
Last attempt: One nit before merge, since this PR already touches the contract documentation: AGENTS.md: change “a full ISO 8601 timestamp with timezone” to wording that matches the actual accepted grammar, e.g. “an extended ISO 8601 timestamp with timezone ( The implementation intentionally rejects basic-form offsets such as Posted automatically by Dispatch on 2026-10-05T09:10:47.139Z |
joryirving
left a comment
There was a problem hiding this comment.
Re-reviewed current head a0ce647. Approve.
The only item from my prior change request is fully addressed:
- AGENTS.md now says “an extended ISO 8601 timestamp with timezone (
Zor±HH:MM)”, matching the actual regex grammar. - The same stale “full ISO 8601” wording in the route comment was also cleaned up to “extended ISO 8601,” so the implementation and contract documentation now use the same terminology.
I rechecked the core behavior as well: raw-body idempotency hashing, inclusive 24h validation, UTC normalization, and silent fallback for invalid/non-string/future/too-old values remain unchanged.
Current-head CI, Security Audit, image build, PR Smoke, and AI review are all green. I found no remaining blocker or nit.
Closes #1120
What
POST /api/agents/{agentName}/tasks/reportpreviously stampedAgentRun.startedAtwith the report time, so every single-report worker run had zero duration. The report body now accepts an optionalstartedAt(extended ISO 8601 timestamp with timezone —Zor±HH:MM— e.g.2026-10-03T04:20:58Zor Pythonisoformat...+00:00; basic-form offsets such as+0000are outside the accepted grammar and fall back silently by design). When it parses, is not in the future, and is no more than 24h before the report, it is stored as the run's start time so the run records a real duration (finishedAt - startedAt). Missing, malformed, non-string, future, or too-old values silently fall back to the report time — never a 400. Courier will send it (misospace/courier#167).Changes
src/app/api/agents/[agentName]/tasks/report/route.ts—resolveReportedStartedAthelper with a strict ISO-8601-with-timezone gate (ISO_8601_TIMESTAMP_PATTERN, extended format only:Zor±HH:MM; basic-form+0000falls back silently by design), not-future check, and 24h age bound (MAX_STARTED_AT_AGE_MS, inclusive: exactly-24h is accepted);nowcomputed once so validation andfinishedAtshare an instant andstartedAt <= finishedAtholds by construction; idempotency payload hash uses the raw body value (reportPayloadHashtakesstartedAt?: unknown, no cast).src/app/api/agents/[agentName]/tasks/report/route.test.ts— 17 new route tests: valid stored (duration > 0), missing, malformed string, number, object, empty string, future, >24h, ~23h boundary accepted, exactly-24h accepted (fake timers, pins the inclusive bound), non-zero offset (+05:30) stored at the correct UTC instant, bare-year/date-only/locale-ambiguous rejected,+00:00offset accepted, response echo (normalized / absent), hash divergence for differingstartedAt, and identical-body retry across the 24h acceptance boundary replays asduplicate: true(not 409).AGENTS.md— documented the optionalstartedAtfield in the Report Outcomes contract as an extended ISO 8601 timestamp with timezone (Zor±HH:MM) matching the regex grammar, per the human review nit (f43bcd7); UTC (Z) normalization of the stored/echoed value; and that the echo reflects re-validation of the current request (so an idempotent duplicate replay may omitstartedAtafter the 24h acceptance window even though the original accepted run stored it).Audit-fix supersession (merge
08982fb). This branch previously cherry-pickedc506e1e(#1169,scripts.audit --include=optional) so this PR could show green CI.mainthen landed the canonical fix for the same root cause (#1166: explicit--include=prod, guarded bypackage-audit.test.ts, non-blocking dev-inclusive visibility pass in the security-audit workflow). Mergingorigin/maininto this branch conflicted onpackage.jsonandSECURITY-ACCEPTED-RISKS.md; the resolution adopts main's side verbatim in every hunk, so this PR's diff vsmainforpackage.json,SECURITY-ACCEPTED-RISKS.md,.github/workflows/security-audit.yaml, andpackage-lock.jsonis now empty — the cherry-picked audit change is fully superseded and this PR again touches no dependency/audit-gate files. The only audit-adjacent remnant is the one-line CAUTION comment this branch added to.npmrc(main didn't touch the file), reworded at the merge to reference the canonical--include=prod/ #1166 so it can't point at a flag that no longer exists in the script.Invariants this change keeps (and every other path enforcing the same rule)
AgentRun.startedAt <= finishedAt; no negative/absurd durations from worker input. Kept here by the not-future + 24h checks against a singlenowused for both fields. Other AgentRun writers:src/app/api/agents/[agentName]/heartbeat/route.tsandsrc/lib/groomer/run.tsstamp server-side pairs and are untouched; the legacyPOST /api/agent-runs(src/app/api/agent-runs/route.ts:83) still ingests caller-suppliedstartedAtunvalidated — unchanged by this PR, and it is not the canonical worker path (AGENTS.md designatestasks/report).startedAtnever 400s — lenient fallback only.raw.startedAtis deliberately excluded from thestringFieldsvalidation loop; every rejection path returnsundefined→now. Kept by the fallback tests (number/object/empty/future/too-old/date-only/basic-form-offset all expect 200).startedAtacceptance is time-relative, hashing the validated value would flip an identical-body retry's hash across the 24h boundary and cause a spurious 409; the hash therefore uses the raw body value (reportPayloadHash({ ...report, startedAt: raw.startedAt })). Pinned by the fake-timer retry-stability test. The duplicate branch'sreport.startedAtecho re-validates the retry request (documented in AGENTS.md); PR-fix settlement (fix(queue): settle only the issued PR-fix attempt and start head #1074/pr-fix: accept an explicit already_addressed settlement without a push #1121) and the no-re-run-of-side-effects guarantee are untouched.report.startedAtappears only when valid (normalized to UTCZ); all pre-existing route tests pass unmodified.startedAt/finishedAttoday (src/app/agents/page.tsxonly displaysstartedAt), so the change is display-safe.npm run auditgates production (+ optional/native) dependencies only andnpm cistill installs devDependencies despite a globalomit=dev. Now enforced entirely on main by the CI: Security Audit failing on the default branch (npm audit) #1166 fix that this merge adopted verbatim:scripts.auditcarries the empirically validated adjacent pair--omit=dev --include=prod, and main'spackage-audit.test.tspins the script flags, the"//".audit rationale, and the non-blocking dev-inclusive workflow step. This PR adds no audit-scoping code of its own; it keeps the rule only by not re-introducing the superseded--include=optionalvariant. Other paths enforcing the same rule:.github/actions/setup-node/action.yml(npm ci --include=dev, install-time only, untouched),Dockerfileprod-deps(npm ci --omit=dev; does not copy.npmrc, immune, untouched),.github/workflows/security-audit.yaml(consumes the script; main's version, untouched by this diff).Verification
vitestfull suite at merge head08982fb: 3806 passed / 16 skipped (includes main'spackage-audit.test.tsalongside the 17 new route tests); route suite 97 passed.npm run lintandnpm run typecheckclean (one pre-existing warning insrc/app/login/page.tsx, unrelated).08982fb: all checks green includingnpm audit(via main's CI: Security Audit failing on the default branch (npm audit) #1166 gate); independent review pass over the merged tree confirmed zero diff vs main on all audit/dependency files, no merge artifacts, and no blockers/majors in thestartedAtchange (two info findings: the extended-format-only offset grammar is documented deliberate behavior; the duplicate-replay echo nuance is now documented in AGENTS.md at23f976f).23f976f) resolved atf43bcd7(docs-only): AGENTS.md now reads "an extended ISO 8601 timestamp with timezone (Zor±HH:MM)", matchingISO_8601_TIMESTAMP_PATTERN; no code change. Branch also synced with main0b90a70at merge0c22e51(conflict:.npmrcCAUTION comment only — resolved by adopting main's canonical--include=prod/"//".audit wording verbatim, consistent withscripts.audit). Full suite atf43bcd7: 3808 passed / 16 skipped; lint + typecheck clean (pre-existinglogin/page.tsxwarning only).