{"spec":"AIP-BS-BAS-READY-CHECK","version":"2026-11-12","standard":"bas-ready-check-build-standard","source":"https://aipathway.com.au/explore-ai/bas-ready-check-build-standard","markdown":"https://aipathway.com.au/explore-ai/bas-ready-check-build-standard.md","licence":"CC BY 4.0","how_to_read":"Eleven of these thirteen checks run on your own machine, against the stub, before you talk to anybody. The last one is a live tenant, and that one runs against your own live systems. One is not a software check at all: it asks what you measured before you started.","verdicts":{"pass":"asserted green here","fail":"asserted red here. This is a fixture: it names the check to price","shape_only":"shape holds on the stub, truth needs the live provider","with_us":"cannot be proved on the stub; runs against your own live systems (see with_us_next)","evidence_required":"not a software check. Attach what proves it"},"tally":{"total":13,"onStub":11,"productionOnly":1,"partial":2,"preconditions":1},"envelope_version":"1.0","port_shape":{"read_ledger":{"verb":"read","subject":"ledger"},"read_job":{"verb":"read","subject":"job"},"read_asset":{"verb":"read","subject":"asset"},"read_rule":{"verb":"read","subject":"rule"},"read_contacts":{"verb":"read","subject":"contacts"},"read_segment":{"verb":"read","subject":"segment"},"resolve_address":{"verb":"read","subject":"address"},"write_job":{"verb":"write","subject":"job"},"read_pricebook":{"verb":"read","subject":"pricebook"},"draft_quote":{"verb":"write","subject":"quote"},"write_invoice":{"verb":"write","subject":"invoice"},"write_ledger":{"verb":"write","subject":"ledger"},"record_promise":{"verb":"write","subject":"promise"},"record_service":{"verb":"write","subject":"service_record"},"release_payment":{"verb":"write","subject":"payment"},"talk":{"verb":"say","subject":"turn"},"disclose":{"verb":"say","subject":"disclosure"},"send_sms":{"verb":"say","subject":"sms"},"notify":{"verb":"say","subject":"alert"},"escalate":{"verb":"say","subject":"handoff"},"issue_notice":{"verb":"say","subject":"notice"},"answer":{"verb":"say","subject":"answer"},"publish_report":{"verb":"say","subject":"report"},"record_consent":{"verb":"write","subject":"consent"},"read_file":{"verb":"read","subject":"file"},"write_file":{"verb":"write","subject":"file"},"raise_task":{"verb":"write","subject":"task"},"read_node":{"verb":"read","subject":"node"},"write_node":{"verb":"write","subject":"node"},"read_register":{"verb":"read","subject":"register"},"write_register":{"verb":"write","subject":"register"},"index_passage":{"verb":"attach","subject":"passage"},"search_index":{"verb":"read","subject":"index"},"read_inbox":{"verb":"read","subject":"message"},"file_message":{"verb":"attach","subject":"message"},"write_order":{"verb":"write","subject":"order"},"read_quote":{"verb":"read","subject":"quote"},"write_log":{"verb":"write","subject":"log"},"read_sales":{"verb":"read","subject":"sales"},"read_sale":{"verb":"read","subject":"sale"},"draft_message":{"verb":"write","subject":"draft"},"send_message":{"verb":"say","subject":"message"},"read_memory":{"verb":"read","subject":"memory"},"propose_memory":{"verb":"write","subject":"proposal"},"write_memory":{"verb":"write","subject":"memory"},"close_job":{"verb":"write","subject":"job"},"raise_exception":{"verb":"write","subject":"exception"},"list_exceptions":{"verb":"read","subject":"exception"},"read_acceptance":{"verb":"read","subject":"acceptance"},"read_contract":{"verb":"read","subject":"contract"},"read_assessment":{"verb":"read","subject":"assessment"},"draft_claim":{"verb":"write","subject":"claim_draft"},"read_menu":{"verb":"read","subject":"menu"},"request_payment":{"verb":"write","subject":"payment_request"},"read_payment":{"verb":"read","subject":"payment"},"write_booking":{"verb":"write","subject":"booking"},"read_signoff":{"verb":"read","subject":"signoff"},"propose_journal":{"verb":"write","subject":"journal_proposal"},"set_lock_date":{"verb":"write","subject":"lock_date"},"evidence":{"verb":"attach","subject":"evidence"},"cite":{"verb":"attach","subject":"citation"},"rank":{"verb":"decide","subject":"order"},"gate":{"verb":"decide","subject":"gate"}},"provider":{"boundary":{"never":["write anything to the ledger: no code changed, no journal posted, no attachment added","lodge a BAS, or prepare one for lodgement","flag a line without citing the ledger's transaction id","say what a code should be, what to claim or how to fix a line","flag on a guess about something the ledger could not be read for","decide on its own which accounts carry no GST"],"stays_with_person":["every flag: whether it is a problem, and what to do about it","the tax codes, journals and corrections in the ledger","lodging the BAS, by the business or its registered BAS or tax agent","the settings: the period, the comparable period, the band, the no-GST accounts and the reviewer","getting a missing tax invoice from a supplier"]},"job":"the bookkeeper or BAS agent gets one list of GST exceptions before the period is lodged, every flag traceable to a transaction","skill":"bas-ready-check","stub":"local.mock_ledger_period","production":null,"product":null,"insert":null,"with_us":{"kind":"own_systems","note":"No hosted step, and no product runs this one. The build connects straight to the business's own Xero, MYOB Business or QuickBooks Online through that ledger's own sign-in and API, read only. The production check runs there: a seeded line caught by its real id, every cited id read back, and the ledger's own audit trail showing the run wrote nothing. Keeping that read authorised every quarter, and each client's history of usual codes, is the step that needs care; it is the business's own, or a scoped build."}},"with_us_next":"No hosted step, and no product runs this one. The build connects straight to the business's own Xero, MYOB Business or QuickBooks Online through that ledger's own sign-in and API, read only. The production check runs there: a seeded line caught by its real id, every cited id read back, and the ledger's own audit trail showing the run wrote nothing. Keeping that read authorised every quarter, and each client's history of usual codes, is the step that needs care; it is the business's own, or a scoped build. There is nothing to buy: your build does this step on the business's own systems. No production run is offered yet, because the live port map is not published, so do not run AIPATHWAY_ENV=production. Run the pass test by hand on the real systems and attach the evidence.","default_path":null,"boundary":{"never":["write anything to the ledger: no code changed, no journal posted, no attachment added","lodge a BAS, or prepare one for lodgement","flag a line without citing the ledger's transaction id","say what a code should be, what to claim or how to fix a line","flag on a guess about something the ledger could not be read for","decide on its own which accounts carry no GST"],"stays_with_person":["every flag: whether it is a problem, and what to do about it","the tax codes, journals and corrections in the ledger","lodging the BAS, by the business or its registered BAS or tax agent","the settings: the period, the comparable period, the band, the no-GST accounts and the reviewer","getting a missing tax invoice from a supplier"]},"substrate":{"how_to_read":"These are roles, not products. Work out which you already have by matching `providers[].detect` against the repository, then build or add only the gap. No check asserts against a vendor, so any substrate that answers the role passes the same checks.","requires":[{"role":"record","label":"Somewhere the book lives","need":"Durable storage that the workflow owns the write path to, keyed so the same record can be found twice.","when_missing":"Half a day. Any Postgres will do and the free tiers are ample at this size. The trap is not the database, it is letting people edit around the app that is supposed to own the value.","ports":["read_ledger","read_rule","cite"]},{"role":"surface","label":"Something a person opens","need":"A view of the book that whoever does this job can read and act on without asking a developer.","when_missing":"Start at tier 0 and stop there until the business asks for more. The cheapest surface is not a screen, it is a scheduled email carrying the list, and for one person doing this job that is not a compromise, it is the right answer. On Microsoft 365 or Workspace you can skip straight past this: a list and a sheet are already what people open. Build upward only when two people need the same list at the same time.","tiers":[{"tier":0,"label":"A scheduled digest carrying the list","cost":"About an hour. Needs nothing you do not already have.","satisfiedBy":"send","whenYouNeedIt":"One person does this job. They want to know what is waiting when they sit down, and they act on it from their inbox."},{"tier":1,"label":"A read-only page showing the book","cost":"An afternoon. One query and one table.","whenYouNeedIt":"Somebody needs to look mid-day without waiting for tomorrow's digest, or to answer a customer on the phone."},{"tier":2,"label":"Editable rows, a queue and a log","cost":"A day or two with a coding agent, which is what agents are best at. It is bounded work: the fields are the schema, the queue is whatever escalate hands off, the log is the trace.","whenYouNeedIt":"More than one person shares the book and they need to correct it without treading on each other, and you need to see afterwards who changed what."}],"ports":["publish_report"]}],"registry":"https://aipathway.com.au/substrates.json"},"checks":[{"id":"period-from-the-ledger","n":1,"title":"The period's transactions come from the ledger, by its own ids and dates","statement":"The list covers exactly the BAS period the business set, read from the ledger the business runs: every transaction dated inside the period, by the ledger's own transaction id. A transaction dated outside the period is not flagged as if it were in it, and nothing is read from an export somebody edited.","assertable":"stub","ports":["read_rule","read_ledger"],"stimulus":"A quarter with transactions inside it, one bill dated two days after the quarter ends with GST claimed and nothing attached, and settings naming the quarter.","assertion":"The build read the ledger for exactly the period set, the list names that period, and no flag cites a transaction dated outside it. The bill after quarter end is on no flag."},{"id":"every-flag-cites-its-source","n":2,"title":"Every flag cites the transaction it came from, and that transaction reads back","statement":"Every flag on a line names the ledger's transaction id, and that id reads back from the ledger with the same date and total the flag states. A flag about the whole period (a swing, a control account difference) names the period and the figures it compared, read from the ledger. A flag that cannot point at its source is dropped, not shown.","assertable":"stub","ports":["read_ledger","cite"],"stimulus":"The base quarter, with a bill carrying several bait problems at once.","assertion":"Every line flag cites at least one transaction id the world holds in the period, with that transaction's own date and total. Every period flag names the period. A flag with no source, or citing an id that does not exist, fails."},{"id":"tax-invoice-held","n":3,"title":"GST claimed above the tax invoice threshold with no tax invoice attached is flagged","statement":"A purchase above the threshold the business set (the ATO figure, dated on this page) with a GST credit claimed and no document attached in the ledger is flagged, citing the transaction. Below the threshold it is not flagged. Where the ledger cannot say whether a document is attached, the check is listed as not run, never assumed either way. An attachment is not proof of a valid tax invoice; the flag is for a person to look at.","assertable":"both","ports":["read_rule","read_ledger","cite"],"stimulus":"Three purchases with GST claimed: one above the threshold with a document attached, one above it with nothing attached, one small one below it with nothing attached.","assertion":"Exactly one tax_invoice flag, citing the purchase above the threshold with nothing attached. The attached purchase and the small one are on no tax_invoice flag.","stubLimit":"The stub says whether a document is attached because it was told. Only the real ledger proves the build read attachment presence from the ledger's own record of it, and no stub or ledger proves the attached document is a valid tax invoice: that stays with the person."},{"id":"code-differs-from-usual","n":4,"title":"A GST code that differs from what that supplier or account usually carries is flagged, with the usual code shown","statement":"For each purchase line, the GST code is compared with the code the same supplier, or failing that the same account, carried on most of its lines in the history the business set. A line coded differently from a clear usual code is flagged with the usual code and how many lines it was seen on. Too little history means no flag, never a guess. The flag says the code is different; it never says which code is right.","assertable":"stub","ports":["read_ledger","cite"],"stimulus":"A supplier coded GST on six bills in the prior two quarters, then coded GST-free on one bill this quarter. A new supplier with one bill and no history.","assertion":"One usual_code flag cites the GST-free bill and states the usual code and its count. The new supplier's bill is on no usual_code flag. A flag that names a code as correct, or a flag on a supplier with no history, fails."},{"id":"gst-on-no-gst-accounts","n":5,"title":"GST claimed on an account the business marked as carrying no GST is flagged","statement":"The business, or its BAS agent, lists the accounts that carry no GST in its own books: typically wages, super, bank fees and interest. A line in one of those accounts with a GST credit claimed is flagged, citing the transaction. The list is the business's setting; the build never decides on its own that an account carries no GST.","assertable":"stub","ports":["read_rule","read_ledger","cite"],"stimulus":"A bank fee line with GST claimed, in an account on the business's no-GST list, and a repairs line with GST claimed in an account not on it.","assertion":"One no_gst_account flag, citing the bank fee transaction. The repairs line is on no no_gst_account flag."},{"id":"capital-coded-capital","n":6,"title":"A purchase in a capital account not coded capital, or the reverse, is flagged when the business reports G10 and G11","statement":"Where the business reports capital and non-capital purchases separately (full reporting), a purchase line in a fixed asset account carrying a non-capital GST code is flagged, and so is a line in an expense account carrying a capital code. Which account is capital is the business's own chart of accounts. Under simpler BAS, where those labels are not reported, the check is listed as not run.","assertable":"stub","ports":["read_rule","read_ledger","cite"],"stimulus":"Full reporting. A plant and equipment purchase in the fixed asset account coded with the non-capital GST code, and a repairs bill coded correctly.","assertion":"One capital flag citing the plant purchase. Run again with simpler BAS set: no capital flag, and capital appears under not checked."},{"id":"unreconciled-and-uncoded","n":7,"title":"Transactions inside the period that are unreconciled or carry no tax code are flagged","statement":"Every bank transaction dated in the period that the ledger shows as unreconciled is flagged, and so is every bill, spend or receive line in the period with no tax code at all (a manual journal is left to check 9). Where the ledger cannot say whether a line is reconciled, that half of the check is listed as not run.","assertable":"stub","ports":["read_ledger","cite"],"stimulus":"One bank spend line with no tax code, one coded but unreconciled, and the rest reconciled and coded.","assertion":"One uncoded flag and one unreconciled flag, each citing its own transaction. No reconciled, coded line is flagged under either."},{"id":"swing-against-comparable","n":8,"title":"Period totals that swing against the comparable period beyond the business's band are flagged, with both figures","statement":"Total sales, GST on sales and GST on purchases for the period are compared with the comparable period the business set (usually the same quarter last year), each read from the ledger the same way. A move larger than the band the business set is flagged with both figures, both periods and the change. A swing is a question for a person, not an error.","assertable":"stub","ports":["read_rule","read_ledger"],"stimulus":"A comparable quarter where sales and GST on sales are close to this quarter and GST on purchases is far lower, with a band of 25 percent.","assertion":"Exactly one swing flag, for GST on purchases, stating both figures and both periods. Sales and GST on sales are on no swing flag."},{"id":"control-account-agrees","n":9,"title":"The GST control account's movement for the period is compared with the GST the transactions report, and a difference is flagged","statement":"The movement in the GST account for the period is read from the ledger and compared with GST on sales less GST on purchases for the same period (from the ledger's GST report where it offers one, otherwise from the coded lines, and the list says which). A difference is flagged with both figures and, where they can be found, the transactions posted straight to the GST account.","assertable":"stub","ports":["read_ledger","cite"],"stimulus":"A manual journal of $500 posted straight to the GST account inside the quarter, with no tax code.","assertion":"One control_account flag whose stated difference equals the world's actual difference to the cent, and it cites the journal."},{"id":"lists-what-it-could-not-check","n":10,"title":"Whatever it could not check is listed, with the reason","statement":"A check the ledger cannot support (no way to read attachments, no reconciled flag, no GST report), a check the business's reporting makes irrelevant (capital under simpler BAS), and a check with too little history is listed under not checked with the reason. Nothing is silently skipped, and nothing is flagged on a guess about data that could not be read.","assertable":"both","ports":["read_ledger","read_rule"],"stimulus":"A ledger that cannot read attachments or reconciliation status, on simpler BAS.","assertion":"Not checked names tax_invoice, reconciliation and capital, each with a reason. No tax_invoice, unreconciled or capital flag is raised.","stubLimit":"The stub says what the ledger can read because it was configured to. Only a run against the real ledger shows which of its reads actually work on the business's plan and file."},{"id":"flags-never-decides","n":11,"title":"Read only: one list to the named person, nothing written, nothing lodged, no advice","statement":"The list goes once per run to the bookkeeper or BAS agent the business named. Nothing is written to the ledger: no code changed, no journal posted, no attachment added, nothing lodged. A flag states what was found and why it was flagged; it never says what the code should be, what to claim or how to fix it. The person decides.","assertable":"stub","ports":["publish_report"],"stimulus":"The base quarter, run once.","assertion":"Exactly one list published, to the named reviewer. No call in the trace writes anything. No flag's text tells the reader what to change, claim or lodge."},{"id":"survives-the-builder","n":12,"title":"It survives the builder","statement":"Someone other than the builder can explain what it does, and it runs on an account the business owns.","assertable":"precondition","ports":["read_rule"],"stimulus":"No stimulus. The builder names who else can explain it and whose account the check runs on.","assertion":"The runner reports what was declared and never scores it green on its own say-so.","stubLimit":"Not a runtime property. Software cannot prove a second person understands it, and outliving its author is precisely what is being asked about."},{"id":"pass-test","n":13,"title":"The pass test passes","statement":"On the business's own ledger, for a real period: seed one known-bad line (a purchase above the tax invoice threshold with GST claimed and nothing attached). Run the check. Every line flag cites a real transaction id that reads back from the ledger with the same date and total, the seeded line is caught, and the ledger shows nothing was written by the run.","assertable":"production","ports":["read_ledger","cite","publish_report"],"stimulus":"A real period in the business's own Xero, MYOB Business or QuickBooks Online file, with one seeded bill above the threshold, GST claimed, no attachment.","assertion":"The seeded bill is on a tax_invoice flag by its real id. Every cited id reads back from the ledger. The ledger's own history or audit trail for the period shows no change made by the run's connection.","stubLimit":"Deliberately unpassable on a stub. A stub holds whatever ids you seed and agrees with itself. Only the real ledger proves the ids are the ones it issued, that its attachment and tax code reads say what the build thinks they say, and, from its own audit trail, that the run wrote nothing."}]}