Repository navigation
dev server: OS_PRODUCT_STAGE / branding.stage never reaches /api/v1/runtime/config — the documented PreviewBadge ops switch is a no-op on OSS #9252
Description
Activity
- addedbugSomething isn't workingSomething isn't workingand removed
on Aug 18, 2026 os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsTriage — first-touch grading (concentrated round, triage seat, session
session_0138jAR5jeREBpm4dhVvbWY7): promoted topm:queue, type Bug,domain:cli, auto-adjudicated.The declared/enforced fork here is adjudicable: objectui's docs already declare
OS_PRODUCT_STAGE/branding.stageas the operational badge switch, so making the OSS runtime push the key through/api/v1/runtime/configis restoration of an already-declared contract, not a public-surface expansion — the bug/repair lane, not the feature floor.Four-facet block (adjudicated: enforce — resolve
OS_PRODUCT_STAGEintobranding.stagein the OSS runtime-config response):- Platform long-term coherence: removes an undocumented OSS/cloud behavioral split behind one documented knob; declared = enforced restored.
- Measured business pull: the promo-video production run (video-studio order) actually needed the switch on OSS and it silently no-opped — real usage, not speculation.
- AI-agent error-resistance: a documented knob that silently does nothing is exactly the declared-but-unenforced trap; a real passthrough is strictly safer than teaching agents "this key works except when it doesn't".
- Startup scope discipline: one key passthrough vs. permanently maintaining a false doc plus a cloud-only carve-out note.
Rationale for the lane:
RuntimeConfigPlugin/ dev-server config lives in the runtime family (domain:cli). Dispatch note (premise-first): re-verify on currentmainthat/api/v1/runtime/configstill omitsbranding.stage— the repro above is against a 08-17 pinned SHA. Not a v17 board blocker (cosmetic badge; RC can ship without it). Veto window open — this ruling will be listed in the round report's adjudication section.
Generated by Claude Code
PM (
domain:cliseat) — NOT dispatched this round: hard same-file serial with in-flight #9442. Sessionsession_012WKSnqAaoqtW3QX7SSf1Vk, round 16. ⛔ No claim made, no assignee set. Card stayspm:queueand is dispatchable the moment #9442 lands.The premise triage asked me to re-verify — confirmed, and more strongly than the card claims
Triage's dispatch note said to re-check on current
mainthat/api/v1/runtime/configstill omitsbranding.stage, since the repro was against a 08-17 pinned SHA. Measured onorigin/mainwith a control first:probe result OS_PRODUCT_STAGE, repo-wide (not justpackages/)0 hits branding.stage/productStage/PreviewBadgeinpackages/0 hits control — OS_CLOUD_URL|OS_ALLOWinpackages/142 files The control is what makes the zeros mean something: the search plainly works, so the producer side genuinely does not exist anywhere in this repository. The bug is live and the repro is not stale.
Why it cannot run alongside #9442
The fix has to reach two places, and one of them is occupied:
packages/cloud-connection/src/runtime-config-plugin.ts:541assembles thebrandingobject — the key has to start being emitted there.packages/cli/src/commands/serve.tsownsServe.RUNTIME_CONFIG_OPTIONS, the constructor options fed toRuntimeConfigPluginat:2145and:2201, and its own comment at:404says "RuntimeConfigPlugincarries the host's branding". A value resolved from an env var has to be threaded in from there.
#9442 is in flight on exactly
packages/cli/src/commands/serve.ts. Same file ⇒ hard serial, and the maintainer re-confirmed on 2026-08-18 (verbatim 「不豁免,维持串行」) that same-package work is not exempted either.⇒ Dispatching now would put a dev into a file another dev is rewriting, and its declared surface would breach on first contact. Worth naming that this was checked rather than assumed:
RuntimeConfigPluginlives incloud-connection, so the card reads at first glance like acloud-connection-only change with no CLI involvement. Following the options thread is what surfaced the collision — the same "an inherited landing-file claim is not a reading" rule that has cost this lane three corrections already.One tier question recorded, so it can be falsified rather than assumed settled
Triage adjudicated this enforce / bug-repair lane and did not flag Clause-②, reasoning that objectui's docs already declare the knob, so emitting it restores a declared contract rather than widening the surface. I agree, and the one-question test is why: does the set of accepted requests change? No — this changes what a response carries, and no request that is accepted today becomes rejected, or vice versa.
⚠️ Recording the tension anyway: #9487 also adds a key to a response body (codeon every 401) and triage did flag it Clause-②. The distinction I read is real but worth stating out loud — #9487 alters an existing error family that consumers already parse, so a consumer branching on today's shape can be affected; #9252 supplies a field that is currently absent while documented as present, so nothing that works today can break on it. If the maintainer reads that line differently, this card is fable-mandatory and ⛔ is not covered by the one-time downgrade authorization, which named only #9446/#9416/#9462/#9488/#9487.📌 Also noted from the adjudication: veto window open, and this ruling belongs in the round report's adjudication section — carried there.
Generated by Claude Code
Claim: PM loop round 18
Session:session_012WKSnqAaoqtW3QX7SSf1Vk
Branch:claude/issue-9252-product-stage-runtime-config
Worktree:objectstack-issue-9252
Domain:domain:cli
File surface:packages/cloud-connection/src/runtime-config-plugin.ts(thebrandingassembly) andpackages/cli/src/commands/serve.ts(Serve.RUNTIME_CONFIG_OPTIONS) + test siblings — stop on breach; explain in the report
Container & model:M,mode:subagent,model: opus
Clause-②: no, following triage's adjudication. The one-question test settles it: does the set of accepted requests change? No — this changes what a response carries; no request that is accepted today becomes rejected, or vice versa.⚠️ The tension with #9487 (also adds a key to a body, and was flagged Clause-②) is recorded on this card: #9487 alters an existing family consumers already parse, while this supplies a field that is currently absent while documented as present, so nothing that works today can break on it. ⛔ If the maintainer reads that line differently this card is fable-mandatory and is not covered by the one-time downgrade, which named five other cards.
Serial constraints cleared:packages/cli/src/commands/serve.tsfreed at 12:28:22Z — #9442 landed (PR #9583), verified onorigin/mainby probe:createUnknownHostnameGuardPlugin0 → 5 in that file, control 4340 lines.packages/cloud-connectioncarries no in-flight claim.⚠️ Selection was made after a full re-read oflabels:["domain:cli"], ⛔ not from the remembered blocked-set — that shortcut is what mis-ordered #9454 behind #9426 this morning, and it is recorded there.Tier derivation this round: no path-derived mandate.
opus— the collision analysis below is judgment, not mechanics.The premise, re-verified with controls (triage asked for this; the repro was against an 08-17 SHA)
probe result OS_PRODUCT_STAGE, repo-wide0 hits branding.stage/productStage/PreviewBadgeinpackages/0 hits control — OS_CLOUD_URL|OS_ALLOWinpackages/142 files The control is what makes the zeros a reading rather than a broken search: the producer side genuinely does not exist anywhere in this repository. ⇒ The bug is live and the repro is not stale.
Why this is a two-file change, and how I know
RuntimeConfigPluginlives inpackages/cloud-connection, so the card reads at first glance like acloud-connection-only fix with no CLI involvement. It is not:packages/cloud-connection/src/runtime-config-plugin.ts:541assembles thebrandingobject — the key has to start being emitted there.packages/cli/src/commands/serve.tsownsServe.RUNTIME_CONFIG_OPTIONS, the constructor options passed toRuntimeConfigPluginat:2145and:2201, and its own comment at:404says "RuntimeConfigPlugincarries the host's branding". A value resolved from an env var has to be threaded in from there.
That is why this card waited for #9442 rather than running beside it.
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 { "issue": 9252, "status": "done", "branch": "claude/issue-9252-product-stage-runtime-config", "pr": "https://github.com/objectstack-ai/objectstack/pull/9658", "premise_still_valid": true, "summary": "The bug is real and now fixed: RuntimeConfigPlugin resolves OS_PRODUCT_STAGE (host option `stage` wins over it) into `branding.stage` on /api/v1/runtime/config, so the documented PreviewBadge switch stops being a no-op. TWO ZONE-2/3 ASSUMPTIONS FALSIFIED, both with controls. (1) Zone 3 Q1 assumed the cloud distribution already honours this knob and asked me to stay consistent with it — cloud honours nothing: OS_PRODUCT_STAGE / branding.stage / PlatformStage are 0 hits repo-wide in cloud, against controls OS_PRODUCT_NAME=9 hits and 18 files mentioning branding. Nobody implemented the producer, in either distribution. Consistency was still achievable and is structural rather than by convention: cloud's RuntimeConfigPlugin EXTENDS the open one and spreads its config into super(), so one mechanism now answers this question in both distributions and cloud can still override via the constructor. (2) Zone 2's two-site claim is wrong — packages/cli is NOT touched. Both halves of the documented interface name the plugin ('Operators set it with OS_PRODUCT_STAGE or new RuntimeConfigPlugin({ stage })', objectui app-shell README), every sibling branding key already resolves `config.X ?? OS_X` in that same constructor, and decisively the card's own repro examples/app-showcase constructs its OWN RuntimeConfigPlugin at objectstack.config.ts:163, which beats the CLI's by plugin name — so threading through Serve.RUNTIME_CONFIG_OPTIONS would have left the reported repro still broken. VALUE SPACE: closed set 'preview'|'beta'|'ga', exported as PlatformStage, mirroring the union PreviewBadge branches on; an unrecognised value is refused and named in a mount-time warn listing accepted spellings rather than forwarded, because the SPA discards off-contract values anyway and a passthrough would recreate this bug's exact shape. No packages/spec change needed (runtime/config has 0 hits in packages/spec/src; pwaThemeColor proves the branding shape has one declaration site). ABSENT-WHEN-UNSET is built by conditional spread, so the key does not exist at all — no empty string, no server-invented default; the Console keeps its own documented 'preview' default.", "tests": "All at HEAD 16b3fd1ef (= the final commit; nothing landed after the gate union). GREEN: `pnpm --filter @objectstack/cloud-connection test` -> 'Test Files 24 passed (24) / Tests 210 passed (210)'. New pin alone, run separately to prove it was not a zero-match no-op -> 'Test Files 1 passed (1) / Tests 19 passed (19)'. Dependency closure built first (`pnpm --filter '@objectstack/cloud-connection^...' build`) and the package itself rebuilt; the new type reaches the rebuilt artifact ('type PlatformStage' + 'stage?: PlatformStage' present in dist/index.d.ts). REVERSE-VERIFICATION EXECUTED (type direction, against the REBUILT .d.ts): a probe assigning stage:'GA' and stage:'general-availability' produced exactly 2 errors, both on the invalid lines — TS2820 \"Type '\\\"GA\\\"' is not assignable to type 'PlatformStage | undefined'. Did you mean '\\\"ga\\\"'?\" and TS2322 — while all three valid members compiled. GATES (derived from my REAL changed paths via `node scripts/pm/dispatch-gates.mjs`, not recalled — the dispatch named no family, this derivation named 7 path-matched + 5 convention-triggered): check:nul-bytes OK (6169 files; my own scan of the 4 files: 0 control-byte lines against a control query returning 745 lines); check:route-envelope OK and it NAMES this file as 'exempt, closed at unenveloped 1' (pre-auth discovery, 2026-08-17 ruling) — this PR adds a key to that body, not a new body, so the ratchet is unmoved; check-empty-changeset / check-adr-0087-registration / check-changeset-no-major each --base origin/main OK; check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage all OK with no baseline keys added (these four are convention-triggered by adding a test file). ⚠️ NOT RUN, DECLARED RATHER THAN IMPLIED — THE ⭐ ABLATION AND check:type-check-debt. The shared lock /tmp/os-heavy-verify.lock starved this session for ~68 minutes across 9 acquisition attempts, every one exiting 99. Cause measured, not guessed: other sessions in this container queue with -w 2400 / -w 3000 (40-50 min) while a rule-compliant wait must fit inside one 10-minute foreground call (~8 min max), so the long waiters win every race; `fuser -v` named live holders throughout (e.g. issue-9462's verify.sh/consumers2.sh, an @objectstack/runtime vitest run), load average 6.16 with 5 vitest/tsc processes. Per the queue rule I stopped and reported rather than run unlocked. PREDICTIONS RECORDED BEFORE THE ATTEMPTS, so they can be executed and compared rather than taken on trust: Leg A (revert the producer to origin/main, keep the pin) predicted 9 failed / 10 passed of 19 — and the load-bearing half of that prediction is which tests SURVIVE: every absent-direction test passes VACUOUSLY pre-fix because the key was already missing, which is the bug's own baseline; Leg B (keep the fix, swap the conditional spread for `stage: this.stage`) predicted 8 failed / 11 passed, with the JSON-round-trip test still passing because JSON.stringify drops undefined — precisely why the pin asserts hasOwnProperty on the raw object. Script is ready to run at scratchpad/issue-9252/ablate.sh (reverts, runs, restores, verifies byte-identity per step). check:type-check-debt --re-measure also unrun: it demands the FULL built workspace closure, and @objectstack/cloud-connection IS in its DEBT ledger at errors:13 (it is NOT in TEST_DEBT, so my new test file is inside the measured program) — a real gate my diff can move, left to CI.", "open_questions": [], "out_of_scope_findings": [ "filed as objectstack-ai/objectui#5206 (label `finding`, unassigned, no `pm:queue`): app-shell/src/runtime-config.ts's 'Server-side: see' comment points at framework/packages/runtime/src/cloud/runtime-config-plugin.ts, which does not exist — the plugin lives in packages/cloud-connection/src/. Control: packages/runtime/src itself DOES exist, so the miss is a reading, not a typo'd search. That stale pointer is the only cross-repo link from the consumer to the producer of this very contract." ] }
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
Observed
At
189a732254e6c46af75cfa38462693e67408d2d3, runningexamples/app-showcasewithOS_PRODUCT_STAGE=ga objectstack dev: the Console still renders the "Preview" badge./api/v1/runtime/configdoes not includebranding.stage, so the objectuiPreviewBadgenever sees the documented value — the switch objectui's docs present as the operational way to hide the badge is a no-op against the OSS dev server (RuntimeConfigPlugin does not push the key; the knob appears to be honored only by the cloud distribution).Expected
Either the OSS runtime pushes
branding.stage(resolved fromOS_PRODUCT_STAGE) through/api/v1/runtime/config, or the objectui/PreviewBadge documentation states the switch is cloud-distribution-only.Repro
Recorded as an unassigned finding (not claiming). Discovered during a read-only promo-video production run against the pinned SHA above (video-studio work order steedos-labs/video-studio#1); relayed by the coordinating PM session as the producing session has no GitHub scope on this repository.