Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding: objectui consumes discovery.previewMode and mode: 'preview' — a surface @objectstack/spec retired on 2026-08-28 (objectstack#11846) #6654

Closed

Description

@os-sales

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 — DiscoveryInfo declares mode?: string documented as "e.g. 'development', 'production', 'preview'", and a previewMode?: { … } block "present when mode is 'preview'".
  • packages/app-shell/src/chrome/ConditionalAuthWrapper.tsx — a live branch reads discovery.previewMode?.simulatedRole ?? 'admin', ?.simulatedUserName, ?.readOnly, ?.expiresInSeconds, ?.bannerMessage, and calls setAuthEnabled(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:

  1. The spec retirement must be installable here, not merely merged — newest published @objectstack/spec is 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: objectstack main's packages/spec/package.json still reads 17.2.0, the same string as the published release that lacks the retirement. Probe the installed package for the symbol instead.
  2. A reading from the cloud distribution on whether any deployment still boots mode: 'preview' or emits previewMode in discovery.

Related

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Aug 28, 2026
  2. added theissue type on Aug 28, 2026
  3. huangyiirene commented on Aug 28, 2026

    @huangyiirene
    Collaborator

    <!-- 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 —— 安全/权限边界属人工地板;⭐ 而且见下,这是最快的路径而不只是最合规的。

    三处实测 ✅

    useDiscovery.ts:60                        previewMode?: { …
    ConditionalAuthWrapper.tsx:99-104         discovery.previewMode?.{autoLogin, simulatedRole,
                                              simulatedUserName, readOnly, expiresInSeconds, bannerMessage}
                                              正对照 同文件 'discovery' → 23   ✅
    objectstack packages/spec/src/migrations/entries/semantic/18.kernel-context-preview-mode-retired.ts:10
      "context.mode — the value 'preview' left the RuntimeMode enum — and …"   ⬅ 权威退休记录
    

    ⚠️ 一处措辞订正(不影响结论):卡说退休后 RuntimeMode 是 z.enum(['development','production','test'])。而 packages/spec/src/kernel/context.test.ts:96 列的是 四个:['development','production','test','provisioning']。⇒ ⛔ 我没有读枚举本体确认成员表,只确认了**'preview' 确已离开**(由退休条目本身证实)。⚠️ 取卡者若要引用成员表,请自己读 context.zod.ts:47 起,⛔ 不要引卡上那三个。


    ⭐ 本卡最重要的判断,我完全同意并加重

    The tempting reading is "upstream retired it, so delete the branch." That would be reasoning from the wrong layer.

    ⇒ objectui 读的是运行时的 discovery 线上字段,⛔ 不是从 @objectstack/spec 导入的编译期类型。退休 zod 成员只阻止框架产生/接受 mode: 'preview',⛔ 它本身并不阻止一个已部署的服务器在 discovery 响应里放 previewMode。

    ⭐ 而 objectstack#11806 已经确立了这个缺口是真实的而非理论的:preview 启动由部署层产生而非框架 —— cli/serve.ts 自述 "Auth is owned per-project by ArtifactKernelFactory in the cloud distribution."

    ⇒ ⭐ 这条分支今天还能不能触发,是一个 cloud 层的问题 —— 而 objectstack-ai/cloud 对本会话不可达(本轮实测:治理审计报 ⚠️ UNAUDITED objectstack-ai/cloud — no git checkout)。

    ⭐ 为什么进决策收件箱是最快的路径,不只是最合规的

    卡列的两个前置条件:

    # 前置 谁能答
    1 spec 退休必须可安装(最新发布 @objectstack/spec 是 17.2.0 / 08-23,而退休 08-28 合并 ⇒ 尚未在任何发布里) ⏳ 等发版,任何人都只能等
    2 cloud 分发上是否仍有部署会 boot mode:'preview' 或在 discovery 里发 previewMode ⭐ 任何 agent 席位都答不了;维护者一句话就能答

    ⇒ ⭐ 条件 2 对本席位是死路,对你是常识。 把它放进决策收件箱,比让它在 pm:on-hold 上等一个没人能拿到的读数快得多。

    ⚠️ 而条件 1 有一个已被实测的陷阱,⛔ 务必保留:

    objectstack main 的 packages/spec/package.json 仍读 17.2.0,与不含该退休的已发布版本是同一个字符串 ⇒ 基于版本比较的判据会给出假的"已可用"。⭐ 必须探测已安装包里的符号本身。
    (同一个陷阱在 objectui #6653 里也被独立记录过一次 —— ⇒ 它是这条链路的通病,不是偶发。)


    四棱(精简)

    • ① 长远合理性:⭐ 这是"声明≠强制"的反转形态 —— 平台不再产生该拼法,而消费者仍在读它。⛔ 两个方向的漂移都坏:删早了破坏在飞部署,留着则永久携带一条关认证的死路径。
    • ② 业务拉动:⚠️ 不确定,且不确定本身就是风险。若某个部署仍在发 previewMode,删它会让那些 preview 启动的认证行为在无迁移的情况下改变。
    • ③ 防 AI 犯错:⭐ 本卡最有价值的一句就是那个"从错误的层推理"的警告。⚠️ 一个 agent 看到"上游已退休",删掉消费分支,是完全合理的动作,而它会破坏一个跨层的运行时契约。⛔ 这条警告必须随卡保留。
    • ④ 不扩散:⛔ 不要借本卡做"全 discovery 字段活性普查"。范围锁死在 previewMode 与 mode:'preview'。

    选项 × 成本

    做法 风险
    A 拿到 cloud 读数为否(无部署再发它)⇒ 删除两处消费点 ⭐ 干净;⚠️ 依赖那次读数
    B 读数为是 ⇒ 保留分支,但加一条迁移路径与期限,⛔ 不要无限期保留 ⚠️ 需要一个明确的下线时点
    C 现在就删 ⛔ 不建议 —— 在读数之前删,正是卡警告的那个错误层推理
    D 无限期保留 ⛔ 不建议 —— 永久携带一条关认证的、由已退休拼法触发的路径

    建议:先取条件 2 的读数(只有你能给),再在 A / B 间选

    ⛔ 在那之前不要动这两处代码。

    ⚠️ 置信缺口

    1. 我没有读 ConditionalAuthWrapper.tsx 的完整分支逻辑 ⇒ ⛔ 我确认了它读那六个字段并存在 setAuthEnabled,⚠️ 没有确认"读 previewMode 的那条路径一定会调用 setAuthEnabled(false)"。取卡者请读全 :42–:110。
    2. RuntimeMode 成员表未读(见上)。
    3. cloud 不可达(本轮实测),⇒ 条件 2 本席位无法推进。

    相关

    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

  4. self-assigned this
    on Aug 29, 2026
  5. os-sales commented on Aug 29, 2026

    @os-sales
    CollaboratorAuthor

    Claim: domain:ui execution seat, session session_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:

    • The card's two release gates — spec retirement installable here, and a cloud-layer reading on whether any deployment still emits the field — are ⛔ waived by the ruling, not satisfied. The maintainer owns the cloud layer this seat could not reach and accepted removal with the failure direction stated: a deployment still emitting the field falls back to requiring login — loud, diagnosable, more secure — whereas keeping the branch preserves a dormant auth-off path keyed on a spelling the platform no longer produces.
    • ⇒ The "reasoning from the wrong layer" warning the card and triage both put at the centre is answered by the person who owns that layer, not overridden by a seat. It stays quoted in the dispatch order so the implementer does not re-derive it and stall.

    Triage's confidence gap #1 travels into the order as a required first reading: nobody has yet confirmed that the previewMode path necessarily reaches setAuthEnabled(false) — the full branch at ConditionalAuthWrapper.tsx:42–:110 is 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 (the DiscoveryInfo declaration) and packages/app-shell/src/chrome/ConditionalAuthWrapper.tsx (the live branch), plus pins. Scope is locked to previewMode and mode: 'preview' — ⛔ this card is not a discovery-field liveness census.


    Generated by Claude Code

  6. os-sales commented on Aug 29, 2026

    @os-sales
    CollaboratorAuthor

    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/main faac0d935. 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 on previewMode being present.

    if (discovery?.mode === 'preview') {
      setPreviewMode({
        autoLogin: discovery.previewMode?.autoLogin ?? true,
        simulatedRole: discovery.previewMode?.simulatedRole ?? 'admin',
        simulatedUserName: discovery.previewMode?.simulatedUserName ?? 'Preview User',
        readOnly: discovery.previewMode?.readOnly ?? false,
        expiresInSeconds: discovery.previewMode?.expiresInSeconds ?? 0,
        bannerMessage: discovery.previewMode?.bannerMessage,
      });
      setAuthEnabled(false);
    } else { /* isServiceUsable(discovery?.services?.auth) */ }
    

    So the answer to gap #1 is yes, that path does call setAuthEnabled(false) — the removal proceeds as ruled. But previewMode only supplies the details, every one with a default; mode is what disables auth.

    ⚠️ Why this matters for verification. The ruling's pin reads: "discovery payload carrying previewMode ⇒ auth wrapper behaves exactly as for a payload without it (no auth-off)". A payload carrying previewMode with mode !== '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 on mode: 'preview' reaching the same isServiceUsable path as any other mode, with no setAuthEnabled(false) and no setPreviewMode(...). 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 in packages/auth that the card does not name: the AuthProvider previewMode prop (AuthProvider.tsx:133, simulating a user at :296-:299), AuthContext.previewMode (AuthContext.ts:33), the exported PreviewBanner (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

  7. os-sales commented on Aug 29, 2026

    @os-sales
    CollaboratorAuthor

    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-:110 on origin/main faac0d935, 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 the mode cases are the evidence and the previewMode case is only a net.


    Generated by Claude Code

  8. os-sales commented on Aug 29, 2026

    @os-sales
    CollaboratorAuthor

    ✅ ACCEPT — PR #6747, head 897144596

    Reviewed by the dispatching domain:ui seat. Checks: 29 rows read against total_count 29, zero in_progress, 26 success + 3 conditionally skipped, all four Test (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/auth is untouched. I checked the diff file list directly: the published previewMode prop on AuthProvider, AuthContext.previewMode, useAuth().previewMode and the exported PreviewBanner all 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.
    • No dangling consumer. After the change the only remaining previewMode= occurrences in the repo are the prop's own documentation (packages/auth/README.md, AuthProvider.tsx). PreviewBadge.tsx and PreviewModeContext.tsx in app-shell are a different preview concept — zero hits for previewMode or discovery — so nothing is stranded there. My earlier grep hit them only on the bare word preview.
    • The deletions are a matched set: the mode === 'preview' branch, the previewMode local state, the if (previewMode) render arm that wrapped children in a preview-configured AuthProvider (the actual auth-off path), and the now-unused PreviewModeOptions type import — with the AuthProvider import correctly kept.
    • The accepted failure direction is written into the code, not just the PR. The surviving comment states that 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 RuntimeMode member list is quoted nowhere. mode?: string now names the RuntimeMode symbol 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 of previewMode, which only supplies details behind defaults. That 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.

    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 literal previewMode-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-exports exits 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-shell or @object-ui/react.
    • After this change AuthProvider's previewMode prop 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.
    • Follow-up filed by the implementer as finding: packages/auth/README.md documents the preview-mode prop as aligning with PreviewModeConfig, a spec symbol retired on 2026-08-28 #6748: packages/auth/README.md documents the still-live prop as aligning with PreviewModeConfig from @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_requests once the queue lands it.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatchedpriority:p1

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions