fix(runtime,metadata): stamp env_local as the default local environment id - #15200
Conversation
…nt id Finish the v5.0 `project` to `environment` rename at the two sites it never reached. `packages/cli/CHANGELOG.md` records "Default local env id: `proj_local` -> `env_local`" and `content/docs/deployment/cli.mdx` documents `env_local`, while `createStandaloneStack` and `MetadataPlugin` still stamped `proj_local` — declared != enforced on a published default. - `packages/runtime/src/standalone-stack.ts`: the boot fallback, plus the two in-file comments that narrate what this stack stamps (one of them also carried a stale line anchor, ~515 -> ~567). - `packages/metadata/src/plugin.ts`: the placeholder that fills the `EnvironmentArtifactSchema` validation envelope for a bare definition. Adds the pin the value never had: the id is asserted where it LANDS, on the `MetadataPlugin` and `ObjectQLPlugin` instances the stack composes, together with the precedence above it (`cfg.environmentId` > `OS_ENVIRONMENT_ID` > default). `cloud-connection`'s dual accept and `package-state-store`'s separate `'default'` sanitiser fallback are deliberately untouched. Part of #13366 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Part of #13366 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
…udience `os serve` sets no OS_ENVIRONMENT_ID for its own boot — it only reads one to name the runtime state file — so a bare `os serve` reaches the changed default too, not only a direct `createStandaloneStack` embedder. Measured at `serve.ts:524` (read-only) against `dev.ts:380` / `start.ts:409` (which export). Part of #13366 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
📓 Docs Drift CheckThis PR changes 2 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4bff6af5648983cad813518ac64d6aaaf82c8517 && git checkout 4bff6af5648983cad813518ac64d6aaaf82c8517
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 919beca43b2901320cea39afd912b7f662ddd3b1 389a485293befeb7e122cc784575d9ca473e1297 && git checkout -B drift-repro 919beca43b2901320cea39afd912b7f662ddd3b1 && git merge --no-ff 389a485293befeb7e122cc784575d9ca473e1297
node scripts/docs-audit/affected-docs.mjs --json 919beca43b2901320cea39afd912b7f662ddd3b1
|
|
Contract review PASS at Generated by Claude Code |
Fixes #13366
Implements the
domain:cliPM ruling in comment 5495605293 — option (a), in the narrow form — and nothing beyond its six-point scope.What changes
The v5.0
projecttoenvironmentrename changed the default local environment id and shipped that change on the surfaces most people meet. It never reached two stamps:packages/runtime/src/standalone-stack.ts— thecreateStandaloneStackboot fallbackproj_localenv_localpackages/metadata/src/plugin.ts— the placeholder that fills the environment-artifact validation envelope for a bare definitionproj_localenv_localTwo comments in
standalone-stack.tsnarrate what this stack stamps (one of them present tense, twenty lines above the line it describes). They are converted in the same edit, because leaving them would have manufactured a freshdeclared != enforcedinside the file this PR exists to make consistent. One of the two also carried a stale line anchor,line ~515for what is now line 567; corrected in passing since the sentence was being edited anyway.Published surface, and what an embedder observes (Clause-② statement)
The changed surface is
createStandaloneStack/createDefaultHostConfig(@objectstack/runtime), on the DEFAULT path only — noenvironmentIdin the config, noOS_ENVIRONMENT_IDin the environment.proj_local. The id is a row-scope key insideObjectQLPluginand reachessys_metadata.environment_id, theX-Environment-Idheader, andMetadataPluginoptions.env_local, which is whatpackages/cli/CHANGELOG.mdandcontent/docs/deployment/cli.mdxhave promised as the default since the rename.environmentId: 'proj_local'in the config, orOS_ENVIRONMENT_ID=proj_localin the environment. Both still outrank the default — that precedence is unchanged and is now pinned.Two audiences reach it, and naming only the first would understate the change:
createStandaloneStack/createDefaultHostConfigdirectly;os serve— one not spawned byos dev/os start.serve.tssets noOS_ENVIRONMENT_IDfor its own boot; it only READS one, to name the runtime state file. So a bareos serveused to run a kernel stampedproj_localwhile publishingruntime.env_local.jsonbeside it. The two now agree, which is the split-brain the phase-1 census described closing.Who does NOT observe it. A boot started by
os dev/os startnever reached the changed line: those commands exportOS_ENVIRONMENT_ID=env_localinto the child process (dev.ts:380,start.ts:409), and the fallback yields to it. That is why the mismatch could hold for a whole major line without one test going red.Where the id crosses to closed cloud. The
repo:cloudseat sweptobjectstack-ai/cloud@9b6abe0f2fd5on the seam card: 14proj_localhits, zeroenv_local, zero production-code comparisons on either literal; the one production consumer (kernel-resolver.ts:256-266) reads the id opaquely intoenvRegistry.resolveById(...). That repo is unreachable from this container, so it is reported as an inherited given, not as something this PR re-measured.Deliberately NOT changed (ruling points 2, 3, 4 and 6)
packages/cloud-connection/src/cloud-connection-plugin.tskeeps its dual accept of both spellings. It guards persistedOS_ENVIRONMENT_ID=proj_localconfigs in the wild, exactly as its comment says. It also puts both literals in the same arm, so it could not have changed outcome either way.packages/runtime/src/package-state-store.tskeeps its separate'default'sanitiser fallback — renaming it would re-key persisted package-disable state files for a cosmetic gain.content/docs/deployment/seed-tenancy-repair.mdxis untouched: its text is true history, not a description of a live broken gate.packages/cli/src/commands/serve.tsruntime-file derivation is untouched. Supervisors pinruntime.env_local.jsonas a contract, and a contract move must not ride a spelling cleanup.Evidence
Red-first, before converting. The new pin was written and run against the unconverted tree, and it failed with the value it exists to keep out:
So the pin reaches the code this PR changes, rather than re-declaring a constant. It reads the id off the two plugin instances the stack composes — the last point at which the stamped value is still identifiable before it dissolves into row scoping — and covers the precedence above it.
Green after, at
389a4852:@objectstack/runtimefull suite@objectstack/metadatafull suiteserve-bound-port-publication,serve-bound-port-publish-order,serve-publishes-bound-port.e2epublish-smoke-port-collision(@objectstack/spec)cloud-connection-plugin(pins the dual accept itself)platform-migrations-arming, threeschema-migrateintegrations,graft-runtime-hooks,merge-boot-config,driver-vocabulary-parity,serve-host-config-security-registrar.pinThe guard set was named in the phase-1 census as the tests that would go red if the CLI default moved. This PR does not move the CLI default, and all five stayed green — the blast radius is what the census said it was.
On the metadata site specifically, stated rather than faked.
EnvironmentArtifactSchemadeclaresenvironmentId: z.string()with no format constraint, and_parseAndRegisterArtifactdiscards the parse result (metadata = def). The literal there is a placeholder that satisfies a required key on a validation call whose output is thrown away, so it has no observable output and is given no behavioural pin. Writing one would have asserted a copy of the constant against itself.Out of scope, filed separately
Converting the stamp leaves five comments in four other packages describing the standalone stamp with the old spelling. They are outside the ruling's scope, so they are recorded on their own card rather than fixed here.
🤖 Generated with Claude Code
https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Generated by Claude Code