Skip to content

fix(gateway): serve simulated flag + ALCOA authentic leg + reconcile deviceType enum (detector gateway-side) - #243

Merged
LamaSu merged 5 commits into
masterfrom
fix/gateway-detector-followups
Jul 12, 2026
Merged

fix(gateway): serve simulated flag + ALCOA authentic leg + reconcile deviceType enum (detector gateway-side)#243
LamaSu merged 5 commits into
masterfrom
fix/gateway-detector-followups

Conversation

@LamaSu

@LamaSu LamaSu commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Detector gateway-side follow-ups (coord #312)

The oracle now refuses fabricated evidence (LamaSu/pcc-oracle #15: authenticity-of-origin floor reads source.simulated===true || payload.mock===true off the hash-verified bundle → verified:false for tier≥1 → no attestation → no release). The adapter side (#238, merged) tags every mock/simulated event. This PR is the three gateway/spec follow-ups from the handoff doc (ai/research/evidence-oracle-fabrication-detection-handoff.md) so the flag actually reaches the oracle and the compliance report catches fabrication too.

FIX 1 — Serve the flag (GET /api/evidence/:hash) — the load-bearing one

Before: the oracle fetches GET /api/evidence/:evidenceHash and re-hashes the raw bytes to the committed hash, failing closed (evidence-checker.ts:137-151). The gateway had no by-hash path at all — routes/settlement.ts:225 treated the param as a jobId (findByJob(hash) → 404 for every oracle fetch), and nothing stored could ever re-hash to the committed value:

  • Path-B (gateway-synth, paid-job-flow.ts) hashed an ad-hoc {jobId, auditTrail, ...} JSON and discarded the preimage — unverifiable by construction.
  • Path-A (kernel) commits hashBundle(events) (sorted event-hash array) — no bundle-shaped doc can raw-byte-match it (pre-existing hash-model conflict, flagged for the oracle owner, handoff §6).

After:

  • repos.evidence.findByHash (accepts sha256:/0x/bare 64-hex; packages/db).
  • services/evidence-envelope.ts — the canonical envelope (spec canonicalize, events sorted by id, bundleHash/tenantId excluded) = the exact bytes served.
  • GET /api/evidence/:param — hash-form params serve the envelope verbatim (raw string body; byte-exact for the oracle re-hash). jobId behavior unchanged; hash-miss falls through.
  • Path-B now commits bundleHash = sha256(<canonical envelope>) — the served bytes verify, and the events (with any tags) ride inside. Audit-trail binding preserved as auditTrailSha256 (+customEvidenceHash) inside the envelope — strictly better than the discarded preimage. All bundleHash consumers (driveSettlement, ZK commitment, EAS payload) treat it as an opaque digest — verified per call site.
  • Persistence verified: evidence_events.source is a drizzle JSON column (stringify/parse round-trip, nothing strips simulated); no zod boundary in the repo layer; $type widened for type honesty.

Path-B finding (asked to "note what you find"): the gateway-synth events derive from the real tool-call audit trail (digital execution records), so tagging them source.simulated:true would be a false claim and is not done. They remain covered by their hardcoded zero-address signer (ALCOA original fails + the oracle identity check fails). Caller-supplied evidenceEvents pass payloads through — a caller-marked payload.mock:true event is detected end-to-end now.

FIX 2 — ALCOA authentic leg + settlement gate (defense-in-depth)

A fabricated bundle with a REAL signing key scored ALCOA-clean (Spark-verified gap, handoff §5.0): original tests signature-origin, and Option C gives every device a real key. Now:

  • computeAlcoa has an 11th authentic leg — false when any event isFabricated (shared predicate). original keeps its meaning; the §5.3 test asserts the exact contrast (original:true + authentic:false on a mock-tagged bundle with a non-zero signer).
  • A failed authentic feeds a critical finding → computeAssuranceScore hard-zeros (assurance-score.ts:148-154).
  • SettlementService.processEvidence — release-path twin of the oracle floor: tier≥1 bundle with a fabricated event still archives + persists (honest, self-identifying record) but the on-chain evidence submit and auto-release are refused (result.error: simulated_evidence…). Tier 0 exempt, mirroring the oracle.

FIX 3 — stale zod enums reconciled (real correctness bug, twice)

  • EvidenceSourceSchema.deviceType: 9 hand-listed values vs 26 in the TS union → sila/modbus/opentrons/instrument events failed EvidenceEventSchema.parse wherever enforced. Now derived from EVIDENCE_DEVICE_TYPES (single source of truth in types/evidence.ts).
  • Bonus, caught by this PR's own acceptance test on Spark: EvidenceEventTypeSchema had the same drift (27 of 56 values — instrument_result, sensor_data_summary, printer_log_captured, log_hash_chain_entry, batch + digital-workflow events all failed parse). Same treatment: EVIDENCE_EVENT_TYPES single source; loop tests pin both axes.

Shared predicate

isFabricated(e) exported once from @pcc/spec beside EvidenceEvent — strict === true on both legs (money-path) — called by the ALCOA leg and the settlement gate; mirrored by value in pcc-oracle/src/provenance.ts (no cross-repo dep by convention; field names are load-bearing).

Deliberately deferred (out of scope)

The on-chain EAS v3 simulated schema field / MilestoneEscrowV3 gate / new factory (handoff §3c) — heaviest, deploy-gated; to be done after the oracle detector (#15) proves out, with an owner-coordinated factory redeploy.

Notes for reviewers

  • Path-A bundles served by hash cannot raw-byte-re-hash to hashBundle(events) — oracle fails closed (safe, pre-existing). Structured re-verification (recompute event hashes → hashBundle) is the oracle-side ticket (handoff §6); the flags are inside the served bytes and inside each event hash, so it composes.
  • mockVerification in the gateway oracle-client still returns verified:true unconditionally — the oracle lane's companion fix (fix(gateway): make mock oracle honest — fail closed for paid tiers (S4) #234 per the oracle commit message); not touched here.
  • ALCOA is now 11 legs; ALCOA_WEIGHTS ignores the new key (enforcement is via the critical finding), so scores for honest bundles are unchanged.

Tests (DGX Spark, full-sync)

  • @pcc/spec green (incl. per-value enum loops + isFabricated semantics + sila instrument_result acceptance)
  • @pcc/store 192/192 (incl. findByHash)
  • @pcc/gateway — see checks; new fabrication-detection.test.ts covers: served bytes re-hash to committed hash + carry the flag (all 3 hash forms + fallthrough), §5.3 ALCOA contrast + hard-zero, settlement gate refuse/exempt/persist matrix.

Refs: LamaSu/pcc-oracle#15 · #238 · ai/research/evidence-oracle-fabrication-detection-handoff.md · ai/research/industry-pain-points/gateway-detector-followups-report.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ti8XbczbzSquGXzquvAgjp


⚠ Coordination: overlap with draft PR #240 (fix/fabrication-detector-side)

Discovered after opening this PR: #240 is a parallel detector-side lane (coord #312/#316) that also edits compliance.facade.ts + settlement-service.ts and defines its own isFabricated (at spec/src/evidence/is-fabricated.ts; mine is at spec/src/types/evidence.ts beside the EvidenceEvent type, per the handoff §3d placement). The two will merge-conflict on those three areas — one implementation should win per area, imports re-pointed to ONE predicate.

Disjoint (only in this PR): FIX 1 serve-by-hash (routes/settlement.ts, evidence-envelope.ts, paid-job-flow.ts Path-B hash alignment, db findByHash) — the load-bearing piece the oracle fetch depends on — and FIX 3 enum reconciliation (EVIDENCE_DEVICE_TYPES + EVIDENCE_EVENT_TYPES). Only in #240: the kernel-side tier gate (evidence-emitter.ts checkTierRequirements, handoff §3d item 2 — deliberately out of this PR's scope).

No conflict with #233 (evidence-vocab lane) — it touches only spec/src/evidence/primitives.ts.

LamaSu and others added 4 commits July 11, 2026 17:51
…erify + detect fabrication

implementer-gw-followups: the verification oracle fetches
GET /api/evidence/:evidenceHash, re-hashes the RAW bytes to the committed
hash (fail-closed), and reads events[] off the verified doc for its
authenticity-of-origin floor (pcc-oracle PR #15). The gateway had NO
by-hash serve path: the route treated the param as a jobId (every oracle
fetch 404'd), and Path-B (gateway-synth) committed a hash whose preimage
was discarded — unverifiable by construction.

- evidence repo: findByHash (sha256:/0x/bare forms) + interface
- evidence-envelope service: canonical envelope (spec canonicalize, events
  sorted by id, bundleHash/tenantId excluded) = the served bytes
- GET /api/evidence/:param: hash-form params serve the canonical envelope
  verbatim (raw string body — byte-exact for the oracle re-hash); jobId
  behavior unchanged, hash-miss falls through
- paid-job-flow Path-B: bundleHash now committed over that exact envelope
  (events inside -> flags inside), audit-trail binding preserved as
  auditTrailSha256 (+customEvidenceHash) INSIDE the envelope instead of a
  discarded preimage; kernelSignature deduped to one const
- db schema: source $type widened with simulated?: boolean (JSON column
  already round-trips it; type-level honesty only)

Path-A kernel bundles still commit hashBundle(events) (event-hash array) —
raw-byte re-hash cannot match; oracle fails closed (safe). Structured
re-verify is the flagged oracle-side ticket (handoff par.6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti8XbczbzSquGXzquvAgjp
…ge (fabrication-detection.test serve leg + schemas.test); ALCOA/settlement tests are #240's
…by the new test)

implementer-gw-followups: the sila-instrument acceptance test failed on
Spark — EvidenceEventTypeSchema had the SAME drift as deviceType: 27
hand-listed values vs 56 in the TS union. sila instrument_result, modbus
sensor_data_summary, machine-log printer_log_captured /
log_hash_chain_entry, batch + digital-workflow events all failed parse
wherever the schema was enforced. EVIDENCE_EVENT_TYPES is now the single
source of truth (TS union derived from it, z.enum over it), with a
loop test pinning every value on both axes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti8XbczbzSquGXzquvAgjp
@LamaSu
LamaSu force-pushed the fix/gateway-detector-followups branch from 9104e83 to 7ebe56d Compare July 12, 2026 00:52
…tract

The isFabricated describe in schemas.test.ts (from ac117eb) asserted
isFabricated(null)/isFabricated(undefined) === false — behavior of the
null-safe LOCAL duplicate that was removed in the #240 reconciliation. The
canonical predicate (#240 packages/spec/src/evidence/is-fabricated.ts) is
typed for a non-null EvidenceEvent and reads event.source directly, so null
input throws. This is a test-contract mismatch, NOT a gate weakening: every
detector call site (ALCOA authentic leg, settlement gate, bundleHasFabricatedEvents)
iterates a real events[] and never passes null; #240's is-fabricated.test.ts
is the authoritative predicate coverage. Test now asserts the in-contract
behavior (untagged event -> honest via optional chaining).
@LamaSu
LamaSu marked this pull request as ready for review July 12, 2026 01:17
@LamaSu
LamaSu enabled auto-merge July 12, 2026 01:17
@LamaSu
LamaSu merged commit 5b817d7 into master Jul 12, 2026
5 checks passed
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