Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
c597741
fix(qbo): per-request fetch timeout on every QuickBooks call
Clarion1631 Sep 1, 2026
c900334
test(qbo): cover qbTimedFetch deadline, pass-through, and caller abort
Clarion1631 Sep 1, 2026
cc5162f
fix(receipts): fail fast on a QBO timeout instead of burning maxDuration
Clarion1631 Sep 1, 2026
ff52e06
feat(ops): pipeline health endpoint + 7am morning digest
Clarion1631 Sep 1, 2026
4d24b13
docs: phase 1 intake-core and phase 4 earned-margin specs
Clarion1631 Sep 1, 2026
b11dad3
fix(qbo): deadline must govern the response BODY, not just the headers
Clarion1631 Sep 2, 2026
69867e6
fix(qbo): restore maxDuration 60; give token refresh its own longer d…
Clarion1631 Sep 2, 2026
59bae35
fix(ops): no false-green health — explicit probe status, no 7d auto-pass
Clarion1631 Sep 2, 2026
03273e9
fix(ops): fail-closed, constant-time Bearer auth for the pipeline routes
Clarion1631 Sep 2, 2026
0bbdb1c
fix(qbo): latch the abort winner; wrap bytes()/clone(); floor timeout…
Clarion1631 Sep 2, 2026
036eafc
fix(qbo): stop fallback parsers swallowing timeouts; attach on alread…
Clarion1631 Sep 2, 2026
6496577
fix(ops): probe deadlines, guaranteed digest delivery, creates-only f…
Clarion1631 Sep 2, 2026
711d04c
fix(qbo,ops): propagate refresh + attachment timeouts; reachable heal…
Clarion1631 Sep 2, 2026
b1d93a9
fix(qbo): match QB timeouts by name, not instanceof (CI Node 20 caugh…
Clarion1631 Sep 2, 2026
b6826ff
fix(qbo): stop the payments cron on a QBO outage; make it visible; re…
Clarion1631 Sep 2, 2026
6b51219
fix(qbo): abort on any QBO sub-call outage; honest run status; valida…
Clarion1631 Sep 2, 2026
363cffb
fix(qbo): fail-closed payments cron; honest run status; token-save sp…
Clarion1631 Sep 2, 2026
0dd50b3
fix(qbo): no silent probe skips; partial runs visible now; normalize …
Clarion1631 Sep 2, 2026
299e241
refactor(ops): digest cron uses main's postTextToWebhook (one Chat he…
Clarion1631 Sep 2, 2026
407e1be
fix(qbo): never settle an unread payment; tokens before ok; latest-ru…
Clarion1631 Sep 2, 2026
43f37ab
fix(qbo): verify empty runs, refuse fabricated paidAt, paginate, aler…
Clarion1631 Sep 2, 2026
6a12e08
fix(qbo): intuit probe fails the check; parse vs transport; end-to-en…
Clarion1631 Sep 2, 2026
ad57b30
fix(qbo): budget from request entry; one audit event per run; a faile…
Clarion1631 Sep 2, 2026
c377885
feat(receipts): ReceiptIntake schema (Receipt Pipeline v2, Phase 1)
Clarion1631 Sep 1, 2026
b043823
feat(receipts): intake endpoint, reader, dedup, booking, and the 5-mi…
Clarion1631 Sep 2, 2026
f133d66
test(receipts): dedup fixtures, prompt pins, booking matrix, and the …
Clarion1631 Sep 2, 2026
80c110a
test(receipts): e2e 401 matrix + idempotent POST; document the as-bui…
Clarion1631 Sep 2, 2026
599aefa
fix(receipts): busyPasses column, exact host guard, UNIQUE partial-in…
Clarion1631 Sep 2, 2026
1b6b01e
fix(receipts): 25s read budget, 2 retries at 1s/3s, HEIC sequence brands
Clarion1631 Sep 2, 2026
d26801b
fix(receipts): Expense.amount is GROSS; vendor confirms a dup; refund…
Clarion1631 Sep 2, 2026
e69d0c9
fix(receipts): dry-run rows can no longer starve the queue; 40s soft …
Clarion1631 Sep 2, 2026
f130894
fix(receipts): sha decides a sourceRef replay; provenance is not call…
Clarion1631 Sep 2, 2026
d1d846b
fix(receipts): STAGING state, per-weak-key lock, requeue inside the c…
Clarion1631 Sep 2, 2026
1d36272
fix(proxy): refuse a Server Action dispatch on the receipt-intake byp…
Clarion1631 Sep 2, 2026
ca59fc9
fix(receipts): strong-before-weak dedup, resumable publish, tagged st…
Clarion1631 Sep 2, 2026
138b4e9
test(receipts): reshape the cross-namespace leak test around the new …
Clarion1631 Sep 2, 2026
ce6a376
fix(pkg): de-duplicate test:receipt-intake, dropped by my rebase reso…
Clarion1631 Sep 2, 2026
1126a73
fix(receipts): staging never 2xx, lease held through routing, attachm…
Clarion1631 Sep 2, 2026
7a14a21
fix(receipts): checks carry no sales tax; persist only the tax bookin…
Clarion1631 Sep 2, 2026
fc91992
fix(receipts): shadow backlog retires as SHADOW_DONE; attachment chec…
Clarion1631 Sep 2, 2026
5d84c17
chore: re-trigger CI for fd0fb4b8 (Actions did not fire on the push)
Clarion1631 Sep 2, 2026
d75de00
fix(health): re-apply the intake probes onto Phase 0's pipeline-health
Clarion1631 Sep 2, 2026
4daa8c2
fix(receipts): cutover boundary, two-step upload, split secrets, phas…
Clarion1631 Sep 2, 2026
e3da4a6
fix(receipts): void-lock, shared object validation, evidence-based re…
Clarion1631 Sep 2, 2026
8297b46
fix(receipts): seal verified objects, quarantine unprovable rows, fen…
Clarion1631 Sep 2, 2026
d6e709b
fix(receipts,proxy): fail-closed cron gate; machine endpoints refuse …
Clarion1631 Sep 2, 2026
8b332c7
fix(receipts): fence every write on the claim, seal after commit, ref…
Clarion1631 Sep 2, 2026
85d6af5
fix(receipts): authorize late fields, CAS every mutation, make cleanu…
Clarion1631 Sep 2, 2026
be9ae4c
fix(receipts): release ownership on every transition; fence publish a…
Clarion1631 Sep 2, 2026
8c21abd
fix(receipts): validate the phase against the job at intake, not just…
Clarion1631 Sep 2, 2026
6f8ecc3
fix(receipts): initial publication obeys null-or-equal and validates …
Clarion1631 Sep 2, 2026
1cb66bb
test(receipts): make the cleanup-test function slices EOL-agnostic
Clarion1631 Sep 2, 2026
7050cbe
fix(receipts): fence the publish on the exact observed park, not the …
Clarion1631 Sep 2, 2026
93bb785
test(e2e): bring the intake spec up to the route's contract
Clarion1631 Sep 2, 2026
0c7112d
fix(receipts): authorize the EFFECTIVE project on every session finalize
Clarion1631 Sep 2, 2026
856517b
fix(receipts): fence the reject, trust v1 evidence, re-arm swept uploads
Clarion1631 Sep 2, 2026
c2e6408
fix(receipts): fenced sweeper reject, upload leases, own bucket, sour…
Clarion1631 Sep 2, 2026
281d14b
fix(receipts): tagged presence on the replay path; a secret owns SOUR…
Clarion1631 Sep 2, 2026
8418a67
fix(receipts): one ceiling, production ref shapes, stable receipt ref…
Clarion1631 Sep 2, 2026
390a62e
feat(receipts): version the upload lease so a resumed upload cannot b…
Clarion1631 Sep 2, 2026
0962751
fix(proxy): allowlist anonymous Server Action dispatch instead of den…
Clarion1631 Sep 2, 2026
afda5be
fix(receipts): inline route shares decideSource; replays verify the o…
Clarion1631 Sep 2, 2026
430e924
fix(intake): round 16 — SHA identity guard, finalize CAS, attachment …
Clarion1631 Sep 2, 2026
5cf88dc
fix(receipts): give the e2e storage stub a list(), and stop expectedS…
Clarion1631 Sep 2, 2026
862c7d7
fix(receipts): round-17 Codex findings — Expense lifecycle, read dead…
Clarion1631 Sep 2, 2026
dddeeb4
ci: retrigger Codex gate (runner sandbox errors on prior attempts)
Clarion1631 Sep 2, 2026
870577c
fix(receipts): round-18 Codex findings — fenced deletes, live-lease r…
Clarion1631 Sep 2, 2026
e466ed5
fix(receipts): PR #440 Codex gate — dry-run kill switch, /start lease…
Clarion1631 Sep 3, 2026
f307454
fix(receipt-intake): PR #440 round-1 gate — 5 Codex findings
Clarion1631 Sep 3, 2026
ff510a7
fix(receipt-intake): PR #440 round-2 gate — 5 Codex findings
Clarion1631 Sep 3, 2026
47ebd90
fix(receipt-intake): finalize refuses a non-object JSON body
Clarion1631 Sep 3, 2026
64a7d29
ci: retrigger Codex gate (runner posted no verdict on 47ebd90b)
Clarion1631 Sep 3, 2026
a2998e8
Merge remote-tracking branch 'origin/main' into feat/phase1-intake-core
Clarion1631 Sep 3, 2026
6ff89b2
fix(receipt-intake): PR #440 a2998e8a gate — 4 adversarial findings
Clarion1631 Sep 3, 2026
e631dcd
Merge origin/main into feat/phase1-intake-core
Clarion1631 Sep 3, 2026
482d05a
test(receipt-intake): wiring checks for the lease and the payment cur…
Clarion1631 Sep 3, 2026
e75b857
fix(receipt-intake): PR #440 round-33 gate — 5 adversarial findings
Clarion1631 Sep 3, 2026
52190f8
fix(receipt-intake): PR #440 round-34 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
631c990
fix(receipt-intake): PR #440 round-34 gate — 4 adversarial findings
Clarion1631 Sep 3, 2026
d654670
fix(receipt-intake): PR #440 round-35 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
59675ef
Merge origin/main (#448 deposit-sweep) into feat/phase1-intake-core
Clarion1631 Sep 3, 2026
39a8b3b
chore(qbo): restore CRLF line endings on src/lib/quickbooks.ts to mat…
Clarion1631 Sep 3, 2026
1c01ad0
fix(receipt-intake): PR #440 round-36 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
4540c84
fix(receipt-intake): PR #440 round-12 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
92f2df5
fix(receipt-intake): PR #440 round-13 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
f1b6c61
fix(receipt-intake): PR #440 round-14 gate — lease fence, structurally
Clarion1631 Sep 3, 2026
267a96f
fix(receipt-intake): PR #440 round-15 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
252908e
fix(e2e): remove a block my lease-threading script duplicated
Clarion1631 Sep 3, 2026
fb95e69
fix(receipt-intake): PR #440 round-16 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
14fdd8d
fix(receipt-intake): PR #440 round-17 gate — 3 adversarial findings
Clarion1631 Sep 3, 2026
a292a78
fix(tests): measure the connection-hold proof on the test's own client
Clarion1631 Sep 3, 2026
f58661d
fix(receipt-intake): PR #440 round-18 gate - 4 adversarial findings
Clarion1631 Sep 3, 2026
593e91c
fix(tests): route the default-repair probe at the probe table, by arg…
Clarion1631 Sep 3, 2026
e220520
fix(tests): prove non-serialization by OVERLAP, not by counting conne…
Clarion1631 Sep 3, 2026
98f2d86
fix(receipt-intake): PR #440 round-19 gate - lease issuance, /start c…
Clarion1631 Sep 3, 2026
f2f76c3
fix(receipt-intake): PR #440 round-20 gate - lease identity, durable …
Clarion1631 Sep 3, 2026
045ceb6
fix(receipt-intake): the two CI failures on f2f76c30
Clarion1631 Sep 3, 2026
423cf0b
wip: round-21 gate (pre-merge checkpoint)
Clarion1631 Sep 3, 2026
cff45ff
Merge remote-tracking branch 'origin/main' into feat/phase1-intake-core
Clarion1631 Sep 3, 2026
1d3278a
fix(receipt-intake): PR #440 round-21 gate - object-claim write skew,…
Clarion1631 Sep 3, 2026
8cc25cf
fix(receipt-intake): record ReceiptObjectClaim's RLS in the blind-spo…
Clarion1631 Sep 3, 2026
dc723b9
fix(tests): the CREATE-only control counts tables, and the list grew
Clarion1631 Sep 3, 2026
ee8ad15
Merge remote-tracking branch 'origin/main' into feat/phase1-intake-core
Clarion1631 Sep 3, 2026
763d40d
fix(receipt-intake): PR #440 round-22 P0 - a delete may not outlive i…
Clarion1631 Sep 3, 2026
48bca8f
Merge remote-tracking branch 'origin/main' into feat/phase1-intake-core
Clarion1631 Sep 3, 2026
6368837
Merge remote-tracking branch 'origin/main' into feat/phase1-intake-core
Clarion1631 Sep 4, 2026
1c208d6
Merge remote-tracking branch 'origin/main' into feat/phase1-intake-core
Clarion1631 Sep 4, 2026
3778383
fix(apply-receipt-intake): the target guard compared an IP to a hostname
Clarion1631 Sep 4, 2026
d5f5606
fix(apply-receipt-intake): resolving --expect-host alone still refuse…
Clarion1631 Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,44 @@ MCP_SECRET=
BANK_LEDGER_INGEST_SECRET=generate_a_strong_random_secret_here
BANK_LEDGER_STATUS_SECRET=generate_a_different_strong_random_secret_here

# --- Receipt Pipeline v2 (docs/plans/PHASE-1-INTAKE-CORE-SPEC.md) ---
# TWO machine secrets, deliberately separate: they belong to different programs
# with different blast radii, and they rotate independently. Setting them to the
# SAME value is refused at runtime — that would silently re-merge the two.
#
# The Apps Script forwarders. May only INGEST (POST /api/receipts/intake,
# /start, /{id}/finalize) and only under source drive|email|chat. Cannot read
# the queue and cannot archive.
RECEIPT_INTAKE_SECRET=
# The nightly Drive archive mirror. May only READ BOOKED/ARCHIVED rows
# (GET /api/receipts/intake?state=BOOKED) and report back what it archived
# (POST /api/receipts/intake/{id}/archived). Cannot create or publish a row.
RECEIPT_ARCHIVE_SECRET=
#
# Storage note (not an env var): receipts live in their OWN private bucket,
# `receipt-intake`, carrying an 8 MiB file-size limit and an allow-list of the
# six formats QuickBooks can attach. Two-step uploads go straight to a signed
# URL and never pass through this server, so the bucket is the only place a
# too-large or wrong-type write can actually be refused; MAX_STORED_BYTES in
# src/lib/receipt-intake/intake-core.ts only lets the server reject the object
# after the fact. It is separate from `secure-docs` because those limits are
# per-bucket, and because a signed upload URL is a write capability that must
# not point at the bucket holding signed contracts.
#
# Do NOT create it by hand: scripts/apply-receipt-intake.mjs creates it when
# missing and VERIFIES it when present (it needs SUPABASE_URL and
# SUPABASE_SERVICE_KEY in the environment, and exits nonzero if the bucket
# exists with a different limit, MIME list, or public flag).
#
# Shadow mode. UNSET or "true" = dry run: rows are read, deduped and routed, and
# NOTHING is booked. Set to the literal "false" only at cutover.
RECEIPT_INTAKE_DRYRUN=
# The instant the Apps Script stopped booking (ISO 8601). Written at the flip to
# forwarder mode; the first live worker pass uses it to split the shadow backlog
# into "v1 already booked this" and "nobody booked this". With it unset the
# cutover refuses to touch either side. Can also live in the `cutoverV1StoppedAt`
# AutomationSetting row, which takes precedence.
CUTOVER_V1_STOPPED_AT=
# Payroll period configuration (src/lib/payroll-config.ts). ALL OPTIONAL — the
# built-in defaults are shown, and they are DEFAULTS PENDING JUSTIN'S DECISION
# (Phase 5 spec section 7, risks 2 and 3), not settled policy. Not secrets.
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,32 @@ jobs:
# The focused integration regression deliberately drifts the disposable
# database's function and trigger after proving the checker recognizes
# the production snapshot. It therefore runs last in this job.
# The receipt-intake claim transaction against a REAL Postgres. The rest
# of that feature's suites mock every DB call, so the SQL — a void
# function read through $queryRaw, a claim that behaves differently than
# its mock — is the one part they cannot see.
- name: Receipt-intake claim + advisory locks against real Postgres
run: npx tsx --test tests/receipt-intake-claim-db.test.ts
env:
RECEIPT_INTAKE_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# THE APPLY SCRIPT ITSELF, run end to end the way production will run it.
# Its exported helpers are unit-tested and its statement list is replayed
# by the DB-gated suite, but main() is executed by nothing else: not the
# flag parsing, not the identity gate, not the statements running in order
# against a real server, not the second run being a genuine no-op. It
# builds two databases -- one pre-Phase-1, and one carrying the OLD
# Phase-1 table with DEFAULT 'RECEIVED' so the additive upgrade and the
# state-default repair are exercised -- and asserts both end up matching
# what the committed migration produces.
#
# `--target ci` inside the driver: the ambient URL, no production baseline
# or project-ref check, and a refusal if that URL looks like Supabase.
- name: Apply script end-to-end against real Postgres
run: node scripts/ci-apply-receipt-intake-e2e.mjs
env:
APPLY_E2E_SERVER_URL: postgresql://probuild:probuild@localhost:5432/postgres
APPLY_E2E_DB: probuild_apply_fresh
# Two REAL connections contending on a User row. Every other concurrency
# proof in the suite is an injected sequence, which shows the branch exists
# but not that PostgreSQL serializes the way the code assumes. Runs BEFORE
Expand Down Expand Up @@ -500,13 +526,26 @@ jobs:
# against the throwaway DB — so unlike the provider secrets above it's a
# literal here rather than a repo secret (there's nothing to rotate).
DEPOSIT_INGEST_SECRET: "e2e-ci-deposit-ingest-secret"

# Shared secret for the Receipt Pipeline v2 intake endpoint
# (src/app/api/receipts/intake/route.ts). Same reasoning as
# DEPOSIT_INGEST_SECRET above: nothing external depends on this value, it
# only gates e2e/receipt-intake.spec.ts against the throwaway DB, so it is
# a literal rather than a repo secret. The spec's "env var unset" case is
# a unit test (tests/receipt-intake-auth.test.ts), not this job.
RECEIPT_INTAKE_SECRET: "e2e-ci-receipt-intake-secret"
# The archive mirror's key is DELIBERATELY different — the specs assert
# that cross-use between the two capabilities is a 403.
RECEIPT_ARCHIVE_SECRET: "e2e-ci-receipt-archive-secret"

# The deposit sweep's live-apply switch (src/lib/deposit-sweep.ts). It is
# OFF by default — booking money on an amount-only match is Justin's
# decision, not a default — so the e2e cases that exercise the MONEY
# WRITE need the server under test configured the way a live deployment
# would be. Everything here runs against the throwaway CI database with
# QuickBooks mocked (E2E_QBO_MOCK), so no real money can move.
DEPOSIT_SWEEP_LIVE_APPLY: "true"

# Forces the Stage A daily-log task matcher (daily-log-task-match.ts) onto
# its deterministic keyword fallback instead of calling Gemini, so
# time-suggestion.spec.ts's Stage A end-to-end test is reproducible in CI.
Expand Down
Loading