Skip to content

fix(gateway): one gate object-authorizes every read of a job's records (F3) - #403

Draft
LamaSu wants to merge 14 commits into
fix/legacy-settlement-truthfrom
fix/job-read-family-auth
Draft

LamaSu wants to merge 14 commits into
fix/legacy-settlement-truthfrom
fix/job-read-family-auth

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Summary

This is readmodels F3. The gateway asked for it in #2831: align the /api/jobs/:id read family with #353's predicate through one shared helper, not copies.

Before: any API key could read any job's record, status, evidence, drift alerts and settlement. Only GET /api/jobs/:jobId/execution checked the caller.

Now: one gate, readmodels/job-read-gate.ts, runs first on every read of a job's records. It reads the job row, applies TENANT_ENFORCE, and authorizes the caller with authorizeJobRead from #353.

  • An admin (X-Admin-Key), the job's kernel operator (case-insensitive) or its recorded buyer reads the job.
  • An anonymous caller gets 401.
  • Anyone else gets the route's own 404 for a job that does not exist. The bytes are identical once the id is swapped, so the answer reveals nothing about whether the job exists.
  • If the record cannot be read, the answer is 503. Nothing falls back.
Route Before Now
GET /api/jobs/:jobId any key gated (JOB_NOT_FOUND 404)
GET /api/jobs/:jobId/execution gated (#353) the same gate, one implementation
GET /api/jobs/:jobId/status any key gated
GET /api/jobs/:jobId/settlement any key gated (through the legacy settlement loader, #382)
GET /api/jobs/:jobId/evidence any key gated
GET /api/jobs/:jobId/drift-alerts any key gated
GET /api/settlement/:jobId any key gated (SETTLEMENT_NOT_FOUND 404)
GET /api/evidence/:jobId any key the job-id form is gated. The hash form stays content-addressed, because the oracle, which is not a party to the job, fetches by committed hash.

Stacked on #382 (fix/legacy-settlement-truth), which is stacked on #353. The diff is F3 alone. The CI suites run only on PRs with base master, so the counts below are my local runs.

Tests

  • readmodels/job-read-gate.test.ts (42 tests) runs every route in the family on a real store:
    • anonymous gets 401;
    • a stranger gets the byte-identical missing-job answer;
    • the operator, the buyer and an admin can read;
    • a wrong admin key is treated as a stranger;
    • under TENANT_ENFORCE, another tenant's job is 404;
    • a failed row read is 503;
    • the hash form is unaffected.
  • Existing tests: route tests that were not about authorization now read as a party through __tests__/helpers/job-read-party.ts, the seeded kernel operator standing in for the API gate. The facade-mocked compliance wiring test mocks the gate open.
  • Results:
    • Gateway 3107 passed / 6 skipped / 0 failed.
    • Mutation check: 8/8 red: no party check, no tenant check, 403 instead of the 404, anonymous treated as a stranger, and each of /api/jobs/:id, drift-alerts, evidence-by-job and the legacy loader left ungated.
  • Secret scan: the only hits are the two known false positives already on master (a public token address and the CLAUDE.md example key).

Found while doing this (gateway's routes, reported on the bus)

PATCH /api/jobs/:jobId/status authorizes the caller by kernel.operatorId and job.submittedBy, and neither field exists:

  • kernels store operatorAddress;
  • jobs have no submittedBy.

So every authenticated PATCH is 403, and a request with no caller (possible only where the API gate is not in front) skips the check entirely.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A2ZvsqsAb7jC7AC8Viisqn

…s (F3)

Gateway #2831: align the /api/jobs/:id read family with #353's predicate through
one shared helper, not copies. Before, any API key read any job's record,
status, evidence, drift alerts and settlement; only /execution checked the caller.

readmodels/job-read-gate.ts gateJobRead reads the job row, applies
TENANT_ENFORCE, and authorizes with authorizeJobRead: an admin (X-Admin-Key), the
kernel operator, or the recorded buyer. refuseJobRead answers 401 for an anonymous
caller, 503 when the record cannot be read, and otherwise the ROUTE'S OWN 404 for a
missing job, so 'not yours' and 'no such job' are byte-identical.

Gated: GET /api/jobs/:jobId, /execution (now through the gate), /status,
/settlement, /evidence and /drift-alerts; GET /api/settlement/:jobId (via the
legacy settlement loader); and the job-id form of GET /api/evidence/:jobId. The
hash form stays content-addressed for the oracle.

agent: pcc-readmodels (c255d7dc)
readmodels/job-read-gate.test.ts (42) covers all 8 reads: anonymous 401; a
stranger gets the byte-identical answer for a missing job; the kernel operator
(case-insensitive), the recorded buyer and an admin read; a wrong admin key is a
stranger; another tenant's job is 404 under TENANT_ENFORCE; a failed row read is
503; the evidence-by-hash form stays content-addressed.

Existing route tests that were not about authorization now read as a party through
helpers/job-read-party.ts (the seeded kernel operator, a stand-in for the API gate).
The facade-mocked compliance wiring test mocks the gate open.

Gateway 3107 passed / 6 skipped / 0 failed. Mutation check: 8/8 killed.

agent: pcc-readmodels (c255d7dc)
CLAUDE.md and docs/AGENT_INTEGRATION.md state the rule for the job read family:
an admin, the kernel operator or the recorded buyer; a 404 identical to a missing
job for anyone else; 401 without auth.

agent: pcc-readmodels (c255d7dc)
Brings #313 @8f946499 and #353's evidence fix (read through the job, not
the never-written evidence_bundles.tenant_id). No conflicts; the gated
execution route drops its now-unused tenant variable, since gateJobRead
already refuses a job outside the caller's tenant.

agent: pcc-readmodels (c255d7dc)
…#403

#353's review-r3 fixes change the predicate this family's gate is built on, so the
merge adapts the gate. The merged tree does not build without it.

- gateJobRead: identity first (precheckJobRead). No credential is 401 and a credential
  without a PROVEN wallet (WP-A's req.provenWallet, SIWE) is 403 identity_unverified,
  both before the job row is read. Then the job, TENANT_ENFORCE, and
  authorizeJobRead(job, provenWallet): kernel operator or recorded buyer, compared as
  addresses. An operatorId or email is never trusted as an identity.
- refuseJobRead and the two legacy settlement routes answer 401 and 403 with the shared
  JOB_READ_REFUSAL bodies, the same for every job id.
- Conflict in routes/jobs.ts: the execution route keeps #403's gate call.
- Tests: the stand-in gate sets a proven wallet for a wallet principal
  (helpers/job-read-party provenWalletFor), the F3 buyer is a wallet, and every route in
  the family pins the new 403 for a key that claims the operator's or buyer's id without
  proof, identical for a missing job.

Effect: until WP-A (#326) merges, no caller has a proven wallet, so the whole job read
family is admin-only (fail closed). After it, email-provisioned and legacy keys cannot
read job records. That is the operator's call (decision posted with the #353 triage).

Tests: gateway 3131/6/0, dashboard 258; tsc clean. Gate mutation check: 5/5 killed.

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Sep 29, 2026
…ntity rule) into #441

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Sep 29, 2026
…d id is 403

After #403 took #353's review-r3 identity rule, the job read gate requires a proven
wallet (WP-A's req.provenWallet). The test's stand-in gate sets one for a wallet
principal. A key that only claims the operator's id, without proof, is pinned as 403
identity_unverified.

Tests: gateway 3144/6/0, dashboard 258; tsc clean.

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Sep 30, 2026
…ir read models (N68)

Astra r1 on #448 (610607c), CRITICAL; reproduced first. POST /api/query
answered its kernel_health, network_status, operator_stats and
find_capability intents with the stored shop_kernels and capabilities rows.
Any self-provisioned key read every kernel's exact coordinates and street
address. The four new tests failed at 610607c with the canary's exact
latitude in each answer.

Those intents now read through the kernel and capability facades, whose
populators project a site's location: coarse unless its operator opted in,
and no street address. The answer text is filled from the same rows. The
job intents are unchanged here; they belong to the job-read gate (F3, #403).

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Sep 30, 2026
…eath it (N79 round 2)

astra's pack 116b on #462 @38cf4116: DO-NOT-SHIP, with three HIGH
findings and one MEDIUM. Each was reproduced first with a failing test at
38cf411 (n79-refund-vs-settlement.test.ts):
- F1 HIGH: a repeated failure write while /complete is in flight
  refunded ("expected 'refunded' to be 'skipped'");
- F2 HIGH: cancelling one job refunded an escrow another job shares;
- F3 HIGH: a refund landed while SettlementService.releaseMilestone
  awaited the chain ("expected 'refund_pending' to be 'skipped'");
- F4 MEDIUM: a completion that failed after a failure write left the
  failed job's escrow funded.
Two more were reproduced the same way:
- the keeper, which sweeps a findAll() snapshot across awaits, drove an
  escrow given back mid-sweep (astra's Q2 note);
- the raw chain release route let a refund land while it awaited the
  chain (the F3 class, found by extending the audit).

The fix is one mechanism: DURABLE SETTLEMENT OWNERSHIP on the escrow row.
- A settlement takes its escrow with a compare-and-set from
  funded/active to `completing` (already in @pcc/spec's vocabulary),
  synchronously at its claim: /complete (same stretch as the job claim),
  resume-settlement, SettlementService.releaseMilestone (before the
  chain await) and the raw release route (by contract address).
- A refund never lands on a `completing` escrow, however often the
  job's mutable status is rewritten (F1, F3, raw route).
- A settlement that gives up BEFORE recording evidence hands the escrow
  back (prior status). If the job ended meanwhile, the escrow goes to the
  payer then, in one transaction (F4). After evidence exists, the
  settlement keeps it, since resume-settlement continues it.
- A release that went through records its milestone `released`, and the
  escrow `completed` once every milestone is.
- The refund gives back a whole escrow only if no other job references
  it; a shared escrow is skipped as `escrow_shared` (F2).
- The keeper re-reads the row right before each drive.

Tests: the six reproductions, plus the gaps astra named:
- a failure on the LAST write rolls back every milestone and the status;
- a failure after evidence keeps ownership;
- resume takes a legacy row (the write repeated, as in F1);
- a partial release hands the escrow back;
- failed releases (service and raw route) hand it back.
All 40 mutants (both rounds) are killed. The gateway suite passes: 3053
passed, 6 skipped (six threads; the Spark is at load 37, where two /complete
tests time out at 5 s and pass alone). tsc is clean. It merges cleanly with
#326, #385, #403, #424, #441, #445, #450, #451 and #459.
LamaSu added a commit that referenced this pull request Sep 30, 2026
…party (F1)

The previous commit gates both legacy settlement routes, so the older route
tests in paid-job-flow.test.ts and settlement.test.ts, which read them with
no identity, got 401: 9 failed in the full suite at cec7fce.

They now read as the seeded kernel-nyc operator's proven wallet, through
the same stand-in gate helper #403 uses (helpers/job-read-party.ts,
byte-identical), so the stack merges cleanly.

agent: pcc-readmodels (c255d7dc)
…job-read-family-auth

#382's round-1 fixes come forward:
- milestones[].forThisJob from the attributed milestone, plus association;
- the corrected route comments;
- the job-party test helper. It was already byte-identical here.

Conflicts resolved to this branch's family gate: gateJobRead and its
unauthenticated, identity_unverified, not_found and unavailable kinds
replace #382's inline precheck and authorize. Both implement #353's rule,
and the gate is the shared one. The legacy-settlement integration tests
keep #382's explicit stand-in gate (identity only from headers), so its
401 and 403 tests hold. Their reads name the kernel-nyc operator
explicitly.

agent: pcc-readmodels (c255d7dc)
…read (F3)

Astra round 1 on #403, CRITICAL 1. Reproduced first: 18 tests fail at
74225e1. GET /api/jobs listed every job to any caller, because the gate
covered single-job reads only.

- jobsReadableBy (readmodels/job-execution.ts) gives the jobs a proven
  wallet may read. The rule is authorizeJobRead's: the wallet operates
  the job's kernel, or is the buyer recorded by the job's single
  negotiation session. It uses one read of the kernels and one of the
  sessions.
- jobReadScopeOf (job-read-gate.ts):
  - refuses as the gate does (401, 403, 503);
  - an admin reads every job, within its tenant under TENANT_ENFORCE;
  - a proven wallet reads jobsReadableBy's jobs;
  - a tenant-less job matches only a tenant-less caller, as in the gate.
- jobRecordFilterOf serves routes that mix records of many jobs. A record
  naming a job is kept only when that job is readable. A record naming
  no job is kept.
- gateJobRecordRead gates a record found by its own id (an evidence
  bundle) on its job, checking identity first.
- refuseJobRead also takes a scope's refusal.
- GET /api/jobs filters by the scope before counting and paging
  (JobFilters.jobIds).
- Timing (MEDIUM): a missing job, another tenant's job and a stranger's
  job now make the same kernel and session reads.

The existing /api/jobs list test reads as the seeded operator's proven
wallet.

agent: pcc-readmodels (c255d7dc)
…n the job read gate (F3)

Astra round 1 on #403, CRITICAL 1: the gate did not cover the whole
job-read surface. Reproduced first.

- /api/telemetry/pipeline/:jobId: the gate. A job the caller may not read
  gets the empty timeline a missing job gets.
- /api/telemetry/active and /jobs: the scope.
- /api/telemetry/logs:
  - with a jobId, the gate (no lines for an unreadable job);
  - without one, the record filter;
  - the limit applies after filtering.
- /api/telemetry/logs/stream: the history and the live fan-out go
  through the caller's record filter. The filter is fixed at connect
  time, so it fails closed.
- /api/batches/by-job/:jobId: the gate. An unreadable job gets the empty
  list a missing job gets.
- /api/sensors/readings/:channel: the gate with a jobId, otherwise the
  record filter.
- /api/compliance/evidence/:bundleId and /tier-compliance:
  gateJobRecordRead on the bundle's job. An unreadable one gets the
  missing-bundle 404.
- /api/query: the job_status and job_history intents answer only
  readable jobs.
- /api/print-and-mail/:jobId: the identity precheck, then admin only.
  Its :jobId is a courier job in the job-offers store, whose poster and
  driver ids are self-declared, so no party rule can be proven yet. This
  goes to gateway with carrier/Lob (CRITICAL 3).

Existing tests: the compliance-routes gate mock gains gateJobRecordRead,
and print-and-mail reads as an admin.

agent: pcc-readmodels (c255d7dc)
…WE session proves its wallet (F3)

Astra round 1 on #403, CRITICAL 2. Reproduced first.
/sse/stream/job/:jobId had its own ownership check, off by default, which
trusted an API key's self-declared operatorId.

- After SSE auth, the route runs gateJobRead:
  - 401 and 403 as the gate refuses;
  - a job the caller may not read gets the missing-job 404.
- resolveSSEAuth sets req.provenWallet from a SIWE session (cookie,
  bearer or ?token=), lowercased, as WP-A does for HTTP. An API key sets
  none.
- The unused isJobOwnershipCheckEnabled/checkJobOwnership path is removed.

agent: pcc-readmodels (c255d7dc)
…a missing job (F3)

f3-job-read-surface.test.ts has 21 tests. The 18 written first all fail
at 74225e1. They cover:
- no credential (401) and no proven wallet (403) on 8 routes;
- a stranger reads exactly what a missing job gives, on each gated route;
- the compliance bundle routes, and /api/query's job intents;
- SSE: anonymous gets 401 and a stranger 404, before any event;
- timing: a stranger's job and a missing job make the same reads.

Three were added during the fix:
- TENANT_ENFORCE: a job moved to another tenant leaves the party's list,
  and its /execution returns 404;
- the live log stream, over a real socket: a stranger and an anonymous
  caller get no line of another job, and the party gets its own;
- the party of a job still receives that job's events on the per-job
  SSE stream.

The print-and-mail case follows the admin-only design: a stranger and a
party both get the missing-job 404, and an admin gets 200.

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Oct 1, 2026
#403 answered its round-1 review in 4988a19. Among its changes, the
compliance bundle routes now run gateJobRecordRead on the bundle's job:
identity first, then the job read rule. That closes this PR's r1b
CRITICAL: GET /api/compliance/evidence/:bundleId returned eventCount
for another tenant's bundle with no object authorization. #441 stacks
on #403. The merge is clean.

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Oct 1, 2026
…Count (#441 r1b CRITICAL)

Cross-family review r1b of #441, CRITICAL. GET
/api/compliance/evidence/:bundleId returned the bundle's eventCount
(this PR loads the events for it) with no object authorization. The
merge of #403 (b6b688f) gates the route with gateJobRecordRead, on
the bundle's job, identity first. The surface test now also asserts
that the job's party reads eventCount and a stranger's answer does
not contain it.

agent: pcc-readmodels (c255d7dc)
LamaSu added 3 commits October 2, 2026 19:12
…ches and nested bindings (F3 r3)

Cross-family review r2 of #403 (rm-f3-403-r2-4988a191, DO-NOT-SHIP)
found four bypasses. f3-r3-bypasses.test.ts reproduced all four at
4988a19 (11 failed, 2 passed). Each is fixed here:

- CRITICAL: the kernel, device and batch SSE streams only called
  resolveSSEAuth, which passes anonymous callers when SSE_AUTH_REQUIRED
  is unset. They carry job-bound sensor readings. They now take the
  kernel's read rule (gateKernelRead): an admin, or the kernel's
  operator with a proven wallet. No credential gets 401, an unproven
  one 403, and anyone else the 404 an unknown kernel, device or batch
  gets, before any subscription.
- CRITICAL: GET /api/batches and /api/batches/:batchId returned every
  batch, and its slots name each sample's job and buyer. The list is
  now the operated kernels' batches (all of them for an admin). The
  detail goes to an admin or the operator; anyone else gets the
  missing batch's answer. By job, a buyer sees only its own job's
  slots of a shared batch.
- HIGH: jobRecordFilterOf read only a top-level jobId. recordBindingsOf
  now reads job and kernel bindings at any depth, in each key spelling.
  A malformed binding fails closed, and a record that names neither is
  an admin's, since its text can name any job. The live log stream uses
  the same rule. Anonymous callers get 401 on the mixed-record routes.
- MEDIUM: the authorization reads were keyed by the requested job.
  jobReaderOf now reads only by the caller's wallet: the kernels it
  operates, and the jobs whose single session names it. A refusal
  costs the same reads whether the job exists or not. Only the
  requested row's own lookup is keyed by the request.

Also (astra r2 on #382, MEDIUM): both legacy settlement routes now have
a test pinning the generic 503 when the job read or the authorization
read throws, for a party, a stranger and a missing job alike.

agent: pcc-readmodels (c255d7dc)
…nd batch streams (F3 r3)

CLAUDE.md's read-access paragraph, the SSE tables (in CLAUDE.md and
docs/AGENT_INTEGRATION.md) and AGENT-SKILLS.md's rows now say who may
read a kernel's batches and its streams, and how log lines and sensor
readings are filtered per record.

agent: pcc-readmodels (c255d7dc)
…the caller may read (F3 r3)

Found while fixing round 3. Reproduced at b170cfa: anonymous got 200
from GET /api/kernels/kernel-nyc/jobs, and the public kernel detail
embedded job-001 (f3-r3-kernels-repro-b170cfaf.log).

- GET /api/kernels/:kernelId/jobs now checks identity first, before the
  jobs are read (jobReadScopeOf): no credential is 401 and an unproven
  one 403. A proven wallet gets the kernel's jobs it may read; an admin
  gets all of them.
- GET /api/kernels/:kernelId stays public. Its recentJobs hold only the
  jobs the caller may read. recentJobsScope ("all",
  "readable_by_caller" or "unavailable") says what the list covers, so
  an empty list never reads as "no jobs".
- The other getById callers (lob, carrier, compose) read only
  operatorAddress.
- The API tables in CLAUDE.md and docs/AGENT_INTEGRATION.md say so.

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Oct 3, 2026
#441 is stacked on #403. This brings in #403's round-3 fixes. The one
conflict was CLAUDE.md's read-access paragraph. It is resolved to
#403's new text, keeping #441's /evidence/provenance route.

tsc exits 0, and the readmodels tests pass (6 files, 216 tests).

agent: pcc-readmodels (c255d7dc)
LamaSu added a commit that referenced this pull request Oct 3, 2026
… read only evidence the caller may read (PX-7 r3)

Cross-family review r2 of #441 (rm-px7-441-r2-e411a9b3, DO-NOT-SHIP),
CRITICAL: the capability compliance report named each recent bundle's
job and hash to anyone, and GET /api/evidence/:hash served the whole
canonical envelope (its events' sources and payloads) to anyone who had
the hash. px7-r3-evidence-bypass.test.ts reproduced it at d2ffa1d
(#441 @e411a9b3 plus the #403 r3 merge): 5 failed of 5.

- GET /api/capabilities/:capabilityId/compliance checks identity first:
  401 without a credential, 403 without a proven wallet. Then the facade
  computes the whole report only from evidence the caller may read
  (ComplianceReportAccess): all of the kernel's for an admin without a
  tenant, or the kernel's operator; anyone else gets the bundles of its
  own jobs. evidenceScope and bundlesConsidered say which. Events,
  capture verdicts, drift and the evidence list all come from those
  bundles only.
- GET /api/evidence/:hash serves the envelope only to:
  - the settlement oracle's verifier read key (X-Verifier-Key, compared
    constant-time with PCC_VERIFIER_READ_KEY; at least 32 characters;
    unset grants nothing; it is not an identity elsewhere);
  - an admin;
  - a party to the bundle's job (gateJobRecordRead, identity first,
    then the bundle lookup).
  Anyone else falls through to the job-id form, so it gets exactly what
  an unknown hash gets.
- compliance-routes.test.ts mocks the gate open; its mock gains the
  scope helpers, and the delegation test expects the access rule.
- The dashboard's ComplianceReportDTO mirror gets the two fields. The
  docs (CLAUDE.md, docs/AGENT_INTEGRATION.md) give the rule and the new
  env var.

Until the oracle sends X-Verifier-Key and the gateway has
PCC_VERIFIER_READ_KEY, the oracle's API-key fetch gets 403 and fails
closed. Provisioning that key is the operator's decision.

agent: pcc-readmodels (c255d7dc)
… batches show only the opportunity, filters judge records as sent (F3 r4)

Cross-family review r3 of #403 (rm-f3-403-r3-f004b709, DO-NOT-SHIP).
f3-r4-bypasses.test.ts at f004b70: 7 of 11 failed. The 3 positive
cases and MEDIUM 6 passed.

- CRITICAL 1 (kernel records ignored TENANT_ENFORCE): each job-bound
  part of a kernel's record now follows the tenant-aware job rule
  (jobReadScopeOf):
  - jobPartScopeOf projects batch slots in the list, the detail and
    by-job;
  - streamEventFilterOf drops any job, kernel, device or batch stream
    event, and any batch-detail event, that names a job the caller may
    not read.
  An admin without a tenant keeps everything.
- CRITICAL 2 (shared batches returned every claim publicly): anyone now
  gets sharedBatchFace, the opportunity with claimedSlotCount and no
  claims. Only an admin without a tenant, or the kernel's operator,
  sees the claims, on /open, /:batchId and the claimedBy list of
  /availability.
- HIGH 3 (?jobId= reads skipped the nested filter): logs, sensor
  readings and the pipeline timeline now apply the gate and the record
  filter together. The pipeline timeline is the same class of path;
  it reproduced against f004b70's telemetry.ts.
- HIGH 4 (the filter saw the live object, not what was sent): asSent
  and keepAsSent make every filter judge the record's JSON text parsed
  back, and every route sends that form. This covers REST logs and
  sensor readings, the log stream's history and live fan-out, all four
  SSE topics, and batch-detail events. A toJSON or a getter can no
  longer make the inspected and the sent records differ.
- MEDIUM 5 (record-by-id timing): gateJobRecordRead now refuses a
  non-party from the record's own job and kernel, using the caller's
  wallet-keyed reader, before any job row is read. Parties and admins
  then go through gateJobRead. A stranger's refusal makes the same
  reads whether the bundle exists or not.
- MEDIUM 6 (unmatched SSE paths leak slots): NOT REPRODUCED, so no code
  change. topicSSE is registered encapsulated (server.ts:844), so its
  onRequest hook runs only for its own matched routes. A pin test
  shows 20 unmatched requests followed by a valid stream: 200.

agent: pcc-readmodels (c255d7dc)

This branch has not been deployed

No deployments
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