Oracle Audit Trail

SPEC_ORACLE_AUDIT_TRAIL.md · 2026-04-20

SPEC_ORACLE_AUDIT_TRAIL — Oracle Audit Trail

Version: 1.0 | Status: AUTHORIZED | Authority: α.13 | Date: 2026-04-16


PURPOSE

The Oracle Audit Trail is a structured, append-only JSONL log (oracle_log.jsonl) that records every verdict request passing through the Oracle pipeline — both the x402 USDC payment path (oracle_toll.py) and the Stripe payment path (Northflank webhook + oracle_email_service.py). It provides a durable, machine-readable record for: revenue analytics, dispute resolution, fraud detection, cache health monitoring, latency profiling, and end-to-end smoke test verification.

Currently (as of April 16 2026), no such log exists. Verdicts are cached per-session in oracle_verdicts/{session_id}.json (Stripe path) or recorded in oracle_toll_receipts.json (x402 path), but neither file captures latency, status, query hash, or verdict hash in a queryable format. This spec authorizes and defines the missing layer.

The audit trail covers both Oracle variants:


INPUTS

Stripe Pipeline Events (oracle_email_service.py path)

Each of the following events produces one audit record:

| Event | Source | Trigger |

|-------|--------|---------|

| Verdict generated (webhook pre-compute) | app/api/webhook/route.ts | checkout.session.completed with payment_status === "paid" |

| Verdict retrieved (result page on-demand) | app/api/verdict/route.ts | Browser GET /oracle/result?session_id={id} |

| Verdict served from cache | verdictCache.ts GET /cache/{id} | Cache HIT on oracle_toll |

| Verdict regenerated (cache miss) | app/api/verdict/route.ts | Cache MISS → Gemini re-call |

| Email delivery attempt | oracle_email_service.py | POST /send-verdict-email |

Required fields per Stripe record:


timestamp       — ISO 8601 UTC (e.g. "2026-04-16T14:23:07.412Z")
session_id      — Stripe session ID (cs_live_abc123...)
tier            — "quick" | "full" | "strategy"
query_hash      — SHA-256 hex of raw query text (first 16 chars for readability: "sha256:abcd1234...")
verdict_hash    — SHA-256 hex of serialized verdict JSON (first 16 chars)
email           — customer email address (hashed: SHA-256, not plaintext) [GAP — needs design: plaintext vs hashed]
latency_ms      — integer milliseconds from event trigger to log write
status          — "OK" | "ERROR" | "CACHED" | "REGENERATED" | "EMAIL_SENT" | "EMAIL_FAILED"
source          — "webhook" | "result_page" | "cache_hit" | "email_service"
error_detail    — null if status=OK; error string if status=ERROR

Oracle Toll Events (x402 path)

Each paid query produces one audit record on successful or failed payment verification:

Required fields per x402 record:


timestamp       — ISO 8601 UTC
tx_hash         — Base mainnet transaction hash (truncated: first 12 chars + "...")
endpoint        — "/query" | "/query/full" | "/query/premium" | "/analyze"
query_hash      — SHA-256 hex of query text (first 16 chars)
amount_usdc     — float (0.05 / 0.25 / 1.00)
latency_ms      — integer milliseconds from request receipt to response sent
status          — "OK" | "PAYMENT_REJECTED" | "RPC_UNREACHABLE" | "RAG_FAIL" | "REPLAY"
source          — "oracle_toll"
error_detail    — null if status=OK; reason string if status != OK

OUTPUTS

oracle_log.jsonl

Location: /home/nous/oracle_log.jsonl

Format: newline-delimited JSON. One JSON object per line. No wrapping array. Append-only — never delete, never truncate in production.

Example Stripe record (verdict generated):


{"timestamp":"2026-04-16T14:23:07.412Z","session_id":"cs_live_abc123","tier":"quick","query_hash":"sha256:8f3a2c1b...","verdict_hash":"sha256:d4e5f6a7...","email":"sha256:1a2b3c4d...","latency_ms":2847,"status":"OK","source":"webhook","error_detail":null}

Example Stripe record (email failure):


{"timestamp":"2026-04-16T14:23:09.011Z","session_id":"cs_live_abc123","tier":"quick","query_hash":"sha256:8f3a2c1b...","verdict_hash":"sha256:d4e5f6a7...","email":"sha256:1a2b3c4d...","latency_ms":1203,"status":"EMAIL_FAILED","source":"email_service","error_detail":"Graph API returned 503"}

Example x402 record (successful query):


{"timestamp":"2026-04-16T14:25:00.000Z","tx_hash":"0x8f3b2c1a4d5e...","endpoint":"/query","query_hash":"sha256:a1b2c3d4...","amount_usdc":0.05,"latency_ms":312,"status":"OK","source":"oracle_toll","error_detail":null}

Example x402 record (replay attempt):


{"timestamp":"2026-04-16T14:25:05.000Z","tx_hash":"0x8f3b2c1a4d5e...","endpoint":"/query","query_hash":"sha256:a1b2c3d4...","amount_usdc":0.05,"latency_ms":4,"status":"REPLAY","source":"oracle_toll","error_detail":"Transaction already used (replay attempt)."}

Derived analytics (future / not yet built)

[GAP — needs design] A read-side query utility (oracle_log_query.py) to extract:


INVARIANTS

  1. Append-only integrity — Records are only ever appended to oracle_log.jsonl. No record is modified or deleted after write. Rotated files (if any) are archived, not truncated. A monotonically increasing record count is a conservation law of the log.
  1. Every paid event is logged — For the Stripe path: every checkout.session.completed with payment_status === "paid" produces at least one log record (source=webhook). For the x402 path: every call to verify_payment() that resolves (OK or rejected) produces one log record. A payment without a log record is a pipeline failure.
  1. No plaintext query storage — Raw query text never appears in oracle_log.jsonl. Only the query_hash (SHA-256 truncated) is stored. This is distinct from oracle_verdicts/{session_id}.json which holds the full query for cache/retrieval purposes. [GAP — current code does not enforce this; log writer must implement the hash step]
  1. No raw email addresses — Customer email is stored as SHA-256 hash only. Plaintext email must not appear in the log. Disputes requiring email lookup must cross-reference oracle_verdicts/ or Stripe records. [GAP — needs design: hash function and salt policy; unsalted SHA-256 is trivially reversible for common emails]
  1. Status is terminal, not intermediate — Each log record captures the final outcome of its specific event (the verdict generation attempt OR the email send attempt), not an in-progress state. If two events occur for the same session (webhook verdict + email send), two records are written — one per event.
  1. Latency is wall-clock, not CPUlatency_ms measures the elapsed time from the beginning of the triggering event (Stripe webhook arrival, browser request arrival, x402 request arrival) to the point the log record is written. It reflects the customer-observable delay.
  1. Log survives service restartoracle_log.jsonl is persisted to disk at /home/nous/oracle_log.jsonl. The log writer opens the file in append mode ("a") and flushes after each write. In-memory buffering is prohibited. A crashed service must not lose a verdict event that was already generated.
  1. Tier fidelity in log — The tier field in a Stripe log record must match session.metadata.tier from the originating Stripe session. No tier substitution or normalization occurs at log-write time. [GAP — not enforced until log writer is implemented]

VERIFICATION CRITERIA

Σ.✓ conditions — audit trail is operating correctly when:

  1. Record count matches verdicts served — After each billing period, wc -l oracle_log.jsonl (webhook records, source=webhook) must equal the number of checkout.session.completed events received from Stripe, plus any additional result-page retrieval events. Divergence indicates dropped log writes or duplicate records. Tolerance: zero dropped records for paid events.
  1. Latency distribution is within bounds — For Stripe webhook path: p95 latency_ms for source=webhook records must be < 60,000ms (Gemini generation timeout). For x402 path: p95 latency_ms for status=OK must be < 5,000ms (RAG retrieval + overhead). Latency > 60,000ms for any single record is a Σ.⊠ indicator.
  1. Cache hit ratio is auditable — Query oracle_log.jsonl for all records with session_id X: exactly one record should have source=webhook (status=OK) and one record should have either source=cache_hit (status=CACHED) or source=result_page (status=REGENERATED). Two webhook records for the same session_id indicates a double-processing bug.
  1. No raw PII in loggrep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" oracle_log.jsonl must return zero matches. Any match is a data governance violation requiring immediate rotation of the affected log segment.
  1. Error detail coverage — Every record with status != "OK" must have a non-null error_detail string. A record with status=ERROR and error_detail=null is a logging bug. [GAP — enforcement requires log writer schema validation]
  1. Smoke test verifiability — After running the end-to-end smoke test (test_oracle_e2e.py — [GAP — not yet written]), the log must contain: one webhook record (status=OK), one cache_hit or result_page record, and one email_service record (EMAIL_SENT or EMAIL_FAILED). The presence of all three confirms log coverage of the full pipeline.

FAILURE MODES

  1. Σ.⊠ Log writer crash mid-verdict — The log write call throws an exception after the verdict is generated but before the record is flushed. The verdict is served (customer receives it) but no log record exists. Impact: revenue record gap; undetectable without cross-referencing Stripe dashboard. Mitigation: log write must occur before response is returned; if log write fails, log to ALERT.log and continue (verdict is not withheld due to log failure — customer experience is not degraded). [GAP — write order and error handling not yet specified]
  1. Σ.⊠ Disk full — log growth uncheckedoracle_log.jsonl grows without rotation. After sufficient volume, disk writes fail silently or with IOError. Service may continue serving verdicts with no log. Mitigation: [GAP — log rotation policy not yet defined; needs logrotate config or size-based rotation in log writer]
  1. Σ.⊠ Duplicate session_id records — Stripe webhook retries (Stripe retries for up to 72 hours on non-2xx) cause the webhook handler to process the same checkout.session.completed event twice. Log receives two records with the same session_id and source=webhook. Revenue analytics overcounts. Mitigation: [GAP — idempotency check on session_id not implemented in current webhook handler; log writer should check for existing session_id before appending]
  1. Σ.⊠ PII leak — email stored plaintext — A log writer implementation error stores customer_email directly instead of the hash. All customer emails in the log are exposed to anyone with read access to /home/nous/. Impact: data governance violation; regulatory risk. Mitigation: log writer unit tests must assert no email regex matches in output; [GAP — hash salt policy undefined]
  1. Σ.⊠ Log writer absent — no records written — The log writer is never implemented (current state as of April 16 2026). The audit trail does not exist. Revenue, latency, and cache health cannot be audited. Impact: operational blindness; no basis for anomaly detection. Mitigation: this spec is the authorization to build it; C.L.O.D. is the implementer.
  1. Σ.⊠ Clock skew — timestamps out of order — Server clock drift or NTP failure causes timestamp values that go backward. Log appears corrupt; time-range queries return wrong results. Mitigation: use datetime.now(timezone.utc).isoformat() (Python) or new Date().toISOString() (TypeScript) — both are NTP-derived wall-clock; monotonic guarantees are not required, only UTC correctness.
  1. Σ.⊠ x402 and Stripe records intermixed without source tag — Records from both Oracle variants land in the same file without a source field that distinguishes them. Analytics treating all records as the same pipeline produce corrupted metrics. Mitigation: source field is mandatory and must be validated at write time; schema enforcement at writer boundary.
  1. Σ.⊠ Log truncated by rotation error — A log rotation script erroneously truncates the active file rather than archiving it. All historical records lost. Mitigation: [GAP — rotation script not yet written; rotation must use rename-and-reopen, never truncate]

GAPS

The following items require design decisions before implementation:

| Gap ID | Description | Impact | Priority |

|--------|-------------|--------|----------|

| GAP-01 | Log writer does not exist — must be implemented in oracle_toll.py (x402 path) and app/api/webhook/route.ts + oracle_email_service.py (Stripe path) | Audit trail absent; no analytics possible | CRITICAL |

| GAP-02 | Email hash policy undefined — unsalted SHA-256 is trivially reversible for common email addresses; salt policy and key management needed | Data governance risk | HIGH |

| GAP-03 | Log rotation policy not defined — oracle_log.jsonl grows unbounded; no size cap or time-based rotation spec | Disk full risk at production volume | HIGH |

| GAP-04 | Idempotency on duplicate webhook delivery — Stripe retries can cause duplicate session_id records; no dedup guard in current webhook handler | Analytics overcounting; misleading revenue figures | HIGH |

| GAP-05 | Log write error handling not specified — if log write fails, should verdict delivery be blocked or proceed silently? | Tradeoff: data integrity vs customer experience | MEDIUM |

| GAP-06 | oracle_log_query.py analytics utility not yet written — no tooling to extract revenue totals, latency distributions, or cache hit ratios from the log | Operational blindness without raw log grep | MEDIUM |

| GAP-07 | End-to-end smoke test (test_oracle_e2e.py) not yet written — no automated verification that the log receives all expected records for a full pipeline run | Pipeline health only known when a customer complains | MEDIUM |

| GAP-08 | verdict_hash definition for Stripe path: hash of raw Gemini JSON or normalized verdict object? Non-determinism in Gemini responses means two equivalent verdicts may differ by whitespace | Inconsistent hash-based dedup | LOW |


DEPENDENCIES

| Dependency | Role | Status |

|------------|------|--------|

| oracle_toll.py (port 8889) | x402 path — log writer hooks into verify_payment() and endpoint handlers | Exists; needs log writer added |

| app/api/webhook/route.ts | Stripe path — log writer hooks into verdict pre-computation block | Exists; needs log writer added |

| oracle_email_service.py (port 8006) | Stripe path — log writer hooks into POST /send-verdict-email handler | Exists; needs log writer added |

| hashlib (Python stdlib) | SHA-256 hashing for query and email | Available; no install required |

| crypto.subtle (Web Crypto API) / crypto (Node.js) | SHA-256 hashing in TypeScript webhook route | Available in Node.js runtime |

| Disk at /home/nous/ | Append-only storage for oracle_log.jsonl | Available; rotation policy [GAP-03] |


DEPENDENTS

| Dependent | Dependency type |

|-----------|----------------|

| Revenue analytics (daily/monthly totals) | Requires complete log with all paid events |

| Cache health monitoring | Requires CACHED vs REGENERATED counts per time window |

| Email delivery SLA tracking | Requires EMAIL_SENT vs EMAIL_FAILED ratio |

| Dispute resolution (customer claims no verdict) | Requires session_id lookup in log |

| Fraud detection (replay attack monitoring) | Requires REPLAY status records from x402 path |

| SPEC_ORACLE_VERDICT_PIPELINE.md GAP-01 resolution | This spec directly addresses that gap |


REFERENCES

| File | Role |

|------|------|

| /home/nous/memories/SPEC_ORACLE_VERDICT_PIPELINE.md | Parent spec; GAP-01 is the origin of this document |

| /home/nous/oracle_toll.py | x402 Oracle — log writer insertion points at verify_payment() and endpoint handlers |

| /home/nous/Aether/app/app/api/webhook/route.ts | Stripe webhook — log writer insertion point in verdict pre-computation IIFE |

| /home/nous/oracle_email_service.py | Email service — log writer insertion point in POST /send-verdict-email |

| /home/nous/Aether/app/app/lib/verdictCache.ts | Cache R/W — log writer insertion point for CACHED status records |

| /home/nous/oracle_toll_receipts.json | Existing x402 replay protection store (partial audit data; does not capture latency or status) |

| /home/nous/oracle_verdicts/ | Existing per-session verdict cache (Stripe path; full query + verdict stored; no latency or status) |

| /home/nous/memories/SPECIFICATION_AUDIT_LOOP.md | Spec template and classification criteria |


Φζ.⊤. κ ⚒ SPEC_ORACLE_AUDIT_TRAIL.md. ΩQ.⊡ → Σ.✓. 10-4, good buddy. Arr, the audit trail's got her blueprint. Over.


Jeremy Zlabis

Chronogeometer · Visionary · Disruptor · Chief

42 Sisters AI · East York, Toronto

🍁 Φ 0.042