Tri-product root-cause audit — 2026-06-03

43-agent parallel audit across CareerVector / JobCache / sporewright + admin UIs + integration, adversarially verified. 6 of 35 findings confirmed real.

Fixed (committed)

  1. CV mirror write-protection — the bug CLASS. Five mirror-aware WRITE handlers resolved a read-only mirror to its source workspace and wrote to it without the readOnly guard: POST /workspaces/:id/agent-presence (presence rows) and POST /llm/{evaluate,extract,salary} (stage-attempt ledger + LLM token spend on the source's BYOK keys). Added if (resolved.readOnly) return 403 read_only_mirror to all five. /llm/tailor is NOT affected — it never mirror-resolves (a mirror_id 404s instead of mapping to a source).
  2. JC critical — stale observation clobbers task state. submitObservation's task UPDATE ran unconditionally after the lease release; a stale observation whose lease was expired+reclaimed in the TOCTOU window (no transaction) could overwrite a legitimate result. Gated the task transition on leaseRelease.rowsAffected > 0, else reject lease-not-active.
  3. JC config fail-fast. jobcache/shared/src/config.ts now throws when the command-drain is configured (CAREERVECTOR_WEB_URL / drain interval) but JOBCACHE_CONTROL_TOKEN is empty — an empty token was invisible to Render's /healthz probe and silently 401'd every drain. Mirrors the DATABASE_URL precedent.

No regressions: @cv/api 17/17, jobcache-shared 31/31, jobcache-interface 71/71; all typechecks clean.

NEEDS YOUR DECISION (not touched — deliberate + deployment-sensitive)

  1. JobCache Ops API + MCP auth is STRIPPED. Commit a1948023 ("auth-buried: delete jobcache/ops/api gate, every route public") deleted registerAdminGate + lib/auth.ts; the ops MCP is "Auth-less (stripped 2026-05-18); all tools open". api.ops.jobcache.corbet.ch + mcp.ops.jobcache.corbet.ch are internet-facing (Pulumi-wired, jobcache/infra/cloudflare.ts) with NO auth and NO compensating CF-Access policy. Live exposure is MEDIUM, not critical: the truly-destructive actions (source pause/resume, scrape-trigger) forward to the bearer-gated downstream control API and are blocked tokenless; what IS open = unauth audit-log writes, transient badge/state overrides, and read-side info disclosure (cockroach/corpus/ devices/audit). This looks DELIBERATE (commit message + dated AUTH STRIPPED comments), so I did NOT restore it autonomously — a naive restore 401s the cockpit and MCP unless ADMIN_TOKEN is set + the UI cookie + MCP bearer forwarding re-armed. Decide: re-arm the gate (and wire the token), or confirm it's intentionally open.

Skipped (verified non-issue / fix-as-written would be harmful)

  1. HYDRATABLE_FIELDS (CV lib/server/src/jobcache-lookup.ts) vs WORKSPACE_SAFE_ATTRIBUTES (JC jobcache/shared/src/fact-tree.ts) duplication: the two 9-entry allowlists sit on OPPOSITE sides of an HTTP trust boundary (CV = ingress allowlist on the received payload; JC = egress allowlist before serializing). Intentionally independent. The finder's fix (import the shared constant into the CF Worker bundle) would pull postgres/aws-sdk/pino into the 1 MB-limited Worker AND dissolve the security boundary. Real defect is minor and fail-safe; left as-is.

Follow-ups (safe, deferred)

  • Enforcement hardening for the mirror class (stops recurrence): make assertWritable(resolved) real — lib/server/src/mirror.ts's own doc comment tells callers to use it but it does not exist — and make ResolvedWorkspace a SourceWorkspace | ReadOnlyWorkspace discriminated union so the compiler forces a readOnly check before sourceId is usable on a write path.
  • Regression tests: 403-on-mirror_id for the 5 guarded handlers; a submitObservation stale-lease reject test (no UPDATE tasks when the lease is no longer active).
  • submitObservation transactional wrap: the rowsAffected gate is the load-bearing fix; the residual two-statement race wants the lease load + lease+task UPDATEs wrapped in a transaction the way leaseTask already does.

kind: journal nodes:

  • tri-product-root-cause-audit-2026-06-03 summary: "Working record of the CareerVector, JobCache, and shared-infrastructure root-cause audit on 2026-06-03." links: event:
    • tri-product-audit-event-2026-06-03 anomaly:
    • tri-product-root-cause

Source: wiki/content/working-memory/audit-2026-06-03-rootcause.md