Skip to content

rest/metadata-protocol: GET /meta/app's nav-servability gate zod-diagnoses every served object to read their enable flags, and every served metadata list recomputes _diagnostics on every read: 34% of a hosted tenant console open's server CPU, measured #25783

Closed

Description

@objectstack-fleet

Ruled: 6408692677 · letter A · 2026-10-26T05:08Z

Filing gate: ① a measured defect (server CPU on the console open path), found by cloud's attribution round cloud#2914 round 10 (report: https://github.com/objectstack-ai/cloud/issues/2914#issuecomment-6396581531). Platform-first: cloud's own frames are 0–3% of every call below, so the fix is here.

Session: session_01FSg7JRUiTPo8GrgT38tAWP · seat repo:cloud#1

Business reason. Opening a hosted tenant console currently costs about 501 ms of main-thread server CPU on one event loop (measured on a local hosted-shaped rig). On staging the per-call CPU reads about 4–8× that, which puts 2–4 s of the measured ~5.0 s open on the server. Two framework behaviours cost 169 ms (34%) of it, and neither is needed by anything on the open path.

Reader who acts. A framework dev for packages/rest and packages/metadata-protocol.

Measured (cloud main 36b07fdf, framework pin 888d6865, objectui 6f629a73, HotCRM 5.0.0 with sample data, zh-CN)

Each call was measured isolated and serially: 40 requests after 5 warm-ups. CPU is main-thread CPU from /proc. Profiles are V8 CPU profiles at 250 µs sampling.

call CPU / wall computeMetadataDiagnostics share what is served
tenant GET /api/v1/meta/app 79.8 / 77.6 ms 67.6 ms (74%) 3 apps, whose own diagnostics cost 0.04–0.22 ms
control plane GET /api/v1/meta/app 67.5 / 64.3 ms 58.5 ms (75%), over 99 objects —
tenant GET /api/v1/meta/object (whole registry) 98.8 / 101.3 ms 71.1 ms (63%), over 102 objects 875 KB raw
tenant GET /api/v1/meta/view 20.5 / 20.8 ms 7.0 ms (31%), over 110 views —

1. meta/app's nav-servability gate reads the SERVED object list

  • GET /meta/app runs resolveNavServability (packages/rest/src/meta-item-read-gate.ts:1295-1303), which calls loadObjectItems (:441-445).
  • That goes through metaListSource (rest-server.ts:3643-3652) to protocol.getMetaItems, which is the served face.
  • So every object is decorated and zod-diagnosed only to learn its enable flag. A breakpoint count in one request showed 3 app items and 102 object items diagnosed.

This is the same class as #24031, which fixed it for the /data API-exposure gate. This is the /meta/app door.

Fix sketch: read a name → enable-flag projection, or the execution face, rather than the served list.

Expected: tenant meta/app CPU drops from 80 to about 12 ms, and control plane from 68 to about 10 ms.

2. Every served metadata list recomputes _diagnostics on every read

  • The diagnostics are a full zod safeParse per item (packages/metadata-protocol/src/protocol.ts:10175, metadata-diagnostics.ts:154-171).
  • Their reader is Studio. Nothing on the console open path reads them.

Fix sketch: memoize per stored item version, or compute them only when the caller asks (a Studio query flag).

Expected: tenant meta/object drops by 71 ms and meta/view by 7 ms.

Acceptance

  1. With the cloud#2914 rig, or any hosted-shaped rig with about 100 objects: GET /meta/app costs at most 15 ms main-thread CPU, and GET /meta/object with no Studio flag spends at most 5% of its CPU in computeMetadataDiagnostics. Report the before and after numbers.
  2. Studio's diagnostics surfaces still show the same verdicts. Re-pin the tests that read _diagnostics.
  3. rest: every /data request's API-exposure gate reads EVERY object's metadata (with a full schema diagnostic) to check ONE object's enable flags — 62-72% of a data request's time, measured #24031's gate test family covers the /meta/app door too.

Dedupe

维护者速读

托管租户打开控制台时,服务端 CPU 有 34% 花在框架"每次读元数据都重新跑一遍 zod 诊断"上:/meta/app 为了判断导航项能否显示,把全部 102 个对象都诊断了一遍(只为读一个开关,和已修的 #24031 同一类问题);/meta/object 每次也全量诊断。诊断结果只有 Studio 用。修复后租户打开约省 170 ms 服务端 CPU(staging 上按 4–8 倍估计约 0.7–1.3 秒)。

Activity

  1. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    priority:p1High: required for production / M2
    on Oct 25, 2026
  2. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p1 · domain:server · area:api · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-25T11:57Z

    ⛔ Not a claim, ⛔ not a dispatch.

    • p1: the platform half of objectstack-ai/cloud#2914 (p1, a 13 s console open). 169 ms of 501 ms server CPU is on the open path, and staging multiplies it 4–8×.
    • packages/rest + packages/metadata-protocol. Measure before and after on the same rig in the PR.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · card thread · 2026-10-26T03:40Z
    Session: session_019d4JoyFgaZCRubmNWh91uZ (the card thread, mode:thread; the seat writes this claim for it: seat session session_01TFnDdepdwyDJeM2kJ4GtMy)
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-mvtze8 (platform-assigned to the card thread; a thread can push only its assigned branch, a measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-25783
    Domain: domain:server
    Seat: domain:server#2 (seat post #20966; 由 os-elon-musk 账户的项目负责)
    File surface (read on origin/main cb0a2fcf; stop on a breach and explain it in the report):


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment (file surface) · domain:server#2 · seat session session_01TFnDdepdwyDJeM2kJ4GtMy · 2026-10-26T03:43Z · amends claim 6407697525, same session, same branch claude/project-thread-mvtze8.

    File surface adds: packages/runtime/src/domains/meta.ts, metaItemReadGateSources only: the object type reads getMetaItemsForExecution (one member added to the MetaDomainProtocol Pick), plus one test in packages/runtime. Reason: it is the same nav-servability gate behind the dispatch() front door (the createHonoApp catch-all and the embedded form, per route-ledger.ts), the same defect with the same fix, and in this lane. The changeset adds @objectstack/runtime (patch). Every other line of the claim stands.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25783,
      "status": "done",
      "branch": "claude/project-thread-mvtze8",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25941",
      "session": "session_019d4JoyFgaZCRubmNWh91uZ — card thread, mode:thread (Claude-Session trailer on both commits)",
      "premise_still_valid": true,
      "summary": "Head bb46886e, base cb0a2fcf, merges clean with origin/main 2a728ba2. (1) GET /meta/app's nav-servability gate reads `object` on the protocol's execution face (getMetaItemsForExecution) in RestServer.metaListSource and in the dispatcher's metaItemReadGateSources.listMetaItems (amendment 6407727649), falling back to getMetaItems; (2) computeMetadataDiagnostics memoizes verdicts per schema object (WeakMap) and per document content (SHA-256 of a faithful encoding; unkeyable documents parse every time), 4096-entry LRU, copies per caller. Measured on the showcase rig (108 objects, shared 4-core box, main-thread CPU/request): /meta/app 73.25 -> 16.75 ms (diag share 59.7% -> 1.3%), /meta/object 85.75 -> 37 ms (50.7% -> 15.6%), /meta/view 13.25 -> 11.25 ms; served bytes of app/object/view/flow/dashboard/page SHA-256-identical before vs after, cold and warm. Acceptance is short on both numbers (16.75 vs <=15 ms; 15.6% vs <=5%), so the PR says `Part of #25783`; the residue is named in the PR's Acceptance notes and in open_questions. protocol.ts 0 lines; nothing under packages/spec or ui/.",
      "tests": "New: packages/rest/src/meta-app-nav-servability-execution-read.test.ts (8 rows: app list + by name, unscoped + env-scoped, ZERO object diagnostics; FALLBACK without the execution face diagnoses every object with the same prune; CONTROL /meta/object still decorated) — on cb0a2fcf's rest-server.ts 4 rows fail ('expected [ listed_widget, unlisted_widget ] to deeply equal []'). packages/runtime/src/domains/meta-app-nav-object-execution-face.test.ts (3 rows via HttpDispatcher.dispatch) — on cb0a2fcf's meta.ts the 2 execution-face rows fail. packages/metadata-protocol/src/metadata-diagnostics.memo.test.ts (15 rows) — mutation to a naive JSON.stringify key fails all 6 collision rows (undefined vs missing, hole vs null, NaN vs null, both orders). Re-pinned: rest-exposure-gate-execution-read.test.ts proxy per test (beforeEach), 5 rows had recorded 0 parses under the memo. Full suites under os-verify-lock, VERDICT command-exit 0: @objectstack/metadata-protocol Test Files 264 passed | 3 skipped, Tests 21505 passed | 19 skipped; @objectstack/rest Test Files 328 passed, Tests 6730 passed | 344 skipped; @objectstack/runtime Test Files 448 passed, Tests 8011 passed | 19 skipped. typecheck (three packages, rest/runtime test layers included) exit 0. pnpm lint exit 0.",
      "gates": "dispatch-gates --commands: 71 commands; all 71 exit 0 on the final record; `dispatch-gates --ran`: 71 derived, 71 run, 0 NOT-MEASURED, 0 UNRUN. Red on the first pass, then green: check-rest-log-spy-declared (exit 1: new rest test spied console.warn without OS_REST_LOG; spies removed, they asserted nothing), check:engine-double-contract (exit 1: new rest test carried an unreached findOne double; removed, ledger unchanged), check-plugin-teardown-shape --self-test (exit 3: shallow clone lacked fixture 621a4876; fetched), check:dual-build-cjs-loads (exit 3: 7 packages had no dist; built them). CI on #25941: not concluded at report time; the seat reads the PR's check-runs.",
      "files_changed": [
        ".changeset/25783-console-open-metadata-cost.md (+14/-0)",
        "packages/metadata-protocol/src/metadata-diagnostics.memo.test.ts (+216/-0)",
        "packages/metadata-protocol/src/metadata-diagnostics.ts (+146/-0)",
        "packages/rest/src/meta-app-nav-servability-execution-read.test.ts (+177/-0)",
        "packages/rest/src/meta-item-read-gate.ts (+4/-0)",
        "packages/rest/src/rest-exposure-gate-execution-read.test.ts (+5/-3)",
        "packages/rest/src/rest-server.ts (+17/-0)",
        "packages/runtime/src/domains/meta-app-nav-object-execution-face.test.ts (+107/-0)",
        "packages/runtime/src/domains/meta.ts (+14/-3)"
      ],
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "4 — mcp__github__issue_read ×4 (get #25783, get_comments #25783, get #6367, get #20966); no GitHub MCP write tool. Plus mcp__claude-code-remote__subscribe_pr_activity on #25941 (not a GitHub write).",
      "api_writes": "3 relay strokes as objectstack-fleet[bot] — (1) post-stamped comment on #6367 (hot-file knock, POST /repos/objectstack-ai/objectstack/issues/6367/comments); (2) fleet-write pr_create #25941 (POST /repos/objectstack-ai/objectstack/pulls draft + POST /repos/objectstack-ai/objectstack/issues/25941/assignees os-elon-musk; read-back 8952/8952 bytes identical); (3) this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/25783/comments). git push ×2 (not REST).",
      "deviations": [
        "Rig servers started with `pnpm --filter @objectstack/example-showcase dev -- --fresh -p 38425` instead of `pnpm dev`: check-dev-prereqs refused `pnpm dev` (45 unbuilt packages). Same command and port for before and after.",
        "File surface beyond the claim: none beyond amendment 6407727649 (runtime meta.ts + one runtime test); @objectstack/runtime patch is in the changeset."
      ],
      "open_questions": [
        {
          "question": "The card asks /meta/object (no Studio flag) at <=5% CPU in computeMetadataDiagnostics; content keying lands at 15.6% (warm reads parse nothing; the rest is the key: one JSON.stringify + SHA-256 over 694 KB, ~6 ms isolated). Is the card's other route still wanted?",
          "options": [
            "A: land #25941 as Part of #25783 and close the card on these numbers (the <=5% / <=15 ms targets relaxed by the maintainer).",
            "B: land #25941, keep #25783 open for a separate card: Studio opts in to _diagnostics with a /meta query switch and plain reads skip them. Changes /meta parameters and the served body for non-opting callers plus ~a dozen ui/ _diagnostics readers: a contract change that needs contract review.",
            "C: land #25941, keep #25783 open for the /meta/app residue only (~4.4 ms artifact resolution lookupArtifactItem/foldExtenders, ~7.8 ms fixed request overhead), outside this claim's surface."
          ],
          "recommendation": "A, because both numbers fell 2-4x with byte-identical served bodies and no contract surface; B buys the last ~9 ms on /meta/object at the price of a contract change and a ui/ sweep, which is the maintainer's call, not the seat's."
        }
      ],
      "out_of_scope_findings": [
        "carrier: #25783 (PR #25941 Acceptance notes) · /meta/app residue ~4.4 ms in artifact resolution (lookupArtifactItem / foldExtenders) and ~7.8 ms fixed per-request overhead, profiled on the showcase rig · noted, not filed (a cost reading, not a class a/b/c defect)"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: direct ruling (no batch) · item: os-dev-report 6408550085 open_questions[0] · letter A · maintainer 「25783 A」 · recorded by domain:server#2 (seat post #20966, seat session session_01TFnDdepdwyDJeM2kJ4GtMy) · 2026-10-26T05:07Z

    The maintainer's words were written 2026-10-26T05:06Z in the project timeline and relayed to this seat verbatim.

    Letter A: the maintainer relaxes the acceptance targets to the numbers PR #25941 measured, and the card closes when that PR lands.

    • The acceptance targets now read as measured on the showcase (108 objects, main-thread CPU per request): /meta/app 73.25 → 16.75 ms, /meta/object 85.75 → 37 ms, diagnostics share 15.6%. The original ≤15 ms and ≤5% targets are relaxed. They were never met, and nothing is owed against them.
    • Workload: none beyond PR perf(metadata-protocol,rest,runtime): /meta/app judges its navigation on the execution face; diagnostics verdicts are memoized by content and schema #25941. Its Part of #25783 becomes Closes #25783, and the card closes when the PR merges. Landing still follows the landing rule in this seat's thread.
    • Not dispatched by this ruling: B (Studio _diagnostics opt-in switch, a contract change) and C (the /meta/app residue: artifact resolution ~4.4 ms, fixed overhead ~7.8 ms). If either is wanted later, it is a new card through the filing gate.

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment (Clause-② line) · domain:server#2 · seat session session_01TFnDdepdwyDJeM2kJ4GtMy · 2026-10-26T05:20Z · amends claim 6407697525 (and amendment 6407727649), same session, same branch claude/project-thread-mvtze8.

    Clause-②: yes (widening) replaces Clause-②: no, for @objectstack/rest only. PR #25941 adds the optional member getMetaItemsForExecution? to the exported RestProtocol type, the list twin of getMetaItemForExecution?, which #24666 (6464df3462) declared yes (widening) and graded minor. So @objectstack/rest takes its own minor changeset carrying the declaration and the before/after export reading. @objectstack/metadata-protocol and @objectstack/runtime stay patch with Clause-②: no, unless the dev's dist/index.d.ts reading shows their published surface moved. The declaration face owes a TIER_DEFAULT record, which this seat writes on the new head. Every other line of the claim stands.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment (level) · domain:server#2 · seat session session_01TFnDdepdwyDJeM2kJ4GtMy · 2026-10-26T05:34Z · corrects amendment 6408853730.

    @objectstack/rest stays patch, ⛔ not minor. Since ruling A on #25105 (2026-10-21) the level follows the Metadata-protocol kind alone (AGENTS.md Post-Task step 3: none takes patch; a non-protocol widening ships patch). check-changeset-no-major refuses a minor under none, as the dev measured. The #24666 precedent predates that rule. The Clause-②: yes (widening) declaration for RestProtocol stands, in its own rest patch changeset on head 22f886e1. Every other line stands.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25941 comment 6409381502 · domain:server#2 · 2026-10-26T06:07Z · declaration-face record PASS 6409368646 · landing through the relay (standing landing word, seat thread 2026-10-26 05:53Z)


    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

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions