{"spec":"AIP-BS-VIRTUAL-ADMIN","version":"2026-10-26","standard":"virtual-admin-build-standard","source":"https://aipathway.com.au/explore-ai/virtual-admin-build-standard","markdown":"https://aipathway.com.au/explore-ai/virtual-admin-build-standard.md","licence":"CC BY 4.0","how_to_read":"Six of these eight checks run on your own machine, against the stub, before you talk to anybody. The last one is a live call, 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":8,"onStub":6,"productionOnly":1,"partial":0,"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":["hard-code business hours instead of reading the hours and holidays the business set","write a second job because a different desk took the second call","raise an invoice or send a chase on a job with an open complaint","find the job for a message or an invoice by the customer's name","keep a customer contact anywhere but the job's timeline","send the owner more than one list, or a line with no owner"],"stays_with_person":["every complaint and dispute, by the owner named for complaints","every exception on the end-of-day list, by its owner","setting the hours, the holidays and the owners","everything Client Desk and Cash Seam keep with a person"]},"job":"the whole office admin role: the desk and the money path on one job record","skill":"virtual-admin","stub":"local.mock_admin_role","production":null,"product":null,"insert":null,"with_us":{"kind":"scoped_build","note":"No hosted admin role as one product. Both desks keep their live step on Front Desk (Booked After Hours), and the money path on Recover (Get Paid). Joining them on one job record, with the hours rule, the complaint hold and the end-of-day list, is quoted as a scoped build on the business's own phone line, job system and ledger."}},"with_us_next":"No hosted admin role as one product. Both desks keep their live step on Front Desk (Booked After Hours), and the money path on Recover (Get Paid). Joining them on one job record, with the hours rule, the complaint hold and the end-of-day list, is quoted as a scoped build on the business's own phone line, job system and ledger. Send the failing check id for a fixed price; do not build the live step yourself.","default_path":null,"boundary":{"never":["hard-code business hours instead of reading the hours and holidays the business set","write a second job because a different desk took the second call","raise an invoice or send a chase on a job with an open complaint","find the job for a message or an invoice by the customer's name","keep a customer contact anywhere but the job's timeline","send the owner more than one list, or a line with no owner"],"stays_with_person":["every complaint and dispute, by the owner named for complaints","every exception on the end-of-day list, by its owner","setting the hours, the holidays and the owners","everything Client Desk and Cash Seam keep with a person"]},"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","write_invoice","read_rule","file_message"]},{"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_sms"]},{"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","publish_report"]},{"role":"call","label":"The live phone call","need":"An Australian number that discloses it is not a person, respects the Do Not Call Register and that state's calling hours, and keeps a record that survives a dispute.","when_missing":"Never free, and never a substrate question. This is the one role that carries a legal obligation with a named party attached, which is why every standard marks it `with_us` rather than letting a build fail on it.","with_us":true,"ports":["talk","disclose"]}],"registry":"https://aipathway.com.au/substrates.json"},"checks":[{"id":"hours-from-the-rule","n":1,"title":"The business's own hours decide which desk answers, read at the time of the call","statement":"Whether a call is handled by the daytime desk (Client Desk) or the after-hours desk (Booked After Hours) is decided by the hours and holidays the business set, read when the call arrives. Nothing hard-codes nine to five, and a public holiday the business closes on is after hours.","assertable":"stub","ports":["read_rule","talk"],"stimulus":"Four calls: Monday 10am, Monday 6pm, Saturday 11am, and 10am on a weekday the business has marked as a public holiday.","assertion":"Only the Monday 10am call is handled by the daytime desk. The other three are handled by the after-hours desk. The hours were read on every call before the desk was chosen. A call put on the wrong desk, or a desk chosen with no read of the hours, fails."},{"id":"one-job-across-both-desks","n":2,"title":"A customer who rings both desks has one job","statement":"A call at night and a call the next morning from the same number about the same thing make one job. Whichever desk answers second attaches to the open job with duplicate_of set, rather than writing its own.","assertable":"stub","ports":["read_job","write_job"],"stimulus":"A caller rings at 9pm about a leaking roof, which the after-hours desk writes as a job. They ring again at 9am the next day about the same leak.","assertion":"Exactly one job exists for the number. The morning call is attached to it with duplicate_of set to that job's id. A second job, whichever desk wrote it, fails."},{"id":"said-on-the-phone-reaches-the-money","n":3,"title":"A complaint or dispute taken on a call stops the invoice and the chase","statement":"When a caller complains about the work or disputes what they owe, the desk writes it onto the job as an exception, owned by the person the business named for complaints. The invoice step and the chase step read the job first and hold while that exception is open.","assertable":"stub","ports":["read_job","escalate","write_invoice","send_sms"],"stimulus":"Two jobs are in progress. The customer on one rings the daytime desk to say the gutter is still leaking after the repair. Both jobs are then marked complete and, a month later, the scheduled chase runs.","assertion":"The complaint is an exception on that job, naming the complaints owner. No invoice is raised and no chase is sent on that job. The other job is invoiced and chased as normal. An invoice or a chase on the job with the open complaint, or a complaint left only in a call note, fails."},{"id":"one-event-one-job-id","n":4,"title":"The job-done message and the invoice come from the same event and name the same job","statement":"Job completed is one event. The completion message to the customer and the invoice are both woken by it and both carry the job id it named, read by that id. Neither finds its job by the customer's name.","assertable":"stub","ports":["read_job","send_sms","write_invoice"],"stimulus":"Two customers with the same name each have a job. Only one job is marked complete.","assertion":"Exactly one completion message and one invoice exist, both naming the completed job's id and both triggered by that job's completion event. A message or an invoice on the other customer's job, or a search by name while the event is handled, fails."},{"id":"timeline-is-the-record","n":5,"title":"Every call, message, invoice and chase is on the job's timeline","statement":"The job is the one record of the customer. Every call either desk takes, every status message, the invoice and every chase are written to that job's timeline, so whoever picks up the phone next, person or agent, sees all of it in one place.","assertable":"stub","ports":["file_message"],"stimulus":"One job through the whole role: an after-hours call, a daytime status call, a completion, an invoice and a chase.","assertion":"The job's timeline holds an entry for each of the two calls, the completion message, the invoice and the chase, each naming the call or event behind it. Anything that happened to the customer and is missing from the timeline fails."},{"id":"one-list-for-the-owner","n":6,"title":"One list at the end of the day, across both halves, with an owner on every line","statement":"At the close of business the build publishes one list: every open exception, callback and held invoice from both the desk and the money path, each with the person who owns it as the business named them. Nothing open is missing and nothing is unowned.","assertable":"stub","ports":["read_rule","read_job","publish_report"],"stimulus":"A day with a status question the job could not answer, which left a callback, a complaint that held an invoice, and a failed invoice write. Then the end-of-day event.","assertion":"Exactly one list is published. It holds a line for the callback, the held invoice and the failed write, each naming an owner from the owners the business set. Two lists, a missing line, or a line with no owner fails."},{"id":"survives-the-builder","n":7,"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 both desks, the money path and the end-of-day list 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":8,"title":"The pass test passes","statement":"On the business's own number, job system and ledger: ring after hours, ring again the next morning and complain about the work, then mark the job complete. One job exists, the morning call is attached to it, the complaint is an exception with an owner, no invoice or chase goes out on it, and the owner's end-of-day list shows it.","assertable":"production","ports":["talk","disclose","read_rule","read_job","write_job","escalate","write_invoice","file_message","publish_report"],"stimulus":"Two real calls to the real number from one phone, one after hours and one in business hours, a real completion in the job system, and the real end-of-day run.","assertion":"Both recordings open with the disclosure. The job system holds one job with both calls on its timeline, and the exception on it names the complaints owner. The ledger holds no invoice for that job. The list the owner received names the complaint and its owner.","stubLimit":"Deliberately unpassable on a stub. A stub's clock says whatever the runner sets and its two desks share memory by construction. Only the real phone line proves the hours rule picked the desk, only the real job system proves both desks found the same job, and only the real ledger proves the invoice did not go out."}]}