{"spec":"AIP-BS-DATABASE-REACTIVATION","version":"2026-10-24","standard":"database-reactivation-build-standard","source":"https://aipathway.com.au/explore-ai/database-reactivation-build-standard","markdown":"https://aipathway.com.au/explore-ai/database-reactivation-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 call, 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":4,"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 fact about a property that is not in the data","dial without a basis, a register check and the do-not-call flags","invent a ranking signal","contact a suppressed or opted-out person"],"stays_with_person":["what may be promised on a call","the opener that names a relationship that does not exist"]},"job":"old database earns again","skill":"database-reactivation","stub":"local.mock_contacts","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 call to a contact who asked to be rung back is Office Voice's governed single call: create_call, basis callback or they_asked. Everyone else on the list is the agent's own call from the daily list; Office Voice refuses prospecting calls by design."}},"with_us_next":"The call to a contact who asked to be rung back is Office Voice's governed single call: create_call, basis callback or they_asked. Everyone else on the list is the agent's own call from the daily list; Office Voice refuses prospecting calls by design. 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":["state a fact about a property that is not in the data","dial without a basis, a register check and the do-not-call flags","invent a ranking signal","contact a suppressed or opted-out person"],"stays_with_person":["what may be promised on a call","the opener that names a relationship that does not exist"]},"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","raise_task","write_log"]},{"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_sms"]},{"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":["talk","disclose"]}],"registry":"https://aipathway.com.au/substrates.json"},"checks":[{"id":"five-fields-present","n":1,"title":"The fields each play needs are present","statement":"Name, mobile and address are needed to work a contact at all. Every other gap is reported and limits only the play that needs it.","assertable":"stub","ports":["read_contacts"],"stimulus":"A list seeded with one contact missing each of the five fields in turn.","assertion":"A contact missing name, mobile or address is reported as not ready and excluded from every play. A contact missing purchase date is excluded from the tenure play only, and one missing the investor flag from the investor play only. One missing last-contact outcome is ranked as cold state. Every gap is reported."},{"id":"four-plays-real-triggers","n":2,"title":"Four plays, each with a real trigger","statement":"Not one campaign against the whole list. A contact qualifies for a play because of something true about them.","assertable":"stub","ports":["read_contacts","rank"],"stimulus":"A list containing one contact who qualifies for each play, and one who qualifies for none.","assertion":"Each lands in its own play and the last lands in none. A build that puts everybody in one sequence fails here."},{"id":"ranking-six-rules-in-order","n":3,"title":"Ranking is by the six rules, in order","statement":"The order is the claim; a build that scores the same inputs differently is a different standard.","assertable":"stub","ports":["rank"],"stimulus":"A seeded set whose correct order under the six rules is known and not the obvious one.","assertion":"The produced order matches exactly. A different order is a different standard, not a tuning choice."},{"id":"one-play-at-a-time","n":4,"title":"A contact belongs to one play at a time","statement":"No contact is in two runs at once, and nobody receives two openers in a week from two plays.","assertable":"stub","ports":["rank","gate"],"stimulus":"A contact who legitimately qualifies for two plays, with both runs executed in the same week.","assertion":"They appear in exactly one run and receive exactly one opener. The runner holds state across runs to see this at all."},{"id":"opener-names-the-reason","n":5,"title":"The opener names why you are calling","statement":"A call that cannot say what prompted it reads as a cold call, because it is one.","assertable":"both","ports":["talk","read_contacts"],"stimulus":"A contact qualified by a specific trigger, then the call opened.","assertion":"The opener references that trigger. A generic opener with the trigger only in the metadata fails.","stubLimit":"The stub proves the trigger reached the opener and was used. Only a live call proves it lands as a reason rather than as a recited field."},{"id":"cadence-has-a-stop","n":6,"title":"Cadence has a stop, and the stop is honoured","statement":"The sequence ends whether or not the contact replied, and a reply ends it.","assertable":"stub","ports":["gate","send_sms","talk"],"stimulus":"One sequence run to its end with no reply, and one where the contact replies at step two.","assertion":"The first stops at its defined end. The second stops on the reply and sends nothing further."},{"id":"gate-under-ranking","n":7,"title":"The gate sits underneath the ranking","statement":"Warm only, consent, do not contact, the contact's own timezone, and calling hours. No score promotes a contact past any of it.","assertable":"both","ports":["gate","disclose"],"stimulus":"The top-ranked contact, run outside their local calling hours; then the same contact flagged do-not-contact.","assertion":"Neither is called, each with a reason, and the highest score does not override either.","stubLimit":"Hours, consent and timezone are fully provable on the stub because the runner owns the clock and the list. A do-not-call register wash is not: a stub cannot hold a registered-user agreement or produce a receipt."},{"id":"dnc-permanent-and-global","n":8,"title":"Do-not-contact is permanent and global","statement":"It survives a re-import, applies across every play, and is never cleared by a new list.","assertable":"stub","ports":["gate","read_contacts"],"stimulus":"A contact flagged do-not-contact, then the whole list re-imported with that contact present and unflagged.","assertion":"The flag survives the import and still blocks every play. An import that clears it is the failure this check exists for."},{"id":"every-skip-explained","n":9,"title":"Every skip is explained","statement":"The audit says why a contact was not called, not merely that it was not. A silent skip is indistinguishable from a bug.","assertable":"stub","ports":["gate","evidence"],"stimulus":"A run where several contacts are skipped for different reasons.","assertion":"Each skip carries its own reason. A count of skips without reasons fails."},{"id":"relationship-needs-a-person","n":10,"title":"A human decides anything that is a relationship","statement":"An offer, a price, a complaint, or a contact who asks a question the play was not built for.","assertable":"both","ports":["escalate","talk"],"stimulus":"A contact who asks for a price, and one who makes a complaint.","assertion":"Both escalate with a typed reason rather than being answered. No price and no resolution is offered by the build.","stubLimit":"The stub proves the canned provocations escalate. Only production proves it holds for the ones real people improvise."},{"id":"daily-list-capped-with-reasons","n":11,"title":"The day's list respects the cap and carries a reason per row","statement":"Each morning the ranked run becomes a short list for a person: no more rows than the cap the agent set, each with its play, the reason, a drafted opener and the last outcome.","assertable":"stub","ports":["rank","gate","raise_task"],"stimulus":"A cap of two against a list where four contacts qualify and one is flagged do-not-contact, then the morning list made.","assertion":"The list holds at most two rows, in rank order, none of them gated out. Every row names its play, the trigger that qualified it, an opener that uses that trigger, and the last outcome. A list longer than the cap, or a row with no reason, fails."},{"id":"outcomes-change-tomorrow","n":12,"title":"Yesterday's outcomes change today's list","statement":"What the agent records against a row is written back to the CRM or sheet the same day, and the next morning's list is made from what was written.","assertable":"both","ports":["write_log","read_contacts","raise_task"],"stimulus":"Day one's list is worked: the first contact is spoken to, the second asks not to be contacted again, and the third asks to be called back. The next morning's list is made.","assertion":"Each outcome is written back on the day it was recorded. Day two's list leaves out the contact spoken to yesterday and the one who opted out, and puts the callback first. A list that comes back the same as yesterday's fails.","stubLimit":"The stub proves the next list reads what was written back. Only a live run proves the write reached the CRM or sheet the agent actually works from, the same day, and that a re-export did not overwrite it."},{"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 list and the plays 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 do-not-contact flag on the top-scoring contact stops the call, and the audit says which gate stopped it.","assertable":"production","ports":["gate","talk","disclose"],"stimulus":"A real run against the live list, at a real time, with a flagged contact ranked first.","assertion":"No call is placed to that contact, and the audit names the gate rather than reporting a generic skip.","stubLimit":"Deliberately unpassable on a stub. A stub register agrees with you; the real one is somebody else's data and can disagree between the read and the dial."}]}