From cca45a7e61c93c48f0f0b09ea2d5ae5576a8e351 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:17:46 +0000 Subject: [PATCH 1/7] wip(spec): refuse a stack datasource named default at authoring --- packages/spec/src/stack.zod.ts | 41 +++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/spec/src/stack.zod.ts b/packages/spec/src/stack.zod.ts index b6da75a5ed0..26a08a10d42 100644 --- a/packages/spec/src/stack.zod.ts +++ b/packages/spec/src/stack.zod.ts @@ -526,6 +526,41 @@ export type ArtifactPackageEntry = z.input; /** Post-parse shape of {@link ArtifactPackageEntry} — defaults applied, transforms run (ADR-0122). */ export type ArtifactPackageEntryParsed = z.infer; +/** + * The datasource name the HOST owns (#3826, #23634): the runtime declares and + * connects `default` itself (`DefaultDatasourcePlugin`, from `OS_DATABASE_URL` + * or the dev fallback), and `AppPlugin` refuses a stack that declares it in + * every boot posture — with a URL, with `--dev`, and on the standalone path. + * The comparison is the runtime's: the entry's `name` equals `default` exactly + * (the map form arrives here already normalized, key → `name`; every other + * spelling of the word is already refused by `DatasourceSchema.name`'s + * snake_case pattern). + * + * The refusal lives on the STACK's collection, ⛔ never on + * {@link DatasourceSchema}: that schema also documents the host's own + * `default` row (the registry, the admin list, the metadata door), which must + * keep parsing. Saying it here moves the runtime's refusal to the authoring + * doors — `defineStack`, `os validate` and publish — with the same sentence + * and the remedy, instead of after the boot's earlier plugins have started. + */ +const HOST_RESERVED_DATASOURCE_NAME = 'default'; + +function refuseHostReservedDatasourceName(datasources: readonly { name?: unknown }[], ctx: z.RefinementCtx): void { + datasources.forEach((datasource, index) => { + if (datasource?.name !== HOST_RESERVED_DATASOURCE_NAME) return; + ctx.addIssue({ + code: 'custom', + path: [index, 'name'], + message: + `This stack declares a datasource named '${HOST_RESERVED_DATASOURCE_NAME}' — that name is reserved for ` + + `the host's primary datasource, and every boot refuses a stack that declares it. To give the project ` + + `its own primary database, set OS_DATABASE_URL and omit this entry. To add a datasource of the stack's ` + + `own, declare it under another name (e.g. 'app_primary') and route objects to it explicitly (an ` + + `object's \`datasource\`, or a \`datasourceMapping\` rule).`, + }); + }); +} + /** * Every metadata COLLECTION `ObjectStackDefinitionSchema` declares, as one * named shape. @@ -560,7 +595,11 @@ export type ArtifactPackageEntryParsed = z.infer Date: Thu, 15 Oct 2026 22:18:45 +0000 Subject: [PATCH 2/7] test(spec): pin the stack's refusal of a datasource named default, with its two controls Claude-Session: https://claude.ai/code/session_01UvyD7nLUx7XGKKQVF37FY9 Co-authored-by: Claude --- ...tack-datasource-host-reserved-name.test.ts | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 packages/spec/src/stack-datasource-host-reserved-name.test.ts diff --git a/packages/spec/src/stack-datasource-host-reserved-name.test.ts b/packages/spec/src/stack-datasource-host-reserved-name.test.ts new file mode 100644 index 00000000000..6d6c918bed4 --- /dev/null +++ b/packages/spec/src/stack-datasource-host-reserved-name.test.ts @@ -0,0 +1,113 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * The stack's `datasources` collection refuses a datasource named `default` + * (#23634, triage's proxy ruling A). + * + * ## What was wrong + * + * `default` is the host's primary datasource: the runtime declares and + * connects it itself, and `AppPlugin` refuses a stack that declares it in every + * boot posture (with `OS_DATABASE_URL`, with `--dev`, on the standalone path). + * Yet `defineStack` and `os validate` accepted that stack, so the refusal first + * reached its author after the boot's earlier plugins had started. + * + * ## What is pinned + * + * - `defineStack` refuses the array form and the map form with the ADR-0112 + * envelope (`STACK_SCHEMA_INVALID`, 422), one issue located at the entry's + * `name`, whose text carries the runtime's sentence and both remedies; + * - the bare schema parse — the `os validate` door's — refuses it too; + * - the reserved set is the runtime's comparison, `name === 'default'`: + * every other spelling of the word is refused by the name pattern already, + * never by this rule; + * - CONTROL: a datasource named `fx_primary` passes; + * - CONTROL: the host's own `default` row still parses through + * `DatasourceSchema`, which the refusal deliberately does not sit on. + * + * The `os validate` exit-code pin lives in + * `packages/cli/test/stack-datasource-default-validate-door.test.ts`. + */ +import { describe, it, expect } from 'vitest'; +import { defineStack, ObjectStackDefinitionSchema } from './stack.zod'; +import { DatasourceSchema } from './data/datasource.zod'; + +type Envelope = Error & { + code?: string; + status?: number; + issues?: ReadonlyArray<{ code?: string; path?: readonly PropertyKey[]; message?: string }>; +}; + +function refusal(fn: () => unknown): Envelope | null { + try { + fn(); + return null; + } catch (e) { + return e as Envelope; + } +} + +const manifest = { id: 'com.example.fx', name: 'fx', version: '1.0.0', type: 'app' as const }; +const sqlite = (name: string) => ({ name, driver: 'sqlite', config: { filename: `${name}.db` }, autoConnect: true }); + +const RUNTIME_SENTENCE = "declares a datasource named 'default' — that name is reserved for the host's primary datasource"; + +function expectReservedNameIssue(issues: Envelope['issues'], path: readonly PropertyKey[]): void { + const hits = (issues ?? []).filter((i) => (i.message ?? '').includes(RUNTIME_SENTENCE)); + expect(hits).toHaveLength(1); + expect(hits[0]!.code).toBe('custom'); + expect(hits[0]!.path).toEqual(path); + expect(hits[0]!.message).toContain('OS_DATABASE_URL'); + expect(hits[0]!.message).toContain('declare it under another name'); +} + +describe('a stack datasource named `default` is refused at authoring', () => { + it('defineStack refuses the array form — STACK_SCHEMA_INVALID / 422 at datasources.0.name', () => { + const refused = refusal(() => defineStack({ manifest, datasources: [sqlite('default')] } as never)); + expect(refused?.code).toBe('STACK_SCHEMA_INVALID'); + expect(refused?.status).toBe(422); + expectReservedNameIssue(refused?.issues, ['datasources', 0, 'name']); + }); + + it('defineStack refuses the map form — the key normalizes to `name`', () => { + const { name: _name, ...body } = sqlite('default'); + const refused = refusal(() => defineStack({ manifest, datasources: { default: body } } as never)); + expect(refused?.code).toBe('STACK_SCHEMA_INVALID'); + expect(refused?.status).toBe(422); + expectReservedNameIssue(refused?.issues, ['datasources', 0, 'name']); + }); + + it('names the entry it refuses, beside a legal one', () => { + const refused = refusal(() => + defineStack({ manifest, datasources: [sqlite('fx_primary'), sqlite('default')] } as never), + ); + expect(refused?.code).toBe('STACK_SCHEMA_INVALID'); + expectReservedNameIssue(refused?.issues, ['datasources', 1, 'name']); + }); + + it('the bare schema parse (the os validate door) refuses it too', () => { + const result = ObjectStackDefinitionSchema.safeParse({ manifest, datasources: [sqlite('default')] }); + expect(result.success).toBe(false); + expectReservedNameIssue(result.error?.issues as Envelope['issues'], ['datasources', 0, 'name']); + }); + + it("the reserved set is the runtime's `name === 'default'`: other spellings meet the name pattern, not this rule", () => { + for (const spelling of ['Default', 'DEFAULT', ' default', 'default ']) { + const result = ObjectStackDefinitionSchema.safeParse({ manifest, datasources: [sqlite(spelling)] }); + expect(result.success, spelling).toBe(false); + const messages = (result.error?.issues ?? []).map((i) => i.message); + expect(messages.some((m) => m.includes(RUNTIME_SENTENCE)), spelling).toBe(false); + } + }); + + it('CONTROL: a datasource named `fx_primary` passes', () => { + const stack = defineStack({ manifest, datasources: [sqlite('fx_primary')] } as never); + expect(stack.datasources?.map((d) => d.name)).toEqual(['fx_primary']); + }); + + it("CONTROL: the host's own `default` row still parses through DatasourceSchema", () => { + const result = DatasourceSchema.safeParse(sqlite('default')); + expect(result.success, JSON.stringify(result.error?.issues)).toBe(true); + expect(result.data?.name).toBe('default'); + }); +}); From 65b857310e31edc72c69cfe75f708fb27abe0402 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:21:41 +0000 Subject: [PATCH 3/7] chore(spec): record the datasources collection's reserved-name refinement in the dropped-refinements ledger Claude-Session: https://claude.ai/code/session_01UvyD7nLUx7XGKKQVF37FY9 Co-authored-by: Claude --- packages/spec/dropped-refinements.baseline.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/spec/dropped-refinements.baseline.json b/packages/spec/dropped-refinements.baseline.json index 7f141d4ad44..0d31e5402e3 100644 --- a/packages/spec/dropped-refinements.baseline.json +++ b/packages/spec/dropped-refinements.baseline.json @@ -78,6 +78,7 @@ "manifest.datasets.element.include.element", "manifest.datasets.element.measures.element", "manifest.datasets.element.measures.element.filter", + "manifest.datasources", "manifest.datasources.element", "manifest.flows.element", "manifest.flows.element.errorHandling", @@ -194,6 +195,7 @@ "data.options[1].manifest.datasets.element.include.element", "data.options[1].manifest.datasets.element.measures.element", "data.options[1].manifest.datasets.element.measures.element.filter", + "data.options[1].manifest.datasources", "data.options[1].manifest.datasources.element", "data.options[1].manifest.flows.element", "data.options[1].manifest.flows.element.errorHandling", @@ -300,6 +302,7 @@ "options[1].manifest.datasets.element.include.element", "options[1].manifest.datasets.element.measures.element", "options[1].manifest.datasets.element.measures.element.filter", + "options[1].manifest.datasources", "options[1].manifest.datasources.element", "options[1].manifest.flows.element", "options[1].manifest.flows.element.errorHandling", @@ -361,6 +364,7 @@ "data.packages.element.options[1].manifest.datasets.element.include.element", "data.packages.element.options[1].manifest.datasets.element.measures.element", "data.packages.element.options[1].manifest.datasets.element.measures.element.filter", + "data.packages.element.options[1].manifest.datasources", "data.packages.element.options[1].manifest.datasources.element", "data.packages.element.options[1].manifest.flows.element", "data.packages.element.options[1].manifest.flows.element.errorHandling", From 022914349335a9c8db925b30195359b18653c5ac Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:23:32 +0000 Subject: [PATCH 4/7] feat(spec)!: register the D3 entry and changeset for the stack's reserved datasource name refusal Claude-Session: https://claude.ai/code/session_01UvyD7nLUx7XGKKQVF37FY9 Co-authored-by: Claude --- .../23634-stack-datasource-default-refused.md | 32 +++++++++++++++++++ ...k-datasource-host-reserved-name-refused.ts | 30 +++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .changeset/23634-stack-datasource-default-refused.md create mode 100644 packages/spec/src/migrations/entries/semantic/18.stack-datasource-host-reserved-name-refused.ts diff --git a/.changeset/23634-stack-datasource-default-refused.md b/.changeset/23634-stack-datasource-default-refused.md new file mode 100644 index 00000000000..28acd9d61cc --- /dev/null +++ b/.changeset/23634-stack-datasource-default-refused.md @@ -0,0 +1,32 @@ +--- +'@objectstack/spec': minor +--- + +feat(spec)!: a stack's `datasources` collection refuses a datasource named `default`, the name the host reserves for its primary datasource + +Clause-②: no (narrowing) + + + +**BREAKING**: an accept-set narrowing on one collection of the stack definition, shipped as `minor` under the launch-window convention for accept-set narrowings. No config that boots today is newly refused: every boot already refused this one. + +`default` is the host's primary datasource. The runtime declares and connects it itself, and the app plugin refuses a stack that declares it in every boot posture (with `OS_DATABASE_URL`, with `--dev`, and on the standalone path): + +> app 'APP_ID' declares a datasource named 'default' — that name is reserved for the host's primary datasource. + +`defineStack` and `os validate` accepted the same stack, so its author met that refusal only after the boot's earlier plugins had started. The stack's `datasources` collection now refuses the entry at authoring, located at `datasources.N.name` (the map form `datasources: { default: { … } }` is refused the same way), with the runtime's sentence and its remedy: + +> This stack declares a datasource named 'default' — that name is reserved for the host's primary datasource, and every boot refuses a stack that declares it. To give the project its own primary database, set OS_DATABASE_URL and omit this entry. To add a datasource of the stack's own, declare it under another name (e.g. 'app_primary') and route objects to it explicitly (an object's `datasource`, or a `datasourceMapping` rule). + +`DatasourceSchema` is unchanged: it also documents the host's own `default` row, which keeps parsing. + +### FROM → TO + +| before | what to write instead | +| --- | --- | +| `datasources: [{ name: 'default', driver: 'sqlite', config: { filename } }]`, meant as the project's database | no entry; set `OS_DATABASE_URL` (e.g. `file:./data/app.db`) and the host connects it as `default` | +| the same entry, meant as a datasource of the stack's own | `datasources: [{ name: 'app_primary', … }]`, and route the objects that use it: `datasource: 'app_primary'` on the object, or a `datasourceMapping` rule naming `app_primary` | + +**The one-line fix: drop the `default` entry and set `OS_DATABASE_URL`, or rename it and route objects to it.** `os migrate meta --from 17` lists the D3 entry `stack-datasource-host-reserved-name-refused`; the rewrite is by hand, because which of the two the entry meant is the author's call. + +**Measured producers.** None in this repository (examples, `create-objectstack`, skills, docs); the one instance found was an agent-written measurement fixture. diff --git a/packages/spec/src/migrations/entries/semantic/18.stack-datasource-host-reserved-name-refused.ts b/packages/spec/src/migrations/entries/semantic/18.stack-datasource-host-reserved-name-refused.ts new file mode 100644 index 00000000000..ce2cbcc818c --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.stack-datasource-host-reserved-name-refused.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'stack-datasource-host-reserved-name-refused', + surface: 'an entry of a stack\'s `datasources` collection named `default` (the array form ' + + '`datasources: [{ name: \'default\', … }]`, or the map form `datasources: { default: { … } }`), ' + + 'in `defineStack`, `os validate` and publish', + replacement: 'for the project\'s own primary database, no entry: set `OS_DATABASE_URL` and the host ' + + 'connects it as `default`. For a datasource of the stack\'s own, the same entry under another ' + + 'snake_case name (e.g. `app_primary`), with the objects that use it routed to it explicitly ' + + '(the object\'s `datasource`, or a `datasourceMapping` rule). ⛔ NOT mechanically convertible: ' + + 'whether the entry was meant as the host\'s database or as one of the stack\'s own is the ' + + 'author\'s call.', + reason: + '`default` is the host\'s primary datasource: the runtime declares and connects it itself, and ' + + 'the app plugin refuses a stack that declares it in every boot posture, with `OS_DATABASE_URL`, ' + + 'with `--dev` and on the standalone path. The authoring doors accepted the same stack, so its ' + + 'author first met the refusal after the boot\'s earlier plugins had started. The stack\'s ' + + '`datasources` collection now refuses the entry with the runtime\'s sentence and remedy. ' + + '`DatasourceSchema` itself is unchanged: it also documents the host\'s own `default` row, which ' + + 'keeps parsing. No config that boots today is newly refused. Measured producers: none in this ' + + 'repository; the one instance found was an agent-written measurement fixture.', + acceptanceCriteria: + 'No stack\'s `datasources` collection carries an entry named `default`; `defineStack` and ' + + '`os validate` report no issue at `datasources.N.name`, and the deployment reaches its primary ' + + 'database through `OS_DATABASE_URL` (or a driver plugin) and any datasource of its own under ' + + 'its own name.', +}; From 1a4f8fa15d0ad081edf51f1e742ea7ab35407f1d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:24:46 +0000 Subject: [PATCH 5/7] test(cli): pin os validate's refusal of a stack datasource named default, with its control Claude-Session: https://claude.ai/code/session_01UvyD7nLUx7XGKKQVF37FY9 Co-authored-by: Claude --- ...k-datasource-default-validate-door.test.ts | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/cli/test/stack-datasource-default-validate-door.test.ts diff --git a/packages/cli/test/stack-datasource-default-validate-door.test.ts b/packages/cli/test/stack-datasource-default-validate-door.test.ts new file mode 100644 index 00000000000..17a5bef3fd7 --- /dev/null +++ b/packages/cli/test/stack-datasource-default-validate-door.test.ts @@ -0,0 +1,133 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * `os validate` — the build door an author runs before shipping — refuses a + * stack that declares a datasource named `default`, run for real (#23634). + * + * The measured starting point: a config declaring + * `datasources: [{ name: 'default', driver: 'sqlite', … }]` validated clean and + * exited 0, while every boot refused it as host-reserved (the app plugin's + * "declares a datasource named 'default' — that name is reserved for the + * host's primary datasource"), with `OS_DATABASE_URL`, with `--dev` and on the + * standalone path. The exit status is what a CI pipeline reads, so this file + * spawns the source CLI and asserts what the shell sees, with the refusal's + * issue code and path from `--json`. + * + * Schema-layer pins (`defineStack`, the map form, the reserved set, and the + * host's own `default` row through `DatasourceSchema`) live in + * `packages/spec/src/stack-datasource-host-reserved-name.test.ts`. + * + * Integration tier: it spawns the source CLI (`packages/cli/vitest-tiers.ts`). + * Not named `.e2e`, so it runs on every PR and in the merge queue; two spawns, + * one per fixture. The CLI runs through `bin/run-dev.js` (source, via tsx), and + * `@objectstack/spec` resolves through `exports` to `dist/`. + */ + +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { execFile } from 'node:child_process'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; +import { writeDefineStackConfig } from './helpers/define-stack-fixture.js'; + +const HERE = resolve(fileURLToPath(import.meta.url), '..'); +const CLI = resolve(HERE, '../bin/run-dev.js'); +const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); + +/** One stack: one object, and one declared sqlite datasource under `name`. */ +const stackOf = (name: string) => ({ + manifest: { + id: 'com.example.fx', + namespace: 'fx', + name: 'Reserved datasource door probe', + version: '1.0.0', + type: 'app', + engines: { protocol: '^18' }, + }, + objects: [ + { + name: 'fx_task', + label: 'Task', + pluralLabel: 'Tasks', + sharingModel: 'private', + fields: { name: { type: 'text', label: 'Name', required: true } }, + }, + ], + datasources: [{ name, driver: 'sqlite', config: { filename: `${name}.db` }, autoConnect: true }], +}); + +const FIXTURES = { + /** The measured shape: the host-reserved name. */ + reserved: stackOf('default'), + /** CONTROL: the same entry under a name of the stack's own. */ + own: stackOf('fx_primary'), +} as const; + +interface Run { + code: number; + stdout: string; + stderr: string; +} + +function runCli(args: string[], cwd: string): Promise { + return new Promise((resolvePromise) => { + execFile( + TSX, + [CLI, ...args], + { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, + (err, stdout, stderr) => { + resolvePromise({ + code: err ? (typeof (err as { code?: unknown }).code === 'number' ? (err as unknown as { code: number }).code : 1) : 0, + stdout: String(stdout), + stderr: String(stderr), + }); + }, + ); + }); +} + +interface ValidateError { + code?: string; + path?: unknown[]; + message?: string; +} + +/** `os validate --json` on failure: the parse's issues ride `errors`. */ +const errorsOf = (stdout: string): ValidateError[] => (JSON.parse(stdout) as { errors?: ValidateError[] }).errors ?? []; + +const dirs = new Map(); + +beforeAll(() => { + for (const [kind, stack] of Object.entries(FIXTURES) as Array<[keyof typeof FIXTURES, unknown]>) { + const dir = mkdtempSync(join(tmpdir(), `os-ds-default-door-${kind}-`)); + // `strict: false`: the door's own parse must judge the content, not `defineStack` at load. + writeDefineStackConfig(dir, stack, { strict: false }); + dirs.set(kind, dir); + } +}); + +afterAll(() => { + for (const dir of dirs.values()) rmSync(dir, { recursive: true, force: true }); +}); + +describe('os validate refuses a stack datasource named `default`', () => { + it('exit 1: refused at datasources.0.name with the runtime\'s sentence and its remedy', async () => { + const run = await runCli(['validate', '--json'], dirs.get('reserved')!); + expect(run.code, `expected exit 1:\n${run.stdout}\n${run.stderr}`).toBe(1); + const errors = errorsOf(run.stdout); + const located = errors.map((e) => ({ code: e.code, path: (e.path ?? []).join('.') })); + expect(located).toEqual([{ code: 'custom', path: 'datasources.0.name' }]); + expect(errors[0]!.message).toContain( + "declares a datasource named 'default' — that name is reserved for the host's primary datasource", + ); + expect(errors[0]!.message).toContain('OS_DATABASE_URL'); + }, 180_000); + + it('CONTROL: the same datasource named `fx_primary`, exit 0', async () => { + const run = await runCli(['validate', '--json'], dirs.get('own')!); + expect(run.code, `expected exit 0:\n${run.stdout}\n${run.stderr}`).toBe(0); + expect((JSON.parse(run.stdout) as { valid?: boolean }).valid).toBe(true); + }, 180_000); +}); From 109c9e90dbc49e710a8b7a2508d653171b5b7b93 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:46:38 +0000 Subject: [PATCH 6/7] chore(spec): move the dropped-refinements ledger's site total with its four new sites Claude-Session: https://claude.ai/code/session_01UvyD7nLUx7XGKKQVF37FY9 Co-authored-by: Claude --- packages/spec/dropped-refinements.baseline.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spec/dropped-refinements.baseline.json b/packages/spec/dropped-refinements.baseline.json index 0d31e5402e3..76c00ab5ba1 100644 --- a/packages/spec/dropped-refinements.baseline.json +++ b/packages/spec/dropped-refinements.baseline.json @@ -3,7 +3,7 @@ "measured": { "zod": "4.4.3", "publishedSchemasWithDroppedRefinements": 234, - "droppedRefinementSites": 759, + "droppedRefinementSites": 763, "refinementSitesThatDidProject": 369, "refinementSitesWithNoJsonFormToCompare": 0 }, From a085cc9bcedb9fae4a1c322b0b005c4421071bdc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 23:20:46 +0000 Subject: [PATCH 7/7] fix(spec): keep the datasources collection's z.array(...) head on one line, where the stack-collection census reads it Claude-Session: https://claude.ai/code/session_01UvyD7nLUx7XGKKQVF37FY9 Co-authored-by: Claude --- packages/spec/src/stack.zod.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/spec/src/stack.zod.ts b/packages/spec/src/stack.zod.ts index 26a08a10d42..1f202d3edb6 100644 --- a/packages/spec/src/stack.zod.ts +++ b/packages/spec/src/stack.zod.ts @@ -595,8 +595,7 @@ function refuseHostReservedDatasourceName(datasources: readonly { name?: unknown * @internal */ const STACK_DEFINITION_COLLECTIONS_SHAPE = { - datasources: z - .array(DatasourceSchema) + datasources: z.array(DatasourceSchema) .superRefine(refuseHostReservedDatasourceName) .optional() .describe('External Data Connections'),