overviewthe security programme, measured

What is actually verified, and what is still open

You asked whether we are doing critical things. The four numbers below are the answer, and none of them is a count of merges. Every row on this page carries the command that produced it, the tree it ran against and the date, because a verdict without those three is a claim rather than a measurement.

passed
0of 46 this pass
failed
0the work queue
not evidence
0exited zero, proves nothing
cannot measure here
46names what this tree lacks
verified passing
42 of 11270 not verified
who gets hurt
34 of 11278 have no scenario

What actually passes

Fifty tickets had never had their own exit commands run by anybody. On 2026-09-28 one pass ran all of them and sorted every one into one of four buckets. It fixed nothing. 0 of the 46 passed, which means the tree was in better shape than the backlog said and the backlog was the thing that was wrong.

From docs/security-programme/state.json -> partition, derived on every run by scripts/gen_security_state.py from the three verdicts plus the two declarations. Nothing on this page is parsed out of prose.

0of 46 passed

And 0 failed, which is the work queue below - named, with the command and what it printed. 0 exited zero on a criterion that proves nothing, and 46 name something this tree does not contain. Across all 112 tickets in the programme, 42 are verified passing and 70 are not.

bucketthis passall 112what it means
passed042every exit command ran and exited zero
failed020ran, and at least one exited non-zero
not evidence02ran and exited zero, on a criterion that could pass with the defect present
cannot be measured here4648names a path, repository or service this tree does not have, or has no command
total46112every ticket in exactly one bucket

The number that is a trap. counts.unrun reads 46, because it counts tickets nothing was ever run against. Four more carry a full run record and are still not verified, which is what the two amber buckets are. The figure that is not a trap is 70 unverified: every ticket whose verdict is not passes.

The work queue

20 tickets have a criterion that is red today, 0 of them found by the pass above and 20 already red before it. Ranked by security impact and then priority, each with the command and what it printed. 16 are measured defects. The other 4 are criteria that cannot pass in any tree - a moved path, a quoting bug, a collision with an unrelated required setting - and they read as we cannot tell, because that is what they are.

From docs/security-programme/state.json -> each ticket's verification.runs. The result field is what the run recorded. Raw stdout for all of it is in docs/security-programme/SWEEP.md, and this page does not re-parse that page.

AUTH-021criticalP1we cannot tell

Four filters in the grant queries can be deleted without any gate going red

This red is the criterion, not the property. Two of the five criteria grep tests/authz/conftest.py, which does not exist - the needle lives in components/authz/tests/conftest.py - so the red is a mispointed path, and the other three criteria pass. So the honest reading is we cannot tell, and the property is unmeasured rather than absent.

Four lines decide that a permission belongs to you rather than to somebody else, and each can be deleted with every test and every gate still green, so one careless edit would let any signed-in customer read, grant and revoke every other customer's permissions with nothing reporting it.

$ PYTHONPATH=. .venv-verify/bin/python -m pytest tests/authz -q
$ grep -q user_id tests/authz/conftest.py
$ grep -q filters_of tests/authz/conftest.py
$ grep -q test_the_access_review_covers_one_scope_only tests/authz/test_grants_postgres.py
$ grep -q test_revoke_takes_the_role_from_one_holder_only tests/authz/test_grants_postgres.py
Ran every exit command; at least one exited non-zero (#0 exit 0; #1 exit 2; #2 exit 2; #3 exit 0; #4 exit 0). Criteria #1 and #2 grep tests/authz/conftest.py, which does not exist. The file the needle lives in is components/authz/tests/conftest.py. #0, #3 and #4 pass, so this is a mispointed path rather than an absent property.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 6 exit command(s)
  1. run: PYTHONPATH=. .venv-verify/bin/python -m pytest tests/authz -q
  2. run: grep -q user_id components/authz/tests/conftest.py
  3. run: grep -q filters_of components/authz/tests/conftest.py
  4. run: grep -q test_the_access_review_covers_one_scope_only tests/authz/test_grants_postgres.py
  5. run: grep -q test_revoke_takes_the_role_from_one_holder_only tests/authz/test_grants_postgres.py
  6. run: PYTHONPATH=. .venv-verify/bin/python -m pytest components/authz/tests -q

CORE-043highP0measured red

A person cannot delete their account, and the audit trail holds personal data so deleting them is not a delete

A person asks for their account to be deleted and it is deleted, except that three declared stores say nothing can reach them - a crash report holding whatever a library put in an exception message, the email address on an invitation nobody accepted, and a job payload that never said who it was about - so the delete is honest about what it missed and the data stays until somebody builds the reach.

$ PYTHONPATH=. .venv-verify/bin/python -m pytest tests/core/test_erasure_extent_is_declared.py -q
$ PYTHONPATH=. .venv-verify/bin/python -m pytest tests/config/test_audit.py tests/config/test_personal_data_registry.py tests/web/test_legal_pages.py -q
$ PYTHONPATH=. .venv-verify/bin/python -c "import json; d=json.load(open('openapi.json')); r=json.dumps(d['paths']['/auth/me']['delete']['responses']['200']); assert 'AccountDeletionResponse' in r, r; p=d['components']['schemas']['AccountDeletionResponse']['properties']; assert p['status']['enum']==['erased','scheduled'], p['status']; assert 'unreachable' in p and 'not_ours_to_delete' in p, sorted(p)"
$ docker rm -f core043-dod-pg >/dev/null 2>&1; out=$(mktemp); docker run -d --name core043-dod-pg -e POSTGRES_PASSWORD=probe -e POSTGRES_USER=probe -e POSTGRES_DB=probe -p 55913:5432 postgres:16-alpine >/dev/null && sleep 8 && TEST_DATABASE_URL=postgresql+asyncpg://probe:probe@127.0.0.1:55913/probe PYTHONPATH=. .venv-verify/bin/python -m pytest tests/auth/test_account_erasure_postgres.py tests/core/test_erasure_postgres.py -q -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ skipped" "$out"; then echo "skipped is not passed"; s=1; fi; docker rm -f core043-dod-pg >/dev/null; rm -f "$out"; exit $s
$ PYTHONPATH=. .venv-verify/bin/python -c "from components.core.personal_data_registry import DISCLOSURES; holes=sorted(f'{d.component}/{d.data}' for d in DISCLOSURES if d.disposition=='unreachable'); assert not holes, holes"
Ran every exit command; at least one exited non-zero (#0 exit 0; #1 exit 0; #2 exit 0; #3 exit 0; #4 exit 1). Four of five criteria pass, including the Postgres pair in its own container. #4 fails: three declarations carry disposition 'unreachable' - core's exception-message residue, invite's unaccepted-invitation email address, and jobs' subject-less payload. This is RECOVERY.md item 12, the owner's own residue item, and it is now measured rather than asserted.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 5 exit command(s)
  1. run: PYTHONPATH=. .venv-verify/bin/python -m pytest tests/core/test_erasure_extent_is_declared.py -q
  2. run: PYTHONPATH=. .venv-verify/bin/python -m pytest tests/config/test_audit.py tests/config/test_personal_data_registry.py tests/web/test_legal_pages.py -q
  3. run: PYTHONPATH=. .venv-verify/bin/python -c "import json; d=json.load(open('openapi.json')); r=json.dumps(d['paths']['/auth/me']['delete']['responses']['200']); assert 'AccountDeletionResponse' in r, r; p=d['components']['schemas']['AccountDeletionResponse']['properties']; assert p['status']['enum']==['erased','scheduled'], p['status']; assert 'unreachable' in p and 'not_ours_to_delete' in p, sorted(p)"
  4. run: docker rm -f core043-dod-pg >/dev/null 2>&1; out=$(mktemp); docker run -d --name core043-dod-pg -e POSTGRES_PASSWORD=probe -e POSTGRES_USER=probe -e POSTGRES_DB=probe -p 55913:5432 postgres:16-alpine >/dev/null && sleep 8 && TEST_DATABASE_URL=postgresql+asyncpg://probe:probe@127.0.0.1:55913/probe PYTHONPATH=. .venv-verify/bin/python -m pytest tests/auth/test_account_erasure_postgres.py tests/core/test_erasure_postgres.py -q -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ skipped" "$out"; then echo "skipped is not passed"; s=1; fi; docker rm -f core043-dod-pg >/dev/null; rm -f "$out"; exit $s
  5. run: PYTHONPATH=. .venv-verify/bin/python -c "from components.core.personal_data_registry import DISCLOSURES; holes=sorted(f'{d.component}/{d.data}' for d in DISCLOSURES if d.disposition=='unreachable'); assert not holes, holes"

CORE-080highP0measured red

a declaration is the security mechanism so the gate enumerates the artefacts and fails on an undeclared one

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ python3.12 scripts/check_declaration_coverage.py --self-test
$ python3.12 scripts/check_declaration_coverage.py
Ran every exit command; at least one exited non-zero (#0 exit 2; #1 exit 2). scripts/check_declaration_coverage.py does not exist in this tree, so both criteria exit 2 on 'can not open file'. The gate the ticket promises has not been written.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 2 exit command(s)
  1. run: .venv-verify/bin/python scripts/check_declaration_coverage.py --self-test
  2. run: .venv-verify/bin/python scripts/check_declaration_coverage.py

CORE-081highP0measured red

every unsafe default refuses at boot and the unsafe defaults are a register rather than a memory

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ python3.12 scripts/check_unsafe_defaults.py --self-test
$ python3.12 scripts/check_unsafe_defaults.py
Ran every exit command; at least one exited non-zero (#0 exit 2; #1 exit 2). scripts/check_unsafe_defaults.py does not exist in this tree, so both criteria exit 2. The register and the boot refusal have not been written.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 2 exit command(s)
  1. run: python3.12 scripts/check_unsafe_defaults.py --self-test
  2. run: python3.12 scripts/check_unsafe_defaults.py

OPS-131highP0measured red

GitHub Actions has started no job since 2026-09-14, so every CI red is the same red

Every CI run on the main branch has shown a red X for three days because the account cannot pay, which looks exactly like a failing test - so a real test failure would be invisible, and nothing has actually been checked since 2026-09-14.

$ make ci-running
exit 2. All 10 of the last 10 runs executed zero steps, oldest in the window from today. The gate says the remedy is an account setting and nothing in the repository will clear it.

Ran 2026-09-17 against compound/security-programme

its own 1 exit command(s)
  1. run: gh run list --branch main --limit 1 --json conclusion,jobs

OPS-183highP0measured red

settle OPS-169's central claim by standing up postgres and running the suite

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ make test-postgres
$ make mutation
$ make examples-gate
Ran every exit command; at least one exited non-zero (#0 exit 2; #1 exit 0; #2 exit 2). #0 `make test-postgres` exits 2 refusing, because the criterion names no database. #1 `make mutation` exits 0. #2 `make examples-gate` exits 2 and still exits 2 when I supply my own container: 47 passed, 1 failed, 15 of 17 declared assertions confirmed, paid-notes red on 4 tests with `assert 201 == 402`. Supplying a database to #0 gives 136 passed, so examples-gate is the blocker rather than Postgres.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 3 exit command(s)
  1. run: make test-postgres
  2. run: make mutation
  3. run: make examples-gate

AUTH-010highP1measured red

There is no second factor anywhere, and the password policy is eight characters and nothing else

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ grep -rqiE 'totp|webauthn' components/auth/
Ran every exit command; at least one exited non-zero (#0 exit 1). `grep -rqiE 'totp|webauthn' components/auth/` exits 1. Spelled a second way per CLAUDE.md: the same case-insensitive grep over all of components/ also finds nothing. There is no second factor anywhere in the component tree.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 3 exit command(s)
  1. run: python3.12 -c 'import base64, importlib.util; from datetime import UTC, datetime; s = importlib.util.spec_from_file_location("totp", "components/auth/services/totp.py"); m = importlib.util.module_from_spec(s); s.loader.exec_module(m); seed = base64.b32encode(b"12345678901234567890").decode(); got = [m.code_at(seed, datetime.fromtimestamp(t, UTC), digits=8) for t in (59, 1111111109, 1234567890, 2000000000)]; assert got == ["94287082", "07081804", "89005924", "69279037"], got; now = datetime(2026, 9, 28, tzinfo=UTC); assert m.verify(seed, m.code_at(seed, now), now) == m.step_at(now); assert m.verify(seed, "000000", now) is None or m.code_at(seed, now) == "000000"'
  2. run: out=$(mktemp); PYTHONPATH=. .venv-verify/bin/python -m pytest components/auth/tests/test_totp.py components/auth/tests/test_login_asks_for_a_second_factor.py -q -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ (skipped|deselected)" "$out"; then echo "skipped is not passed"; s=1; fi; rm -f "$out"; exit $s
  3. run: python3.12 -c 'import json; p = json.load(open("openapi.json"))["paths"]; need = ["/auth/mfa", "/auth/mfa/enrol", "/auth/mfa/enrol/confirm", "/auth/mfa/verify", "/auth/mfa/disable"]; missing = [x for x in need if x not in p]; assert not missing, missing; login = json.dumps(p["/auth/login"]["post"]["responses"]["200"]); assert "MfaChallengeResponse" in login, "login cannot answer a challenge"'

COMP-069highP1measured red

A capability URL that carries its token in the query string is still copied into next

A child's wishlist link is copied whole into the sign-in page's return address, so the credential is handed to the browser's history and to every proxy between the child and the shop.

$ PYTHONPATH=. .venv-verify/bin/python -m pytest tests/web/test_shell.py -q -k a_capability_url_in_a_query_string
Ran every exit command; at least one exited non-zero (#0 exit 4). The criterion names tests/web/test_shell.py, which moved to components/webauth/tests/test_shell.py, AND selects -k a_capability_url_in_a_query_string, which exists in no file in this tree. So this is not only a moved path: the case it names was never written or was renamed with no trace.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 2 exit command(s)
  1. run: PYTHONPATH=. .venv-verify/bin/python -m pytest components/webauth/tests/test_shell.py -q -k a_capability_url_in_a_query_string
  2. run: PYTHONPATH=. .venv-verify/bin/python -m pytest components/core/tests/test_safe_path.py -q -k the_surface_declared_is_the_surface_covered

CORE-045highP1we cannot tell

Nothing erases a person, and the erasure that adr-006 decided has no implementation

This red is the criterion, not the property. Two of the five criteria never reach their own assertion: both fail constructing Settings because BILLING_MODE has no default, an unrelated required setting added after the criteria were written. Supplied with BILLING_MODE, the property they assert holds. So the honest reading is we cannot tell, and the property is unmeasured rather than absent.

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ docker rm -f core045-dod-pg >/dev/null 2>&1; out=$(mktemp); docker run -d --name core045-dod-pg -e POSTGRES_PASSWORD=probe -e POSTGRES_USER=probe -e POSTGRES_DB=probe -p 55915:5432 postgres:16-alpine >/dev/null && sleep 8 && TEST_DATABASE_URL=postgresql+asyncpg://probe:probe@127.0.0.1:55915/probe PYTHONPATH=. .venv-verify/bin/python -m pytest tests/core/test_erasure_postgres.py::test_erasing_a_person_leaves_the_trail_able_to_say_what_happened -q -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ (skipped|deselected)" "$out"; then echo "skipped is not passed"; s=1; fi; docker rm -f core045-dod-pg >/dev/null; rm -f "$out"; exit $s
$ grep -rqE 'grace_days' components/core/config/concerns.py
$ ERASURE_GRACE_DAYS=7 PYTHONPATH=. .venv-verify/bin/python -c "from components.core.config import Settings; assert Settings(_env_file=None, database_url='postgresql://x:y@h/d').erasure.grace_days == 7"
$ PYTHONPATH=. .venv-verify/bin/python -c "from components.core.config import Settings; assert Settings(_env_file=None, database_url='postgresql://x:y@h/d').erasure.grace_days == 0"
$ docker rm -f core045-c4-pg >/dev/null 2>&1; out=$(mktemp); docker run -d --name core045-c4-pg -e POSTGRES_PASSWORD=probe -e POSTGRES_USER=probe -e POSTGRES_DB=probe -p 55916:5432 postgres:16-alpine >/dev/null && sleep 8 && TEST_DATABASE_URL=postgresql+asyncpg://probe:probe@127.0.0.1:55916/probe PYTHONPATH=. .venv-verify/bin/python -m pytest tests/auth/test_account_erasure_postgres.py::test_the_task_the_worker_discovers_is_what_closes_the_window -q -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ (skipped|deselected)" "$out"; then echo "skipped is not passed"; s=1; fi; docker rm -f core045-c4-pg >/dev/null; rm -f "$out"; exit $s
Ran every exit command; at least one exited non-zero (#0 exit 0; #1 exit 0; #2 exit 1; #3 exit 1; #4 exit 0). Criteria #2 and #3 do not reach their own assertion. Both fail constructing Settings because BILLING_MODE has no default, an unrelated required setting added after these criteria were written. Diagnostically, with BILLING_MODE=free_beta supplied, ERASURE_GRACE_DAYS=7 yields grace_days 7 and the default yields 0 - so the property holds and the criterion is what is red. I changed nothing.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 5 exit command(s)
  1. run: docker rm -f core045-dod-pg >/dev/null 2>&1; out=$(mktemp); docker run -d --name core045-dod-pg -e POSTGRES_PASSWORD=probe -e POSTGRES_USER=probe -e POSTGRES_DB=probe -p 55915:5432 postgres:16-alpine >/dev/null && sleep 8 && TEST_DATABASE_URL=postgresql+asyncpg://probe:probe@127.0.0.1:55915/probe PYTHONPATH=. .venv-verify/bin/python -m pytest tests/core/test_erasure_postgres.py::test_erasing_a_person_leaves_the_trail_able_to_say_what_happened -q -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ (skipped|deselected)" "$out"; then echo "skipped is not passed"; s=1; fi; docker rm -f core045-dod-pg >/dev/null; rm -f "$out"; exit $s
  2. run: grep -rqE 'grace_days' components/core/config/concerns.py
  3. run: BILLING_MODE=free_beta ERASURE_GRACE_DAYS=7 PYTHONPATH=. .venv-verify/bin/python -c "from components.core.config import Settings; assert Settings(_env_file=None, database_url='postgresql://x:y@h/d').erasure.grace_days == 7"
  4. run: BILLING_MODE=free_beta PYTHONPATH=. .venv-verify/bin/python -c "from components.core.config import Settings; assert Settings(_env_file=None, database_url='postgresql://x:y@h/d').erasure.grace_days == 0"
  5. run: docker rm -f core045-c4-pg >/dev/null 2>&1; out=$(mktemp); docker run -d --name core045-c4-pg -e POSTGRES_PASSWORD=probe -e POSTGRES_USER=probe -e POSTGRES_DB=probe -p 55916:5432 postgres:16-alpine >/dev/null && sleep 8 && TEST_DATABASE_URL=postgresql+asyncpg://probe:probe@127.0.0.1:55916/probe PYTHONPATH=. .venv-verify/bin/python -m pytest tests/auth/test_account_erasure_postgres.py::test_the_task_the_worker_discovers_is_what_closes_the_window -q -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ (skipped|deselected)" "$out"; then echo "skipped is not passed"; s=1; fi; docker rm -f core045-c4-pg >/dev/null; rm -f "$out"; exit $s

CORE-082highP1we cannot tell

CORS is one policy for the whole application, so a public path and a private path cannot allow different origins

This red is the criterion, not the property. Its one criterion names tests/config/test_cors.py, which does not exist - tests/config/test_cors_refuses_the_unsafe_pair.py does - so pytest exits 4 before asserting anything at all. So the honest reading is we cannot tell, and the property is unmeasured rather than absent.

A founder who needs their public widget callable from any website has no way to say that only their own site may reach the pages that carry a login session, so they must either widen the whole application or break the widget.

$ PYTHONPATH=. .venv-verify/bin/python -m pytest tests/config/test_cors.py -q
Ran every exit command; at least one exited non-zero (#0 exit 4). The criterion names tests/config/test_cors.py, which does not exist. tests/config/test_cors_refuses_the_unsafe_pair.py does. Mispointed path; pytest exit 4.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 4 exit command(s)
  1. run: PYTHONPATH=. .venv-verify/bin/python -m pytest tests/config/test_cors_scopes_per_path.py tests/config/test_cors_refuses_the_unsafe_pair.py -q
  2. run: sh -c '! PYTHONPATH=. .venv-verify/bin/python -m pytest tests/config/test_cors_scopes_per_path.py -q 2>&1 | grep -qi skipped'
  3. run: test "$(PYTHONPATH=. .venv-verify/bin/python -m pytest tests/config/test_cors_scopes_per_path.py --collect-only -q 2>/dev/null | grep -c '::test_')" -ge 30
  4. run: PYTHONPATH=. .venv-verify/bin/python -c "from fastapi import FastAPI; from fastapi.testclient import TestClient; from components.core.config import CorsConfig, CorsScope; from components.core.middleware import setup_cors; app = FastAPI(); app.add_api_route('/api/v1/signup', lambda: {'ok': 1}, methods=['GET']); app.add_api_route('/api/v1/waitlists', lambda: {'ok': 1}, methods=['GET']); setup_cors(app, CorsConfig(origins='https://app.example', allow_credentials=True), scopes=(CorsScope('/api/v1/signup', CorsConfig(origins='*')),)); c = TestClient(app); h = lambda p: c.options(p, headers={'origin': 'https://stranger.example', 'access-control-request-method': 'GET'}).headers.get('access-control-allow-origin'); g = lambda p: c.get(p, headers={'origin': 'https://stranger.example'}).headers.get('access-control-allow-origin'); assert h('/api/v1/signup') == g('/api/v1/signup') == '*'; assert h('/api/v1/waitlists') is g('/api/v1/waitlists') is None"

OPS-037highP1measured red

Level 2 carries production secrets in a dotenv and a shell, and the documented JWT rotation names a variable no code reads

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ grep -rq 'AUTH_JWT_SECRET_PREVIOUS\|jwt_secret_previous' components/auth/
$ grep -qiE 'hetzner|level 2' docs/SECRETS.md
Ran every exit command; at least one exited non-zero (#0 exit 1; #1 exit 1). Both criteria fail. `jwt_secret_previous` appears nowhere under components/ - only in the ticket's own backlog file - so the rotation docs/SECRETS.md documents names a variable the code does not have. docs/SECRETS.md also has no hetzner or level 2 section.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 5 exit command(s)
  1. run: grep -rq 'AUTH_JWT_SECRET_PREVIOUS\|jwt_secret_previous' components/auth/
  2. run: grep -qiE 'hetzner|level 2' docs/SECRETS.md
  3. run: PYTHONPATH=. .venv-verify/bin/python -m pytest components/auth/tests/test_jwt_rotation.py -q
  4. run: PYTHONPATH=. .venv-verify/bin/python -m pytest tests/auth/test_secrets_doc_names_what_the_code_reads.py -q
  5. run: PYTHONPATH=. .venv-verify/bin/python -m pytest tests/auth/test_every_jwt_call_goes_through_the_shared_keys.py -q

OPS-057highP1measured red

The only two row-ownership tests in the repository are collected by nothing, because examples/ holds a delta and not a project

The only two tests in this repository that check one customer cannot read another customer's rows could not be collected by anything, so nobody has ever confirmed that data is separated between accounts.

$ make examples-gate
exit 2, 3 of 6 example products failing. The ticket's claim is withdrawn - both tests run now and one passes - but its acceptance is unmet. The cause is COMP-104.

Ran 2026-09-17 against atomic/ops-186/re-verify at origin/main, with a Postgres container

its own 7 exit command(s)
  1. run: test -f cli/examples.py && test -f cli/gates/example_products.py
  2. run: test -f examples/scratchpad/example.toml && test -f examples/waitlist-collector/example.toml && test -f examples/phone-book/example.toml
  3. run: grep -q '^examples-gate:' Makefile && grep -q 'cli.gates.example_products' Makefile
  4. run: grep -q 'make examples-gate' .github/workflows/ci.yml
  5. run: grep -q 'test_two_accounts_cannot_see_each_other' examples/scratchpad/example.toml
  6. run: grep -q 'test_signups_are_scoped_to_the_owner' examples/waitlist-collector/example.toml
  7. run: grep -q 'def test_every_committed_example_declares_itself' tests/test_examples_gate.py

OPS-170highP1measured red

every credential this project holds has a row naming its scope its holder and when it dies

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ python3.12 scripts/check_credential_register.py --self-test
$ python3.12 scripts/check_credential_register.py
Ran every exit command; at least one exited non-zero (#0 exit 2; #1 exit 2). scripts/check_credential_register.py does not exist in this tree, so both criteria exit 2. The credential register has not been written.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 2 exit command(s)
  1. run: python3.12 scripts/check_credential_register.py --self-test
  2. run: python3.12 scripts/check_credential_register.py

OPS-171highP1measured red

security impact is self reported and nothing corroborates it so a mis-tagged critical defect is invisible

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ python3.12 scripts/propose_security_tags.py --self-test
$ python3.12 scripts/propose_security_tags.py --shortlist
Ran every exit command; at least one exited non-zero (#0 exit 2; #1 exit 2). scripts/propose_security_tags.py does not exist in this tree, so both criteria exit 2. The corroboration check has not been written.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 2 exit command(s)
  1. run: python3.12 scripts/propose_security_tags.py --self-test
  2. run: python3.12 scripts/propose_security_tags.py --shortlist

AUTH-024highP2measured red

Re-authenticating a Google account does not make Google ask for anything, so a borrowed laptop still deletes it

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ grep -rqE 'prompt' components/auth/adapters/identity/google.py
$ .venv-verify/bin/python -m pytest components/auth/tests/test_google_signin.py -q
Ran every exit command; at least one exited non-zero (#0 exit 1; #1 exit 0). The grep for `prompt` in components/auth/adapters/identity/google.py exits 1 - the file is 14KB and holds no occurrence. Its sibling criterion, the Google sign-in test suite, passes. So the suite is green while the re-consent property is absent.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 3 exit command(s)
  1. run: PYTHONPATH=. .venv-verify/bin/python -c "import inspect, sys; from components.auth.adapters.identity.protocol import IdentityProvider; sys.exit(0 if 'reauthenticate' in inspect.signature(IdentityProvider.authorization_url).parameters else 1)"
  2. run: PYTHONPATH=. .venv-verify/bin/python -c "import sys; from urllib.parse import parse_qs, urlparse; from components.auth.adapters.identity.google import GoogleIdentityProvider; from components.auth.config import AuthSettings; p = GoogleIdentityProvider(AuthSettings(jwt_secret='x'*64, google_client_id='probe.apps.googleusercontent.com', _env_file=None)); q = lambda r: parse_qs(urlparse(p.authorization_url(state='s', nonce='n', code_challenge='c', reauthenticate=r)).query); sys.exit(0 if q(True).get('prompt') == ['login'] and 'prompt' not in q(False) else 1)"
  3. run: PYTHONPATH=. .venv-verify/bin/python -c "import sys, pytest; seen = []; sys.exit(pytest.main(['-q', sys.argv[1]], plugins=[type('P', (), {'pytest_runtest_logreport': lambda self, report: seen.append(report.outcome)})()]) or (0 if seen.count('passed') and 'skipped' not in seen else 1))" components/auth/tests/test_google_signin.py

CORE-060highP2measured red

A declared credential path that matches no route is inert and looks like protection

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ PYTHONPATH=. .venv-verify/bin/python -m pytest components/core/tests/test_safe_path.py -q -k every_declared_prefix_matches_a_route
Ran every exit command; at least one exited non-zero (#0 exit 5). pytest exit 5, '21 deselected': the file exists and the -k name `every_declared_prefix_matches_a_route` matches nothing. The nearest test, test_a_prefix_that_cannot_match_is_refused_when_it_is_declared, asserts a weaker property - that a prefix cannot ever match a path shape - and its own docstring says it does not judge whether a declared route exists. The ticket's property is unmeasured.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 1 exit command(s)
  1. run: PYTHONPATH=. .venv-verify/bin/python -m pytest components/core/tests/test_safe_path.py -q -k every_declared_prefix_matches_a_route

OPS-036highP2measured red

No dependency or secret scan reaches a generated product, and the template's own pip-audit lives in an opt-in lens

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ grep -q 'pip-audit' skeleton/.github/workflows/ci.yml
$ test -f skeleton/.github/dependabot.yml
$ make secrets
$ test -f skeleton/.gitleaks.toml
$ grep -q 'make secrets' skeleton/.github/workflows/ci.yml
$ grep -q gitleaks .githooks/pre-commit && cmp -s .githooks/pre-commit skeleton/.githooks/pre-commit
Ran every exit command; at least one exited non-zero (#0 exit 0; #1 exit 1; #2 exit 0; #3 exit 0; #4 exit 0; #5 exit 0). Five of six criteria pass. #1 fails: skeleton/.github/dependabot.yml does not exist, so the kit ships no dependency-update config.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 6 exit command(s)
  1. run: grep -q 'pip-audit' skeleton/.github/workflows/ci.yml
  2. run: test -f skeleton/.github/dependabot.yml
  3. run: make secrets
  4. run: test -f skeleton/.gitleaks.toml
  5. run: grep -q 'make secrets' skeleton/.github/workflows/ci.yml
  6. run: grep -q gitleaks .githooks/pre-commit && cmp -s .githooks/pre-commit skeleton/.githooks/pre-commit

OPS-058highP2measured red

A restored backup brings an erased person back, and nothing re-applies the erasure

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ grep -qi 'erasure' scripts/deploy/prove_migration.sh
Ran every exit command; at least one exited non-zero (#0 exit 1). `grep -qi 'erasure' scripts/deploy/prove_migration.sh` exits 1. The file exists and is 30KB. scripts/deploy/prove_worker.sh does reference auth.erasure.due, so the word exists in the deploy tree - just not in the migration proof, which is the one the ticket is about.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 1 exit command(s)
  1. run: docker rm -f ops058-dod-pg >/dev/null 2>&1; out=$(mktemp); docker run -d --name ops058-dod-pg -e POSTGRES_PASSWORD=probe -e POSTGRES_USER=probe -e POSTGRES_DB=probe -p 55521:5432 postgres:16-alpine >/dev/null && sleep 8 && TEST_DATABASE_URL=postgresql+asyncpg://probe:probe@127.0.0.1:55521/probe PYTHONPATH=. .venv-verify/bin/python -m pytest tests/core/test_erasure_replay_postgres.py -q -m "" -p no:randomly -rs --no-header >"$out" 2>&1; s=$?; cat "$out"; if grep -qE "[0-9]+ (skipped|deselected)" "$out"; then echo "skipped is not passed"; s=1; fi; docker rm -f ops058-dod-pg >/dev/null; rm -f "$out"; exit $s

OPS-130highP2measured red

Six guards die only in the suite make verify never runs

The 135 tests that check who is allowed to see which rows pass when a database is present and vanish without one, and the command this project trusts runs without a database - so the checks that matter most are the ones nobody runs.

$ .venv-verify/bin/python -m pytest tests/test_verify_names_what_it_did_not_run.py -q
$ python3.12 -c 'import json,sys; d=json.load(open("scripts/mutation_sites.json")); s=d["sites"] if isinstance(d,dict) else d; sys.exit(0 if s and all(x.get("postgres") in ("killed","survived") for x in s) else 1)'
Ran every exit command; at least one exited non-zero (#0 exit 0; #1 exit 1). #0 passes. #1 fails: all 74 sites in scripts/mutation_sites.json have postgres = None. Not one site carries a postgres verdict, so the column the ticket is about was never written.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 2 exit command(s)
  1. run: .venv-verify/bin/python -m pytest tests/test_verify_names_what_it_did_not_run.py -q
  2. run: python3.12 -c 'import json,sys; d=json.load(open("scripts/mutation_sites.json")); s=d["sites"] if isinstance(d,dict) else d; sys.exit(0 if s and all(x.get("postgres") in ("killed","survived") for x in s) else 1)'

OPS-191highP2we cannot tell

the mutation probe's sites are pinned by line number, so any edit above one turns the gate red without a survivor

This red is the criterion, not the property. Its second criterion can never pass in any tree: the inner double quotes are backslash-escaped in the stored command, so bash fails the command substitution and test then compares an empty string. Unescaped, the same assertion exits zero. So the honest reading is we cannot tell, and the property is unmeasured rather than absent.

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ python3.12 scripts/mutation_probe.py --self-test
$ test \"$(python3.12 -c \"import json;d=json.load(open('scripts/mutation_sites.json'));ss=d['sites'] if isinstance(d,dict) and 'sites' in d else d;print(sum(1 for s in ss if 'line' in s))\")\" -eq 0
Ran every exit command; at least one exited non-zero (#0 exit 0; #1 exit 2). Criterion #1 can never pass in any tree. Its inner double quotes are backslash-escaped in the stored criterion, so bash reports 'syntax error near unexpected token (' inside the command substitution and `test` then compares an empty string, exiting 2 with 'integer expression expected'. Unescaped, the same assertion exits 0: 0 sites carry a `line` key. I changed nothing.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

its own 2 exit command(s)
  1. run: python3.12 scripts/mutation_probe.py --self-test
  2. run: test "$(python3.12 -c "import json;d=json.load(open('scripts/mutation_sites.json'));ss=d['sites'] if isinstance(d,dict) and 'sites' in d else d;print(sum(1 for s in ss if 'line' in s))")" -eq 0

Passed, and it does not count

2 tickets ran their own criterion and it exited zero, and neither is counted as verified. OPS-187: a criterion that was never shown able to fail is not evidence, so a pass from it says nothing about whether the defect is there. Each row below says, in one sentence, what would have exited zero either way.

From docs/security-programme/state.json -> not_evidence, a declared sentence in docs/security-programme/evidence.toml. The generator refuses it on any ticket whose verdict is passes, which is the guarantee spelled a second way.

OPS-092highP1exited zero, proves nothing

The skeleton ships the broken secret scan this repository already fixed in its own Makefile

What could pass whether or not the defect is present. Its one criterion is make secrets, which runs this repository's own scan, and the ticket is about the scan skeleton/Makefile ships - so it exits zero whether or not the kit's copy was ever repaired.

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ make secrets
Every exit command exited zero (#0 exit 0) and the verdict is NOT `passes`. The criterion could pass whether or not the defect is present, so it is not evidence (OPS-187). Its one criterion is `make secrets`, which runs THIS repository's scan. The ticket is about the scan `skeleton/Makefile` ships. The command exits 0 whether or not the kit's copy was ever repaired, so it cannot fail for the reason the ticket exists. Reading skeleton/Makefile suggests the repair did land, and reading is not a criterion.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

OPS-122highno priorityexited zero, proves nothing

No gate asks whether a product configured from env.example alone can sign a token

What could pass whether or not the defect is present. Its one criterion greps for the test's own def line, so it asserts the test is defined rather than that it runs - and without a database that test is skipped and pytest still exits zero. It would pass identically with the body deleted.

No failure scenario is written for this one, so nothing here says who gets hurt or how. That is an absence in evidence.toml rather than a judgement that the risk is small.

$ grep -q "def test_a_default_configured_product_can_sign_in" tests/test_kit_gates.py
Every exit command exited zero (#0 exit 0) and the verdict is NOT `passes`. The criterion could pass whether or not the defect is present, so it is not evidence (OPS-187). Its one criterion greps tests/test_kit_gates.py for `def test_a_default_configured_product_can_sign_in`. It asserts the test is DEFINED, not that it runs. Run without a database the test is SKIPPED and pytest still exits 0: '1 skipped, 37 deselected'. Given a database on my own container it passes, so the gate works - but the criterion would pass identically with the body deleted.

Ran 2026-09-18 against atomic/sweep/what-actually-passes, cut from origin/compound/security-programme at deff84fe. Single host, one laptop. Where a database was needed the criterion stood up its own container, except where noted.

Cannot be measured here

48 tickets, of which 10 name no exit command at all - there is nothing a machine could run to settle them. The others name a path, a repository or a service this tree does not contain. A hatched cell below is a ticket where nothing was attempted, which is different from a ticket where something was attempted and could not reach.

From docs/security-programme/state.json -> partition.ids.unrunnable, which is every unrun verdict with no not_evidence declaration.

tickettaggedwhat it is aboutwhy this tree cannot settle it
AUTH-031highP0a password reset does not end the sessions the account already hadno exit command at all
AUTH-037highP0The admin and founder bootstraps grant the role to an unverified account, so registering the owner's address first makes you system adminno exit command at all
AUTH-043highP0An admin or founder role granted to an unverified account before AUTH-037 is never revokedno exit command at all
CLI-069highP0The scratchpad example delta imports app.webauth.templating, which components/webauth does not shipno exit command at all
COMP-118highP0A production deployment verifies fake-provider webhooks with a secret this repository publishesno exit command at all
OPS-084highP0a Cloudflare token with DNS edit on duchovni.net outlives the job it was minted forno exit command at all
OPS-247highP0On the Hetzner path every email goes to Mailpit, because the compose file pins SMTP_HOST over the founder's .envno exit command at all
PROD-024highP0A child's wishlist token is a path segment, so every page view writes a live bearer credential into the access logAttempted and not measurable in this tree (#0 exit 127). names tests/test_shop.py and uv run pytest, the toy-shop product's own test file and runner. tests/test_shop.py does not exist in this tree; it exists at ~/workspace/projects/toy-shop. Exit 127, and uv built a stray .venv here before failing on a missing pytest.
PROD-040highP0The shopkeeper sets up the family with one password, and the token links are retiredno exit command at all
AUTH-033highP1Nothing limits login attempts per account, so credential stuffing is unthrottledno exit command at all
AUTH-034highP1The password policy is one length check, so a stuffed credential that is guessable still worksno exit command at all
AUTH-048highP1API keys: a customer's programs call the product without a passwordno exit command at all
CLI-076highP1saas feature --no-owner generates an unauthenticated public read and write, contradicting the product CLAUDE.md it ships besideno exit command at all
CLI-095highP1a production deploy is a publish, blocked until every fake is fixed or acceptedno exit command at all
CLI-096highP1saas build --static emits an edge gate, so a private static site is private on every hostname including pages.devno exit command at all
CLI-097highP1publish applies a site's declared access level, and refuses a private site the host cannot enforceno exit command at all
COMP-022highP1Two provider events in one second do not order each other, so a stale snapshot resurrects a cancelled subscriptionno exit command at all
COMP-163highP1a site password gates every path of a server-rendered product, before accountsno exit command at all
COMP-230highP1a product cannot tell Google Ads or Meta that an ad brought a sign-up or a payment, so no founder can run ads that optimiseno exit command at all
COMP-231highP1a product has no consent record, so nothing can gate ad tags or conversions on what a visitor agreed tono exit command at all
CORE-094highP1SECURITY_CSP validates only the nonce, so an operator can ship script-src * and bootno exit command at all
CORE-114highP1The level 2 app is always behind cloudflared, and the example env tells it the hop count is 0no exit command at all
CORE-119highP1nothing declares what a log line may carry, so an undeclared log field cannot be a findingno exit command at all
OPS-035highP1Nothing configures encryption at rest, and the only backup is a manual pg_dump with no schedule, no encryption and no retentionno exit command at all
OPS-136highP1An org-level CI runner that every product can leaveAttempted and not measurable in this tree (#0 exit 1; #1 exit 4). names infra/runner, which does not exist in this tree, and tests/infra/test_runner_repos_are_private.py, which does not exist either. Its acceptance names a GitHub organisation runner group, which is a service outside any repository. Exit 1 then exit 4.
OPS-205highP1The security lens gates four paths, and most of the trust boundaries live in the other treesno exit command at all
OPS-299highP1CI runs on a self-hosted runner on any SSH-reachable VM, provisioned by a script that needs only a host and a userno exit command at all
OPS-302highP1Provision the chosen CI box and run the template's whole CI on it once, then walk the exitno exit command at all
OPS-381highP1a drifted mutation site is found only by running the slow gateno exit command at all
PROD-096highP1subscriptions, sponsors and a shop as one commerce model: billing takes the money, grants say what it boughtno exit command at all
AUTH-035highP2Nothing sends reauthenticate=True, so a Google account's erasure gate is still only a freshness checkno exit command at all
AUTH-059highP2An administrator must have answered a second factor to reach the admin surfaceno exit command at all
COMP-144highP2a tenant row and its members: several people inside one instance, with the operator kept out of the customer's admin roleno exit command at all
COMP-167highP2billing takes card payments for physical goods through an Israeli gateway beside Paddleno exit command at all
COMP-232highP2a founder cannot switch on a Google, Meta or LinkedIn tag without editing templates and the CSPno exit command at all
COMP-233highP2a feature built API-first is invisible on its own page, because the routes take a bearer token and the page holds a cookieno exit command at all
OPS-075highP2The secret scan catches a bare high-entropy key about three times in four, and nothing says sono exit command at all
OPS-303highP2A product gets the self-hosted runner switch and runner.sh, not only the templateno exit command at all
OPS-305highP2infra/runner keeps an admin PAT on the box, which adr-018 forbidsno exit command at all
OPS-301highP3The runner refill loop runs from a laptop, so CI queues whenever the laptop sleepsno exit command at all
OPS-306highP3A job that may use docker is root on the runner box, until docker is rootlessno exit command at all
AUTH-044highno priorityA verified address is taken as account ownership while a password and sessions from before verification surviveno exit command at all
AUTH-057highno prioritya chosen password may be 8 characters and is checked against 547 words, where 800-63B-4 asks 15 for a single factor and a compromised-password listno exit command at all
CORE-112highno priorityA null origin with credentials is accepted, and any sandboxed iframe or data URL sends Origin nullno exit command at all
CORE-122highno prioritya product can put the whole site behind one shared passwordno exit command at all
CORE-123highno prioritythe built image carries no ENVIRONMENT, so a server started without saas deploy runs as developmentno exit command at all
CORE-133highno prioritynothing isolates one user's rows from another's, so every query must remember its own WHEREno exit command at all
OPS-378highno prioritywork survives the owner's Mac dying: nothing that matters exists only on itno exit command at all

Fixed, and found already fixed

These are two different things and one of them is not security work. A ticket the programme closed is a hole that was open and is now shut. A ticket re-verified and found already satisfied is a record catching up with a tree that was always fine. Adding them together would report the most clerical stretch of this programme as its most productive.

From docs/security-programme/state.json -> programme_state, which is the officer's declaration in evidence.toml rather than the ticket's own status.

Closed by the programme: 2

Marked fixed. Both are instruments - the tools that tell a working guard from a deleted one - rather than holes in the product.

  • OPS-184 Eight drifted sites stop make mutation measuring any of its seventy guards
  • OPS-186 Re-verify every security claim against today's tree before any agent is sent to fix one

Found already fixed: 16

Marked proposed_for_closure. Re-verified against the tree and already satisfied. Real, welcome, and bookkeeping.

  • OPS-034 Neither deploy path enforces encryption in transit - the fly module serves plain HTTP on 80 and the deploy script disables TLS to Postgres
  • COMP-068 The account bar copies the request path into next, so a capability URL becomes a credential in a query string
  • CORE-006 CORS is app-wide, and * with credentials is the shipped pairing
  • CORE-011 The rate limit is bypassable with one header, because get_client_ip trusts X-Forwarded-For unconditionally
  • CORE-067 The personal-data gate is table-level, so a new column on a declared table walks past it
  • OPS-052 A sign-in token is in history and not in the tree, and the history scan is red on it
  • OPS-111 cp env.example env gives every auth route a 500 while every gate stays green
  • COMP-010 No admin surface over users, and nothing decides who an admin is
  • CORE-016 DEBUG=true is honoured in production, leaking tracebacks in 500 responses
  • OPS-033 The fly deploy path never sets ENVIRONMENT, so a deployed product runs in development mode with debug on and console logs
  • PROD-004 The two shipped examples hold an email address and an IP address and declare neither, so the page a founder copies is false about the code beside it
  • AUTH-009 Nothing revokes a token, so deactivating or deleting a user does not end their session
  • CORE-048 Nothing sets Strict-Transport-Security, so the redirect that closed OPS-034 still costs one cleartext request
  • CORE-049 A failed job's traceback keeps whatever it was handling, in a column no sweep touches and no erasure can select on
  • CORE-053 ENVIRONMENT decides two startup refusals and .env.example never declares it
  • OPS-041 Every GitHub Action is pinned by moving tag rather than by commit SHA

In flight: 4

What this page will not do is lead with a count of merges. OPS-181 forbids it, and the reason is measured: twenty-two units merged and verified_passing moved by zero, because the number was never a measurement - it was the absence of one. A page headed by merges would have hidden exactly that. And a caveat about this panel itself: docs/security-programme/DIGEST.md names three closed holes - the capability token in a URL, the webhook that refused its own provider, the product that could not close registration. None of the three carries programme_state: fixed in the file this page reads, so none of them is counted on the left. This page renders the field rather than the narration, and the two disagree.

Who gets hurt, and how

One sentence per ticket, concrete, naming who gets hurt and how. "CORS is misconfigured" is not one. It is the field the owner said he cares most about, and 78 of 112 tickets do not have one - which is the reason the danger list above cannot be trusted to be complete. All 112 are below in severity order, each with the bucket it is in, so a sentence and the verdict that answers it are on the same line. A hatched cell is a ticket nobody can rank.

From docs/security-programme/state.json -> failure_scenario, hand written in docs/security-programme/evidence.toml. It cannot be derived - a generator that invented these would be inventing the only field on this page written by a person.

34of 112 written

Every critical ticket has one. The 78 that do not are hatched below, in the same severity order as the queue, so the most expensive gaps are nearest the top.

All 112, worst first

tickettaggedwhat it is aboutwho gets hurt, and how
AUTH-015criticalP0passedThe shipped email adapter prints every reset and sign-in link to stdout, and nothing refuses it in productionThe shipped email setting prints the whole message to the console instead of sending it, so in any normal container deploy every password-reset link a customer requests is written into the server logs, and anyone who can read logs can take over any account by replaying one.
CORE-084criticalP0passedThe shipped hop count gives every caller one rate limit bucket, so one attacker switches off authentication for everybodyno failure scenario
AUTH-006criticalP1passedThe .env.example JWT secret signs tokens at runtime, and only an advisory command objectsThe signing key that proves who you are is published in this public repository, so anyone who finds a product built from the template can mint a token for any account on it, including an administrator's, without needing a password or leaving a failed login behind.
AUTH-021criticalP1failedFour filters in the grant queries can be deleted without any gate going redFour lines decide that a permission belongs to you rather than to somebody else, and each can be deleted with every test and every gate still green, so one careless edit would let any signed-in customer read, grant and revoke every other customer's permissions with nothing reporting it.
OPS-034criticalP1passedNeither deploy path enforces encryption in transit - the fly module serves plain HTTP on 80 and the deploy script disables TLS to PostgresA deployed product would answer plain unencrypted HTTP on port 80 and talk to its own database with TLS disabled, so anyone between the two could read every row in transit.
AUTH-017criticalP2passedThe stub identity provider is permitted in staging, and staging is a public deployA staging deployment is a public web address, and on staging the shipped identity setting signs you in as whatever email address you type, so a stranger who finds the staging URL can sign in as the founder or as any customer by typing their address.
AUTH-031highP0cannot be measured herea password reset does not end the sessions the account already hadno failure scenario
AUTH-037highP0cannot be measured hereThe admin and founder bootstraps grant the role to an unverified account, so registering the owner's address first makes you system adminno failure scenario
AUTH-043highP0cannot be measured hereAn admin or founder role granted to an unverified account before AUTH-037 is never revokedno failure scenario
CLI-069highP0cannot be measured hereThe scratchpad example delta imports app.webauth.templating, which components/webauth does not shipno failure scenario
COMP-021highP0passedAn unauthenticated caller starves the billing webhook out of its rate limit, and COMP-018 claims the oppositeno failure scenario
COMP-068highP0passedThe account bar copies the request path into next, so a capability URL becomes a credential in a query stringA child's wishlist link, which is itself the password, was copied into the sign-in page's return address, so the credential travelled in another URL and into another log line.
COMP-093highP0passedA product cannot close self-service registration, and the kit ships it openA shop whose own privacy declaration says every visitor is a child serves an open account-creation form to anyone who finds the address, and nothing anywhere can switch it off.
COMP-103highP0passeda token in a URL cannot become a session, so every product puts the credential in the pathFive children's wishlist links are themselves their passwords, and every page view writes one into the production access log, so anyone who can read those logs can open any of those children's pages.
COMP-118highP0cannot be measured hereA production deployment verifies fake-provider webhooks with a secret this repository publishesno failure scenario
CORE-006highP0passedCORS is app-wide, and * with credentials is the shipped pairingWith CORS set to allow every origin and send credentials, any website a signed-in customer visited could read their data and act as them from their own browser.
CORE-011highP0passedThe rate limit is bypassable with one header, because get_client_ip trusts X-Forwarded-For unconditionallyAnyone on the internet could send a made-up X-Forwarded-For header on every request and never hit the rate limit, so one laptop could exhaust a product's login or password-reset endpoint while honest users were throttled.
CORE-043highP0failedA person cannot delete their account, and the audit trail holds personal data so deleting them is not a deleteA person asks for their account to be deleted and it is deleted, except that three declared stores say nothing can reach them - a crash report holding whatever a library put in an exception message, the email address on an invitation nobody accepted, and a job payload that never said who it was about - so the delete is honest about what it missed and the data stays until somebody builds the reach.
CORE-067highP0passedThe personal-data gate is table-level, so a new column on a declared table walks past itAdding one column to a table that already declared its personal data would hide a person's phone number from the erase routine and from the privacy page, with every gate still green.
CORE-075highP0passedNine postgres tests are red on mainno failure scenario
CORE-080highP0faileda declaration is the security mechanism so the gate enumerates the artefacts and fails on an undeclared oneno failure scenario
CORE-081highP0failedevery unsafe default refuses at boot and the unsafe defaults are a register rather than a memoryno failure scenario
OPS-044highP0passedThe fly deploy sets three secrets, so a product with billing installed deploys green and cannot sell anythingno failure scenario
OPS-052highP0passedA sign-in token is in history and not in the tree, and the history scan is red on itA real sign-in token sits in this repository's history, so anybody who clones it gets a credential - though this particular one is single use, already spent, was signed by a development secret that was never deployed and only ever addressed a laptop's loopback interface.
OPS-084highP0cannot be measured herea Cloudflare token with DNS edit on duchovni.net outlives the job it was minted forA live API token can rewrite the DNS of the owner's personal domain, including the records that say where his email goes, and it has had no job to do since 2026-09-13.
OPS-111highP0passedcp env.example env gives every auth route a 500 while every gate stays greenA founder who copied .env.example and deployed would get a product that boots, answers health checks and accepts sign-ups, all signed with a JWT secret published in this public repository, so anybody could mint a token for any account.
OPS-131highP0failedGitHub Actions has started no job since 2026-09-14, so every CI red is the same redEvery CI run on the main branch has shown a red X for three days because the account cannot pay, which looks exactly like a failing test - so a real test failure would be invisible, and nothing has actually been checked since 2026-09-14.
OPS-183highP0failedsettle OPS-169's central claim by standing up postgres and running the suiteno failure scenario
OPS-184highP0passedEight drifted sites stop make mutation measuring any of its seventy guardsThe one tool that can tell a working guard from a deleted one refused to run, so nobody could answer whether deleting a security check would be noticed - and five of the plan's six causes rested on the answer.
OPS-186highP0passedRe-verify every security claim against today's tree before any agent is sent to fix oneA programme acting on the security plan would have re-fixed fourteen things that were already fixed, and each one would have produced a diff, a commit and a closed ticket - indistinguishable from progress.
OPS-247highP0cannot be measured hereOn the Hetzner path every email goes to Mailpit, because the compose file pins SMTP_HOST over the founder's .envno failure scenario
PROD-024highP0cannot be measured hereA child's wishlist token is a path segment, so every page view writes a live bearer credential into the access logFive children aged 9 to 15 have live 256-bit credentials accumulating in a production access log, and they cannot be rotated quietly because rotating one breaks that child's link and requires a conversation with the child and their grandfather.
PROD-040highP0cannot be measured hereThe shopkeeper sets up the family with one password, and the token links are retiredno failure scenario
AUTH-010highP1failedThere is no second factor anywhere, and the password policy is eight characters and nothing elseno failure scenario
AUTH-020highP1passedA deactivated account keeps every route, and no test in this repository noticesno failure scenario
AUTH-033highP1cannot be measured hereNothing limits login attempts per account, so credential stuffing is unthrottledno failure scenario
AUTH-034highP1cannot be measured hereThe password policy is one length check, so a stuffed credential that is guessable still worksno failure scenario
AUTH-048highP1cannot be measured hereAPI keys: a customer's programs call the product without a passwordno failure scenario
CLI-059highP1passedsaas update reports a lost security declaration as an ordinary conflict, and both naive resolutions are wrongno failure scenario
CLI-076highP1cannot be measured heresaas feature --no-owner generates an unauthenticated public read and write, contradicting the product CLAUDE.md it ships besideno failure scenario
CLI-095highP1cannot be measured herea production deploy is a publish, blocked until every fake is fixed or acceptedno failure scenario
CLI-096highP1cannot be measured heresaas build --static emits an edge gate, so a private static site is private on every hostname including pages.devno failure scenario
CLI-097highP1cannot be measured herepublish applies a site's declared access level, and refuses a private site the host cannot enforceno failure scenario
COMP-010highP1passedNo admin surface over users, and nothing decides who an admin isWith no admin surface and nothing deciding who an admin is, an operator had no way to look at a suspected account, and any page added for it would have been reachable by any signed-in customer.
COMP-022highP1cannot be measured hereTwo provider events in one second do not order each other, so a stale snapshot resurrects a cancelled subscriptionno failure scenario
COMP-035highP1passedThe webhook MAC can stop covering the timestamp and every test still passesno failure scenario
COMP-069highP1failedA capability URL that carries its token in the query string is still copied into nextA child's wishlist link is copied whole into the sign-in page's return address, so the credential is handed to the browser's history and to every proxy between the child and the shop.
COMP-163highP1cannot be measured herea site password gates every path of a server-rendered product, before accountsno failure scenario
COMP-230highP1cannot be measured herea product cannot tell Google Ads or Meta that an ad brought a sign-up or a payment, so no founder can run ads that optimiseno failure scenario
COMP-231highP1cannot be measured herea product has no consent record, so nothing can gate ad tags or conversions on what a visitor agreed tono failure scenario
CORE-016highP1passedDEBUG=true is honoured in production, leaking tracebacks in 500 responsesAn error on a production page would return the full stack trace to whoever caused it, including the SQL statement and its parameters, which is a free read of the database schema and often of live values.
CORE-033highP1passedThe generated CI never sets TEST_DATABASE_URL, so every feature's owner-isolation test is green by skippingno failure scenario
CORE-045highP1failedNothing erases a person, and the erasure that adr-006 decided has no implementationno failure scenario
CORE-071highP1passedA paying customer's crash reaches nobody, and a traceback that would carry it carries their address toono failure scenario
CORE-082highP1failedCORS is one policy for the whole application, so a public path and a private path cannot allow different originsA founder who needs their public widget callable from any website has no way to say that only their own site may reach the pages that carry a login session, so they must either widen the whole application or break the widget.
CORE-083highP1passedthe rate limiter is one module-global, so a rate-limit test poisons its siblings and only collection order hides itno failure scenario
CORE-094highP1cannot be measured hereSECURITY_CSP validates only the nonce, so an operator can ship script-src * and bootno failure scenario
CORE-114highP1cannot be measured hereThe level 2 app is always behind cloudflared, and the example env tells it the hop count is 0no failure scenario
CORE-119highP1cannot be measured herenothing declares what a log line may carry, so an undeclared log field cannot be a findingno failure scenario
OPS-033highP1passedThe fly deploy path never sets ENVIRONMENT, so a deployed product runs in development mode with debug on and console logsA deployed product would decide it was in development, turn on debug mode and log to the console, which means full stack traces including database contents returned to whoever triggered an error.
OPS-035highP1cannot be measured hereNothing configures encryption at rest, and the only backup is a manual pg_dump with no schedule, no encryption and no retentionno failure scenario
OPS-037highP1failedLevel 2 carries production secrets in a dotenv and a shell, and the documented JWT rotation names a variable no code readsno failure scenario
OPS-054highP1passedForty-six tests need a database, no gate provides one, and five different variables name itThe gate this project holds itself to reports success over a selection that excludes every test needing a database, so a green result says nothing about whether one customer can read another's data.
OPS-057highP1failedThe only two row-ownership tests in the repository are collected by nothing, because examples/ holds a delta and not a projectThe only two tests in this repository that check one customer cannot read another customer's rows could not be collected by anything, so nobody has ever confirmed that data is separated between accounts.
OPS-092highP1not evidenceThe skeleton ships the broken secret scan this repository already fixed in its own Makefileno failure scenario
OPS-136highP1cannot be measured hereAn org-level CI runner that every product can leaveno failure scenario
OPS-170highP1failedevery credential this project holds has a row naming its scope its holder and when it diesno failure scenario
OPS-171highP1failedsecurity impact is self reported and nothing corroborates it so a mis-tagged critical defect is invisibleno failure scenario
OPS-185highP1passedThe mutation probe is blind to decorator-applied guards and to route modulesThe guard checker reports a hundred percent pass rate over a list that contains no decorator-applied guard and only one route file, so a rate-limit exemption can be deleted from a payment webhook and the checker will still say everything is covered.
OPS-192highP1passedthree of the six examples never declare their cross-account test, so the gate's own safety net misses themno failure scenario
OPS-193highP1passedcheck_done_when's comment-only rule leads on # and is blind to docstrings, where this repository keeps its proseno failure scenario
OPS-195highP1passedstate.json's guarantee requires a run record but not that the command be the ticket's own criterionno failure scenario
OPS-198highP1passedForty-nine criteria name a test path that does not resolve, and 22 of them name a tree the gate cannot reachno failure scenario
OPS-205highP1cannot be measured hereThe security lens gates four paths, and most of the trust boundaries live in the other treesno failure scenario
OPS-299highP1cannot be measured hereCI runs on a self-hosted runner on any SSH-reachable VM, provisioned by a script that needs only a host and a userno failure scenario
OPS-302highP1cannot be measured hereProvision the chosen CI box and run the template's whole CI on it once, then walk the exitno failure scenario
OPS-381highP1cannot be measured herea drifted mutation site is found only by running the slow gateno failure scenario
PROD-004highP1passedThe two shipped examples hold an email address and an IP address and declare neither, so the page a founder copies is false about the code beside itA founder copying an example would ship a privacy page promising to erase personal data, over code that stored an email address and an IP address the erase routine could not find.
PROD-096highP1cannot be measured heresubscriptions, sponsors and a shop as one commerce model: billing takes the money, grants say what it boughtno failure scenario
AUTH-008highP2passedOne resolver answers every scope, so a product's own policy decides who administers the installationno failure scenario
AUTH-009highP2passedNothing revokes a token, so deactivating or deleting a user does not end their sessionSacking someone or deleting their account would not log them out, so an ex-employee's open browser tab would keep working until the token expired on its own.
AUTH-024highP2failedRe-authenticating a Google account does not make Google ask for anything, so a borrowed laptop still deletes itno failure scenario
AUTH-035highP2cannot be measured hereNothing sends reauthenticate=True, so a Google account's erasure gate is still only a freshness checkno failure scenario
AUTH-059highP2cannot be measured hereAn administrator must have answered a second factor to reach the admin surfaceno failure scenario
COMP-144highP2cannot be measured herea tenant row and its members: several people inside one instance, with the operator kept out of the customer's admin roleno failure scenario
COMP-167highP2cannot be measured herebilling takes card payments for physical goods through an Israeli gateway beside Paddleno failure scenario
COMP-232highP2cannot be measured herea founder cannot switch on a Google, Meta or LinkedIn tag without editing templates and the CSPno failure scenario
COMP-233highP2cannot be measured herea feature built API-first is invisible on its own page, because the routes take a bearer token and the page holds a cookieno failure scenario
CORE-048highP2passedNothing sets Strict-Transport-Security, so the redirect that closed OPS-034 still costs one cleartext requestA person typing the product's bare hostname on cafe wifi would make one unencrypted request before being redirected, and anybody on that network could read their session cookie from it.
CORE-049highP2passedA failed job's traceback keeps whatever it was handling, in a column no sweep touches and no erasure can select onA failed welcome email would write the customer's address into a database column no retention sweep touches and no erasure request can reach, permanently and silently.
CORE-053highP2passedENVIRONMENT decides two startup refusals and .env.example never declares itA founder filling in .env.example would never set the one variable that gates two startup refusals, because the file did not mention it, and would deploy with both refusals inert.
CORE-060highP2failedA declared credential path that matches no route is inert and looks like protectionno failure scenario
OPS-036highP2failedNo dependency or secret scan reaches a generated product, and the template's own pip-audit lives in an opt-in lensno failure scenario
OPS-041highP2passedEvery GitHub Action is pinned by moving tag rather than by commit SHAEvery GitHub Action was pinned to a moving tag, so the author of any one of them could have repointed it at code that runs with this repository's secrets and read every deploy credential.
OPS-058highP2failedA restored backup brings an erased person back, and nothing re-applies the erasureno failure scenario
OPS-075highP2cannot be measured hereThe secret scan catches a bare high-entropy key about three times in four, and nothing says sono failure scenario
OPS-130highP2failedSix guards die only in the suite make verify never runsThe 135 tests that check who is allowed to see which rows pass when a database is present and vanish without one, and the command this project trusts runs without a database - so the checks that matter most are the ones nobody runs.
OPS-191highP2failedthe mutation probe's sites are pinned by line number, so any edit above one turns the gate red without a survivorno failure scenario
OPS-196highP2passeda criterion satisfied by a constant's definition cannot fail when the constant's use is deletedno failure scenario
OPS-303highP2cannot be measured hereA product gets the self-hosted runner switch and runner.sh, not only the templateno failure scenario
OPS-305highP2cannot be measured hereinfra/runner keeps an admin PAT on the box, which adr-018 forbidsno failure scenario
OPS-301highP3cannot be measured hereThe runner refill loop runs from a laptop, so CI queues whenever the laptop sleepsno failure scenario
OPS-306highP3cannot be measured hereA job that may use docker is root on the runner box, until docker is rootlessno failure scenario
AUTH-044highno prioritycannot be measured hereA verified address is taken as account ownership while a password and sessions from before verification surviveno failure scenario
AUTH-057highno prioritycannot be measured herea chosen password may be 8 characters and is checked against 547 words, where 800-63B-4 asks 15 for a single factor and a compromised-password listno failure scenario
CORE-112highno prioritycannot be measured hereA null origin with credentials is accepted, and any sandboxed iframe or data URL sends Origin nullno failure scenario
CORE-122highno prioritycannot be measured herea product can put the whole site behind one shared passwordno failure scenario
CORE-123highno prioritycannot be measured herethe built image carries no ENVIRONMENT, so a server started without saas deploy runs as developmentno failure scenario
CORE-133highno prioritycannot be measured herenothing isolates one user's rows from another's, so every query must remember its own WHEREno failure scenario
OPS-122highno prioritynot evidenceNo gate asks whether a product configured from env.example alone can sign a tokenno failure scenario
OPS-197highno prioritypassedseven drafted tickets assert a fix on a criterion pinned to a name that outlives the thingno failure scenario
OPS-378highno prioritycannot be measured herework survives the owner's Mac dying: nothing that matters exists only on itno failure scenario

Verified passing

42 of 112. Each carries a run record with a command, a tree, a date and a result - the file has no code path that reports a pass without one. The fourth column is OPS-195's check: whether the command that ran named the same Python file the ticket's own criterion names. 30 of 42 could be compared and 12 were declined, because one side names no Python file and calling that covered would be an overstatement.

From docs/security-programme/state.json -> criterion_check and criterion_coverage, derived on every run.

tickettaggedwhat it is aboutown criterionran
AUTH-015criticalP0The shipped email adapter prints every reset and sign-in link to stdout, and nothing refuses it in productioncovered2026-09-18
CORE-084criticalP0The shipped hop count gives every caller one rate limit bucket, so one attacker switches off authentication for everybodycovered2026-09-20
AUTH-006criticalP1The .env.example JWT secret signs tokens at runtime, and only an advisory command objectscovered2026-09-18
OPS-034criticalP1Neither deploy path enforces encryption in transit - the fly module serves plain HTTP on 80 and the deploy script disables TLS to Postgrescovered2026-09-17
AUTH-017criticalP2The stub identity provider is permitted in staging, and staging is a public deploycovered2026-09-18
COMP-021highP0An unauthenticated caller starves the billing webhook out of its rate limit, and COMP-018 claims the oppositecovered2026-09-18
COMP-068highP0The account bar copies the request path into next, so a capability URL becomes a credential in a query stringcovered2026-09-17
COMP-093highP0A product cannot close self-service registration, and the kit ships it opencovered2026-09-18
COMP-103highP0a token in a URL cannot become a session, so every product puts the credential in the pathcovered2026-09-18
CORE-006highP0CORS is app-wide, and * with credentials is the shipped pairingnot compared2026-09-17
CORE-011highP0The rate limit is bypassable with one header, because get_client_ip trusts X-Forwarded-For unconditionallycovered2026-09-18
CORE-067highP0The personal-data gate is table-level, so a new column on a declared table walks past itcovered2026-09-17
CORE-075highP0Nine postgres tests are red on mainnot compared2026-09-18
OPS-044highP0The fly deploy sets three secrets, so a product with billing installed deploys green and cannot sell anythingcovered2026-09-18
OPS-052highP0A sign-in token is in history and not in the tree, and the history scan is red on itnot compared2026-09-17
OPS-111highP0cp env.example env gives every auth route a 500 while every gate stays greencovered2026-09-17
OPS-184highP0Eight drifted sites stop make mutation measuring any of its seventy guardsnot compared2026-09-17
OPS-186highP0Re-verify every security claim against today's tree before any agent is sent to fix onenot compared2026-09-17
AUTH-020highP1A deactivated account keeps every route, and no test in this repository noticescovered2026-09-18
CLI-059highP1saas update reports a lost security declaration as an ordinary conflict, and both naive resolutions are wrongcovered2026-09-18
COMP-010highP1No admin surface over users, and nothing decides who an admin isnot compared2026-09-17
COMP-035highP1The webhook MAC can stop covering the timestamp and every test still passescovered2026-09-18
CORE-016highP1DEBUG=true is honoured in production, leaking tracebacks in 500 responsesnot compared2026-09-17
CORE-033highP1The generated CI never sets TEST_DATABASE_URL, so every feature's owner-isolation test is green by skippingnot compared2026-09-18
CORE-071highP1A paying customer's crash reaches nobody, and a traceback that would carry it carries their address toocovered2026-09-18
CORE-083highP1the rate limiter is one module-global, so a rate-limit test poisons its siblings and only collection order hides itcovered2026-09-18
OPS-033highP1The fly deploy path never sets ENVIRONMENT, so a deployed product runs in development mode with debug on and console logsnot compared2026-09-17
OPS-054highP1Forty-six tests need a database, no gate provides one, and five different variables name itcovered2026-09-18
OPS-185highP1The mutation probe is blind to decorator-applied guards and to route modulesnot compared2026-09-18
OPS-192highP1three of the six examples never declare their cross-account test, so the gate's own safety net misses themcovered2026-09-18
OPS-193highP1check_done_when's comment-only rule leads on # and is blind to docstrings, where this repository keeps its prosecovered2026-09-18
OPS-195highP1state.json's guarantee requires a run record but not that the command be the ticket's own criterioncovered2026-09-18
OPS-198highP1Forty-nine criteria name a test path that does not resolve, and 22 of them name a tree the gate cannot reachcovered2026-09-18
PROD-004highP1The two shipped examples hold an email address and an IP address and declare neither, so the page a founder copies is false about the code beside itcovered2026-09-17
AUTH-008highP2One resolver answers every scope, so a product's own policy decides who administers the installationcovered2026-09-18
AUTH-009highP2Nothing revokes a token, so deactivating or deleting a user does not end their sessioncovered2026-09-17
CORE-048highP2Nothing sets Strict-Transport-Security, so the redirect that closed OPS-034 still costs one cleartext requestcovered2026-09-28
CORE-049highP2A failed job's traceback keeps whatever it was handling, in a column no sweep touches and no erasure can select ondeclared substitution2026-09-17
CORE-053highP2ENVIRONMENT decides two startup refusals and .env.example never declares itnot compared2026-09-17
OPS-041highP2Every GitHub Action is pinned by moving tag rather than by commit SHAnot compared2026-09-17
OPS-196highP2a criterion satisfied by a constant's definition cannot fail when the constant's use is deletedcovered2026-09-18
OPS-197highno priorityseven drafted tickets assert a fix on a criterion pinned to a name that outlives the thingcovered2026-09-18
what that comparison cannot see
  • it compares Python filenames, so a criterion naming no .py file and a run command naming no .py file are both declined rather than judged - criterion_coverage.declined says how many
  • it compares whole path strings, so the same file named two ways - tests/web/test_shell.py against components/webauth/tests/test_shell.py - reads as two files, which is why COMP-068 needed a declaration rather than a looser comparison
  • one shared file counts as covering every criterion, so a ticket with four criteria whose run touches one of them is reported covered
  • it says nothing about whether the criterion would pass if run. A declared substitution makes the record honest about a criterion that is red - it does not make the criterion green. check_done_when.py and check_tickets.py grade the criterion itself
  • substitutes_for is validated against the ticket's done_when and against nothing else, so a declaration can name a real criterion for a bad reason

What this page refuses to show

Each of these is a way this programme has already been misled, so each is a rule with a reason rather than a preference.

A count of closed or merged tickets, as the headline

OPS-181. Twenty-two units merged and verified_passing did not move, because it was never a measurement. The four buckets are at the top instead, and merges are not on this page at all.

A pass from a criterion never shown able to fail

OPS-187. Two of those are marked and neither is counted as verified. Where a passing ticket's criterion could not even be compared against what ran, the cell says not compared rather than covered.

Anything parsed out of prose

Every figure here is read from docs/security-programme/state.json. The two distinctions that used to live only in sentences - this pass proves nothing and this red is the criterion - are declared fields now, validated by the generator. A parser over the 7,098-line log would be a second source of truth for the same facts.

A verdict without the tree it describes

This programme reported make doc-gates green four times while it was red at origin/main. Both were true and the omission is what misled, so every result on this page names its tree and its date in the row itself.

The file's own guarantee, verbatim: verification.verdict is never 'passes' without a run record carrying a command, a tree, a date and a result. A ticket nobody has run says 'unrun'. It never says passed. And where both the ticket's own done_when and its run commands name Python files, a 'passes' either shares one of those files or carries substitutes_for naming a criterion the ticket actually has: a declared substitution is allowed, a silent one is refused. That last clause is a filename comparison and it is crude on purpose - it declines every ticket where one side names no Python file, and one shared file counts as covering all of them. criterion_coverage carries both counts and what the comparison cannot see, derived on every run.