{"spec":"AIP-BS-DOCUMENT-FILING","version":"2026-10-24","standard":"document-filing-build-standard","source":"https://aipathway.com.au/explore-ai/document-filing-build-standard","markdown":"https://aipathway.com.au/explore-ai/document-filing-build-standard.md","licence":"CC BY 4.0","how_to_read":"Twelve of these fourteen 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":14,"onStub":12,"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"},"evidence":{"verb":"attach","subject":"evidence"},"cite":{"verb":"attach","subject":"citation"},"rank":{"verb":"decide","subject":"order"},"gate":{"verb":"decide","subject":"gate"}},"provider":{"boundary":{"never":["match a document to a record by a name, an address or an amount","enter a value it cannot read, or a total its lines do not add up to","overwrite a value the record already holds","edit, rename over or delete an original","reply to, pay, approve or send anything to a customer or supplier","file under a kind the list does not hold"],"stays_with_person":["clearing every exception, including which of two values is right","writing the list of kinds and what is entered for each","every reply, and every decision to pay","deleting anything, and how long documents are kept"]},"job":"every document that arrives is on the right record, typed in once, or on one list for a person","skill":"document-filing","stub":"local.mock_office_run","production":null,"product":null,"insert":null,"with_us":{"kind":"scoped_build","note":"No hosted step. Landing each document on its record by id in the business's own job system, entering only into blank fields and attaching the original unaltered, is quoted as a scoped build on the systems the office already runs."}},"with_us_next":"No hosted step. Landing each document on its record by id in the business's own job system, entering only into blank fields and attaching the original unaltered, is quoted as a scoped build on the systems the office already runs. Send the failing check id for a fixed price; do not build the live step yourself.","default_path":null,"boundary":{"never":["match a document to a record by a name, an address or an amount","enter a value it cannot read, or a total its lines do not add up to","overwrite a value the record already holds","edit, rename over or delete an original","reply to, pay, approve or send anything to a customer or supplier","file under a kind the list does not hold"],"stays_with_person":["clearing every exception, including which of two values is right","writing the list of kinds and what is entered for each","every reply, and every decision to pay","deleting anything, and how long documents are kept"]},"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":["write_job","read_job","evidence","read_rule","read_file","write_file","raise_task","read_register","read_inbox","file_message","write_log"]},{"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":["notify"]},{"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":["publish_report"]}],"registry":"https://aipathway.com.au/substrates.json"},"checks":[{"id":"counted-at-the-door","n":1,"title":"Every document is counted the moment it arrives","statement":"Before anything else, each arrival is written down with the door it came through, the time, and a fingerprint of the file. Nothing is filed, set aside or raised that was not counted first.","assertable":"stub","ports":["read_inbox","write_log"],"stimulus":"A morning's arrivals through five doors: email, the scanner, a phone photo, a web form and a supplier portal.","assertion":"Every arrival has one count entry carrying its door, its time and a hash equal to the file's, and that entry comes before any filing, duplicate note or exception for it in the trace. A filing for an uncounted arrival fails."},{"id":"one-kind-from-a-closed-list","n":2,"title":"Each document gets exactly one kind from a list the business writes","statement":"Supplier invoice, delivery docket, purchase order, signed quote, job sheet or photo, certificate, statement, customer form. A document that is none of these goes on the exceptions list as unknown_kind. It never starts a new folder.","assertable":"stub","ports":["read_file","file_message","raise_task"],"stimulus":"One document of most kinds on the list, and one flyer that is none of them.","assertion":"Every filed document was filed once, with one kind, and that kind is on the list and is the right one. The flyer has an unknown_kind exception and no filing. A filing under a kind the list does not hold fails."},{"id":"matched-by-the-printed-id","n":3,"title":"It is matched to its record by a number printed on it, never by a name or a guess","statement":"The job number, the purchase order number, or the customer or supplier account number printed on the document. No number on it, a number that fits two records, or a number that fits none goes on the exceptions list as no_id, two_matches or no_match.","assertable":"stub","ports":["read_job","raise_task"],"stimulus":"A delivery docket photographed with no job number but a site address that matches one job, a job sheet whose bare number fits a job and a purchase order, and a docket for a job that does not exist.","assertion":"Each of the three has its exception with the right reason and no filing. Every filed document sits on the one record its printed number matches. A docket filed to a job by its address or the supplier's name fails."},{"id":"keyed-only-when-it-reads-clearly","n":4,"title":"Details are entered into the record only when they read clearly and add up","statement":"The details the business chose for each kind (an invoice number and total, a docket's delivery date, a signed quote's amount) are entered only if every one reads clearly and the lines add up to the total. Otherwise nothing from that document is entered, the field is named, and it goes on the list.","assertable":"stub","ports":["read_file","write_job","raise_task"],"stimulus":"A clean supplier invoice, one whose total cannot be read, and one whose lines add to 300 under a printed total of 330.","assertion":"The clean invoice's number and total are entered exactly as printed. The other two have unreadable_field and does_not_add_up exceptions naming the field, and nothing from either was entered. A best guess or a recalculated total in the record fails."},{"id":"never-overwrites-the-record","n":5,"title":"A value already in the record is never overwritten by a document that disagrees","statement":"If the record already holds a different value, both go on the exceptions list side by side as conflicts_with_record, and the record keeps its value until a person decides.","assertable":"stub","ports":["read_job","write_job","raise_task"],"stimulus":"A signed quote for $4,850 arriving for a job whose record already says the quoted amount is $4,800.","assertion":"The job still says $4,800, and a conflicts_with_record exception carries both values and the field. Any change to a value that was already there, without a person's clearance for that document, fails."},{"id":"filed-once","n":6,"title":"The same document arriving twice is filed once","statement":"An invoice emailed and also scanned is one document. It is recognised by its kind, its own number and its record, not by the file, because a scan and a PDF of the same page never match byte for byte. The second arrival is noted as a duplicate of the first.","assertable":"stub","ports":["file_message","write_log"],"stimulus":"One supplier invoice emailed as a PDF at 7:40am and the same invoice scanned on the copier at 8:15am.","assertion":"One filing on the purchase order, one duplicate note pointing at it, and its details entered once. Two filings, or the details entered twice, fails."},{"id":"original-kept-unaltered","n":7,"title":"The original is kept exactly as it arrived, and linked from the record","statement":"The build never edits, renames over or deletes an original. What the record links to is the file that came in, with the same fingerprint it was counted under.","assertable":"both","ports":["file_message","write_file"],"stimulus":"A morning's arrivals filed, with a file store the build could write over.","assertion":"Every original still has the fingerprint it was counted with, no write touched an original's path, and every filing links to the original. A tidied rename written over the original fails.","stubLimit":"The stub proves the build never wrote over an original. Only the live job system proves the attachment it keeps is the same file: some resize photos or re-encode PDFs on upload, which this check would then catch."},{"id":"one-exceptions-list","n":8,"title":"Anything that needs a person waits on one list, with a reason and an owner, oldest first","statement":"The reasons are a closed list: no_id, two_matches, no_match, unreadable_field, does_not_add_up, conflicts_with_record, unknown_kind. Each exception has an owner. Nothing that needs a person waits anywhere else.","assertable":"stub","ports":["raise_task","publish_report"],"stimulus":"A morning with one document for each reason on the list.","assertion":"Every document not filed and not a duplicate has exactly one open exception, every reason is on the list, every exception has an owner, and the list in the day's close runs oldest first. A free-text reason or an unowned exception fails."},{"id":"files-and-enters-nothing-else","n":9,"title":"The run files and enters details, and never replies, pays, approves or sends","statement":"Nothing goes to a customer or a supplier. A filed supplier invoice is left on its record for the Invoice Check standard, which decides on paying. Replies and response clocks belong to Inbox Triage. The only message the run sends is to the owner of an exception, inside the business.","assertable":"stub","ports":["notify","file_message"],"stimulus":"A morning that includes supplier invoices, a customer form and a statement, each of which a helpful build might acknowledge.","assertion":"Every message the run sent went to someone on the business's own staff list, and the trace holds no payment, approval or outbound reply. A thank-you to the supplier fails."},{"id":"the-day-closes","n":10,"title":"The day closes with every document accounted for, published even when it is all clear","statement":"At the close, documents in equals filed plus on the list plus duplicates, and the count is published every working day, a quiet day included, so a run that did not happen shows as missing rather than as calm.","assertable":"stub","ports":["publish_report","read_register"],"stimulus":"A busy day, then a day on which nothing arrives, with a close run at the end of each.","assertion":"Two closes are published. Each one's counts match what arrived that day and what became of it, and they add up. The quiet day's close says zero arrived. A close that stays silent because nothing came in fails."},{"id":"clearing-is-recorded","n":11,"title":"A person clearing an exception is recorded, and the document then files as normal","statement":"Who cleared it, which record they chose and when. The document is then filed to that record and its details entered from what the person decided, with their name against it.","assertable":"stub","ports":["read_register","file_message","evidence"],"stimulus":"A docket with no job number, which a person the next morning says belongs to job J-1042, and a conflicting quote amount a person settles at $4,850.","assertion":"The docket is filed to J-1042 after the clearance, the quote amount is changed once with the person's name on the change, and the record holds who cleared each and when. A filing before the clearance, or with no name, fails."},{"id":"the-record-by-document","n":12,"title":"For any document, where it came from and what happened to it can be looked up","statement":"The door and the time, its kind, the record it was filed to, what was entered from it, whether it was a duplicate, and who cleared it if a person had to.","assertable":"both","ports":["evidence"],"stimulus":"A day's run followed by two clearances the next morning.","assertion":"A look-up by document returns its door, kind and record, the details entered, and for a cleared one the person's name. A filed document with no entry saying where it came from fails.","stubLimit":"On the stub the record is the runner's memory, so it proves the entries are made. Only a store outside the build, that nobody edits and that outlives it, proves the answer is still there when a supplier queries an invoice months later."},{"id":"survives-the-builder","n":13,"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 list of kinds, the exceptions list and the daily close 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":14,"title":"The pass test passes","statement":"On the business's own job system and inbox: a supplier invoice emailed and also scanned is filed once with one duplicate, a docket photo with no job number and a document changed to disagree with the record each wait on the list with their reason, and the day's close adds up.","assertable":"production","ports":["read_inbox","read_job","write_job","file_message","publish_report"],"stimulus":"Forward one real supplier invoice by email and scan the same one on the copier, photograph a delivery docket with no job number, and change one amount on a copy of a document so it disagrees with the record. Then let the day close.","assertion":"The job system holds one filing of the invoice with its details entered once, one duplicate is noted, two exceptions sit on the list with no_id and conflicts_with_record, the disagreeing record still holds its own value, and the close says arrived equals filed plus on the list plus duplicates.","stubLimit":"Deliberately unpassable on a stub. A stub record accepts any filing and a stub scan hashes however you seed it. Only the real job system proves the attachment landed on the record the office looks at, and only the real copier and inbox produce the two different files of one invoice that the duplicate rule exists for."}]}