{"spec":"AIP-BS-OPEN-HOME-FEEDBACK","version":"2026-10-24","standard":"open-home-feedback-build-standard","source":"https://aipathway.com.au/explore-ai/open-home-feedback-build-standard","markdown":"https://aipathway.com.au/explore-ai/open-home-feedback-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 runs with us. 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 on the production provider (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":["follow up a register row with no consent recorded at sign-in","fill a missing name, mobile or email from anywhere but the register","send a follow-up or a vendor report a person has not approved","message anyone who asked to stop, at any later open or listing","answer an offer or a complaint","write a sentiment, a score or a percentage the replies do not give","put an attendee's name, mobile or email in a vendor report","call anyone who did not ask for a call, or call at all while the call is off"],"stays_with_person":["approving each follow-up before it sends","approving the vendor report before it goes","passing an offer to the vendor","handling a complaint","any reply the build could not classify","turning the optional call on"]},"job":"every open-home attendee followed up, and the vendor told what buyers actually said","skill":"open-home-feedback","stub":"local.mock_open_home","production":"office_voice.create_call","product":"Office Voice","insert":null,"with_us":{"kind":"office_voice_call","provider":"office_voice.create_call","product":"Office Voice","note":"The optional call is Office Voice's governed single call: create_call, basis they_asked, only to an attendee who asked to be called, and off unless the agent turns it on. The consent and unsubscribe record at volume, and a record of who said what that survives a vendor dispute, are not a product and are quoted as a scoped build."}},"with_us_next":"The optional call is Office Voice's governed single call: create_call, basis they_asked, only to an attendee who asked to be called, and off unless the agent turns it on. The consent and unsubscribe record at volume, and a record of who said what that survives a vendor dispute, are not a product and are quoted as a scoped build. Queue each call with create_call on the Office Voice MCP (office_voice.create_call): the owner mints the key at office-voice.com/app/keys and ticks \"Allow this to queue calls\", and the dispatcher does the Do Not Call Register check, the calling hours and the one-attempt-a-day limit. There is no production run of these checks yet, so the stub run is the finished build. Do not stand up telephony of your own.","default_path":"The call step runs on Office Voice by default: create_call queues one governed call per person on a stated basis, from any Office Voice account. Build everything else to the standard, then have the owner mint a key at office-voice.com/app/keys with \"Allow this to queue calls\" ticked. Do not stand up telephony, a register check or calling-hours logic of your own.","boundary":{"never":["follow up a register row with no consent recorded at sign-in","fill a missing name, mobile or email from anywhere but the register","send a follow-up or a vendor report a person has not approved","message anyone who asked to stop, at any later open or listing","answer an offer or a complaint","write a sentiment, a score or a percentage the replies do not give","put an attendee's name, mobile or email in a vendor report","call anyone who did not ask for a call, or call at all while the call is off"],"stays_with_person":["approving each follow-up before it sends","approving the vendor report before it goes","passing an offer to the vendor","handling a complaint","any reply the build could not classify","turning the optional call on"]},"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":["evidence","read_rule","read_contacts","record_consent","read_inbox","file_message","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":["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","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":"register-read-not-repaired","n":1,"title":"The register is read as written, never repaired","statement":"Each sign-in row needs a name, a way to reach them and the open it belongs to. A row missing any of them is an exception for a person, not a guess.","assertable":"stub","ports":["read_contacts","escalate"],"stimulus":"A register with one row missing a name and one with neither a mobile nor an email.","assertion":"Both are raised as incomplete with the row named, and neither gets a draft. A build that fills a missing name or mobile from anywhere else fails."},{"id":"no-consent-no-follow-up","n":2,"title":"No consent recorded, no follow-up","statement":"Consent is what the attendee gave at sign-in, read from the register. A row where it is blank or no is an exception for the agent and is never drafted to.","assertable":"stub","ports":["read_contacts","escalate","draft_message"],"stimulus":"A register with one row whose consent is blank and one whose consent is no.","assertion":"Both are raised as no_consent and no draft exists for either. Inferring consent from the fact that a number was written down is the failure."},{"id":"one-attendee-across-opens","n":3,"title":"The same person at two opens is one attendee","statement":"Matched on the mobile or the email, not the name. They get one follow-up for the listing, and the duplicate is flagged so the agent sees they came back.","assertable":"stub","ports":["read_contacts","escalate","draft_message"],"stimulus":"One person signed in at Saturday's open and again at Wednesday's twilight open, their mobile written differently each time.","assertion":"Exactly one draft for that person for the listing, and one duplicate_attendee exception naming both opens. Two drafts is the failure."},{"id":"one-question-per-draft","n":4,"title":"Each draft thanks them and asks one question","statement":"Interest, price feedback or the contract, one of them. The draft names the agency, carries a way to opt out, and is written, not sent.","assertable":"stub","ports":["draft_message"],"stimulus":"A clean register of attendees with consent.","assertion":"Every draft has exactly one question mark, names the sender, and carries the opt-out line. A draft that asks three questions, or none, fails."},{"id":"person-approves-each-send","n":5,"title":"A person approves every follow-up before it sends","statement":"The send is a separate step from the draft, and it reads the agent's yes against that draft. No approval, no send.","assertable":"stub","ports":["gate","send_message"],"stimulus":"Five drafts, of which the agent approves three.","assertion":"Three sends, each for an approved draft, and no attempted send for the other two. The trace is read, so a send the world refused still counts as the failure."},{"id":"opt-out-is-permanent","n":6,"title":"An opt-out is honoured across every open and listing","statement":"A reply of STOP, or any request to stop, is recorded as withdrawn consent and blocks every later message to that person, whichever open they sign in at next.","assertable":"stub","ports":["record_consent","gate","send_message"],"stimulus":"An attendee replies STOP, then signs in at another listing's open the following week with consent ticked.","assertion":"The withdrawal is recorded, and nothing is sent to them again. A fresh tick on a new register does not clear it: only the person themselves, asking, does."},{"id":"reply-classified","n":7,"title":"Every reply is classified, and an unclear one goes to a person","statement":"Hot buyer, price feedback, not interested, or also has a property to sell (an appraisal lead). A reply can carry more than one. One that fits none is unclear and goes to the agent.","assertable":"stub","ports":["read_inbox","file_message","escalate"],"stimulus":"One reply of each kind, one that is both price feedback and an appraisal lead, and one that fits none.","assertion":"Each is filed with its classes, the two-class reply carries both, and the unclear one is escalated rather than forced into a class."},{"id":"offer-or-complaint-to-agent","n":8,"title":"An offer or a complaint goes to the agent, unanswered","statement":"An offer is the agent's to pass to the vendor, and a complaint is the agent's to handle. The build hands both over with a typed reason and sends nothing back.","assertable":"both","ports":["escalate","send_message"],"stimulus":"One reply that names a figure they would pay, and one that complains about how the open was run.","assertion":"Both escalate as offer and complaint, and no message goes to either person afterwards. The offer does not appear in the vendor report as price feedback.","stubLimit":"The stub proves the canned offer and complaint are routed. Only production proves it holds for the ways real people phrase an offer, which are many."},{"id":"report-counts-and-verbatim","n":9,"title":"The vendor report counts, quotes and invents nothing","statement":"Counts that add up, price comments word for word, themes that each point at the replies they came from, and no names or numbers of attendees. No sentiment score and no percentage that the counts do not give.","assertable":"stub","ports":["read_inbox","publish_report"],"stimulus":"A week with replies of every class, two of them price comments.","assertion":"The counts match the trace, each price comment is the reply text exactly, every theme cites a reply, no attendee's name, mobile or email appears, and the report has no field the standard does not name."},{"id":"person-approves-the-report","n":10,"title":"The agent approves the vendor report before it goes","statement":"The report is drafted to the agent first. It goes to the vendor only once the agent has approved that version.","assertable":"stub","ports":["gate","publish_report"],"stimulus":"A drafted report the agent has not approved, then the same report approved.","assertion":"Nothing reaches the vendor before the approval, and what reaches the vendor is the version that was approved."},{"id":"who-said-what-recorded","n":11,"title":"Who said what is on the record","statement":"Consent as captured, every send with who approved it, every reply word for word, every class and every opt-out, kept so that a vendor dispute weeks later can be answered from the record and not from memory.","assertable":"both","ports":["evidence"],"stimulus":"A full week: register, approvals, sends, replies, an opt-out and the report.","assertion":"Every send, reply and opt-out has its own record entry, 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 that nobody edits, proves the record survives the dispute."},{"id":"voice-step-off-by-default","n":12,"title":"The call is off by default, and only to someone who asked","statement":"If the agent turns the optional call on, it goes only to an attendee who ticked that they would take one, through a gate that decides at dial time, and opens by saying it is an AI calling for the agency.","assertable":"both","ports":["gate","disclose","talk"],"stimulus":"A week run with the call left off, then the same week with it turned on and one attendee who asked for a call and one who did not.","assertion":"No call while it is off. Once on, only the attendee who asked is called, each after the gate allowed it, with the disclosure as the first turn.","stubLimit":"Calling hours and who asked are provable on the stub because the runner owns the clock and the register. The Do Not Call Register and a real line with a recording are not."},{"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 register 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":"A vendor disputes a week's report, and the record answers it: who signed in with what consent, what was sent with whose approval, and what each person said, word for word. An attendee who opted out was never messaged again.","assertable":"production","ports":["evidence","record_consent","send_message","publish_report"],"stimulus":"A real week on the live channel: an attendee replies STOP and later signs in at another open, and a price comment lands in the report. Weeks later, the vendor queries it.","assertion":"The record reproduces the consent, the approvals, the sends and the exact replies behind every line of the report, and shows no message to the person who opted out after they did.","stubLimit":"Deliberately unpassable on a stub. On a stub the opt-out arrives because the runner sent it; on a live channel it comes back through the carrier or the mail provider, days later, and the record has to outlive the build that wrote it."}]}