{"spec":"AIP-BS-NEXT-SERVICE","version":"2026-10-24","standard":"next-service-build-standard","source":"https://aipathway.com.au/explore-ai/next-service-build-standard","markdown":"https://aipathway.com.au/explore-ai/next-service-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"},"evidence":{"verb":"attach","subject":"evidence"},"cite":{"verb":"attach","subject":"citation"},"rank":{"verb":"decide","subject":"order"},"gate":{"verb":"decide","subject":"gate"}},"provider":{"boundary":{"never":["type a due date by hand, or use a date somebody typed onto the job","guess an install date, a model, an equipment type, a schedule or a warranty term the record does not hold","remind a customer who already has a job open for that asset","message a customer on a channel they did not agree to, or anyone who asked to stop","send more than one reminder and one follow-up for a date","say what a warranty covers, that a repair is covered, or anything about the customer's rights","write a second booking request for the same asset and date","pick a time slot or a price"],"stays_with_person":["booking the time and pricing the work for every booking request","writing and changing the service schedule","fixing a record with a missing install date, model or type","answering any reply that is not a yes or a stop","anything about warranty cover or consumer rights"]},"job":"every unit you installed or serviced gets a reminder before it is due, and a yes becomes one booking request","skill":"next-service","stub":"local.mock_service_book","production":null,"product":null,"insert":null,"with_us":{"kind":"scoped_build","note":"No hosted step. Writing each yes into the live job system as exactly one booking request against the right customer and asset, safe to retry, is the step not to hand-roll, and it is quoted as a scoped build on the job system the business already runs. Tradify has no public API, so it cannot meet that check live."}},"with_us_next":"No hosted step. Writing each yes into the live job system as exactly one booking request against the right customer and asset, safe to retry, is the step not to hand-roll, and it is quoted as a scoped build on the job system the business already runs. Tradify has no public API, so it cannot meet that check live. Send the failing check id for a fixed price; do not build the live step yourself.","default_path":null,"boundary":{"never":["type a due date by hand, or use a date somebody typed onto the job","guess an install date, a model, an equipment type, a schedule or a warranty term the record does not hold","remind a customer who already has a job open for that asset","message a customer on a channel they did not agree to, or anyone who asked to stop","send more than one reminder and one follow-up for a date","say what a warranty covers, that a repair is covered, or anything about the customer's rights","write a second booking request for the same asset and date","pick a time slot or a price"],"stays_with_person":["booking the time and pricing the work for every booking request","writing and changing the service schedule","fixing a record with a missing install date, model or type","answering any reply that is not a yes or a stop","anything about warranty cover or consumer rights"]},"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","read_asset","record_service","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":"captured-at-completion","n":1,"title":"The equipment is taken from the finished job, as recorded","statement":"When a job is marked complete, its equipment type, make and model, serial if any, install or service date and warranty term are recorded against the asset exactly as the job holds them. A job that is not complete records nothing, and running twice records nothing twice.","assertable":"stub","ports":["read_job","record_service"],"stimulus":"Completed install and service jobs carrying equipment details, one job still open, and the run repeated.","assertion":"Each completed job has exactly one service record whose fields equal the job's, and the open job has none."},{"id":"missing-details-to-a-person","n":2,"title":"A missing install date, model or equipment type goes to a person","statement":"An asset without one of them, or of a type with no written schedule, is raised as an exception with the gap named. No date is guessed, no schedule is borrowed from a manufacturer the record does not name, and no reminder goes.","assertable":"stub","ports":["read_asset","read_rule","escalate","send_message"],"stimulus":"Four assets: no install date, no model, no equipment type, and a type that has no schedule written for it.","assertion":"Each is raised with its own kind (missing_install_date, missing_model, missing_equipment_type, no_schedule) and none of them has a reminder attempted."},{"id":"dates-derived-not-stored","n":3,"title":"Due dates are worked out from the schedule, never typed in","statement":"The next service date is the install or last service date plus the interval the written schedule gives for that equipment type, worked out on every run. Change the schedule and the dates move. A date somebody typed onto the job is never used. The same rule as the Compliance Calendar standard.","assertable":"stub","ports":["read_rule","read_asset","send_message"],"stimulus":"A hot water system serviced six months ago with a twelve-month schedule and a typed next-due date, then the schedule changed to six months.","assertion":"Nothing goes before the change. After it, a reminder goes for the date six months on from the service, with no date edited anywhere. A build that stored or typed the date fails."},{"id":"warranty-from-the-record","n":4,"title":"The warranty expiry comes from the term on the record","statement":"Install date plus the warranty term the job recorded. No term on the record, no warranty reminder: the term is never assumed from the make, the type or what is usual.","assertable":"stub","ports":["read_asset","send_message"],"stimulus":"Two identical solar inverters installed the same day, one with a sixty-month term on the record and one with none.","assertion":"The first gets a warranty reminder dated its install plus sixty months. The second gets no warranty reminder at all."},{"id":"reminder-ahead-of-the-date","n":5,"title":"One reminder, a written lead time ahead of each date","statement":"The lead time is written down, not decided per customer. A reminder goes inside that window before the date, once per date, and never for a date already past.","assertable":"stub","ports":["read_rule","send_message"],"stimulus":"A book of assets with dates inside the lead time, further out, and already past, run twice on the same day.","assertion":"Each date inside the window has exactly one reminder, sent no earlier than the lead time before it and no later than the date. Dates further out or past have none."},{"id":"channel-they-agreed-to","n":6,"title":"Only on a channel the customer agreed to","statement":"SMS to someone who agreed to SMS, email to someone who agreed to email. A customer with neither is an exception for a person, not a message.","assertable":"stub","ports":["gate","send_message","escalate"],"stimulus":"A customer who agreed to email only, and a customer with no consent recorded for either, both with a date in the window.","assertion":"The first is reminded by email. The second has no attempted message and a no_consent exception. The trace is read, so a send the world refused still counts as the failure."},{"id":"already-booked-no-reminder","n":7,"title":"Already booked means no reminder","statement":"Before every reminder the job system is checked for an open job on that asset. If one exists, the customer is already sorted and hears nothing.","assertable":"both","ports":["read_job","send_message"],"stimulus":"An air conditioner due for its service that already has a job scheduled for it in the job system.","assertion":"No reminder is attempted for it, and every reminder that is attempted was preceded by a read of the open jobs for that asset.","stubLimit":"The stub proves the check is made before each send. Only the live job system proves the build reads the status that means booked there, which differs between ServiceM8, Simpro, Fergus, Jobber and AroFlo."},{"id":"one-follow-up-then-stop","n":8,"title":"One follow-up if there is no answer, then stop","statement":"A written number of days after the reminder, one follow-up, and nothing more for that date. Any reply ends the follow-ups, and a reply that is not a yes or a stop goes to a person.","assertable":"stub","ports":["read_inbox","send_message","escalate"],"stimulus":"Reminders sent, one customer replies with a question, and the clock is moved on past the follow-up gap twice and then by weeks.","assertion":"No date has more than one reminder or one follow-up, every follow-up is at least the gap after its reminder, the customer who replied gets no follow-up, and their reply is raised as unclear_reply."},{"id":"opt-out-for-good","n":9,"title":"A stop is honoured for good, on every asset","statement":"A reply of STOP, or any request to stop, is recorded as withdrawn consent for that customer. Nothing is sent to them again, about any asset, including equipment installed after they said it.","assertable":"stub","ports":["record_consent","gate","send_message"],"stimulus":"A customer replies STOP, then a new install for the same customer is completed with its service date in the window.","assertion":"The withdrawal is recorded and no message is attempted to that customer after it. A new job does not clear it: only the customer, asking, does."},{"id":"warranty-says-only-the-date","n":10,"title":"A warranty reminder states the expiry date and nothing about cover","statement":"It says when the warranty on the record ends. It never says what the warranty covers, that a repair would be covered, or anything about the customer's rights under the consumer guarantees.","assertable":"stub","ports":["send_message"],"stimulus":"A warranty reminder for an inverter whose recorded term ends inside the window.","assertion":"The message carries the expiry date exactly as derived, and no message contains a claim about cover, a free repair, a claim or a guarantee."},{"id":"one-booking-request","n":11,"title":"A yes becomes one booking request in the job system","statement":"A reply of yes, or a tap on the booking link, writes one booking request into the job system against that customer and that asset. A second yes, or a second run, makes no second one.","assertable":"both","ports":["read_inbox","read_job","write_job"],"stimulus":"One customer replies YES by SMS and later YES again; another books through the link; the run is repeated.","assertion":"Exactly one booking request exists for each of the two assets, each against the right customer and asset, however many times the build ran.","stubLimit":"The stub proves the build checks before it writes. Only the live job system proves the request lands against the real customer and asset there, and that a retry after a timeout does not make a second job."},{"id":"no-time-no-price","n":12,"title":"The build never picks a time or a price","statement":"The booking request carries the customer, the asset and what they said. A person books the time and prices the work, and is told a request is waiting.","assertable":"stub","ports":["write_job","notify"],"stimulus":"Two customers say yes in the same week.","assertion":"Neither booking request carries a time slot or a price, and a person is notified of each one by its job number."},{"id":"record-by-asset","n":13,"title":"Every reminder, reply and booking is on the record, by asset","statement":"Each reminder and follow-up with its date and channel, each reply word for word, each stop and each booking request with its job number, kept against the asset so the history of one hot water system can be read in one place.","assertable":"both","ports":["evidence"],"stimulus":"A run with reminders, a yes, a stop and a follow-up.","assertion":"Every send, reply, stop and booking request has its own record entry naming the asset, and each reply entry holds the reply text exactly.","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 outlives it and nobody edits, proves the history survives."},{"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 schedule, the reminders 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":15,"title":"The pass test passes","statement":"In the business's own job system: change a schedule and the due dates move without anyone editing a date; a customer marked do not contact hears nothing; a YES from a real phone makes exactly one booking request, and a second YES makes no second one.","assertable":"production","ports":["read_rule","record_consent","read_inbox","write_job"],"stimulus":"The real schedule changed for one equipment type, one real customer marked do not contact, and YES sent twice from the tester's own phone to a real reminder.","assertion":"The dates for that type move with no date edited, the do-not-contact customer has no message, and the live job system shows one booking request for the asset after both replies.","stubLimit":"Deliberately unpassable on a stub. On a stub the reply arrives because the runner wrote it and the job list is the runner's memory; live, the YES comes back through a carrier, the booking request lands in somebody else's system, and a retry after a slow write is where the second job comes from."}]}