{"spec":"AIP-BS-PARTS-READY","version":"2026-11-01","standard":"parts-ready-build-standard","source":"https://aipathway.com.au/explore-ai/parts-ready-build-standard","markdown":"https://aipathway.com.au/explore-ai/parts-ready-build-standard.md","licence":"CC BY 4.0","how_to_read":"Nine of these ten checks run on your own machine, against the stub, before you talk to anybody. The last one is a live tenant, and that one runs against your own live systems.","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; runs against your own live systems (see with_us_next)","evidence_required":"not a software check. Attach what proves it"},"tally":{"total":10,"onStub":9,"productionOnly":1,"partial":2,"preconditions":0},"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"},"read_memory":{"verb":"read","subject":"memory"},"propose_memory":{"verb":"write","subject":"proposal"},"write_memory":{"verb":"write","subject":"memory"},"close_job":{"verb":"write","subject":"job"},"raise_exception":{"verb":"write","subject":"exception"},"list_exceptions":{"verb":"read","subject":"exception"},"evidence":{"verb":"attach","subject":"evidence"},"cite":{"verb":"attach","subject":"citation"},"rank":{"verb":"decide","subject":"order"},"gate":{"verb":"decide","subject":"gate"}},"provider":{"boundary":{"never":["guess what a job needs when the record holds no needs list","trust a van count older than the business's limit","write a purchase order outside Material Order's path","order a part that cannot arrive before the job","substitute a part, or change a job's parts list, without a person","order or message twice for one job on a re-run"],"stays_with_person":["moving a job, or sourcing a part, when the lead time misses the slot","accepting an equivalent part","writing the needs template for each job type","counting the van","every flag on the exceptions queue"]},"job":"the tech opens the van tomorrow and the part is there","skill":"parts-ready","stub":"local.mock_parts_board","production":null,"product":null,"insert":null,"with_us":{"kind":"own_systems","note":"No hosted step. The check runs on the business's own board, van counts and shelf, and a shortfall is ordered through Material Order's path, whose order write is the builder's until Office Voice's purchase order tools ship. The pass test runs on the business's own systems."}},"with_us_next":"No hosted step. The check runs on the business's own board, van counts and shelf, and a shortfall is ordered through Material Order's path, whose order write is the builder's until Office Voice's purchase order tools ship. The pass test runs on the business's own systems. There is nothing to buy: your build does this step on the business's own systems. No production run is offered yet, because the live port map is not published, so do not run AIPATHWAY_ENV=production. Run the pass test by hand on the real systems and attach the evidence.","default_path":null,"boundary":{"never":["guess what a job needs when the record holds no needs list","trust a van count older than the business's limit","write a purchase order outside Material Order's path","order a part that cannot arrive before the job","substitute a part, or change a job's parts list, without a person","order or message twice for one job on a re-run"],"stays_with_person":["moving a job, or sourcing a part, when the lead time misses the slot","accepting an equivalent part","writing the needs template for each job type","counting the van","every flag on the exceptions queue"]},"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","read_asset","read_contacts","write_order","raise_exception"]},{"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":"jobs-come-from-the-board","n":1,"title":"Tomorrow's jobs come from the dispatched board, by job id, and a job added later re-runs for that job only","statement":"The run reads tomorrow's board and works from each slot's job id and assigned tech, never from an address or a customer name. A job added to the board after the first run is checked on the next run on its own: the jobs already checked are not re-ordered, re-reserved or re-sent.","assertable":"stub","ports":["read_job","publish_report"],"stimulus":"Three jobs on tomorrow's board. The run goes, a fourth job is added to the board, and the run goes again.","assertion":"The board is read before anything is reserved, ordered, sent or published. Every result names a job id that is on the board. The second run reserves, orders, messages and flags only for the added job."},{"id":"needs-come-from-the-record","n":2,"title":"What a job needs comes from the record, and a job with no needs list is flagged, never guessed","statement":"A job's needs are the accepted quote's lines, the job's own materials, or a written template for its job type, in that order. A job with none of these is flagged needs_list_unknown to a person with an owner. The build never works out from the job's description, or from what is usual, what a job needs.","assertable":"stub","ports":["read_job","raise_exception"],"stimulus":"Tomorrow's board holds a job with quote lines, a job whose needs come from its type's template, and a job with no quote lines, no materials and no template for its type.","assertion":"The needs are read for each job before anything is written for it. The job with no needs list has one exception, needs_list_unknown, with an owner, and nothing reserved, ordered or listed on a tech's message for it. The template job's lines are the template's."},{"id":"stock-is-read-with-an-as-of","n":3,"title":"Van stock is read with the time it was counted, and a count older than the limit is flagged, not trusted","statement":"Van and shelf stock are read on every run, each with an as-of time. Van stock counted longer ago than the business's limit (24 hours unless it sets another) is stock_unknown: no line is reported as on the van from it, and each of that job's lines is flagged to a person.","assertable":"stub","ports":["read_asset","raise_exception"],"stimulus":"Two techs: one van counted this morning, one van last counted three days ago. Each has a job tomorrow whose part the van count shows.","assertion":"Each van is read. The fresh van's job reports its part on_van. The stale van's job reports nothing on_van, and each of its lines has a stock_unknown exception with an owner."},{"id":"reserve-before-order","n":4,"title":"Shelf stock is reserved to the job before anything is ordered","statement":"Before any order is requested, the shelf is read. A line the shelf covers is reserved against that job, the same reserve as Material Order's check 2, and is not ordered. The tech is told where to pick it up.","assertable":"stub","ports":["read_asset","write_order"],"stimulus":"A job tomorrow whose part is not on the tech's van but is on the shelf at a named location.","assertion":"The shelf read precedes the first reservation or order request. The part is reserved against the job, appears on no order request, and the job's result says pick_from_shelf with the location."},{"id":"order-only-through-material-order","n":5,"title":"A shortfall is ordered only through Material Order, one request per supplier per run, and a second run orders nothing again","statement":"Parts Ready never writes a purchase order. A part held nowhere is handed to Material Order's path, grouped into one request per supplier per run, so that standard's threshold and approved-supplier rules decide the release. A second run on the same board with nothing changed requests nothing.","assertable":"both","ports":["read_contacts","write_order"],"stimulus":"Two jobs tomorrow whose parts are held nowhere, both from the same supplier. The run goes twice, half an hour apart.","assertion":"The first run makes exactly one order request to that supplier carrying both jobs' lines, each line naming its job. The second run makes no order request. Each job's result is ordered with the purchase order id that came back.","stubLimit":"The stub proves one request per supplier through the port and no second request on a re-run. Only the live path proves Material Order's threshold and approved-supplier rules actually held the order, and that the purchase order id is real."},{"id":"lead-time-decides","n":6,"title":"A part that cannot arrive before the job is flagged with the reason, not ordered and hoped for","statement":"Each supplier's lead time is read as data. A part whose arrival would land after the job's slot is flagged lead_time_after_job, naming the job, so a person can move the job or source the part. It is not ordered.","assertable":"stub","ports":["read_contacts","raise_exception"],"stimulus":"Two parts held nowhere: one from a supplier who delivers overnight, one from a supplier whose lead time is three days.","assertion":"The supplier list is read before any order request. The overnight part is ordered. The slow part is on no order request, and has one lead_time_after_job exception naming its job, with an owner."},{"id":"no-silent-substitution","n":7,"title":"An equivalent part is a suggestion on the flag, never a change to the order, the reservation or the tech's list","statement":"When a part cannot be supplied in time and another part in stock would do the same job, the build may say so on the flag. It never reserves, orders or lists the other part for the tech, and never changes the job's parts list. A person decides.","assertable":"stub","ports":["raise_exception","write_order"],"stimulus":"A part that cannot arrive in time, while the tech's van holds a part marked as able to stand in for it.","assertion":"Every reserved, ordered and messaged line carries a code on that job's needs list. The part is flagged. If an equivalent is named, it is named only on the exception."},{"id":"tech-told-once","n":8,"title":"Each tech is told once per job, with the job id: what is on the van, what to pick up and where","statement":"Each tech with a job tomorrow gets one message for that job, carrying the job id: the parts on the van, the parts to pick up and the shelf location, and anything flagged. A second run with nothing changed sends nothing.","assertable":"both","ports":["notify","read_job"],"stimulus":"Two techs with jobs tomorrow, one part on a van and one on the shelf. The run goes twice.","assertion":"Across both runs, each tech and job pair appears in exactly one message. The first tech's message lists the van part for the job id; the second's lists the shelf part with its location.","stubLimit":"The stub proves one message per tech and job with the right lines. Only a real send proves the message reached the tech's phone or inbox before they left."},{"id":"publishes-by-the-cutoff","n":9,"title":"The run publishes by the cut-off whether or not it found anything, and every flag goes to the exceptions queue with an owner","statement":"Every run publishes a result for tomorrow's date by the business's cut-off (15:00 unless it sets another), including when every part is already on a van. A quiet afternoon is a result, not silence. Every flag is written to the business's one exceptions queue with an owner.","assertable":"stub","ports":["publish_report","raise_exception"],"stimulus":"A board where every part tomorrow is already on the van.","assertion":"One result is published for tomorrow's date, at or before the cut-off, with every job on the board in it. Every exception raised, in any scenario, names an owner."},{"id":"pass-test","n":10,"title":"The pass test passes","statement":"On tomorrow's real board: one job whose part is on the van, one whose part is only on the shelf, and one whose part is held nowhere. By the cut-off each tech is told correctly, the shelf shows the reservation, and the third job has a purchase order through Material Order or a flag naming it. A re-run orders nothing again.","assertable":"production","ports":["read_job","read_asset","write_order","notify"],"stimulus":"The business's own job system, its real van counts and shelf, a real supplier account through Material Order, and the techs' own phones.","assertion":"The first tech's message lists the part as on the van. The second says pick it up from the shelf, and the shelf shows it reserved to the job. The third has a purchase order drafted through Material Order, or, if the lead time misses the slot, a flag naming the job. Change nothing and re-run: no second order and no second message.","stubLimit":"Deliberately unpassable on a stub. A stub van count is whatever the runner seeded, and a stub supplier accepts every request. Only the real board, a van somebody counted, a real reservation on the shelf and a real purchase order id prove the tech will open the van tomorrow and find the part."}]}