Assure checks itself: v0.1.2
53 of 61 promises decided 51 hold 2 disproven 8 not established
Engine e698723a · 22 Oct 2026, 05:20 UTC
- holds
- True of this release.
- not established
- Not shown either way yet.
- disproven
- This release breaks it.
- From the code
- Checked against Assure's own source.
- From a run record premised
- An outcome we measure each release, such as how setup went.
Holds 51
51 promises, each read from the code.
What the live API answers
-
R1holdsEvery answer tells browsers to speak to this API over HTTPS only.
Read from the code and the live API's answers We sent requests to api.symbolia.ai and to our own server code, and every answer read (/healthz, /, /v1/profiles) tells browsers to use HTTPS only.
Evidence
serve/api.py, deploy/Caddyfile (assure/checkers/http-observed-baseline/PROFILE.json#obligations/HTTP-STS-PRESENT) @ e698723aChecked by
http:http-observed-baseline (assure-self): HTTP-STS-PRESENT holds on every path read (/healthz 200, / 404, /v1/profiles 401) with HEAD https://api.symbolia.ai (served), the tree's own server (serve/api.py) behind deploy/Caddyfile -
R2holdsNo answer states both its length and that it is sent in chunks.
Read from the code and the live API's answers We sent requests to api.symbolia.ai and to our own server code, and every answer read (/healthz, /, /v1/profiles) states its length or says it is chunked, never both.
Evidence
serve/api.py, deploy/Caddyfile (assure/checkers/http-observed-baseline/PROFILE.json#obligations/HTTP-FRAMING-CL-TE) @ e698723aChecked by
http:http-observed-baseline (assure-self): HTTP-FRAMING-CL-TE holds on every path read (/healthz 200, / 404, /v1/profiles 401) with HEAD https://api.symbolia.ai (served), the tree's own server (serve/api.py) behind deploy/Caddyfile -
R3holdsAnswers that describe the service are never stored by anything between the server and the customer.
Read from the code and the live API's answers We sent requests to api.symbolia.ai and to our own server code, and every answer read (/healthz, /, /v1/profiles) tells anything in between not to store it.
Evidence
serve/api.py, deploy/Caddyfile (assure/checkers/http-observed-baseline/PROFILE.json#obligations/HTTP-CACHE-NO-STORE) @ e698723aChecked by
http:http-observed-baseline (assure-self): HTTP-CACHE-NO-STORE holds on every path read (/healthz 200, / 404, /v1/profiles 401) with HEAD https://api.symbolia.ai (served), the tree's own server (serve/api.py) behind deploy/Caddyfile -
R4holdsBrowsers are told to take an answer's declared type at its word and never guess it from the bytes.
Read from the code and the live API's answers We sent requests to api.symbolia.ai and to our own server code, and every answer read (/healthz, /, /v1/profiles) tells browsers not to guess its type.
Evidence
serve/api.py, deploy/Caddyfile (assure/checkers/http-observed-baseline/PROFILE.json#obligations/HTTP-NOSNIFF-PRESENT) @ e698723aChecked by
http:http-observed-baseline (assure-self): HTTP-NOSNIFF-PRESENT holds on every path read (/healthz 200, / 404, /v1/profiles 401) with HEAD https://api.symbolia.ai (served), the tree's own server (serve/api.py) behind deploy/Caddyfile -
R5holdsThe API never sets a cookie.
Read from the code and the live API's answers We sent requests to api.symbolia.ai and to our own server code, and every answer read (/healthz, /, /v1/profiles) sets no cookie.
Evidence
serve/api.py, deploy/Caddyfile (assure/checkers/http-observed-baseline/PROFILE.json#obligations/HTTP-NO-SET-COOKIE) @ e698723aChecked by
http:http-observed-baseline (assure-self): HTTP-NO-SET-COOKIE holds on every path read (/healthz 200, / 404, /v1/profiles 401) with HEAD https://api.symbolia.ai (served), the tree's own server (serve/api.py) behind deploy/Caddyfile -
R6holdsA key issued to one account cannot read another account's verdicts.
Read from the code We read every route and handler of our API server (serve/) and found that every stored item is looked up under the account of the key that asks for it.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:50 @ e698723aChecked by
identity:python/stdlib-http (assure-self): as B10: a key reads only its own account's verdicts -
R7holdsAn API key is never written to any log line.
Read from the code We read every route and handler of our API server (serve/) and found that no log line writes a key; the request log records the key's id, never the key.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:43 @ e698723aChecked by
identity:python/stdlib-http (assure-self): no logging site names a key; the request log record carries the key id, never its value
Getting to value
-
A2holdsEvery run tells the customer something useful, including a refused one, and names one next step.
Read from the code We read every message our API sends (serve/render.py), and each of the 31 ways a run can end, refusals included, names a next step.
Evidence
serve/render.py (next_step, failure_markdown, pr_refusal_markdown, summary_markdown, pr_comment_markdown) @ e698723aChecked by
promise_readers.surfaces: a refused run: failure_markdown and pr_refusal_markdown each reach next_step, whose fallback is the envelope's action, and each of the 31 outcomes has a non-empty action; a verdict run: summary_markdown reaches not_established_lines and pr_comment_markdown reaches _pr_closing
Who can reach what
-
B1holdsWithout a key, someone reaches only what is meant to be public, and each public route is reached only by the means it is meant for: a signature, a signed link, or nothing sensitive.
Read from the code We read every route and handler of our API server (serve/) and found that without a key only /healthz and the routes meant to be public answer, each by its own means: a signature, a signed link, or nothing sensitive.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:5 @ e698723aChecked by
identity:python/stdlib-http (assure-self): every route outside /healthz is protected from the anonymous principal (IDENT-1); /healthz answers before the key guard from the server's own status (R38); the tree page opens only for a link whose signature matches under the configured signing value, before any record is read, until its expiry (R27); the GitHub sign-in mints a key only after the proof's signature, issuer, audience and expiry verify (R28); the billing webhook checks its signature before the body (premise) -
B2holdsEvery other request needs a valid API key.
Read from the code We read every route and handler of our API server (serve/) and found that every other route needs a valid API key.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:5 @ e698723aChecked by
identity:python/stdlib-http (assure-self): every route outside the declared public routes is protected by the API key guard (IDENT-1), each key-guarded method-route pair reads protected, and the key is compared by digest in constant time -
B3holdsOnce a key is revoked, the very next request made with it is turned away.
Read from the code We read every route and handler of our API server (serve/) and found that each request reads the key's current state, so a revoked key is turned away on its next use.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:36 @ e698723aChecked by
identity:python/stdlib-http (assure-self): Keys.revoke marks the key row revoked and authenticate_key reads the rows on each request and refuses a revoked key -
B4holdsEvery key has an expiry set when it is issued; responses say when it expires and warn before it does; an expired key is turned away.
Read from the code We read every route and handler of our API server (serve/) and found that every key is issued with an expiry, responses carry it and warn before it, and an expired key or one without an expiry is turned away.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:11 @ e698723aChecked by
identity:python/stdlib-http (assure-self): the issued expiry and its check (IDENT-7: a key row with no `expires` is refused too, #4202) and the expiry header with its warning from KEY_WARNING_DAYS before (R41) -
B5holdsRotating a key brings the new key in and retires the old one after a stated window, and never leaves an account without a working key.
Read from the code We read every route and handler of our API server (serve/) and found that rotation issues the new key first and retires the old one only after the stated window.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:47 @ e698723aChecked by
identity:python/stdlib-http (assure-self): Keys.rotate refuses before the window opens, mints a successor expiring at the clock plus a bounded duration, and writes neither `expires` nor `revoked` on the presented row: the old key works until its own expiry -
B6holdsA paid action runs only when the plan, balance or agreed overage covers it.
Read from the code We read every route and handler of our API server (serve/) and found that every paid route checks the plan, balance or agreed overage before doing the work.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:48 @ e698723aChecked by
identity:python/stdlib-http (assure-self): every PAID_ROUTES handler reaches a meter admission or hold with the principal before the work it pays for; the meter refuses a once-only balance past what is left and a post-paid part past the budget -
B7holdsA proof-tree link opens only if the server signed it, only until it expires, and never after the verdict has been deleted.
Read from the code We read every route and handler of our API server (serve/) and found that a proof-tree link opens only when we signed it, it has not expired and the verdict is still kept.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:31 @ e698723aChecked by
identity:python/stdlib-http (assure-self): the tree page opens only for a signed, unexpired link (R27), refuses when no account holds the check or the verdict's retention has ended, and never serves a link longer than the retention (R45) -
B8holdsA billing event is applied only when its signature verifies and the event is recent.
Read from the code We read every route and handler of our API server (serve/) and found that a billing event is applied only when its signature checks out and it is recent.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:46 @ e698723aChecked by
identity:python/stdlib-http (assure-self): the webhook applies an event only when its signature verifies under the configured signing value and its timestamp lies within TOLERANCE_S of the clock -
B9holdsEvery signing or verification key comes from configuration.
Read from the code We read every route and handler of our API server (serve/) and found that every signing and verification key is read from configuration, never written in the code.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:10 @ e698723aChecked by
identity:python/stdlib-http (assure-self): every declared signing or verification key (api_key, stripe_signature, tree_link, github_oidc) resolves to a configuration surface (IDENT-8); a literal default on the environment read reads literal -
B10holdsEvery stored item belongs to one account; no other account can read it or tell that it exists.
Read from the code We read every route and handler of our API server (serve/) and found that every stored item is opened only under its own account, and another account gets the same answer as if it did not exist.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:50 @ e698723aChecked by
identity:python/stdlib-http (assure-self): every store call in the handler class passes the principal; every account-keyed Store method opens that account's directory; Store._get refuses the same not_found for a malformed id and an absent or foreign record
What a verdict is
-
C1holdsA requirement reads holds, not established or disproven.
Read from the code We read the verdict code (assure/checkers/identity/grade.py), and it has exactly three readings: holds, not established and disproven.
Evidence
assure/checkers/identity/grade.py:51 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): verdict-three-readings: STATES is exactly holds, not_established, disproven (assure/checkers/identity/grade.py:51); MAP_001's 10 result types map onto them (assure/checkers/identity/grade.py:53); map_result returns only those states; _row takes its state from map_result (assure/checkers/identity/grade.py:318) -
C2holdsNothing reads holds when there was nothing to check; it reads not established.
Read from the code We read the verdict code (assure/checkers/identity/grade.py), and every case with nothing to check reads not established.
Evidence
assure/checkers/identity/grade.py:53 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): verdict-nothing-holds-on-nothing: 11 holds rows each name their domain; 8 empty-domain branches read vacuous; vacuous maps to not_established (assure/checkers/identity/grade.py:53); a requirement line no row phrases never holds (assure/checkers/identity/grade.py:520, assure/checkers/identity/grade.py:524) -
C3holdsNothing reads disproven unless the code really breaks the rule; a route whose purpose is not known reads not established.
Read from the code We read the verdict code (assure/checkers/identity/grade.py), and only a found break reads disproven; anything not understood reads not established.
Evidence
assure/checkers/identity/grade.py:53 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): verdict-disproven-only-on-a-break: only fails maps to disproven in MAP_001 (assure/checkers/identity/grade.py:53); a line not understood or unmatched is never disproven (assure/checkers/identity/grade.py:520, assure/checkers/identity/grade.py:524); _span_pass demotes a disproven row inside a refused span to representation (assure/checkers/identity/grade.py:1235) -
C4holdsThe engine checks that its own code matches its pin before it runs.
Read from the code We read the server start-up (serve/api.py, serve/worker.py, serve/check.py), and it checks the engine's pin before loading the engine.
Evidence
serve/api.py:3415; serve/worker.py:420; serve/check.py:753 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): pin-before-engine-api: make_server verifies the pin (serve/api.py:3415) before the engine is imported (serve/api.py:3416); the module imports no engine byte at top level; pin-before-engine-worker: child_main verifies the pin (serve/worker.py:420) before the engine is imported (serve/worker.py:428); the module imports no engine byte at top level; pin-before-engine-check: main verifies the pin (serve/check.py:753) before the engine is imported (serve/check.py:763); the module imports no engine byte at top level -
C5holdsEvery verdict names the engine version it ran on.
Read from the code We read how verdicts are built (serve/envelope.py, serve/check.py), and every verdict carries the engine version it ran on.
Evidence
serve/envelope.py:1545; serve/check.py:226; serve/worker.py:160; serve/worker.py:432; serve/api.py:1081; serve/api.py:1058 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): verdict-engine-block: build writes 'engine' from its required engine argument (serve/envelope.py:1545); verdict-engine-passed-check: run_check passes engine=engine to envelope.build (serve/check.py:226); verdict-engine-request-written: write_request writes 'engine' from its required engine argument (serve/worker.py:160); verdict-engine-from-request: child_main passes engine=doc.get('engine') to run_check (serve/worker.py:432); verdict-engine-from-pin: _run_job binds engine = self.pin_verify() (serve/api.py:1081) and hands it to self._run (serve/api.py:1092); verdict-engine-passed-api: _run passes engine=engine to worker.run_in_child (serve/api.py:1058) -
C6holdsThe limits the API publishes are the limits it enforces.
Read from the code We read the API (serve/api.py, serve/bundle.py), and the limits it publishes are the same values it enforces.
Evidence
serve/api.py:609; serve/api.py:1852; serve/bundle.py:194; serve/bundle.py:606; serve/api.py:542; serve/meter.py:2217; serve/meter.py:2262 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): limits-published-from-the-object: public_profiles publishes 'limits' as body_bytes = limits.body_bytes, file_bytes = limits.file_bytes, files = limits.files, json_depth = limits.json_depth (serve/api.py:609); body-cap-read-from-the-published-object: _read_body reads self.server.limits.body_bytes and refuses oversize_input (serve/api.py:1852); bundle-body-and-depth-caps-from-the-object: parse_json reads limits.body_bytes, limits.json_depth and refuses oversize_input (serve/bundle.py:194); bundle-file-caps-from-the-object: check_files reads limits.files, limits.file_bytes and refuses oversize_input (serve/bundle.py:606); rate-window-published-is-the-one-read: served_routes publishes 'rate_window' as RATE_WINDOWS[...] (serve/api.py:542) and _admit reads RATE_WINDOWS[...] (serve/api.py:1764); rate-limits-published-from-the-limiter: Meter.usage publishes 'limits' as requests_per_minute = self.limiter.rpm, records_per_day = self.limiter.rpd (serve/meter.py:2217); rate-admitted-through-the-limiter: Meter.admit_request reads self.limiter.admit (serve/meter.py:2262) -
C7holdsAn account cannot create a volume of requests beyond reliable limits.
Read from the code We read the API (serve/api.py), and every request that creates work passes a rate limit first.
Evidence
serve/api.py:398 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): every-post-template-admitted: every POST template outside the declared exceptions admits on its RATE_WINDOWS window through its handler (13 templates; _admit maps minute+day to admit_request, minute to admit_rate) (serve/api.py:398) -
C8holdsSending the same check twice with the same idempotency key returns the first check and is not charged again.
Read from the code We read the check route (serve/api.py, serve/store.py), and a repeated idempotency key returns the first check before any charge.
Evidence
serve/api.py:2172; serve/store.py:840 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): replay-before-charge: _post_check looks the key up under srv.idem_lock (serve/api.py:2172), replays a known check before any admission (serve/api.py:2192), and admits, records and submits only when the key is unknown (serve/api.py:2233; the record serve/api.py:2235 precedes the submit serve/api.py:2244); idempotency-record-written-once: put_idempotency links the record once (serve/store.py:840) and returns False when it already exists (serve/store.py:842) -
C10holdsWhen a checker can model only the first few of something, everything past that point reads not established with the bound named.
Read from the code We read the checker's limits (assure/checkers/identity/explore.py), and past each limit the result reads not established and names the limit.
Evidence
assure/checkers/identity/explore.py:301; serve/profiles.py:1484 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): identity-bound-refused-and-named: BOUNDS caps states, routes, principals (assure/checkers/identity/explore.py:59); each is raised as Overbound (states at assure/checkers/identity/explore.py:273, routes at assure/checkers/identity/explore.py:257, principals at assure/checkers/identity/explore.py:255); explore refuses representation/bound naming the dimension and the cap (assure/checkers/identity/explore.py:301); MAP_001 maps representation to not_established (assure/checkers/identity/grade.py:53); postgresql-rows-past-the-first-table: bounded_machines refuses M5 when _distinct(tables) > 1 (serve/profiles.py:1483) with a reason naming the bound (one protected table per declaration) and the count (serve/profiles.py:1484); _derive_evaluate reads each machine bounded_machines names as representation with that reason, never evaluated (serve/profiles.py:2067)
How a job runs
-
D1holdsA job completes at most once.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that a job completes at most once.
Evidence
hosted/MACHINE.json#invariants/completes_at_most_once @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): completes_at_most_once holds on 736 steps; explored 13 abstract states of the reference queue -
D2holdsA worker whose lease has lapsed writes nothing.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that a worker whose lease has lapsed writes nothing.
Evidence
hosted/MACHINE.json#invariants/stale_lease_never_writes @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): stale_lease_never_writes holds on 93 steps; explored 13 abstract states of the reference queue -
D3holdsTwo workers never claim the same job.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that two workers never hold the same job.
Evidence
hosted/MACHINE.json#invariants/claim_exclusive @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): claim_exclusive holds on 515 steps; explored 13 abstract states of the reference queue -
D4holdsA job is attempted at most its allowed number of times.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that a job is never attempted more than its allowed number of times.
Evidence
hosted/MACHINE.json#invariants/attempts_bounded @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): attempts_bounded holds on 736 steps; explored 13 abstract states of the reference queue -
D5holdsA job is retried only when the failure was not its own; a refusal is final.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that a refused job is never retried.
Evidence
hosted/MACHINE.json#invariants/refusal_never_retried @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): refusal_never_retried holds on 354 steps; explored 13 abstract states of the reference queue -
D6holdsEvery way a job can end records a typed outcome.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that every ending records a typed outcome.
Evidence
hosted/MACHINE.json#invariants/exit_typed_outcome @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): exit_typed_outcome holds on 535 steps; explored 13 abstract states of the reference queue -
D7holdsA job moves only along the allowed path.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that every move is one of the allowed moves.
Evidence
hosted/MACHINE.json#transitions @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): every observed move (8) is a declared transition; explored 13 abstract states of the reference queue -
D8holdsIf a worker is interrupted mid-job, nothing is written.
Read from the code We explored every state our job queue can reach (hosted/MACHINE.json) and found that an interrupted job writes nothing.
Evidence
hosted/MACHINE.json#invariants/interrupt_writes_nothing @ e698723aChecked by
machine:job_lifecycle (hosted/MACHINE.json): interrupt_writes_nothing holds on 10 steps; explored 13 abstract states of the reference queue
What happens to a customer's data
-
E1holdsOnly what a check needs leaves the customer's runner, never raw rows, names or values.
Read from the code We read what the runner sends (action/collect.py, serve/bundle.py), and the raw files are held back on the runner; only what the check needs leaves it.
Evidence
serve/bundle.py (RUNNER_ONLY, from_directory), action/collect.py (_gather) @ e698723aChecked by
promise_readers.data_code (ast over the tree): only the withheld raw/ files leave the runner: REDACTION-MANIFEST.json is a runner-only record (RUNNER_ONLY, read by from_directory) and _gather returns withhold.apply(from_directory(...)) -
E2holdsThe facts collected for a check are deleted when the check ends; on a paid plan one sealed copy is kept, encrypted for that account alone.
Read from the code We read the worker (serve/worker.py, serve/bundle_keep.py), and it deletes the check's files on every exit, keeping one sealed copy only on a paid plan.
Evidence
serve/worker.py (run_in_child), serve/bundle_keep.py (keep, includes_premium, PREMIUM_TIERS) @ e698723aChecked by
promise_readers.data_code (ast over the tree): run_in_child deletes the request directory in a finally on every exit path; bundle_keep.keep seals one copy only when includes_premium(tier), the tiers that include Premium (PREMIUM_TIERS: internal, usage, usd_20, usd_200), never the free tier -
E3holdsWhat we keep is minimal and kept briefly, then deleted with everything that belongs to it.
Read from the code We read our data-keeping rules (hosted/data_rule.py) and found that each kind of kept data has a short limit or ends with the tier or account it belongs to.
Evidence
hosted/data_rule.py @ e698723aChecked by
machine:data_rule (hosted.data_rule.read_promises): every day-bounded class is kept at most 30 days, a tier-scoped class goes when the account leaves the tier and a class kept for the life of the account is deleted on request; declared, not derived: classes.signin_record.retention, classes.sealed_bundle.deletion_events, classes.collected_facts -
E5holdsUsing de-identified configuration data to improve the product is on by default for the free tier and off by default for paid tiers, and one setting changes it.
Read from the code We read our data-keeping rules (hosted/data_rule.py) and found that product use of de-identified data is on by default for free, off for paid, and one setting changes it.
Evidence
hosted/data_rule.py @ e698723aChecked by
machine:data_rule (hosted.data_rule.read_promises): product_data on by default for free, off for paid tiers, one setting (product_data); declared, not derived: product_data_settings -
E6holdsAn enterprise account keeps none of its code or collected facts after the check ends.
Read from the code We read our data-keeping rules (hosted/data_rule.py) and found that an enterprise account keeps none of its code or collected facts after a check.
Evidence
hosted/data_rule.py @ e698723aChecked by
machine:data_rule (hosted.data_rule.read_promises): enterprise keeps none of its code or collected facts after the check ends; its private model only encrypted under its own key; verdicts kept under decision 8; declared, not derived: classes.collected_facts, classes.signin_record -
E8holdsFeedback and telemetry are kept only as long as they are useful, and never a requirement's text.
Read from the code We read our data-keeping rules (hosted/data_rule.py) and found that feedback and telemetry are kept 12 months and neither keeps a requirement's text.
Evidence
hosted/data_rule.py @ e698723aChecked by
machine:data_rule (hosted.data_rule.read_promises): feedback kept 12 months, telemetry 12 months; neither carries a requirement text field; declared, not derived: telemetry.months -
E9holdsA customer's private model is deleted when they ask, and soon after the account closes.
Read from the code We read our data-keeping rules (hosted/data_rule.py) and found that a private model is deleted on request and 30 days after the account closes.
Evidence
hosted/data_rule.py @ e698723aChecked by
machine:data_rule (hosted.data_rule.read_promises): the private model is deleted on request and 30 days after close
Money
-
F1holdsAn account's plan and charges change only by something it agreed to or was given, and never twice for the same event.
Read from the code We read our billing code (serve/billing_machine.py) and found that each billing event is applied once, a repeat changes nothing, and the plan moves only by an event or a gift.
Evidence
serve/billing_machine.py @ e698723aChecked by
machine:billing (serve/billing_machine.py readings, read_f4): every applied event is one events.jsonl row, a repeated id changes nothing, the plan moves only by an applied event or a gift, every settled request is one charge row, a request settled twice changes no charge, and a charge without consent to overage never passes its hold, over 155 sequences of at most 3 steps (118 distinct: a replay before any event sends nothing); 35 gifts moved the plan -
F2holdsWhen a customer leaves a paid plan, what was kept for it is deleted.
Read from the code We read our billing code (serve/billing_machine.py) and found that leaving a paid plan deletes what was kept for it.
Evidence
serve/billing_machine.py @ e698723aChecked by
machine:billing (serve/billing_machine.py): customer.subscription.deleted leaves no kept bundle and no sign-in record of the account, and a lapsed gift's recorded move to free leaves no sign-in record -
F4holdsOverage is charged at the published rate, and only within the budget the customer agreed.
Read from the code We read our billing code (serve/billing_machine.py) and found that overage is charged at the published rate and refused past the agreed budget.
Evidence
serve/billing_machine.py @ e698723aChecked by
machine:billing (serve/billing_machine.py readings, read_f4): the charge row carries the basis at the published rate (hosted/tiers.json margin), and the hold whose post-paid part would pass the agreed budget is refused allowance_exhausted with nothing written -
F5holdsEvery ledger is append-only, and every total is recomputed from its entries.
Read from the code We read our billing code (serve/billing_machine.py) and found that every ledger only ever grows, and every total is worked out again from its entries.
Evidence
serve/billing_machine.py @ e698723aChecked by
machine:billing (serve/billing_machine.py readings, read_f4): every ledger (the four JSONL ledgers and ledger.sqlite3) only grew by appending after each event, settlement and usage report, overage_cents and each statement total equal the sums recomputed from the ledger rows, and each paid plan has one statement, over 155 sequences of at most 3 steps (118 distinct: a replay before any event sends nothing) -
F6holdsA customer can always see their plan and what each check cost them.
Read from the code We read the usage route and cost records (serve/api.py, serve/meter.py), and an account's plan and every check's cost come from its own ledger.
Evidence
serve/api.py _usage; serve/meter.py check_costs; persisted customer ledgers @ e698723aChecked by
machine:customer plan and per-check costs: complete-body AST establishes account-bound all-period ledger selection and serialization; authenticated current plan and every historical check cost match the account ledgers; settled allowance/top-up/charge components, uncharged terminal failure, zero-cost work, plan changes and restart checked; known pending amounts, retry reconciliation, receipt SQL failure, stored-envelope fallback and accepted-marker recovery checked without monetary replay -
F7holdsA statement shows the same figures a real bill would.
Read from the code We produced statements (serve/billing.py) for a gifted account and a paid one, and each showed the figures a real bill would.
Evidence
serve/billing.py @ e698723aChecked by
run:statement (serve/billing.py Billing.statement): a 12-month gift's statement reads base USD 0, total USD 0; a paid usd_20 account's reads its USD 20.0 base
What a customer reads
-
G2holdsNo file a customer reads or commits carries an internal flag or marker.
Read from the code We read the 45 files a customer reads or commits (docs, action, skills), and none carries an internal flag or marker.
Evidence
action/build_dist.py (RECORD_RE, FLAG_LINE) @ e698723aChecked by
promise_readers.surfaces: no internal record number or flag line on any surface read; 45 files read: docs/customer prose and openapi files, action/action.yml, action/skills, serve/profiles texts, and 5627 output-template literals of serve/render.py, action/entry.py, action/declaration_draft.py, action/withhold.py; docs/customer/samples is not read (served verdict copies) -
G3holdsEvery refusal names what was refused and the field or input that fixes it.
Read from the code We read all 557 places our API and Action refuse (serve/, action/), and each names what was refused and why.
Evidence
serve/outcomes.py (_TABLE, Refusal.__init__) @ e698723aChecked by
promise_readers.surfaces: 557 Refusal( sites in serve/ and action/ read by ast, each with an outcome and a non-empty reason; 31 outcomes of serve/outcomes.py _TABLE each with a non-empty action, and Refusal.__init__ refuses any other outcome -
G4holdsEvery served profile answers in readable words on its first run, in its verdict, its summary and its pull-request comment.
Read from the code We ran every profile we serve on its first run (serve/profiles), and each answered in readable words in its verdict, summary and pull-request comment.
Evidence
serve/PROPERTIES.json; serve/profiles/*.json; tests/checkers/fixtures/http/flaskr-intent-001; tests/serving/fixtures/mysql-declared/positive; tests/serving/fixtures/run-37121027064; tests/serving/fixtures/run-37121027064; tests/checkers/fixtures/python-auth/flask-session-a @ e698723aChecked by
promise_readers.first_run: every served profile (http-observed-baseline, mysql-declared-model, postgresql-declared-model, postgresql-observed-baseline, python-auth-declared-model) answers in words on its first run: every verdict row has its words, every comment rule is named by a sentence, and the summary lines are sentences
Not established 7
7 promises read from the code that Assure could not decide. Each says what is missing.
-
A3not establishedThe pre-flight predicts the check; it never says the code is ready when the check cannot read it.
Read from the code We replayed unreadable code and route tables for two app types (serve/lint.py, serve/input_readers.py), and the pre-flight never said ready; those replays do not cover every kind of app we serve.
Evidence
serve/lint.py; serve/input_readers.py; serve/profiles.py; tests/checkers/identity/fixtures/adapter-2-fastapi/positive @ e698723aChecked by
promise_readers.preflight: the FastAPI and stdlib unreadable-source and route-table replays found no false readiness; complete equivalence of every readability guard across every served profile is not established by these finite replays -
B11not establishedKeys and signing values are never written to logs, records or responses.
Read from the code We read how keys are stored (serve/keys.py): issuing a key stores only a fingerprint of it, but we could not yet follow the key through rotation, so this is not yet shown.
Evidence
assure/checkers/identity/adapters/python_stdlib_http/fixtures/assure-self/requirements.md:43 @ e698723aChecked by
identity:python/stdlib-http (assure-self): not established through premise:key-digest-stored: serve.keys.Keys.issue: holds: serve.keys.Keys.issue stores only _digest(…) = sha256 of the key (line 579); the key itself is returned once and never stored; serve.keys.Keys.issue_oidc: holds: serve.keys.Keys.issue_oidc stores only _digest(…) = sha256 of the key (line 639); the key itself is returned once and never stored; serve.keys.Keys.rotate: not_established: serve.keys.Keys.rotate digests a compound argument at line 706 whose returned-key provenance this read does not establish -
B12not establishedPOST /v1/checks and POST /v1/declaration-proposal refuse a request whose declared size exceeds the account's plan limit (Free 6 MiB, Premium 24 MiB, Pro 64 MiB) before any request content is read, and the refusal names the plan that fits.
Read from the code Nothing in our checks reads this promise yet.
Evidence
- @ e698723aNo checker yet. no reader decides it yet
-
C9not establishedThe API and the check process never contact anything outside the server.
Read from the code We read the worker (serve/worker.py), but part of the code it loads is outside what this read covers, so it is not yet shown that nothing outside is contacted.
Evidence
serve/worker.py:56 @ e698723aChecked by
properties:python/stdlib-http (assure/checkers/identity/adapters/python_stdlib_http/properties.py): api-outbound-clients-declared reads not_established: serve/worker.py:56 imports local serve, whose code is outside the transitive read set; an unobserved helper cannot establish no outside contact -
E7not establishedBackups keep only what is needed to restore accounts and billing, briefly, and never a customer's verdicts or collected facts.
Read from the code We looked for a backup job and found none; the backup rule is written down (hosted/data_rule.py) but nothing runs it yet.
Evidence
hosted/data_rule.py (RESTORE_ONLY, declared) @ e698723aChecked by
promise_readers.data_code (ast over the tree): no backup job in the tree; RESTORE_ONLY in hosted/data_rule.py is declared and read by no job, so a holds on it would be vacuous -
F3not establishedNo one is charged beyond what they agreed: overage starts at zero, only a person's yes turns it on, the customer is told as it nears the budget, and it is never raised for them.
Read from the code The overage budget this promise needs is not in this release yet, so there is nothing to read.
Evidence
serve/billing_machine.py @ e698723aChecked by
machine:billing (serve/billing_machine.py readings, read_f4): contingent on the account overage budget (refs #3100, #3895), not on main; the machine gives no F3 row -
G1not establishedA drafted goal never claims more than its checks prove, and says what it covers.
Read from the code The goal drafter this promise is about is not in this release yet, so there is nothing to read.
Evidence
- @ e698723aChecked by
promise_readers.first_run: contingent on the goal drafter (refs #2376), not on main: no drafted goal to read
Read from a run record 3
These 3 promises are about measured outcomes rather than code. We state them as Assure's own customer, and each release reads them from the latest record of a measured run.
A1 and A4 are disproven, read from the record of the 22 Oct setup run. They measure how setup went on sample apps, not the code: no promise read from the code is disproven. Each row below says what was found and what it means for you.
-
A1disprovenpremisedAnyone can set Assure up from one line, and is asked only what only they can answer, each question with its tradeoff.
Read from a run record We read the record of our v0.1.2 setup measurement on ten sample apps, which keeps the questions each setup agent asked (carried from our 17 October run).
What we found Most of the setup questions were about how the product works, not something only the customer knows, and none came with its tradeoff.
For you Setup may ask you things it should work out itself, and you may have to answer without being told what each choice costs. Tracked as #3089.
What we state Each setup agent asked the person only what only they could answer, and gave the tradeoff of each question.
Record
docs/validation/setup-qual/runs/20261022T033014Z-v0.1.2/results.jsonl, 2026-10-22 (the setup measurement at 03:30Z; the questions are the ones the setup agents asked in the run of 2026-10-17)This reading comes from a record of a run, not from reading our code, so it is only as good as that run: it tells you how setup went on ten sample apps, not how it will go on yours.
Evidence
docs/validation/setup-qual/runs/20261022T033014Z-v0.1.2/results.jsonl @ e698723aChecked by
promise_readers.setup_qual: 14 of 19 questions outside the customer-only list (microblog: product; microblog: product; flasky: product; flasky: unclassified; flask-base: unclassified; realworld: product); no tradeoff recorded for each question of microblog, flasky, flask-base, realworld, flask-user-starter, cookiecutter-demo, jwt-restful, rest-tutorial, flaskr, flask-bones -
A4disprovenpremisedA fresh agent working from the one line gets a correct result on at least 6 of 10 qualification apps, and each other app is reachable through Assure's shipped extension paths.
Read from a run record We read the record of our v0.1.2 setup measurement on ten sample apps, each with a known flaw added, to see whether the check found it.
What we found Only 5 of 10 sample apps got a real check on the first run. All 5 found the added flaw, and none reported a broken rule that was not broken. The other 5 stopped, each naming the line it could not read.
For you On an app like yours, setup may not reach a working check the first time, and you may need to step in. Tracked as #3679.
What we state A fresh agent following the one line got a check that found the added flaw, without false alarms, on nearly every sample app.
Record
docs/validation/setup-qual/runs/20261022T033014Z-v0.1.2/results.jsonl, 2026-10-22 (setup measurement at 03:30Z against the v0.1.2 release head on staging)This reading comes from a record of a run, not from reading our code, so it is only as good as that run: it tells you how setup went on ten sample apps, not how it will go on yours.
Evidence
docs/validation/setup-qual/runs/20261022T033014Z-v0.1.2/results.jsonl @ e698723aChecked by
promise_readers.setup_qual: 5 of 10 correct first runs (a real check naming the flaw), 0 false broken; 0 of 5 others reached a real reading through an extension path; no extension record: flask-base (no extension record), flask-bones (no extension record), flask-user-starter (no extension record), flasky (no extension record), realworld (no extension record) -
G5not establishedpremisedEvery feedback note a customer files is read back over the API by Symbolia's own loop and answered or routed; none sits unread.
Read from a run record We looked for the record of our own loop reading customer feedback back over the API, and there is none yet (docs/validation/feedback-digest/).
What we state A record of our own loop reading the feedback notes customers filed back over the API, with each one answered or routed.
Record none yet: no such record exists under docs/validation/feedback-digest/
This promise can only be shown by a record of us reading and answering your feedback notes, and no such record exists yet, so we cannot yet show that a note you file will be read and answered.
Evidence
docs/validation/feedback-digest/ @ e698723aChecked by
promise_readers.setup_qual: no feedback digest record under docs/validation/feedback-digest/