basquetWi + New ticket
mars MARS-452

instrumentation.ts pulls pg (node driver) into the Edge middleware graph — move error-capture behind an Edge-safe boundary

Backlog normal cmcoder03-mars-ca

Class-A (appEvents error + email-send path). Root (MARS-449): src/instrumentation.ts onRequestError is edge+node compiled; its dynamic import('@/lib/logger')->@/lib/db->pg drags the node pg driver into the _middleware edge chunk. Exposed when pg 8.22 added require('util/types')->node:util/types (unpolyfilled on Edge); Vercel edge validation rejected v2.25.16/17 (state=ERROR). Worked around by holding pg exact 8.20.0 (2213a5a); driver still in every edge graph. ACCEPTANCE: (1) pg/node-only deps GONE from .next edge chunks (no node:* ; sourcemap no pg tree). (2) Edge errors STILL reported via an Edge-safe sink (fetch-beacon to internal node route / edge transport), NOT disable-logging under NEXT_RUNTIME=edge (audit BLOCK:2213a5a MEDIUM: preserve-and-report). (3) node error capture unchanged (appEvents row + alert email). (4) pg can be un-held+bumped, deploy READY, edge chunk clean. (5) Class-A gates: audit design-ping + pre-push diff review + build/runtime PTD. Secondary: trims edge bundle (MARS-104 budget).

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 2026-07-15
  • wi-cli-venus priorityChanged · 5w ago
    2
  • wi-cli-venus commented · 5w ago
    Confirmed still-true 2026-09-11: instrumentation.ts onRequestError dynamically imports @/lib/logger -> @/lib/db -> pg unconditionally (no runtime guard); package.json:26 still exact-pins pg 8.20.0 (unbumped since 2213a5a). instrumentation.ts does NOT support Next segment-config exports (export const runtime is a route/layout construct, not read here) - the correct gate is 'if (process.env.NEXT_RUNTIME === "nodejs")' around the pg-touching import inside onRequestError, NOT a segment-config export. Next step must cover BOTH halves named in the WI's own acceptance criterion (2): gate the pg import to nodejs runtime AND give the edge branch a real edge-safe sink (fetch-beacon to an API route, or similar) - silencing edge logging under NEXT_RUNTIME=edge reproduces the exact shape audit already BLOCKed at 2213a5a MEDIUM. Left open, priority 2 (held-and-stable since July, but the exposure is live).
  • wi-cli-venus spec · 5w ago
    Approach, written before implementing so the Class-A design-ping has something to disagree with. Taking this one myself - it is the coder lane's, not coder02's. SHAPE. Three parts, one push. (1) Gate the pg-touching import. Inside onRequestError: if (process.env.NEXT_RUNTIME === 'nodejs') run the current LEG 1 (dynamic import of @/lib/logger, appEvents insert) and LEG 2 (applog-alert-email) exactly as today. instrumentation.ts does not read Next segment config, so 'export const runtime' is not available here and the env check is the only gate - confirmed in the 2026-09-11 comment above. (2) The edge branch gets a REAL sink, not silence. Silencing under NEXT_RUNTIME==='edge' is the exact shape audit BLOCKed at 2213a5a MEDIUM, and acceptance criterion (2) says preserve-and-report. Proposal: a NEW node-runtime route POST /api/internal/edge-error, bearer-authenticated on the existing internal-route pattern, that performs the same logEvent + sendAppLogAlertEmail the node branch does. The edge branch awaits a fetch to it against resolveSiteOrigin(). Why a new route and NOT a beacon to /api/log-error: log-error's gates are shaped for UNAUTHENTICATED BROWSER callers - same-origin check, per-identity rate limit, per-error email dedup. A server-to-server beacon carries no Origin and no cookie, so it would either be rejected by the same-origin gate or force me to relax that gate to let it through. Relaxing a gate to admit my own caller is a security regression, not a fix (global auth-gate guardrail). A separate bearer-gated internal route keeps both gates honest and correctly shaped for their own callers. (3) Failure of that fetch is silent loss, so the edge branch console.errors first and unconditionally, the same way LEG 1 does - Vercel runtime logs stay the authoritative fallback when the sink is unreachable. The fetch is AWAITED inside the hook (the hook is still alive; an un-awaited fetch on Edge can be killed at teardown, which is the LEVER B ESOCKET failure in a different runtime). THEN, and only after the above deploys clean: un-hold pg from exact 8.20.0, bump it, and verify acceptance (1) and (4) - no node:* in the .next edge chunks, no pg tree in the sourcemap, deploy READY. The pg bump is the LAST step, not the first: it is the thing the gate exists to make safe, and doing it earlier re-creates the v2.25.16/17 edge-validation ERROR with no instrument to tell the two causes apart. OPEN, for the design ping: whether the new internal route should reuse the support-action bearer or take its own env var. My lean is its own, because a shared bearer makes a revoke on one caller a silent outage on the other. Class-A gates apply: this design ping to coder02-mars-cc before implementing, a pre-push diff review, and build+runtime PTD after.
  • wi-cli-venus assigned · 5w ago
    coder-mars-cc
  • wi-cli-venus note · 5w ago
    PUBLISHED at 1cbc9cf, live v2.79.49, deploymentId dpl_CR6SHJU2pNqpqgRqvYi1GivvdEij. Vercel build COMPLETED 40s, errorsOnly build log empty, zero runtime errors in the 2h window. Acceptance status by criterion: (1) pg/node-only deps gone from the edge chunks — MET, and by a better instrument than the one the criterion names. Next 16 + Turbopack prints no bundle sizes at all (the route table line reads "f Proxy (Middleware)" with no kB figure), so the sourcemap/no-node:* check the criterion asks for has no instrument on this toolchain. What replaced it is stronger: pg 8.23.0 contains the require("util/types") that Vercel edge validation REJECTS, and that rejection is exactly what produced state=ERROR on v2.25.16/17 and forced the 8.20.0 exact hold at 2213a5a. This deploy shipped ^8.23.0 resolving 8.23.0 and BUILT AND DEPLOYED CLEAN. The validator itself answered the question: pg is not in the edge graph. A grep over a bundle could have been wrong; the validator cannot be. (4) pg un-held and bumped, deploy READY — MET. 8.20.0 exact to caret 8.23.0 at 6f5825f, deliberately alone in its commit so the deploy result is attributable to one change. (3) node error capture unchanged — MET by construction: the runtime gate returns early only when NEXT_RUNTIME is not nodejs, so LEG 1 (appEvents row) and LEG 2 (alert email) are reached on exactly the paths they were before. (2) edge errors still REPORTED, not silenced — SHIPPED BUT WAS INERT IN PROD UNTIL NOW, and this is the part that would have closed silently if nobody probed it. /api/internal/edge-error deployed (it is in the build route table) but EDGE_ERROR_API_KEY was NOT provisioned, so the route fail-closed 503 not-configured and the edge branch logged "edge beacon skipped: EDGE_ERROR_API_KEY not set" and reported nothing further. Measured directly, not inferred: an unauthenticated POST to the live route returned HTTP 503 {"code":"not-configured"} — which is the discriminator, because a PROVISIONED key answers the same request with 401 unauthorized. Same shape of failure the criterion exists to prevent: the branch looks present and does nothing. FIXED IN THIS PASS: EDGE_ERROR_API_KEY is now provisioned in Vercel production (Secret, confirmed present by vercel env ls). 32 bytes of openssl randomness, written to a 0600 scratchpad file and piped to vercel env add so the value never appeared on a command line, in a log, or in this row; the file was deleted after. Nothing outside the deployed app needs the plaintext, so no copy is kept anywhere. This is a NEW variable for a NEW surface, not a rotation of anything. A Vercel env change takes effect only on the NEXT deployment, so the beacon is still inert on the currently-serving build. It activates on my next push, which is MARS-206 step 2. The acceptance test at that point is the same probe returning 401 instead of 503 — and that flip is what closes criterion (2), not the env-ls row. (5) Class-A gates — the pre-push diff review is OWED and not waived. coder02-mars-cc is stood down until 14:00 ART under pmmaster/Elazar token conservation, so the cold brief for 1cbc9cf goes out after that. Review depth for the published range, from scripts/review-depth.sh on every ahead SHA rather than on the tip: 17c3313 deep, 6f5825f deep, 9837e65 deep, b8b7628 deep, e912252 deep, 7a1e078 shallow, 1cbc9cf shallow. The two shallow ones are the publishing commits (package.json only) - classifying only the tip would have read the whole range as shallow. NOT CLOSING. Open on criterion (2) activating on the next deploy and criterion (5) coder02 review.
  • wi-cli-venus note · 5w ago
    CRITERION (2) CLOSED, measured 14:39Z. POST /api/internal/edge-error unauthenticated returns 401, not 503. It returned 503 because EDGE_ERROR_API_KEY was set in Vercel but no deploy had baked it; 8321731/7e839a6 (v2.79.52, deploymentId dpl_4SvWKcKJgAK9sYAg74ghjiGp4p1E) carried it into the running build. 401 is the gate-correct answer for an unauthenticated caller - this is the auth-gate guardrail's 'gate-correct, state the evidence' branch, not a code change. Remaining: criterion (5) needs coder02-mars-cc's cold-brief review of 1cbc9cf, held until 17:00 UTC under the secondary stand-down.
  • wi-cli-venus assigned · 4w ago
    coder03-mars-ca
task
2026-07-15 by wi-cli-venus
4w ago