{"spec":"AIP-BS-UNCODED-QUERY","version":"2026-11-12","standard":"uncoded-query-build-standard","source":"https://aipathway.com.au/explore-ai/uncoded-query-build-standard","markdown":"https://aipathway.com.au/explore-ai/uncoded-query-build-standard.md","licence":"CC BY 4.0","how_to_read":"Thirteen of these fifteen checks run on your own machine, against the stub, before you talk to anybody. The last one is a live tenant, and that one is quoted as a scoped build. 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; the live step is quoted as a scoped build (see with_us_next)","evidence_required":"not a software check. Attach what proves it"},"tally":{"total":15,"onStub":13,"productionOnly":1,"partial":3,"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":["propose or write an account or tax code the client's own chart does not hold, or borrow one from another client","write a coding no named person has approved, or write anything other than exactly what they approved","reconcile a bank line, match one, or create a transaction for one","give tax advice: say whether something is deductible or claimable, or put an account or tax code in front of the client","ask the client about a line already answered, or send a second question list in the same cycle","place a call to the client"],"stays_with_person":["approving every coding, and changing one before approving it","reconciling an unreconciled bank line with its approved coding","every line whose answer fits no account in the client's chart","following up a client who has not answered after the last reminder, by phone or otherwise","every tax question the answers raise"]},"job":"one plain question list a month for the bank lines nobody can code, and an approved coding written once","skill":"uncoded-query","stub":"local.mock_client_ledgers","production":null,"product":null,"insert":null,"with_us":{"kind":"scoped_build","note":"No hosted product writes a coding. The approval-gated, keyed write into the client's own ledger is built against that ledger's API, quoted as a scoped build. No follow-up call is offered: Office Voice has no lawful basis for one here, so the call stays with the person."}},"with_us_next":"No hosted product writes a coding. The approval-gated, keyed write into the client's own ledger is built against that ledger's API, quoted as a scoped build. No follow-up call is offered: Office Voice has no lawful basis for one here, so the call stays with the person. Send the failing check id for a fixed price; do not build the live step yourself.","default_path":null,"boundary":{"never":["propose or write an account or tax code the client's own chart does not hold, or borrow one from another client","write a coding no named person has approved, or write anything other than exactly what they approved","reconcile a bank line, match one, or create a transaction for one","give tax advice: say whether something is deductible or claimable, or put an account or tax code in front of the client","ask the client about a line already answered, or send a second question list in the same cycle","place a call to the client"],"stays_with_person":["approving every coding, and changing one before approving it","reconciling an unreconciled bank line with its approved coding","every line whose answer fits no account in the client's chart","following up a client who has not answered after the last reminder, by phone or otherwise","every tax question the answers raise"]},"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","evidence","read_rule","read_contacts","write_ledger","raise_task","read_inbox"]},{"role":"schedule","label":"Something that runs it on time","need":"A trigger that fires on a cadence and can call an authenticated endpoint.","when_missing":"An hour. Expose one authenticated endpoint that starts the work and let anything call it, so the schedule is never welded to whoever is hosting you.","ports":["gate"]},{"role":"send","label":"Something that sends","need":"Email or SMS from a domain the recipient recognises, with delivery visible enough to tell a bounce from a silence.","when_missing":"An hour for the wiring, longer for the DNS. Sending from a domain you do not own is the single fastest way into a spam folder.","ports":["send_message"]},{"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."}],"already_reachable":[{"tier":0,"label":"A scheduled digest carrying the list","because":"this standard already requires the send role, so nothing new is needed for it"}],"ports":["escalate"]}],"registry":"https://aipathway.com.au/substrates.json"},"checks":[{"id":"lines-from-the-ledger","n":1,"title":"Only lines the ledger still shows as uncoded are asked about","statement":"The lines come from the client's ledger at the time of the run: transactions parked on a suspense or uncategorised account, and bank lines not yet reconciled. A line coded by hand since the last run is not asked about.","assertable":"both","ports":["read_ledger","send_message"],"stimulus":"Three uncoded lines for one client, one of which the bookkeeper codes by hand before the monthly run.","assertion":"The question list carries the two lines still uncoded and not the one coded by hand, and the ledger was read in the same run before the list was sent.","stubLimit":"The stub proves the order and the filter. Only a live ledger proves the read is the real suspense account and the real unreconciled lines, which each ledger exposes differently."},{"id":"one-list-per-client-per-cycle","n":2,"title":"One question list per client per cycle, with every open line in it","statement":"Each client gets one list a cycle, grouping all of that client's uncoded lines. A second run in the same cycle sends no second list, and a line that lands mid-cycle waits for the next list rather than starting another one.","assertable":"stub","ports":["read_contacts","read_ledger","read_inbox","send_message"],"stimulus":"Two clients, each with uncoded lines. The build runs three times in the cycle, and a new line lands for one client between the first and second runs.","assertion":"Exactly one list per client in the cycle, each carrying every line that was uncoded at the first run, and no list carries a line from another client."},{"id":"plain-question-per-line","n":3,"title":"Each line is one plain question with a place to answer it","statement":"A line is put to the client as the amount, who it went to or came from as the statement printed it, and the date, then \"what was it for?\" Every list carries a link where each line can be answered on its own.","assertable":"stub","ports":["send_message"],"stimulus":"A client with a $412.50 payment to BUNNINGS on 3 October and a $1,200.00 deposit from J SMITH on 5 October.","assertion":"The list holds one question per line, each carrying the amount, the payee as printed and the date, and the list carries an answer link for that client."},{"id":"no-tax-advice","n":4,"title":"No tax advice and no bookkeeping words reach the client","statement":"Nothing sent to a client says whether something is deductible or claimable, names a tax code or an account code, or mentions suspense or reconciling. The client is asked what a payment was for; what that means for tax is the practice's call.","assertable":"stub","ports":["send_message"],"stimulus":"A month with a list and two reminders to a client whose lines include a hardware store, a fuel card and a transfer.","assertion":"No list or reminder text matches the published advice words."},{"id":"never-ask-twice","n":5,"title":"A line already answered is never asked about again","statement":"Once the client has answered a line, it leaves every reminder, and next cycle's list does not carry it even while the coding waits for approval and the ledger still shows it as uncoded.","assertable":"stub","ports":["read_inbox","send_message"],"stimulus":"A client answers one of two lines. A reminder falls due, the cycle rolls over, and the answered line is still uncoded in the ledger because nobody has approved it yet.","assertion":"The reminder carries only the unanswered line, and the next cycle's list does not carry the answered one."},{"id":"reminders-then-a-person","n":6,"title":"Reminders on the schedule the practice set, then a person","statement":"Reminders go only for unanswered lines, no sooner than the interval the practice set and no more than the number it set. After the last one goes unanswered, the client's open lines go to a person, who decides whether to ring. The build places no call.","assertable":"stub","ports":["read_rule","read_inbox","send_message","escalate"],"stimulus":"A client who never answers. The practice set a reminder every three days, two at most. The build runs every day for twelve days.","assertion":"Two reminders, each at least three days after the message before it, then one hand-off naming the open lines, and no message to the client after it."},{"id":"answer-kept-as-written","n":7,"title":"The client's answer is kept word for word, with who and when","statement":"Each answer is recorded against its line exactly as the client typed it, with who answered and when, before anything is proposed from it.","assertable":"stub","ports":["read_inbox","evidence"],"stimulus":"A client answers \"tiles and grout for the Smith St job\" against one line.","assertion":"The record for that line holds the answer word for word with the client's name and the time, ahead of any proposal for it."},{"id":"proposal-from-chart-and-history","n":8,"title":"The proposed coding comes from the client's own chart, and their own history first","statement":"Every proposal is an account and a tax code from this client's chart. Where this client has coded the same payee before, that coding is proposed; otherwise the answer is matched to the client's own accounts. The chart is read in the run before any proposal.","assertable":"stub","ports":["read_ledger","read_inbox","raise_task"],"stimulus":"A client who has coded BUNNINGS to Materials before answers \"tiles and grout\", and answers \"fuel for the ute\" against a payee with no history.","assertion":"The BUNNINGS line is proposed to the account its history holds, the fuel line to the client's own fuel account with that account's tax code, and the chart was read before the first proposal."},{"id":"never-invents-an-account","n":9,"title":"No account or tax code the client's chart lacks is ever proposed or written","statement":"When an answer fits no account in the client's chart, the line goes to a person as no_account_fits with the answer, and nothing is proposed. The build never creates an account, never borrows one from another client and never makes up a tax code.","assertable":"stub","ports":["read_ledger","raise_task","escalate","write_ledger"],"stimulus":"A client answers \"bought some crypto\" and has no account that fits; another client of the practice has a Crypto Assets account.","assertion":"A no_account_fits hand-off carries the answer, and no proposal or write attempt for that line names an account or tax code outside that client's chart."},{"n":10,"assertable":"both","stubLimit":"The stub proves nothing leaves without a release event carrying a named person. Only the live path proves what left is what was released, rather than something regenerated after the approval.","id":"approved-before-written","title":"Nothing is written until a named person approves that exact coding","statement":"A proposal is not a write. The build writes a coding only when a person has approved it, by name, for that line, and it writes exactly the account and tax code they approved.","ports":["gate","write_ledger"],"stimulus":"Two answered lines with proposals. A person approves one as proposed and approves the other to a different account than proposed. A third line is proposed and never approved.","assertion":"The first is written as proposed, the second as the person approved it, and the third is never written. No write precedes its approval."},{"id":"never-reconciles","n":11,"title":"The build never reconciles a bank line; a person does","statement":"The build changes the coding of a transaction already in the ledger: by editing it, or by a journal off the suspense account where the ledger will not take the edit. A bank line the ledger has not reconciled gets its question, its proposal and its approval, and then goes to a person with the approved coding to reconcile. The build never matches, creates a transaction for, or reconciles a bank line.","assertable":"stub","ports":["write_ledger","escalate"],"stimulus":"A client answers a line that is an unreconciled bank line, and a person approves its coding.","assertion":"No write is attempted for that line, and a reconcile_by_person hand-off names it with the approved account and tax code."},{"id":"written-once","n":12,"title":"An approved coding is written once, however often the build runs","statement":"Every write carries one key for that line and that approval, so a retry after a timeout cannot write twice. A later run reads the ledger first and writes nothing for a line that already holds its approved coding.","assertable":"stub","ports":["read_ledger","write_ledger"],"stimulus":"An approved line whose first write lands in the ledger and then times out. The build runs three times.","assertion":"The ledger changed for that line exactly once, and every write attempt for it carried the same key."},{"id":"read-back-and-record","n":13,"title":"Every write is read back, and a mismatch goes to a person","statement":"After a write, the build reads the line back from the ledger and records what the ledger now holds against the approval. If it differs from what was approved, the line goes to a person as readback_mismatch and is not written again.","assertable":"both","ports":["read_ledger","write_ledger","escalate","evidence"],"stimulus":"Two approved lines. The ledger stores the first as approved and stores the second with a different tax code than the one written.","assertion":"Both writes are followed by a read of that line. The first is recorded as written with the ledger's coding, and the second is handed off as readback_mismatch with one landed write.","stubLimit":"The stub proves the read follows the write and a mismatch is handed off. Only a live ledger proves what it actually stores: a locked period, a tax code it rewrites, or a transaction it will not edit."},{"id":"survives-the-builder","n":14,"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 question lists, the proposals and the writes 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":15,"title":"The pass test passes","statement":"On a real client file, an approved coding is written into the ledger exactly once and reads back as approved, and a proposed coding nobody approved is never written.","assertable":"production","ports":["read_ledger","gate","write_ledger"],"stimulus":"Two lines parked on the suspense account of your own ledger (or its demo company), both answered and proposed. Approve one. Run the build three times.","assertion":"The approved line holds exactly the approved account and tax code, read back from the ledger, with one change in its history. The unapproved line is still on the suspense account. A third run changes nothing.","stubLimit":"Deliberately unpassable on a stub. A stub ledger holds whatever it was seeded with and agrees with every write. A real ledger has its own chart and tax codes, locked periods, transactions it will not edit once reconciled, its own answer to a repeated key, and a history that shows every change, so only it can show one approved write landed once and the other never did."}]}