Repository navigation
C4-1b: AP fixes on the contract (ADR 0008 7.4) - #78
Merged
Merged
Conversation
The C4-1b migration of ADR 0008 section 12: vendor_invoices gains created_at NOT NULL, revision, the number (AP-, a sequence, backfilled in (created_at, id) order with the DEFAULT minting through the writer's transaction), the branch (from the purchase order's branch when that column exists, else the default branch; a defaults trigger serves raw inserts), the currency, a status CHECK on today's values (lowercase spellings uppercased and unknown values become PENDING, each with a notice), po_id as a real foreign key (orphans set null, with a notice), UNIQUE (vendor_id, invoice_number) after duplicates are suffixed #2, #3 in (created_at, id) order, amount_open and gl_entry_id; the lines gain position, purchase_order_line_id, product_id, a unit_price widened to scale 4 and po_freight_charge_id (the carrier freight line link of 7.3, written from package C's freight rule on); the keyset indexes. The test builds the awkward legacy rows in a scratch database (three duplicates of one vendor number, an orphan po_id, a NULL created_at, odd statuses, paid, partial and voided rows), reads every backfill back, checks the constraints refuse what the old columns took, applies the migration a second time (a no-op), runs the down (which keeps the suffixed duplicates, normalized statuses and nulled orphans, and refuses while a unit price is finer than cents) and applies again. Signed-off-by: colton <colton@futurebuild.ai>
The vendor invoice acts rebuilt on the module recipe:
- approve loads the lines inside the transition's transaction and posts
the entry through gl.PostEntry there (a debit leg per line on the
line's account, the tax spread across the line accounts pro rata with
the last line taking the remainder, the credit to 2010 for the whole
total); a posting failure fails the act with its cause (a closed
period the 409 period_closed blocker, a legacy line with no account
the line_needs_account blocker) where the base posted an entry with
only the payable leg and swallowed the error;
- VOIDED is reachable by a transition that reverses the entry (a
pending bill has nothing posted and simply dies; a bill with money
applied is refused has_payments);
- a payment applies only to approved or partial bills (the pending,
paid and voided ones refused invoice_not_approved), locks them in id
order with the money applying in the order the request named them,
and posts its entry through gl.PostEntry inside the act (a failure
fails the act); more than the named bills still owe is refused
exceeds_open and writes nothing;
- the wire: Gable's own number (AP-) as number, the vendor's as
vendor_invoice_number, the cursor list with vendor_id, po_id and
status filters, the error envelope with one 400 naming every field,
revision with If-Match (428 without, 409 stale), lowercase statuses,
cents and ten thousandths, the transitions route replacing /approve,
POST /ap/invoices/{id}/transitions, and the branch wall on every
read and write (the create's payload branch held by the guard);
- vendor_invoice.created, .approved, .partial, .paid and .voided
through the outbox as the last statements of their transactions, and
an audit row per act; a failing event or audit write rolls the whole
act back.
The two broken sync functions are deleted from internal/gl (their only
caller was AP) and gl gains the 2010 account code constant; the matcher
reads the new invoice types and compares the wire's scale 4 line facts
through its own unconverted arithmetic until its own conversion; serve
wires the service with the outbox, audit log, transaction runner and
branch guard behind scoped roles, as every converted module is.
The tests carry the recipe's set (the create shape, exact money, the
validation 400, the list and its cursor, the error envelope, the
revision preconditions, the lifecycle edges with their events,
idempotency, the branch wall) beside the three live failures in their
converted form, the GL ties after every act (each entry balanced, the
2010 control account equal to what the open bills owe), the tax spread,
the failing event and audit writes rolling each write back, the pool 4
contenders (two payments of one invoice, a payment racing a void, three
racers on one revision) and the gated saturation proof.
Signed-off-by: colton <colton@futurebuild.ai>
The fragment describes the vendor invoice routes exactly (the cursor envelope, the cents and ten thousandths, the transitions route with its precondition and blockers, the payment and aging routes in their today shapes with the payment act's new refusals); the model binding ties the new ApVendorInvoice, ApVendorInvoiceSummary and ApVendorInvoiceLine to the Go structs; the census carries the transitions route in place of /approve and the pending list empties; CONTRACT-CHANGES gains the C4-1b rows. The ap and ap_payments goldens re-record on the converted routes: the create in cents, ten thousandths and a decimal string quantity, the 428 transition without a precondition, the approve through /transitions and its 409 on a second attempt, the events feed step, the partial and settling payments, the paid bill's further payment refused invoice_not_approved, the void with payments refused has_payments, the validation 400, the status filters and the pending void. The harness gains the expenseAccount seed anchor the bills' lines name. Signed-off-by: colton <colton@futurebuild.ai>
The service walks the invoice list's cursor to the last page; the types carry the wire's cents, ten thousandths, decimal string quantities, lowercase statuses, revision and both numbers (Gable's AP- and the vendor's own); the page approves through the transitions route with the revision it loaded, pays from amount_open_cents, and prices the draft total in exact integer arithmetic with the server's one rounding (half away from zero), so the preview never drifts a cent from the bill that is filed; the bill form sends cents, a decimal string quantity and ten thousandths with each line's gl_account_id. The tests assert the request bodies at the wire's scales beside the rendering, and the generated client schema follows the fragment. Signed-off-by: colton <colton@futurebuild.ai>
The two broken sync functions leave internal/gl (their only caller was AP: SyncVendorInvoice posted only the payable leg and swallowed the insert's error, SyncVendorPayment resolved its accounts by name the same way), and gl gains the 2010 account code constant beside the sales postings'; serve wires the AP service as every converted module is (outbox writer, audit log, database as transaction runner, branch guard) behind scoped admin, owner, finance with the branch middleware, and the wall fixture's AP stub follows the new invoice type. Signed-off-by: colton <colton@futurebuild.ai>
The matcher reads the converted invoice types: the purchase order link and the lines keep their places, and the line's quantity and unit price arrive as the wire's scale 4 facts (a decimal string and ten thousandths), which the unconverted tolerance arithmetic reads through its own float and cents lens until the matcher's own conversion (C4-2 E) rebuilds it on the line keys of ADR 0008 7.3. Signed-off-by: colton <colton@futurebuild.ai>
Brings the branch level with refactor/v1 at 76d266f (the delivery wire contract of C5-1d, the follow-ups and the drafts and links work of C5-2a, and the delivery defect fixes). Conflicts and resolutions: - docs/refactor/CONTRACT-CHANGES.md, one content conflict where this branch's nine C4-1b rows and refactor/v1's C5-1d and C5-2a rows were both appended at the end of the same table. Resolved as the union: the refactor/v1 rows first, then the nine C4-1b rows, every row of both sides exactly once. Verified by script: ours 227 rows, theirs 258, shared base 218, merged 267, the sorted merged rows equal the sorted union of both sides, no duplicate row. Generated files, regenerated from their sources, never hand merged: - core/api/openapi.yaml by go run ./api/tools/merge (319 paths, 406 operations, 511 schemas) - core/api/ROUTES.txt by go run ./cmd/census -write (406 routes) - web/packages/api-client/src/schema.d.ts by openapi-typescript over the merged openapi.yaml Signed-off-by: colton <colton@futurebuild.ai>
Both were last recorded in the C2-4 era; this branch's own 7.4 work (the approve posting through gl.PostEntry, the payment seam rewrite, the aging from amount_open) post-dates that recording, so the two knock-on goldens drifted. The refactor/v1 merge changed no AP, GL, dashboard, reports or seed line (verified by diff 52bff83..76d266f on those directories: empty), so the merge itself changed no recorded shape here; every other golden, including all the delivery ones the merge brought, re-recorded byte identical under -update. - gl_accounts_entries.json: the ap_payments group's approve now posts the VENDOR_INVOICE entry for bill AP-000006 (10000 cents), which no earlier recording held, so every later entry number shifts by one (143 to 144 and the reversal memo follows) and the positional id tokens renumber; the payment postings' memo casing follows the seam rewrite (Vendor Payment to Vendor payment, the old sync functions deleted). - clockwindow.json: the AP aging reads amount_open, so the group's fully paid 100 dollar bill (paid 40 then 60) owes nothing in the current bucket: BC Roofing Wholesale current 10000 to 0, total 222212 to 212212. No other response changed. The suite ran twice after the re-record with both runs green and no further drift. Signed-off-by: colton <colton@futurebuild.ai>
TestMigration106_OnTheSeededDatabase applies the repository's own seed (DEMO_SEED=1, the seed binary, the scratch database's URL on its command) and then migration 106 over awkward legacy bills written on top of it, following migration 099's pattern: only this proof reaches the branch backfill's purchase order source, because the seed writes purchase orders on the yards while every fixture row falls to the default branch. The test went red first and found a live defect in the migration: the guard compared information_schema's table_schema to the literal string 'current_schema', never to the schema's name (migrations 092 and 094 call current_schema()), so the purchase order branch backfill was dead code and every bill took the default branch. The guard now calls current_schema(). The migration is this branch's own and unmerged, so the file is corrected rather than followed by a fix migration. The test holds: a bill tied to a real seeded purchase order takes that order's branch (and keeps it through the down-up cycle), the duplicate pair suffixes in (created_at, id) order, the orphan po_id is nulled, lowercase statuses normalize, every row ends with a unique AP- number, a branch, a currency and amount_open at total less paid, a second up is a no-op, and the down then up over the seeded database loses no row. Signed-off-by: colton <colton@futurebuild.ai>
The id order half of ADR 0008 7.4's payment line (section 9's lock order) had no biting test: the concurrency proof's payment race names one bill, where every lock order agrees. TestTx_PaymentLocksInIDOrder races three payments across the same three approved bills named in rotated orders, five rounds at pool size 4, and holds that every contender succeeds, the cents apply exactly once and the control account ties after each round. Red first: with the id sort removed the requests lock in caller order and Postgres cancels a contender with deadlock detected, SQLSTATE 40P01, in round 0. Signed-off-by: colton <colton@futurebuild.ai>
The merge with origin/refactor/v1 brought six unformatted files (units_report_test.go, chain_c5_2a_test.go, serve.go, feed_test.go, levels_embed_test.go, unit model.go); they are unformatted at the integration head itself, and no CI job checks gofmt there. This commit is formatting only, no line of code changes, so this branch's tree is gofmt clean as its gates require. Signed-off-by: colton <colton@futurebuild.ai>
The approve handler compared and reloaded invoiceId, a name that survived an earlier draft of the page; the compiler sees only invoice, so the desk, the local stack image and the Playwright bundles all refused to build (TS2552 on two lines). The handler now uses invoice.id, and the workspace build, lint and test run green. Signed-off-by: colton <colton@futurebuild.ai>
Signed-off-by: colton <colton@futurebuild.ai>
…one with none A bill the base approved holds its journal entry (the removed sync posted it with source VENDOR_INVOICE and source_ref_id the bill) but no pointer to it, so after 106 every legacy approved bill had gl_entry_id NULL and its void posted no reversal: the bill died owing nothing while the credit stayed in 2010 (both reviews' P2-1). 106 now links every approved or partial bill to its newest posted, unreversed VENDOR_INVOICE entry and counts the bills left without one in a notice. The void transition refuses an approved or partial bill with no linked entry (409, entry_not_linked) instead of voiding it silently. Red first: TestWire_VoidLegacyApprovedBill failed on 27dce25's code (the void answered 200, wrote no reversal and left control 10 against open 0; the unlinked bill voided 200) and TestMigration106_LinksLegacyApprovedEntries failed (the link stayed null, no notice). Both pass with the backfill and the refusal; every money act in the new test asserts the ties (entries balanced, 2010 equal to the open bills' amount_open). Signed-off-by: colton <colton@futurebuild.ai>
The aging query summed amount_open over every branch and the payments list showed payments applied to other branches' bills, while every other AP read and write held the wall (both reviews' P2-2). The aging query now carries the same wall predicate the list uses, and the payments list filters through the bills a payment applies to (a payment whose every bill is outside the wall is not the caller's to read). Red first: TestWire_BranchWall failed on the previous code (the aging total behind the wall was 1610 for a caller whose branch holds 1010, and the payments list behind the wall showed the other branch's payment). Green with the two predicates; the test now covers a user bound by user_locations and a key bound to one branch, on aging, the payments list and the payment act itself, with the GL ties asserted after the payment. Signed-off-by: colton <colton@futurebuild.ai>
…ready holds The suffix pass wrote X #2 without checking whether the vendor already had a bill numbered X #2, so a genuine suffixed number beside duplicate X rows made the UNIQUE fail and rolled the whole migration back, blocking the upgrade (review r2 P2-3; the down's own comment names the case). Each suffixed row now takes the next suffix no row of that vendor holds, so the duplicates of X beside a genuine X #2 become X #3 and X #4. Red first: TestMigration106_OnAwkwardLegacyRows, grown with a genuine DUP-1 #2 beside three DUP-1 rows, failed on the previous code (could not create unique index vendor_invoices_vendor_number_key). Green with the next free suffix, the genuine number kept. Signed-off-by: colton <colton@futurebuild.ai>
The apply loop walks invoice_ids with the locked snapshot taken before the first application, so a repeated id re-applied a bill off stale numbers and the amount_open CHECK turned the client mistake into a bare 500 (review r1 P2-3, review r2 P2-4). PayRequest.Parse now refuses a repeated id with a 400 on invoice_ids[i] (appears twice) before any money moves. Red first: TestWire_PaymentRefusesDuplicateInvoiceIDs failed on the previous code (pay 12 cents with [A, A] against a 10 cent bill answered 500 internal_error). Green with the check; the test asserts nothing is written and the GL ties hold after the refusal and after a following clean payment. Signed-off-by: colton <colton@futurebuild.ai>
The bill is 10 cents (price 1000 ten thousandths); the refused duplicate asks 12 cents and the clean following payment 10, not 1200 and 1000. Signed-off-by: colton <colton@futurebuild.ai>
The payment request's amount was float dollars converted with one silent rounding (10.005 became 1001 cents, 0.1 + 0.2 became 30), the one float left on the AP money paths (review r2 P3-4, promoted by the lead: ADR 0008 section 11 lists payments and aging as converted). The request now takes amount_cents, int64 cents like every other money field: a decimal is a 400 naming amount_cents and the old float amount field is refused as unknown. The response's amount stays cents as it already was, and the rest of the payment request shape keeps its today form until the payment routes' own conversion. The desk AP page sends cents (its form still speaks dollars to the clerk, converted once and exactly on submit), and the CONTRACT -CHANGES row records the change. Red first: TestWire_PayAmountIsCents failed on the previous code (amount_cents was an unknown field, 400, where 201 was wanted). Green with the new field; the golden ap_payments is re-recorded for exactly the five pay request bodies (amount 40, 60 and 1 dollars become amount_cents 4000, 6000 and 100), every response unchanged. Signed-off-by: colton <colton@futurebuild.ai>
…lows the trigger's rule A hand-written CANCELLED bill became PENDING owing its whole total, and the amount_open backfill zeroed only VOIDED, so a short-paid PAID bill migrated still owing and an overpaid PARTIAL one silently clamped (review r2 P3-1 and P3-2). Cancel-like values (CANCELLED, CANCELED) now map to the module's voided state owing nothing, and the backfill uses the insert trigger's rule: zero on a voided or paid bill, the total less paid otherwise. A notice names the short-paid PAID and overpaid PARTIAL rows so an operator can mend them. Red first: TestMigration106_OnAwkwardLegacyRows, grown with a CANCELLED, a short-paid PAID and an overpaid PARTIAL row, failed on the previous code (CANCELLED read back PENDING with 60.00 open, PAIDSHORT 6.00 open). Green with the mapping and the rule. Signed-off-by: colton <colton@futurebuild.ai>
An approve whose control account (2010) was missing, or a payment whose entry debits it, answered 500 internal_error: nothing was written, but the wire told the operator nothing about the gap to mend (review r2 P3-3). gl wraps the refusal as ErrAccountMissing and both AP postings map it to a 409 blocker account_missing naming the code. Red first: TestWire_ApproveAccountMissing failed on the previous code (the approve answered 500 with no 2010 in the chart). Green with the mapping; the test covers the approve and the payment, asserting nothing is written and the ties hold. Signed-off-by: colton <colton@futurebuild.ai>
The module recipe says a create that rolls back abandons its number and never reuses it; the number comes from the sequence DEFAULT, so the behaviour was already right, but nothing proved it (review r1 P3-1). TestTx_RolledBackCreateAbandonsItsNumber creates a bill, lets a create fail after its insert (the event write fails), and shows the sequence stands past the minted number with only the committed bill present, and that the next bill takes a later number. No behaviour change: the test went green on arrival, which is the point of a proof of existing behaviour. Signed-off-by: colton <colton@futurebuild.ai>
…one scaling helper The bill form silently truncated a quantity or unit price finer than the wire's four decimals (1.00005 filed as 1.0000), and exactLineCents repeated scaledTenThousandths byte for byte as an inner function (review r1 P3-3). exactLineCents now calls scaledTenThousandths, and the submit validation refuses a quantity or unit price that does not round-trip at scale 4. Red first: the new vitest case failed on the previous code (the bill with a 1.00005 price was filed, truncated to 10000 ten thousandths). Green with the refusal; all 18 AP page tests pass. Signed-off-by: colton <colton@futurebuild.ai>
ListVendorInvoices listed the headers with a Limit of 100000 and then read every bill whole with one Get per row: an N+1 read on every match run, in code this PR itself adds (review r1 P3-5, judged worth fixing because the read moved into the AP module here). Repository.ListFull reads the page of summaries and then every listed bill's lines in one query, and ListVendorInvoices filters by storage status in Go. Same answers, two queries whatever the page holds; the matching tests and the ap suite pass unchanged. No red first: the behaviour is identical, only the query count changed. Signed-off-by: colton <colton@futurebuild.ai>
One row per behaviour change this fix round makes: the entry_not_linked void refusal, the branch wall on aging and the payments list, the repeated invoice_ids 400, the account_missing 409, the migration 106 mapping fixes, and the amount_cents conversion beside its code change. One note records the documented boundary r2 raised: a legacy pending bill with no lines, or a line with no account, can never be approved and voiding is its only way out, until line editing arrives. Signed-off-by: colton <colton@futurebuild.ai>
… a 500 Review r4 P2-1: the base's approve posted a one leg entry (the Accounts Payable credit with no debit; it loaded no lines), migration 106 linked it, and the void's reversal mirror was refused as unbalanced, so voiding every base approved bill answered 500. Migration 106 now links only entries whose debits equal their credits: the newest posted, unreversed, balanced VENDOR_INVOICE entry of each approved or partial bill. A bill whose only entry has one leg stays unlinked and a notice of its own names it (post the missing debit legs, then link), apart from the bills with nothing to link. The void maps gl.ErrUnbalanced to a 409 invalid_state_transition with the entry_unbalanced blocker and writes nothing; the fragment, the assembled openapi and the CONTRACT-CHANGES row say so. The migration test seeds the base's real one leg shape (one credit leg, no debit) and asserts both the refusal to link and the notice; the wire test rewrites an approve entry into that shape and asserts the refused void leaves the bill, the entry count, the only unbalanced entry (the seeded one) and both sides of the tie untouched. Signed-off-by: colton <colton@futurebuild.ai>
Review r4 P2-2: the base's create validated no sign, so a vendor credit entered as a negative bill is a plausible legacy row; for it amount_open became 0, the range CHECK 0 <= total failed, and the whole migration rolled back, blocking the upgrade. Chosen shape: map, not refuse. The migration's rule everywhere else is that an awkward legacy row never blocks the upgrade (duplicates are suffixed, orphans nulled, cancel-like statuses mapped, each with a notice), and a refusal, however well named, still blocks every table behind one bad row. A negative total bill owes nothing as a bill: it becomes VOIDED with open 0 and a notice naming the count and telling the operator to re-enter it as a credit, exactly the cancel-like mapping. The range CHECK's upper bound (never more than the total) now holds only while the bill owes: a voided or paid bill is zero by rule at any total, so the constraint admits the mapped rows and a negative total already VOIDED. The down keeps the mapping with the other normalized statuses. The awkward rows test seeds a PENDING -10.00 bill (mapped, noticed) and a VOIDED -5.00 one (kept, admitted by the CHECK) and asserts the migration applies, the statuses, the open amounts and the down/up cycle. The CONTRACT-CHANGES migration row gains the mapping and the CHECK's carved shape, and its link clause takes the balanced wording the previous commit introduced. Signed-off-by: colton <colton@futurebuild.ai>
Review r4 P2-3: the walled payments list needed an application row on a bill inside the wall, so a legacy payment with no application rows (the base wrote one whenever invoice_ids was empty) vanished from every caller, an administrator with no header, an unbound key and a single branch install included. The wall now filters only a walled caller; an open wall lists every payment. TestWire_BranchWall seeds an applicationless legacy payment and asserts both callers (red on the old query, green now). CONTRACT-CHANGES states the rule, the cross branch payment shown whole, the account_missing row's true code (409 conflict, review r3 P2-1) and points the superseded older rows at their review fix rows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: colton <colton@futurebuild.ai>
Review r3 P2-2: ListFull had no test that could fail. Two bills of different line counts, each carrying its own lines in order, with and without the status filter; it fails with AND false on the lines query. The approve in it is followed by the GL ties. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: colton <colton@futurebuild.ai>
Review r4 P3-a: an amount_cents past the payment column's NUMERIC(12,2) (the int64 maximum included) is a 400 naming amount_cents, not a 500 numeric overflow; TestWire_PayAmountIsCents asserts it with the ties after (red before: 500). Review r4 P3-b and P3-c: migration 106 raises a notice counting the VOIDED bills (hand cancelled or negative total) that still hold a posted, unreversed entry, and another counting the bills posted more than once. ADR 0008 7.4 does not ask the migration to reverse a cancelled bill's credit, so it is counted for an operator, not reversed. The migration test asserts both counts (red on the old migration). Review r4 P3-f: the stray SyncVendorInvoice comment heading PostTillOverShort is removed. Review r3 P3-1: the older CONTRACT-CHANGES rows point at the review fix rows that supersede them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: colton <colton@futurebuild.ai>
The one leg void fix changed an openapi description; the Frontend job's drift check (openapi-typescript --check) failed on the stale generated file. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: colton <colton@futurebuild.ai>
Signed-off-by: colton <colton@futurebuild.ai>
…sals state the limit A bill that prices to zero approves with no journal entry, and the void refused it for ever with entry_not_linked (review r6 P2-1). The void now refuses only a bill that owes and has no linked entry. The refusal texts, the transitions description and the CONTRACT-CHANGES row no longer name an operator step that does not exist (review r5 P2-1): no route links an entry to a bill yet, so such a bill stays approved and payable until a later repair item. TestWire_VoidZeroBill covers a zero price and a price that rounds to 0 cents, with the ties after each act. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: colton <colton@futurebuild.ai>
…nd negative paid bill, and state the range rule exactly A hand edited negative amount_paid on a positive bill opened the bill past its total and aborted the migration on the range CHECK (review r6 P3-1): amount_open is now clamped between zero and the total, and a notice counts such rows. The overpaid notice counts APPROVED bills as well as PARTIAL ones (review r6 P3-3). The CHECK says what the code means: a voided or paid bill owes zero, any other owes between zero and its total (review r5 P3-2). TestMigration106_OnAwkwardLegacyRows seeds both rows; it failed on the old migration with the abort, and each of the clamp, the notice filter and the CHECK fails it alone when reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: colton <colton@futurebuild.ai>
The bill create summed its lines and took the tax with no bound, so a tax, unit price, line total, subtotal or total past its NUMERIC column answered 500 on a numeric overflow (reviews r5 P3-1 and r6 P3-2, the create twin of the payment ceiling). Each is now a 400 naming the field that tipped it over, with nothing written. TestWire_CreateMoneyCeilings failed on all six cases before the change (500 each) and creates a bill near the limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: colton <colton@futurebuild.ai>
Signed-off-by: colton <colton@futurebuild.ai>
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.
C4-1b: the AP fixes of ADR 0008 section 7.4 on the wire contract, the migration of section 12 and the builds of section 13, level with
refactor/v1at 76d266f. Every line with its proof, and each proof bitten by a mutant (the mutant named, the failure it produced):ADR 0008 section 7.4 (the AP posting fixes)
TestWire_ApproveEntryCarriesLineDebitsreads the entry back: the line's debit on its account beside the2010credit, balanced. Mutant (the entry posts payable legs only): FAIL,the entry's accounts = map[2010:{10000 10000}], want 5020 (the line) and 2010 (the payable).TestWire_ApproveSpreadsTaxpins the tax spread pro rata with the last line taking the remainder.gl.PostEntryinside the approve transaction and a failure fails the act:TestWire_ApprovePostingFailureFailsTheActcloses the fiscal period holding the bill's date and the approve answers 409 with theperiod_closedblocker, the bill staying pending at revision 1 with no entry written. Mutant (the posting error logged and swallowed): FAIL, the approve answered 500 with no cause.TestWire_ApproveRefusesLineWithoutAccountholds the legacy line refusal (line_needs_account, nothing posts).VOIDEDis reachable by a transition that reverses the entry:TestWire_Lifecyclevoids a pending bill (no entry to reverse, owes 0) and an approved bill (exactly one reversal entry), and refuses the void of a bill with money applied with thehas_paymentsblocker. Mutant (the void skips the reversal): FAIL,the void wrote 0 reversals, want 1.vendor_invoicesgains branch, currency, revision, a status CHECK,po_idas a real FK andUNIQUE (vendor_id, invoice_number): migration 106 withTestMigration106_OnAwkwardLegacyRows. Mutant (the duplicate suffixing removed): FAIL,could not create unique index vendor_invoices_vendor_number_key (SQLSTATE 23505).approvedorpartial, locked in id order (section 9):TestWire_PaymentRefusesUnapprovedInvoicerefuses a pending bill with theinvoice_not_approvedblocker and writes nothing. Mutant (the status check removed): FAIL, the refusal came asexceeds_openinstead. The id order half:TestTx_PaymentLocksInIDOrderraces three payments across the same three approved bills named in rotated orders, five rounds at pool size 4; every contender succeeds, the cents apply exactly once and the control account ties after each round. Mutant (the id sort removed): FAIL,deadlock detected (SQLSTATE 40P01)in round 0.TestTx_Concurrency_Pool4ThreeContendersadds the two payments of one invoice (the 10 cents applied exactly once) and a payment racing a void (exactly one winner).ADR 0008 section 12 (migration 106, planned 098)
dabe8e0plus45fe983.TestMigration106_OnAwkwardLegacyRows: duplicates suffixed#2,#3in(created_at, id)order with a notice, the orphanpo_idset null with a notice, the NULLcreated_atfilled, odd statuses normalized with notices,numberunique and backfilled in(created_at, id)order with the sequence past the backfill, the currency,amount_open(zero on a voided bill), the constraints refusing the old shapes, the lines positioned, unlinked, with a scale 4 unit price; a second up is a no-op; the down rolls the shape back, refuses while a unit price is finer than cents, and the up applies again to the same result.TestMigration106_OnTheSeededDatabase(new,45fe983) applies the repository's own seed (DEMO_SEED=1, the seed binary, the scratch URL on its own command) and then migration 106 over awkward legacy bills written on top, the pattern of migration 099's seeded proof. Only this proof reaches the branch backfill's purchase order source: a bill tied to a real seeded purchase order takes that order's branch, not the default branch every fixture row falls to, and keeps it through the down-up cycle; the duplicate pair suffixes, the orphan nulls, lowercase statuses normalize, every row ends with a uniqueAP-number, a branch, a currency andamount_openat total less paid, a second up is a no-op, and the down then up over the seeded database loses no row. The test went red first and found a live defect: the guard comparedinformation_schema'stable_schemato the literal string'current_schema', never to the schema's name (092 and 094 callcurrent_schema()), so the purchase order branch backfill was dead code;45fe983corrects the file, which is this branch's own and unmerged.ADR 0008 section 13 (C4-1b builds)
1ddab4a.dabe8e0, the seeded proof and guard fix45fe983.numberAP-, the vendor's asvendor_invoice_number) andvendor_invoice.*events:TestWire_InvoiceShape,TestWire_List(the cursor, the filters, the strict query guard,include=total),TestWire_GetRefusals,TestWire_Revision(428, 409 stale, weakIf-Match),TestWire_Idempotency,TestWire_BranchWall(another branch's bill a 404 on the by-id routes, never in the list, a foreign payload branch a 403; mutant with the wall predicate neutralized: FAIL,a held caller voids another branch's bill: 409, want 404and the walled list shows it),TestWire_Aging(the buckets fromamount_open), and the acts' event order pinned inTestWire_LifecycleandTestWire_PaymentAppliesAndTiesbeside theapandap_paymentsgoldens.TestWire_ApproveEntryCarriesLineDebits, mutant FAIL above), the swallowed posting error (TestWire_ApprovePostingFailureFailsTheAct, mutant FAIL above), the payment applied to an unapproved invoice (TestWire_PaymentRefusesUnapprovedInvoice, mutant FAIL above).TestTx_FailedEventWriteRollsBackEachWriteandTestTx_FailedAuditWriteRollsBackEachWriteroll each kind of write back when the event or the audit row cannot be written (mutants that swallow the write error: both FAIL),TestTx_Pool4SaturationNeedsNoSecondConnectionholds each kind of write at pool saturation inside one transaction (mutant with one read through the pool inside the transaction: FAIL, the read sees nothing the transaction wrote), and the concurrency proofs above run at pool size 4.TestWire_PaymentAppliesAndTiesand both concurrency proofs assert each entry balanced and the2010control account equal to what the open bills owe.Level with
refactor/v1(76d266f)The merge commit
64aa638resolved one conflict,docs/refactor/CONTRACT-CHANGES.md, as the union of both sides: ours 227 rows, theirs 258, shared base 218, merged 267, the sorted merged rows equal to the sorted union, no duplicate row. The generated files were regenerated from their sources, never hand merged:openapi.yamlbyapi/tools/merge,ROUTES.txtbycmd/census -write, the TypeScript schema byopenapi-typescript. The merge brought six files unformatted from the integration head (no CI job there checks gofmt);a76c573is formatting only so this tree is gofmt clean.The
clockwindowandgl_accounts_entriesgoldens were re-recorded (31670bb): both were last recorded in the C2-4 era, this branch's own 7.4 work post-dates that recording (the approve posting, the payment seam's memo casing, the aging fromamount_open), and the merge itself changed no AP, GL, dashboard, reports or seed line. Every other golden re-recorded byte identical. The suite ran twice green after the re-record with no further drift.The wire changes are in
docs/refactor/CONTRACT-CHANGES.md; the desk AP page walks the cursor, sends cents, ten thousandths and the revision, and prices its draft total in the server's exact arithmetic.Fix round 3 (the lead, FROM-PRINCIPAL 39)
From reviews r5 (Sonnet) and r6 (Opus), each red first:
2d253ad: the void refuses only a bill that owes and has no linked entry; a bill that prices to zero (a zero price, or one that rounds to 0 cents) voids with nothing to reverse (TestWire_VoidZeroBill). The refusal texts, the transitions description and the CONTRACT-CHANGES row state the limit plainly: no route links an entry to a bill yet, so such a bill stays approved and payable until a later repair item.d14124f: migration 106 clampsamount_openbetween zero and the total (a hand edited negative amount paid no longer aborts it), counts APPROVED as well as PARTIAL overpaid bills and the negative paid ones in notices, and its range CHECK says the rule exactly (a voided or paid bill owes zero).ac73087: the bill create bounds the tax, the unit price, the line total, the subtotal and the total at their columns, each a 400 naming the field (TestWire_CreateMoneyCeilings).entry_unbalancedmapping stays as a defensive branch (unreachable by the wire, since lines are validated and the total derived).