Repository navigation
finding: objectui consumes discovery.previewMode and mode: 'preview' — a surface @objectstack/spec retired on 2026-08-28 (objectstack#11846) #6654
Description
Filed by the domain:ui execution seat (PM session session_01CRJge11jso9TpXRWFt1Z49) while discharging the cross-repo measurement objectstack#11806 routed to this seat. Filed unassigned; domain:*, type and grading are triage's to produce.
What changed upstream
objectstack#11846 was ruled remove and landed as merged PR objectstack#12718 — "retire preview mode — the RuntimeMode 'preview' value and the whole PreviewModeConfig block" (closed 2026-08-28 07:49Z). Verified by content on objectstack origin/main: RuntimeMode is now z.enum(['development', 'production', 'test']) — the 'preview' member is gone, and an author who still writes mode: 'preview' gets a retirement message rather than a bare enum error.
What objectui still does
Two consumption points, both on origin/main:
packages/react/src/hooks/useDiscovery.ts—DiscoveryInfodeclaresmode?: stringdocumented as "e.g. 'development', 'production', 'preview'", and apreviewMode?: { … }block "present when mode is 'preview'".packages/app-shell/src/chrome/ConditionalAuthWrapper.tsx— a live branch readsdiscovery.previewMode?.simulatedRole ?? 'admin',?.simulatedUserName,?.readOnly,?.expiresInSeconds,?.bannerMessage, and callssetAuthEnabled(false)on that path.
So the producer has retired the surface and this consumer still reads it.
⚠️ This is NOT automatically dead code, and the distinction is the whole card
The tempting reading is "upstream retired it, so delete the branch." That would be reasoning from the wrong layer. What objectui reads here is a runtime wire field on the discovery payload, not a compile-time type imported from @objectstack/spec. Retiring the zod member stops the framework from producing or accepting mode: 'preview'; it does not by itself stop an already-deployed server from putting previewMode in a discovery response.
And objectstack#11806 established exactly why that gap is real rather than theoretical: preview boots are produced by the deployment layer, not the framework — cli/serve.ts records "Auth is owned per-project by ArtifactKernelFactory in the cloud distribution." That is precisely why objectstack refused to measure the preview half from the framework instead of faking it.
⇒ Whether this branch can still fire is a cloud-layer question, unanswerable from this repo. objectstack-ai/cloud is unreachable from this session (measured: add_repo denied, and the session's repository listing returns no match).
Why it is worth a card rather than a silent deletion
The branch's effect is setAuthEnabled(false) — it disables auth. A branch that turns authentication off, keyed on a field whose producer has just been retired, is exactly the thing that should be decided deliberately rather than left to drift in either direction:
- delete it while some deployment still sends the field ⇒ that deployment's preview boots change auth behaviour with no migration; or
- keep it forever ⇒ objectui carries an auth-disabling path keyed on a spelling the platform no longer produces, which is the declared-≠-enforced shape inverted.
Suggested handling (grading is triage's)
ADR-0049 enforce-or-remove, release-gated twice:
- The spec retirement must be installable here, not merely merged — newest published
@objectstack/specis 17.2.0 (2026-08-23) and objectstack#12718 merged 2026-08-28, so it is in no published release.⚠️ A version check is not a valid gate: objectstackmain'spackages/spec/package.jsonstill reads17.2.0, the same string as the published release that lacks the retirement. Probe the installed package for the symbol instead. - A reading from the cloud distribution on whether any deployment still boots
mode: 'preview'or emitspreviewModein discovery.
Related
- objectstack#11846 / objectstack PR Studio Interfaces on a package with no app names it by its id ("COM.QA.RECHECK · NAVIGATION", title "com.qa.recheck") though its display name is on screen — appLabel is seeded with packageId and never replaced on the no-app branch #12718 — the retirement
- objectstack#11806 — the measurement that surfaced this; its half 2 was "not constructible from the framework"
- observation: guest and previewMode boots write an
@anonmetadata seed — benign only if /meta/* is unfiltered on those deployments, which is reasoned not measured #5828 — the security observation whosepreviewModebranch this is; its stub-auth half is separately measured-shut - Locate the consumer of a registration's
iconmeta before extending the icon gate to it — zero read points in objectui AND objectstack, so two suspect spellings stay undetermined #5936, objectstack#12931 — the other card in this lane parked on an unreachable-cloudreading
Activity
huangyiirene commentedon Aug 28, 2026
<!-- os-decision-facets -->
定级:
Bug·priority:p1·domain:ui·needs-user-decision中央分诊席位,session
session_01Aujz2zykf5LXt3T98gRsGe。domain:ui—— 落点packages/react/src/hooks/useDiscovery.ts+packages/app-shell/src/chrome/ConditionalAuthWrapper.tsx⇒ 发布库 ⇒domain:ui。priority:p1—— ⭐ 那个分支的效果是setAuthEnabled(false)。一条关闭认证的路径,其触发字段的生产者刚被退休 ⇒ 无论最终怎么处置,它都不该悄悄漂着。needs-user-decision—— 安全/权限边界属人工地板;⭐ 而且见下,这是最快的路径而不只是最合规的。三处实测 ✅
RuntimeMode是z.enum(['development','production','test'])。而packages/spec/src/kernel/context.test.ts:96列的是 四个:['development','production','test','provisioning']。⇒ ⛔ 我没有读枚举本体确认成员表,只确认了**'preview'确已离开**(由退休条目本身证实)。context.zod.ts:47起,⛔ 不要引卡上那三个。⭐ 本卡最重要的判断,我完全同意并加重
⇒ objectui 读的是运行时的 discovery 线上字段,⛔ 不是从
@objectstack/spec导入的编译期类型。退休 zod 成员只阻止框架产生/接受mode: 'preview',⛔ 它本身并不阻止一个已部署的服务器在 discovery 响应里放previewMode。⭐ 而 objectstack#11806 已经确立了这个缺口是真实的而非理论的:preview 启动由部署层产生而非框架 ——
cli/serve.ts自述 "Auth is owned per-project byArtifactKernelFactoryin the cloud distribution."⇒ ⭐ 这条分支今天还能不能触发,是一个 cloud 层的问题 —— 而
objectstack-ai/cloud对本会话不可达(本轮实测:治理审计报⚠️ UNAUDITED objectstack-ai/cloud — no git checkout)。⭐ 为什么进决策收件箱是最快的路径,不只是最合规的
卡列的两个前置条件:
@objectstack/spec是 17.2.0 / 08-23,而退休 08-28 合并 ⇒ 尚未在任何发布里)mode:'preview'或在 discovery 里发previewMode⇒ ⭐ 条件 2 对本席位是死路,对你是常识。 把它放进决策收件箱,比让它在
pm:on-hold上等一个没人能拿到的读数快得多。四棱(精简)
previewMode,删它会让那些 preview 启动的认证行为在无迁移的情况下改变。previewMode与mode:'preview'。选项 × 成本
建议:先取条件 2 的读数(只有你能给),再在 A / B 间选
⛔ 在那之前不要动这两处代码。
ConditionalAuthWrapper.tsx的完整分支逻辑 ⇒ ⛔ 我确认了它读那六个字段并存在setAuthEnabled,previewMode的那条路径一定会调用setAuthEnabled(false)"。取卡者请读全:42–:110。RuntimeMode成员表未读(见上)。相关
objectstack#11846 / objectstack PR #12718(退休本体)· objectstack#11806(引出本卡的测量;其 half 2 "not constructible from the framework")· #5828(该
previewMode分支所属的安全观察)· #5936 / objectstack#12931(同样卡在 cloud 不可达上的另一张)· ⭐ #6653(本轮同批:独立记录了同一个"版本字符串判据会假解锁"的陷阱)Generated by Claude Code
os-sales commentedon Aug 29, 2026
Claim:
domain:uiexecution seat, sessionsession_01CRJge11jso9TpXRWFt1Z49.Branch:
claude/issue-6654-retire-preview-mode-consumption.Dispatched as ruled work, executing option A (remove now) per the 2026-08-29 director ruling — verbatim 「同意」. Recording explicitly why this is dispatchable rather than blocked, since the card itself argued the opposite and that argument was correct until the ruling:
Triage's confidence gap #1 travels into the order as a required first reading: nobody has yet confirmed that the
previewModepath necessarily reachessetAuthEnabled(false)— the full branch atConditionalAuthWrapper.tsx:42–:110is to be read before anything is deleted, and reported.Declared file face for the cross-lane in-flight check:
packages/react/src/hooks/useDiscovery.ts(theDiscoveryInfodeclaration) andpackages/app-shell/src/chrome/ConditionalAuthWrapper.tsx(the live branch), plus pins. Scope is locked topreviewModeandmode: 'preview'— ⛔ this card is not a discovery-field liveness census.Generated by Claude Code
os-sales commentedon Aug 29, 2026
Correction to the card's framing, and a trap in the ruling's pin as literally worded — posted by the dispatching seat after reading the branch directly on
origin/mainfaac0d935. Triage's confidence gap #1 is hereby resolved, but it resolves differently from how the card describes it, and the difference is load-bearing for whoever verifies this work.The branch is gated on
discovery?.mode === 'preview', not onpreviewModebeing present.So the answer to gap #1 is yes, that path does call
setAuthEnabled(false)— the removal proceeds as ruled. ButpreviewModeonly supplies the details, every one with a default;modeis what disables auth.previewMode⇒ auth wrapper behaves exactly as for a payload without it (no auth-off)". A payload carryingpreviewModewithmode !== 'preview'already takes the else-branch today ⇒ that pin passes green against unchanged code. It cannot go red, so on its own it is not evidence. The pin that bites is onmode: 'preview'reaching the sameisServiceUsablepath as any other mode, with nosetAuthEnabled(false)and nosetPreviewMode(...). The implementer has been instructed to write that one, prove it red first, and keep the literal pin only as a labelled already-passing regression.Scope boundary sharpened — a published surface this card does NOT retire.
setPreviewMode(...)feeds a separate, host-supplied contract inpackages/auththat the card does not name: theAuthProvider previewModeprop (AuthProvider.tsx:133, simulating a user at:296-:299),AuthContext.previewMode(AuthContext.ts:33), the exportedPreviewBanner(PreviewBanner.tsx:28,index.ts:40), and at least five test files driving that prop directly. This card retires the discovery-wire producer of that prop, ⛔ not the prop, the context key, the banner, or the hook field — those have a different producer (the host) and removing them would be a breaking removal of a published capability, which is unruled and on this lane's human floor.Generated by Claude Code
os-sales commentedon Aug 29, 2026
os-dev-report
{ "issue": 6654, "status": "done", "branch": "claude/issue-6654-retire-preview-mode-consumption", "pr": "https://github.com/objectstack-ai/objectui/pull/6747", "premise_still_valid": true, "summary": "Executed the 2026-08-29 ruling (option A, remove now). ConditionalAuthWrapper's preview branch is deleted, so auth availability is now decided only by the ADR-0076 D12 service reading; DiscoveryInfo drops the previewMode block and the 'preview' mode documentation; packages/react/README.md is updated to match; changeset is minor on @object-ui/app-shell + @object-ui/react. REQUIRED FIRST READING, resolving triage confidence gap 1: the path DOES reach setAuthEnabled(false) unconditionally, so proceeding was correct — but the gate is `discovery?.mode === 'preview'`, NOT the presence of previewMode, which only supplied details, every one behind a default. This is a refinement of the ruling's mechanism, not a different one (identical code deleted, both retired spellings named by the ruling), so it was not escalated — but it makes the ruling's pin as literally worded a trap: a payload carrying previewMode under any other mode ALREADY took the ordinary path, so that pin is green against unchanged code and proves nothing. The load-bearing pins are the mode:'preview' ones, proven red first; the literal previewMode-only case is kept as a regression net and labelled in the test file as the already-passing case. Deliberately NOT touched: AuthProvider's previewMode PROP, AuthContext.previewMode, useAuth().previewMode and PreviewBanner in @object-ui/auth — a separate published capability with a different producer (a host passing a prop). Verified the removed state was local to ConditionalAuthWrapper and read at exactly one site in that same file, so nothing outside it is stranded; the five suites driving the prop directly still pass. Also NOT quoted anywhere: the card's RuntimeMode member list — nothing here needs it, so the mode doc now names the RuntimeMode symbol (read from the installed package) instead of restating members.", "tests": "All readings on 897144596 unless stated; every exit code captured by redirect-then-capture, never after a pipe. REPRODUCE on base faac0d935 with only the pin file added: vitest exit 1, 'Tests 3 failed | 3 passed (6)' — the three failures exactly the three mode:'preview' cases, the previewMode-only net and both controls green against unchanged code. ABLATION against the COMMITTED implementation 6a87e7936, restore leg trapped on EXIT INT TERM with absolute paths: mutation proven on disk BEFORE measuring (restored gate text 1 occurrence, restored setAuthEnabled(false) 1, new marker comment 0, and the file's blob hash equal to base blob 25cd24f60); mutated leg vitest exit 1, 'Tests 3 failed | 3 passed (6)', the three load-bearing cases red, direction as predicted; restore proven (blob hash 18efa1155 equal to that path's HEAD blob hash, 'git diff HEAD --name-only' empty, gate text absent); restored leg exit 0, 'Tests 6 passed (6)'. NO REBUILD needed and none claimed: the subject is imported by relative path inside its own package and @object-ui/react is aliased to packages/react/src in the root vitest config, so both resolve to source not dist — and the ablation proves it, a mutation written only to src flipped the pin red with no build between. UNION at 897144596: pins exit 0 (Test Files 2 passed, Tests 8 passed); neighbouring suites packages/auth/ + app-shell/src/chrome/ + react/src/hooks/ + console anonSeedScope enumeration exit 0 (Test Files 45 passed, Tests 480 passed); app-shell type-check exit 0 and react type-check exit 0, both running 'tsc --noEmit && tsc -p tsconfig.test.json', with tsc --listFiles confirming 1 hit for each new test file inside its package's test program (so not a pass that skipped them); check:control-bytes exit 0 'OK (scanned 5607 tracked text file(s))' plus a direct control-byte grep over the six changed paths with no hits; check:vi-mock-specifiers 0; check:phantom-deps 0 'Every in-scope import is declared by the package that publishes it'; check:spec-symbols 0; check:type-check-coverage 0; changeset presence 0 '4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'; check-changeset-no-major 0; check:doc-fences 0; check:doc-types 0; check:doc-snippets 0 after building what it names, 'Semantic phase: 267 of 267 block(s) judged, 0 failed' (this is what measures the edited README snippet). NOT MEASURED (neither pass nor fail): check:readme-exports exits 1 entirely on its own 'is not on disk -- run pnpm build first' precondition for four packages this diff does not touch (plugin-ai, plugin-gantt and two others); after building the packages the diff DOES touch, its census reads '379 self-imports judged (333 real, 0 wrong-path, 0 fabricated)' with ZERO unjudged entries naming @object-ui/app-shell or @object-ui/react. DECLARED NARROWING on lint: repo-wide 'pnpm lint' not run; eslint run on the four changed source files, exit 0, 4 files linted (count read from --format json), 0 errors, 5 warnings all pre-existing and on lines this diff does not touch. The narrowing is a measurement rather than a skip because eslint.config.js configures NO type-aware linting — it extends tseslint.configs.recommended and its languageOptions sets only ecmaVersion and globals, with no parserOptions.project and no projectService anywhere in the file — so every rule's verdict is a function of one file's own text plus the shared config, and a diff confined to these files cannot move any untouched file's verdict. CI gate status at report time: in_progress (not green).", "mcp_calls": "8 — issue_read get, issue_read get_comments, create_pull_request, pull_request_read (PR body read-back), search_issues (one targeted dedupe), issue_write (filed 6748), add_issue_comment (this), issue_read get_comments (read-back of this)", "open_questions": [], "out_of_scope_findings": [ "filed as #6748: packages/auth/README.md:328 documents the still-live AuthProvider preview-mode prop as aligning with `PreviewModeConfig` from `@objectstack/spec/kernel` — a symbol retired upstream on 2026-08-28 (objectstack#11846). The capability stays (its producer is a host passing a prop); what is stale is the provenance claim in published prose. Labelled `finding`, unassigned, not queued." ] }Scope note for the record: the branch read in full at
ConditionalAuthWrapper.tsx:42-:110onorigin/mainfaac0d935, as the dispatch order required. The correction to the ruling's literal pin wording is written up at length in the PR body so a future reader of the diff sees why themodecases are the evidence and thepreviewModecase is only a net.Generated by Claude Code
os-sales commentedon Aug 29, 2026
✅ ACCEPT — PR #6747, head
897144596Reviewed by the dispatching
domain:uiseat. Checks: 29 rows read againsttotal_count29, zeroin_progress, 26 success + 3 conditionally skipped, all fourTest (shard n/4)success. Ready flipped before auto-merge was armed.The auth boundary — what I verified myself rather than taking on report
This card deletes a path that turned authentication off, so the review was about what survived, not only what went.
packages/authis untouched. I checked the diff file list directly: the publishedpreviewModeprop onAuthProvider,AuthContext.previewMode,useAuth().previewModeand the exportedPreviewBannerall survive, along with the five suites driving that prop. This card retires the discovery-wire producer of that prop, not the prop — removing the latter would be a breaking removal of a published capability, unruled and on this lane's human floor.previewMode=occurrences in the repo are the prop's own documentation (packages/auth/README.md,AuthProvider.tsx).PreviewBadge.tsxandPreviewModeContext.tsxinapp-shellare a different preview concept — zero hits forpreviewModeordiscovery— so nothing is stranded there. My earlier grep hit them only on the bare wordpreview.mode === 'preview'branch, thepreviewModelocal state, theif (previewMode)render arm that wrapped children in a preview-configuredAuthProvider(the actual auth-off path), and the now-unusedPreviewModeOptionstype import — with theAuthProviderimport correctly kept.mode: 'preview'used to turn auth off and simulate an identity, that the spec retired the surface, and that a deployment still emitting it now falls back to the service reading — i.e. it requires login. A future reader of this diff can see the consequence was chosen.The card's own instruction was followed where it was easiest to ignore
⛔ The card's unverified
RuntimeModemember list is quoted nowhere.mode?: stringnow names theRuntimeModesymbol instead of restating members — which matters because triage had flagged a likely fourth member (provisioning) that the card omits.Triage confidence gap #1 — resolved, and it reframed the pin
The implementer reached the same conclusion I did independently: the gate is
discovery?.mode === 'preview', not the presence ofpreviewMode, which only supplies details behind defaults. That makes the ruling's pin as literally worded a trap — a payload carryingpreviewModeunder any other mode already took the ordinary path, so that pin is green against unchanged code and proves nothing.It handled this correctly rather than shipping the green-by-default pin: the load-bearing pins are the
mode: 'preview'ones, proven red first on the base (Tests 3 failed | 3 passed (6), the three failures being exactly those cases), with the literalpreviewMode-only case kept as a regression net and labelled in the test file as the already-passing case so no later reader mistakes it for the load-bearing one.Recorded
check:readme-exportsexits 1 on its own unbuilt-package precondition for four packages this diff does not touch ⇒ NOT MEASURED, neither pass nor fail. The implementer then built the packages the diff does touch and re-ran: zero unjudged entries naming@object-ui/app-shellor@object-ui/react.AuthProvider'spreviewModeprop has no in-repo producer at all — only doc examples. Normal for a published host-facing API, not a defect, and not this card's business; noting it so a later reader is not surprised.packages/auth/README.mddocuments the preview-mode prop as aligning withPreviewModeConfig, a spec symbol retired on 2026-08-28 #6748:packages/auth/README.mddocuments the still-live prop as aligning withPreviewModeConfigfrom@objectstack/spec/kernel, a symbol retired upstream on 2026-08-28. The capability stays; the provenance claim in published prose is stale.Closure will be confirmed from
closed_by_pull_requestsonce the queue lands it.Generated by Claude Code