You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Derived from the 2026-08-07 audit in #21, which remains the backlog record. This PRD covers all four audit workstreams and resolves the open decisions that #21 deliberately left unanswered.
Problem Statement
tstack-on-cf is a public, reusable template. Someone clones it expecting a starting point that is already correct — so every gap in it is inherited by every project built from it, and every stale sentence in its docs is a lie told at scale.
Today a person who clones this template hits four classes of problem:
They cannot trust what it tells them. The README shows a compatibility date that no longer matches the real config, promises per-environment migration folders that were never created, and links a LICENSE file that does not exist despite claiming MIT. The agent instructions declare a /docs directory the single source of truth for business requirements; that directory is not in the repo. A human skims past this. An AI coding agent — the audience this template explicitly courts — follows the instruction, finds nothing, and either stalls or invents.
Nothing catches a bad change. There is no CI on pull requests. Lint, type-check, tests, unused-code detection, and the build all run only on a maintainer's laptop or after merge. The dependency bots and the compatibility-date bot open pull requests into this repo that nothing validates before merge.
A workaround has calcified into the toolchain. Type generation currently infers the Worker's secret names from a gitignored local file, so it cannot run anywhere that file is absent. Every workflow disables git hooks to route around this, and one workflow skips typegen outright with a comment explaining why. The workaround is load-bearing, which means the type-safety guarantee it protects is not actually enforced anywhere automated.
The tests do not exercise the thing being shipped. Roughly 27 cases across 10 files, all of which assert on configuration and documentation. Nothing runs inside the Workers runtime, so the request dispatch that splits API traffic from application traffic, the environment wiring, and server-side rendering are never executed under test. Component testing libraries are installed and unused, hidden from the unused-dependency checker by an ignore list that also conceals genuinely dead packages.
Underneath all four: production stack traces arrive minified because source map upload was never switched on, and the deployed environments have no stated routing or public-URL posture at all.
Solution
Harden the template along the axis that matters for a template specifically: claims the repository makes about itself are mechanically enforced, and the paths a cloner will actually walk are either working or honestly labelled.
Concretely, from the cloner's perspective:
Docs that cannot rot. Four new invariant tests fail the build when the README drifts from real configuration, when a documented pointer resolves to nothing, when the declared secrets diverge from the example environment file, or when promised migration directories are missing. The repository already uses this pattern in three places; this extends it to every claim the audit found broken. Fix the drift once, then make that class of drift impossible.
A gate on every pull request. One required check runs lint, types, tests, unused-code detection, and a real build — including on the bots' own pull requests.
Secrets declared in configuration, not inferred from a local file. Wrangler's secrets property (shipped March 2026) takes the declared secret names as the source of truth for type generation, so typegen runs anywhere. The hook-disabling workarounds are deleted from all three workflows. Because deploys stay manual, this lands a second benefit: deploy now fails with a named list if a required secret was never set on the Worker — the exact mistake a laptop-driven deploy invites.
Deploys stay manual, and the manual path gets written down. No CD pipeline. Instead: an explicit, ordered release runbook covering the migration gate, environment-scoped builds, gradual rollout, and rollback — so the fact that a human drives deploys is a documented choice rather than an undocumented gap.
Tests that run in the real runtime. A two-project test setup: one running the Worker entry point and API inside workerd, one running components under a DOM. The installed-but-unused testing libraries become used; the genuinely dead packages get removed and the ignore list shrinks to match reality.
Decisions recorded instead of re-litigated. The /docs directory stops being a dead pointer by becoming the home for three decision records: why the fetch-based Postgres driver is kept over Hyperdrive, why Smart Placement ships off with instructions to measure, and which AI agent tools are actually supported.
A visible security posture. The demo API is public unauthenticated CRUD. The factory that builds API routes gains a documented middleware seam so a cloner sees exactly where authentication attaches, and the README states plainly that the demo surface must not ship as-is.
User Stories
Cloning and first run
As someone cloning the template, I want the README's configuration snippets to match the repository's actual configuration, so that I do not copy a stale compatibility date into my project.
As someone cloning the template, I want every file and directory the docs reference to exist, so that following the setup instructions does not dead-end.
As someone cloning the template, I want a LICENSE file present when the README claims MIT, so that I know the terms under which I am allowed to use this.
As someone cloning the template, I want the required Node version pinned in the repository, so that I do not debug failures caused by running a different runtime than the maintainer.
As someone cloning the template, I want the example environment file named by the convention the tooling expects, so that I do not have to reverse-engineer which file becomes which.
As someone cloning the template, I want the workspace configuration file to explain why it exists, so that I do not assume this is a monorepo and structure my project wrongly.
As someone cloning the template, I want the list of secrets my Worker needs declared in one obvious place, so that I know what to provision before my first deploy.
As someone cloning the template, I want documentation of which migration directories exist versus which are created on demand, so that my first environment promotion is not a surprise.
Contributing and changing code
As a contributor, I want every pull request to run lint, type-check, tests, unused-code detection, and a build, so that a broken change cannot reach the default branch.
As a contributor, I want the automated dependency and compatibility-date pull requests to pass the same checks as mine, so that bot merges are as safe as human ones.
As a contributor, I want type generation to succeed on a fresh checkout with no local secrets file, so that I can validate my change without first obtaining credentials.
As a maintainer, I want the hook-disabling environment variable removed from all workflows, so that automation runs the same verification a developer's machine runs.
As a maintainer, I want the deliberate typegen skip removed from the compatibility-date workflow, so that the bot's pull requests carry regenerated types when configuration changes.
As a contributor, I want the unused-code checker's ignore list to reflect genuine exceptions only, so that its passing result means something.
As a contributor, I want the database and error-handling directories no longer blanket-excluded from unused-code analysis, so that dead code there is surfaced rather than hidden.
Documentation integrity
As a maintainer, I want a test that fails when the README's configuration snippet drifts from the real configuration, so that this specific drift cannot recur.
As a maintainer, I want a test that fails when any documented pointer in the agent instructions or README does not resolve, so that dead references are caught at build time rather than by a confused reader.
As a maintainer, I want a test that fails when the declared required secrets and the example environment file disagree, so that a cloner is never handed an incomplete template.
As a maintainer, I want a test that fails when documented per-environment migration directories do not match the configured environments, so that the docs and the filesystem cannot diverge.
As an AI coding agent working in this repository, I want the declared source of truth for requirements to exist and contain something, so that I can follow the instruction I was given instead of guessing.
As an AI coding agent, I want a stated matrix of which agent tools this template supports and at what level, so that I do not assume parity that is not there.
Cloudflare conformance
As an operator debugging a production incident, I want source maps uploaded with the Worker, so that stack traces in logs point at real source lines instead of minified output.
As someone deploying the template, I want each environment to state its public-URL and routing posture explicitly, so that I know whether an environment is reachable and how, rather than discovering it after deploy.
As someone deploying the template, I want commented routing placeholders following the repository's own deployment guidance, so that I can enable a custom domain by filling in a blank rather than researching the syntax.
As someone adopting the template, I want Smart Placement present but off, with an explanation of when to enable it and how to measure the result, so that I make a deliberate choice rather than inheriting one that may not fit my database's location.
As someone evaluating the template, I want a written record of why the fetch-based Postgres driver was chosen over the platform's connection-pooling product, so that I can judge whether that reasoning applies to me.
As someone deploying manually, I want deploy to fail with a named list when a required secret is unset, so that I cannot ship an environment that will fail on its first database call.
Deploying and operating
As a maintainer, I want an ordered release runbook covering migration, build, and deploy per environment, so that manual deploys are repeatable rather than remembered.
As an operator, I want documented rollback guidance, so that I can recover from a bad deploy without improvising under pressure.
As an operator, I want documented gradual-rollout guidance, so that I can ship a risky change to a fraction of traffic first.
As a maintainer, I want the manual migration gate documented as a deliberate decision, so that its absence from automation is understood as a choice rather than read as an oversight.
As someone adopting the template, I want the security posture of the demo API stated plainly in the README, so that I do not ship public unauthenticated write access by accident.
As someone adopting the template, I want a documented middleware seam in the API factory, so that I know exactly where to attach authentication without restructuring the code.
Testing
As a maintainer, I want the Worker entry point's request dispatch tested inside the Workers runtime, so that the split between API and application traffic is verified against real runtime behaviour.
As a maintainer, I want the API routes tested through real HTTP requests in the runtime, so that tests exercise the module boundary rather than its internals.
As a maintainer, I want environment binding wiring exercised under test, so that a misconfigured binding fails in CI rather than in production.
As a maintainer, I want at least one component test running under a DOM, so that the installed testing libraries are proven to work for anyone who builds on them.
As a contributor, I want both test projects to run from the single existing test command, so that there is one way to verify the repository.
As a maintainer, I want genuinely unused packages removed rather than concealed, so that install time and audit surface reflect what the template actually uses.
Staying current
As a maintainer, I want a monthly automated report of available major upgrades in a single tracking issue, so that majors are visible without the weekly minor-bump bot creating noise.
As a maintainer, I want that report to update the existing issue rather than open a new one each month, so that the backlog does not accumulate duplicates.
As a maintainer, I want the currently pending majors captured by that mechanism rather than hand-tracked, so that the tracking survives my attention lapsing.
As a maintainer, I want strict import-type enforcement enabled in the compiler to match what the linter already requires, so that the two tools agree.
Implementation Decisions
Repository identity. This is a public reusable template. Where a decision trades cloner ergonomics against maintainer convenience, cloner ergonomics wins; where it trades opinionation against neutrality, the template ships the safe default and documents the opinionated path.
Deployment model — manual, documented. No continuous deployment. Deploys remain driven by a human with local platform authentication. This is a deliberate reversal of the audit's proposal: for a template, a CD pipeline encodes account structure, secret names, and branch conventions that no cloner shares, and it cannot be exercised in the template's own repository without real infrastructure. The gap the audit identified is real but is a documentation gap, so it is closed with a runbook. Continuous integration on pull requests is unaffected and in scope.
Secrets as declared configuration. The Wrangler secrets property is the single source of truth for the Worker's required secret names. This was selected over hand-maintaining an environment interface in a committed type declaration file because it removes rather than relocates the duplication, and because it carries two capabilities the hand-written approach does not: it constrains which keys local development loads, and it validates secret presence at deploy time. The latter is disproportionately valuable given deploys are manual. Verified against current platform documentation during this PRD's preparation; per-environment secrets are supported, with secrets present in only some environments typed as optional.
Consequences of the secrets decision. Type generation no longer depends on any gitignored file, so the hook-disabling variable is removed from all three workflows and the compatibility-date workflow's typegen skip is deleted. These deletions are the acceptance signal for this decision, not incidental cleanup.
Database driver — keep the fetch-based driver, record the reasoning. The connection-pooling product is the platform's standard answer for external Postgres, but it requires every cloner to provision an additional resource before the template runs, and the fetch-based driver has no such prerequisite. For a template, prerequisite count dominates. This is recorded as a decision document rather than left implicit, including the conditions under which a cloner should switch.
Smart Placement — present, disabled, documented. The reference configuration points at a region-pinned database, which is the canonical case for enabling it. But a cloner's database may be elsewhere, and their application may not be round-trip bound, in which case the setting is neutral at best. It ships commented with guidance on when to enable and how to measure, rather than as an inherited default.
Routing posture — explicit per environment. Each deployed environment states its public-URL and preview-URL posture rather than leaving it to platform defaults, with commented custom-domain placeholders following the repository's existing deployment guidance, which already prefers custom domains over route patterns because they provision DNS and certificates automatically.
Documentation integrity as executable tests. All four proposed invariants are implemented as tests, extending a pattern already present in the repository three times over. This is the PRD's highest-leverage component: it converts one-time fixes into permanent guarantees, and it is the only mechanism that prevents a template's documentation from decaying between maintainer visits. Each test fixes its corresponding drift as a precondition.
The requirements directory becomes real. Rather than deleting the claim that it is the source of truth, the directory is created and immediately earns its place by housing the three decision records produced by this PRD. This resolves the dead pointer and gives the decisions a home in one move.
Agent support — declared, not mirrored. A support matrix states which agent tooling is first-class, which is best-effort, and which is unsupported. Mirroring agent definitions across tools was rejected: it creates a permanent synchronisation obligation for a secondary audience, and an honest matrix serves a cloner better than partial parity that silently rots.
Authentication — seam and statement, no implementation. The API factory gains a documented middleware attachment point and the README gains a security-posture section. No working authentication is implemented: the demo routes are scaffolding a cloner removes, and shipping a token check invites it being mistaken for production-grade. The seam plus the warning gives a cloner the pattern without the false assurance.
Test topology — two projects, one command. Runtime-level tests for the Worker entry point and API run in the real Workers runtime via the platform's Vitest pool; component tests run under a DOM. Both are driven by the existing single test command. This uses the DOM and component-testing packages already installed, so only the packages that remain genuinely unused after this change are removed. Route files stay excluded from test discovery as they are today.
Unused-code ignore list shrinks to genuine exceptions. Generated UI primitives remain excluded. Blanket exclusions of the database and error-handling directories are removed so that dead code there becomes visible.
Major upgrades — monthly, single issue. A scheduled job reports available majors into one tracking issue, updating it in place. Monthly was chosen over weekly to keep majors distinct from the existing weekly minor-bump automation, and over quarterly so that a pending major is never more than a month invisible. Reporting only — the job never opens upgrade pull requests.
Node version pinned in two places. Both the package manifest's engine constraint and a version file, so that both tooling that reads the manifest and tooling that reads the version file agree.
Validation Strategy
Component-level definitions of done
Documentation integrity harness. Done when all four invariant tests exist, each one fails against the pre-fix state of the repository, and each passes after its corresponding fix. Demonstrating the failure first is required — a test that has never been red does not prove it is wired to anything. Verifies stories 1–3, 16–19.
Secrets and typegen contract. Done when type generation produces byte-identical output with the local secrets file present and absent, no workflow sets the hook-disabling variable, and the compatibility-date workflow no longer skips typegen. The identical-output check is the acceptance test; the workflow deletions are the observable consequence. Verifies stories 7, 11–13, 27.
Continuous integration gate. Done when a deliberately broken pull request — one lint failure, one type error, one failing test — is blocked, and when a bot-authored pull request shows the same checks. Verifies stories 9, 10.
Test runtime split. Done when the single test command runs both projects, at least one test executes the Worker entry point's dispatch inside the real runtime and asserts that API and application traffic diverge correctly, at least one API test issues a real HTTP request, and at least one component test renders under a DOM. Coverage counts are not a criterion; running in the correct runtime is. Verifies stories 34–38.
Platform configuration. Done when source map upload is enabled, each deployed environment states its public-URL and routing posture, Smart Placement is present and commented with measurement guidance, and the configuration invariant test covers the new properties. Verifies stories 22–25.
Release runbook. Done when a maintainer can follow the documented sequence for one environment end to end without consulting external documentation, and when rollback and gradual-rollout procedures are each documented with the command that performs them. Verifies stories 28–31.
Security posture. Done when the API factory exposes a documented middleware attachment point exercised by at least one test proving the seam is invoked, and the README states the demo API's posture explicitly. Verifies stories 32, 33.
Decision records. Done when three decision documents exist — driver choice, placement posture, agent support matrix — each stating the decision, the reasoning, and the conditions under which a cloner should decide differently. Verifies stories 20, 21, 26.
Dependency hygiene. Done when unused-code detection passes with an ignore list containing only generated UI primitives, and no package remains in the manifest without a usage or a documented reason. Verifies stories 14, 15, 39.
Upgrade cadence. Done when the scheduled job has run at least once, produced a tracking issue listing the currently pending majors, and a second run has updated that issue in place rather than opening a duplicate. Verifies stories 40–42.
Repository-wide acceptance
The existing verification commands — type-check, tests, lint — pass, plus unused-code detection and a production build.
No source file exceeds the repository's 500-line limit.
Every claim in the README and agent instructions either resolves or is covered by an invariant test.
A fresh clone reaches a running development server following only the documented setup steps, with no step requiring knowledge absent from the repository.
Quality thresholds
Documentation invariant tests must fail before their fix lands. Untested tests are not accepted.
Type generation determinism is binary: identical output regardless of local file presence, or the component is not done.
The unused-code ignore list is capped at generated UI primitives. Additions require a written justification in the configuration.
Out of Scope
Continuous deployment. Explicitly rejected above, not deferred. Deploys stay manual by decision.
Executing the pending major upgrades. The build tool, linter, and TypeScript compiler majors are tracked by the new cadence, not performed here. The cadence's first report is the deliverable; acting on it is separate work.
Migrating to the connection-pooling database product. Decided against and recorded; revisiting requires new evidence, not a new PRD.
Real authentication. Only the middleware seam and the written posture are in scope. No token validation, session handling, or identity provider integration.
Rate limiting and cross-origin configuration. Considered alongside the authentication seam and deliberately excluded to keep the security surface a documented seam rather than a partial implementation.
Mirroring agent definitions to secondary tooling. Replaced by the support matrix.
New product features or user interface work. The demo surface is scaffolding; it is not extended.
Per-environment migration directories for staging and production. Only the development directory exists; the invariant test enforces that documentation matches reality rather than forcing directories into existence before an environment is provisioned.
Further Notes
On why documentation tests are the centrepiece. Every other item here is a one-time fix that a maintainer could have made without a PRD. The invariant tests are the only component that changes the repository's future rather than its present. A template is read far more often than it is edited, and its docs decay silently because nothing exercises them. Four tests convert the audit from a snapshot into a standing guarantee — which is why they are specified with the requirement that each be demonstrated failing first.
On reversing the audit's CD recommendation.#21 proposed a staged CD pipeline as a P0 item. That was correct reasoning for an application and wrong for a template: the pipeline would encode account identifiers, secret names, and branch conventions specific to one deployment, and it could not be meaningfully exercised in the template repository itself. The underlying concern — that the path to production is undocumented — is fully addressed by the runbook. Continuous integration was retained at P0 because it validates the repository's own contents, which is exactly what a template needs.
On the secrets decision's second-order value. Deploy-time secret validation was not what the audit was asking for; it was asking to unblock type generation in CI. That the same configuration property also prevents a manual deploy from shipping an environment with unset secrets makes it strictly better than the hand-written alternative, and it partially compensates for the deliberate absence of a deployment pipeline.
On sequencing. The secrets contract should land first — it unblocks the bot workflows and removes a workaround that would otherwise have to be carried through every other change. The documentation invariant tests should land alongside their corresponding fixes, never after, so that each fix ships with its guard. The test runtime split is the largest single piece of work and is independent of everything else; it can proceed in parallel.
On the requirements directory. Creating it and immediately filling it with the three decision records is deliberate. An empty directory created solely to satisfy a pointer would be a second lie in place of the first.
Supersedes nothing.#21 stays open as the audit record and backlog. This PRD resolves its open decisions; it does not replace its findings.
PRD: Template hardening — Cloudflare conformance, deploy readiness, agent-truthful docs, real-runtime tests
Derived from the 2026-08-07 audit in #21, which remains the backlog record. This PRD covers all four audit workstreams and resolves the open decisions that #21 deliberately left unanswered.
Problem Statement
tstack-on-cfis a public, reusable template. Someone clones it expecting a starting point that is already correct — so every gap in it is inherited by every project built from it, and every stale sentence in its docs is a lie told at scale.Today a person who clones this template hits four classes of problem:
They cannot trust what it tells them. The README shows a compatibility date that no longer matches the real config, promises per-environment migration folders that were never created, and links a LICENSE file that does not exist despite claiming MIT. The agent instructions declare a
/docsdirectory the single source of truth for business requirements; that directory is not in the repo. A human skims past this. An AI coding agent — the audience this template explicitly courts — follows the instruction, finds nothing, and either stalls or invents.Nothing catches a bad change. There is no CI on pull requests. Lint, type-check, tests, unused-code detection, and the build all run only on a maintainer's laptop or after merge. The dependency bots and the compatibility-date bot open pull requests into this repo that nothing validates before merge.
A workaround has calcified into the toolchain. Type generation currently infers the Worker's secret names from a gitignored local file, so it cannot run anywhere that file is absent. Every workflow disables git hooks to route around this, and one workflow skips typegen outright with a comment explaining why. The workaround is load-bearing, which means the type-safety guarantee it protects is not actually enforced anywhere automated.
The tests do not exercise the thing being shipped. Roughly 27 cases across 10 files, all of which assert on configuration and documentation. Nothing runs inside the Workers runtime, so the request dispatch that splits API traffic from application traffic, the environment wiring, and server-side rendering are never executed under test. Component testing libraries are installed and unused, hidden from the unused-dependency checker by an ignore list that also conceals genuinely dead packages.
Underneath all four: production stack traces arrive minified because source map upload was never switched on, and the deployed environments have no stated routing or public-URL posture at all.
Solution
Harden the template along the axis that matters for a template specifically: claims the repository makes about itself are mechanically enforced, and the paths a cloner will actually walk are either working or honestly labelled.
Concretely, from the cloner's perspective:
Docs that cannot rot. Four new invariant tests fail the build when the README drifts from real configuration, when a documented pointer resolves to nothing, when the declared secrets diverge from the example environment file, or when promised migration directories are missing. The repository already uses this pattern in three places; this extends it to every claim the audit found broken. Fix the drift once, then make that class of drift impossible.
A gate on every pull request. One required check runs lint, types, tests, unused-code detection, and a real build — including on the bots' own pull requests.
Secrets declared in configuration, not inferred from a local file. Wrangler's
secretsproperty (shipped March 2026) takes the declared secret names as the source of truth for type generation, so typegen runs anywhere. The hook-disabling workarounds are deleted from all three workflows. Because deploys stay manual, this lands a second benefit: deploy now fails with a named list if a required secret was never set on the Worker — the exact mistake a laptop-driven deploy invites.Deploys stay manual, and the manual path gets written down. No CD pipeline. Instead: an explicit, ordered release runbook covering the migration gate, environment-scoped builds, gradual rollout, and rollback — so the fact that a human drives deploys is a documented choice rather than an undocumented gap.
Tests that run in the real runtime. A two-project test setup: one running the Worker entry point and API inside workerd, one running components under a DOM. The installed-but-unused testing libraries become used; the genuinely dead packages get removed and the ignore list shrinks to match reality.
Decisions recorded instead of re-litigated. The
/docsdirectory stops being a dead pointer by becoming the home for three decision records: why the fetch-based Postgres driver is kept over Hyperdrive, why Smart Placement ships off with instructions to measure, and which AI agent tools are actually supported.A visible security posture. The demo API is public unauthenticated CRUD. The factory that builds API routes gains a documented middleware seam so a cloner sees exactly where authentication attaches, and the README states plainly that the demo surface must not ship as-is.
User Stories
Cloning and first run
Contributing and changing code
Documentation integrity
Cloudflare conformance
Deploying and operating
Testing
Staying current
Implementation Decisions
Repository identity. This is a public reusable template. Where a decision trades cloner ergonomics against maintainer convenience, cloner ergonomics wins; where it trades opinionation against neutrality, the template ships the safe default and documents the opinionated path.
Deployment model — manual, documented. No continuous deployment. Deploys remain driven by a human with local platform authentication. This is a deliberate reversal of the audit's proposal: for a template, a CD pipeline encodes account structure, secret names, and branch conventions that no cloner shares, and it cannot be exercised in the template's own repository without real infrastructure. The gap the audit identified is real but is a documentation gap, so it is closed with a runbook. Continuous integration on pull requests is unaffected and in scope.
Secrets as declared configuration. The Wrangler
secretsproperty is the single source of truth for the Worker's required secret names. This was selected over hand-maintaining an environment interface in a committed type declaration file because it removes rather than relocates the duplication, and because it carries two capabilities the hand-written approach does not: it constrains which keys local development loads, and it validates secret presence at deploy time. The latter is disproportionately valuable given deploys are manual. Verified against current platform documentation during this PRD's preparation; per-environment secrets are supported, with secrets present in only some environments typed as optional.Consequences of the secrets decision. Type generation no longer depends on any gitignored file, so the hook-disabling variable is removed from all three workflows and the compatibility-date workflow's typegen skip is deleted. These deletions are the acceptance signal for this decision, not incidental cleanup.
Database driver — keep the fetch-based driver, record the reasoning. The connection-pooling product is the platform's standard answer for external Postgres, but it requires every cloner to provision an additional resource before the template runs, and the fetch-based driver has no such prerequisite. For a template, prerequisite count dominates. This is recorded as a decision document rather than left implicit, including the conditions under which a cloner should switch.
Smart Placement — present, disabled, documented. The reference configuration points at a region-pinned database, which is the canonical case for enabling it. But a cloner's database may be elsewhere, and their application may not be round-trip bound, in which case the setting is neutral at best. It ships commented with guidance on when to enable and how to measure, rather than as an inherited default.
Routing posture — explicit per environment. Each deployed environment states its public-URL and preview-URL posture rather than leaving it to platform defaults, with commented custom-domain placeholders following the repository's existing deployment guidance, which already prefers custom domains over route patterns because they provision DNS and certificates automatically.
Documentation integrity as executable tests. All four proposed invariants are implemented as tests, extending a pattern already present in the repository three times over. This is the PRD's highest-leverage component: it converts one-time fixes into permanent guarantees, and it is the only mechanism that prevents a template's documentation from decaying between maintainer visits. Each test fixes its corresponding drift as a precondition.
The requirements directory becomes real. Rather than deleting the claim that it is the source of truth, the directory is created and immediately earns its place by housing the three decision records produced by this PRD. This resolves the dead pointer and gives the decisions a home in one move.
Agent support — declared, not mirrored. A support matrix states which agent tooling is first-class, which is best-effort, and which is unsupported. Mirroring agent definitions across tools was rejected: it creates a permanent synchronisation obligation for a secondary audience, and an honest matrix serves a cloner better than partial parity that silently rots.
Authentication — seam and statement, no implementation. The API factory gains a documented middleware attachment point and the README gains a security-posture section. No working authentication is implemented: the demo routes are scaffolding a cloner removes, and shipping a token check invites it being mistaken for production-grade. The seam plus the warning gives a cloner the pattern without the false assurance.
Test topology — two projects, one command. Runtime-level tests for the Worker entry point and API run in the real Workers runtime via the platform's Vitest pool; component tests run under a DOM. Both are driven by the existing single test command. This uses the DOM and component-testing packages already installed, so only the packages that remain genuinely unused after this change are removed. Route files stay excluded from test discovery as they are today.
Unused-code ignore list shrinks to genuine exceptions. Generated UI primitives remain excluded. Blanket exclusions of the database and error-handling directories are removed so that dead code there becomes visible.
Major upgrades — monthly, single issue. A scheduled job reports available majors into one tracking issue, updating it in place. Monthly was chosen over weekly to keep majors distinct from the existing weekly minor-bump automation, and over quarterly so that a pending major is never more than a month invisible. Reporting only — the job never opens upgrade pull requests.
Node version pinned in two places. Both the package manifest's engine constraint and a version file, so that both tooling that reads the manifest and tooling that reads the version file agree.
Validation Strategy
Component-level definitions of done
Documentation integrity harness. Done when all four invariant tests exist, each one fails against the pre-fix state of the repository, and each passes after its corresponding fix. Demonstrating the failure first is required — a test that has never been red does not prove it is wired to anything. Verifies stories 1–3, 16–19.
Secrets and typegen contract. Done when type generation produces byte-identical output with the local secrets file present and absent, no workflow sets the hook-disabling variable, and the compatibility-date workflow no longer skips typegen. The identical-output check is the acceptance test; the workflow deletions are the observable consequence. Verifies stories 7, 11–13, 27.
Continuous integration gate. Done when a deliberately broken pull request — one lint failure, one type error, one failing test — is blocked, and when a bot-authored pull request shows the same checks. Verifies stories 9, 10.
Test runtime split. Done when the single test command runs both projects, at least one test executes the Worker entry point's dispatch inside the real runtime and asserts that API and application traffic diverge correctly, at least one API test issues a real HTTP request, and at least one component test renders under a DOM. Coverage counts are not a criterion; running in the correct runtime is. Verifies stories 34–38.
Platform configuration. Done when source map upload is enabled, each deployed environment states its public-URL and routing posture, Smart Placement is present and commented with measurement guidance, and the configuration invariant test covers the new properties. Verifies stories 22–25.
Release runbook. Done when a maintainer can follow the documented sequence for one environment end to end without consulting external documentation, and when rollback and gradual-rollout procedures are each documented with the command that performs them. Verifies stories 28–31.
Security posture. Done when the API factory exposes a documented middleware attachment point exercised by at least one test proving the seam is invoked, and the README states the demo API's posture explicitly. Verifies stories 32, 33.
Decision records. Done when three decision documents exist — driver choice, placement posture, agent support matrix — each stating the decision, the reasoning, and the conditions under which a cloner should decide differently. Verifies stories 20, 21, 26.
Dependency hygiene. Done when unused-code detection passes with an ignore list containing only generated UI primitives, and no package remains in the manifest without a usage or a documented reason. Verifies stories 14, 15, 39.
Upgrade cadence. Done when the scheduled job has run at least once, produced a tracking issue listing the currently pending majors, and a second run has updated that issue in place rather than opening a duplicate. Verifies stories 40–42.
Repository-wide acceptance
Quality thresholds
Out of Scope
Further Notes
On why documentation tests are the centrepiece. Every other item here is a one-time fix that a maintainer could have made without a PRD. The invariant tests are the only component that changes the repository's future rather than its present. A template is read far more often than it is edited, and its docs decay silently because nothing exercises them. Four tests convert the audit from a snapshot into a standing guarantee — which is why they are specified with the requirement that each be demonstrated failing first.
On reversing the audit's CD recommendation. #21 proposed a staged CD pipeline as a P0 item. That was correct reasoning for an application and wrong for a template: the pipeline would encode account identifiers, secret names, and branch conventions specific to one deployment, and it could not be meaningfully exercised in the template repository itself. The underlying concern — that the path to production is undocumented — is fully addressed by the runbook. Continuous integration was retained at P0 because it validates the repository's own contents, which is exactly what a template needs.
On the secrets decision's second-order value. Deploy-time secret validation was not what the audit was asking for; it was asking to unblock type generation in CI. That the same configuration property also prevents a manual deploy from shipping an environment with unset secrets makes it strictly better than the hand-written alternative, and it partially compensates for the deliberate absence of a deployment pipeline.
On sequencing. The secrets contract should land first — it unblocks the bot workflows and removes a workaround that would otherwise have to be carried through every other change. The documentation invariant tests should land alongside their corresponding fixes, never after, so that each fix ships with its guard. The test runtime split is the largest single piece of work and is independent of everything else; it can proceed in parallel.
On the requirements directory. Creating it and immediately filling it with the three decision records is deliberate. An empty directory created solely to satisfy a pointer would be a second lie in place of the first.
Supersedes nothing. #21 stays open as the audit record and backlog. This PRD resolves its open decisions; it does not replace its findings.