{"spec":"AIP-BS-JUST-SOLD-PACK","version":"2026-10-24","standard":"just-sold-pack-build-standard","source":"https://aipathway.com.au/explore-ai/just-sold-pack-build-standard","markdown":"https://aipathway.com.au/explore-ai/just-sold-pack-build-standard.md","licence":"CC BY 4.0","how_to_read":"Fourteen of these 16 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. 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; runs against your own live systems (see with_us_next)","evidence_required":"not a software check. Attach what proves it"},"tally":{"total":16,"onStub":14,"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":["state a sale's price, date or address that the verified record does not hold","state, estimate or hint at a price that was withheld","draft anything about a sale that is not yet public or was asked to be kept confidential","send a draft nobody approved","message a contact on a channel they did not consent to, or one flagged do not contact","say what the reader's own home is worth or could sell for","place a call unless the agency turned calling on"],"stays_with_person":["approving each message before it goes","the personal calls on the short list","anything about the reader's own home, its value or a price","clearing an exception, including a figure that does not match"]},"job":"a sale nearby becomes a message to the neighbours, with a person's yes on each one","skill":"just-sold-pack","stub":"local.mock_sale_feed","production":null,"product":null,"insert":null,"with_us":{"kind":"own_systems","note":"No hosted step. Verified sale facts are the hard step, so the production check runs on the agency's own sale record and a data source it already licenses. The neighbours did not ask to be called, so any call stays with the agent; Office Voice will not place it."}},"with_us_next":"No hosted step. Verified sale facts are the hard step, so the production check runs on the agency's own sale record and a data source it already licenses. The neighbours did not ask to be called, so any call stays with the agent; Office Voice will not place it. 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":["state a sale's price, date or address that the verified record does not hold","state, estimate or hint at a price that was withheld","draft anything about a sale that is not yet public or was asked to be kept confidential","send a draft nobody approved","message a contact on a channel they did not consent to, or one flagged do not contact","say what the reader's own home is worth or could sell for","place a call unless the agency turned calling on"],"stays_with_person":["approving each message before it goes","the personal calls on the short list","anything about the reader's own home, its value or a price","clearing an exception, including a figure that does not match"]},"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":["resolve_address","evidence","read_rule","read_contacts","read_sale","draft_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":["rank","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":["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"]},{"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":["disclose"]}],"registry":"https://aipathway.com.au/substrates.json"},"checks":[{"id":"sale-read-first","n":1,"title":"The sale is read from its source before anyone is matched","statement":"One sale, read with the time it was verified, is the trigger. Every fact a draft states comes from that read, never from memory or a typed note.","assertable":"both","ports":["read_sale","read_contacts","draft_message"],"stimulus":"A sale event with known facts, and a contact list near it.","assertion":"The sale is read before the contacts are, and every draft carries the street, the date and the price exactly as the read returned them.","stubLimit":"The stub proves the facts came through the read rather than being typed in. Only a live source proves they are true today: a stub feed agrees with whatever it was seeded with."},{"id":"not-public-holds-the-sale","n":2,"title":"A sale that is not public, or asked to be kept quiet, holds the whole pack","statement":"Before exchange is public, or when the vendor or the buyer asked for confidentiality, nothing is drafted and nobody is short-listed. One exception says which.","assertable":"stub","ports":["read_sale","draft_message","rank","escalate"],"stimulus":"A sale marked not yet public; then the same sale made public with the vendor asking for confidentiality.","assertion":"No draft and no short list exist after either run, and the exceptions name sale_not_public and confidential respectively."},{"id":"figures-must-match","n":3,"title":"Figures that disagree hold the sale","statement":"When the source and the agency's own record disagree on the price, the date or the address, the sale is held with the field named. Neither figure is used.","assertable":"stub","ports":["read_sale","draft_message","escalate"],"stimulus":"A sale whose source price differs from the agency's record of the same sale.","assertion":"No draft is written, and a figures_mismatch exception names the price. A build that picks one of the two figures fails."},{"id":"price-withheld-never-stated","n":4,"title":"A withheld price is never stated, estimated or hinted","statement":"The pack still goes, about the sale, with no price, no range and no rounded figure. A person is told the price was withheld.","assertable":"stub","ports":["read_sale","draft_message","escalate"],"stimulus":"A public sale with the price withheld at the source, while the agency's own record still holds the figure.","assertion":"Drafts exist, none carries a price or any figure that reads as one, and a price_withheld exception is raised."},{"id":"who-to-tell-three-reasons","n":5,"title":"Who to tell: same street, within the radius, or an owner who matches","statement":"A contact is in the pack for a reason that is true about where they live or what they own. Everybody else is left out, without a message.","assertable":"stub","ports":["read_contacts","resolve_address","read_rule"],"stimulus":"Contacts on the same street, inside the radius, owning a matching home further out, and one far away matching nothing.","assertion":"Each lands in the pack with its own reason, and the far contact has no entry at all."},{"id":"one-entry-per-contact","n":6,"title":"One entry per contact per sale","statement":"A contact who matches two reasons gets one message, under the strongest reason. Running the pack twice does not make a second.","assertable":"stub","ports":["read_contacts","draft_message"],"stimulus":"A contact on the same street who also owns a matching home, with the pack run twice for the same sale.","assertion":"That contact has exactly one entry, its reason is same street, and no contact has two entries for the sale."},{"id":"do-not-contact-absent","n":7,"title":"Do not contact means absent from every list","statement":"Not drafted, not short-listed for a call, and recorded as an exception so a person can see why a neighbour is missing.","assertable":"stub","ports":["gate","draft_message","rank","escalate"],"stimulus":"A contact on the same street, flagged do not contact, who would otherwise rank first.","assertion":"No draft and no short-list place for that contact, and a do_not_contact exception names them."},{"id":"channel-follows-consent","n":8,"title":"Email or SMS only with consent for that channel","statement":"A contact with no consent for electronic messages gets a letter if there is a postal address, and is held as an exception if there is not.","assertable":"stub","ports":["gate","read_contacts","draft_message","escalate"],"stimulus":"Contacts with email consent, SMS consent, no consent but a postal address, and no consent and no address.","assertion":"Every draft's channel is one the contact consented to or a letter, and the last contact has no draft and a no_consent exception."},{"id":"active-sequence-held","n":9,"title":"A contact already in another sequence is held","statement":"Someone mid-way through another run of messages is not sent a second one about a sale. They are held, and a person decides.","assertable":"stub","ports":["gate","read_contacts","escalate"],"stimulus":"A contact inside the radius who is already in an active follow-up sequence.","assertion":"No draft for that contact, and an active_sequence exception names the sequence."},{"id":"draft-states-only-the-record","n":10,"title":"A draft states the sale and nothing about the neighbour's own home","statement":"The street, the date and, if disclosed, the price, as recorded. Never a value, a range or a guess for the reader's own property.","assertable":"stub","ports":["draft_message","read_sale"],"stimulus":"A pack drafted for a public sale with a disclosed price.","assertion":"Every draft names the street, carries the recorded date and price, and contains no statement of what the reader's home is worth or could sell for."},{"id":"call-list-orders-never-permits","n":11,"title":"The call short list orders, it never permits","statement":"Ranked by the stated rules, in order: prior engagement, then reason, then longest since last spoken to. Anyone held is not on it, whatever their rank would have been.","assertable":"stub","ports":["rank","gate"],"stimulus":"A seeded set whose correct order under the rules is known and not the obvious one, with a held contact who would rank first.","assertion":"The short list matches the expected order exactly and holds nobody the gate refused."},{"n":12,"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":"human-approves-every-send","title":"A person approves every send","statement":"Nothing leaves on its own. A draft is sent only after a named person approved that entry, and an unapproved draft stays a draft.","ports":["draft_message","send_message"],"stimulus":"A pack drafted, one entry approved by a named person, and the build run again.","assertion":"Exactly the approved entry is sent, once. No send is attempted on an unapproved entry, and none is repeated."},{"id":"calling-off-by-default","n":13,"title":"Calling is off until the agency turns it on, and then goes through a gate","statement":"The short list is for the agent to ring personally. No call is opened by the build unless calling was turned on, and then every call asks the gate first.","assertable":"both","ports":["gate","disclose"],"stimulus":"The pack run with calling off; then calling turned on with the top short-listed contact on the do-not-call register.","assertion":"No call is opened in the first run. In the second, that contact is not called and the gate names the register, and every call opened was cleared by the gate first.","stubLimit":"The stub proves the switch and the order of gate then call. A register wash, calling hours on a real line and the disclosure heard by a person are only proved live."},{"id":"every-exception-explained","n":14,"title":"Every exception has a kind and a reason","statement":"Each one names its kind from the closed list, the sale, the contact where there is one, and why. A count without reasons is not a log.","assertable":"stub","ports":["escalate","evidence"],"stimulus":"A pack where several contacts are held for different reasons.","assertion":"Every exception carries a kind on the closed list, the sale id and a non-empty reason, and more than one kind appears."},{"id":"survives-the-builder","n":15,"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 matching, the drafts and the log 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":16,"title":"The pass test passes","statement":"On a real sale from a live source, a figure that disagrees with the agency's record holds the sale, and a do-not-contact neighbour is absent from every list.","assertable":"production","ports":["read_sale","gate","send_message"],"stimulus":"A real recent sale read live, the agency's record altered by one figure, then corrected, against the real contact list with one neighbour flagged do not contact.","assertion":"The altered run drafts nothing and names the field. The corrected run drafts only with the figures the live source verified that day, sends nothing unapproved, and never reaches the flagged neighbour.","stubLimit":"Deliberately unpassable on a stub. A stub feed agrees with you; a live source is somebody else's data, can withhold a price after settlement, and can change between the read and the send."}]}