{"spec":"AIP-BS-REVIEW-REQUEST","version":"2026-10-24","standard":"review-request-build-standard","source":"https://aipathway.com.au/explore-ai/review-request-build-standard","markdown":"https://aipathway.com.au/explore-ai/review-request-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":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"},"evidence":{"verb":"attach","subject":"evidence"},"cite":{"verb":"attach","subject":"citation"},"rank":{"verb":"decide","subject":"order"},"gate":{"verb":"decide","subject":"gate"}},"provider":{"boundary":{"never":["send the review link only to customers who scored 4 or 5, or send a low scorer anywhere else","hold or skip an ask because of a score, a predicted mood or anything the customer said","send anything to a customer after they reply, or a second reminder","write, draft, suggest or post a review, or reply to one on Google","offer a discount, voucher, draw or anything else in exchange for a review","ask a customer who opted out, on any later job","ask about a job the job system does not show as finished"],"stays_with_person":["answering every Google review, good and bad","calling a customer who scored 1 to 3","closing a callback, settling a dispute or closing a complaint in the job system","writing the message, the wait, the reminder interval and the window","taking a customer off the opt-out list, only if they ask"]},"job":"every customer asked for a Google review the same way once the job is finished, with low scores sent to the owner to call","skill":"review-request","stub":"local.mock_review_jobs","production":null,"product":null,"insert":null,"with_us":{"kind":"scoped_build","note":"No hosted product. Reading the job's status, linked callbacks and invoice state from ServiceM8, Simpro, Fergus, Jobber or AroFlo at the moment of sending, and bringing SMS replies and STOPs back from the carrier, run on the business's own job system and messaging account and are quoted as a scoped build."}},"with_us_next":"No hosted product. Reading the job's status, linked callbacks and invoice state from ServiceM8, Simpro, Fergus, Jobber or AroFlo at the moment of sending, and bringing SMS replies and STOPs back from the carrier, run on the business's own job system and messaging account and are quoted as a scoped build. Send the failing check id for a fixed price; do not build the live step yourself.","default_path":null,"boundary":{"never":["send the review link only to customers who scored 4 or 5, or send a low scorer anywhere else","hold or skip an ask because of a score, a predicted mood or anything the customer said","send anything to a customer after they reply, or a second reminder","write, draft, suggest or post a review, or reply to one on Google","offer a discount, voucher, draw or anything else in exchange for a review","ask a customer who opted out, on any later job","ask about a job the job system does not show as finished"],"stays_with_person":["answering every Google review, good and bad","calling a customer who scored 1 to 3","closing a callback, settling a dispute or closing a complaint in the job system","writing the message, the wait, the reminder interval and the window","taking a customer off the opt-out list, only if they ask"]},"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","record_consent","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":["notify","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":"trigger-is-the-job-finished","n":1,"title":"The ask starts when the job system says the job is finished","statement":"The trigger is the job marked complete, or paid, whichever the business picked and wrote down, read from the job system itself. Not a list somebody typed, not a guess from the calendar. A job still in progress is never asked about.","assertable":"both","ports":["read_rule","read_job","send_message"],"stimulus":"A business whose written trigger is paid, with one job paid, one complete but unpaid, and one still in progress in the job system.","assertion":"Only the paid job is asked, timed from when it was paid. A build that fires on complete when the written rule says paid, or asks about the job in progress, fails.","stubLimit":"On the stub the runner sets each job's status. Only the real job system (ServiceM8, Simpro, Fergus, Jobber or AroFlo) proves the build sees the status change the office actually makes, on the day it makes it."},{"id":"the-wait-is-written","n":2,"title":"The ask waits the written time after the job is finished","statement":"The business writes down how long to wait (two hours, say) so the customer has seen the result. Nothing goes before that, whichever run of the build happens to notice the job first.","assertable":"stub","ports":["read_rule","send_message"],"stimulus":"A written wait of three hours. A job finished at 9am, with the build run at 9:30am, 11:05am and 12:05pm.","assertion":"No ask before noon, and one ask on the 12:05pm run. A build that assumes two hours, or sends on the first run that sees the job, fails."},{"id":"same-message-same-link","n":3,"title":"Every customer gets the same message and the same review link","statement":"One message for everyone: thanks, how did we go from 1 to 5, and the business's Google review link, with the business's name and a way to opt out. Nobody gets a different link, a private feedback form instead, or a second message because of what they replied.","assertable":"stub","ports":["send_message","read_inbox"],"stimulus":"A week of finished jobs. One customer replies 5, one replies 2, one does not reply.","assertion":"Every ask, with the customer's name taken out, is the same text, and every one carries the written link. Nothing is sent to a customer after they reply. A build that asks for the score first and sends the link only to a 4 or a 5, or sends a low scorer a feedback form, fails."},{"id":"not-finished-is-held","n":4,"title":"A job that is not really finished is held, with the reason","statement":"An open callback or rework job, an invoice marked disputed, or an open complaint recorded on the job in the job system means the job is not finished yet. The ask is held and the owner is told why. The hold reads the job, the same way for every customer, and is never based on a score, a guess at how the customer feels, or anything they said.","assertable":"stub","ports":["read_job","escalate","send_message"],"stimulus":"Three jobs marked complete: one with a callback job still open, one with its invoice marked disputed, one with an open complaint on it.","assertion":"Each is held with its own reason (open callback, disputed invoice, open complaint), and no ask is attempted for any of them. A hold with any other reason fails."},{"id":"read-again-before-sending","n":5,"title":"The job is read again at the moment of sending","statement":"A callback raised during the wait holds the ask, even though the job looked finished when it was first seen. The decision is made on the job as it stands when the message would go.","assertable":"stub","ports":["read_job","escalate","send_message"],"stimulus":"A job finished at 9am with nothing open. A callback job is raised against it at 10am, and the build runs again at 11:05am.","assertion":"No ask is attempted for that job, and it is held as an open callback. Sending on what the build saw at 9am fails."},{"id":"hold-clears-the-same-way","n":6,"title":"When the hold clears, the same ask goes the same way","statement":"Once the callback is closed, the dispute is settled or the complaint is closed in the job system, the customer gets exactly the message everyone else got. A hold is not a way to skip an awkward customer, whatever way the dispute ended.","assertable":"stub","ports":["read_job","send_message"],"stimulus":"A job held for a disputed invoice. A week later the dispute is marked settled in the job system, and the build runs.","assertion":"One ask goes for that job, and with the name taken out it is the same text, with the same link, as every other ask. A held job that is never asked after it clears fails."},{"id":"one-ask-per-customer-per-window","n":7,"title":"One ask per customer, however many jobs they had","statement":"At most one ask per customer in the window the business writes down (thirty days, say), and never two for one job. A customer with three jobs in a week hears from you once.","assertable":"stub","ports":["read_rule","read_inbox","send_message"],"stimulus":"One customer with three jobs finished on Monday, Wednesday and Friday, and the build run many times across the fortnight.","assertion":"That customer gets exactly one ask, and no job anywhere is asked twice."},{"id":"channel-and-opt-out","n":8,"title":"Asked only on the channel on record, and a STOP is for good","statement":"SMS or email, whichever the customer's record holds. A customer with neither is an exception, not a guess. A STOP or unsubscribe is recorded against the customer, not the job, and no later job ever asks them again.","assertable":"both","ports":["record_consent","gate","send_message","escalate"],"stimulus":"A customer replies STOP to an ask. Six weeks later they have a new job, finished and clean. Another customer has no mobile and no email on record.","assertion":"The opt-out is recorded, no reminder follows it, and no ask is attempted for the later job. Every message goes on the customer's own channel, and the customer with no way to reach them is raised as an exception with nothing sent.","stubLimit":"On the stub the runner delivers the STOP. Only the live channel proves it comes back from the carrier or the mail provider and is honoured inside the five working days the Spam Act allows."},{"id":"one-reminder-then-stop","n":9,"title":"One reminder if nothing comes back, then stop","statement":"If there is no reply and the business has not marked a review as posted after the written interval, one reminder goes with the same link. A reply, a STOP or a review marked as posted stops it. There is never a third message.","assertable":"stub","ports":["read_rule","read_job","read_inbox","send_message"],"stimulus":"Three asked customers: one replies, one never replies, and one leaves a review the office marks as posted. The build runs daily for a week.","assertion":"Only the customer who never replied gets a reminder, exactly one, no sooner than the written interval, carrying the same link. Nobody gets more than two messages for a job."},{"id":"low-score-alerts-the-owner","n":10,"title":"A low score tells the owner to call, and changes nothing for the customer","statement":"A reply of 1, 2 or 3 sends the owner an alert with the job, the customer and their words, so they can ring while it is fixable. The customer keeps the link they already have. Nothing asks them not to post, and nothing routes them anywhere else. A high score gets no extra push either.","assertable":"stub","ports":["read_inbox","notify","send_message"],"stimulus":"One customer replies 2, it was left in a mess. Another replies 5, great job.","assertion":"The owner is alerted about the 2 with the job named and the reply as written, and neither customer receives anything after replying. An alert for the 5, or any message to either customer after their reply, fails."},{"id":"never-makes-up-or-rewards-a-review","n":11,"title":"It never makes up, rewards or answers a review","statement":"The build never writes a review, suggests what to say or how many stars, offers anything in exchange (a discount, a voucher, a draw), posts anywhere, or replies to a review on Google. Answering reviews is the owner's job. There is nothing in the build that could post.","assertable":"stub","ports":["send_message"],"stimulus":"A full week of asks and reminders across every kind of customer.","assertion":"No message offers anything, mentions stars or a rating to give, or carries suggested review wording. A message promising a discount for a review fails."},{"id":"the-record-by-job","n":12,"title":"Every ask, reminder, reply, hold and alert is on the record by job","statement":"Kept so that a customer who says they were pestered, or a question about how the reviews came in, is answered from the record: what went, when, on which channel, what came back word for word, and why a job was held.","assertable":"both","ports":["evidence"],"stimulus":"A full week: asks, a reminder, replies of every kind, a STOP, holds and an alert.","assertion":"Every ask and reminder has its own record entry against its job, every reply holds the text exactly, and every hold and alert is recorded.","stubLimit":"On the stub the record is the runner's memory, so it proves the entries are made. Only a store the business owns, outside the build and kept after it, proves the record is there when somebody asks."},{"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 job system connection and the record 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":"Real jobs, marked finished in the real job system, produce one identical ask each at the written time. A job with an open callback is held until the callback closes. A low score, texted back days later from a real phone, reaches the owner, and the customer's link is never taken away.","assertable":"production","ports":["read_job","read_inbox","notify","evidence"],"stimulus":"Two real jobs marked complete in the job system, one with a callback job open against it. The owner replies 2 from their own phone to the ask that arrives. Later the callback is closed.","assertion":"One ask, with the link, for the clean job after the wait. The other is held, named as an open callback, and asked the same way once the callback is closed. The reply of 2 alerts the owner, nothing else is sent to that number, and the record holds all of it by job.","stubLimit":"Deliberately unpassable on a stub. On a stub the job status and the reply are the runner's; live, the status change comes out of ServiceM8, Simpro, Fergus, Jobber or AroFlo when the office makes it, and the reply comes back through the carrier days later."}]}