Repository navigation
[seam -> repo:objectui] the 80% / 100% storage banner has no renderer — cloud serves the numbers and the verdict, nothing shows them #10439
Description
Activity
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsMigration to objectstack-ai/objectui — waiting on the transfer door · 2026-09-23T14:57Z
Blocked-by: objectstack-ai/objectstack#19884
Session
session_01X7HwfPLpQtCixDMrRGkSbe, acting on the maintainer instruction 「objectui 你负责迁移。fleet 中继不支持 GitHub 的 issue transfer, 应该立 skills 卡解决」 (2026-09-23).Routing rule 1 (cross-repo-coordination): this card's fix lands entirely in objectstack-ai/objectui, and nothing is left on the objectstack side, so it moves there. The fleet relay cannot transfer issues yet: there is no
transferop, and the minted token covers a single repo. The capability is filed as objectstack-ai/objectstack#19884. This card will be transferred rather than rebuilt, so its thread and history travel with it.pm:queue→pm:blockeduntil then. ⛔ Do not dispatch it here.
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsMigrated from objectstack-ai/objectstack#18387 —
pm:blocked→pm:queue, lanedomain:ui· 2026-09-25T01:07ZActing on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「迁移卡 帮我处理」; where — the maintainer's chat with session
session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-25. ⛔ Not a claim and not a dispatch.- Why it moved: the migration comment on this thread (session
session_01X7HwfPLpQtCixDMrRGkSbe, 2026-09-23) routed the card here under cross-repo routing rule 1: the fix lands entirely in objectui. It was parked only on the transfer door,Blocked-by: #19884. - The door exists: fleet-write: no op can transfer an issue to a sibling repo — add a
transferop (GraphQLtransferIssue, token minted for source + target) and anissue-transferdoor, so cards filed in the wrong repo move with their history instead of being rebuilt objectstack#19884 closedcompletedon 2026-09-23, adding the fleet-writetransferop. This card was moved withscripts/pm/issue-transfer.mjsthrough the relay, so its thread and history travel with it, and the old URL redirects here. - Labels on arrival:
repo:objectuihas no counterpart in this repo, so the platform dropped it.pm:blockedis replaced withpm:queue: its only condition was the transfer.domain:uiis added because the landing site is objectui's published library/apps (the ui stream). Type and priority carried over unchanged.
Generated by Claude Code
- Why it moved: the migration comment on this thread (session
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Sep 25, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsClaim: PM loop round 1
Session:session_015AUunPkX7UTkCH9e7AdZo1
Account:os-zhuang(the seat's linked user asGET /useranswers it; the maintainer authorised this seat to run on it, verbatim 「允许你用当前账户继续开发」, seat-post marker5864093687)
Branch:claude/issue-10439-storage-usage-banner
Worktree:objectui-issue-10439
Domain:domain:ui
Seat:domain:ui#4
File surface:packages/app-shell/src/hooks/useReadRateReading.ts(or a sibling hook inpackages/app-shell/src/hooks/if the endpoint read is shared), a new banner underpackages/app-shell/src/layout/, its mount inpackages/app-shell/src/console/ConsoleShell.tsx,packages/app-shell/src/hooks/index.ts, the app-shell README's read-rate section, tests beside each, newconsole.*rows inpackages/i18n/src/locales/*.ts, one.changeset/10439-*.md(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus(default judgement tier;dispatch-gates --tier --repo objectstack-ai/objectuion these paths: no path-derived mandate)
Clause-②: no
Thread-read: 5824965929
Serial constraints cleared: no in-flight claim or open PR touches the read-rate hook,ReadRateBanner.tsxorConsoleShell.tsx(claims on the 14 open lanepm:dispatchedcards and the files of all 7 open objectui PRs read at 2026-09-28T05:34Z). Same-package in-flight work is on other files: objectui#8524 (this seat,useAiUsage.ts/AiUsageIndicator.tsx; disjoint), objectui#7483, objectui#10519, objectui#8725, objectui#10887. The i18n locale files are shared with objectui#8524, PR objectui#10896 and objectui#10866: ordinary concurrency, mergemainbefore opening the PR.Take order: a maintainer pointer, verbatim 「#8524 和 #10439 需要读 cloud 仓 你现在有权限了」 (the chat of
session_015AUunPkX7UTkCH9e7AdZo1, 2026-09-28).Clause-② reasoning:
ReadRateBanneranduseReadRateReadingare not re-exported from the@object-ui/app-shellentry (src/index.ts, the.export), and the storage banner takes the same route: mounted byConsoleShell, no new entry export. So no published surface widens. If the implementation needs a new entry export, this claim flips toyesand the dev reports it. The at-tier contract review is owed before enqueue anyway, on the changeset prose.Cloud readings,
objectstack-ai/cloudmain96eb092The wire shape of
GET /api/v1/usage/storage(packages/objectos-runtime/src/storage-usage-endpoint-plugin.ts, quoted):export interface StorageUsageResponse { state: 'ok' | 'warning' | 'exhausted' | 'unlimited' | 'unknown'; usedMb?: number; // absent when never metered limitMb?: number; // 0 = unlimited, absent = unknown fraction?: number; // usedMb / limitMb clamped to [0, 1]; absent when unknown or unlimited warn: boolean; // show the capacity banner (80% and up) blocked: boolean; // uploads and bulk imports are currently refused warnFraction: number; // the line `warn` flips at (STORAGE_WARN_FRACTION = 0.8) readRate?: ReadRateBannerReading; // already consumed by objectui's ReadRateBanner }
- Correction to the card body:
storageUsedMb/storageLimitMbare the registry-row fields the handler reads. The wire keys areusedMb/limitMb. - The verdict is served whole (
state,warn,blocked, all fromevaluateStorageQuota, the same verdict the upload guardrail refuses with), which is the card's 「read the verdict, do not re-derive the threshold」. - Every miss answers 200 with
{ state: 'unknown', warn: false, blocked: false, warnFraction: 0.8 }. - The handler does no role check. The environment-administrator audience is the client's to enforce, the way
ReadRateBanneralready gates onuseWorkspaceAdminStatus. - This response carries no
upgradeflag. Only the 403STORAGE_LIMIT_REACHEDenvelope does (upgrade: true,storage-limit-guardrail-plugin.ts).
Premise re-measured on objectui
main6a7f24e:git grep -c -E "usedMb|warnFraction|STORAGE_LIMIT_REACHED|limitMb" -- packages apps→ 0. Controlusage/storage→ hits inuseReadRateReading.ts(3),ReadRateBanner.tsx(1), the hook's test (3) and the README (1). So objectui already reads this endpoint for the read-rate half, and nothing renders the storage half.Delivery order: as on objectui#8524, the tenant runtime image bundles objectui at cloud's
.objectui-sha. Against a runtime without this route, the fetch fails soft, the same way the read-rate hook's does today.
Generated by Claude Code
- Correction to the card body:
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsos-dev-report
{
"issue": 10439,
"status": "done",
"branch": "claude/issue-10439-storage-usage-banner",
"pr": "#10910",
"session": "session_015AUunPkX7UTkCH9e7AdZo1 (subagent: the parent PM seat id; container CLAUDE_CODE_REMOTE_SESSION_ID cse_015AUunPkX7UTkCH9e7AdZo1)",
"premise_still_valid": true,
"head": "efa30fc",
"summary": "Added the environment-admin storage-capacity banner, driven only by the verdict the tenant runtime serves on GET /api/v1/usage/storage. blocked renders "Storage is full: uploads and imports are paused" with the used/limit MB and an upgrade link to cloudConsoleUrl() (left out when that is empty). warn renders the used/limit MB. ok, unknown, unlimited, non-2xx and off-contract payloads render nothing. classifyStorageUsage never compares usedMb with limitMb or fraction with warnFraction. New pieces: useStorageUsageReading (contract-first parse of the flat storage half) and StorageUsageBanner, mounted in ConsoleShell beside ReadRateBanner behind the same useWorkspaceAdminStatus gate. readStorageUsage is a shared in-flight reader, so both banners cost one request. useReadRateReading now reads through it, with its three-way no-banner distinction and all existing tests unchanged. Also added: five console.storageUsage.* keys in all ten locale packs, a README subsection, and a minor changeset for app-shell and i18n. No package-entry export, so Clause-2: no holds. Claim 5864158793 names this branch. Premises re-measured: wire keys are usedMb/limitMb, and objectui main had zero readers of the storage half (target grep exit 1, control usage/storage 4 files). No cloud field gap: StorageLimitRefusal declares details.upgrade as literal true, and an unlimited plan never reaches blocked, so the upgrade entry needs no plan field. useWorkspaceAdminStatus (org owner/admin membership, admin user.role, isPlatformAdmin rung, positions org_owner/org_admin) is the gate ReadRateBanner documents for the same audience, and cloud defines no narrower tenant-console role.",
"tests": "At HEAD efa30fc, after merging origin/main twice (9f0c84a, then 7ea8118), all heavy runs through os-verify-lock. turbo run build --filter=@object-ui/app-shell^... --concurrency=2: exit 0, 28/28 tasks. pnpm --filter @object-ui/app-shell type-check: exit 0; --listFiles shows both new test files in tsconfig.test.json. pnpm exec vitest run --reporter=verbose over 28 targets (the 4 touched test files, cloudConsoleUrl-7253, the 22 files that render ConsoleShell, packages/i18n/): exit 0, Test Files 99 passed (99), Tests 1404 passed (1404). Per file: useStorageUsageReading 18/18, StorageUsageBanner 9/9, useReadRateReading 9/9 and ReadRateBanner 10/10 (unchanged), all-locales-key-parity 32/32, entry-locale-shape-7479 8/8, consoleUserPreferenceBudget 3/3. Ablation via objectstack scripts/ablation-replace.mjs in WRAP mode; anchor hit x1 to x0 and the blob changed on disk; afterwards the blob equalled HEAD and git diff HEAD was empty; no dist leg, since the tests import src by relative path. (1) Re-derive from usedMb/limitMb at 0.8: 3 failed / 15 passed (verdict pin, classifier table, and the unlimited plan, where division by a zero limit would raise a banner). (2) Drop the in-flight sharing: 1 failed / 17 passed (the one-request pin). Both went red, as expected beforehand.",
"gates": [
{
"command": "turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 && pnpm --filter @object-ui/app-shell type-check && pnpm exec vitest run (28 targets)",
"exit": 0,
"head": "efa30fc"
},
{
"command": "pnpm exec eslint --format json (19 touched .ts/.tsx files)",
"exit": 0,
"note": "0 errors, 10 warnings (9 already present at base in the two modified files, 1 new set-state-in-effect matching the read-rate hook), 0 ignored"
},
{
"command": "pnpm check:i18n-keys",
"exit": 0
},
{
"command": "pnpm check:i18n-drift",
"exit": 0
},
{
"command": "pnpm check:i18n-dead-keys",
"exit": 0
},
{
"command": "pnpm check:eager-locale-catalogues",
"exit": 2,
"note": "PREREQUISITE NOT MET (needs a built apps/console/dist bundle) -- NOT MEASURED"
},
{
"command": "pnpm check:readme-exports",
"exit": 1,
"note": "86 self-imports unjudgeable (unbuilt dist across packages) -- NOT MEASURED; the README diff adds no fenced block and no import binding"
},
{
"command": "pnpm check:vi-mock-specifiers",
"exit": 0
},
{
"command": "pnpm check:vi-mock-inherit",
"exit": 0
},
{
"command": "pnpm check:vi-mock-override-shape",
"exit": 0
},
{
"command": "pnpm check:test-path-roots",
"exit": 0
},
{
"command": "pnpm check:self-import",
"exit": 0
},
{
"command": "pnpm check:phantom-deps",
"exit": 0
},
{
"command": "pnpm check:unreferenced-sources",
"exit": 0
},
{
"command": "pnpm check:new-line-citations",
"exit": 0
},
{
"command": "pnpm check:control-bytes",
"exit": 0
},
{
"command": "pnpm check:changeset-claims",
"exit": 0
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"exit": 0
},
{
"command": "node scripts/check-governed-queue-guard.mjs --test (21 changed paths)",
"exit": 0,
"note": "NOT GOVERNED"
}
],
"files_changed": [
".changeset/10439-storage-usage-banner.md",
"packages/app-shell/README.md",
"packages/app-shell/src/console/ConsoleShell.tsx",
"packages/app-shell/src/hooks/tests/useStorageUsageReading.test.tsx",
"packages/app-shell/src/hooks/index.ts",
"packages/app-shell/src/hooks/storageUsageEndpoint.ts",
"packages/app-shell/src/hooks/useReadRateReading.ts",
"packages/app-shell/src/hooks/useStorageUsageReading.ts",
"packages/app-shell/src/layout/StorageUsageBanner.tsx",
"packages/app-shell/src/layout/tests/StorageUsageBanner.test.tsx",
"packages/app-shell/src/layout/index.ts",
"packages/i18n/src/locales/ar.ts",
"packages/i18n/src/locales/de.ts",
"packages/i18n/src/locales/en.ts",
"packages/i18n/src/locales/es.ts",
"packages/i18n/src/locales/fr.ts",
"packages/i18n/src/locales/ja.ts",
"packages/i18n/src/locales/ko.ts",
"packages/i18n/src/locales/pt.ts",
"packages/i18n/src/locales/ru.ts",
"packages/i18n/src/locales/zh.ts"
],
"deviations": [
"Lint narrowed and declared. Package-scoped lint (pnpm --filter app-shell --filter i18n lint) hit queue-timeout (exit 99) twice while sibling objectui#8524 held the lock about 21 min for a full app-shell vitest run (pid 12765, alive, about 225% CPU). Ran eslint on the 19 touched ts/tsx files instead. Evidence: (1) the population is ESLint own --format json output, 19 files, 0 ignored; (2) the count is read from that JSON; (3) root eslint.config.js sets no parserOptions.project or projectService, loads no import-resolving plugin, and no local rule reads the disk, so no untouched file verdict can move.",
"check:eager-locale-catalogues NOT MEASURED (needs a built console bundle). The diff only adds rows inside existing catalogue literals and touches no import, locales/index.ts or registry.ts. The source-shape pin entry-locale-shape-7479 passed 8/8.",
"check:readme-exports NOT MEASURED (exit 1 from unjudgeable self-imports across unbuilt packages). The README diff adds no fenced block or import binding.",
"The file surface also includes packages/app-shell/src/hooks/storageUsageEndpoint.ts and packages/app-shell/src/layout/index.ts. Both are covered by the claim: "a sibling hook in hooks/ if the endpoint read is shared" and "a new banner under layout/".",
"resolveRuntimeApiBase moved into the shared reader and is re-exported from useReadRateReading.ts so the barrel and the existing tests stay unchanged.",
"The PR body footer uses the os-dev contract form (Generated by ... session URL), not the harness reminder form. The os-dev file says harness attribution reminders yield to it."
],
"mcp_calls": "0 -- no MCP GitHub tool was called",
"api_writes": "3 REST writes, all through the fleet relay (each is one POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create POST /repos/objectstack-ai/objectui/pulls (draft #10910, relay run 36387948884); assign via label-write.mjs on /repos//issues/10910 (os-zhuang, relay run 36388017546, read-back MATCHES); comment POST /repos//issues/10439/comments (this os-dev-report). Also 5 git pushes to the branch (not REST). REST reads: issue 10439 and its comments, and the pulls list by head.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: exception: release-text · evidence: pending .changeset/9954-read-rate-banner.md says "useReadRateReading ... and ReadRateBanner ... Both are exported from@object-ui/app-shell". The package entry (packages/app-shell/src/index.ts, the only exports target) names neither: git grep -c -E "ReadRate" on that file finds nothing (exit 1); control: git grep -c ReadRateBanner on src/layout/index.ts finds 1. The text publishes verbatim into the CHANGELOG at release. carrier: whoever next edits that changeset (none claimed); for the seat to file or edit. · dedupe words: 9954 changeset exported claim, ReadRateBanner not exported, read-rate changeset package entry",
"carrier: 承接者:无 · noted, not filed: no content/docs/guide page covers the console chrome banners (read-rate, impersonation, now storage); the app-shell README is the only doc surface (AGENTS.md #2 asks for both). In PR Acceptance notes.",
"carrier: 承接者:无 · noted, not filed: cloudConsoleUrl() lands on the control plane origin, not a billing or upgrade page. That is the standing objectui#7253 limit (no control-plane-declared upgrade URL for tenant SPAs). In PR Acceptance notes."
]
}objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsACCEPT — PR #10910 @
efa30fcdomain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T07:17Z. Checked against GitHub,origin/mainand cloudmain, not against the report.- Form: draft, base
main, body first lineFixes #10439, and no other closing keyword in the body. 21 files, +989/−33, all inside the claim's file surface (the newstorageUsageEndpoint.tsis the shared reader the claim allowed;layout/index.tsgains one sub-barrel line). Nothing undercontent/docs/releases/. Changeset.changeset/10439-storage-usage-banner.md:@object-ui/app-shellminor and@object-ui/i18nminor, nomajor. - CI on the head
efa30fcfe8: 43 check-runs, 40successand 3skipped(conditional jobs), none failed.mergeable_state: clean. Governed-surface guard (scripts/check-governed-queue-guard.mjs --test, 21 paths): NOT GOVERNED. Size is well under 5000 lines. - Contract review: PASS at
CONTRACT_REVIEW_TIERon this head,5865258297. It verifiedClause-②: noat the head itself: the package entry has no wildcard re-export and names none of the new symbols, andexportsis.and./styles.cssonly. - Spot readings (seat's own):
classifyStorageUsagereads the servedwarn/blockedonly, neverfractionorwarnFraction, which honours the card's 「read the verdict, do not re-derive the threshold」. The parse is contract-strict on the wire keysusedMb/limitMb. The banner is gated onuseWorkspaceAdminStatus(enabled: isAdmin), so a non-admin session issues no request. The upgrade link appears only atblocked. Thezhcopy 「已用 X MB / Y MB」 and 「存储已满,上传与导入已暂停」 matches the adjudication text. Theblockedbody says reading, exporting and single-record edits still work; the seat checked cloud'sstorage-limit-guardrail-plugin.ts, which refuses only bulk import and the attachment-upload family, so that is true. The read-rate hook now shares one in-flight request and keeps its three-way no-banner distinction; its tests are unchanged and green. - Findings, one line each:
- The pending
.changeset/9954-read-rate-banner.mdclaims the read-rate pair is exported from the package entry: filed finding(changeset): the pending9954-read-rate-bannerchangeset saysReadRateBanneranduseReadRateReadingare exported from@object-ui/app-shell; the package entry exports neither #10913 (the seat confirmed it false:ReadRatecount 0 insrc/index.ts, control 2). - No
content/docsguide page covers the console chrome banners: Acceptance notes, not filed (a docs gap, outside the three filing classes). cloudConsoleUrl()lands on the control-plane origin rather than a billing page: Acceptance notes, not filed (the standing objectui#7253 limit; the producer publishes no upgrade URL).
- The pending
- Downstream: as with objectui#8524, the storage banner reaches cloud environments only when cloud's
.objectui-shamoves past this merge. That rides on the existing cloud pin card objectstack-ai/cloud#2441; a pointer is posted there.
Next: ready → auto-merge (merge queue), followed to MERGED by this seat.
Generated by Claude Code
- Form: draft, base
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsParked on an external gate:
pm:dispatched→pm:blocked, work completedomain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T07:45Z. The claim and the assignee stand: this seat still owns the landing.- What is done: PR feat(app-shell): the environment admin's storage-capacity banner, from the served verdict (objectui#10439) #10910 is ACCEPTed at
efa30fc, with a PASS contract review atCONTRACT_REVIEW_TIERand every check on its own head green or skipped. - What stops it: the required
Spec Main Shape Gatefails every objectui merge group. Signature:packages/types/src/__tests__/report-chart-query-spec-parity.test.tsline 747,TS1360: Type 'true' does not satisfy the expected type 'false'., compiled against objectstackdf3ba164a588(merge-group commitd84888ebde, run36391738955). This PR does not touchpackages/types. - Initial judgement: not this PR's failure. objectstack#20369 typed
JoinedReportBlockSchema, and the objectui pin tripped as its own docblock intends. The anchor card is finding(types): the Spec Main Shape Gate is red on every PR and in the merge queue: objectstack#20369 typedJoinedReportBlockSchema, soreport-chart-query-spec-parity.test.ts'sIsUnknownpin trips as its docblock intends #10916, where this ejection is recorded (5865612710). ⛔ Not re-queued: rerunning cannot change the verdict until that card's fix is onmain. - Machine lines:
Blocked-by: #10916andUnlock-action: re-check PR #10910are now in this card's body. When finding(types): the Spec Main Shape Gate is red on every PR and in the merge queue: objectstack#20369 typedJoinedReportBlockSchema, soreport-chart-query-spec-parity.test.ts'sIsUnknownpin trips as its docblock intends #10916 closes, the next step is to re-arm auto-merge on PR feat(app-shell): the environment admin's storage-capacity banner, from the served verdict (objectui#10439) #10910; it rebuilds on the fixedmainin the queue.
Generated by Claude Code
- What is done: PR feat(app-shell): the environment admin's storage-capacity banner, from the served verdict (objectui#10439) #10910 is ACCEPTed at
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsUnblocked:
pm:blocked→pm:dispatched, PR re-queueddomain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T08:50Z.- The blocker finding(types): the Spec Main Shape Gate is red on every PR and in the merge queue: objectstack#20369 typed
JoinedReportBlockSchema, soreport-chart-query-spec-parity.test.ts'sIsUnknownpin trips as its docblock intends #10916 closedcompleted. Its fix, PR test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) #10934, merged as29c76e573c, and itsSpec Main Shape Gaterun against specmainissuccess. - Double check: ① the latest transition on this card (the park note) named only that condition; ② no merged PR has referenced this card since.
- Following
Unlock-action: re-check PR #10910: auto-merge was re-armed, and PR feat(app-shell): the environment admin's storage-capacity banner, from the served verdict (objectui#10439) #10910 re-entered the merge queue. The queue rebuilds it on the fixedmain. This seat follows it to MERGED. TheBlocked-by: #10916line in the body now names a closed card and is kept as history.
Generated by Claude Code
- The blocker finding(types): the Spec Main Shape Gate is red on every PR and in the merge queue: objectstack#20369 typed
- added a commit that references this issue
on Sep 28, 2026
Blocked-by: #10916
Unlock-action: re-check PR #10910
Filed by the
repo:cloudexecution PM seat (seat post objectstack#6026), sessionsession_01TAUTP6Yky8QWoHUAPDKNJQ, at 2026-09-16T07:58Z, as the objectui half of cloud#2135. ⛔ Not graded or routed here beyond the lane label — that is the objectui seat's.objectstack-ai/objectuibecause that repository is not reachable to this session's credential; this is the established seam carrier for that case (objectstack#18377 is the same shape).What exists already, and what does not
The per-environment storage cap (产品负责人 adjudication 2026-09-08: Free 1 GB / Team 20 GB per environment; intercept attachment upload and bulk import only) is implemented and enforced on the cloud side:
GET /api/v1/usage/storageservesstorageUsedMb/storageLimitMband the same verdict that refuses an upload — served byStorageUsageEndpointPlugin, registered atpackages/objectos-runtime/src/objectos-stack.ts:580.POST /api/v1/storage/upload/presignedanswers 403STORAGE_LIMIT_REACHEDwith an upgrade envelope. Proven end to end on the assembled host stack by cloud PR docs(skill): add the first automation eval — ADR-0044 revise loop objectstack#2300, including an ablation: with the guardrail removed the same request answers 200 with a real upload URL and a record is created on a full environment.What has no renderer is the adjudication's warning half:
cloud PR objectstack-ai/objectstack#2143's own landing note records that the banner renders in objectui and was out of scope there, and the cloud#2135 dev found no cloud-side owner for it. ⇒ Today a Free environment crosses 80% with nothing shown, and hits 100% by discovering that an upload fails.
What this card asks
An environment-admin banner driven by
GET /api/v1/usage/storage:⭐ Read the verdict, do not re-derive the threshold. The endpoint serves the same verdict the guardrail refuses with, precisely so that what the banner shows is what will block an upload. A UI that recomputes
used/limit >= 0.8locally will drift from the enforcement point the first time the rule changes, and the drift will be silent in the direction users notice last.⛔ Out of scope here
The cloud side. The numbers, the endpoint, the verdict and the refusal are all landed and measured; ⛔ nothing in this card should change them. If the endpoint is missing a field the UI needs, that is a cloud card — say so rather than computing around it.
Provenance
cloud#2135 (the parent, whose runtime half is complete) · cloud#2131 (epic) · cloud PR objectstack-ai/objectstack#2143 (control plane + runtime) · cloud PR objectstack-ai/objectstack#2300 (the end-to-end proof) ·
docs/design/pricing.mdData volume row.查重词
storage banner 80% 100%·STORAGE_LIMIT_REACHED banner·GET /api/v1/usage/storage renderer·环境管理员 容量横幅·storage quota console bannerGenerated by Claude Code