Phase 2 completion — owner rulings and remaining fixes
Status: approved by the owner on 2026-09-28. Branch phase-2c, based on main 737f4c8.
Phase 2 shipped with its open decisions O-1..O-13 recorded in the Phase 2 final review. The owner has now ruled on them: every ruling below is binding. The operator console (O-13) stays out. Items that need an outside authority (a tax agent, a bank, the MyInvois sandbox) are not ruled here: WHT rates, sections and forms; SST-02 form references; a Malaysian bank’s MT940 sample and bulk-payment format; LHDN’s SST-number length. They stay open in the ROADMAP.
Every ruling is audit-driven: each refusal is a coded refusal with an en/ms/zh sentence, every exception is an audit row with a mandatory reason, and nothing already posted is mutated.
Migration ranges
Section titled “Migration ranges”| Lane | Range |
|---|---|
| C1 business date | 00420–00429 |
| C2 tax controls and reports | 00430–00439 |
| C3 e-Invoice | 00440–00449 |
| C4 payables and batches | 00450–00459 |
| C5 banking and budgets | 00460–00469 |
A lane that changes the report_export_report_known CHECK writes a union migration listing
all ten current reports plus its own, whose Down restores the list it found (see 00404).
C1 — Business date is the organization’s day (O-1)
Section titled “C1 — Business date is the organization’s day (O-1)”- Ruling: “today” is the calendar day in the organization’s
company_profile.timezone(defaultAsia/Kuala_Lumpur), stored as midnight UTC of that date (§20.1.2 unchanged). An action at 07:30 MYT on 1 April is dated 1 April. - The kernel stays free of platform imports:
clockgains a zone-aware day function; the zone is resolved once per request/transaction for the tenant and carried in the context, the same way the organization is. A missing zone falls back toAsia/Kuala_Lumpur, never to UTC. - Every caller of
clock.TodayUTCthat dates a business fact (invoice, posting, SST/WHT period, fiscal step, due date, aging as-of, workflow business date, reports’ default as-of) moves to the organization’s day.TodayUTCremains only where UTC is the statute or the protocol (MyInvoisIssueDate/IssueTimeare UTC by specification). - A drift test fails when a non-test file outside the allowlist calls
TodayUTC; watch it fail. - Tests: per affected module, an action at 23:30 UTC (07:30 MYT next day) is dated the MYT day (presence: the same action at 12:00 UTC gives the same day under both rules, so the test proves the boundary, not the fixture). The claim-form e2e flake tied to this is re-checked.
- DEPLOY_NOTES: existing rows are not rewritten; the change applies from deploy.
C2 — Tax controls and report visibility (O-7, O-9, O-4, O-5)
Section titled “C2 — Tax controls and report visibility (O-7, O-9, O-4, O-5)”- O-7: accounts 2140 (SST payable) and 2160 (WHT payable) get a control kind with an owner
module, so the ledger’s manual-line refusal applies: manual journals and banking’s
journal-from-line refuse them, and
PostFromLineDialogno longer offers them. Provisioning and a migration set it for existing organizations. Drift: the control-kind list covers both. - O-9: each report export and download also requires the owning module’s read permission
(AP aging → payables invoice read; AR aging and customer statement → receivables read; WHT
register → payables/withholding read; budget vs actual → budget read; SST return → tax
return read), in addition to
reports.*. Presence-before-absence test per report. - O-4: the tax-return periods list computes
stalefor real (a filed period whose figures changed); a superseded or discarded sheet export carries a visible “not the filed version” stamp. Two;-separated SST numbers stay allowed on the company profile; the return files the registration’s own single number (document this). - O-5: a WHT remittance cannot be voided once LHDN’s receipt number is recorded (coded refusal); a 0% treaty withholding code can be recorded, with its treaty reference required.
C3 — e-Invoice (O-2, O-3a, O-3c, O-3f, minors)
Section titled “C3 — e-Invoice (O-2, O-3a, O-3c, O-3f, minors)”- O-2: a self-billed purchase invoice cannot be paid, individually or in a batch, until
its self-billed e-Invoice is
valid(coded refusal). Invoices not in e-Invoice scope are unaffected. - O-3a: an organization with exactly one active member may void its own sales document
and issue its own correcting credit note (the sole-proprietor exception). It requires a
mandatory reason, writes an audit row flagged
sole_owner_exception, and applies only while the organization has one active member; with two or more, the normal claimant rule refuses. - O-3c: voiding a document whose e-Invoice outcome is
outcome_unknownorduplicate_unresolvedrequires a recorded portal check (who, when, the portal result) first; without it, the void is refused. - O-3f: MyInvois calls from the worker go through a rate limiter that respects LHDN’s
published per-endpoint limits, with backoff on 429; the window watch’s
Detailscalls batch or pace so ~1,000 documents/day stay under the ceiling. - Minors: window watch continues until LHDN’s 72 h cancel window closes and the UI keeps
Cancel while it is open (M2); the statutory link (
uuid,long_id, …) becomes write-once and its changes audited (M3); switching in scope checks both sales graphs have a void exit (M7); workflow validation refuses the reverses-accrual flag on an edge before any posting state (F-b);purchase_invoice.cancelled_atbecomes write-once like 00410; 00410’s first write on an issued sales invoice is constrained (deferred constraint trigger or reading the workflow semantic).
C4 — Payables and batches (O-12, O-11, batch claims and advances, AP minors)
Section titled “C4 — Payables and batches (O-12, O-11, batch claims and advances, AP minors)”- O-12 posting date: an approved purchase invoice posts on its invoice date. If that date’s fiscal period is closed, it posts on the first open date after it and records why on the posting. The approval date is no longer the posting date.
- O-11/O-12 inactive parties: an inactive supplier or customer blocks new documents, but receipts, payments, credit-note offsets and allocations against that party’s existing open items are allowed.
- Batch claims and advances (§19 “200 claims”): a payment batch can hold approved expense claim payouts and advance payouts as well as supplier payments. Each line moves through its own existing payout edge when the batch is marked paid (no new journal path); the batch approver must be able to approve every line under its own grants; the bank file carries employee bank details under the same snapshot and verification rules as suppliers.
- AP minors: line errors shown beside the line, not as a banner; the supplier list’s per-row registry lookups become one query (scalability); layering guard G-1 stops reading error strings as SQL.
C5 — Banking and budgets (O-6, O-8, recon export, audited unlock, minors)
Section titled “C5 — Banking and budgets (O-6, O-8, recon export, audited unlock, minors)”- O-6: auto-match matches one bank debit to a whole payment batch (the batch’s total, its bank account, the mark-paid date window) as a many-to-one group.
- O-8: the budget preparer cannot approve their own budget version (coded refusal). Budgets are kept forever; actuals stay by posting date.
- Reconciliation export: CSV/XLSX/PDF of a locked reconciliation through the report-export pipeline (new report kind; union migration).
- Audited unlock: an unlock of a locked reconciliation needs its own permission and a mandatory reason, writes an audit row, and keeps the original lock’s record.
- Minors: the roadmap’s banking deferred minors, including
FuzzParseMT940andFuzzParseCSV(no panic; every error a*ParseErroror*FieldError), the property-test gaps,bank_statement_chainrequiringstarts_onafter the previousends_on, and the spuriousErrDifferenceretry under READ COMMITTED.
Not changed
Section titled “Not changed”Refusal codes not_draft, inactive and voided keep their names: renaming them changes the
API for no reader-visible gain, since each already has a feature-specific sentence.