Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
150 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
23f1f9e
feat(expenses): Phase 3 attribution schema — Expense.projectId, tax, …
Clarion1631 Sep 2, 2026
2a38d69
feat(expenses): one attribution resolver, one copy of the cost-code r…
Clarion1631 Sep 2, 2026
968ba3d
feat(expenses): every writer stamps the job, and cost-code provenance
Clarion1631 Sep 2, 2026
1b2145f
refactor(expenses): every money-path reader resolves a job through on…
Clarion1631 Sep 2, 2026
9ac81ae
feat(expenses): attribution backfill — dry-run default, coverage tabl…
Clarion1631 Sep 2, 2026
20b3a03
feat(reports): /reports/tax-paid-at-source — the WA excise deduction
Clarion1631 Sep 2, 2026
5cbbdb8
fix(ai): cost-forecast reads spend the same way the variance report does
Clarion1631 Sep 2, 2026
a4b3430
fix(qbo-sync): split the projectId fill out of the sync update; scope…
Clarion1631 Sep 2, 2026
e6264d8
fix(charts): rank spend by the resolver, not by estimateId — the page…
Clarion1631 Sep 2, 2026
5ca7213
fix(backfill): an itemId on another job's line item is a data problem…
Clarion1631 Sep 2, 2026
8d44879
fix(reports,auth): label and authorize an expense by the project it i…
Clarion1631 Sep 2, 2026
ad059e4
fix(tax-report): integer cents, company-timezone periods, formula-saf…
Clarion1631 Sep 2, 2026
7e545af
fix(migration): verify the FK by DEFINITION, not by its name
Clarion1631 Sep 2, 2026
dbe7131
fix(expenses): attribution is write-once; stale-plan predicates; CSV …
Clarion1631 Sep 2, 2026
7e52a26
fix(expenses): backfill ordering, phase scope everywhere, no default …
Clarion1631 Sep 2, 2026
7fd2150
fix(tests): the phase-scope test must not need a JWT secret
Clarion1631 Sep 2, 2026
9885f1c
fix(expenses): authorize the expense PUT; fail closed on phases; scop…
Clarion1631 Sep 2, 2026
dc98f68
fix(expenses): tax corrections get their own PATCH; DELETE authorized…
Clarion1631 Sep 2, 2026
918da9c
fix(expenses): PATCH can correct tax; the deduction invariant is a DB…
Clarion1631 Sep 2, 2026
0bfbd44
ci: retrigger checks (push event was not queued)
Clarion1631 Sep 2, 2026
7876e28
refactor(expenses): one definition of the cost-code fallback after th…
Clarion1631 Sep 2, 2026
2f1722e
fix(migration): record the CHECK exactly as Postgres renders it
Clarion1631 Sep 2, 2026
a7c326d
fix(expenses): company-day dates, tax-vs-gross invariant, overhead ex…
Clarion1631 Sep 2, 2026
b396576
chore(rebase): onto Phase 1 head 4daa8c20 (phase re-validation, two-s…
Clarion1631 Sep 2, 2026
6401828
fix(expenses): retire tax on deactivation, close the review lifecycle…
Clarion1631 Sep 2, 2026
5471c62
fix(expenses): two-step capture rules, already-booked fill, reader mi…
Clarion1631 Sep 2, 2026
168d899
fix(expenses): unblock E2E, flag invalidated allocations, guard the b…
Clarion1631 Sep 2, 2026
52ba7d5
fix(expenses): one per-expense lock, attribution in every CAS, projec…
Clarion1631 Sep 2, 2026
643a319
fix(expenses): updatedAt survives the pre-deploy window; delete + bac…
Clarion1631 Sep 2, 2026
c3850e1
fix(scripts): run under Node 20; backfill re-reads and re-plans under…
Clarion1631 Sep 2, 2026
5db5f38
fix(expenses): suggestions read the persisted row; one script writes …
Clarion1631 Sep 2, 2026
cebe23d
test(receipts): follow authorizeLateFields to the Denial shape
Clarion1631 Sep 2, 2026
bef26f4
fix(expenses): any amount change re-opens a tax classification
Clarion1631 Sep 2, 2026
3a830b6
fix(expenses): PUT parses the amount once, and zero is an amount
Clarion1631 Sep 2, 2026
db56fa5
feat(expenses): taxSource provenance, lock-then-read fill, one-transa…
Clarion1631 Sep 2, 2026
460ca4b
fix(backfill): re-read the facts each write rests on, at write time
Clarion1631 Sep 2, 2026
bc6fca3
fix(expenses): a review flag clears only on an explicit acknowledgement
Clarion1631 Sep 2, 2026
4162d88
fix(scripts): build the suggester's Prisma client lazily
Clarion1631 Sep 2, 2026
b648b6f
fix(expenses): one tax bound for both writers, and lock what a backfi…
Clarion1631 Sep 2, 2026
e99f9f6
fix(expenses): signed tax, per-decision provenance, locked phase reva…
Clarion1631 Sep 2, 2026
31fe0ef
fix(expenses): one signed-credit model, and phase validity as a trans…
Clarion1631 Sep 2, 2026
6247f27
fix(expenses): four tax states, capture provenance, and the phase inv…
Clarion1631 Sep 2, 2026
0962751
fix(proxy): allowlist anonymous Server Action dispatch instead of den…
Clarion1631 Sep 2, 2026
51a5ef4
fix(expenses): a blank tax says which blank it is, and a fallback job…
Clarion1631 Sep 2, 2026
afda5be
fix(receipts): inline route shares decideSource; replays verify the o…
Clarion1631 Sep 2, 2026
a9d7494
fix(expenses): taxAtSource is derived, and attribution is written as …
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
dfa06fc
fix(expenses): every writer of the pair re-reads it under lock, and s…
Clarion1631 Sep 2, 2026
e663fa0
fix(tests): add DATABASE_URL mock for CI unit tests
Clarion1631 Sep 2, 2026
862c7d7
fix(receipts): round-17 Codex findings — Expense lifecycle, read dead…
Clarion1631 Sep 2, 2026
dc7bf48
fix(expenses): refuse a QBO attribution write when the estimate moved…
Clarion1631 Sep 2, 2026
dddeeb4
ci: retrigger Codex gate (runner sandbox errors on prior attempts)
Clarion1631 Sep 2, 2026
45648bb
fix(expenses): reject non-number tax figures and non-string costCodeI…
Clarion1631 Sep 2, 2026
ca69538
fix(expenses): restore original line endings on untouched lines in ro…
Clarion1631 Sep 2, 2026
870577c
fix(receipts): round-18 Codex findings — fenced deletes, live-lease r…
Clarion1631 Sep 2, 2026
467c33d
fix(expenses): PR #442 round-31 gate — taxSource provenance scoping, …
Clarion1631 Sep 3, 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
08d0514
fix(expenses): guard OCR tax fill against a manually-set deduction base
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
8322ef9
fix(expenses): PR #442 round-31 gate — atomic receipt ingest, distinc…
Clarion1631 Sep 3, 2026
90b83f0
merge: bring feat/phase1-intake-core (47ebd90b) into phase3 — clears …
Clarion1631 Sep 3, 2026
a8730ae
merge: bring origin/main (e01b15cb, #446 apply-script import guard) i…
Clarion1631 Sep 3, 2026
fc93568
Merge remote-tracking branch 'origin/main' into feat/phase3-attribution
Clarion1631 Sep 3, 2026
bf73ec7
fix(expenses): PR #442 round-32 gate — estimate-move pair guard, proo…
Clarion1631 Sep 3, 2026
28071e1
fix(expenses): PR #442 round-33 gate — ingest lock, whole-document va…
Clarion1631 Sep 3, 2026
4bf6f11
fix(expenses): PR #442 round-34 gate — receipt identity column, base-…
Clarion1631 Sep 3, 2026
c9b0fe6
fix(expenses): PR #442 round-35 gate — PUT tax re-validation, receipt…
Clarion1631 Sep 3, 2026
fcb1b2d
Merge origin/main (#448 deposit sweep) into feat/phase3-attribution
Clarion1631 Sep 3, 2026
0ba62d4
chore(qbo): restore CRLF on src/lib/quickbooks.ts to match main
Clarion1631 Sep 3, 2026
0c5e610
fix(expenses): PR #442 round-36 gate — split-job rollout window, inde…
Clarion1631 Sep 3, 2026
66be228
fix(expenses): PR #442 round-37 gate — human phase-clear, amount/tax …
Clarion1631 Sep 3, 2026
19f96d4
fix(expenses): PR #442 round-38 gate — FK-implicit lock order, backfi…
Clarion1631 Sep 3, 2026
98211ff
fix(expenses): PR #442 round-39 gate — import-safe DB test, and a per…
Clarion1631 Sep 3, 2026
24b120f
fix(expenses): PR #442 round-40 gate — Expense locks last, signed mon…
Clarion1631 Sep 3, 2026
b44b335
fix(expenses): PR #442 round-41 gate — backfill locks jobs first, PUT…
Clarion1631 Sep 3, 2026
9d0de5f
fix(expenses): PR #442 round-42 gate — plan before the base check, a …
Clarion1631 Sep 3, 2026
02d9d7e
fix(expenses): PR #442 round-43 gate — an ack must name the installat…
Clarion1631 Sep 3, 2026
0ad2d23
fix(expenses): PR #442 round-44 gate — migration runs shape then data…
Clarion1631 Sep 3, 2026
b06b928
fix(expenses): PR #442 round-15 gate — phase A is a sequence of short…
Clarion1631 Sep 3, 2026
8b5234d
fix(expenses): PR #442 round-16 gate — CHECK verification by normaliz…
Clarion1631 Sep 3, 2026
5383178
fix(expenses): PR #442 round-17 gate + prod-target guard — modal ack …
Clarion1631 Sep 3, 2026
7754400
fix(expenses): pin the apply script to a PROJECT REF, and take the da…
Clarion1631 Sep 3, 2026
ed1f9e7
fix(expenses): PR #442 round-18 gate — drain-window bridge for Drive …
Clarion1631 Sep 3, 2026
f72ffa5
fix(expenses): pin the prod target guard against the REAL pooler URL …
Clarion1631 Sep 3, 2026
ea0de77
Merge remote-tracking branch 'origin/main' into feat/phase3-attribution
Clarion1631 Sep 3, 2026
b9e9537
fix(expenses): PR #442 round-19 gate — the drain-window bridge no lon…
Clarion1631 Sep 3, 2026
09386f4
Merge remote-tracking branch 'origin/main' into feat/phase3-attribution
Clarion1631 Sep 3, 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
39 changes: 39 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,42 @@ MCP_SECRET=
# Bank-ledger automation: keep these separate; the posting-status key is read-only.
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=
100 changes: 100 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,96 @@ 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 phase invariant's PHANTOM hole. Everything else in that suite runs
# against a scripted fake, which has no lock manager and so cannot tell a
# verdict that holds its rows from one that merely read them.
- name: Phase-invariant proof-query lock against real Postgres
run: npx tsx --test tests/phase-invariant-db.test.ts
env:
PHASE_INVARIANT_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# The deploy bridge's TRIGGERS. Every other test of them reads the SQL as
# text; only a real server can say whether a BEFORE UPDATE trigger
# actually keeps an old writer's amount change from leaving a stale tax
# classification — or from failing the CHECK constraints outright.
- name: Expense attribution compatibility triggers against real Postgres
run: npx tsx --test tests/expense-attribution-triggers-db.test.ts
env:
PHASE_INVARIANT_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# The Project/Estimate acquisition order, both ways round. Carries its own
# pre-fix CONTROL: the old Estimate-then-Project sequence really does
# raise 40P01 against a Project-first editor, so the tests that follow are
# measuring the fix rather than an interleaving that never collided.
- name: Attribution lock order against real Postgres
run: npx tsx --test tests/attribution-lock-order-db.test.ts
env:
PHASE_INVARIANT_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# THE MIXED-VERSION DRAIN WINDOW. An old instance's INSERT is raw SQL that
# names neither new column -- a shape today's Prisma client cannot express --
# and the fix is a trigger that stamps it and takes the route's advisory lock.
# Neither half exists outside a real server.
- name: Receipt-ingest drain-window bridge against real Postgres
run: npx tsx --test tests/receipt-ingest-drain-window-db.test.ts
env:
PHASE_INVARIANT_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# The backfill's coverage headline against the report it claims to describe,
# on one seeded database. A hand-built fixture cannot catch the two readers
# drifting apart, because the fixture is written by whoever wrote the code.
- name: Backfill coverage parity with the variance report
run: npx tsx --test tests/backfill-coverage-parity-db.test.ts
env:
PHASE_INVARIANT_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# An index can EXIST and enforce nothing. A failed CREATE INDEX
# CONCURRENTLY leaves the index behind with the right name and
# `indisvalid = false`; the planner ignores it, a UNIQUE one guards
# nothing, and `IF NOT EXISTS` then matches the name forever. Only a real
# server can produce that state, so only a real server can prove the
# verifier notices it and rebuilds it.
- name: Apply-script index validity against real Postgres
run: npx tsx --test tests/apply-script-index-db.test.ts
env:
PHASE_INVARIANT_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# The excise deduction's FILTER, against the real planner. A relation
# filter like `{ estimate: { projectId: null } }` compiles to an EXISTS,
# so it silently excludes rows with no estimate at all -- the shape
# ON DELETE SET NULL creates. Only a real database can answer that.
- name: Tax-at-source report filter against real Postgres
run: npx tsx --test tests/tax-at-source-report-db.test.ts
env:
PHASE_INVARIANT_DB_TEST_URL: postgresql://probuild:probuild@localhost:5432/probuild_migrations

# THE APPLY SCRIPT, RUN FOR REAL (round 46, item 0).
#
# Every other test in this repo imports the script's CONSTANTS and checks
# their text. `main()` -- the part that connects, runs the phases and
# VERIFIES the result -- had never executed in CI, and it was broken: the
# CHECK-constraint comparison used regexes that do not match Postgres 16's
# rendering, so the script exited 1 on a database it had just built
# correctly. A deploy blocker no amount of source-text testing could see.
#
# The driver builds a PRE-PHASE-3 database (the phase-3 migration is moved
# aside), so this is a genuine from-scratch apply, then runs pre-deploy,
# post-deploy, and pre-deploy again for idempotency.
- name: Apply script end-to-end against real Postgres
run: node scripts/ci-apply-expense-attribution-e2e.mjs
env:
APPLY_E2E_SERVER_URL: postgresql://probuild:probuild@localhost:5432/postgres
APPLY_E2E_DB: probuild_apply

- name: Prove the blind-spot checker rejects function and trigger drift
run: npx tsx --test tests/migration-history-blind-spots.test.ts
env:
Expand Down Expand Up @@ -198,6 +288,16 @@ 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
Expand Down
Loading
Loading