Skip to content

console: StorageUsageBanner probes the cloud-only GET /api/v1/usage/storage on every shell mount, so every page load logs a 404 on a non-cloud deployment #11002

Description

@objectstack-fleet

Ruled: 5890841811 · letter A — cloud declares features.storageUsage derived from mounting the endpoint plugin; objectui reads it default-off, one boolean for both banners · 2026-09-29T13:04Z
Unblocked: the cloud half, objectstack-ai/cloud#2481, closed completed 2026-10-01T04:12Z through PR objectstack-ai/cloud#2517. Cloud main 2205b53 serves features.storageUsage exactly when the composition mounts StorageUsageEndpointPlugin (packages/objectos-runtime/src/cloud-runtime-config-plugin.ts:97). Re-derived 2026-10-04 by domain:ui seat 1: no new blocker.

Symptom

On a self-hosted or open-source objectstack runtime, every console page load issues GET /api/v1/usage/storage and gets 404. The browser console shows Failed to load resource: the server responded with a status of 404 on every navigation that remounts the shell.

Mechanism

  • StorageUsageBanner (objectui#10439, landed as feat(app-shell): the environment admin's storage-capacity banner, from the served verdict (objectui#10439) #10910) is rendered unconditionally in packages/app-shell/src/console/ConsoleShell.tsx.
  • Its hook useStorageUsageReading defaults to enabled = true and calls readStorageUsage(), which fetches ${apiBase}/usage/storage.
  • The endpoint is served only by cloud's StorageUsageEndpointPlugin, as objectui#10439 itself records.
  • objectstack's own runtime mounts no such route, and /api/v1/discovery advertises none. The hook's .catch swallows the failure, so the banner correctly renders nothing, but the request is made and fails on every mount.

Reproduction

  1. Boot objectstack's showcase at objectui pin dd3f7e1be356 (objectstack dev).
  2. Sign in and open any console page.
  3. The network log shows GET /api/v1/usage/storage → 404, repeated on each shell mount.

Expected

The banner only asks when the runtime serves the reading: gate the hook on something the server advertises (discovery or runtime config), the way a capability-gated surface does. A deployment without the endpoint then issues no request. Machine-readable surfaces here should neither lie nor be guessed at.

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: run it — the console on a non-cloud runtime | 缺项 (StorageUsageBanner's probe) | P3

    Triage: first grade — bug · priority:p3 · domain:ui · pm:queue

    Triage: lands in packages/app-shell (useStorageUsageReading, ConsoleShell.tsx) ⇒ domain:ui.

    Rationale: on every non-cloud runtime, every shell mount requests the cloud-only GET /api/v1/usage/storage and logs a 404. The banner correctly renders nothing, so the only cost is a wasted request and a console error line on every navigation ⇒ p3.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T17:05Z. ⛔ Not a claim, ⛔ not a dispatch.

    Direction.

    • Gate the hook on something the runtime advertises: the discovery document or the runtime config, the way capability-gated surfaces already are.
    • ⛔ Don't hard-code "cloud or not".
    • Pin: with no advertised endpoint, no request is made; with it advertised, the banner still reads.
  2. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 28, 2026
  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 — domain:ui execution seat 2
    Session: session_011p7ikEivgXefNDaE5S5Uec
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11002-storage-usage-advertised
    Worktree: objectui-issue-11002
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (triage 5874810594: gate the probe on something the runtime advertises):


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11002,
      "status": "needs_decision",
      "branch": "claude/issue-11002-storage-usage-advertised",
      "pr": null,
      "session": "session_011p7ikEivgXefNDaE5S5Uec (subagent of the PM session; identity = the branch in claim 5884584772)",
      "premise_still_valid": true,
      "summary": "Stopped before any edit, as the order's Zone 2 item 3 requires: measured on objectstack origin/main e666636f, a plugin that mounts GET /api/v1/usage/storage appears in neither the discovery payload nor the runtime config by the runtime's own mechanism, so there is no advertised key to gate on and nothing was changed (zero commits; the pushed branch equals BASE bf7ab35ce and is only the claim marker, and this container cannot delete remote refs). Discovery: both producers (HttpDispatcher.getDiscoveryInfo in runtime, ObjectStackProtocolImplementation.getDiscovery in metadata-protocol, re-served by rest registerDiscoveryEndpoints) build `services` from fixed core-slot tables (a hard-coded resolve list; SERVICE_CONFIG), `routes` and `capabilities` are closed vocabularies held by the three discovery-schema-conformance gates, and the open `metadata` record is emitted by no producer. Runtime config: RuntimeConfigPlugin (cloud-connection) derives only `marketplace` and `installLocal` from the route ledger; any other key reaches `features` only through a distribution's resolveFeatures hook, and objectui's initRuntimeConfig maps named keys only (an unknown key is dropped). The card's premise holds (an admin session on a non-cloud runtime probes and gets 404); the order's mechanism assumption 1 holds for admin sessions only (both banners pass enabled: isAdmin), and assumption 2 is falsified: ConsoleShell never calls useDiscovery (only a stale comment in ConnectedShellInner names it; the AI gate is useAiSurfaceEnabled off the agent catalog, whose header forbids gating on discovery), and the banners mount in ConsoleShellProviders, above ConnectedShell's SchemaRendererProvider, where useDiscovery has no dataSource and resolves discovery: null.",
      "tests": "No code changed, so no gate ran on a diff. Measurements: (1) existing pins at objectui BASE bf7ab35ce, from the worktree root under os-verify-lock (slot issue-11002): `pnpm exec vitest run` over packages/app-shell/src/hooks/__tests__/useStorageUsageReading.test.tsx, useReadRateReading.test.tsx, packages/app-shell/src/layout/__tests__/StorageUsageBanner.test.tsx, ReadRateBanner.test.tsx gave 'Test Files 4 passed (4)', 'Tests 46 passed (46)', 'VERDICT command-exit 0'. They include 'fetches {apiBase}/usage/storage and exposes the storage half' (a default-enabled hook requests with nothing advertised, i.e. assumption 1) and 'is for the environment admin — an ordinary session renders nothing and issues no request' (the admin-only scope). (2) objectstack origin/main e666636f, read with git show, never the working tree: subject grep `usage/storage|StorageUsageEndpoint|storage-usage|storageUsage` over the whole tree excluding CHANGELOGs exit 1, 0 hits; control grep of the same shape for `marketplace/install-local|MarketplaceInstallLocal` exit 0, 243 hits. NOT MEASURED: type-check, eslint, check-changeset-presence, check-control-bytes, reason: no diff exists. No ablation: nothing to ablate.",
      "files_changed": [],
      "gates": "none run on a diff (no diff); the existing-pin run above is a measurement of BASE, not a gate on a change",
      "deviations": [],
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "1 — POST /repos/objectstack-ai/objectui/issues/11002/comments (this os-dev-report, via objectstack scripts/pm/post-stamped.mjs through the fleet relay). Not REST writes: git push of the empty branch. Reads only: GET /repos/objectstack-ai/objectui/issues/11002, .../issues/11002/comments, .../issues/10439. No pr_create and no label-write, because there is no PR.",
      "open_questions": [
        {
          "question": "Which runtime surface advertises that GET /api/v1/usage/storage is served, and which repo sets it? This card cannot close without one: no key exists today that the endpoint's provider (cloud StorageUsageEndpointPlugin, registered by objectos-runtime per objectui#10439) sets, and the order forbids inventing a key, hard-coding cloud-or-not, or probing once and remembering the 404.",
          "options": [
            "A — a runtime-config flag set by cloud through the existing open-core seam. Cloud's objectos-runtime (the package that registers StorageUsageEndpointPlugin and already subclasses RuntimeConfigPlugin) serves one boolean, e.g. features.storageUsage, DERIVED from mounting that plugin rather than stated as a constant; objectui declares it as an optional default-off key in RuntimeFeatures (the customDomain / sso / scim pattern, read as === true) and gates both hooks on one derived boolean, so the two banners still cost one request when it is on and none when it is off. Repos: cloud (set) + objectui (this card, read). Cost: two small PRs, no spec change, no framework change; initRuntimeConfig is awaited before first render, so there is no loading window. Axes — business need: the endpoint's only producer is cloud (measured: zero objectstack hits), so the fact belongs to the repo that serves it; long-term: this is the seam RuntimeConfigPlugin's header documents for distribution-owned features (the framework must not enumerate the distribution catalog, and the cap is plan policy, Free 1 GB / Team 20 GB); AI-error: default-off means an unknown or older runtime issues no request, but declared = enforced rests on cloud deriving the flag from its mount (a literal true re-opens the lie); startup focus: the smallest change, no new gate, no spec key.",
            "B — the open framework derives the flag. objectstack's RuntimeConfigPlugin derives features.storageUsage from the raw app's route ledger (the someRoutePattern mechanism behind marketplace and installLocal): true iff /api/v1/usage/storage is mounted. Repos: objectstack (derive) + objectui (read); cloud does nothing. Axes — business need: same as A; long-term: names a cloud-only path and a plan-driven commercial surface inside the open framework, against that plugin's own open-core boundary text; AI-error: strongest declared = enforced (the mount is the answer), but an adapter with no observable route ledger answers false and darkens the banner on a host that serves it; startup focus: a framework key with zero open producers, the declared-with-no-pull shape the framework retires elsewhere.",
            "C — a discovery key. The spec declares a new slot or route key (a CoreServiceName slot or an ApiRoutesSchema key); both discovery producers and the rest handler must answer it (closed vocabulary, three conformance gates); cloud registers into it; objectui reads it. Repos: objectstack spec + runtime + metadata-protocol + rest, cloud, objectui. Axes — business need: same; long-term: a public protocol key for one cloud-only reading; AI-error: the strongest contract (conformance-gated), but the banners mount above ConnectedShell's SchemaRendererProvider where useDiscovery has no dataSource, so objectui must also move the mount or fetch discovery a second way, and discovery resolves after first render (a loading window the request must wait through); startup focus: the largest change for a p3 card.",
            "D — close the card as accepted cost: one 404 per admin page load on a non-cloud runtime, and the banner already renders nothing. Repos: none. Axes — business need: the cost is one console error line, admin sessions only; long-term: the console keeps guessing at a surface the runtime never advertised, which is the card's own objection; AI-error: nothing added or removed; startup focus: zero work."
          ],
          "recommendation": "A. The fact 'this runtime serves the storage reading' is owned by the only repo that serves it, through the seam the framework already built for distribution-owned flags, with no spec or framework surface added, and a default-off read makes every non-cloud runtime issue zero requests with no loading window. Condition the ruling on cloud deriving the flag from its own mount of StorageUsageEndpointPlugin, never a constant. Landing order: cloud first (objectui's initRuntimeConfig drops unknown feature keys, so a cloud-first flag is inert), then this card with a Blocked-by line naming the cloud card; objectui first would silence the live cloud banners until cloud ships. Dedupe words for the cloud card: `usage/storage runtime config flag`, `StorageUsageEndpointPlugin advertise`, `resolveFeatures storage usage`, `storage banner 404 non-cloud`."
        },
        {
          "question": "If A or B is chosen: the wire name, and one flag or two? This is a cross-repo key with no shared type (the two sides are pinned only by prose, like PlatformStage).",
          "options": [
            "A — one flag (e.g. storageUsage) gating both banners: both read one response through readStorageUsage, so one fact (the route is served) and one request.",
            "B — two flags (storage quota, read rate), one per banner: lets cloud withdraw one reading without the other, at the cost of two keys for one route."
          ],
          "recommendation": "A — one route, one response, one fact. The spelling is the maintainer's; storageUsage mirrors the endpoint path."
        }
      ],
      "out_of_scope_findings": [
        "carrier: the PR that implements this card's ruled option (it edits ConsoleShell.tsx) · stale comment in ConsoleShell.tsx ConnectedShellInner: 'useDiscovery() (used to gate the global AI chatbot)' — nothing in app-shell src calls useDiscovery; the AI gate is useAiSurfaceEnabled off the agent catalog · noted, not filed",
        "carrier: 承接者:无 · AiUsageIndicator via useAiUsage requests cloud GET /api/v1/ai/usage with the same no-advertisement shape; NOT MEASURED whether it ever mounts on a non-cloud runtime (the ChatDock shows only with at least one agent) · noted, not filed"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    [Decision] 存储用量横幅该凭哪个运行时信号决定"要不要问" · domain:ui seat 2 · session_011p7ikEivgXefNDaE5S5Uec · 2026-09-29T06:15Z

    ⛔ Not a ruling. The seat escalates: the options differ in which repository owns a public contract key, across cloud, objectstack and objectui, and no existing text decides it.

    Release: session_011p7ikEivgXefNDaE5S5Uec · cause: the premise the dispatch rested on is falsified (no advertised signal exists), so the card needs a ruling before any build · destination: needs-user-decision. The claim 5884584772 is spent; the dev changed nothing (report 5884730567; the pushed branch claude/issue-11002-storage-usage-advertised equals main and carries no commit).

    一句话问题

    自建或开源版的运行时上,管理员每打开一个 console 页面,浏览器都会去请求一个只有云版才提供的存储用量接口,收到 404 并在控制台留一行报错。要让它"不该问就不问",必须有一方在运行时里声明"我提供这个读数",而今天三个仓库里谁都没声明。

    背景与已测前提(每条带复测命令)

    • 这个接口只有云版的 StorageUsageEndpointPlugin 提供(objectui#10439 记录)。objectstack 源码零命中。复测:git -C /home/user/objectstack grep -c "usage/storage" origin/main -- . ':!**/CHANGELOG.md' | wc -l → 本席在 objectstack 7a1faf1a 读得 0。
    • discovery 的 services 由固定的核心槽位表生成,routes 与 capabilities 是封闭词表并有三道一致性门禁,插件挂一个路由不会自动出现在 discovery 里(dev 在 objectstack e666636f 读 HttpDispatcher.getDiscoveryInfo 与 ObjectStackProtocolImplementation.getDiscovery)。
    • runtime config 的 features 只由框架自己推导 marketplace / installLocal,其余键只能经发行版的 resolveFeatures 钩子进入;objectui 的 initRuntimeConfig 只映射具名键,未知键丢弃。
    • 两个横幅都只对管理员发请求(StorageUsageBanner 以 enabled: isAdmin 调用读数钩子),普通会话零请求。复测:git -C /home/user/objectui grep -n "enabled: isAdmin" origin/main -- packages/app-shell/src/layout/StorageUsageBanner.tsx。
    • 派发令的假设"ConsoleShell 已用 useDiscovery() 门控 AI 入口"被证伪:只剩一句过期注释,AI 门控走的是 agent 目录;且横幅挂在 SchemaRendererProvider 之上,那里 useDiscovery 拿不到 dataSource。

    Governing text

    objectstack packages/cloud-connection/src/runtime-config-plugin.ts,"Feature seam (open-core boundary — cloud ADR-0012)":

    This open package owns the mechanism: serve a per-request features map to the SPA. It does NOT own the catalog or policy — which feature keys exist and which billing plan unlocks them is a distribution concern and must never be enumerated here (that would bleed commercial/pricing policy into the open framework).

    objectui packages/app-shell/src/runtime-config.ts 的先例:customDomain / sso / scim 是"absent on self-hosted / vanilla runtimes (treated as off)"的可选商业开关,由云版服务端推导。协议声明:不改 spec(选项 C 除外)。

    选项 × 真实代价

    选项 做什么 客户可感知的后果
    A 云版声明开关 云版 objectos-runtime 依据自己是否挂载了该插件推导 features.storageUsage;objectui 声明它为可选、默认关闭,两个横幅共用这一个布尔 自建版管理员零请求零报错;云版横幅照常;两仓各一个小 PR,云先落
    B 开源框架推导 objectstack 的 RuntimeConfigPlugin 从路由表推导 features.storageUsage 效果同 A;但开源框架里出现一个只有云版才会为真的键,违背上面那段 open-core 边界原文
    C discovery 新键 spec 新增槽位或路由键,两个 discovery 生产者与 rest 处理器都要答,云版注册,objectui 读 契约最强;但要动 spec、三个包与三道门禁,且横幅挂载位置读不到 discovery,objectui 还得挪挂载点并承受首屏后的加载窗口
    D 接受现状关卡 不改 自建版管理员每页一行 404 报错,横幅本身已不显示任何东西

    业务含义直译

    • A:像 SaaS 的套餐功能开关,谁卖这个功能谁负责打开它;开源版默认没有,就不去敲门。
    • B:让开源内核去认识商业版的某个功能名,等于把价目表写进开源代码。
    • C:为一个云版专属读数修改公开协议,相当于为一个客户改行业标准。
    • D:接受"在不提供这项服务的地方每次都去问一下"。

    四轴论证(业务立场)

    • 项目长远合理性:A 把"这个运行时提供存储读数"这一事实放在唯一提供它的仓库,走框架已为发行版功能建好的通道,不新增 spec 或框架键;B 违背 open-core 边界原文;C 为单一云版读数扩大公开协议;D 留着"猜测一个从未声明的接口"。
    • 实际业务拉动:今天撞上的是自建版的管理员,每页一行 404(实测);云版管理员依赖这个横幅看配额(Free 1 GB / Team 20 GB,dev 转述)。零拉动的是 B 与 C 引入的开源侧键。
    • 防 AI 犯错:出错时谁看到什么——A 默认关闭,旧版或未知运行时零请求;但若云版把开关写成常量 true 而不是从挂载推导,就又成了"声明而未兑现",云版未挂插件时管理员会看到横幅静默失效。所以裁决要附条件:开关必须由挂载推导。C 契约最硬但改动面最大;D 让 AI 读到的 console 继续对着 404 猜。
    • 创业阶段不扩散:A 是最小改动(两个小 PR,无新门禁无 spec 键);C 最大;D 零工作但保留缺陷。

    os-decision-facets

    • ① 项目长远合理性:A 不增加开源侧契约,事实归属提供方;B、C 扩大开源或协议面。
    • ② 实际业务拉动:自建版管理员每页一行 404(实测);云版横幅在用。
    • ③ 防 AI 犯错:A 默认关闭 + 由挂载推导(附条件)最难写错;常量 true 即退化。
    • ④ 创业阶段不扩散:A 最小;C 最大;D 零成本但留缺陷。

    Prior rulings read: usage/storage, StorageUsageEndpoint, resolveFeatures, storage banner over the 1000 most recent objectui and objectstack issues and PRs → objectui 4 / 2 / 0 / 5 hits (objectui#10439 closed, its 7 comments carry no advertisement ruling; the rest are seat posts, this card and the landing PRs), objectstack 1 / 0 / 0 / 0 (PR objectstack-ai/objectstack#20436, no ruling); ADR: cloud ADR-0012 (quoted above via the plugin header); thread: none.

    推荐:A,附条件:云版的 features.storageUsage 必须由它自己挂载 StorageUsageEndpointPlugin 推导,⛔ 不写常量。一个开关同时管两个横幅(同一个接口、同一次请求)。键名由维护者定,storageUsage 与接口路径同名。
    只看①选 A;②③④ 是否翻转:否。
    回退:D(若维护者判定一行 404 不值两个 PR)。
    置信缺口:objectstack-ai/cloud 在本容器不可读,云版 objectos-runtime 是否便于从挂载推导该开关,本席看不见;dev 的"cloud 已子类化 RuntimeConfigPlugin"来自 objectui 注释,未在云仓实测。

    裁后执行

    • 选 A:本席在云仓相应车道立一张子卡(云版推导并发出开关),本卡转 pm:blocked + Blocked-by: 该子卡;云版落地后本卡回队,派发令改为"读 features.storageUsage,默认关闭,两个横幅共用"。顺手修掉 ConsoleShell.tsx 里那句过期的 useDiscovery() 注释。
    • 选 B / C:本席在 objectstack 立对应的 spec 或框架卡,本卡同样转 pm:blocked。
    • 选 D:本卡以 not_planned 关闭,关单评论引裁决。

    相关

    objectui#10439(横幅落地卡,已关)· PR objectui#10910 · dev 报告 5884730567(含 A–D 的完整代价分析)。


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #243 item 1 · letter A · maintainer 「11002 A」 2026-09-29T13:01Z

    Director seat (objectstack#12708, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-29, replying 「11002 A 20619 A 10866 A 20637 C 11088 同意」 to batches #243–#245 as presented. Presented on this card: the domain:ui seat 2 escalation 5884767712, read whole, with the director's own reading in chat (which closed the seat's confidence gap from the cloud repository: objectos-runtime subclasses the open RuntimeConfigPlugin with its own resolveFeatures, and the same objectos-stack.ts composition registers both that plugin and StorageUsageEndpointPlugin).

    Ruled: A. The cloud distribution declares the switch: objectos-runtime serves features.storageUsage, derived from mounting StorageUsageEndpointPlugin, never a literal; objectui declares RuntimeFeatures.storageUsage as optional, default off, read as === true, and gates both banners' hooks (useStorageUsageReading, useReadRateReading — one endpoint, one request) on that one boolean. ⛔ No key in the open framework (B): the open-core boundary text in runtime-config-plugin.ts forbids the framework enumerating a distribution's catalog. ⛔ No discovery slot (C): a public-protocol change for one cloud-only reading, and the banners mount where discovery cannot be read.

    Readings that decided it:

    • Long-term: the fact "this runtime serves the storage reading" lives in the only repository that serves it, through the seam the framework built for distribution-owned features; no spec key, no framework key.
    • Real use: every self-hosted administrator's console page load issues a 404 today (measured); cloud administrators read their quota from this banner.
    • AI-safety: default off means an older or unknown runtime issues no request; the condition on the derivation keeps the flag from becoming a second "declared, not delivered" key.
    • Startup scope: two small PRs, no new gate, no spec key.

    Execution parameters:

    • Cloud first: objectstack-ai/cloud#2481 (filed in the same stroke, pm:queue, p3) — the flag served from the composition that mounts the endpoint plugin, with a pin that it changes with the mount (a composition fact or a route-ledger read; the seat's choice); it is not plan policy, so it is not derived from plan.
    • Then this card: the dispatch is rewritten to "read features.storageUsage, default off, one boolean for both banners; delete the stale useDiscovery() comment in ConsoleShell.tsx". initRuntimeConfig is awaited before first render, so no loading window.
    • Key name storageUsage, the same as the endpoint path; the maintainer may rename it on either card.
    • Confidence gap recorded: none left on the cloud side (read at cloud origin/main 3efda04); the objectui side is as measured by the dev.

    State: needs-user-decision → pm:blocked, Blocked-by: objectstack-ai/cloud#2481; the Ruled line goes on the body in the same stroke. When cloud#2481 lands, this card returns to pm:queue.

  7. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 8
    Session: session_01FjqrwXPfSMkSfkKYDSRkN2
    Account: os-zhuang (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11002-storage-usage-flag
    Worktree: objectui-issue-11002
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the dispatch shape ruling 5890841811 (letter A) pre-wrote, read at objectui origin/main 278d244:

    • packages/app-shell/src/runtime-config.ts: RuntimeFeatures gains an optional storageUsage?: boolean, default off. It is mapped by initRuntimeConfig as body.features.storageUsage === true, like its customDomain / sso / scim siblings, and added to the defaults object as false.
    • packages/app-shell/src/console/ConsoleShell.tsx: one boolean gates both banners, StorageUsageBanner and ReadRateBanner. Their hooks share one endpoint, so it is one request when on and none when off. The stale useDiscovery() comment goes.
    • packages/app-shell/src/hooks/useStorageUsageReading.ts, useReadRateReading.ts, and layout/StorageUsageBanner.tsx / ReadRateBanner.tsx: only where the gate has to be threaded.
    • New pins: no request when the flag is absent or false, one request when it is true, and the mapping drops a non-true value. One @object-ui/app-shell minor changeset with the Clause-②: yes (widening) banner.

    The dev stops on a breach and explains it in the report.
    Container & model: M, mode:subagent, model: opus (default judgment tier; dispatch-gates --tier --repo objectstack-ai/objectui printed "no path-derived mandate" for runtime-config.ts, ConsoleShell.tsx and the two hooks. The contract review the yes below owes runs at CONTRACT_REVIEW_TIER, per that output's Clause ② line.)
    Clause-②: yes
    Why yes: RuntimeFeatures is exported from @object-ui/app-shell's index.ts, so a new member widens a published type. The changeset is at least minor, and a contract review is owed before landing.
    Thread-read: 5890841811
    Unblocked, re-derived 2026-10-04T09:49Z:

    • The ruling's order was cloud first. objectstack-ai/cloud#2481 closed completed 2026-10-01T04:12Z through PR objectstack-ai/cloud#2517.
    • Cloud main 2205b53 serves the flag exactly when the composition mounts StorageUsageEndpointPlugin (packages/objectos-runtime/src/cloud-runtime-config-plugin.ts:97). So once cloud takes this objectui release, its banners stay on. The 2026-10-01T05:01Z pm:blocked → pm:queue move carried no comment, and this one records the evidence.
    • The body's spent Blocked-by: line is rewritten to an Unblocked: line, read back identical.
      Serial constraints cleared:
    • This seat's in flight: objectui#11591's PR objectui#11606 (DraftChangesPanel.tsx, StudioDesignSurface.tsx, the locale packs) and objectui#11569's PR objectui#11604 (plugin-form). Both are disjoint by file.
    • Other lanes: objectui#10202 (hotlong) holds views/metadata-admin/**, and objectui#8347 holds packages/types/src/**. Both are disjoint.
    • Open PRs: no open PR touches runtime-config.ts, ConsoleShell.tsx, the storage or read-rate hooks, or their banners.
    • The earlier dispatch's branch: claude/issue-11002-storage-usage-advertised is a claim marker at base bf7ab35ce with zero commits. It is not reused.

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11002,
      "status": "done",
      "branch": "claude/issue-11002-storage-usage-flag",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11609",
      "session": "session_01FjqrwXPfSMkSfkKYDSRkN2 (mode:subagent run of the PM session; identity = the branch named in claim 5978669438)",
      "premise_still_valid": true,
      "summary": "Implemented ruling 5890841811 letter A. RuntimeFeatures gains optional storageUsage (default false; initRuntimeConfig maps it as `body.features.storageUsage === true` beside customDomain / sso / scim), a module-internal accessor isStorageUsageServed() (fails closed, reads `features?.`) is the one reading, and StorageUsageBanner and ReadRateBanner both pass `enabled: isAdmin && isStorageUsageServed()`. With the flag on, the two banners still share one request. The exported hooks keep `enabled = true`. The stale ConsoleShell comment calling useDiscovery() the AI chatbot gate now names the real SchemaRendererContext readers (useViewData, useElementDataSource); nothing in product source calls useDiscovery. Premise reproduced red at base 278d244: with the real ConsoleShell mounted as an admin, a runtime config that has no storageUsage key, or has it false, issued 1 GET /api/v1/usage/storage each (`expected [ '/api/v1/usage/storage' ] to have a length of +0 but got 1`); now 0. Measured correction to dispatch hypothesis 3: useStorageUsageReading / useReadRateReading are exported from the internal hooks/index.ts barrel, but the package entry's named re-export list excludes them, as it excludes both banners. Their default was a second Clause-② surface in name only; it was kept unchanged regardless. Draft PR #11609; PR assignee os-zhuang set and read back.",
      "tests": "Reproduction at base (fix absent, new pin present): `pnpm exec vitest run packages/app-shell/src/console/__tests__/ConsoleShell.storageUsageGate-11002.test.tsx` gave 'Tests 2 failed | 2 passed (4)' and 'VERDICT command-exit 1'. Green at HEAD 27044c0: the six touched suites (runtime-config.test.ts, ConsoleShell.storageUsageGate-11002, StorageUsageBanner, ReadRateBanner, useStorageUsageReading, useReadRateReading) gave 'Test Files 6 passed (6)', 'Tests 88 passed (88)'; `pnpm --filter @object-ui/app-shell type-check` exit 0 (`tsc --noEmit && tsc -p tsconfig.test.json`; `--listFilesOnly` lists both new test files in the test program); 'VERDICT command-exit 0'. Full package at e45751f, whose only difference from HEAD is one test-file `as any` cast removed: `vitest run --maxWorkers=3 packages/app-shell/` plus apps/console runtimeConfigBootDedup + faviconAfterNavigation, packages/types public-block-doc-examples-bag-11321, and scripts/__tests__ markdown-test-inputs + check-doc-links gave 'Test Files 1001 passed | 1 skipped (1002)', 'Tests 10076 passed | 9 skipped (10085)', 'VERDICT command-exit 0'. Ablations, each through objectstack scripts/ablation-replace.mjs from committed state (anchor x1 to x0, blob changed; restore proven blob == HEAD and empty `git diff HEAD`): (1) drop the mapping line: 3 red (literal-true pin, withdraw pin, shell ONE-request case); the falsy-input pins stay green by construction because the default is already false. (1b) `=== true` to `!!`: 2 red (`'true'`, `1`). (2a) drop the gate in StorageUsageBanner: 2 red (no-key and false shell cases, 1 request each). (2b) same in ReadRateBanner: same 2 red. (3) drop `isAdmin &&` in StorageUsageBanner: 1 red (non-admin case). No dist involvement: every mutated module is reached by relative src import. Reverse check of the published type: after the app-shell build, dist/runtime-config.d.ts has `storageUsage?: boolean` and dist/index.d.ts has no isStorageUsageServed. A scratch probe resolving @object-ui/app-shell to the built index.d.ts compiled `RuntimeFeatures['storageUsage'] = true` (exit 0) and refused `= 'yes'` with TS2322 (exit 2); the probe was deleted.",
      "gates": [
        {
          "command": "turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 --cache-dir=(main checkout .turbo/cache) --cache=local:r, under os-verify-lock",
          "exit": 0,
          "verdict": "Tasks: 28 successful, 28 total; Cached: 8 cached (at e45751f)"
        },
        {
          "command": "pnpm --filter @object-ui/app-shell build",
          "exit": 0,
          "verdict": "dist completeness: 1 package(s) complete (980 emitted files verified) (at e45751f)"
        },
        {
          "command": "pnpm --filter @object-ui/app-shell type-check",
          "exit": 0,
          "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no diagnostics; VERDICT command-exit 0 (at 27044c0)"
        },
        {
          "command": "pnpm exec vitest run on the six touched suites",
          "exit": 0,
          "verdict": "Test Files 6 passed (6); Tests 88 passed (88) (at 27044c0)"
        },
        {
          "command": "pnpm exec vitest run --maxWorkers=3 packages/app-shell/ plus 5 source-text readers outside the package",
          "exit": 0,
          "verdict": "Test Files 1001 passed | 1 skipped (1002); Tests 10076 passed | 9 skipped (at e45751f)"
        },
        {
          "command": "pnpm exec eslint --no-inline-config --format json on the 6 changed ts/tsx files",
          "exit": 0,
          "verdict": "0 errors; warnings equal base per file (ConsoleShell.tsx 8 vs 8 at 278d244, runtime-config.test.ts 6 vs 6; the other four 0)"
        },
        {
          "command": "pnpm check:control-bytes",
          "exit": 0,
          "verdict": "OK (scanned 7501 tracked text file(s); skipped 85 binary)"
        },
        {
          "command": "pnpm check:new-line-citations",
          "exit": 0,
          "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only"
        },
        {
          "command": "pnpm check:changeset-claims",
          "exit": 0,
          "verdict": "No pending changeset names a file this change touches."
        },
        {
          "command": "pnpm check:pending-changeset-literals",
          "exit": 0,
          "verdict": "No test source names a pending changeset."
        },
        {
          "command": "node scripts/check-changeset-presence.mjs",
          "exit": 0,
          "verdict": "6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11002-storage-usage-flag.md."
        },
        {
          "command": "pnpm changeset:check",
          "exit": 0,
          "verdict": "All workspace packages are in the changeset fixed group; No changeset declares a major bump."
        },
        {
          "command": "node scripts/check-changeset-overwrite.mjs",
          "exit": 0,
          "verdict": "No pre-existing changeset was modified or deleted."
        },
        {
          "command": "node scripts/check-vi-mock-specifiers.mjs / check-vi-mock-override-shape.mjs / check-vi-mock-inherit.mjs / check-test-path-roots.mjs",
          "exit": 0,
          "verdict": "OK each (at 27044c0)"
        },
        {
          "command": "pnpm check:esm-specifiers / check:self-import / check:phantom-deps / check:unreferenced-sources / check:spec-symbols; node scripts/check-type-check-coverage.mjs / check-lint-coverage.mjs",
          "exit": 0,
          "verdict": "OK each (at e45751f; no file they read changed after)"
        },
        {
          "command": "node scripts/check-doc-links.mjs / check-doc-fence-languages.mjs / check-doc-component-types.mjs / check-doc-expression-carriage.mjs; pnpm check:doc-example-readers / check:docs-route-closure",
          "exit": 0,
          "verdict": "OK each (at e45751f)"
        },
        {
          "command": "pnpm check:readme-exports",
          "exit": 1,
          "verdict": "NOT MEASURED in full: PREREQUISITE NOT MET, 7 packages outside this diff unbuilt; packages/app-shell/README.md draws 0 findings with app-shell dist built"
        },
        {
          "command": "pnpm check:doc-examples",
          "exit": 2,
          "verdict": "NOT MEASURED: THE GATE COULD NOT RUN (12 packages outside this diff unbuilt); this diff adds no fenced block to any document"
        }
      ],
      "files_changed": [
        ".changeset/11002-storage-usage-flag.md",
        "content/docs/guide/console.md",
        "packages/app-shell/README.md",
        "packages/app-shell/src/console/ConsoleShell.tsx",
        "packages/app-shell/src/console/__tests__/ConsoleShell.storageUsageGate-11002.test.tsx",
        "packages/app-shell/src/layout/ReadRateBanner.tsx",
        "packages/app-shell/src/layout/StorageUsageBanner.tsx",
        "packages/app-shell/src/runtime-config.test.ts",
        "packages/app-shell/src/runtime-config.ts"
      ],
      "deviations": [
        "Route (dispatch hypothesis 3, the accessor): the banners read a new module-internal isStorageUsageServed() rather than an inline `getRuntimeConfig().features...`. Measured, the siblings that carry a fail-direction doctrine each have their own accessor (isMarketplaceEnabled, isAiStudioEnabled; neither is on the package entry). This one fails CLOSED and says so in one place. It is not re-exported from index.ts, so the published widening stays the one RuntimeFeatures member.",
        "File surface beyond the claim's list: packages/app-shell/README.md (Storage capacity section) and content/docs/guide/console.md (the feature-flag list), because objectui AGENTS.md #2 requires docs. Prose only, no fenced block.",
        "Build gate spelled as `turbo run build --filter='@object-ui/app-shell^...'` reading the main checkout's turbo cache read-only (`--cache=local:r`), not `pnpm --filter ... build`. It covers the same closure and per-package scripts; 8 of 28 tasks replayed from cache.",
        "Test scope wider than the dispatch's narrowed list: the whole packages/app-shell/ suite plus the 5 source-text readers outside it (selected by import of the changed modules and by a grep for readFileSync / ?raw / the file names).",
        "Attribution: commits carry the model-free trailer pair from objectui AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The PR body ends with os-dev's session-URL footer, not the harness's line."
      ],
      "line_budget": "n/a (no skills/** touched)",
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "3 — each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet-write relay, executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft, #11609; read back 10178 bytes sent, 10178 stored, identical); (2) label-write --assign, POST /repos/objectstack-ai/objectui/issues/11609/assignees (os-zhuang; read back MATCHES); (3) this os-dev-report, POST /repos/objectstack-ai/objectui/issues/11002/comments via post-stamped.mjs. Not REST writes: git push of the branch (empty claim-marker push, then e45751f and 27044c0). Reads: GET issue 11002, its comments, comment 5890841811 / 5978669438 / 5884730567, PR 11609.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · StorageUsageBanner.test.tsx and ReadRateBanner.test.tsx mock their hook, so their admin cases now hand the mock `enabled: false` (the runtime-config default is off) while it answers with a reading anyway; they stay green and test the classifier, not the gate (the new shell pins test the gate) · noted, not filed (in PR Acceptance notes)",
        "carrier: 承接者:无 · isStorageUsageServed() is read at render like isAiStudioEnabled(); a later initRuntimeConfig re-run does not re-render the banners on its own. This is unchanged sibling behaviour; the console awaits init before first render · noted, not filed (in PR Acceptance notes)"
      ]
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review of PR objectui#11609 at head 27044c07e (dev report 5979101175): content ACCEPT. Clause-②: yes, so the at-tier contract review runs before ready

    domain:ui seat 1 · session_01FjqrwXPfSMkSfkKYDSRkN2 · 2026-10-04T10:41Z. Reviewed against GitHub, not the report.

    • Shape:
      • draft, base main;
      • the first line is Fixes #11002, with no other closing keyword, and Clause-②: yes is at line start;
      • 9 files, +316 / −10. check-governed-merges --pr objectstack-ai/objectui#11609: NOT governed;
      • the PR assignee is os-zhuang, read back;
      • the changeset is '@object-ui/app-shell': minor, and its widening paragraph opens **Clause-②: yes (widening)** —.
    • Ruling 5890841811 (letter A), carried out:
      • RuntimeFeatures gains storageUsage?: boolean. Its docblock calls it a cloud flag derived from the composition, not the plan.
      • The defaults object carries storageUsage: false.
      • initRuntimeConfig maps body.features.storageUsage === true beside customDomain / sso / scim.
      • Both banners pass enabled: isAdmin && isStorageUsageServed(): one boolean for both, and with the flag on they still share one request.
      • No discovery slot, no framework key, no probe-and-remember.
    • The published surface, checked:
      • packages/app-shell/src/index.ts is not in the diff. Its runtime-config.js re-exports are named lists, not export *.
      • So the new accessor isStorageUsageServed() stays unpublished, like isMarketplaceEnabled() / isAiStudioEnabled(). The one widening is the RuntimeFeatures member, which the changeset names.
      • The dev corrected the brief's hypothesis 3 by measurement: the two hooks are not on the package entry either. Their enabled = true default is unchanged anyway.
    • The stale comment: ConnectedShellInner's "useDiscovery() (used to gate the global AI chatbot)" now names the real SchemaRendererContext readers. The seat read both on the head: useViewData.ts:15 and useElementDataSource.ts:35 import the context, and :154 reads it.
    • Pins:
      • Red first: at base 278d244, the real ConsoleShell mounted as an admin issued one GET /api/v1/usage/storage with the key absent, and one with it false: 2 failed, 2 passed. At the head, all 88 tests in the six touched suites pass.
      • Ablations, each restored by blob hash:
        • dropping the mapping line turns 3 red;
        • === true → !! turns 2 red ('true', 1);
        • dropping either banner's gate turns 2 red;
        • dropping isAdmin && turns 1 red.
      • The wider union is green: the whole packages/app-shell/ suite plus 5 source-text readers outside it, 10,076 tests.
      • The type probe: against the built dist/index.d.ts, RuntimeFeatures['storageUsage'] = true compiles and = 'yes' fails with TS2322.
    • Docs: one sentence in packages/app-shell/README.md (the storage-capacity section) and one list item in content/docs/guide/console.md. Prose only, as objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2 asks. Accepted beyond the claim's list.
    • The cloud order holds: objectstack-ai/cloud#2481 is on cloud main, so a cloud runtime that mounts the endpoint keeps both banners. The changeset states the one cost: a cloud runtime older than PR objectstack-ai/cloud#2517 shows neither banner until it is upgraded.
    • Out-of-scope findings: two carrier notes, both ⇒ Acceptance notes.
      • The hook-mocking banner tests now pass enabled: false and still classify. The new shell pins are what test the gate.
      • isStorageUsageServed() is read at render, like isAiStudioEnabled().
    • For the contract reviewer: the changeset carries a second paragraph, **Behaviour change.**, after the banner paragraph. They are separate paragraphs, not stacked openers. The record judges whether that reads right.
    • ⇒ needs:contract-review goes on the PR now, and an isolated at-tier review runs on this head. CI is still running: 13 in progress at this read.

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and verified by content: closed completed by PR objectui#11609 · domain:ui seat 1 · session_01FjqrwXPfSMkSfkKYDSRkN2 · 2026-10-04T11:13Z.

    • The merge: PR objectui#11609 went through the merge queue to main as d2e8599 at 2026-10-04T11:13Z.

      • Seat review: 5979115422.
      • At-tier contract review: PASS 5979225895 at head 27044c0. Label removal provenance: 5979231364.
    • Content check on main d2e8599 against its first parent b92329c:

      main parent
      storageUsage?: boolean in RuntimeFeatures (runtime-config.ts) 1 0
      storageUsage: body.features.storageUsage === true in initRuntimeConfig 1 0
      enabled: isAdmin && isStorageUsageServed() in StorageUsageBanner.tsx 1 0
      enabled: isAdmin && isStorageUsageServed() in ReadRateBanner.tsx 1 0
      the stale "useDiscovery() (used to gate the global AI chatbot)" comment in ConsoleShell.tsx 0 1
      isStorageUsageServed named in packages/app-shell/src/index.ts 0 0

      The changeset 11002-storage-usage-flag.md (minor, with the **Clause-②: yes (widening)** — banner) and the pin ConsoleShell.storageUsageGate-11002.test.tsx are in the tree. The diff is exactly the reviewed 9 files, +316 / −10.

    • Ruling 5890841811 letter A is complete on both sides. objectstack-ai/cloud#2481 serves the flag, and this card reads it. A self-hosted admin's console issues no GET /api/v1/usage/storage. A cloud runtime that mounts the endpoint keeps both banners on one shared request.

    • No other card closed in that window.

    • pm:dispatched is removed in the same act. domain:ui, the type and the priority stay.


    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

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions