Payload
{
"ref": "refs/heads/fix/wire-shared-runtime-env",
"before": "68cf8443d45f686c6877bb272ab4e7325e79ac67",
"after": "23993d5a0afc3348b971885beebcee476776acc8",
"repository": {
"id": 1110409800,
"node_id": "R_kgDOQi-CSA",
"name": "stackpanel",
"full_name": "darkmatter/stackpanel",
"private": false,
"owner": {
"name": "darkmatter",
"email": "hello@dm.sh",
"login": "darkmatter",
"id": 17834193,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjE3ODM0MTkz",
"avatar_url": "https://avatars.githubusercontent.com/u/17834193?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/darkmatter",
"html_url": "https://github.com/darkmatter",
"followers_url": "https://api.github.com/users/darkmatter/followers",
"following_url": "https://api.github.com/users/darkmatter/following{/other_user}",
"gists_url": "https://api.github.com/users/darkmatter/gists{/gist_id}",
"starred_url": "https://api.github.com/users/darkmatter/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/darkmatter/subscriptions",
"organizations_url": "https://api.github.com/users/darkmatter/orgs",
"repos_url": "https://api.github.com/users/darkmatter/repos",
"events_url": "https://api.github.com/users/darkmatter/events{/privacy}",
"received_events_url": "https://api.github.com/users/darkmatter/received_events",
"type": "Organization",
"user_view_type": "public",
"site_admin": false
},
"html_url": "https://github.com/darkmatter/stackpanel",
"description": "Ship products, not plumbing. Making Nix dev environments accessible to all.",
"fork": false,
"url": "https://api.github.com/repos/darkmatter/stackpanel",
"forks_url": "https://api.github.com/repos/darkmatter/stackpanel/forks",
"keys_url": "https://api.github.com/repos/darkmatter/stackpanel/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/darkmatter/stackpanel/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/darkmatter/stackpanel/teams",
"hooks_url": "https://api.github.com/repos/darkmatter/stackpanel/hooks",
"issue_events_url": "https://api.github.com/repos/darkmatter/stackpanel/issues/events{/number}",
"events_url": "https://api.github.com/repos/darkmatter/stackpanel/events",
"assignees_url": "https://api.github.com/repos/darkmatter/stackpanel/assignees{/user}",
"branches_url": "https://api.github.com/repos/darkmatter/stackpanel/branches{/branch}",
"tags_url": "https://api.github.com/repos/darkmatter/stackpanel/tags",
"blobs_url": "https://api.github.com/repos/darkmatter/stackpanel/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/darkmatter/stackpanel/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/darkmatter/stackpanel/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/darkmatter/stackpanel/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/darkmatter/stackpanel/statuses/{sha}",
"languages_url": "https://api.github.com/repos/darkmatter/stackpanel/languages",
"stargazers_url": "https://api.github.com/repos/darkmatter/stackpanel/stargazers",
"contributors_url": "https://api.github.com/repos/darkmatter/stackpanel/contributors",
"subscribers_url": "https://api.github.com/repos/darkmatter/stackpanel/subscribers",
"subscription_url": "https://api.github.com/repos/darkmatter/stackpanel/subscription",
"commits_url": "https://api.github.com/repos/darkmatter/stackpanel/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/darkmatter/stackpanel/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/darkmatter/stackpanel/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/darkmatter/stackpanel/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/darkmatter/stackpanel/contents/{+path}",
"compare_url": "https://api.github.com/repos/darkmatter/stackpanel/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/darkmatter/stackpanel/merges",
"archive_url": "https://api.github.com/repos/darkmatter/stackpanel/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/darkmatter/stackpanel/downloads",
"issues_url": "https://api.github.com/repos/darkmatter/stackpanel/issues{/number}",
"pulls_url": "https://api.github.com/repos/darkmatter/stackpanel/pulls{/number}",
"milestones_url": "https://api.github.com/repos/darkmatter/stackpanel/milestones{/number}",
"notifications_url": "https://api.github.com/repos/darkmatter/stackpanel/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/darkmatter/stackpanel/labels{/name}",
"releases_url": "https://api.github.com/repos/darkmatter/stackpanel/releases{/id}",
"deployments_url": "https://api.github.com/repos/darkmatter/stackpanel/deployments",
"created_at": 1764916677,
"updated_at": "2026-06-11T00:58:55Z",
"pushed_at": 1781140881,
"git_url": "git://github.com/darkmatter/stackpanel.git",
"ssh_url": "git@github.com:darkmatter/stackpanel.git",
"clone_url": "https://github.com/darkmatter/stackpanel.git",
"svn_url": "https://github.com/darkmatter/stackpanel",
"homepage": "https://stackpanel.com",
"size": 117448,
"stargazers_count": 5,
"watchers_count": 5,
"language": "TypeScript",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"has_discussions": false,
"forks_count": 1,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 1,
"license": null,
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"has_pull_requests": true,
"pull_request_creation_policy": "all",
"topics": [],
"visibility": "public",
"forks": 1,
"open_issues": 1,
"watchers": 5,
"default_branch": "main",
"stargazers": 5,
"master_branch": "main",
"organization": "darkmatter",
"custom_properties": {}
},
"pusher": {
"name": "czxtm",
"email": "1325802+czxtm@users.noreply.github.com"
},
"forced": true,
"organization": {
"login": "darkmatter",
"id": 17834193,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjE3ODM0MTkz",
"url": "https://api.github.com/orgs/darkmatter",
"repos_url": "https://api.github.com/orgs/darkmatter/repos",
"events_url": "https://api.github.com/orgs/darkmatter/events",
"hooks_url": "https://api.github.com/orgs/darkmatter/hooks",
"issues_url": "https://api.github.com/orgs/darkmatter/issues",
"members_url": "https://api.github.com/orgs/darkmatter/members{/member}",
"public_members_url": "https://api.github.com/orgs/darkmatter/public_members{/member}",
"avatar_url": "https://avatars.githubusercontent.com/u/17834193?v=4",
"description": ""
},
"enterprise": {
"id": 469843,
"slug": "darkmatter",
"name": "darkmatter",
"node_id": "E_kgDOAAcrUw",
"avatar_url": "https://avatars.githubusercontent.com/b/469843?v=4",
"description": "",
"website_url": "darkmatter.io",
"html_url": "https://github.com/enterprises/darkmatter",
"created_at": "2025-09-07T16:01:00Z",
"updated_at": "2026-06-07T16:53:26Z"
},
"sender": {
"login": "czxtm",
"id": 1325802,
"node_id": "MDQ6VXNlcjEzMjU4MDI=",
"avatar_url": "https://avatars.githubusercontent.com/u/1325802?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/czxtm",
"html_url": "https://github.com/czxtm",
"followers_url": "https://api.github.com/users/czxtm/followers",
"following_url": "https://api.github.com/users/czxtm/following{/other_user}",
"gists_url": "https://api.github.com/users/czxtm/gists{/gist_id}",
"starred_url": "https://api.github.com/users/czxtm/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/czxtm/subscriptions",
"organizations_url": "https://api.github.com/users/czxtm/orgs",
"repos_url": "https://api.github.com/users/czxtm/repos",
"events_url": "https://api.github.com/users/czxtm/events{/privacy}",
"received_events_url": "https://api.github.com/users/czxtm/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"installation": {
"id": 131074261,
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTMxMDc0MjYx"
},
"created": false,
"deleted": false,
"base_ref": null,
"compare": "https://github.com/darkmatter/stackpanel/compare/68cf8443d45f...23993d5a0afc",
"commits": [
{
"id": "9f49aa73f0eae4a6d7141eada1ab030c57b21ce9",
"tree_id": "48ef9f175d3983d42be286c0118daa86da56d896",
"distinct": true,
"message": "fix(env): wire shared secrets through to per-app runtime payloads\n\nJoining the waitlist (and every other tRPC call) on production returned\nHTTP 500 with `You are using the default secret. Please set BETTER_AUTH_SECRET ...`.\n\nRoot cause: `.stack/config.apps.nix:envs.shared` declared\n`BETTER_AUTH_SECRET` and `POLAR_ACCESS_TOKEN` without a `sops:` source —\njust `required = false` and a description. The codegen rendered\n`\"BETTER_AUTH_SECRET\": \"\"` into `packages/gen/env/data/<env>/<app>.sops.json`\nand the embedded runtime payload, so at request time\n`process.env.BETTER_AUTH_SECRET === \"\"`. Better-auth fell back to its hard-\ncoded sentinel `\"better-auth-secret-12345678901234567890\"` and `validateSecret()`\nthrew on every request, because `createTRPCContext` calls\n`auth.api.getSession()` for every procedure (waitlist included).\n\n`POLAR_WEBHOOK_SECRET`, `POLAR_PRO_PRODUCT_ID_PRODUCTION`, and\n`POLAR_FREE_PRODUCT_ID_PRODUCTION` were missing from `envs.shared`\nentirely — only present in the deploy scope — so any `process.env.*`\nreader saw `undefined` despite the SOPS source existing.\n\nFix wires every shared env that has a corresponding SOPS source:\n\n BETTER_AUTH_SECRET → /shared/better-auth-secret (required)\n POLAR_ACCESS_TOKEN → /shared/polar-access-token\n POLAR_WEBHOOK_SECRET → /shared/polar-webhook-secret\n POLAR_PRO_PRODUCT_ID_PRODUCTION → /shared/polar-pro-product-id-production\n POLAR_FREE_PRODUCT_ID_PRODUCTION → /shared/polar-free-product-id-production\n\n`BETTER_AUTH_URL`, `CORS_ORIGIN`, `POLAR_SUCCESS_URL` stay `required = false`\nwithout a SOPS source — they are per-env URL config and the consumer code\nalready handles missing values gracefully (better-auth derives the URL\nfrom the request host; CORS_ORIGIN and POLAR_SUCCESS_URL fall back to\nupstream defaults). Documented in the comment block.\n\nRe-ran `stackpanel codegen build` — every per-app per-env runtime payload\nnow embeds real SOPS ciphertext for these keys (verified via\n`sops -d packages/gen/env/data/prod/web.sops.json`). codegen-drift gate\nshould pass.\n\nRefs stackpanel-3tj.",
"timestamp": "2026-05-01T01:19:33-07:00",
"url": "https://github.com/darkmatter/stackpanel/commit/9f49aa73f0eae4a6d7141eada1ab030c57b21ce9",
"author": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T01:19:33-07:00",
"username": "czxtm"
},
"committer": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T01:19:33-07:00",
"username": "czxtm"
},
"added": [],
"removed": [],
"modified": [
".gitignore",
".stack/config.apps.nix",
"packages/gen/env/README.md",
"packages/gen/env/data/dev/api.sops.json",
"packages/gen/env/data/dev/docs.sops.json",
"packages/gen/env/data/dev/web.sops.json",
"packages/gen/env/data/prod/api.sops.json",
"packages/gen/env/data/prod/docs.sops.json",
"packages/gen/env/data/prod/web.sops.json",
"packages/gen/env/data/staging/api.sops.json",
"packages/gen/env/data/staging/docs.sops.json",
"packages/gen/env/data/staging/web.sops.json",
"packages/gen/env/src/effect/api.ts",
"packages/gen/env/src/effect/docs.ts",
"packages/gen/env/src/effect/web.ts",
"packages/gen/env/src/embedded-data.ts",
"packages/gen/env/src/exports/api.ts",
"packages/gen/env/src/exports/docs.ts",
"packages/gen/env/src/exports/web.ts",
"packages/gen/env/src/runtime/generated-payloads/api/dev.ts",
"packages/gen/env/src/runtime/generated-payloads/api/prod.ts",
"packages/gen/env/src/runtime/generated-payloads/api/staging.ts",
"packages/gen/env/src/runtime/generated-payloads/docs/dev.ts",
"packages/gen/env/src/runtime/generated-payloads/docs/prod.ts",
"packages/gen/env/src/runtime/generated-payloads/docs/staging.ts",
"packages/gen/env/src/runtime/generated-payloads/web/dev.ts",
"packages/gen/env/src/runtime/generated-payloads/web/prod.ts",
"packages/gen/env/src/runtime/generated-payloads/web/staging.ts"
]
},
{
"id": "4155a2ea4868ae96d81db581181d3e28e2037026",
"tree_id": "7fbc9ade05f10f3351d3a26dcb58d943d529ba94",
"distinct": true,
"message": "fix(web): pass runtime secrets to Cloudflare Worker env\n\nThe web Worker doesn't call loadAppEnv() at request time; it relies on\nCloudflare to inject env vars set on the deployed script. Previously only\nDATABASE_URL was forwarded — BETTER_AUTH_SECRET and the four Polar\nsecrets we just SOPS-wired were not, so process.env.BETTER_AUTH_SECRET\nwas empty in production, better-auth fell back to its sentinel, and\nevery tRPC call (including waitlist.join) returned 500.\n\nThis forwards the five secrets from process.env (populated by\nloadDeployEnv at the top of alchemy.run.ts) into the Cloudflare.Vite\nenv: map. Polar values default to '' so a missing-secret deploy still\nboots; consumer code treats empty as feature-disabled.\n\nRefs stackpanel-3tj.",
"timestamp": "2026-05-01T02:59:38-07:00",
"url": "https://github.com/darkmatter/stackpanel/commit/4155a2ea4868ae96d81db581181d3e28e2037026",
"author": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T02:59:38-07:00",
"username": "czxtm"
},
"committer": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T02:59:38-07:00",
"username": "czxtm"
},
"added": [],
"removed": [],
"modified": [
"apps/web/alchemy.run.ts"
]
},
{
"id": "ea81d5b67e14d252fa5881d5a4a2206e5bcf4446",
"tree_id": "bee0a992035c90077a9e8a471c2266fa797b96b1",
"distinct": true,
"message": "fix(web): decrypt runtime secrets in Worker via @gen/env, not deploy-time forwarding\n\nThis reverses the env-shovel approach from 21c00841 in favour of decrypting\nthe embedded `@gen/env` SOPS payload at Worker boot. The Worker now needs\nonly `SOPS_AGE_KEY` (the AGE key material) and `APP_ENV` (the SOPS\nnamespace discriminator) at deploy time; every other secret is unsealed\non first request from the encrypted payload already shipped in\npackages/gen/env/src/runtime/generated-payloads/web/{dev,staging,prod}.ts.\n\nWhy:\n- Single source of truth. Adding a new app secret used to require two\n edits (.stack/config.apps.nix AND apps/web/alchemy.run.ts); now only\n the Nix scope edit + a codegen rebuild is needed.\n- No dual-write into Cloudflare's secret store. The encrypted payload is\n the only place secret material lives.\n- Mirrors the Fly-deployed apps/api boot pattern.\n\nChanges:\n- apps/web/src/server.ts: top-level `await loadAppEnv(\"web\", APP_ENV,\n { inject: true })` against the new edge-safe `@gen/env/runtime/edge`\n loader. Guarded by `process.env.SOPS_AGE_KEY` so vite dev / vitest\n keep working with whatever process.env they already have.\n- apps/web/alchemy.run.ts: drop BETTER_AUTH_SECRET and the four POLAR_*\n forwards added in 21c00841. Add SOPS_AGE_KEY (read from process.env\n after `loadDeployEnv`) and APP_ENV (the resolved appEnv literal).\n- packages/auth/src/index.ts: lazify `betterAuth({...})` behind a\n Proxy-backed `auth` export. Defers `validateSecret` to first property\n access (per-request) so the import chain\n routeTree.gen.ts → routes/api/trpc.$.ts → @stackpanel/auth no longer\n crashes when env injection hasn't happened yet. Adds `getAuth()` for\n callers that want to surface init errors eagerly.\n- nix/stackpanel/lib/codegen/env-package.nix: add `./runtime/edge`\n export pointing at `loader.ts` (no FileSystem/ChildProcess deps).\n Required because the existing `./runtime` export resolves to\n `node-loader.ts`, which pulls in @effect/platform-node — fine for\n alchemy.run.ts on Node, broken in a Cloudflare Worker.\n- packages/gen/env/package.json: regenerated from the Nix change.\n- docs/adr/0001-runtime-secrets-via-gen-env-loader.md (+ README): ADR\n documenting the decision, consequences, and rejected alternatives.\n\nRefs: bd stackpanel-3tj",
"timestamp": "2026-05-01T03:28:46-07:00",
"url": "https://github.com/darkmatter/stackpanel/commit/ea81d5b67e14d252fa5881d5a4a2206e5bcf4446",
"author": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T03:28:46-07:00",
"username": "czxtm"
},
"committer": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T03:28:46-07:00",
"username": "czxtm"
},
"added": [
"docs/adr/0001-runtime-secrets-via-gen-env-loader.md",
"docs/adr/README.md"
],
"removed": [],
"modified": [
"apps/web/alchemy.run.ts",
"apps/web/src/server.ts",
"nix/stackpanel/lib/codegen/env-package.nix",
"packages/auth/src/index.ts",
"packages/gen/env/package.json"
]
},
{
"id": "23993d5a0afc3348b971885beebcee476776acc8",
"tree_id": "8582d9f256385e90c90b1fa56821693e4c4d94d0",
"distinct": true,
"message": "fix(auth): TLA-load runtime env inside @stackpanel/auth\n\nMove the SOPS payload decrypt from `apps/web/src/server.ts` into\n`packages/auth/src/index.ts` so it happens BEFORE `betterAuth({...})`\nconstructs the auth instance — and before `polarClient` is built.\n\nThe previous PR-24 attempt put the load in `server.ts` with a Proxy in\n`@stackpanel/auth`, but ESM hoisting meant `payments.ts` (and the rest of\n`index.ts`'s static imports) evaluated before the `server.ts` TLA ran:\n`polarClient` was always `null` in the Worker, and `betterAuth({...})`\nwas being initialised before `process.env.BETTER_AUTH_SECRET` had been\nwritten by `loadAppEnv`. Result: HTTP 500 \"you are using the default\nsecret\" on every tRPC call.\n\nNow `@stackpanel/auth/index.ts`:\n\n- Awaits `loadAppEnv(\"web\", APP_ENV, { inject: true })` at the top of\n the module (gated on `process.env.SOPS_AGE_KEY`).\n- Reads `BETTER_AUTH_SECRET` and `POLAR_*` via a local `envOf()` helper\n that prefers the decrypted payload over `process.env` (so we don't\n rely on `process.env` being writable at the edge — Cloudflare's\n unenv shim is mutable today, but it shouldn't be load-bearing).\n- Constructs `polarClient` inline in `index.ts` AFTER the TLA, instead\n of importing it from `./lib/payments`. The static import was the\n reason `polarClient` was always null in the Worker.\n- Drops the lazy Proxy: now that the env load happens inside this\n module, eager construction is safe again.\n\n`apps/web/src/server.ts` keeps its own (defense-in-depth) TLA load —\nboth calls are idempotent because the loader caches the decrypted\npayload.\n\nRefs ADR docs/adr/0001-runtime-secrets-via-gen-env-loader.md.",
"timestamp": "2026-05-01T03:42:43-07:00",
"url": "https://github.com/darkmatter/stackpanel/commit/23993d5a0afc3348b971885beebcee476776acc8",
"author": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T03:42:43-07:00",
"username": "czxtm"
},
"committer": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T03:42:43-07:00",
"username": "czxtm"
},
"added": [],
"removed": [],
"modified": [
"packages/auth/src/index.ts"
]
}
],
"head_commit": {
"id": "23993d5a0afc3348b971885beebcee476776acc8",
"tree_id": "8582d9f256385e90c90b1fa56821693e4c4d94d0",
"distinct": true,
"message": "fix(auth): TLA-load runtime env inside @stackpanel/auth\n\nMove the SOPS payload decrypt from `apps/web/src/server.ts` into\n`packages/auth/src/index.ts` so it happens BEFORE `betterAuth({...})`\nconstructs the auth instance — and before `polarClient` is built.\n\nThe previous PR-24 attempt put the load in `server.ts` with a Proxy in\n`@stackpanel/auth`, but ESM hoisting meant `payments.ts` (and the rest of\n`index.ts`'s static imports) evaluated before the `server.ts` TLA ran:\n`polarClient` was always `null` in the Worker, and `betterAuth({...})`\nwas being initialised before `process.env.BETTER_AUTH_SECRET` had been\nwritten by `loadAppEnv`. Result: HTTP 500 \"you are using the default\nsecret\" on every tRPC call.\n\nNow `@stackpanel/auth/index.ts`:\n\n- Awaits `loadAppEnv(\"web\", APP_ENV, { inject: true })` at the top of\n the module (gated on `process.env.SOPS_AGE_KEY`).\n- Reads `BETTER_AUTH_SECRET` and `POLAR_*` via a local `envOf()` helper\n that prefers the decrypted payload over `process.env` (so we don't\n rely on `process.env` being writable at the edge — Cloudflare's\n unenv shim is mutable today, but it shouldn't be load-bearing).\n- Constructs `polarClient` inline in `index.ts` AFTER the TLA, instead\n of importing it from `./lib/payments`. The static import was the\n reason `polarClient` was always null in the Worker.\n- Drops the lazy Proxy: now that the env load happens inside this\n module, eager construction is safe again.\n\n`apps/web/src/server.ts` keeps its own (defense-in-depth) TLA load —\nboth calls are idempotent because the loader caches the decrypted\npayload.\n\nRefs ADR docs/adr/0001-runtime-secrets-via-gen-env-loader.md.",
"timestamp": "2026-05-01T03:42:43-07:00",
"url": "https://github.com/darkmatter/stackpanel/commit/23993d5a0afc3348b971885beebcee476776acc8",
"author": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T03:42:43-07:00",
"username": "czxtm"
},
"committer": {
"name": "Cooper Maruyama",
"email": "me@cooperm.com",
"date": "2026-05-01T03:42:43-07:00",
"username": "czxtm"
},
"added": [],
"removed": [],
"modified": [
"packages/auth/src/index.ts"
]
}
}