Skip to content

feat(repo): give the site worker one d1 database, emulated locally under pnpm dev - #63

Merged
kiro-systemf[bot] merged 203 commits into
mainfrom
lake1/d1
Oct 7, 2026
Merged

kiro-systemf[bot] merged 203 commits into
mainfrom
lake1/d1

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Lake 1, new layer 2 (Kiro ruling, 2026-10-06), stacked on #62. Restacked onto the RPC layer.

  • apps/site/alchemy.run.ts declares Cloudflare.D1.Database('Database') and binds it to the Worker as DB. alchemy dev creates and serves it locally (under apps/site/.alchemy/local) with no cloud account; each cloud stage gets its own database.
  • apps/site/src/cloudflare-env.d.ts types cloudflare:workers' env from Alchemy's InferEnv of the Site, so renaming the binding on one side fails the typecheck.
  • The health procedure runs one D1 probe (env.DB.prepare('SELECT 1')) and hands its outcome to src/api/check-health.workflow.ts, a pure Workflow.make decision: healthy when the probe answered, otherwise the tagged refusal DatabaseUnreachable, which is the procedure's typed error. The home page shows unreachable on any failure, defects included.
  • Queries go through the D1 binding itself: no effect/sql, no @effect/sql-d1.
  • README: the token needs Workers and D1 edit rights.

Gate after the cycle 52 restack (Linux only)

Gate at 7f70d04, clean worktree, Linux, sandboxed:

$ pnpm bootstrap                    # exit 0
$ pnpm check:ci                     # exit 1, only check:sfs-sources:
check-sfs-sources: 11 @systemfsoftware/* package(s) resolve from the npm registry, not the systemfsoftware flake
 Tasks:    9 successful, 9 total   # lint, typecheck, test, build
 Tasks:    1 successful, 1 total   # dist
ok | 1 passed (9 steps) | 0 failed  # sandbox proofs
$ pnpm journeys                     # exit 0
Tests  2 passed (2)   # health over RPC, strict CSP

CI run 37573598160 on 7f70d04: 6 of 7 jobs pass (format, lint, typecheck, test, dist, journeys); check (sfs-sources) fails, the same red as the local gate above.

Sabotage: renaming the binding in alchemy.run.ts fails the typecheck (Property 'DB' does not exist on type 'Env'). Breaking the query (SELECT FROM) fails the health journey with Worker health: unreachable.

Cycle 35: the site's own test (Kiro rulings)

After F5 dropped --passWithNoTests, the guestbook's property test was the site's only test, so removing the guestbook left check:ci red on @endgame/site#test (No test files found). Ruling: the site gets a test of its own that survives removal, as a pure decision with a property, on this layer.

  • apps/site/src/api/check-health.workflow.ts: command CheckHealth { probe: ProbeAnswered | ProbeUnanswered }, decision Healthy (private to the file), refusal DatabaseUnreachable. The handler only runs the probe and calls it.
  • apps/site/src/api/__tests__/check-health.workflow.property.test.ts: ∀c_Healthy_=ProbeAnswered, drawing the command from its production schema: the workflow succeeds exactly when the probe answered, and otherwise refuses with DatabaseUnreachable.
  • The site's test and mutation toolchain moves here from the guestbook layer with the first workflow: vitest, @systemfsoftware/vitest, the four @systemfsoftware/stryker-* packages, @systemfsoftware/effect-cell-types, vitest.config.ts, stryker.config.ts, the test (vitest run) and mutation scripts, and stryker.mutate: ["src/**/*.workflow.ts"]. The release-gate planner now plans @endgame/site (packages=["@endgame/site"]), and check:sfs-sources counts 11 packages here, the F1 gap that closes with stryker-js-effect#196's flake.
$ pnpm --filter @endgame/site test        # at 9d4a3a4
✓ src/api/__tests__/check-health.workflow.property.test.ts (1 test)
# sabotage: decide's arms swapped (answered → DatabaseUnreachable, unanswered → Healthy)
× ∀c_Healthy_=ProbeAnswered
PropertyRefuted: … Shrunk input: {"command":{"probe":ProbeUnanswered}}
# restored (the replay seed file the failure wrote deleted): 1 passed
$ pnpm journeys
Tests  2 passed (2)                       # health over RPC, strict CSP

Gate after cycle 83 (every @systemfsoftware/* package from our flakes, #52)

Gate at 89b434f, clean worktree, Linux, sandboxed:

$ pnpm bootstrap                    # exit 0
$ pnpm check:ci                     # exit 0
check-sfs-sources: all 25 @systemfsoftware/* packages resolve from the systemfsoftware flake
 Tasks:    9/9 successful, 9/9 total   # lint, typecheck, test, build
 Tasks:    1 successful, 1 total   # dist
ok | 1 passed (9 steps) | 0 failed  # sandbox proofs
$ pnpm journeys                     # exit 0
Tests  2 passed (2)   # health over RPC, strict CSP

CI run 37676551794 on 89b434f: all 7 jobs pass, check (sfs-sources) included. The exit 1 gates above are from before the stryker packages came from the stryker-js-effect flake.

check:ci no longer runs stryker. The release gate on push to main plans one
shard per workspace package that declares a mutation script, refuses an
empty set, and runs each shard at break 100 on the fleet with its
incremental report cached. The checker drops
prioritizePerformanceOverAccuracy, which stryker-js 15 removes. Operator
approval: Kiro, 2026-10-05 (GATE1)
The plan and per-package mutation jobs move from the self-hosted fleet,
which admits only private repositories, to ubuntu-latest. Mutation stays
one parallel job per package.

Operator approval: Kiro, 2026-10-05 (GATE1)
A package with a mutation script whose mutate globs match nothing
reports zero mutants and passes a break-100 threshold vacuously
(review finding #9). Each package now declares its globs once, as
stryker.mutate in package.json; its Stryker config reads them and the
release gate's shard planner expands them. The planner fails red at
plan time naming the package, its directory and its globs when they
match no file, including a package with no declared globs. Its Deno
tests are ordinary tests: a turbo root task, test:scripts, runs them
through the dev shell's deno, and both pnpm test and check:ci run it,
so a local check:ci covers the planner exactly as CI does
…ns to mutate

A decision is a *.workflow.ts file. When no workspace package has one, the planner emits an empty shard list and a 'No decisions to mutate' notice, and the mutation job is skipped. When decisions exist and no package declares a mutation script, it still refuses the empty set, naming how many decisions went unmutated. Kiro ruling, 2026-10-06
Every catalog entry is an exact version: effect 4.0.1, the
systemfsoftware toolchain on its Effect 4 stable majors (recommended preset
4.0.0, stryker-js 15.0.1, vitest 2.0.0, gherkin-spec 7.0.1) and current
tooling. minimumReleaseAge 1440 is explicit, so the policy is strict;
the exclude list holds the effect name patterns and one exact entry per
resolved systemfsoftware package. oxlint-tsgolint stays 7.0.2001, the
exact peer the presets require. turbo 2.11 writes an agent guidance block
into AGENTS.md unless agentGuidance is false, so turbo.json opts out.
Grader package majors move here because they require Effect 4 stable
(CONST-W3 declared)
oxlint.shared.ts, vitest.shared.ts and stryker.shared.ts hold the house
settings; each package config extends them and declares only its own
mutate set and aliases. Both lint overrides are gone: preset 4.0.0 already
accepts Gherkin step bodies and build-config imports. The root lint file is
named oxlint.shared.ts so lint-staged's nearest-config walk never treats
the repository root as a package. Turbo inputs track the shared files.
Operator approval: Kiro, 2026-10-05 (GATE1)
…root

Root tsconfig.base.json extends the bundler/dom preset and allows exactly
effect/http through the effect language service. effect 4.0.1 ships HTTP
only as unstable and global-fetch-in-effect bans the fetch alternative.
Operator approval: Kiro, 2026-10-05 (GATE1)
One Cloudflare Worker serves the TanStack Start site, defined with Alchemy and run locally by pnpm dev through alchemy dev. The e2e journeys run against that local app
bin/journeys starts the app with pnpm dev, runs the journeys against it and stops it with one SIGINT to its process group, so alchemy's local sidecar is not orphaned. The dev port is strict, so a stale server fails the run instead of moving it to another port
… site worker

The site Worker routes /api/ to an Effect HttpApi: GET /api/health answers {"status":"ok"}, and /api/openapi.json serves the OpenAPI 3.1 document HttpApiBuilder generates. Every other path still goes to TanStack Start under the strict CSP. The root unstable-API opt-in gains exactly effect/http-api, where Effect 4.0.1 ships HttpApi (Kiro ruling, 2026-10-06). A journey reads the API description and calls the health check against pnpm dev
…der pnpm dev

alchemy.run.ts declares a D1 database and binds it to the Worker as DB. alchemy dev creates and serves it locally with no cloud account, and each cloud stage gets its own. The Worker reads the binding from cloudflare:workers, typed through Alchemy's InferEnv of the Site, so a binding renamed on one side fails the typecheck. The Health procedure now answers ok only when the database answers SELECT 1, and the home page shows unreachable when it does not. Queries go through the D1 binding itself, with no effect/sql or @effect/sql-d1
…e release gate

In an adopter's copy, never the template, a same-repo pull request deploys as its pr-<N> stage, gets its URL in one PR comment, runs the e2e journeys against it and is destroyed when it closes. Main deploys production once every mutation shard passes, to SITE_DOMAIN when that repository variable is set. The deployed journeys run through pnpm journeys:deployed, whose sandbox reaches only the site and gets no Cloudflare credentials
Ryan ruling via Kiro 2026-10-07: one RpcGroup served by RpcServer over HTTP at /api/rpc, a typed RpcClient in the page; a Health procedure replaces /api/health and the OpenAPI document goes. The home page shows the Worker's health and a browser journey reads it. The unstable-API opt-in lists effect/rpc instead of effect/http-api
packages/starter and every trace of it leave in one commit: the README
sections that describe it, the trusted-publisher note in .changeset, and
the catalog entries no remaining package uses (rimraf, tsdown).

npm publishing goes with it. The release job keeps capture, release-note
assertion, tagging and GitHub Releases and drops build, OIDC and pnpm
publish. A version in a manifest with no matching <name>@v<version> tag is
owed; no script queries registry.npmjs.org.
Operator approval: Kiro, 2026-10-05 (release.yml)
The Worker draws a fresh nonce per request, hands it to TanStack Start for its scripts and sends a strict Content-Security-Policy with Trusted Types. A browser journey in Chromium loads the home page under that policy with no violations, and removing the Trusted Types directive fails it
…packages from nix

Every script that runs dependency code (install, build, test, dev, the journeys and the git hooks) goes through prm's sandbox launcher, systemfsoftware packages come from the systemfsoftware flake as tarballs, and the pnpm store is built from per-tarball fetches. check:sfs-sources stays red on the two Stryker packages, which still resolve from npm until systemfsoftware/stryker-js-effect ships them as flake outputs
pnpm run deploy deploys the Worker with Alchemy to the adopter's own Cloudflare account through bin/cloud, the one place Cloudflare credentials enter the sandbox; cloud stages keep their state in Cloudflare. A production domain is adopter configuration: SITE_DOMAIN, read by alchemy.run.ts on the prod stage. The template holds no credentials
With no decisions to mutate the release gate skips the mutation job, which used to skip the deploy that needs it. Production now deploys when the plan passed and the mutation job passed or was skipped, and never when either failed or the run was cancelled
Conductor rulings, cycles 74 and 77: pnpm-release-management main 537d17c carries #14, #20 (the consumer store) and #24 (the linked-worktree change), so the starter's own copy of that change (nix/patches/sandbox-linked-worktree-git.patch) and its applyPatches wiring go. pnpm-release-management becomes a direct input on main, locked to 537d17c; systemfsoftware follows it, and its nixpkgs, comment-checker and importPnpmLock follow the starter's
starter-verify c73: withoutSandboxOnPath removed only the first directory that held sandbox, so with two launchers on PATH the hooks still found one and the refusal step failed falsely. It now drops every PATH entry that provides sandbox. Two launchers on PATH: the old helper fails the step, the new one passes; with the hooks' sandbox prefix stripped, the step fails
Conductor ruling, cycle 83: stryker-js-effect main f06f244 (#196) publishes its 17 members as tarballs with an index.json. The flake adds it as an input (locked to f06f244, following the starter's nixpkgs, comment-checker, importPnpmLock, pnpm-release-management and systemfsoftware), and .sfs-deps now holds both flakes' tarballs with one merged index.json. Every @systemfsoftware/stryker-* member is a file:.sfs-deps catalog entry with an override, the same mechanism as the systemfsoftware packages, plus one for the stryker-js-vm-runner npm alias that stryker-js 17.0.2 declares, so check:sfs-sources passes with no npm-sourced @systemfsoftware/* package
7857171 (lake1/nix-sandbox merged into lake1/deploy) was pushed to lake1/nix-sandbox by mistake during the cycle 83 merge-up. This restores the layer's tree to fb4f8a1 exactly; lake1/deploy reverts this revert so it keeps its own content
-s ours: the only change on lake1/nix-sandbox since 7857171 is the revert of 7857171 itself, which this layer must not take
@kiro-systemf
kiro-systemf Bot changed the base branch from lake1/http-api to main October 7, 2026 21:23

@kiro-systemf kiro-systemf Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: 9/9 green on 85438ee, diff = d1's 13 files, 0 threads, hunt clean.

@kiro-systemf
kiro-systemf Bot merged commit e08b049 into main Oct 7, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant