You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit acab609
Browse filesBrowse the repository at this point in the historyBrowse files
fix(cli): key the runtime state file by project, not by environment alone (#15968)
* fix(cli): key the runtime state file by project, not by environment alone
`os serve` publishes `{ pid, port, url, environmentId, startedAt }` under the
ObjectStack home so a supervisor can answer "is my server running, and where?".
The file was `runtime.<environment>.json`, and both halves of where it lived are
machine-global: `resolveObjectStackHome()` takes no arguments, and an environment
id is not a project identity. Two projects on one machine, both in the ordinary
`local` environment, wrote one file.
Driven with two real boots, two project roots and one home — two failures, one
cause:
* project B's boot replaced project A's record, so a reader asking about A was
answered with B's pid/port/url while A's server was still alive elsewhere;
* project A's shutdown then deleted the file that by then described B, leaving
a running server with no supervision record at all.
The file is now `runtime.<environment>.<project>.json`, keyed on the served
app's root — the root `serve` already resolves for host-anchored loads. The
payload is unchanged: no new key, and no database path (issue 15374 ruled that
one out deliberately, because it would turn a best-effort supervision file into
an identity contract).
The three in-repo readers that spelled the old name out now ask the writer's own
naming function instead, so a second copy of the rule cannot drift from it.
`scripts/publish-smoke.sh` globs `runtime.*.json` in a home it pins itself and is
unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
* test(cli): build the collision children's env through `childEnv`, not a bare spread
`check:cli-test-child-env` is the gate that keeps `packages/cli/test`'s spawners
off `{ ...process.env }`: vitest sets `TEST`, `VITEST` and the `VITEST_*` family
on its worker, and a child inheriting them boots with a different auth and
crypto posture than the one under test. The new collision pin spawned its
children with a bare spread and the gate's shrink-only ratchet named it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
* docs(cli): carry the runtime-state rename's breaking-ness in the changeset
The launch window refuses `major`, so the bump level is not the carrier: a
breaking change ships as `minor` and its only signals are the BREAKING banner
in the changeset body and the ADR-0087 disposition marker that
`check-adr-0087-registration.mjs` re-verifies. This diff retires a shipped
observable -- the on-disk name a supervisor opens -- and the changeset said so
in its own prose while carrying neither signal.
Adds both. The disposition is `not-required (no-migration-prescription)`,
derived from the gate's own `CATEGORIES` const rather than its prose header:
nothing authorable moves, `packages/spec` is untouched, and what is retired is
a path on disk rather than a metadata surface the ledger can project into
`spec-changes.json` or the upgrade guide. `unpublished` is false (the package
publishes), `already-registered` names no entry that covers this, and neither
`runtime-interface-only` nor `type-surface-only` has a `path#Symbol` to name.
Also ends the two names `serve.ts` was stating at once. The
`BoundPortChannels.writeRuntimeState` interface doc and the four other
current-tense sites named the retired file; they now name the shipped one,
while the measured ENOENT quote and the pre-repair collision diagram keep the
name they observed and say that is what it was. The anchoring boundary an
out-of-tree reader has to replicate -- the app root is the config's own
directory only when that config exists and that directory carries a
`package.json`, else the CWD -- is stated beside the symlink boundary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
---------
Co-authored-by: Claude <noreply@anthropic.com>
`os serve`'s runtime state file is keyed by the PROJECT, not by the environment id alone — so two projects on one machine stop overwriting each other's supervision record.
6
+
7
+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable moves: no spec key, export, config field or stored metadata changes spelling or shape, `packages/spec` is untouched, and `objectstack migrate meta` has nothing to rewrite. What is retired is the NAME of a best-effort supervision file that `os serve` writes under the ObjectStack home — a path on disk, not a metadata surface the ledger can project into `spec-changes.json` or the generated upgrade guide. The affected party is an out-of-tree supervisor that opens that path, and the one action it takes is deleting a single stale file; there is no authored artifact for a metadata upgrader to rewrite, and no ledger entry could reach the party that is affected. -->
8
+
9
+
**BREAKING** for anything that opens the runtime state file by its old name. Shipped as `minor` under the launch-window convention: while the whole workspace versions in lockstep the bump level carries no breaking-ness, so this banner and the ADR-0087 disposition above are the carriers. The file `os serve` writes under the ObjectStack home was named `runtime.<environment>.json` and is now named `runtime.<environment>.<project>.json`.
10
+
11
+
`os serve` publishes `{ pid, port, url, environmentId, startedAt }` to a file under the ObjectStack home, so a supervisor can answer *"is my server running, and where?"*. That file was named `runtime.<environment>.json`, and both halves of where it lived were machine-global: `resolveObjectStackHome()` takes no arguments (it reads `OS_HOME`, else `~/.objectstack`), and an environment id is not a project identity. Two different projects on one machine, both in the ordinary `local` environment, therefore wrote one file.
12
+
13
+
Driven with two real boots, two project roots and one home, that produced two failures with one cause:
14
+
15
+
- project B's boot replaced project A's record, so a reader asking about A's server was answered `pid`/`port`/`url` belonging to **B** — confidently, while A's own server was still alive and still listening elsewhere;
16
+
- project A's shutdown then deleted the file that by that point described **B**, leaving a running server with no supervision record at all.
17
+
18
+
The file is now `runtime.<environment>.<project>.json`, where the project component is a sanitised basename plus a short digest of the served app's root — the same root `serve` already resolves for host-anchored package loads. The payload is unchanged: no new key, and in particular no database path (which #15374 ruled out deliberately, because it would turn a best-effort supervision file into an identity contract).
19
+
20
+
**If you read this file:** a reader that hard-codes `runtime.<environment>.json` now gets `ENOENT` rather than a stale or foreign record — a loud, correct answer to "is my server running", where the old name could only give a confident wrong one. Readers that glob `runtime.*.json` inside a home they pinned themselves (as `scripts/publish-smoke.sh` does) are unaffected. A `runtime.<environment>.json` left over from an earlier version is no longer written or cleaned up by `os serve`; delete it once.
21
+
22
+
**Which root the project component is taken from**, for a supervisor that has to reconstruct the name out of tree: it is the app root `serve` anchors at, which is the config file's own directory when that file exists and that directory carries a `package.json`, and the process's working directory otherwise. Two boundaries follow, stated rather than fixed: the same app served from two working directories without a manifest keys two files, and the key is the resolved path rather than the realpath, so two symlinked spellings of one project key differently — each spelling gets its own file, and each is internally consistent.
23
+
24
+
Two boots of the *same* project from the *same* anchor still share one file, which is the same-project case and unchanged here.
0 commit comments