{"spec":"AIP-BS-INBOX-TRIAGE","version":"2026-10-03","standard":"inbox-triage-build-standard","source":"https://aipathway.com.au/explore-ai/inbox-triage-build-standard","markdown":"https://aipathway.com.au/explore-ai/inbox-triage-build-standard.md","licence":"CC BY 4.0","how_to_read":"Ten of these twelve 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":12,"onStub":10,"productionOnly":1,"partial":1,"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"},"evidence":{"verb":"attach","subject":"evidence"},"cite":{"verb":"attach","subject":"citation"},"rank":{"verb":"decide","subject":"order"},"gate":{"verb":"decide","subject":"gate"}},"provider":{"boundary":{"never":["classify onto a kind the list does not hold","file a copy in a folder instead of against the record","reply with a price, a date or a decision without a release","touch a complaint","delete spam"],"stays_with_person":["every substantive reply","the complaint","deciding a supplier invoice is genuine"]},"job":"nothing in the inbox is lost, and nothing is answered that a person should answer","skill":"inbox-triage","stub":"local.mock_inbox","production":null,"product":null,"insert":null,"with_us":{"kind":"scoped_build","note":"No hosted step. The filing write against the record and the release gate are quoted as a scoped build on the mailbox the business already runs."}},"with_us_next":"No hosted step. The filing write against the record and the release gate are quoted as a scoped build on the mailbox the business already runs. Send the failing check id for a fixed price; do not build the live step yourself.","default_path":null,"boundary":{"never":["classify onto a kind the list does not hold","file a copy in a folder instead of against the record","reply with a price, a date or a decision without a release","touch a complaint","delete spam"],"stays_with_person":["every substantive reply","the complaint","deciding a supplier invoice is genuine"]},"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_job","evidence","read_rule","raise_task","read_inbox","file_message"]},{"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":["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":["escalate"]}],"registry":"https://aipathway.com.au/substrates.json"},"checks":[{"id":"classified-onto-a-closed-list","n":1,"title":"Every message gets exactly one kind from a closed list","statement":"The kinds are supplier_invoice, quote_request, job_update, statement, complaint, remittance, spam and other. Every message gets one of those and only one. A free-text kind, or a message with two kinds or none, fails.","assertable":"stub","ports":["read_inbox","file_message"],"stimulus":"Six messages in the inbox, one of each kind the seed covers, including one that fits nothing but other.","assertion":"Every message has exactly one classification and its kind is on the list. A kind the list does not hold, a second classification for one message, or an unclassified message fails."},{"id":"filed-against-the-record","n":2,"title":"A message about a job or a supplier is filed against that record by id","statement":"The message is attached to the job or the supplier it belongs to, by the record's own id, never as a copy in a folder. A filed message with no record id fails. A message classified other may stay unfiled, but it must become a task for a person.","assertable":"stub","ports":["read_job","file_message"],"stimulus":"A job update about an existing job, a supplier statement from a known supplier, and one message that matches no record.","assertion":"The two that match a record are filed to that record's id and the item carries it. No filing lands in a folder. The unmatched message has an open task with an owner. A filing with no record id fails."},{"id":"attachment-becomes-the-artefact","n":3,"title":"A supplier invoice attachment is filed as evidence with its hash and the message id","statement":"The attachment is the artefact, filed as evidence carrying its hash, the message id and the supplier record, so the Invoice Check standard can read it where it was filed. The body text alone is not the artefact.","assertable":"stub","ports":["file_message","evidence"],"stimulus":"A supplier invoice from a known supplier with one attachment, hashed by the runner.","assertion":"Evidence exists whose hash is the attachment's hash, whose message id is this message, and whose record is the supplier. A build that files the body and attaches nothing fails."},{"id":"every-kind-has-a-queue-and-an-owner","n":4,"title":"Every classified message lands in the queue for its kind, with a named owner and a due date","statement":"Each kind has a queue, and the queue has a named owner and a response clock. The item takes the owner's name and a due date from that clock. A message with no queue, no owner or no due date fails.","assertable":"stub","ports":["raise_task"],"stimulus":"Messages of several kinds, and a queue table naming an owner and a clock for each kind.","assertion":"Every message has one item, the item's owner is the queue's owner for its kind, and its due date is when it was raised plus the kind's clock. An item with no owner or a due date from nowhere fails."},{"id":"complaints-go-to-a-person-untouched","n":5,"title":"A complaint is escalated to a named person with the full message, and nothing is sent to the customer","statement":"A complaint is never acknowledged, answered or summarised by the machine. It is handed whole to a named person, and the item is marked escalated. Any outbound on a complaint fails.","assertable":"stub","ports":["escalate"],"stimulus":"One complaint about an existing job, beside one ordinary job update.","assertion":"The complaint's item is escalated to a named person and the escalation carries the full message. No acknowledgement and no reply exists for that item. The job update beside it may be acknowledged."},{"n":6,"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":"no-reply-without-a-release","title":"The only automatic outbound is an acknowledgement, and any substantive reply carries a named person's release","statement":"The machine may send one thing on its own: an acknowledgement saying a person will reply and by when. Anything that quotes a price, a date or a decision leaves only after a release event naming a person. A reply with no release fails.","ports":["gate","notify"],"stimulus":"Two quote requests: one with a person's release and reply on file, one with nothing.","assertion":"Both are acknowledged with a promised reply time. The released one is replied to after a gate event naming that person, and the gate precedes the send in the trace. The other has no reply. A reply without a release fails."},{"id":"the-clock-is-on-the-kind","n":7,"title":"Each kind carries a response clock, and an item past its clock escalates to the owner's manager","statement":"The clock belongs to the kind, not to whoever happens to be looking. An open item still unanswered when its clock runs out is escalated to the owner's manager, named. It never just gets older.","assertable":"stub","ports":["raise_task","escalate"],"stimulus":"A quote request raised with a one-day clock. The runner advances the clock two days and runs the build again.","assertion":"After the second run the item is escalated to the manager named on its queue, the escalation is dated after the due date, and there is still one item. An item silently older than its clock fails."},{"id":"duplicates-thread-not-multiply","n":8,"title":"A follow-up in the same thread updates the existing item","statement":"One thread is one item. A second message in a thread the build has already raised an item for is filed to that item, and the item now carries both message ids. A second item for one thread fails.","assertable":"stub","ports":["read_inbox","file_message"],"stimulus":"A job update, processed. Then a reply in the same thread arrives and the build runs again.","assertion":"Exactly one item exists for the thread, it carries both message ids, and the second message was filed to the same record. A second item for the thread fails."},{"id":"spam-is-quarantined-not-deleted","n":9,"title":"Spam is filed to quarantine with its hash, retrievable, and never deleted by the machine","statement":"A message classified spam is moved to quarantine with its hash, where a person can find it. The machine never deletes a message. A message the machine trashed fails.","assertable":"stub","ports":["file_message"],"stimulus":"One spam message beside one supplier statement.","assertion":"The spam message is filed to quarantine with its hash, its item is quarantined, and the message is still retrievable from the world. Any filing to trash, for any message, fails."},{"id":"the-record-by-message","n":10,"title":"Message id, kind, record, owner, clock, what was sent and by whom are retrievable by message","statement":"When the customer says nobody answered, the record shows the message, its kind, the record it was filed to, the owner, the due date, every outbound and the person who released it.","assertable":"stub","ports":["read_inbox","evidence"],"stimulus":"One supplier invoice filed with evidence, and one quote request acknowledged, released and replied to.","assertion":"A read by message id returns the classification, the item with its owner and due date, the filing, the evidence where there is one, and each outbound with the person who released it. A record missing the release fails."},{"id":"survives-the-builder","n":11,"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 inbox rules and the queues 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":12,"title":"The pass test passes","statement":"On the real mailbox for a week: a real supplier invoice filed as evidence against its supplier, a real complaint reaching a person untouched, a real thread of three messages held as one item, and nothing substantive sent without a release.","assertable":"production","ports":["read_inbox","file_message","gate"],"stimulus":"A week of real mail on the business's own mailbox, with real suppliers, customers and a real complaint.","assertion":"The invoice's attachment is evidence on the supplier record with its hash, the complaint's item is escalated with no outbound, the three-message thread is one item, and every reply in the sent folder carries a release naming a person.","stubLimit":"Deliberately unpassable on a stub. A stub inbox holds the messages you seed and a stub record accepts every filing. Only the real mailbox proves the filing landed where the bookkeeper looks and the release names someone who works there."}]}