Repository navigation
Conversation
pcc.evidence-provenance/v1 is the charter's EvidenceSummary/ProvenanceDTO: what the gateway can truthfully say about a job's evidence, for ordinary users, with raw inspect pointers. The vocabulary was confirmed by the evidence lane (#3346). - integrity: event_bundle_hash is evidence integrity (the one LO-EV model /settle recomputes). gateway_envelope is only the gateway's storage integrity. no_model_reproduces is not proof of tampering. - signature: checked is false (no stored bundle signature is checked). - tierCoverage: recorded non-fabricated event types only; self-reported. - archive: not_recorded. - verification: no_verdict_recorded (no verdict is stored). agent: pcc-readmodels (c255d7dc)
…vidence counts that are real
- readmodels/evidence-provenance.ts: loads each bundle of the job with
its events and builds the DTO.
- Integrity is recomputed on read: every event hash plus the bundle
hash (event_bundle_hash), otherwise the /complete envelope
(gateway_envelope).
- Tier coverage counts non-fabricated events against
DEFAULT_TIER_REQUIREMENTS.
- Bundles are listed newest first; an unreadable store is
unavailable, never none.
- routes/evidence-provenance.ts: behind the job read family's gate
(#403). The admin, the kernel operator or the buyer can read it;
anyone else gets the missing-job 404, and an anonymous caller 401.
cache-control: no-store.
- Legacy truth (evidence facts map, item B): bundle rows carry no
events, so EvidenceSummaryDTO.eventCount was always 0 on
GET /api/jobs/:jobId/evidence, /api/compliance/evidence/:bundleId and
JobDetailDTO.evidenceBundles. The facades now attach each bundle's
events.
Tests: 13, including a real PUT /complete bundle that reproduces as
gateway_envelope (so the recomputation matches what the route writes)
and the legacy counts. Mutation check 12/12 killed. Gateway 3125/6/0.
agent: pcc-readmodels (c255d7dc)
agent: pcc-readmodels (c255d7dc)
LamaSu
added a commit
that referenced
this pull request
Sep 29, 2026
PUT /api/jobs/:jobId/complete writes the gateway's own execution_completed and the caller's evidenceEvents, of any type, under source.deviceId "gateway". No device reported them. Until now evidenceLevelOf read that stamp as a device: a gateway completion counted as device_reported, and it named "gateway" as an executing device, so a printer inspecting its own output could read as inspected_output. GATEWAY_STAMPED_DEVICE_ID is not a device attribution, so those events prove no level and name no executor. deriveContradictions is unchanged (the oracle mirrors it, J4): types decide there, and a contradiction can only refuse. Found while checking readmodels' #441 against the evidence vocabulary; #441 will take evidenceLevel from evidenceLevelOfBundle. spec 826/826, tsc clean. Mutant (drop the clause) fails both new tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ntity rule) into #441 agent: pcc-readmodels (c255d7dc)
…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)
…e placeholder signer, keeps envelope integrity apart The evidence lane, which owns the contract, checked #441 against #3346 (#3680): F1. Tier coverage counts only events a DEVICE recorded: neither fabricated nor gateway-stamped (source.deviceId "gateway"). PUT /complete stamps its own execution_completed AND the caller's body events of any type that way, so a /complete bundle claiming tier 2 read "covers" with no device event at all. The lane's probe is now a test. F2. The gateway's placeholder signature (the zero-address signer /complete writes for events it synthesized, or value "gateway-auto-sign") is no signature: signer and algorithm null. F3. Integrity is a discriminated union. recomputed_match means only the LO-EV model (event_bundle_hash); an envelope-only match is storage_envelope_match (model gateway_envelope). A surface reading only `state` never sees "recomputed" for a bundle /settle would refuse. Nits: - firstAt/lastAt are ordered by parsed time and shown as recorded; a string sort put "...00.500Z" before "...00Z". - A model that throws on a stored value (a circular value today; an integer beyond 2^53-1 after #359) is caught per bundle and reads no_model_reproduces. It no longer fails the whole read. Tests: evidence-provenance 18; gateway 3149/6/0; spec 866; dashboard 258; tsc clean. Mutation check: 7/7 killed. agent: pcc-readmodels (c255d7dc)
…no signature (PX-7) Evidence #4088 (the contract owner); reproduced first with a failing test at 5a94723. POST /api/operator/evidence stores a bundle that has no device signature as {signer: <kernelId>, algorithm: "sha256", value: "operator-relay-auto"}, and the provenance read showed it as signed by that kernel. signatureOf now treats a signature as a placeholder when any of these holds: - its value is in the gateway's own PLACEHOLDER_SIGNATURE_VALUES (services/device-evidence-settlement.ts); - its signer is the zero address; - its value carries the emitter's test_sig_ marker. These are the non-signature checks isDeviceSignedSignature applies. It does not require ed25519, so a secp256k1 kernel signature is still shown. agent: pcc-readmodels (c255d7dc)
…ence store to start PUT /complete starts the in-process evidence store (Helia by default). Alone, the file takes about 4 s. In the full gateway suite on a loaded machine these tests exceeded vitest's 5 s default: 2 timed out at 5a94723, and 1 after the placeholder fix. The block now allows 30 s per test and for the teardown that stops the store. The wait is for startup, not for an answer the tests could miss. agent: pcc-readmodels (c255d7dc)
LamaSu
added a commit
that referenced
this pull request
Sep 29, 2026
…(N79) The previous commit guarded the gateway's own settlement flow (PUT /complete, resume-settlement, the keeper). An audit of every release entry found two more that ignored a refund decision; both are now refused. Each was reproduced first, with a test that failed on 830e4ff. 1. The raw chain routes: POST /api/escrow/chain/:address/{evidence, attestation,release}/:idx, on the EAS (V2/V3) and V1 paths. They are operator/admin-scoped, but they read no refund state, so an operator could push a refund-pending escrow to a release. 24 cases failed: "expected 200 to be 409". They now answer 409 escrow_refunded and send nothing. They also fail closed with 503 when the escrow registry cannot be read. Disputes stay open, because V2 refunds only through resolveDispute; approve-release only encodes calldata for the payer's own wallet, so it is unchanged. 2. SettlementService.releaseMilestone: POST /api/settlement/release and the automatic release after evidence. It released a refund-pending escrow and then set its job "settled", the N79 negative itself (4 cases failed). It now refuses when the job's own escrow, or the escrow it is asked to release (the named address or the default, in any letter case), was given back. Its activity no longer retries that refusal. The lookup is shared: givenBackEscrow and escrowByContractAddress in escrow-refund.ts. Also: - The route tests assert the escrow rows, which are what this change writes. They no longer go through GET /api/jobs/:id/settlement: that read is readmodels', and #441 makes it authenticated. - Two import lines move one line away from #459's and #441's insertion points, so both merge cleanly with this branch. All ten new mutants are killed: each route guard alone, the status check, fail-open on registry errors, the service guard, the job and address halves of the lookup, the checksum form and the activity's retry. The gateway suite passes: 3037 passed, 6 skipped. tsc is clean.
…rd tier coverage
M3: tierCoverageOf() only excluded fabricated and gateway-stamped
("gateway") events, so an event with source: null (or no deviceId)
was neither fabricated nor gateway-stamped and still counted toward
"DEVICE recorded" tier coverage, contradicting the DTO contract's
"event types that a DEVICE reported" description.
Reproduced with a new test at 5f3c86e: a tier-1 LO-EV bundle with
the three required types, every event source: null, asserted
tierCoverage.state is not "covers" -> failed ("expected 'covers' not
to be 'covers'") before the fix.
Added deviceReported(): true only when source.deviceId is a
non-empty string other than the gateway's "gateway" stamp.
tierCoverageOf() now filters on !isFabricated && deviceReported
instead of !gatewayStamped. Updated the builder's and spec's doc
comments to document that source-less events are excluded the same
way as gateway-stamped ones. No existing fixture relied on
source-less events counting, so no fixtures changed.
Files: packages/gateway/src/readmodels/evidence-provenance.ts,
packages/spec/src/readmodels/evidence-provenance.ts,
packages/gateway/src/__tests__/readmodels/evidence-provenance.test.ts
agent: implementer-bravo for pcc-readmodels (c255d7dc)
…ature
M4: routes/setup.ts's deviceless-kernel branch (:765,:783) writes
kernelSignature: { signer: "self-attest", algorithm: "none", value:
"self-attested by kernel ... at ..." } as a gateway-created,
non-cryptographic placeholder. signatureOf() only suppressed the
zero-address signer, the two PLACEHOLDER_SIGNATURE_VALUES, and
test_sig_-prefixed values, so this shape passed through and the DTO
reported signer:"self-attest", algorithm:"none", checked:false as if
it were a real signature.
Reproduced with a new test at 5f3c86e: a bundle carrying that exact
stored shape, asserted signature equals {signer: null, algorithm:
null, checked: false} -> failed ("expected { signer: 'self-attest',
...(2) } to deeply equal { signer: null, algorithm: null, ...(1) }")
before the fix.
signatureOf() now also treats signer === "self-attest" OR algorithm
=== "none" as a placeholder (either alone is sufficient — no real
signature ever declares algorithm "none"). Added SELF_ATTEST_SIGNER /
NO_ALGORITHM constants and updated the builder's and spec's doc
comments. The same test asserts a real secp256k1 and a real ed25519
signature are still shown as stored.
Files: packages/gateway/src/readmodels/evidence-provenance.ts,
packages/spec/src/readmodels/evidence-provenance.ts,
packages/gateway/src/__tests__/readmodels/evidence-provenance.test.ts
agent: implementer-bravo for pcc-readmodels (c255d7dc)
…string M5: the DTO contract promises bundles are "Newest first", but the builder's sort compared raw storedAt strings (a.storedAt < b.storedAt ? ...). Equivalent ISO-8601 offsets can reverse actual time order under a string comparison: a "+02:00" offset string can sort ahead of a "Z" string that is chronologically later. Reproduced with two new tests at 5f3c86e: (1) stored times "2026-09-28T12:00:00+02:00" (10:00 UTC) and "2026-09-28T10:30:00Z" (30 min later) -> the code put the older "+02:00" bundle first, failing "expected [ 'b-off', 'b-utc' ] to deeply equal [ 'b-utc', 'b-off' ]"; (2) an unparseable storedAt sorted first instead of last, failing similarly. Added compareBundlesNewestFirst(): sorts by Date.parse(storedAt) descending; a storedAt that fails to parse sorts after every bundle that parses; equal (or equally unparseable) times break the tie on bundleId ascending, for a deterministic order. storedAt is still displayed exactly as recorded — only the sort key is parsed. The one other multi-bundle ordering test (unambiguous UTC timestamps) is unaffected. Files: packages/gateway/src/readmodels/evidence-provenance.ts, packages/gateway/src/__tests__/readmodels/evidence-provenance.test.ts agent: implementer-bravo for 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.
#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)
…and a blank deviceId names no device (PX-7) Cross-family review r1b of #441 (5f3c86e), MEDIUM 2, plus the owner's follow-up to MEDIUM 3. Both reproduced first with failing tests. MEDIUM 2: an LO-EV match binds only each event's {type, timestamp, source, payload} and the sorted event hashes. Yet the DTO showed the row's kernel, claimed tier, time and signature, and the event ids, beside recomputed_match. Changing kernelId, assuranceTier and an event id left the match intact (the reviewer's reproduction). Every integrity result now carries: - covers: the stored fields it vouches for; - notCovered: every other stored field. event_bundle_hash covers event.type, event.timestamp, event.source, event.payload, event.hash and bundle.bundleHash. Its notCovered lists the bundle's id, jobId, stepId, kernelId, assuranceTier, createdAt and kernelSignature, plus event.id. The /complete envelope covers all 14 stored fields (still storage integrity only). A non-match covers none. The spec exports EVIDENCE_STORED_FIELDS and INTEGRITY_COVERAGE. MEDIUM 3 follow-up: 811441e requires a device source, but a deviceId of spaces still counted as a device, and " gateway " escaped the gateway's own stamp. deviceReported now trims the id first. agent: pcc-readmodels (c255d7dc)
…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)
… 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)
#441 is stacked on #403. This brings in #403's rounds 4 and 5. - gateJobRecordRead now takes the record itself (job, kernel, tenant). The hash route's lookup returns the bundle row instead of its jobId. - The one conflict was CLAUDE.md's read-access paragraph. It is resolved to #403's text, plus #441's /evidence/provenance route and its envelope-by-hash sentence. - That sentence, here and in docs/AGENT_INTEGRATION.md, now states that the route is behind the API gate, so the oracle sends X-Verifier-Key together with its ordinary Bearer credential (astra r3 on #441, LOW). tsc exits 0, and the readmodels and compliance-routes tests pass (10 files, 258 tests). agent: pcc-readmodels (c255d7dc)
… r4) Cross-family review r3 of #441 (rm-px7-441-r3-01ed0dac), CRITICAL: the new X-Verifier-Key could reach Sentry's request context. Reproduced with the installed SDK (@sentry/node 10.45.0). Its request-data integration copies every request header into an error event, and removes only cookies and IP headers on request (core integrations/requestdata.js:5-9, 80-94). The SDK's sensitive-header list applies to span attributes only. sentry-scrub.test.ts "baseline" sends an error under a request carrying the verifier key through the plain SDK with a capture transport: the key is in the sent event. - sentry.ts: sentryOptions() sets sendDefaultPii: false, plus beforeSend and beforeSendTransaction = scrubSentryEvent. The scrub redacts every header whose name matches the SDK's own sensitive-name list (auth, token, secret, session, key, cookie, ...), the cookies, and credential-named query parameters in both query_string and the URL. initSentry uses sentryOptions(). - sentry-scrub.test.ts: with the gateway's options, none of the verifier key, admin key, bearer key, session cookie or ?token= value is sent, and the user-agent and path still are. agent: pcc-readmodels (c255d7dc)
Cross-family review r3 of #441, MEDIUM: the negative tests were narrower than the attack list. px7-r4-evidence-matrix.test.ts pins, against the real routes: - every accepted hash form: bare, 0x, sha256:, upper and mixed case, space-wrapped. Each resolves for an admin, and the 0x-stored bundle resolves from its sha256: form; - a stranger's forbidden hash answers exactly as an unknown one does (status, content-type, body with the hash masked), with the same findByHash and job reads and no event read; - the verifier header: the name in any case works; a value in another case, space-padded, or repeated is no credential; a configured key of 31 characters grants nothing, even to an exact header; - under TENANT_ENFORCE, tenant A's admin gets the unknown-hash answer for tenant B's bundle, and tenant B's admin reads it; - a bundle whose job row does not exist is an unknown hash; - reports over mixed bundles: the buyer sees only its job's bundle (bundlesConsidered 1), tenant A's admin only tenant A's, and the operator all of them (scope "all"). No source change: every case already held at the head. agent: pcc-readmodels (c255d7dc)
Cross-family review r4 of #441 (rm-px7-441-r4-3c6783bc), CRITICAL 1 and MEDIUM 2. At 3c6783b a failing request's query credentials reached the Fastify request log (req.url), the gateway's own Sentry capture (extra.url, sent whenever the SDK's Fastify hook skips the error) and every transaction's span attributes (http.query, http.url, http.target, url.path). Signature headers (payment-signature, x-hmac-signature, lob-signature) reached error events, transaction request headers and span attributes. A write request's query credential was stored in the audit log. observability-redact.ts is one redaction for the whole record: - headers by allowlist, in every shape; - span header attributes by the same allowlist; - credential-named keys; - credential parameters in every string; - JSON bodies, parsed and redacted. Where it runs: - Sentry: beforeSend, beforeSendTransaction, beforeSendSpan and beforeBreadcrumb. - The Fastify logger: a request serializer that logs the redacted URL and no headers, plus a streamWrite hook that redacts every line. - At the source: the error handler's extra.url and the audit log's url. Tests: - observability-redaction.test.ts boots the real gateway with its own Sentry and logger options (a capture transport and a buffered stream). It also pins the verifier key through the real API gate: alone it gets 401, and with a Bearer credential 200. - observability-redact.test.ts pins each mechanism. - sentry-scrub.test.ts adds transactions, breadcrumbs, signature headers and a request body. agent: pcc-readmodels (c255d7dc)
Cross-family review r4 of #441 (rm-px7-441-r4-3c6783bc), MEDIUM 3. A report over readable and unreadable bundles equals, in every derived field, the report over a dataset that holds only the readable bundles. Counting the unreadable ones would change recentEvidence, bundlesConsidered, tierCompliance, captureVerification and assuranceScore. The test passes at 3c6783b too: the report already used only readable bundles. It closes the coverage gap. agent: pcc-readmodels (c255d7dc)
…e of the request's URL The steward's guidance on #441 round 4 (post #5182): the request log's req serializer should drop every query value, not only credential-named ones, because a name list can miss one (an OAuth code, a one-time link's token). The URL in the error report's extra.url now drops them the same way. The generic redaction of every string and the audit URL keep the credential-name rule. A mutation check confirms the new test catches a serializer that redacts by name only: with the old serializer, the code parameter's value was logged. agent: pcc-readmodels (c255d7dc)
…content (N107) Cross-family review r5 of #441 (rm-px7-441-r5-179c4777), CRITICAL 2 and 3, split out as N107 (PR steward #5254). Both sinks are live on master too, and these files are byte-identical on master 7d688ce. - The security monitor sent PostHog the raw request URL, query parameter, cookie or body as an attack event's attackPayload. Every event's fingerprint also carried the raw Referer, and a path that kept its fragment. Now: - an attack event carries a summary: its type, its source, the path with no query or fragment, and the content's length (attackLength); - the Referer is reduced to its URL with no query or fragment; - the monitor's own log lines carry the path instead of the URL. - The payment gate kept each paid request's raw URL in recentPayments, and GET /api/x402/stats showed the list to any caller. It now keeps the path with no query or fragment, and only an admin (X-Admin-Key) sees the list. Everyone else gets the counts. No client reads this route. Reproduced first: n107-telemetry-sinks.test.ts failed 5 of 5 at 179c477, with every marker sent under an arbitrary name. Each fix was mutation-checked with file copies: undoing any one of the five fails its test. agent: pcc-readmodels (c255d7dc)
…e's name (#441 r6) Cross-family review r5 of #441 (rm-px7-441-r5-179c4777), CRITICAL 1, under the PR steward's rule for round 6 (#5254): no credential-name list is load-bearing. Reproduced first at 179c477: observability-r6.test.ts failed 5 of 5. The test sends a marker under arbitrary names (zq1, x_custom, code) in the query, fragment, headers, form body and JSON body, through the real gateway. - Strings, in every sink: every URL-like token keeps its path and its parameter names and drops every query and fragment value. A form-encoded string drops every value. withoutQueryValues also covers a URL with a fragment and no query. - Bodies: Sentry no longer collects request bodies, cookies or query strings. The HTTP integration reads no incoming body, and the request-data integration attaches none. As a second line, the redaction drops request.data, request.cookies and any "body" field whole. - The audit record keeps the URL with no query or fragment value. - PostHog: every event's and every person's properties go through the same redaction at the posthog-service boundary, whoever calls. - The credential-name rule stays only as a second line, for prose. - Two r4/r5 tests pinned a body's non-secret field being kept; they now pin the body being dropped whole. The r5 production-path test now composes the gateway's own Sentry integrations. Mutation checks with file copies: undoing any one of seven parts fails a test. The parts are the URL tokens, the form rule, the body drop, the at-source Sentry setting, the PostHog boundary, the audit URL and the fragment-only URL. agent: pcc-readmodels (c255d7dc)
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
…or log or payment row keeps a caller's value (N107 r2) Cross-family review r1 of #514 (rm-n107-514-r1-081b0c49, DO-NOT-SHIP). Each finding was reproduced at 081b0c4 by n107-r1.test.ts before any fix: 3 of its tests failed. - CRITICAL 1: every fingerprint sent PostHog raw header values (User-Agent, Accept-Language, Content-Type, cf-*, X-Forwarded-For, x-railway-edge), and the Referer kept its userinfo. buildFingerprint is now a closed schema of derived values: - the client is a keyed hash (per-process key), which PostHog's distinct id uses too; - the User-Agent is a class (browser, http_library, scanner and so on); - the language is its primary subtag; - the Referer is a kind (direct, same_origin, cross_origin, invalid); - the content type is an allowlisted media type, and the country an ISO code; - the edge is an enum, and the forwarding chain a hop count; - the path is the matched route's pattern. - CRITICAL 2: the monitor's logs carried User-Agent text (HONEYPOT's ua, and bot reasons). Bot reasons now name the signal only. The log lines carry the validated address, the route and the UA class. - MEDIUM 3: MPP storage, both protocols' 402 paths, and exact counter deltas had no test. n107-mpp.test.ts (a scripted MPP charge handler: a 402, a success, a failed charge) and the legacy case in n107-r1.test.ts now pin them. Both passed at 081b0c4 too: the behaviour was right, and only the tests were missing. - recentPayments now keeps the route's pattern, not pathOnly(req.url). An encoded separator (%3F, %23) or a value in a path segment can no longer reach the stats. This is the same class as cross-family review r6 of #441, MEDIUM 3. Mutation checks on file copies, each restored and checked with cmp, fail the tests: - a raw User-Agent in the fingerprint: 2 tests fail; - the User-Agent back in the HONEYPOT line: 1 fails; - the raw URL in a legacy payment row: 1 fails. agent: pcc-readmodels (c255d7dc)
…ity redaction (#441 r7) This fixes cross-family review r6 of #441 (rm-px7-441-r6-57233724), MEDIUM 3: the value-free rule found a query or fragment only by a literal ? or #. So "/cb%3Fzq1%3D<value>" kept its value in strings sent to Sentry, in the request log and in the audit row's URL. Reproduced at 5723372 by observability-r7.test.ts before the fix: 2 of 2 failed, the verdict's redactCredentials case and the request log plus audit row through the real createGateway. observability-redact.ts now reads %3F, %23, %26 and %3D (any case) as ?, #, & and = wherever a rule looks for separators: in withoutQueryValues (the request log's serializer, extra.url and the audit URL), in each URL-like token of any string, and in the whole-string form rule. The output shows them decoded. A mutation check confirms the tests catch it: with 5723372's redactor, both new tests fail. Under the PR steward's strict ruling (#5315, #5319), the redactors stay as defence in depth. The closed producer schema (the monitor's fingerprint, PostHog's distinct id, allowlisted header values, generic property values) is built in #514, and #441 rebases onto it. Those r6 findings were reproduced at 5723372 and are not changed here. agent: pcc-readmodels (c255d7dc)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PX-7: EvidenceProvenanceDTO (
GET /api/jobs/:jobId/evidence/provenance)This is the charter's EvidenceSummary/ProvenanceDTO: what the gateway can truthfully say about a job's evidence, for ordinary users, with raw inspect pointers.
returns/pcc-evidence-work/readmodels-evidence-summary-answers.md).returns/pcc-readmodels-work/evidence-facts-map-20260924.md, 41 facts with file:line).Stack: on #403 (
fix/job-read-family-auth@ 7395657), whose job read gate this route uses. CI suites run only on PRs based on master, so the counts below are from local runs.What the DTO says, and what it never says
integrityevent_bundle_hashis evidence integrity: every event hash reproduces from{type, timestamp, source, payload}, and the bundle hash from the sorted event hashes. This is the one LO-EV model/settlerecomputes.gateway_envelopeis only the gateway's storage integrity (the envelopePUT /completehashes), never evidence integrity.no_model_reproducesis not proof of tampering (a device or relay hash has no model here).not_recomputablemeans there are no events.signaturechecked: false. No stored bundle's signature has ever been checked.tierCoverageDEFAULT_TIER_REQUIREMENTS). Self-reported, not a verification. Fabricated events never count (evidence #3346).eventsfabricated,gatewayAuthored(written by the gateway, not a device), and first and last timestamps.archivenot_recorded: no archive CID is stored for any bundle.verificationno_verdict_recorded: the gateway stores no verifier or oracle verdict, so it never says "verified".inspectAn unreadable store is
unavailablewith null counts, nevernone.Access: the admin, the job's kernel operator or its buyer, through #403's gate. Anyone else gets the missing-job 404; an anonymous caller gets 401.
no-store.Also fixed (legacy truth)
Bundle rows carry no events, so
EvidenceSummaryDTO.eventCountwas always 0 onGET /api/jobs/:jobId/evidence,GET /api/compliance/evidence/:bundleIdandJobDetailDTO.evidenceBundles. The facades now attach each bundle's events.verifiedthere staysfalse: no column exists, and nothing records a verdict.Not in this PR
evidenceLevelandcontradictions. They land when evidence's feat(spec): evidence levels -- submitted / device_reported / inspected_output (must-close 5) #345 merges (evidenceLevelOfBundle,deriveContradictions).Evidence
pnpm test: 3125 passed / 6 skipped / 0 failed (192 files).PUT /completebundle that reproduces asgateway_envelope(so the recomputation matches what the route writes) and the legacy counts.afterAll, asserver.ts'sonClosedoes. Without that, a real/completerun could abort the process at exit.🤖 Generated with Claude Code
https://claude.ai/code/session_01A2ZvsqsAb7jC7AC8Viisqn