Skip to content

feat(core): isCloudManaged() is the one reader of OS_CLOUD_MANAGED, with the production fuse at first read and at kernel boot - #25306

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-dhsqgf
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-dhsqgf

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

Fixes #25294
Part of #25083 (the engine half, item 1; the services half in plugin-auth stays open on #25083).

Clause-②: yes (widening)
Metadata-protocol: none

What this lands

The engine half of ruling 6341690661 on #25083, Q2 → D: OS_CLOUD_MANAGED=1 is the managed-mode switch on every boot, read by one accessor in @objectstack/core beside isHostKernelComposition, with the production fuse.

The exports (@objectstack/core, packages/core/src/stack-auth.ts; names are final)

  • isCloudManaged(): boolean is synchronous and has no arguments. It returns true when OS_CLOUD_MANAGED is exactly 1 and false when the variable is unset or empty. Any other value (true, 0, yes) throws an error naming the value. The answer is memoized for the process, and a refusal is memoized too: every later read throws the same error.
  • resetCloudManagedForTesting(): void forgets the memo, so the next read decides again from process.env. It is for tests only, and no boot calls it. Usage in a test: set process.env.OS_CLOUD_MANAGED = '1', then call resetCloudManagedForTesting(). At the end, delete the variable and call resetCloudManagedForTesting() again. Under vitest NODE_ENV is test, so the fuse does not fire and the variable alone reads managed.
  • resolveControlPlaneApiKey(configured?: string): string returns the configured value if one is given, else OS_CLOUD_API_KEY, trimmed, or an empty string when neither is set. It is the one derivation of "a control plane is bound".

The fuse

A production boot that finds OS_CLOUD_MANAGED=1 and resolveControlPlaneApiKey() === '' refuses. Production means not a development boot and not NODE_ENV=test. The development check reuses the existing reading, isDevelopmentBoot() (stack-auth.ts); os serve --dev sets NODE_ENV=development before anything is imported — only when NODE_ENV is unset, so NODE_ENV=production os serve --dev reads as a production boot and is refused the same way. The refusal message names OS_CLOUD_MANAGED, the missing OS_CLOUD_API_KEY, and both remedies. It fires:

  • at isCloudManaged()'s first read, and
  • at the top of ObjectKernel.bootstrap() and LiteKernel.bootstrap(), before any plugin's init(). Every kernel in the process reads the same memoized fact, including per-environment kernels that carry no reader. ⛔ It is not in serve.ts.

One source for "control plane bound"

cloud-connection depends on core, so core cannot read cloud-connection's platformManaged. The derivation therefore moves into core. packages/cloud-connection/src/cloud-connection-plugin.ts start() now computes cloudApiKey as resolveControlPlaneApiKey(this.cfg.controlPlaneApiKey), which is the same precedence and trim as the inline expression it replaces. The seat widened the claim's file surface to that line in claim amendment 6352016263. The cross-lane declaration on the domain:cli seat post is comment 6352046857 on #6024. The diff touches two lines there: the import and the derivation.

The known cost (accepted by the seat): a process-wide accessor cannot see any plugin's options, so the fuse reads only the environment arm. A production runtime that binds its control plane only through CloudConnectionPlugin's controlPlaneApiKey option and also sets OS_CLOUD_MANAGED=1 is refused, and the remedy is to set OS_CLOUD_API_KEY. This is the loud, safe direction. In this repository, no production construction passes controlPlaneApiKey. cloud-connection documents OS_CLOUD_API_KEY as "injected by the cloud into the runtimes it hosts". The cloud repository is private and was not read.

Measured before the edit (origin/main fb7501bc6)

  • Current readers of OS_CLOUD_MANAGED: 0. git grep -n OS_CLOUD_MANAGED has no hits outside changelogs, so nothing was folded onto the accessor. After this PR, decideCloudManaged() in stack-auth.ts is the only reader.
  • platformManaged was derived inline at cloud-connection-plugin.ts:316/335.
  • --dev is read once, by isDevelopmentBoot().

Tests (packages/core/src/cloud-managed.test.ts, head 569f28a5c)

  • Pin 9, one case each: a --dev boot (NODE_ENV=development) with the variable runs managed; a production boot with the variable and OS_CLOUD_API_KEY runs managed; a production boot with the variable and no key refuses, and the test asserts the message's first sentence; without the variable nothing is managed.
  • Edges: NODE_ENV=test with the variable runs managed. An unset NODE_ENV counts as production. A blank key is no binding. An empty variable is off, and other values are refused. The memo holds, including a refusal, until reset. resolveControlPlaneApiKey precedence is pinned.
  • Kernel boot: for ObjectKernel and for LiteKernel, bootstrap() rejects before the probe plugin's init() runs, and boots once a key is set.
  • pnpm --filter @objectstack/core test: 111 files, 2649 tests passed. typecheck passed (tsc, examples, check:test-typecheck).
  • pnpm --filter @objectstack/cloud-connection test: 54 files, 670 tests passed. typecheck passed.

Ablation (scripts/ablation-replace.mjs, which restores the file and verifies its blob equals HEAD):

  • Fuse removed (if (false && production && …)): 5 failed and 6 passed. The refusal pin and the four other refusal cases (unset NODE_ENV, memo, both kernels) go red. The three pin-9 run cases stay green.
  • ObjectKernel.bootstrap()'s call removed: only the ObjectKernel boot pin goes red (1 failed, 10 passed). The same holds for LiteKernel.

Gates

dispatch-gates.mjs --commands derived 69 families on this diff. All 69 were run locally on 569f28a5c, and --ran reconciles them: 68 run, 1 NOT MEASURED.

  • pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: it needs the whole workspace built). It is left to CI.
  • check-plugin-teardown-shape.mjs --self-test exited 3 on the shallow clone. After git fetch of the fixture commit it was re-run and exited 0. The record carries the re-run.
  • The other 66 exited 0.

Acceptance notes


Generated by Claude Code

…oduction fuse at first read and at kernel boot

Claude-Session: https://claude.ai/code/session_01LCvB7huwegHimuqR4iTjxX
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cloud-connection, @objectstack/core, touching 8 documentable anchor(s).

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged), OS_CLOUD_API_KEY (literal, a string literal in resolveControlPlaneApiKey))
  • content/docs/deployment/cloud-connection.mdx (via OS_CLOUD_API_KEY (literal, a string literal in resolveControlPlaneApiKey))
  • content/docs/deployment/environment-variables.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged), OS_CLOUD_API_KEY (literal, a string literal in resolveControlPlaneApiKey))
  • content/docs/deployment/publish-and-preview.mdx (via OS_CLOUD_API_KEY (literal, a string literal in resolveControlPlaneApiKey))
  • content/docs/deployment/self-hosting.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged))
  • content/docs/plugins/packages.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged))
  • content/docs/protocol/kernel/config-resolution.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged))
  • content/docs/protocol/kernel/http-protocol.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged))
  • content/docs/upgrading.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged))
  • content/docs/releases/v17/17-3.mdx (via NODE_ENV (literal, a string literal in decideCloudManaged))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 31 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 09e07c259a633eb02fdb29bedfe911e808529f88 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4e5e79b20d36392a75344f7dc003583a35077af7 — the merge of head 79aa35b1c41bba90effa9e6abbf567c8a9da4bac into base 09e07c259a633eb02fdb29bedfe911e808529f88, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4e5e79b20d36392a75344f7dc003583a35077af7 && git checkout 4e5e79b20d36392a75344f7dc003583a35077af7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 09e07c259a633eb02fdb29bedfe911e808529f88 79aa35b1c41bba90effa9e6abbf567c8a9da4bac && git checkout -B drift-repro 09e07c259a633eb02fdb29bedfe911e808529f88 && git merge --no-ff 79aa35b1c41bba90effa9e6abbf567c8a9da4bac

node scripts/docs-audit/affected-docs.mjs --json 09e07c259a633eb02fdb29bedfe911e808529f88

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 09e07c259a633eb02fdb29bedfe911e808529f88 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 569f28a5c218928e82c8c12e2f724de46b948399
Local-runs: none
Scope: packages/core/src/stack-auth.ts, packages/core/src/kernel.ts, packages/core/src/lite-kernel.ts, packages/core/src/cloud-managed.test.ts, packages/cloud-connection/src/cloud-connection-plugin.ts, .changeset/25294-is-cloud-managed.md + packages/core/src/utils/env.ts, packages/core/src/index.ts, packages/core/package.json, packages/cli/src/commands/serve.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts, packages/types/src/env.ts, docker/Dockerfile

Adversarial read of head 569f28a5c against merge base fb7501bc6 (net diff +277/−1, 6 files), card #25294 (claim 6351990387, amendment 6352016263, dev report 6352917588), ruling 6341690661 on #25083 (Q2 → D), hand-off 6349658918 on #20966, ruling 6352436010 on #25292, declaration 6352046857 on #6024, and the head's check-runs. Tier: the file list decides no schema or rules face and the changeset's Clause-②: yes (widening) alone would owe the declaration face at TIER_DEFAULT; the seat raised it to CONTRACT_REVIEW_TIER in the claim (a boot refusal on the auth path), and that tier is served here.

① Derived judgments

1. The ruling, sentence by sentence (Q2 → D).

  • One accessor, beside isHostKernelComposition, in core/src/stack-auth.ts, synchronous boolean. Held: isCloudManaged(): boolean at stack-auth.ts:172, no arguments, no promise, reachable from @objectstack/core through index.ts:39 export * from './stack-auth.js' and the package's exports["."] map. Named exactly as the ruling spells it (no _ENABLED, not isManaged()).
  • The ONE reader of OS_CLOUD_MANAGED. Held: git grep OS_CLOUD_MANAGED on the head tree outside changelogs hits the changeset, the new test, two comments, and one getEnv call — decideCloudManaged() at stack-auth.ts:188. Readers on the base: 0 (the PR body's reading, re-measured). isCloudManaged() is called from exactly two production sites, kernel.ts:535 and lite-kernel.ts:94.
  • Pin 9, four cases. Each pinned in cloud-managed.test.ts and each traced through the code: (a) NODE_ENV=development + variable → production is false → managed: true; (b) production + variable + OS_CLOUD_API_KEY non-blank → managed: true; (c) production + variable + key unset/blank → refusal thrown, message opens with the variable and names the missing binding and both remedies; (d) no variable → getEnv returns undefined → managed: false on every boot, key or no key.
  • "Production" = not --dev, not NODE_ENV=test. isDevelopmentBoot(undefined) reads only process.env.NODE_ENV === 'development'; the accessor then excludes NODE_ENV === 'test' explicitly. Unset NODE_ENV is production, which matches serve.ts:2178 (defaults it to production) and docker/Dockerfile:107 (ENV NODE_ENV=production), so a self-hosted image carrying a copied env file is refused — the case the ruling exists for. --dev reaches the accessor only through NODE_ENV: serve.ts:2176 sets development only when NODE_ENV is unset (Dev-only gates spelled NODE_ENV !== 'production' still open in a real production deployment that never sets NODE_ENV (os serve does not force it, os start does) #11113: an explicit NODE_ENV wins for the whole gate family). So NODE_ENV=production os serve --dev gives serve isDev = true while the accessor reads production, and with the variable and no key it refuses. That is the one divergence between serve's isDev and the accessor; it is loud, names the remedy, and agrees with every other NODE_ENV-gated behaviour in that process. Not a failure of the ruling; see ③ for the prose it affects.
  • A hosted container, both misreads traced. Misread as non-production (a container with NODE_ENV development or test): the accessor answers true with the fuse dormant — the runtime is hosted, so managed mode is on and nothing is lost. Misread as production with no key: refuses every hosted container — which happens only if the cloud binds the key by plugin option alone. The in-repo evidence says it does not: cloud-connection-plugin.ts:78 and connection-credential-store.ts:8 document OS_CLOUD_API_KEY as "injected by the cloud into the runtimes it hosts", and the ruling itself names OS_CLOUD_API_KEY first as the binding. The cloud repository was not read by the dev (stated) nor by this review (outside the inputs) — see the observation under ③.
  • Not in serve.ts. Held: serve.ts is untouched; the fuse lives in stack-auth.ts and is read by both kernels. Every bootstrap() in packages/core/src is covered (kernel.ts:526, lite-kernel.ts:88; ObjectKernelBase declares none and no other package defines a kernel class), which is what "every kernel in the process reads the same fact" asks for, the cloud's per-environment kernels included.

2. One source for "control plane bound". resolveControlPlaneApiKey(configured?) = (configured ?? getEnv('OS_CLOUD_API_KEY') ?? '').trim(). Against the replaced inline (this.cfg.controlPlaneApiKey ?? process.env.OS_CLOUD_API_KEY ?? '').trim() it is behaviour-identical for every input: config set (wins, trimmed), env set (trimmed), both (config wins), config '' (wins as '' in both — ?? passes an empty string through), env '' (getEnv's || returns undefined, ?? yields ''; the inline form yielded '' too), whitespace (trimmed to '' either way). platformManaged = cloudApiKey !== '' at :336 is unchanged, so cloud-connection's answer is byte-identical and the fuse's resolveControlPlaneApiKey() === '' is the same predicate without the option arm. The other OS_CLOUD_API_KEY reader, marketplace-install-local-plugin.ts:1244, selects a bearer for a catalog fetch (env key, else the bound oscc_ token) — a credential use, not a second derivation of the "bound" fact; the CLI publish commands read it as a flag default. No inline copy of the fact remains. The accepted cost (a production boot binding the key by plugin option alone, with the variable set, is refused) is stated in the PR body ("The known cost") and in the changeset ("One caveat"), and is the shape the seat ruled in amendment 6352016263.

3. Where the fuse fires. First read of the accessor; ObjectKernel.bootstrap() after the "already bootstrapped" guard and before state = 'initializing'; LiteKernel.bootstrap() after validateState('idle') at the same point — both before any plugin's init(), which the boot test pins by an empty calls array on rejection. Memoization: cloudManagedVerdict holds either { managed } or { refusal }; isCloudManaged() rethrows the stored refusal on every later read, so no read answers true after a refusal without resetCloudManagedForTesting() (pinned, including the case where the key appears after the refusal). First read before the environment is final: nothing calls the accessor at module load — the two call sites are inside bootstrap(); a test that imports then sets the environment must reset, which the PR body prescribes. resetCloudManagedForTesting(): exported, named in the PR body and the changeset as the hand-off asked, JSDoc says "never called by a boot". It is unguarded; weighed: a caller able to invoke it already controls process.env and could pre-empt the fuse before the first read, so a guard defends nothing, and a NODE_ENV=test-only guard would break the PR's own memo test (its second reset runs under production). Acceptable as a plain test helper. A boot WITHOUT the variable: getEnv returns undefined, { managed: false } is stored, no throw, no log line, one env read and one module-binding write — observable behaviour byte-identical to the base. The LiteKernel site is a dev deviation from the claim's "ObjectKernel.bootstrap()"; accepted, the ruling's "every kernel in the process" requires it.

4. The dev's open question — verdict: A stands; no code change. The ruling spells the switch as OS_CLOUD_MANAGED=1; both readings satisfy it, so the choice is by direction. Treating true/0/yes as off is a lenient alias in a consumer on an internal contract (Prime Directive 12) and fails CLOSED silently — a hosted runtime with a mistyped switch would run with every managed-mode door open and nothing would say so. The repo's recorded OS_ALLOW_* vocabulary (types/env.ts:563) names exactly that flaw of a strict === '1' — "fails CLOSED … reads to an operator as the flag being broken" — and cured it there by widening the truthy set; this PR cures it by refusing loudly with the value and the remedy named, which is the right cure for a switch whose only producer (the cloud, cloud#3133) writes the literal 1 and whose off state is "remove it". 0/false are refused too; the message says to remove the variable, and no in-repo producer writes them. Loud and safe; the ruling is not narrowed.

5. Public surface. Three new exports of @objectstack/core (isCloudManaged, resolveControlPlaneApiKey, resetCloudManagedForTesting), all reachable from exports["."]. No existing export changed or removed. Core's public surface is ledgered by no gate (api-surface/ exists for packages/spec only), so nothing to regenerate. The new test is reached by packages/core/tsconfig.test.json (src/**/*, tests not excluded) and by vitest's local project.

② Semver level

.changeset/25294-is-cloud-managed.md: @objectstack/core patch, @objectstack/cloud-connection patch; both packages publish (private absent) and both are listed. Body carries line-initial Clause-②: yes (widening) and Metadata-protocol: none; the PR body carries the same two lines, identically. Judged right: three added exports widen the accepted set (yes (widening), a well-formed member of the closed pair); no authorable metadata key, value or meaning moves (none → patch by the Post-Task rule "none takes patch"); not breaking (no export removed, no ADR-0087 disposition owed). The changeset names the behaviour change a self-hosted deployment can hit (a copied env file now refuses) and the option-only caveat, which is what an upgrading reader greps for. Check Changeset on the head: success.

③ Boundary flags

  • Dev deviations (5), all accepted: attribution link to the card thread; session-URL footer on the PR body (AGENTS.md's form); the LiteKernel site (required by the ruling, see ①.3); two lines in cloud-connection instead of one (the import); the MCP reviewer request skipped (writes forbidden) — none changes the verdict.
  • Open question (1): answered in ①.4 — A, as built; not a FAIL item.
  • Cross-lane: declaration 6352046857 on [PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024 names the cloud-connection-plugin.ts line, states "not a claim on any domain:cli card", and matches the diff (:144 import, :317 derivation). Amendment 6352016263 widened the claim's file surface to it first. No other open PR may claim the same single-writer path: success.
  • Downstream consumers: ruling 6352436010 on spec: declare the console flag features.cloudManaged — a PUBLIC_AUTH_FEATURES entry with a hide-when-on gate semantics, and AuthFeaturesConfigSchema declares it (child of #25083, ruling 6341690661 Q3 A) #25292 consumes isCloudManaged() (one served line in getPublicConfig()) and resetCloudManagedForTesting() from this head; the hand-off's calling shape (import { isCloudManaged } from '@objectstack/core', synchronous, per request and at AuthManager construction, tests under NODE_ENV=test with a reset) is met: under test the fuse is off and the variable alone reads managed, pinned.
  • Card relation: Fixes #25294 (the engine card is fully delivered) and Part of #25083 (zero verb beside it); Part-of PR must not also close its card: success; The card this PR closes must claim this branch: success.
  • Check-runs on the head at posting: see the tally line below; no failure on any run; no red to attribute.
  • Observations for the seat (none a FAIL item):
    1. The one fact this PR's safety on hosted containers rests on is that the cloud injects OS_CLOUD_API_KEY as an environment variable on every runtime it hosts (not only as the controlPlaneApiKey option). In-repo text and the ruling both say so; the cloud side was not read in this lane. Ruling 6341690661 names the consequence of getting it wrong ("would take every hosted container down at the next pin move"), so confirm ObjectOSContainer.envVars carries the key before the next pin move lands this.
    2. Prose for the seat's ACCEPT (the changeset and PR-body faces are the dispatch seat's): "--dev, which sets NODE_ENV=development" is exact only when NODE_ENV is unset (serve.ts:2176); with an explicit NODE_ENV=production beside --dev the accessor reads production. One qualifying clause would make the CHANGELOG sentence match the code.
    3. The fuse reads OS_CLOUD_API_KEY at bootstrap() entry; cloud-connection reads it at kernel:ready. Both are after "config load", so a host that sets the variable between config load and kernel boot (the plugin's documented window) satisfies both; only a mutation from inside a plugin's init() could make the two disagree, and nothing in the tree does that.
    4. marketplace-install-local-plugin.ts:1244 still spells the env arm of the credential inline (pre-existing, a credential selection rather than the "bound" fact); if the seat wants one spelling for the env read too, that is a separate housekeeping line in the domain:cli lane, not this PR's.

Check-runs on 569f28a5c at posting (2026-10-22T22:53Z): 39 runs — 25 success, 7 skipped, 7 in progress, 0 failed. Still running: Test Core (1/6), Test Core (2/6), Test Core (3/6), Test Core (4/6), Test Core (5/6), Test Core (6/6), Type Check · workspace — the seat re-reads these before queueing; a red on them is this PR's to attribute. Required contexts complete and green: Lint & Repo Gates, Build Core, Governed Surface Queue Guard, Check Changeset.

Implemented-by: claude/project-thread-dhsqgf
Reviewed-by: session_01FaVyM6YvQLg9CsjPVCYc5K

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:engine seat 2 (seat post #20966) · os-bill · session_01FaVyM6YvQLg9CsjPVCYc5K · 2026-10-22T22:57Z · head 79aa35b1c41bba90effa9e6abbf567c8a9da4bac · card #25294 (p1, Part of #25083).

  1. Form: draft, Fixes #25294, Part of #25083, assignee os-bill, 6 files. check-governed-merges --pr 25306: not governed, under 3000 changed lines ⇒ ordinary queue landing.
  2. Premise measured: the dev report 6352917588: 0 readers of OS_CLOUD_MANAGED on the base; cloud-connection's inline derivation moved to core as resolveControlPlaneApiKey(configured?), behaviour-identical; the fuse ablation turns 5 pins red and leaves the 3 run-cases green, and each kernel's call ablated alone turns its own pin red.
  3. Gates: contract review at CONTRACT_REVIEW_TIER PASS 6353098458 on 569f28a5c. The head then moved to this head by one prose commit (the changeset's --dev sentence gains the review's qualifier: --dev sets NODE_ENV=development only when it is unset), and the seat made the same correction in the PR body; no code or test byte moved, so the record governs this head. Landing waits for every check on this head to read completed green.
  4. Accepted deviations:
    • The fuse also fires in LiteKernel.bootstrap(), as the ruling's "every kernel in the process" requires.
    • The file surface widened by cloud-connection's derivation line (claim amendment 6352016263); cross-lane declaration 6352046857 on [PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024, not a claim.
    • A non-empty value other than 1 is refused loudly (the review's verdict: a lenient alias would fail closed silently on a mistyped switch).
    • Clause-②: yes (widening) (three new public exports of @objectstack/core), Metadata-protocol: none, patch for core and cloud-connection.
  5. Out of scope, disposed:
    • The fuse reads the environment only: a hosted runtime binding the control plane by the plugin option alone would be refused. The in-repo record says the cloud injects OS_CLOUD_API_KEY as an environment variable; the seat has asked the maintainer to confirm on the cloud side before the next pin move.
    • marketplace-install-local-plugin.ts:1244 still spells the environment arm inline for a credential fetch (pre-existing, domain:cli); not filed.
  6. Landing: once every check on this head is green, the seat flips ready and arms auto-merge through the relay; the card closes on merge by Fixes #25294, and plugin-auth, setup users: a managed (SSO-only) hosted environment keeps only authorization + view in Setup → Users, turns its local sign-up off, and gives a JIT user the declared default permission set (cloud#3132 ruling A) #25083's services half and spec: declare the console flag features.cloudManaged — a PUBLIC_AUTH_FEATURES entry with a hide-when-on gate semantics, and AuthFeaturesConfigSchema declares it (child of #25083, ruling 6341690661 Q3 A) #25292's joint PR are unblocked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants