| P04 | CLI-051 | the examples gate declares BILLING_MODE and not WEB_BETA_PROMISE, so every page test in paid-notes 500s | make examples-gate is red for paid-notes on main, and the only row-ownership assertions in this repository are in that gate - so the gate OPS-057 built to stop them going uncollected is failing for a reason that has nothing to do with them. | S |
| P04 | COMP-062 | saas add auth leaves a tree the product's own pre-commit hook refuses | The first commit a founder makes after installing auth is rejected by the hooks the template installed for them, and the only obvious way out is to weaken the secret scan. | S |
| P04 | CORE-040 | Nothing sends an email through the Resend adapter, and three smaller gaps found beside it | saas doctor proves a Resend key is accepted and a curl proves the API works, but neither executes components/core/adapters/email/resend.py, so the adapter's first real run is a stranger's login attempt. | S |
| P04 | CORE-043 | A person cannot delete their account, and the audit trail holds personal data so deleting them is not a delete | No route or command deletes an account, and components/core/models/audit.py stores actor_id, ip_address and a free-form context JSON, so deleting the user would not be a delete. | L |
| P04 | CORE-067 | The personal-data gate is table-level, so a new column on a declared table walks past it | no reason written | M |
| P04 | CORE-075 | Nine postgres tests are red on main | make test-postgres against a real database on main is 9 failed, 126 passed. Every one of the nine is an erasure, export or account-deletion test - the privacy half of the suite. make verify does not run them, and CI's postgres job does, so either that job is red on main or nobody has read it. Found by OPS-126's mutation probe, which could not measure core/erasure.py at all because the baseline was red before any mutation. | M |
| P04 | DOC-057 | DEV-CHECKLIST section A does not run as written | The file written to be handed to a coding assistant has a step that exits 2 and omits the one credential without which the product's main flow returns 500 while every probe it tells you to run stays green. | S |
| P04 | OPS-038 | The repository's own protection settings are clicks, and the OpenTofu that would replace them cannot be applied on the current GitHub plan | CC.08.04 and CC.08.07 ask for a protected branch pattern and required status checks and this repository has neither, infra/scm/ now plans cleanly against the live repository, and the branch protection API returns 403 Upgrade to GitHub Pro because avner-organization is on the free plan and the repository is private. | S |
| P04 | OPS-068 | The scaffold gate hands the product an environment CI does not, so a product that imports locally fails on its first push | cli/gates/scaffold.py sets PROBE_ENV with DATABASE_URL before importing every module, and the shipped skeleton/.github/workflows/ci.yml fast job set none, so make imports died on 1 validation error for Settings on a real runner, run 34756167144, on the first push of a tree saas new had just produced. Nothing compares the two environments, so the local gate will hide the next one too. | S |
| P04 | OPS-084 | a Cloudflare token with DNS edit on duchovni.net outlives the job it was minted for | The token this session holds carries Zone -> DNS -> Edit on duchovni.net. The owner settled on 2026-09-13 that records are central and written in the duchovni-net repository, so the token has no remaining job - and DNS edit on that zone includes the mail records the apex now publishes. | S |
| P04 | OPS-111 | cp env.example env gives every auth route a 500 while every gate stays green | AuthSettings' refusal of a published signing key is reached only from get_auth_settings, which is called from sixteen places and every one of them is inside a request handler, a dependency or a scheduled task - so it is discovered by an HTTP request that signs or verifies a token, and /healthz, /readyz and make verify all pass without making one. | M |
| P04 | OPS-114 | plan_batch --propose and --files crash, so rule 1 cannot run and make batch is green anyway | scripts/check_doc_paths.py::resolves grew a second parameter, where, and scripts/plan_batch.py:347 still calls it with one. Every code path through inferred_files raises TypeError, which is --files and --propose for any lane holding a live ticket. make batch runs --self-test, --check and --render --check and all three pass, because batch-001's units name their tickets and never reach the inference. So the planner's first rule - fan out on files, never on a seam - is unrunnable and no gate says so. | S |
| P04 | OPS-121 | Nothing says which build a deployed product is serving | toys.duchovni.net/wishlist answered 404 for two days because main was ahead of the deployed image, and the only thing that noticed was a curl a journey's required walked pin forced somebody to type. No gate, workflow or endpoint anywhere reads a deployed instance. | M |
| P04 | OPS-131 | GitHub Actions has started no job since 2026-09-14, so every CI red is the same red | 155 consecutive CI runs on main have had every job refused before its first step with "The job was not started because recent account payments have failed or your spending limit needs to be increased". The postgres job is correctly written and has not executed once since 2026-09-14T11:22Z, so nine red privacy tests survived on main with a red X beside every commit. | S |
| P04 | PROD-024 | A child's wishlist token is a path segment, so every page view writes a live bearer credential into the access log | The template's own request-log module names this exact mistake and says not to make it; the first product made it, for five children's credentials, in production. | M |
| P13 | AUTH-027 | One login costs 64 MiB and forty can run at once in a single-process container | PasswordHasher() takes argon2's library defaults of 64 MiB per hash, anyio's default thread limiter permits 40 at once, and the Dockerfile runs one uvicorn process - so forty simultaneous logins peak at 2.5 GB of RSS, measured, and the process is OOM-killed on any instance size a founder would start with. | S |
| P13 | CLI-060 | saas update carries a new required setting into .env.example and says nothing, so the deploy finds out | CORE-073 made PRODUCT_NAME required in staging and production. saas update delivered the new .env.example line as one of 67 silently updated files, mentioned PRODUCT_NAME zero times in its output, and correctly never touched .env - so the first thing that notices is create_app() refusing on a deployed machine. | M |
| P13 | COMP-014 | contracts/billing.md needs revising - two interface decisions and four stale commands | The contract puts entitlement on the adapter Protocol and describes it as a local read that never touches the provider, which cannot both hold, and four of its criteria call a get_adapter signature main no longer has. | S |
| P13 | COMP-019 | Billing's routers are not wired into the skeleton, so a scaffolded product has no billing routes | include_router appears nowhere in cli/, components/ or skeleton/, so saas add billing copies the code into a product and nothing mounts it. The same is true of auth and webauth. | S |
| P13 | COMP-020 | Every Stripe 4xx becomes BillingProviderUnavailable, so a bad price id can read as a 72-hour outage | StripeBillingAdapter._request raises BillingProviderUnavailable for every status >= 400, so a 400 for a price id that does not exist is indistinguishable from a 503, and BILLING_STALENESS_WINDOW_HOURS fails open for 72 hours on what it reads as an outage. | S |
| P13 | COMP-022 | Two provider events in one second do not order each other, so a stale snapshot resurrects a cancelled subscription | the monotonic guard is a strict occurred_at < provider_state_at and Stripe stamps events with created, a UNIX second, so any two events inside one second both apply and the last one delivered wins whatever order they really happened in. | M |
| P13 | COMP-026 | No request from the Paddle adapter has reached Paddle, and no account exists | Every call in components/core/adapters/billing/paddle.py was executed only against a Prism mock loaded with Paddle's own OpenAPI description. That proves the request shapes are in the specification and nothing about a real account - not that an Israeli seller is accepted at signup, not that a default payment link can be approved, not what a real error body looks like, and not that a real signed webhook is believed. | S |
| P13 | COMP-031 | BILLING_PLANS is an allowlist of provider ids, not a catalogue, so entitlement is answered in the provider's vocabulary | BILLING_PLANS is a comma-separated string of provider price ids, checked for membership. There is no price, interval, currency or entitlement anywhere in the repository, so there is nothing a provider's catalogue could be a projection of. Worse, Subscription.plan carries the provider's price id inward through the webhook into subscriptions.plan, which entitlement.py then reads - so the application's own vocabulary for what a customer may do is the payment provider's. | L |
| P13 | COMP-037 | Nothing acts when a grace period expires, so a past_due subscription stays past_due forever and the customer finds out by being refused | resolve_entitlement is pure and reads the clock it is handed, so PAST_DUE_GRACE_EXPIRED is computed at the moment an authenticated request arrives and at no other time. Nothing transitions the subscription, nothing sends a reminder inside the window, and a customer who stops using the product during their grace period is never told it ended. | M |
| P13 | COMP-038 | A webhook that fails to apply is only recovered by the provider's retry, and the shipped provider does not retry | components/billing/routes/webhooks.py answers 5xx and rolls the event row back so the provider's retry is the whole recovery mechanism, which is a dependency on somebody else's policy - and under the shipped ADAPTER_BILLING=fake there is no provider and therefore no retry, so a transient database failure loses the event permanently. | M |
| P13 | COMP-053 | A pricing page can be read in a browser and the checkout behind it cannot | POST /billing/checkout is a Bearer-authenticated JSON call that answers with a hosted payment URL, so a person who has just read GET /pricing in a browser has no route to paying - every call to action on the landing and pricing pages points at /register because there is nothing further to point at. | M |
| P13 | COMP-057 | A charge the provider accepted is rolled back with the request that failed after it | billing_charges says it records the attempt and not only the success, and it does not. _claim writes inside the caller's transaction and DatabaseConfig.session rolls back on any exception, so a provider call that charged the card and then timed out leaves zero rows - for start_checkout, change_plan, cancel and start_purchase alike. | M |
| P13 | COMP-070 | the stripe webhook route 500s when there is no secret key, and the refusal claims it stops the process | A product with a correct webhook secret and no API key answers 500 to every genuinely signed delivery, which Stripe retries, while /healthz stays green - so the deployment looks healthy and no subscription ever activates. | S |
| P13 | COMP-077 | One place from which every alert to a user is produced | He described it twice in eighteen minutes on 2026-09-12, the second time unprompted, and gave the worked example himself. COMP-016 is one instance of it and COMP-007 is one channel. The seam has no ticket, and a seam decided after two callers exist is a refactor rather than a design. | L |
| P13 | COMP-082 | the account bar makes every page need the database, so a stopped Postgres is a bare 500 on every page a signed-in person opens | Every page in a product with webauth installed answers a bare 500 after five seconds when the database is unreachable, including /, /pricing and /terms, whose own routes take no session and need no database at all. The cost shell.py documents is one query. The cost it does not document is that the query is on the critical path of every page, so an unreachable host turns a marketing site into Internal Server Error. | M |
| P13 | COMP-093 | A product cannot close self-service registration, and the kit ships it open | toys.duchovni.net serves a working account-creation form to anybody who finds the URL. The shop's own privacy declaration states that everyone browsing it is a minor and that collecting an address for a nine-year-old to look at a plush is a liability with no product reason. There is no setting anywhere in the kit that closes self-service signup, so the product could not have been configured otherwise. | S |
| P13 | CORE-010 | Adapter registry state leaks between tests | A test that passes alone and fails in the suite teaches everyone to ignore the suite. | S |
| P13 | CORE-017 | The adapter registry erases the Protocol, and the overloads that restore it are an assertion nothing checks | mypy --strict reports LLMAdapter for an object that isinstance says is not one, because the registry stores dict[str, type] and the overloads re-assert the mapping where no checker can reach it. | M |
| P13 | CORE-036 | Routes are unversioned, so no client can be shipped to a device | A native app in a store runs against whatever the server becomes, and today there is no version in any URL and no way to tell an old client it is old. | M |
| P13 | CORE-056 | Every scaffolded product ships a failing /readyz test, so examples-gate is red for all of them | CORE-054 gave /readyz a query over the product's declared tables and updated the template's own stub session to answer it. skeleton/tests/test_health.py's _StubSession still returns None, so the healthy case raises TypeError, answers 503, and every product scaffolded from main fails its own test suite. | S |
| P13 | CORE-057 | Free is currently the absence of billing rather than a setting, so it cannot be flipped or tested | An absence cannot be flipped and cannot be tested, and the whole free-beta model depends on the flip being one setting that has been exercised. | M |
| P13 | DOC-001 | No document carries a provenance block | No document in the repo records what validated it, so nothing can tell a description of the code from a description of an intention. | M |
| P13 | DOC-004 | Directory READMEs and CLAUDE.md files | An agent editing a directory has to read sixteen global rules to find the one that governs it. | M |
| P13 | DOC-005 | nothing says where product code goes, or what the template has no opinion about | No document in the repo says where a founder's own product code goes, and the scaffold's only stated patterns are component-shaped. | S |
| P13 | DOC-006 | the pinned product gate counts are stale in the pessimistic direction | DEV-CHECKLIST and README pin product format-check and typecheck as failing when both are green, and README calls under-promising the same defect as over-promising. | S |
| P13 | DOC-009 | Four files still say saas new copies the lenses into every product | The review layer became opt in and four documents outside the trim's scope still assert the old behaviour, including the one that promises a product gets the template's review capability. | S |
| P13 | DOC-017 | reviews/verification-baseline.md is status current, wrong on half its rows, and exempt from the staleness gate by directory | docs/VERIFICATION.md sends every reader to this file for "the current failing output", and six of its twelve summary rows disagree with make report at HEAD, while check_provenance.py skips all of reviews/ as dated transcripts. | S |
| P13 | DOC-024 | The deploy tail has four different numbers and the asserted one is outvoted | tests/deploy_walk.py computes a 14-step tail with 10 unexecuted, and docs/QUEUE.md says 31 with 11, docs/DEVELOPING.md says 15 with 11, and docs/PATHS.md says 26 with 9, so the headline metric has four spellings and the asserted one is in the minority. | S |
| P13 | DOC-038 | Two of the three priority inputs measure one axis, so half the register lands in P1 | value and importance are never more than one level apart on any of the 102 scored tickets and agree exactly on 65% of them, so points is effectively 2*value+urgency and 57 of 107 priced tickets land in one band. | M |
| P13 | DOC-070 | A real-usage review that drives the product and takes screenshots | He asked on 2026-09-14 and in substance on 2026-09-11. The one screenshot-based review that exists was done by hand, no lens requires it, and the word screenshot appears nowhere in backlog. lenses/README.md's own opening argument is that reading cannot detect absence of execution, and the visual half of the product is the part nothing executes. | L |
| P13 | DOC-077 | Why 22 gates and 2139 tests missed seven live defects | The owner was told the template is roughly 75% of the way to production-grade and answered "I think there's a shitton of bugs an mistakes in th ecode, so I'd prolly say 56% or so". The rate of finding defects had not dropped in two days, and nothing had classified the roughly one hundred found defects by what actually discovered each one. | M |
| P13 | DOC-080 | BLOCKED-ON-YOU.md promises four fields per entry and 2 of 49 carry them | the owner wants what is blocked on him queryable with its context, and the file already states the format it does not keep | M |
| P13 | OPS-012 | The teardown proves nothing about volumes, because it destroys the app and then asks the app for its volumes | A forgotten volume bills with no app attached to it, and both the script and the service guide check for one in a way that cannot report anything. | S |
| P13 | OPS-016 | make dev does not run the answerability check that two other commands have | saas doctor and saas deploy local both refuse a borrowed port and name the process holding it, and make dev, the command that suffers the defect, runs neither - reproduced at edd465d with two IPv4 listeners on one port. | S |
| P13 | OPS-035 | Nothing configures encryption at rest, and the only backup is a manual pg_dump with no schedule, no encryption and no retention | CC.06.24 wants disk-level encryption on databases, storage and backups and CC.06.11 wants restricted backup access, and the level 2 path puts Postgres on a docker volume on an unencrypted root disk whose only backup is an operator typing a command. | M |
| P13 | OPS-039 | Dependabot alerts are off, and no update configuration exists in either face | A vulnerability feed that is switched off reports nothing and looks identical to one reporting no vulnerabilities, which is the green-on-unverified-state shape this repo keeps finding. | S |
| P13 | OPS-042 | Nothing proves the whole chain, because no automated run goes from empty directory to a deployed URL | Every existing test covers a piece of the path, and the one defect that mattered most lived in the joins between them. | M |
| P13 | OPS-052 | A sign-in token is in history and not in the tree, and the history scan is red on it | The working-tree scan is clean and the history scan is not, which is the whole reason the two are separate gates - and the decision about what to do with a committed credential is the owner's, not an agent's, because both available answers cost something irreversible. | S |
| P13 | OPS-056 | The negative control proving the migration lock is load-bearing did not break in CI, so the lock is unproven there | On CI's first run the inverted race reported "without the lock all four runners still passed", so the negative control that exists to prove the advisory lock is load-bearing proves nothing on a GitHub runner. | M |
| P13 | OPS-061 | The fly.io account is past its trial, so nothing in this repository can walk a deploy any more | flyctl answers every call, fly apps list and fly apps create included, with Error: trial has ended, please add a credit card. So scripts/deploy/flyio.sh up cannot run, and the deploy path this repository has walked twice - and which four contracts take criteria from - cannot be walked again by anybody here. | S |
| P13 | OPS-080 | a downgrade that deletes rows in raw SQL is costed at zero | drops_in_downgrade reads only drop_column and drop_table, so a downgrade() whose body is op.execute('DELETE FROM ...') is costed at zero rows and assert_rollback_is_lossless prints 'nothing in those downgrade() bodies drops a table or a column, so no row is destroyed by undoing them' before destroying them. OPS-078 inverted the upgrade direction to an allowlist; the rollback direction still reads two verbs. | S |
| P13 | OPS-082 | every gate runs saas from an interpreter no founder has | cli/gates/scaffold.py:197 and cli/examples.py:278 both invoke the CLI as [sys.executable, '-m', 'cli.main'] out of .venv-verify, which carries the whole product stack. README.md tells a founder to uv tool install --editable, whose venv carries the CLI's five declared dependencies and nothing else. So every end-to-end gate exercises saas in an environment no user has, and OPS-077 - saas new writing no migration for anybody who followed the README - was green under all of them for its whole life. | M |
| P13 | OPS-101 | jobs is installed, four schedules are registered, and the product's own deployment runs no worker | Installing jobs beside auth registers four hourly sweeps, and docker-compose.yml has no worker service while README.md and CLAUDE.md never mention make worker - so saas deploy local runs a product whose retention never runs and nothing notices. | M |
| P13 | OPS-120 | walk.py's three shapes no longer describe what saas new produces, so fifteen and nine are both wrong | make walk-benchmark is red on main and nothing in the gate ledger says so. saas new installs invite by default, whose dependency closure is auth, authz and webauth, so the core-only shape is not core-only and the core+auth shape's saas add auth changes nothing. Two necessity probes are firing correctly and the step count they guard has to come down, which makes docs/DEV-CHECKLIST.md's fifteen and nine both wrong and section A step 6's "core is already in" wrong with them. | M |
| P13 | OPS-134 | A product's ruff format rewrites the python inside its own markdown, and format-check is red on it | skeleton/pyproject.toml.j2 has no [tool.ruff.format] table, so a product's make format-check is red on any markdown file holding a python fence and make format silently rewrites the evidence in it. The template excluded *.md from the formatter for exactly this reason and the kit never got the change, which is OPS-092's shape again. | S |
| P13 | OPS-137 | A shared resource with no declared separation is refused, not noted | adr-013 allows a product to share tier-two infrastructure only if the separation carries its own command, its measured cost and what is unavailable while it runs. Without a registry declaring those, the tier is prose and no gate can read it - the same reason personal_data.py, egress.py and SHELL_CONTEXT are declarations beside the code rather than documents describing the code. | M |
| P13 | OPS-138 | A scaffolded product must reference none of the factory's infrastructure | adr-013 says standalone has to mean something checkable rather than a claim in a README. A product tree containing an account id, an org-level resource name, a runner group or a bucket it does not own is coupled to the factory whatever the documentation says. This is the property that makes the whole decision enforceable, and it is decidable by reading a scaffolded tree. | M |
| P13 | OPS-140 | The shipped restore verb cannot restore into a destroyed database, and its prompt says it can | the first restore ever run in this project found two cases where scripts/deploy/hetzner.sh restore refuses, and one of the two is the case the verb exists for - a box that is gone. Both were measured against a scaffolded product on 2026-09-16 and both are in docs/RESTORE.md. | S |
| P13 | OPS-141 | A scaffolded product has no backup command, so the only one lives in the factory | adr-013 Tier 3 says a product is a copy of the code with no link back, and the only backup and restore verbs in this repository are scripts/deploy/hetzner.sh, which never travels. A standalone product can rehearse a restore since OPS-140 and still cannot take a backup with a command it owns. | M |
| P13 | PROD-003 | Shared phone book | The smallest product that needs group-scoped authorization, so it forces the thing AUTH-008 describes and scratchpad could not reach. | L |
| P13 | PROD-005 | Nobody can be removed from a shared phone book, so the half of the invariant about losing a role has no caller | PROD-003 grants roles and never takes one away, so a demoted admin and a removed member are both unreachable through the API and the three attacks about them cannot be written. | M |
| P13 | PROD-007 | File drop, the smallest product whose erasure has to delete bytes | components/files has no caller in any example. The interesting half is not the upload, it is that erase_subject must reach object storage and not only rows, which is declared as the indirect disposition and proven only at template level. | L |
| P13 | PROD-009 | TimeMachine, the session lifecycle a stopwatch does not have | Start, pause, resume, stop and abort are the whole product, and abort is the one a stopwatch has never had. Every route here derives the session from the caller and takes no id, which is PROD-002's argument applied to a row that is identified by being open rather than by being owned. | M |
| P13 | PROD-011 | TimeMachine, one open session across devices that were offline | This is the rung. A timer runs while a phone is in a tunnel, and two devices can both believe a session is open without anybody doing anything wrong, so PROD-006's invariant is violated by physics rather than by an attacker. No product on the ladder has had a uniqueness constraint that a network partition can break, and adr-004 has never had a caller that needed one. | L |
| P13 | PROD-023 | The shop's address is still one string, and the fields it should be exist now | COMP-059 built the fields and declared them. Until the shop moves onto them, the only product that has the problem still has it. | M |