Skip to content

fix(gateway): N122 telemetry stats, audit and emit follow the job read rule - #580

Open
LamaSu wants to merge 5 commits into
fix/job-read-family-authfrom
fix/n122-telemetry-operator-reads
Open

LamaSu wants to merge 5 commits into
fix/job-read-family-authfrom
fix/n122-telemetry-operator-reads

Conversation

@LamaSu

@LamaSu LamaSu commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Board N122 (bus #6347, #6351; the steward's #6488), stacked on #403 (F3's job read gate). F3 already gates the per-job telemetry reads (the pipeline timeline, active, jobs, logs and the stream). This PR closes the rest of the telemetry read family.

Route Before Now
GET /api/telemetry/stats counted every job's pipeline, for anyone counts only the caller's job scope (jobReadScopeOf). Anonymous 401, unproven 403, a proven party only its jobs, the admin all.
GET /api/telemetry/audit the gateway-wide audit log, for anyone admin only. Its records name actors, not jobs (F3's mixed-record rule).
POST /api/telemetry/emit any API key wrote into any job's timeline (the admin without a key was refused) the job read gate: the admin or a PROVEN party of the job. A non-party gets the 404 an unknown job gets.
getTimeline() returned the stored array returns a deep copy (the class of #538 r3's stored-entries MEDIUM)

Reproduced first, at #403's head plus each new test: stats and audit (5 of 7 failed), then emit and timeline (5 of 12 failed). Now 12 of 12.

The operator policy and approvals reads, which #6488 also folded into N122, need auth/kernel-authority.ts (#575), so they are in the N31 stack (N31c).

Changed flows:

  • the dashboard TelemetryPage's stats need a proven wallet or the admin;
  • scripts/ot2-agent.py's telemetry emit needs the admin key until WP-A;
  • the demo pcc-telemetry-live.html and scripts/real-e2e-verbose.ts audit reads need the admin key.

Verify at 8a9819e6: tsc 0, 255 of 256 files pass. The one failure was telemetry-audit (it read without the admin key), fixed in ae201a82 (test-only).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sbd5dpwvmRsJqdff9zvNW6

LamaSu added 4 commits October 3, 2026 20:05
…udit log is the admin's

Board N122 (bus #6347, #6351). On #403's head (2de4298), with F3 already gating the per-job
telemetry reads:
- GET /api/telemetry/stats counted every job's pipeline for any caller. It now takes the
  caller's job read scope (jobReadScopeOf, as /jobs and /active do): 401 anonymous, 403
  unproven, a proven party counts only its jobs (events per minute included), the admin all.
- GET /api/telemetry/audit returned the gateway-wide audit log to any caller. Its records name
  actors, not jobs, so it is the admin's (F3's mixed-record rule).
Reproduced at 2de4298 with n122-telemetry-reads.test.ts: 5 of 7 failed (stats 200 to anonymous
with every job counted; audit 200 to anonymous). Now 7 of 7.

agent: pcc-readmodels (c255d7dc)
…f the job; timeline reads are copies

The steward's #6488 (telemetry EMIT: proven wallet or admin only). Reproduced at 35bf12c with
n122-telemetry-reads (5 failed; the harness sets apiKeyId as apiGate does for a key):
- any API key emitted pipeline events into any job (a claimed key: 200; a proven stranger: 200),
  while the admin without a key was refused (403) and anonymous got 403, not 401;
- a caller that changed a getTimeline() result changed the stored timeline (the class of #538
  r3's stored-entries MEDIUM, noted by its implementer).
Now:
- POST /api/telemetry/emit runs the job read gate (#403): anonymous 401, an unproven key 403
  identity_unverified, a proven non-party the 404 an unknown job gets, and the admin or a
  proven party (the job's kernel operator or its buyer) emits.
- PipelineTelemetryService.getTimeline returns a deep copy.

agent: pcc-readmodels (c255d7dc)
A plain merge with no manual edit. N122 stays stacked on #403 (the job read gate).

agent: pcc-readmodels (c255d7dc)
… (N122)

The audit log is now the admin's (N122, #6488), so this suite's identity-less app sends the
admin key. Its assertions are unchanged.

agent: pcc-readmodels (c255d7dc)
… not other parties' emissions

astra r1 (rm-n122-580-r1-ae201a82, N122-1, CRITICAL): _minuteBucketsOf
anchored a caller's scoped window to the newest GLOBAL minute bucket,
which an emission on any job moves. An emission on a job the caller
cannot read therefore changed the caller's eventsPerMinute: the
existence and timing of another party's pipeline activity leaked.

The scoped window is now the last 10 minutes by the clock. The admin's
unscoped view keeps the global buckets.

Two tests reproduce astra's trace; both failed at ae201a8:
- 11 quiet minutes after the caller's event, a stranger's emission
  changes nothing (it read 1 and then 0);
- at every minute of the window and past its edge, a stranger's
  emission leaves every scoped statistic unchanged (the 11-minute case
  differed).

agent: pcc-readmodels (c255d7dc)

This branch had an error being deployed

1 failed deployment
trusted-checks — 9f25ac85 Deployed Oct 4, 2026 by LamaSu via post-verdicts #59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant