From e6d394dc3f92a879e18294254e4cd93400df1561 Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 10:56:23 +0200 Subject: [PATCH 1/9] Move to alchemy-convex 0.14.0 and convex 1.48.0 convex/ imports the package now, so it moves to dependencies. The release-age exclusion moves to 0.14.0. The exclusions for 0.11.0 to 0.13.0 go: no entry of the lockfile needs them. convex 1.48.0 prints names only from `env list` when it detects an AI agent. hasVariable read NAME=value lines, so keys:init run by an agent saw no key set and would replace it. It asks for the names now. Co-Authored-By: Claude Opus 5.5 --- package.json | 4 ++-- pnpm-lock.yaml | 30 +++++++++++++++--------------- pnpm-workspace.yaml | 7 +------ scripts/convex-cli.ts | 10 +++++++--- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 4514569..d03ad45 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "lint": "vp lint" }, "dependencies": { + "@samebase/alchemy-convex": "0.14.0", "@samebase/identity-broker": "0.6.1", - "convex": "1.47.0" + "convex": "1.48.0" }, "devDependencies": { "@effect/platform-node": "4.0.5", - "@samebase/alchemy-convex": "0.13.0", "@types/node": "24.19.2", "alchemy": "2.0.0-beta.84", "effect": "4.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 721c1b3..1aded2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,19 +191,19 @@ importers: .: dependencies: + '@samebase/alchemy-convex': + specifier: 0.14.0 + version: 0.14.0(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.48.0)(effect@4.0.5) '@samebase/identity-broker': specifier: 0.6.1 - version: 0.6.1(convex@1.47.0) + version: 0.6.1(convex@1.48.0) convex: - specifier: 1.47.0 - version: 1.47.0 + specifier: 1.48.0 + version: 1.48.0 devDependencies: '@effect/platform-node': specifier: 4.0.5 version: 4.0.5(effect@4.0.5)(redis@6.3.0) - '@samebase/alchemy-convex': - specifier: 0.13.0 - version: 0.13.0(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.47.0)(effect@4.0.5) '@types/node': specifier: 24.19.2 version: 24.19.2 @@ -860,8 +860,8 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@samebase/alchemy-convex@0.13.0': - resolution: {integrity: sha512-GZfPgo0HYp8lr/hqJ2GXQSbpEeefB+ci0CCUAZE/CZuKFWjjzL8gmpcOzWZOOwfQFb5rj29kRSIzVdlpKzCTSQ==} + '@samebase/alchemy-convex@0.14.0': + resolution: {integrity: sha512-hzTC0R5EeLnbyVidCxEGZEgfUxO0fTWhcAR1VmI6MA0ASfnZwu1umWU269NsPc7oAINJGREKB7JsSFh12NmOGQ==} engines: {node: '>=22'} peerDependencies: '@effect/platform-node': ^4.0.5 @@ -1305,8 +1305,8 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - convex@1.47.0: - resolution: {integrity: sha512-B1z/lOmex7i09NASq8+2WG9rZLzGXXOSkpwGOpaNIvOs4awyW4RMgpVBjt8r2wvvHwhEBEsOr2pGYpulnmAxYQ==} + convex@1.48.0: + resolution: {integrity: sha512-M9jEgTvCN8ziP/VgQQUH+j0ijM7g+NmQd8tFlvETK8/HMC/aaqHZwcu4hBO30X5DV2VIyYK4mOoLQGIb1766ew==} engines: {node: '>=20.0.0', npm: '>=7.0.0'} hasBin: true peerDependencies: @@ -2042,17 +2042,17 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@samebase/alchemy-convex@0.13.0(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.47.0)(effect@4.0.5)': + '@samebase/alchemy-convex@0.14.0(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.48.0)(effect@4.0.5)': dependencies: '@convex-dev/platform': 0.1.11 '@effect/platform-node': 4.0.5(effect@4.0.5)(redis@6.3.0) alchemy: 2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0) - convex: 1.47.0 + convex: 1.48.0 effect: 4.0.5 - '@samebase/identity-broker@0.6.1(convex@1.47.0)': + '@samebase/identity-broker@0.6.1(convex@1.48.0)': dependencies: - convex: 1.47.0 + convex: 1.48.0 jose: 6.2.13 oauth4webapi: 3.8.8 @@ -2342,7 +2342,7 @@ snapshots: convert-source-map@2.0.0: {} - convex@1.47.0: + convex@1.48.0: dependencies: esbuild: 0.27.0 prettier: 3.9.10 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9d35932..295e741 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,15 +6,10 @@ minimumReleaseAgeIgnoreMissingTime: false # Keep release-age exceptions exact so later releases do not bypass the cooling period. minimumReleaseAgeExclude: # Published 2026-10-30. Remove on 2026-11-06, when it is older than the seven-day release age. - - "@samebase/alchemy-convex@0.13.0" - # Published 2026-10-29. Remove on 2026-11-05, when it is older than the seven-day release age. - - "@samebase/alchemy-convex@0.12.0" + - "@samebase/alchemy-convex@0.14.0" # Reviewed first-party release, published 2026-10-29 by samebase/identity-broker release.yml. # Remove after 2026-11-05, when the 7-day cooling period is complete. - "@samebase/identity-broker@0.6.1" - # Reviewed first-party release: the Alchemy provider that deploys this hub and keeps its state in - # the team store, published 2026-10-27 15:18 UTC. Remove after 2026-11-03 15:19 UTC. - - "@samebase/alchemy-convex@0.11.0" # Reviewed releases: the Alchemy that alchemy-convex 0.11.0 needs, published 2026-10-24 00:43 UTC. # Remove after 2026-10-31 00:44 UTC. - "alchemy@2.0.0-beta.84" diff --git a/scripts/convex-cli.ts b/scripts/convex-cli.ts index 6d69004..eb03d6f 100644 --- a/scripts/convex-cli.ts +++ b/scripts/convex-cli.ts @@ -32,8 +32,12 @@ export function runConvex(args: string[], options: { input?: string } = {}) { }); } -/** Whether the deployment has the variable at all. `env list` prints one NAME=value line each. */ +/** + * Whether the deployment has the variable at all. The names only: a plain `env list` prints values + * or names, by whether the CLI detects an AI agent, and a wrong answer here makes keys:init replace + * a key set. + */ export async function hasVariable(name: string, selection: string[]) { - const listed = await runConvex(["env", "list", ...selection]); - return listed.stdout.split("\n").some((line) => line.startsWith(`${name}=`)); + const listed = await runConvex(["env", "list", "--names-only", ...selection]); + return listed.stdout.split("\n").includes(name); } From 1f649ffbd43e1f3f80fc5f19995ce72e15475913 Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 10:56:23 +0200 Subject: [PATCH 2/9] Serve the Alchemy state store from the hub The app mounts the alchemyState component without a name and exports the store functions with the host "base", so one deployment can run the hub and hold the state of the team. The exported functions ship as TypeScript and read identity claims with dot access, so the Convex project stops checking noPropertyAccessFromIndexSignature. Co-Authored-By: Claude Opus 5.5 --- convex/_generated/api.d.ts | 5 +++++ convex/auth.config.ts | 5 +++++ convex/convex.config.ts | 5 ++++- convex/reader.ts | 5 +++++ convex/state.ts | 21 +++++++++++++++++++++ convex/tsconfig.json | 4 +++- 6 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 convex/auth.config.ts create mode 100644 convex/reader.ts create mode 100644 convex/state.ts diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 07dada9..172e990 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -12,6 +12,8 @@ import type * as connectors from "../connectors.js"; import type * as crons from "../crons.js"; import type * as http from "../http.js"; import type * as identityBroker from "../identityBroker.js"; +import type * as reader from "../reader.js"; +import type * as state from "../state.js"; import type { ApiFromModules, @@ -24,6 +26,8 @@ declare const fullApi: ApiFromModules<{ crons: typeof crons; http: typeof http; identityBroker: typeof identityBroker; + reader: typeof reader; + state: typeof state; }>; /** @@ -54,4 +58,5 @@ export declare const internal: FilterApi< export declare const components: { identityBroker: import("@samebase/identity-broker/_generated/component.js").ComponentApi<"identityBroker">; + alchemyState: import("@samebase/alchemy-convex/state/_generated/component.js").ComponentApi<"alchemyState">; }; diff --git a/convex/auth.config.ts b/convex/auth.config.ts new file mode 100644 index 0000000..3d666f3 --- /dev/null +++ b/convex/auth.config.ts @@ -0,0 +1,5 @@ +import { authConfig } from "@samebase/alchemy-convex/state/auth.config"; +import { env } from "./_generated/server"; + +// Verifies the read tokens of `reader:authorize`. +export default authConfig(env.CONVEX_CLOUD_URL); diff --git a/convex/convex.config.ts b/convex/convex.config.ts index ef71670..3bf80a4 100644 --- a/convex/convex.config.ts +++ b/convex/convex.config.ts @@ -1,3 +1,4 @@ +import alchemyState from "@samebase/alchemy-convex/state/convex.config"; import identityBroker from "@samebase/identity-broker/convex.config"; import { defineApp } from "convex/server"; import { v } from "convex/values"; @@ -9,7 +10,7 @@ const app = defineApp({ env: { /** The key set and the pairwise salt: `npx identity-broker keys`. */ IDENTITY_BROKER_KEYS: v.string(), - /** The issuer URL once a custom domain exists. Without it, the deployment's own convex.site URL. */ + /** The issuer URL, which the stack sets. Without it, the deployment's own convex.site URL. */ ISSUER: v.optional(v.string()), /** * The Google OAuth client of the team, whose redirect URIs include @@ -28,5 +29,7 @@ const app = defineApp({ }); app.use(identityBroker); +// Without a name, so that this base serves the rows that the seed of the store wrote. +app.use(alchemyState); export default app; diff --git a/convex/reader.ts b/convex/reader.ts new file mode 100644 index 0000000..56e8799 --- /dev/null +++ b/convex/reader.ts @@ -0,0 +1,5 @@ +import { readerFunctions } from "@samebase/alchemy-convex/state/functions"; +import { components } from "./_generated/api"; + +// The redacted read of one repository's state, for a reader with a token of `authorize`. +export const { authorize, repository } = readerFunctions(components.alchemyState); diff --git a/convex/state.ts b/convex/state.ts new file mode 100644 index 0000000..02931fa --- /dev/null +++ b/convex/state.ts @@ -0,0 +1,21 @@ +import { stateFunctions } from "@samebase/alchemy-convex/state/functions"; +import { components } from "./_generated/api"; + +// The Alchemy state store of the team. A push replaces every function of the deployment, so this +// base serves the functions of the seed that Convex.state() pushes elsewhere. Each export has its +// name here, so that the Convex analysis finds each function. +export const { + version, + host, + stacks, + stages, + list, + get, + replaced, + set, + remove, + removeStack, + getOutput, + setOutput, + readStack, +} = stateFunctions(components.alchemyState, "base"); diff --git a/convex/tsconfig.json b/convex/tsconfig.json index e9f48b4..b0048fe 100644 --- a/convex/tsconfig.json +++ b/convex/tsconfig.json @@ -11,7 +11,9 @@ "noUncheckedSideEffectImports": true, "noImplicitReturns": true, "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, + /* The state functions of @samebase/alchemy-convex ship as TypeScript, so this project compiles + * them, and they read the claims of a UserIdentity with dot access. */ + "noPropertyAccessFromIndexSignature": false, "noUncheckedIndexedAccess": false, "allowUnreachableCode": false, "allowUnusedLabels": false, From b356b3e3d7811bde25295c254be3fb802cd9c6ba Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 10:56:23 +0200 Subject: [PATCH 3/9] Deploy the base to the project samebase as the stack samebase/base A new constant stack name starts with empty state, so no run reads the rows of samebase/identity-hub, which hold secret-chickadee-10. The stack adopts the project and its production deployment and retains both. It sets ISSUER. The workflow loses its adopt input: the stack carries the adoption. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/alchemy.yml | 16 ++------ AGENTS.md | 16 +++++--- README.md | 76 ++++++++++++++++++++++++----------- alchemy.run.ts | 47 ++++++++++------------ 4 files changed, 88 insertions(+), 67 deletions(-) diff --git a/.github/workflows/alchemy.yml b/.github/workflows/alchemy.yml index 4014785..6c4907f 100644 --- a/.github/workflows/alchemy.yml +++ b/.github/workflows/alchemy.yml @@ -1,22 +1,17 @@ -# Deploys with Alchemy (alchemy.run.ts): a push to main deploys the stage `prod`, the production -# deployment, and nothing else; the hub has no previews. The state is in the `alchemy-state` -# project of the team, under the stack name GITHUB_REPOSITORY. The run needs the team token +# Deploys with Alchemy (alchemy.run.ts): a push to main deploys the stage `prod` of the stack +# `samebase/base`, the production deployment of the Convex project `samebase`, and nothing else; +# the hub has no previews. The state is in that same deployment. The run needs the team token # CONVEX_ACCESS_TOKEN, with which it mints the deploy key itself, plus the Google and GitHub # clients, which the stack hands to the deployment. Only the Alchemy step gets the secrets. name: alchemy on: push: { branches: [main] } - # Run once on main with adopt to take over a project with the name of the repository that - # exists already. - workflow_dispatch: { inputs: { adopt: { type: boolean, default: false } } } permissions: { contents: read } # One deploy at a time: the state store has no lock. A newer push waits, so the last deploy is the # newest commit. concurrency: { group: alchemy-prod, queue: max, cancel-in-progress: false } jobs: alchemy: - # A dispatch from another branch deploys nothing: only main is production. - if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -48,11 +43,8 @@ jobs: fi - name: Deploy production if: steps.keys.outputs.skip != 'true' - run: | - if [ "$ADOPT" = "true" ]; then set -- --adopt; fi - pnpm exec alchemy deploy --stage prod --yes --no-input "$@" + run: pnpm exec alchemy deploy --stage prod --yes --no-input env: - ADOPT: ${{ inputs.adopt }} CONVEX_ACCESS_TOKEN: ${{ secrets.CONVEX_ACCESS_TOKEN }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} diff --git a/AGENTS.md b/AGENTS.md index 4bbacee..c96c2fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,15 +1,21 @@ # AGENTS.md -A Convex app that mounts the identity-broker component and is the sign-in hub of one team. See -README.md. The component's own rules live in its repository. +A Convex app that mounts the identity-broker and alchemyState components: the base app of one team, +which serves its sign-in hub and its Alchemy state. See README.md. Each component's own rules live +in its repository. - `convex/connectors.ts` lists the providers the hub offers. A provider that needs credentials also gets its variables declared in `convex/convex.config.ts`. - `convex/http.ts` mounts the broker; `convex/identityBroker.ts` exports the admin functions for `npx convex run`; `convex/crons.ts` runs the cleanup. -- `alchemy.run.ts` is the stack (project, production deployment, push); `scripts/check-keys.ts` - checks the key set inside the `deploy` script the push runs and never creates it; - `scripts/init-keys.ts` creates it once, by hand. +- `convex/state.ts`, `convex/reader.ts`, and `convex/auth.config.ts` serve the Alchemy state store, + from the exports of `@samebase/alchemy-convex`. A push replaces every function, so a function that + leaves these files leaves every stack of the team without its state. +- `alchemy.run.ts` is the stack `samebase/base` (adopted project, adopted production deployment, + push); `scripts/check-keys.ts` checks the key set inside the `deploy` script the push runs and + never creates it; `scripts/init-keys.ts` creates it once, by hand. +- Never use `npx convex import --replace-all` on this deployment, also not with `--component`: it + deletes the rows of the other components. - Never use the U+2014 em dash character. No `any`, no type assertions except `as const`. - `pnpm install`, then `pnpm run check`. `convex/_generated` is committed; `npx convex dev --once` refreshes it. diff --git a/README.md b/README.md index 8de7378..8637b6f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,25 @@ # identity-hub -The sign-in hub of the Samebase team on [Convex](https://convex.dev): one deployment that holds the -team's Google and GitHub connections once and signs users in to all its apps, through the -[identity-broker](https://github.com/samebase/identity-broker) component. It keeps no user accounts; -each app creates its own user from the `id_token` it gets. +The base app of the Samebase team on [Convex](https://convex.dev): the code of the production +deployment of the project `samebase`, which holds the team-level components. -The repository is public and holds no secrets or team values: everything specific to a hub is a -deployment variable or a line in `convex/connectors.ts`, so a copy of it runs another team's hub. +- `identityBroker`, from [identity-broker](https://github.com/samebase/identity-broker), is the + sign-in hub. It holds the team's Google and GitHub connections once and signs users in to all its + apps. It keeps no user accounts; each app creates its own user from the `id_token` it gets. +- `alchemyState`, from [alchemy-convex](https://github.com/samebase/alchemy-convex), is the Alchemy + state of every stack of the team. + +The repository is public and holds no secrets. The values of the team are the constants in +`alchemy.run.ts` and the lines in `convex/connectors.ts`, so a copy of it with other values runs +another team's base. ## Deploy `alchemy.run.ts` is the stack, in the shape of every app of the team with one stage: `prod` is the -production deployment of the Convex project named after this repository, and the push is the -`deploy` package script, which checks the key set and pushes the functions. The state is in the -`alchemy-state` project of the team. The hub has no previews: no app points at one, and Google +production deployment of the Convex project `samebase`, and the push is the `deploy` package script, +which checks the key set and pushes the functions. The stack is named `samebase/base`, a constant +that does not follow the repository name. Its state is in the deployment that it deploys, next to +the state of the other stacks of the team. The hub has no previews: no app points at one, and Google cannot sign in on one, since only the production callback is registered at Google. `.github/workflows/alchemy.yml` deploys each push to `main`; pull requests run the checks only. @@ -28,21 +34,37 @@ cannot sign in on one, since only the production callback is registered at Googl and refuses a deployment that has the variable. Every deploy then checks the variable before the push and stops when it is missing or holds anything that is not a key set; no deploy ever creates or replaces it. -4. Merge to `main`. The first run of the workflow on a project that exists already needs `adopt` - checked (Run workflow on the Actions tab). From a laptop, `pnpm exec alchemy deploy --stage prod` - runs the same stack with the Convex CLI login. +4. Merge to `main`. The stack adopts the project and its production deployment, so no run needs + `--adopt`, and it retains both on destroy. Adoption replaces the code and sets the variables of + the stack. It does not change other variables, the data, or the project. From a laptop, + `pnpm exec alchemy deploy --stage prod` runs the same stack with the Convex CLI login. Back up `IDENTITY_BROKER_KEYS` somewhere safe. It holds the signing keys and the pairwise salt: a hub that loses it issues new subjects to every user of every app. An established hub that lost the variable gets the backup restored by hand; its deploys stop until then. +## Base and seed + +A push replaces every function of a deployment. `Convex.state()` of `@samebase/alchemy-convex` +pushes a small store app, a seed, to a project `samebase` that has no base, and it never pushes over +a base. Thus this app serves the same store functions as the seed: `convex/state.ts` with the host +`base`, `convex/reader.ts`, and `convex/auth.config.ts`. The `alchemyState` component has no name in +`convex/convex.config.ts`, so this app serves the rows that the seed wrote. + +The store version of this base is the store version of the `@samebase/alchemy-convex` in +`package.json`. The stack and the app share that one pin. When the store version rises, this +repository moves first and each consumer follows. A consumer of the team fails on a base that is +older or newer than the consumer. + +Push a base for the first time when no stack of the team runs: the push of a seed has no lock. + ## Issuer -The hub answers at the deployment's `.convex.site` URL. Set `ISSUER` to a custom domain before the -first app registers: the issuer is the name every app trusts, in discovery, in every `id_token`, and -in the callback URLs registered with GitHub and Google. Changing it later means changing -`HUB_ISSUER` on every app's deployments and the callback URLs at every provider in one go; the app -ids, the clients and the salt stay, so the subjects do not change. +The hub answers at the deployment's `.convex.site` URL and at the custom domain `auth.samebase.com`. +The stack sets `ISSUER` to that domain: the issuer is the name every app trusts, in discovery, in +every `id_token`, and in the callback URLs registered with GitHub and Google. Changing it later +means changing `HUB_ISSUER` on every app's deployments and the callback URLs at every provider in +one go; the app ids, the clients and the salt stay, so the subjects do not change. ## Connectors @@ -76,13 +98,19 @@ which also writes the client into the deployment's variables; see the component' ## Operate -The admin functions are internal: `npx convex run --prod identityBroker:`. The deploy key of -the hub is an administrator credential (it can read and replace the keys and run every internal -function); keep it out of every app's CI. The Convex CLI ignores `--prod` when `CONVEX_DEPLOY_KEY` -names another deployment, so unset it before an admin command. A cron runs the broker's cleanup -daily: expired requests and codes, and sign-in records older than 90 days. Idle preview clients are -kept: the broker prunes them only when the host sets `previewClientIdleDays`, and this hub does not, -because a consumer's registration resource does not register a pruned client again. +The admin functions are internal: `npx convex run --prod identityBroker:`, from a checkout +that selects the project `samebase`. The deploy key of the base is an administrator credential: it +can read and replace the keys, read every secret in the Alchemy state of the team, and run every +internal function. Keep it out of every app's CI. The Convex CLI ignores `--prod` when +`CONVEX_DEPLOY_KEY` names another deployment, so unset it before an admin command. A cron runs the +broker's cleanup daily: expired requests and codes, and sign-in records older than 90 days. Idle +preview clients are kept: the broker prunes them only when the host sets `previewClientIdleDays`, +and this hub does not, because a consumer's registration resource does not register a pruned client +again. + +Never run `npx convex import --replace-all` on this deployment, also not with `--component`: it +also deletes the rows of the other components. To restore the tables of one component, import a ZIP +of that component with `--component --replace`. ## License diff --git a/alchemy.run.ts b/alchemy.run.ts index 7ad3673..b6e504e 100644 --- a/alchemy.run.ts +++ b/alchemy.run.ts @@ -1,19 +1,25 @@ -// The stack of this hub, in the shape of samebase/base-alchemy with one stage: `prod` is the -// production deployment of the Convex project of this repository, and nothing else is deployed. -// The hub has no previews: no app points at one, and Google cannot sign in on one, since only the -// production callback is registered at Google. The push is the `deploy` package script, which -// checks the key set and pushes the functions. The stack is named after the repository, -// `owner/name`, and its Convex project after `name`. The state is in the `alchemy-state` project -// of the team. +// The stack of the team's base app, with one stage: `prod` is the production deployment of the +// Convex project `samebase`, and nothing else is deployed. The base holds the team-level +// components of that deployment: the sign-in hub (`identityBroker`) and the Alchemy state of every +// stack of the team (`alchemyState`). The hub has no previews: no app points at one, and Google +// cannot sign in on one, since only the production callback is registered at Google. The push is +// the `deploy` package script, which checks the key set and pushes the functions. +// +// The stack name is a constant, so it does not follow the repository name, and it is not the old +// stack `samebase/identity-hub`, whose rows name the deployment that this base replaces. This +// stack never reads those rows. The project and the deployment exist already, and this stack did +// not create them, so both are adopted and retained. Adoption changes the variables of `env` and +// the code; it leaves every other variable, the data, and the project as they are. // // Every run needs the team token CONVEX_ACCESS_TOKEN, or a Convex CLI login (`npx convex login`) // on a laptop; it mints the deploy key itself. Every run also reads the Google and GitHub client // ids and secrets and hands them to the deployment, for the two connectors: no connector variable -// is ever set by hand. +// is ever set by hand. IDENTITY_BROKER_KEYS is not in `env`: it stays on the deployment, and the +// `deploy` script stops when it is missing. // pnpm exec alchemy deploy --stage prod -import { execFileSync } from "node:child_process"; import * as Convex from "@samebase/alchemy-convex"; import * as Alchemy from "alchemy"; +import { adopt } from "alchemy/AdoptPolicy"; import * as RemovalPolicy from "alchemy/RemovalPolicy"; import * as Config from "effect/Config"; import * as Effect from "effect/Effect"; @@ -21,23 +27,12 @@ import * as Effect from "effect/Effect"; /** The Convex team that holds the project and the state. */ const team = "nicu"; -/** The GitHub repository, `owner/name`: GITHUB_REPOSITORY in Actions, else the origin remote. */ -const repository = - process.env["GITHUB_REPOSITORY"] ?? - /[:/]([^/:]+\/[^/]+?)(?:\.git)?$/.exec( - execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8" }).trim(), - )?.[1]; -if (repository === undefined) { - throw new Error("Set GITHUB_REPOSITORY to owner/name: the origin remote names no repository."); -} -const projectName = repository.slice(repository.indexOf("/") + 1); - export default Alchemy.Stack( - repository, + "samebase/base", { providers: Convex.providers(), state: Convex.state({ team }) }, Effect.gen(function* () { const { stage } = yield* Alchemy.Stack; - // One stage: any other name would create a second project and deployment with this name. + // One stage: any other name would push this code over the same production deployment. if (stage !== "prod") { throw new Error(`This stack has one stage, prod. The stage ${stage} is not deployed.`); } @@ -50,14 +45,14 @@ export default Alchemy.Stack( // The variables of the deployment. The secret is a prop, so the state store holds it next to // the deployment it serves. const env = { + ISSUER: "https://auth.samebase.com", GOOGLE_CLIENT_ID: yield* Config.String("GOOGLE_CLIENT_ID"), GOOGLE_CLIENT_SECRET: yield* Config.Redacted("GOOGLE_CLIENT_SECRET"), AUTH_GITHUB_CLIENT_ID: yield* Config.String("AUTH_GITHUB_CLIENT_ID"), AUTH_GITHUB_CLIENT_SECRET: yield* Config.Redacted("AUTH_GITHUB_CLIENT_SECRET"), }; - // The project is kept on destroy: it holds the hub's registrations and the key set. A project - // with this name that the stack did not create is taken over with `alchemy deploy --adopt`. - const project = yield* Convex.Project("Project", { team, name: projectName }).pipe( + const project = yield* Convex.Project("Project", { team, slug: "samebase" }).pipe( + adopt(true), RemovalPolicy.retain(), ); const deployment = yield* Convex.Deployment("Deployment", { @@ -65,7 +60,7 @@ export default Alchemy.Stack( type: "prod", source, env, - }); + }).pipe(adopt(true), RemovalPolicy.retain()); return { url: deployment.siteUrl }; }), ); From 6b6e1c0a2c1760200edce0f7e8dec8ac11a360b3 Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 10:58:48 +0200 Subject: [PATCH 4/9] Format the README Co-Authored-By: Claude Opus 5.5 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8637b6f..698dab8 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,9 @@ preview clients are kept: the broker prunes them only when the host sets `previe and this hub does not, because a consumer's registration resource does not register a pruned client again. -Never run `npx convex import --replace-all` on this deployment, also not with `--component`: it -also deletes the rows of the other components. To restore the tables of one component, import a ZIP -of that component with `--component --replace`. +Never run `npx convex import --replace-all` on this deployment, also not with `--component`: it also +deletes the rows of the other components. To restore the tables of one component, import a ZIP of +that component with `--component --replace`. ## License From 6f9c82303423ce5163e4d694cb957335836f509b Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 11:02:42 +0200 Subject: [PATCH 5/9] Name the stack samebase/identity-hub/base samebase/base is the starter template's repository. A stack under this repository's name is found by the repository reader, whose run records name the same repository, so the Samebase view keeps showing it and the repository needs no rename. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/alchemy.yml | 2 +- AGENTS.md | 2 +- README.md | 2 +- alchemy.run.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/alchemy.yml b/.github/workflows/alchemy.yml index 6c4907f..d54342d 100644 --- a/.github/workflows/alchemy.yml +++ b/.github/workflows/alchemy.yml @@ -1,5 +1,5 @@ # Deploys with Alchemy (alchemy.run.ts): a push to main deploys the stage `prod` of the stack -# `samebase/base`, the production deployment of the Convex project `samebase`, and nothing else; +# `samebase/identity-hub/base`, the production deployment of the Convex project `samebase`, and nothing else; # the hub has no previews. The state is in that same deployment. The run needs the team token # CONVEX_ACCESS_TOKEN, with which it mints the deploy key itself, plus the Google and GitHub # clients, which the stack hands to the deployment. Only the Alchemy step gets the secrets. diff --git a/AGENTS.md b/AGENTS.md index c96c2fd..8f4df81 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ in its repository. - `convex/state.ts`, `convex/reader.ts`, and `convex/auth.config.ts` serve the Alchemy state store, from the exports of `@samebase/alchemy-convex`. A push replaces every function, so a function that leaves these files leaves every stack of the team without its state. -- `alchemy.run.ts` is the stack `samebase/base` (adopted project, adopted production deployment, +- `alchemy.run.ts` is the stack `samebase/identity-hub/base` (adopted project, adopted production deployment, push); `scripts/check-keys.ts` checks the key set inside the `deploy` script the push runs and never creates it; `scripts/init-keys.ts` creates it once, by hand. - Never use `npx convex import --replace-all` on this deployment, also not with `--component`: it diff --git a/README.md b/README.md index 698dab8..3111570 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ another team's base. `alchemy.run.ts` is the stack, in the shape of every app of the team with one stage: `prod` is the production deployment of the Convex project `samebase`, and the push is the `deploy` package script, -which checks the key set and pushes the functions. The stack is named `samebase/base`, a constant +which checks the key set and pushes the functions. The stack is named `samebase/identity-hub/base`, a constant that does not follow the repository name. Its state is in the deployment that it deploys, next to the state of the other stacks of the team. The hub has no previews: no app points at one, and Google cannot sign in on one, since only the production callback is registered at Google. diff --git a/alchemy.run.ts b/alchemy.run.ts index b6e504e..de866c8 100644 --- a/alchemy.run.ts +++ b/alchemy.run.ts @@ -28,7 +28,7 @@ import * as Effect from "effect/Effect"; const team = "nicu"; export default Alchemy.Stack( - "samebase/base", + "samebase/identity-hub/base", { providers: Convex.providers(), state: Convex.state({ team }) }, Effect.gen(function* () { const { stage } = yield* Alchemy.Stack; From 355cc892871c28b90aab69952c62346d00cbf6d9 Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 11:03:36 +0200 Subject: [PATCH 6/9] Rewrap the docs after the longer stack name Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8f4df81..82a61aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,9 +11,9 @@ in its repository. - `convex/state.ts`, `convex/reader.ts`, and `convex/auth.config.ts` serve the Alchemy state store, from the exports of `@samebase/alchemy-convex`. A push replaces every function, so a function that leaves these files leaves every stack of the team without its state. -- `alchemy.run.ts` is the stack `samebase/identity-hub/base` (adopted project, adopted production deployment, - push); `scripts/check-keys.ts` checks the key set inside the `deploy` script the push runs and - never creates it; `scripts/init-keys.ts` creates it once, by hand. +- `alchemy.run.ts` is the stack `samebase/identity-hub/base` (adopted project, adopted production + deployment, push); `scripts/check-keys.ts` checks the key set inside the `deploy` script the push + runs and never creates it; `scripts/init-keys.ts` creates it once, by hand. - Never use `npx convex import --replace-all` on this deployment, also not with `--component`: it deletes the rows of the other components. - Never use the U+2014 em dash character. No `any`, no type assertions except `as const`. diff --git a/README.md b/README.md index 3111570..8fba5e9 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ another team's base. `alchemy.run.ts` is the stack, in the shape of every app of the team with one stage: `prod` is the production deployment of the Convex project `samebase`, and the push is the `deploy` package script, -which checks the key set and pushes the functions. The stack is named `samebase/identity-hub/base`, a constant -that does not follow the repository name. Its state is in the deployment that it deploys, next to -the state of the other stacks of the team. The hub has no previews: no app points at one, and Google -cannot sign in on one, since only the production callback is registered at Google. +which checks the key set and pushes the functions. The stack is named `samebase/identity-hub/base`, +a constant that does not follow the repository name. Its state is in the deployment that it deploys, +next to the state of the other stacks of the team. The hub has no previews: no app points at one, +and Google cannot sign in on one, since only the production callback is registered at Google. `.github/workflows/alchemy.yml` deploys each push to `main`; pull requests run the checks only. 1. In the repository, set five secrets: `CONVEX_ACCESS_TOKEN`, the team's Convex access token, with From a32beb20f489971c3e9369038b15cc8f98d2331b Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 11:10:59 +0200 Subject: [PATCH 7/9] The stack follows the repository: samebase/team-base The repository is renamed to samebase/team-base, so the stack is named after it, like every other stack of the team, and the hard-coded name goes. The old stack samebase/identity-hub keeps its rows, and this stack never reads them. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/alchemy.yml | 2 +- AGENTS.md | 2 +- README.md | 13 +++++++------ alchemy.run.ts | 21 ++++++++++++++++----- package.json | 2 +- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.github/workflows/alchemy.yml b/.github/workflows/alchemy.yml index d54342d..89811b6 100644 --- a/.github/workflows/alchemy.yml +++ b/.github/workflows/alchemy.yml @@ -1,5 +1,5 @@ # Deploys with Alchemy (alchemy.run.ts): a push to main deploys the stage `prod` of the stack -# `samebase/identity-hub/base`, the production deployment of the Convex project `samebase`, and nothing else; +# `samebase/team-base`, the production deployment of the Convex project `samebase`, and nothing else; # the hub has no previews. The state is in that same deployment. The run needs the team token # CONVEX_ACCESS_TOKEN, with which it mints the deploy key itself, plus the Google and GitHub # clients, which the stack hands to the deployment. Only the Alchemy step gets the secrets. diff --git a/AGENTS.md b/AGENTS.md index 82a61aa..70d37fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ in its repository. - `convex/state.ts`, `convex/reader.ts`, and `convex/auth.config.ts` serve the Alchemy state store, from the exports of `@samebase/alchemy-convex`. A push replaces every function, so a function that leaves these files leaves every stack of the team without its state. -- `alchemy.run.ts` is the stack `samebase/identity-hub/base` (adopted project, adopted production +- `alchemy.run.ts` is the stack `samebase/team-base` (adopted project, adopted production deployment, push); `scripts/check-keys.ts` checks the key set inside the `deploy` script the push runs and never creates it; `scripts/init-keys.ts` creates it once, by hand. - Never use `npx convex import --replace-all` on this deployment, also not with `--component`: it diff --git a/README.md b/README.md index 8fba5e9..f365030 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# identity-hub +# team-base The base app of the Samebase team on [Convex](https://convex.dev): the code of the production deployment of the project `samebase`, which holds the team-level components. @@ -17,11 +17,12 @@ another team's base. `alchemy.run.ts` is the stack, in the shape of every app of the team with one stage: `prod` is the production deployment of the Convex project `samebase`, and the push is the `deploy` package script, -which checks the key set and pushes the functions. The stack is named `samebase/identity-hub/base`, -a constant that does not follow the repository name. Its state is in the deployment that it deploys, -next to the state of the other stacks of the team. The hub has no previews: no app points at one, -and Google cannot sign in on one, since only the production callback is registered at Google. -`.github/workflows/alchemy.yml` deploys each push to `main`; pull requests run the checks only. +which checks the key set and pushes the functions. The stack is named after the repository, +`samebase/team-base`, a constant that does not follow the repository name. Its state is in the +deployment that it deploys, next to the state of the other stacks of the team. The hub has no +previews: no app points at one, and Google cannot sign in on one, since only the production callback +is registered at Google. `.github/workflows/alchemy.yml` deploys each push to `main`; pull requests +run the checks only. 1. In the repository, set five secrets: `CONVEX_ACCESS_TOKEN`, the team's Convex access token, with which the run mints the deploy key itself; `GOOGLE_CLIENT_ID` with `GOOGLE_CLIENT_SECRET`, the diff --git a/alchemy.run.ts b/alchemy.run.ts index de866c8..fd81e82 100644 --- a/alchemy.run.ts +++ b/alchemy.run.ts @@ -5,10 +5,10 @@ // cannot sign in on one, since only the production callback is registered at Google. The push is // the `deploy` package script, which checks the key set and pushes the functions. // -// The stack name is a constant, so it does not follow the repository name, and it is not the old -// stack `samebase/identity-hub`, whose rows name the deployment that this base replaces. This -// stack never reads those rows. The project and the deployment exist already, and this stack did -// not create them, so both are adopted and retained. Adoption changes the variables of `env` and +// The stack is named after the repository, `owner/name`, as every stack of the team is. The +// repository was `samebase/identity-hub`, and the rows of that old stack name the deployment that +// this base replaces. This stack never reads those rows. The project and the deployment exist +// already, and this stack did not create them, so both are adopted and retained. Adoption changes the variables of `env` and // the code; it leaves every other variable, the data, and the project as they are. // // Every run needs the team token CONVEX_ACCESS_TOKEN, or a Convex CLI login (`npx convex login`) @@ -17,6 +17,7 @@ // is ever set by hand. IDENTITY_BROKER_KEYS is not in `env`: it stays on the deployment, and the // `deploy` script stops when it is missing. // pnpm exec alchemy deploy --stage prod +import { execFileSync } from "node:child_process"; import * as Convex from "@samebase/alchemy-convex"; import * as Alchemy from "alchemy"; import { adopt } from "alchemy/AdoptPolicy"; @@ -27,8 +28,18 @@ import * as Effect from "effect/Effect"; /** The Convex team that holds the project and the state. */ const team = "nicu"; +/** The GitHub repository, `owner/name`: GITHUB_REPOSITORY in Actions, else the origin remote. */ +const repository = + process.env["GITHUB_REPOSITORY"] ?? + /[:/]([^/:]+\/[^/]+?)(?:\.git)?$/.exec( + execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8" }).trim(), + )?.[1]; +if (repository === undefined) { + throw new Error("Set GITHUB_REPOSITORY to owner/name: the origin remote names no repository."); +} + export default Alchemy.Stack( - "samebase/identity-hub/base", + repository, { providers: Convex.providers(), state: Convex.state({ team }) }, Effect.gen(function* () { const { stage } = yield* Alchemy.Stack; diff --git a/package.json b/package.json index d03ad45..85870ac 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "identity-hub", + "name": "team-base", "private": true, "type": "module", "scripts": { From 956471c81feafe7d14da4f2e5f91066a6beca585 Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 11:11:20 +0200 Subject: [PATCH 8/9] Drop the stale stack sentence from the README Co-Authored-By: Claude Opus 5.5 --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f365030..f6f8b13 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,10 @@ another team's base. `alchemy.run.ts` is the stack, in the shape of every app of the team with one stage: `prod` is the production deployment of the Convex project `samebase`, and the push is the `deploy` package script, which checks the key set and pushes the functions. The stack is named after the repository, -`samebase/team-base`, a constant that does not follow the repository name. Its state is in the -deployment that it deploys, next to the state of the other stacks of the team. The hub has no -previews: no app points at one, and Google cannot sign in on one, since only the production callback -is registered at Google. `.github/workflows/alchemy.yml` deploys each push to `main`; pull requests -run the checks only. +`samebase/team-base`. Its state is in the deployment that it deploys, next to the state of the other +stacks of the team. The hub has no previews: no app points at one, and Google cannot sign in on one, +since only the production callback is registered at Google. `.github/workflows/alchemy.yml` deploys +each push to `main`; pull requests run the checks only. 1. In the repository, set five secrets: `CONVEX_ACCESS_TOKEN`, the team's Convex access token, with which the run mints the deploy key itself; `GOOGLE_CLIENT_ID` with `GOOGLE_CLIENT_SECRET`, the From 117a14f9b5d6f80278d5cc2d0865c26ecdc0ffae Mon Sep 17 00:00:00 2001 From: Nicu Chiciuc Date: Fri, 30 Oct 2026 11:22:34 +0200 Subject: [PATCH 9/9] Move to alchemy-convex 0.14.1 and drop the tsconfig workaround 0.14.1 reads the reader's claims with bracket access, so convex/tsconfig.json keeps noPropertyAccessFromIndexSignature. Co-Authored-By: Claude Opus 5.5 --- alchemy.run.ts | 6 +++--- convex/tsconfig.json | 4 +--- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- pnpm-workspace.yaml | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/alchemy.run.ts b/alchemy.run.ts index fd81e82..d02a401 100644 --- a/alchemy.run.ts +++ b/alchemy.run.ts @@ -6,9 +6,9 @@ // the `deploy` package script, which checks the key set and pushes the functions. // // The stack is named after the repository, `owner/name`, as every stack of the team is. The -// repository was `samebase/identity-hub`, and the rows of that old stack name the deployment that -// this base replaces. This stack never reads those rows. The project and the deployment exist -// already, and this stack did not create them, so both are adopted and retained. Adoption changes the variables of `env` and +// repository was `samebase/identity-hub`, and this stack never reads the rows of that old stack. +// The project and the deployment exist already, and this stack did not create them, so both are +// adopted and retained. Adoption changes the variables of `env` and // the code; it leaves every other variable, the data, and the project as they are. // // Every run needs the team token CONVEX_ACCESS_TOKEN, or a Convex CLI login (`npx convex login`) diff --git a/convex/tsconfig.json b/convex/tsconfig.json index b0048fe..e9f48b4 100644 --- a/convex/tsconfig.json +++ b/convex/tsconfig.json @@ -11,9 +11,7 @@ "noUncheckedSideEffectImports": true, "noImplicitReturns": true, "noImplicitOverride": true, - /* The state functions of @samebase/alchemy-convex ship as TypeScript, so this project compiles - * them, and they read the claims of a UserIdentity with dot access. */ - "noPropertyAccessFromIndexSignature": false, + "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": false, "allowUnreachableCode": false, "allowUnusedLabels": false, diff --git a/package.json b/package.json index 85870ac..dd0acdb 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint": "vp lint" }, "dependencies": { - "@samebase/alchemy-convex": "0.14.0", + "@samebase/alchemy-convex": "0.14.1", "@samebase/identity-broker": "0.6.1", "convex": "1.48.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1aded2e..a81e1e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,8 +192,8 @@ importers: .: dependencies: '@samebase/alchemy-convex': - specifier: 0.14.0 - version: 0.14.0(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.48.0)(effect@4.0.5) + specifier: 0.14.1 + version: 0.14.1(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.48.0)(effect@4.0.5) '@samebase/identity-broker': specifier: 0.6.1 version: 0.6.1(convex@1.48.0) @@ -860,8 +860,8 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@samebase/alchemy-convex@0.14.0': - resolution: {integrity: sha512-hzTC0R5EeLnbyVidCxEGZEgfUxO0fTWhcAR1VmI6MA0ASfnZwu1umWU269NsPc7oAINJGREKB7JsSFh12NmOGQ==} + '@samebase/alchemy-convex@0.14.1': + resolution: {integrity: sha512-9K8OmSt5mhrSseOuVuBWQHLsm9GqNsXezFd8Is52OV+cuEgwkrDehoAp1f3qiob2kqLFk6o/PYHE0srV2W9n+Q==} engines: {node: '>=22'} peerDependencies: '@effect/platform-node': ^4.0.5 @@ -2042,7 +2042,7 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@samebase/alchemy-convex@0.14.0(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.48.0)(effect@4.0.5)': + '@samebase/alchemy-convex@0.14.1(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(alchemy@2.0.0-beta.84(@effect/platform-node@4.0.5(effect@4.0.5)(redis@6.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.19.2)(esbuild@0.27.0)(typescript@7.0.2)(yaml@2.9.1))(effect@4.0.5)(ws@8.22.0))(convex@1.48.0)(effect@4.0.5)': dependencies: '@convex-dev/platform': 0.1.11 '@effect/platform-node': 4.0.5(effect@4.0.5)(redis@6.3.0) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 295e741..42dc1e8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,7 +6,7 @@ minimumReleaseAgeIgnoreMissingTime: false # Keep release-age exceptions exact so later releases do not bypass the cooling period. minimumReleaseAgeExclude: # Published 2026-10-30. Remove on 2026-11-06, when it is older than the seven-day release age. - - "@samebase/alchemy-convex@0.14.0" + - "@samebase/alchemy-convex@0.14.1" # Reviewed first-party release, published 2026-10-29 by samebase/identity-broker release.yml. # Remove after 2026-11-05, when the 7-day cooling period is complete. - "@samebase/identity-broker@0.6.1"