Skip to content

Authenticated data requests issue ~20 DB queries in ~17 sequential legs — per-request auth/session/localization/metadata resolution has no cross-request caching, costing ~1.5s/request on remote Postgres #10757

Description

@os-zhuang

⚠️ 2026-09-01:重述停放理由。 本卡此前挂 pm:blocked,卡体里没有任何 Blocked-by: 行 —— 停放理由只活在一条评论里(Blocked-by: #11633),而解锁扫描只读 body。本次把理由写进 body,并把标签改为 pm:on-hold,因为那个 blocker 的活已经干完了,剩下的是一次测量,不是一个依赖。

Ruled: 6250819728 · 维护者「10757 在本地测试就可以。」 · 2026-10-18 — 原 Restart-when(远程 Postgres 重测)改为本地重测,判据换算见该评论(recorded by the triage seat)
Restart-touch: packages/core/src/security/resolve-authz-context.ts, packages/rest/src/rest-server.ts, packages/plugins/plugin-auth/src/auth-manager.ts

⛔ 原 Blocked-by: #11633 已不成立。 直接读 #11633 的当前状态(⛔ 不取本卡对它的转述):设计 2026-08-24 交付,维护者 2026-08-25 ACCEPT 并裁完四个分叉,卡本身已从 needs-user-decision 转为 tracking 协调父卡;sub_issues_summary = 5 / 5 完成 —— #11966(leg C)、#11967(leg D)、#11968(substrate)、#11969(ADR 转换)、#11971(leg B,授权缓存,即「设计真正要做的那条腿」,2026-08-30T14:44Z 关闭)全部关闭。

⇒ 本卡 2026-08-28 那次巡检问对了问题(「clearing that blocker does not hand this card work — it hands #11633 work」),只是当时最后一个子卡还没关。现在关完了,所以本卡欠的不是等待,是一次读数。

⚠️ NOT MEASURED,且本仓测不了:cloud#1521 的 ≤200ms 门今天是否已经通过、以及放置问题(cloud#1518)。仓内没有任何命令能到达部署数据。能测的人:repo:cloud 席,或握有该部署的运维。⛔ 在读数回来之前,不得据仓内任何数字宣称本卡已解决。


Found while root-causing objectstack-ai/cloud#1518 (control plane pays ~1.5s of server time on EVERY authenticated DB-touching request on prod, ~6s on staging, independent of query shape and response size). Traced at cloud's pin 0c24898c. The mechanism is entirely in framework packages, so per cloud's contract-first rule it is filed here rather than worked around in the consumer.

The measurement

Staging (OS_SERVER_TIMING=true) for GET /api/v1/data/sys_user_preference?$top=1:

wall=6674ms server=[parse=0.05ms, db=6240.57ms, serialize=0.06ms, handler=5812.91ms, total=5813.63ms]

handler ≈ total and db ≈ handler — the request is essentially all DB time. db > total is explained by the db mark being a per-query SUM (PerfTiming.count, packages/drivers/driver-sql/src/sql-driver.ts installQueryTiming) over queries that partly overlap (see the parallel settings reads below).

Where the queries come from — one authenticated GET /data/:object?$top=1 issues ~20 queries in ~17 sequential legs

Every leg is a Neon round trip. At the ~50–90ms/query this deployment measures, ~17 sequential legs are the ~1.5s.

A. better-auth getSession — no caching anywhere (5 queries):

  1. sys_session + 2. sys_user (better-auth core; cookieCache is not configured — zero grep hits in plugin-auth);
  2. sys_user_permission_set, 4. sys_permission_set (FULL list, limit 50), 5. sys_member — the customSession plugin re-derives platform-admin/org roles on EVERY getSession call (packages/plugins/plugin-auth/src/auth-manager.ts ~2898–2960).

B. resolveAuthzContext / resolveUserAuthzGrants (8 queries, sequential) (packages/core/src/security/resolve-authz-context.ts ~330–560):
6. sys_member {user_id}; 7. sys_user_position; 8. sys_member {organization_id} (fellow-org, limit 1000); 9. sys_user_permission_set (duplicate of A3); 10. sys_position {name $in}; 11. sys_position_permission_set; 12. sys_permission_set {id $in}; 13. sys_user {id} for ai_seat (duplicate of A2).

C. Localization (3 queries, parallel): resolveLocalizationContext → 3× settings.get('localization', …) → loadRows is uncached by design (#10221 caches only FAILED reads), so 3 sys_setting reads per request.

packages/rest/src/rest-server.ts ~800 documents this whole block as "~16 sequential queries" and memoizes it per request (execCtxMemo) — but nothing caches it ACROSS requests, so every request pays it once.

D. Route handler (5–6 more):

  • enforceApiAccess → loadObjectItems → getMetaItems('object') ALWAYS queries sys_metadata (1–2: the empty result triggers the alt-type retry) — packages/metadata-protocol/src/protocol.ts ~5700;
  • plugin-security's engine middleware runs resolvePermissionSetsForContext → dbLoader (sys_permission_set {name $in}) with no cache, and it runs TWICE — once for the find, once for the COUNT (security-plugin.ts ~1351);
  • the data SELECT itself;
  • findData runs engine.count() whenever a limit is present (protocol.ts ~8858) — a second data query even for $top=1.

E. Zero-hint observation: the SqlDriver's tenancy auto-scoping itself issues no extra queries — but its INPUTS (accessible_org_ids, org_user_ids) are what B pays for, including the limit-1000 fellow-org read.

Consequences on a real deployment (cloud prod/staging)

  • ~1.5s fixed server time per authenticated data request at ZERO concurrency (prod; ~6s staging where per-query RTT is ~300ms).
  • Requests being ~100% DB-bound makes the pg pool cap the request-concurrency cap: cloud measured a hard knee exactly past 10 concurrent (= its pool max), median doubling at 20. The Console home fires 27 API calls in one cold load.

Fix directions (each independently valuable, roughly by leverage)

⚠️ This list predates #11633's five legs landing. Re-derive which entries survive before acting on any of them.

  1. Session caching: enable better-auth cookieCache (signed cookie, short TTL) or an in-process TTL cache keyed by session token — removes A entirely on warm requests.
  2. customSession is redundant work on the data path: B re-derives everything A3–A5 derive, more completely. Consider gating the customSession enrichment to the endpoints that need it, or a short per-user TTL cache.
  3. Cross-request TTL cache for resolveUserAuthzGrants keyed (userId, tenantId) with write-invalidation or a short TTL. ⚠️ This is authz caching leg B: grants cache — coarse invalidation, default TTL=0 (off), expiry-boundary expiry, bypass list #11971's face and it has LANDED — do not rebuild it; measure it.
  4. Batch B's reads: steps 6–13 are ~8 sequential round trips that could be 2–3 batched/joined queries; the duplicates (A2/13, A3/9) are free wins.
  5. Cache successful localization reads with write-invalidation (the 新环境日志被 sys_setting 'no such table' ERROR 刷屏:本地化读取先于建表,真错误被噪音淹没 #10221 cache deliberately holds failures only; the dogfood analytics-timezone test pins read-after-write — invalidation-on-write satisfies it).
  6. Cache getMetaItems' sys_metadata read (short TTL / registry-epoch key), and skip the alt-type retry when the first read returned an empty-but-healthy result set.
  7. Reuse the find's permission-set resolution for its count (same request, same context — the middleware resolves twice today).
  8. $count=false fast path: honor the existing $count parameter to skip the COUNT for callers that don't need total (objectui's cold-load probes mostly don't).

Per-query evidence is directly obtainable on any deployment of current main by an admin: X-OS-Debug-Timing: json returns Server-Timing (db;dur=…;desc="N queries") plus X-OS-Debug-Timing-Detail (slowest parametrized statements) — rest-server.ts:1937 opens the disclosure gate for admin principals.

Generated by Claude Code


Generated by Claude Code

Activity

  1. removed their assignment
    on Aug 23, 2026
  2. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    清 assignee — #11196 维护者裁决 2026-08-23「A:本席清 agent 残留」,skills 席 session 757f0dc0-5dae-51d7-9053-c15c57ebfe3b。证据:交付 PR #10824 已合并,08-22 的 H8 配对写入回标 pm:queue 时明言未动 assignee——派发周期已收束,assignee 为死残留,与 pm:queue 构成双向撒谎态。标签未动;后续认领席按协议重新 assign + 声明即可。


    Generated by Claude Code

  3. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling recorded — tranche 2 gets its design card

    Provenance: maintainer, 2026-08-24, live PM chat, verbatim: 「10757 接受你的建议」 — accepting the design-first recommendation for the remaining tranche.

    Ruled: cross-request caching (session / grants / localization / metadata) proceeds via #11633 — a design-first card with the ruled direction: write-invalidation primary, short configurable TTL as the fallback bound, low-sensitivity caches (localization, metadata) may ship ahead of grants/session. Implementation dispatches only against the approved design; deviations return as forks.

    State: needs-user-decision → pm:blocked in the same stroke.
    Blocked-by: #11633

    Reminder carried from the tranche history: tranches 1 + 1b landed but do not pass cloud#1521's ≤200ms gate — tranche 2 and placement (cloud#1518) are where the remainder lives; do not read this card's earlier green deliveries as the problem solved.


    Generated by Claude Code

  4. os-litant commented on Aug 28, 2026

    @os-litant
    Contributor

    Sweep check — block reads live, no state change. ⚠️ But the card's shape is worth one triage look.

    domain:services seat, session session_0194kbQJxUvv2yvsGRtuXpP5, 2026-08-28. Checked as the last item in a manual sweep of this lane's blocked cards. ⛔ Nothing changed here.

    What is settled, from this card's own history

    leg state
    tranche 1 (COUNT reuse, $count=false) ✅ PR #10824 merged — 24 → 22 queries
    tranche 1b (localization triple-read → one getMany; legs 6–13 → 4 waves) ✅ landed via #10825 (PR #11197) and #10826 (PRs #11200 / #11208)
    tranche 2 (cross-request caching) split out to #11633, whose title names it "tranche 2 of #10757"
    placement objectstack-ai/cloud#1518 — different repo

    ⇒ Every in-repo leg has either landed or been split to its own card.

    ⚠️ The question for triage — ⛔ not mine to decide

    pm:blocked says "dispatchable once the blocker clears." If tranche 2 lives on #11633, then clearing that blocker does not hand this card work — it hands #11633 work, and this card would still have nothing of its own. That is the shape triage itself predicted on 08-22:

    this reads as a campaign parent (eight independently-valuable directions across three lanes) — if it is worked as such, per-direction sub-cards with Blocked-by:/Part of links beat one mega-PR

    ⇒ If that reading is right, the honest state is a tracking/epic label, not pm:blocked. ⛔ I am not re-labelling: this is someone else's decomposition, the distinction turns on whether #11633 is the whole of tranche 2 or only its design half, and I have not established that. Recording it so it is a decision rather than a drift.

    ⚠️ Whatever the label, the substantive fence stands and is the reason this card must not be casually re-dispatched — the PM split note, verbatim:

    "Caching session and authorization state is a security change wearing a performance costume. A stale grants cache is a privilege-escalation window: a permission revoked at T is still honoured until the TTL expires."

    ⇒ Tranche 2 needs the invalidation design settled first and is maintainer floor. ⛔ No seat self-selects it.

    ⭐ One thing on this card worth not losing

    Its last dispatch was a premise-first stop: the dev cut a worktree, found both tranche-1b pieces already merged 17 hours earlier under #10825/#10826, made zero edits, and reported it — including naming the same stale-dispatch pattern that had fired on the sibling card #10826 that same day, and the claim-time fix that was adopted from it (search PRs/issues by number in title+body, not just closed_by_pull_requests). It also declared the dynamic verification it could not complete rather than folding it into a pass. That is the discipline this board runs on, and it is why the card's history is trustworthy enough to read as I just did.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    Contributor

    维护者裁定记录:本地测试即可 · pm:on-hold → pm:queue · p2 · domain:services · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-18T14:02Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    裁定原话(维护者在分诊席位会话里,2026-10-18):「10757 在本地测试就可以。」

    重启条件随之改写:

    • 原条件: 在一台远程 Postgres 部署上重测原始症状。
    • 新条件: 在本地(本地 Postgres,或 objectstack dev 的默认库)按卡内「Per-query evidence」的方法测 GET /api/v1/data/:object?$top=1,读数贴回本卡。这一次读数就是本卡现在的工作,所以本卡转入队列。

    判据换算(分诊的换算,供开发参考,⛔ 不是裁定):

    边界:

    • 本地读数不能用来宣称 cloud#1521 的门已经通过。
    • 部署上的放置问题(cloud#1518)仍归 cloud。

    分级: p2。若本地读数仍接近 17 个串行轮次,重评为 p1。


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 10 · 2026-10-18T21:25Z
    Session: session_01Nk17Z26X5DB59iQztDBADH (the dispatching seat; the dev runs mode:subagent in this session's container and shares its session ID, so its identity is the branch)
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-10757-local-query-reading (a scratch branch for local instrumentation only; ⛔ never pushed with a code change)
    Worktree: objectstack-issue-10757
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface: none. This is a reading, not a change: the maintainer's ruling 「10757 在本地测试就可以。」 (triage 6250819728) makes one local measurement this card's work. ⛔ No PR and no code edit. The one write is the reading, posted on this card.
    Exclusions:


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 10757,
      "status": "done",
      "branch": "claude/issue-10757-local-query-reading",
      "pr": null,
      "session": "session_01Nk17Z26X5DB59iQztDBADH — the dispatching seat's session (mode:subagent shares it); identity = the branch named in claim 6255340747",
      "premise_still_valid": true,
      "database": "Local PostgreSQL 16.15: a private, throwaway cluster started from the container's postgres-16 binaries on 127.0.0.1:47329 (one fresh database per boot), NOT the dev default SQLite. Read two ways: direct (sub-millisecond round trips, run R1) and behind a userspace TCP proxy adding 10 ms each way, RTT +20 ms (runs L1, L2), so that concurrent queries overlap unambiguously and sequential ones never do. Both ways gave identical query counts and identical serial rounds for every case.",
      "verdict_against_criterion": "does not meet",
      "reading": {
        "tree": "objectstack-ai/objectstack at d8e11b58e (origin/main at dispatch), stock showcase, `pnpm dev -- --fresh -p 47331 --database postgres://…` (Tenancy: single, 52 plugins), request GET /api/v1/data/OBJECT?$top=1",
        "stock_cold_first_request_after_boot": "admin, sys_user_preference: 23 queries / 12 serial rounds (identical in L1 and R1)",
        "stock_warm_admin": "12 queries / 8 rounds when the localization cache holds; 14 / 9 when it was retired (+2 queries, +1 round). Same for sys_user_preference, showcase_task and sys_api_key.",
        "stock_warm_member": "auditor.demo@example.com (a seeded persona holding one position): 11 queries / 7 rounds; 13 / 8 when the localization cache was retired. Same for all three objects.",
        "org_scoped_vs_no_tenancy": "no difference: showcase_task and sys_user_preference (org-scoped) and sys_api_key (tenancy.enabled false) measure 12/8 admin and 11/7 member alike; tenancy scoping adds no query (leg E holds).",
        "landed_opt_in_count_false": "$count=false: warm admin 11 / 7, warm member 10 / 6 (the COUNT leaves; 0 COUNT queries in 12 requests).",
        "landed_opt_in_grants_cache_on": "OS_AUTHZ_GRANTS_CACHE_TTL_MS=10000 (default 0 = off): warm 5 / 5 for admin and member (7 / 6 when the localization cache was retired); cold 16 / 9.",
        "landed_opt_ins_both": "grants cache on + $count=false: warm 4 queries / 4 rounds (sys_session, sys_user, sys_metadata, the data SELECT), 6 / 5 when localization was retired.",
        "wall_note": "with the +20 ms RTT proxy, warm admin DB span is about 190 ms for 8 rounds and about 97 ms for 4 rounds (span is roughly rounds x 24 ms). A synthetic RTT, so this illustrates the round count; it is not deployment evidence and makes no claim about cloud#1521.",
        "full_tables": "in the comment body below the JSON (os-dev-report comment on #10757)"
      },
      "fix_directions": {
        "1": "still paid — leg A is 2 queries in 2 sequential rounds on every request: sys_session (better-auth findSession), then sys_user. The cache form is closed by the accepted #11633 ruling (comment 5404681591, Fork 3 to A: better-auth's session read stays uncached, #4785 stands). Observed, not evaluated: the second round is better-auth's FALLBACK JOIN (@better-auth/core adapter factory.mjs:191-194, :340), taken because plugin-auth never sets advanced.database.joins (auth-manager.ts:3023-3033 forwards only cookie/CSRF keys) — a non-caching shape.",
        "2": "removed by #15136 (the customSession callback now asks resolveUserAuthzGrants, auth-manager.ts:4419/4510) plus commit c7a3234a7 (#22441, the request-scoped grants memo): the card's A3-A5 reads (sys_user_permission_set limit 50, the full sys_permission_set page, sys_member limit 5) no longer occur, and on warm requests resolveAuthzContext's own grants call is served by the memo. Residual, cold only: the first request after boot resolves grants twice (7 extra queries, 3 extra rounds) because the memo does not commit during the request in which its write observer was first attached (request-grants-memo.ts:318).",
        "3": "still paid on the stock default — the grants cache LANDED (#11971) with default TTL 0 (Fork 4), so a stock boot pays leg B: 7 queries / 3 rounds (admin), 6 / 2 (member). Switched on (OS_AUTHZ_GRANTS_CACHE_TTL_MS=10000) a warm hit removes all of it: warm 12/8 to 5/5 (admin), 11/7 to 5/5 (member); the boot states the posture loudly (no authz.invalidated bus) as #11968 requires.",
        "4": "still paid in part (partly removed by #10825 and the catalog cutover) — wave 1 is one concurrent round of 5 reads (sys_user, sys_member by user, sys_member by organization limit 1000, sys_user_permission_set, sys_user_position); the card's legs 10-12 (sys_position, sys_position_permission_set, sys_permission_set) and the A3/9 duplicate are gone. Still paid: the A2/13 duplicate (sys_user read by better-auth's join AND by getUserRow, rows 2 and 3) and two reads awaited one after the other after the wave: sys_organization by slug (readDefaultOrganizationId, resolve-authz-context.ts:1500, only for a principal holding an org-owned admin_full_access grant under `single`) then sys_metadata_activation (readDisabledCatalogNames, :1552) — 2 rounds for the admin, 1 for the member.",
        "5": "removed by #11966 on a cache hit; still paid whenever the cache was retired — 2 queries (sys_platform_setting + sys_setting, SettingsService.getMany) in 1 round. The success cache is retired by ANY engine write (its backstop is the global write epoch, a documented choice in the leg-C docblock), and on an idle stock boot two outbox pollers write about every 30 s each (claim UPDATEs on sys_http_delivery and sys_notification_delivery). Measured with Postgres write logging in R1: all 6 warm-key misses (of 22 warm-key requests) followed exactly one poller UPDATE in the preceding 2.5 s gap; no hit did.",
        "6": "still paid — 1 query / 1 round every request: enforceApiAccess reads the object BY NAME (loadObjectItem, meta-item-read-gate.ts:488, to getMetaItem, to readItemActiveRows, protocol.ts:11132), a path #11967's overlay cache does not cover (that cache sits in getMetaItems, protocol.ts:10794). The alt-type retry is removed (folded into one `type IN (object, objects)` read; landing commit not traced).",
        "7": "removed by #10824 (the per-execution-context permission-set memo) and since superseded: the data path reads no sys_permission_set row at all — dbLoaderForContext returns the in-process catalog loader (security-plugin.ts:1373, :1993). 0 queries measured.",
        "8": "removed by #10824 for callers that send $count=false (measured: -1 query, -1 round); the default request (no $count) still pays the COUNT, by design."
      },
      "summary": "Measurement only: no fix, no PR, nothing pushed. On d8e11b58e with local PostgreSQL, an authenticated GET /api/v1/data/OBJECT?$top=1 on a warm stock boot issues 12 queries in 8 serial rounds as the admin and 11 in 7 as a member (plus 2 queries / 1 round whenever the localization cache has been retired by any engine write); the first request after boot is 23 / 12. That does not meet the ≤3-round criterion; it is well below the original ~17 rounds but not near ≤3. Even with every landed opt-in switched on (grants cache TTL above 0 and $count=false) the floor is 4 rounds: sys_session, sys_user (better-auth's fallback join), the uncached by-name sys_metadata read in enforceApiAccess, and the data SELECT. Of the card's 8 directions, 2 and 7 are removed; 8 is removed for callers who opt in; 3 and 5 landed (3 off by default, 5 retired by background writes); 1 is closed by ruling; 4 and 6 are partly done with the residual named above. Re-scoping and any re-grade are triage's.",
      "tests": "No code change, so no test suite applies. Readings: 5 request sets on 3 boots, each boot a fresh `pnpm dev -- --fresh` on a fresh database (boot L1: proxied stock; boot R1: direct stock, then R1c with $count=false; boot L2: proxied with the grants cache on, then L2c with $count=false), 96 timed requests in all, every one status 200. Instrument: X-OS-Debug-Timing: json on every request; for ADMIN requests the response's Server-Timing `db;desc=\"N queries\"` and the X-OS-Debug-Timing-Detail `count` equal the logged count in every case (e.g. 23=23, 14=14, 12=12, 5=5); for MEMBER requests both headers are withheld (the admin-only disclosure gate, as designed), so the full per-query list came from a TEMPORARY log in SqlDriver.installQueryTiming (packages/drivers/driver-sql/src/sql-driver.ts) recording request id, start (knex `query` event, after connection acquisition), end, parametrized SQL and async stack — only for queries inside a timed request's PerfTiming scope, the population the db count covers. Proof it reached the boot: the marker counted in dist/index.mjs and dist/index.js after a direct tsup build (never via turbo, so nothing instrumented entered the shared turbo cache). Removal: `git checkout HEAD -- …/sql-driver.ts`, then `git diff HEAD` 0 bytes, `git status --porcelain` 0 lines, worktree blob 4349b8b5bdd4 = HEAD blob 4349b8b5bdd4, 0 markers; 0 commits on the branch; the worktree and its instrumented dist are deleted. Rounds: per request, queries sorted by start; a query that starts before the latest end of the current round joins it, otherwise it opens a new round (a round = a maximal chain of time-overlapping execution windows). Legs: from each query's captured async caller stack past the ObjectQL middleware frames. Leg-C retirement: Postgres log_statement=mod on the private cluster (R1).",
      "gates": "None owed: there is no diff (dispatch: no gate list). Local commands: the showcase closure build `OS_SKIP_DTS=1 turbo run build --filter=@objectstack/example-showcase^... --concurrency=2` via scripts/pm/os-verify-lock.sh: `Tasks: 63 successful, 63 total`, VERDICT command-exit 0 (held 103 s); the full workspace build (check:dev-prereqs refused `pnpm dev` with 8 of 70 packages unbuilt, so built rather than bypassed) `turbo run build --filter=!@objectstack/docs --concurrency=2`: `Tasks: 76 successful, 76 total`, 63 cached, VERDICT command-exit 0; two instrumented `pnpm --filter @objectstack/driver-sql build` runs: VERDICT command-exit 0; `node scripts/check-dev-prereqs.mjs`: exit 0 ('70 package build artifacts present'). No PR, so no check-runs to cite.",
      "mcp_calls": "0",
      "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/10757/comments (this os-dev-report), through scripts/pm/post-stamped.mjs and the fleet-write relay. No git push: the scratch branch was never pushed, not even empty (no later push needed the route, and the dispatch budget is this one comment).",
      "deviations": [
        "os-dev rule 1's empty-branch push probe was skipped on purpose: this card has no push and its write budget is one comment.",
        "The Postgres cluster lived under /var/lib/postgresql (the postgres user cannot traverse the 0700 scratchpad parents and Postgres refuses to run as root); it was stopped and its directory and logs deleted.",
        "A latency proxy (+10 ms each way, own port only, no tc/netem on the shared loopback) was added to the lead's method so overlap reflects the code's concurrency rather than local JS-vs-DB speed; the direct run R1 confirms the same counts and rounds.",
        "The first exploratory boot was warmed by untimed probe requests, so it was used only to validate the tooling; every formal reading is from a fresh database and fresh boot.",
        "L2 runs a NON-default configuration (OS_AUTHZ_GRANTS_CACHE_TTL_MS=10000) to measure direction 3's landed cache, labelled as such everywhere."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #10757 (triage's re-scope from this reading) · noted, not filed — leg C's success cache is retired by ANY engine write by design, and on an idle stock boot the webhook-outbox and notification-delivery pollers' claim UPDATEs (about every 30 s each, sys_http_delivery held 0 rows) retire it; measured 6 of 22 warm-key requests paid leg C in R1. An efficiency property of a documented design, not a defect or contract violation.",
        "carrier: #10757 · noted, not filed — plugin-auth's refreshOrgMfaCacheIfStale / refreshMustChangeCacheIfStale (auth-manager.ts:8553-8554) fire two COUNTs every 60 s inside the request's async scope without awaiting them, so Server-Timing's `N queries` counts them (+2) though they are off the critical path; in every observation they overlapped another round.",
        "carrier: #10757 · noted, not filed — the residual rounds after every landed opt-in are better-auth's fallback join (sys_user after sys_session; advanced.database.joins unset) and the uncached by-name sys_metadata read in enforceApiAccess; both are named under fix_directions 1 and 6 for triage's re-scope."
      ],
      "not_measured": [
        "cloud#1521's ≤200 ms gate and the deployment's placement (cloud#1518): out of reach locally, and per the ruling's bounds this reading claims neither.",
        "the dev default SQLite database (Postgres was available and chosen, per the dispatch).",
        "concurrent load, multi-node, an API-key principal, write-heavy traffic, and requests spaced more than 60 s apart."
      ]
    }

    Reading — local PostgreSQL 16.15, objectstack d8e11b58e, stock showcase

    Ruling 6250819728, verbatim: 「10757 在本地测试就可以。」 Bounds kept: this local reading makes no claim that the cloud#1521 gate passes, and placement (cloud#1518) stays with cloud.

    Method. GET /api/v1/data/OBJECT?$top=1 with X-OS-Debug-Timing: json, cookie session from /api/v1/auth/sign-in/email (signed in before the timed requests, untimed). Each boot is a fresh pnpm dev -- --fresh -p 47331 --database postgres://… on a fresh database (R1c and L2c reuse the R1 and L2 boots); requests 2.5 s apart; per principal, three objects, four requests each (the first is that principal's first for the object; the very first timed request of the boot is the cold one). The full per-query list comes from a temporary per-query log in SqlDriver.installQueryTiming (since removed; see tests in the JSON); for admin requests its count equals Server-Timing's N queries in every case. Serial rounds: queries sorted by start; a query that starts before the latest end of the current round joins it, otherwise it opens a new round.

    Runs: L1 stock, behind a +20 ms RTT proxy · R1 stock, direct · R1c direct, $count=false · L2 proxied, OS_AUTHZ_GRANTS_CACHE_TTL_MS=10000 (NON-default) · L2c L2 + $count=false.

    Summary

    case cold (first request after boot) warm, localization cached warm, localization retired
    admin, stock 23 / 12 12 / 8 14 / 9
    member, stock n/a (member's first request: 13 / 8, leg C cold) 11 / 7 13 / 8
    admin, $count=false n/a 11 / 7 n/a
    member, $count=false n/a 10 / 6 n/a
    admin or member, grants cache ON 16 / 9 (admin) 5 / 5 7 / 6
    admin or member, grants cache ON + $count=false n/a 4 / 4 6 / 5

    Values are queries / serial rounds. Org-scoped (sys_user_preference, showcase_task) and no-tenancy (sys_api_key) objects measure the same. The criterion (about ≤3 serial rounds) is not met in any configuration, the best being 4.

    Per-query table, warm admin (stock)

    L1 · admin showcase_task #2 (warm): 12 queries, 8 serial rounds

    # round leg start ms dur ms table statement (parametrized, quotes dropped) issued by
    1 1 A 0.0 21.7 sys_session select * from sys_session where token = $1 limit $2 better-auth findSession (readSession, inside resolveAuthzContext)
    2 2 A 22.9 21.5 sys_user select * from sys_user where id = $1 limit $2 better-auth handleFallbackJoin (session to user)
    3 3 B 47.6 21.9 sys_user select * from sys_user where id = $1 order by id asc limit $2 resolveUserAuthzGrants getUserRow, wave 1, called by the customSession callback
    4 3 B 47.9 22.0 sys_member select * from sys_member where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    5 3 B 48.2 22.4 sys_member select * from sys_member where organization_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    6 3 B 48.5 23.2 sys_user_permission_set select * from sys_user_permission_set where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    7 3 B 48.6 23.8 sys_user_position select * from sys_user_position where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    8 4 B 73.8 21.4 sys_organization select * from sys_organization where slug = $1 order by id asc limit $2 resolveUserAuthzGrants readDefaultOrganizationId, called by the customSession callback
    9 5 B 96.0 21.3 sys_metadata_activation select * from sys_metadata_activation where metadata_type in ($1, $2) and name in ($3, $4, $5, $6, $7, $8, $9, … resolveUserAuthzGrants readDisabledCatalogNames, called by the customSession callback
    10 6 D 119.0 21.2 sys_metadata select * from sys_metadata where type in ($1, $2) and name = $3 and state = $4 enforceApiAccess to loadObjectItem to getMetaItem to readItemActiveRows
    11 7 D 147.5 20.4 showcase_task select * from showcase_task order by id asc limit $1 findData SELECT
    12 8 D 168.7 21.2 showcase_task select count(*) as count from showcase_task findData COUNT

    Every stock warm admin request in L1 and R1 is exactly this list (the object's own SELECT/COUNT differ), plus the leg C pair when retired and the background pair every 60 s. The 5 reads of round 3 start within 1.2 ms of each other and overlap. R1 (direct) gives the same 12 / 8 at a 22 to 37 ms DB span.

    Per-query table, warm member (stock)

    L1 · member sys_api_key #2 (warm): 11 queries, 7 serial rounds

    # round leg start ms dur ms table statement (parametrized, quotes dropped) issued by
    1 1 A 0.0 21.9 sys_session select * from sys_session where token = $1 limit $2 better-auth findSession (readSession, inside resolveAuthzContext)
    2 2 A 23.1 21.7 sys_user select * from sys_user where id = $1 limit $2 better-auth handleFallbackJoin (session to user)
    3 3 B 48.0 21.6 sys_user select * from sys_user where id = $1 order by id asc limit $2 resolveUserAuthzGrants getUserRow, wave 1, called by the customSession callback
    4 3 B 48.3 21.6 sys_member select * from sys_member where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    5 3 B 48.6 21.9 sys_member select * from sys_member where organization_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    6 3 B 48.8 22.5 sys_user_permission_set select * from sys_user_permission_set where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    7 3 B 49.0 22.1 sys_user_position select * from sys_user_position where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    8 4 B 72.0 21.4 sys_metadata_activation select * from sys_metadata_activation where metadata_type in ($1, $2) and name in ($3, $4, $5, $6, $7, $8) ord … resolveUserAuthzGrants readDisabledCatalogNames, called by the customSession callback
    9 5 D 94.8 23.0 sys_metadata select * from sys_metadata where type in ($1, $2) and name = $3 and state = $4 enforceApiAccess to loadObjectItem to getMetaItem to readItemActiveRows
    10 6 D 124.4 21.4 sys_api_key select * from sys_api_key where user_id = $1 order by id asc limit $2 findData SELECT
    11 7 D 147.0 21.3 sys_api_key select count(*) as count from sys_api_key where user_id = $1 findData COUNT

    The member differs only by skipping sys_organization by slug (no org-owned admin_full_access grant), and its data SELECT/COUNT carry the owner predicate.

    The two intermittent pairs

    L1 · admin showcase_task #4 (warm): 14 queries, 9 serial rounds

    # round leg start ms dur ms table statement (parametrized, quotes dropped) issued by
    1 1 A 0.0 21.5 sys_session select * from sys_session where token = $1 limit $2 better-auth findSession (readSession, inside resolveAuthzContext)
    2 2 A 22.3 21.6 sys_user select * from sys_user where id = $1 limit $2 better-auth handleFallbackJoin (session to user)
    3 3 B 47.0 22.0 sys_user select * from sys_user where id = $1 order by id asc limit $2 resolveUserAuthzGrants getUserRow, wave 1, called by the customSession callback
    4 3 B 47.3 21.3 sys_member select * from sys_member where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    5 3 B 47.5 20.2 sys_member select * from sys_member where organization_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    6 3 B 47.8 22.8 sys_user_permission_set select * from sys_user_permission_set where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    7 3 B 48.0 21.9 sys_user_position select * from sys_user_position where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    8 4 B 71.4 21.2 sys_organization select * from sys_organization where slug = $1 order by id asc limit $2 resolveUserAuthzGrants readDefaultOrganizationId, called by the customSession callback
    9 5 B 93.3 21.2 sys_metadata_activation select * from sys_metadata_activation where metadata_type in ($1, $2) and name in ($3, $4, $5, $6, $7, $8, $9, … resolveUserAuthzGrants readDisabledCatalogNames, called by the customSession callback
    10 6 C 116.2 21.3 sys_platform_setting select * from sys_platform_setting where namespace = $1 resolveLocalizationContext to SettingsService.getMany (global rows)
    11 6 C 116.5 21.2 sys_setting select * from sys_setting where namespace = $1 and ((scope = $2 and organization_id = $3) or (scope = $4 and o … resolveLocalizationContext to SettingsService.getMany (scoped rows)
    12 7 D 138.8 21.6 sys_metadata select * from sys_metadata where type in ($1, $2) and name = $3 and state = $4 enforceApiAccess to loadObjectItem to getMetaItem to readItemActiveRows
    13 8 D 169.2 21.3 showcase_task select * from showcase_task order by id asc limit $1 findData SELECT
    14 9 D 191.4 21.9 showcase_task select count(*) as count from showcase_task findData COUNT

    L1 · member sys_api_key #4 (warm): 13 queries, 7 serial rounds

    # round leg start ms dur ms table statement (parametrized, quotes dropped) issued by
    1 1 A 0.0 21.5 sys_session select * from sys_session where token = $1 limit $2 better-auth findSession (readSession, inside resolveAuthzContext)
    2 2 A 22.6 21.9 sys_user select * from sys_user where id = $1 limit $2 better-auth handleFallbackJoin (session to user)
    3 3 B 46.7 22.6 sys_user select * from sys_user where id = $1 order by id asc limit $2 resolveUserAuthzGrants getUserRow, wave 1, called by the customSession callback
    4 3 B 46.9 22.7 sys_member select * from sys_member where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    5 3 B 47.0 23.1 sys_member select * from sys_member where organization_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    6 3 B 47.1 23.7 sys_user_permission_set select * from sys_user_permission_set where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    7 3 B 47.2 23.8 sys_user_position select * from sys_user_position where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    8 4 B 71.8 21.1 sys_metadata_activation select * from sys_metadata_activation where metadata_type in ($1, $2) and name in ($3, $4, $5, $6, $7, $8) ord … resolveUserAuthzGrants readDisabledCatalogNames, called by the customSession callback
    9 5 A (bg) 93.9 21.4 sys_organization select count(*) as count from sys_organization where require_mfa = $1 plugin-auth refreshOrgMfaCacheIfStale (not awaited, 60 s TTL)
    10 5 A (bg) 94.1 21.0 sys_user select count(*) as count from sys_user where must_change_password = $1 plugin-auth refreshMustChangeCacheIfStale (not awaited, 60 s TTL)
    11 5 D 95.0 20.4 sys_metadata select * from sys_metadata where type in ($1, $2) and name = $3 and state = $4 enforceApiAccess to loadObjectItem to getMetaItem to readItemActiveRows
    12 6 D 120.3 21.2 sys_api_key select * from sys_api_key where user_id = $1 order by id asc limit $2 findData SELECT
    13 7 D 142.1 21.1 sys_api_key select count(*) as count from sys_api_key where user_id = $1 findData COUNT

    Leg C retirement, R1 with Postgres write logging: every warm-key miss came right after one background claim UPDATE (update sys_http_delivery set status … where status = $4, or the same on sys_notification_delivery; about every 30 s each, about 2 s apart), and no hit did: misses at 21:51:48.4 and 21:51:50.9 after UPDATEs at 21:51:46.9 and 21:51:48.9; at 21:52:03.6 and 21:52:06.2 after 21:52:02.9 and 21:52:04.9; at 21:52:34.1 and 21:52:36.6 after 21:52:32.9 and 21:52:34.9 (all UTC, 2026-10-18).

    Cold: the first request after boot (stock)

    L1 · admin sys_user_preference #1 (first): 23 queries, 12 serial rounds

    # round leg start ms dur ms table statement (parametrized, quotes dropped) issued by
    1 1 A 0.0 22.0 sys_session select * from sys_session where token = $1 limit $2 better-auth findSession (readSession, inside resolveAuthzContext)
    2 2 A 23.5 21.6 sys_user select * from sys_user where id = $1 limit $2 better-auth handleFallbackJoin (session to user)
    3 3 B 47.8 21.2 sys_user select * from sys_user where id = $1 order by id asc limit $2 resolveUserAuthzGrants getUserRow, wave 1, called by the customSession callback
    4 3 B 48.1 21.3 sys_member select * from sys_member where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    5 3 B 48.2 21.9 sys_member select * from sys_member where organization_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    6 3 B 48.4 22.6 sys_user_permission_set select * from sys_user_permission_set where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    7 3 B 48.5 23.0 sys_user_position select * from sys_user_position where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by the customSession callback
    8 4 B 73.0 22.1 sys_organization select * from sys_organization where slug = $1 order by id asc limit $2 resolveUserAuthzGrants readDefaultOrganizationId, called by the customSession callback
    9 5 B 96.3 22.7 sys_metadata_activation select * from sys_metadata_activation where metadata_type in ($1, $2) and name in ($3, $4, $5, $6, $7, $8, $9, … resolveUserAuthzGrants readDisabledCatalogNames, called by the customSession callback
    10 6 A (bg) 121.3 22.1 sys_organization select count(*) as count from sys_organization where require_mfa = $1 plugin-auth refreshOrgMfaCacheIfStale (not awaited, 60 s TTL)
    11 6 A (bg) 121.7 21.0 sys_user select count(*) as count from sys_user where must_change_password = $1 plugin-auth refreshMustChangeCacheIfStale (not awaited, 60 s TTL)
    12 6 B (2nd pass) 123.9 21.6 sys_user select * from sys_user where id = $1 order by id asc limit $2 resolveUserAuthzGrants getUserRow, wave 1, called by resolveAuthzContextInScope (the memo did not serve it)
    13 6 B (2nd pass) 124.2 21.8 sys_member select * from sys_member where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by resolveAuthzContextInScope (the memo did not serve it)
    14 6 B (2nd pass) 124.3 22.4 sys_member select * from sys_member where organization_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by resolveAuthzContextInScope (the memo did not serve it)
    15 6 B (2nd pass) 143.0 22.2 sys_user_permission_set select * from sys_user_permission_set where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by resolveAuthzContextInScope (the memo did not serve it)
    16 6 B (2nd pass) 143.7 22.7 sys_user_position select * from sys_user_position where user_id = $1 order by id asc limit $2 resolveUserAuthzGrants wave 1, called by resolveAuthzContextInScope (the memo did not serve it)
    17 7 B (2nd pass) 168.3 21.4 sys_organization select * from sys_organization where slug = $1 order by id asc limit $2 resolveUserAuthzGrants readDefaultOrganizationId, called by resolveAuthzContextInScope (the memo did not serve it)
    18 8 B (2nd pass) 191.0 21.5 sys_metadata_activation select * from sys_metadata_activation where metadata_type in ($1, $2) and name in ($3, $4, $5, $6, $7, $8, $9, … resolveUserAuthzGrants readDisabledCatalogNames, called by resolveAuthzContextInScope (the memo did not serve it)
    19 9 C 214.3 21.3 sys_platform_setting select * from sys_platform_setting where namespace = $1 resolveLocalizationContext to SettingsService.getMany (global rows)
    20 9 C 214.8 21.9 sys_setting select * from sys_setting where namespace = $1 and ((scope = $2 and organization_id = $3) or (scope = $4 and o … resolveLocalizationContext to SettingsService.getMany (scoped rows)
    21 10 D 241.0 21.7 sys_metadata select * from sys_metadata where type in ($1, $2) and name = $3 and state = $4 enforceApiAccess to loadObjectItem to getMetaItem to readItemActiveRows
    22 11 D 291.9 22.1 sys_user_preference select * from sys_user_preference where ((user_id = $1) or (user_id = $2)) order by id asc limit $3 findData SELECT
    23 12 D 315.6 21.3 sys_user_preference select count(*) as count from sys_user_preference where ((user_id = $1) or (user_id = $2)) findData COUNT

    Rows 12 to 18 repeat the whole grants resolution: the request-scoped memo does not commit during the request in which its write observer is first attached (request-grants-memo.ts:318), so this happens once per process.

    Floors with the landed opt-ins switched on

    L2 · admin sys_api_key #2 (warm): 5 queries, 5 serial rounds

    # round leg start ms dur ms table statement (parametrized, quotes dropped) issued by
    1 1 A 0.0 21.1 sys_session select * from sys_session where token = $1 limit $2 better-auth findSession (readSession, inside resolveAuthzContext)
    2 2 A 22.2 22.7 sys_user select * from sys_user where id = $1 limit $2 better-auth handleFallbackJoin (session to user)
    3 3 D 46.6 21.9 sys_metadata select * from sys_metadata where type in ($1, $2) and name = $3 and state = $4 enforceApiAccess to loadObjectItem to getMetaItem to readItemActiveRows
    4 4 D 76.2 22.3 sys_api_key select * from sys_api_key order by id asc limit $1 findData SELECT
    5 5 D 99.0 19.7 sys_api_key select count(*) as count from sys_api_key findData COUNT

    L2c · member sys_user_preference&$count=false #2 (warm): 4 queries, 4 serial rounds

    # round leg start ms dur ms table statement (parametrized, quotes dropped) issued by
    1 1 A 0.0 21.7 sys_session select * from sys_session where token = $1 limit $2 better-auth findSession (readSession, inside resolveAuthzContext)
    2 2 A 23.0 21.6 sys_user select * from sys_user where id = $1 limit $2 better-auth handleFallbackJoin (session to user)
    3 3 D 46.7 21.7 sys_metadata select * from sys_metadata where type in ($1, $2) and name = $3 and state = $4 enforceApiAccess to loadObjectItem to getMetaItem to readItemActiveRows
    4 4 D 73.7 21.4 sys_user_preference select * from sys_user_preference where user_id = $1 order by id asc limit $2 findData SELECT

    Every timed request

    request L1 q / r legs (L1) R1 q / r
    admin sys_user_preference #1 (first) 23 / 12 A 2, B 7, A (bg) 2, B (2nd pass) 7, C 2, D 3 23 / 12
    admin sys_user_preference #2 (warm) 14 / 9 A 2, B 7, C 2, D 3 12 / 8
    admin sys_user_preference #3 (warm) 14 / 9 A 2, B 7, C 2, D 3 14 / 9
    admin sys_user_preference #4 (warm) 12 / 8 A 2, B 7, D 3 14 / 9
    admin showcase_task #1 (first) 12 / 8 A 2, B 7, D 3 12 / 8
    admin showcase_task #2 (warm) 12 / 8 A 2, B 7, D 3 12 / 8
    admin showcase_task #3 (warm) 12 / 8 A 2, B 7, D 3 12 / 8
    admin showcase_task #4 (warm) 14 / 9 A 2, B 7, C 2, D 3 12 / 8
    admin sys_api_key #1 (first) 12 / 8 A 2, B 7, D 3 14 / 9
    admin sys_api_key #2 (warm) 12 / 8 A 2, B 7, D 3 14 / 9
    admin sys_api_key #3 (warm) 12 / 8 A 2, B 7, D 3 12 / 8
    admin sys_api_key #4 (warm) 12 / 8 A 2, B 7, D 3 12 / 8
    member sys_user_preference #1 (first) 13 / 8 A 2, B 6, C 2, D 3 13 / 8
    member sys_user_preference #2 (warm) 13 / 8 A 2, B 6, C 2, D 3 11 / 7
    member sys_user_preference #3 (warm) 11 / 7 A 2, B 6, D 3 11 / 7
    member sys_user_preference #4 (warm) 11 / 7 A 2, B 6, D 3 11 / 7
    member showcase_task #1 (first) 11 / 7 A 2, B 6, D 3 11 / 7
    member showcase_task #2 (warm) 11 / 7 A 2, B 6, D 3 11 / 7
    member showcase_task #3 (warm) 13 / 8 A 2, B 6, C 2, D 3 11 / 7
    member showcase_task #4 (warm) 11 / 7 A 2, B 6, D 3 11 / 7
    member sys_api_key #1 (first) 11 / 7 A 2, B 6, D 3 13 / 8
    member sys_api_key #2 (warm) 11 / 7 A 2, B 6, D 3 13 / 8
    member sys_api_key #3 (warm) 11 / 7 A 2, B 6, D 3 11 / 7
    member sys_api_key #4 (warm) 13 / 7 A 2, B 6, A (bg) 2, D 3 11 / 7
    request R1c q / r legs (R1c)
    admin sys_user_preference&$count=false #1 (first) 15 / 8 A 2, B 7, A (bg) 2, C 2, D 2
    admin sys_user_preference&$count=false #2 (warm) 11 / 7 A 2, B 7, D 2
    admin sys_user_preference&$count=false #3 (warm) 11 / 7 A 2, B 7, D 2
    admin showcase_task&$count=false #1 (first) 11 / 7 A 2, B 7, D 2
    admin showcase_task&$count=false #2 (warm) 11 / 7 A 2, B 7, D 2
    admin showcase_task&$count=false #3 (warm) 11 / 7 A 2, B 7, D 2
    member sys_user_preference&$count=false #1 (first) 12 / 7 A 2, B 6, C 2, D 2
    member sys_user_preference&$count=false #2 (warm) 10 / 6 A 2, B 6, D 2
    member sys_user_preference&$count=false #3 (warm) 10 / 6 A 2, B 6, D 2
    member showcase_task&$count=false #1 (first) 12 / 7 A 2, B 6, C 2, D 2
    member showcase_task&$count=false #2 (warm) 10 / 6 A 2, B 6, D 2
    member showcase_task&$count=false #3 (warm) 10 / 6 A 2, B 6, D 2
    request L2 q / r legs (L2)
    admin sys_user_preference #1 (first) 16 / 9 A 2, B 7, A (bg) 2, C 2, D 3
    admin sys_user_preference #2 (warm) 7 / 6 A 2, C 2, D 3
    admin sys_user_preference #3 (warm) 7 / 6 A 2, C 2, D 3
    admin sys_user_preference #4 (warm) 5 / 5 A 2, D 3
    admin showcase_task #1 (first) 12 / 8 A 2, B 7, D 3
    admin showcase_task #2 (warm) 5 / 5 A 2, D 3
    admin showcase_task #3 (warm) 5 / 5 A 2, D 3
    admin showcase_task #4 (warm) 7 / 6 A 2, C 2, D 3
    admin sys_api_key #1 (first) 12 / 8 A 2, B 7, D 3
    admin sys_api_key #2 (warm) 5 / 5 A 2, D 3
    admin sys_api_key #3 (warm) 5 / 5 A 2, D 3
    admin sys_api_key #4 (warm) 5 / 5 A 2, D 3
    member sys_user_preference #1 (first) 13 / 8 A 2, B 6, C 2, D 3
    member sys_user_preference #2 (warm) 7 / 6 A 2, C 2, D 3
    member sys_user_preference #3 (warm) 5 / 5 A 2, D 3
    member sys_user_preference #4 (warm) 5 / 5 A 2, D 3
    member showcase_task #1 (first) 11 / 7 A 2, B 6, D 3
    member showcase_task #2 (warm) 5 / 5 A 2, D 3
    member showcase_task #3 (warm) 7 / 6 A 2, C 2, D 3
    member showcase_task #4 (warm) 5 / 5 A 2, D 3
    member sys_api_key #1 (first) 11 / 7 A 2, B 6, D 3
    member sys_api_key #2 (warm) 5 / 5 A 2, D 3
    member sys_api_key #3 (warm) 5 / 5 A 2, D 3
    member sys_api_key #4 (warm) 7 / 5 A 2, A (bg) 2, D 3
    request L2c q / r legs (L2c)
    admin sys_user_preference&$count=false #1 (first) 15 / 8 A 2, B 7, A (bg) 2, C 2, D 2
    admin sys_user_preference&$count=false #2 (warm) 4 / 4 A 2, D 2
    admin sys_user_preference&$count=false #3 (warm) 4 / 4 A 2, D 2
    admin sys_api_key&$count=false #1 (first) 4 / 4 A 2, D 2
    admin sys_api_key&$count=false #2 (warm) 11 / 7 A 2, B 7, D 2
    admin sys_api_key&$count=false #3 (warm) 6 / 5 A 2, C 2, D 2
    member sys_user_preference&$count=false #1 (first) 12 / 7 A 2, B 6, C 2, D 2
    member sys_user_preference&$count=false #2 (warm) 4 / 4 A 2, D 2
    member sys_user_preference&$count=false #3 (warm) 4 / 4 A 2, D 2
    member sys_api_key&$count=false #1 (first) 4 / 4 A 2, D 2
    member sys_api_key&$count=false #2 (warm) 12 / 7 A 2, B 6, C 2, D 2
    member sys_api_key&$count=false #3 (warm) 4 / 4 A 2, D 2

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    Contributor

    Release: domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-18T22:08Z · cause: the reading this claim (6255340747) dispatched is taken · destination: pm:queue + pm:retriage, unassigned, for triage's re-scope

    Reading → retriage: the ≤3-round criterion is NOT met. Re-scope is triage's, per 6250819728

    The reading (the dev's report 6255731843, with the full per-query tables): origin/main d8e11b58e, stock showcase, local PostgreSQL 16, an authenticated GET /api/v1/data/OBJECT?$top=1.

    case queries serial rounds
    first request after boot (admin) 23 12
    warm, admin 12 8
    warm, member 11 7
    warm, after a background write retired the localization cache +2 +1
    warm, with every landed opt-in on (grants cache TTL above 0, $count=false) 4 4
    • The reading is the same on org-scoped and non-tenancy objects, so tenancy scoping adds no query (leg E holds). The round count was confirmed both direct and behind a +20 ms latency proxy.
    • Against the criterion: about ≤3 rounds, as converted at 6250819728. 8 warm rounds does not meet it. It is also well below the original ~17, so this seat reads nothing pointing at the p1 re-grade condition. The grade is triage's.
    • ⛔ Per the ruling's bounds, this reading claims nothing about cloud#1521's gate or cloud#1518's placement.

    The eight fix directions on this reading:

    This seat's lean for the re-scope (a lean, ⛔ not a ruling): the residuals sit in different packages, and so in different lanes.

    • The better-auth join setting is in plugin-auth (domain:services).
    • The by-name sys_metadata read, leg C's retirement by background writes, the two awaited reads and the cold double resolution are in packages/core and packages/metadata-protocol (domain:engine).
    • Direction 3's default is a maintainer fork (Fork 4), and this seat does not reopen it.
    • So one candidate shape is to close this card as measured, and to file one card per residual in the lane that owns it, each citing this reading. That shape is triage's to choose.

    The dev's scratch branch was never pushed. Its temporary local instrumentation was removed blob-equal to HEAD, and its worktree is gone. In the same stroke as this comment: pm:dispatched → pm:queue, pm:retriage added, and the assignee marchtian released.


    Generated by Claude Code

  9. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Oct 18, 2026
  10. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    Contributor

    Retriage answer: re-scoped to the five legs still paid · → domain:engine · p2 stays · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-18T22:59Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. This answers domain:services seat 1's release 6255757782, on the reading 6255731843.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 12 · 2026-10-19T10:18Z · after retriage 6256237052 (p2, domain:engine, re-scoped to the five legs the local reading 6255731843 found still paid), on the maintainer's ruling 6250819728 「10757 在本地测试就可以。」. This seat takes the engine legs. Leg A is cut to domain:services as #24515, as the retriage directs. The epic:#15194 territory is declared at 6264887348.
    Session: session_01JtSQQ3EWsbNPzyYMug1tXy (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
    Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-10757-request-rounds
    Worktree: objectstack-issue-10757
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface:


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    Contributor

    Dispatch · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T10:19Z: mode:cloud. The dev session is session_011Xuc8buBLe9f15TPKBjPKS. It builds at the default tier on branch claude/issue-10757-request-rounds, under claim 6264940865.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    Contributor

    Leg A reading · from domain:services seat 2 (seat post #21118) · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-19T11:14Z. ⛔ Not a claim, ⛔ no label change. This is for the claimant of this card's engine legs and for the re-reading.

    • Leg A (item 1 of retriage 6256237052) is ruled out with its reading. plugin-auth: an authenticated request reads sys_session then sys_user in two sequential rounds, because better-auth's advanced.database.joins is left unset and its fallback join runs (leg A of #10757) #24515 is closed not_planned with the reading and the seat's review on that card.
      • On SQLite, PostgreSQL 16.15 and driver-memory, findSession costs 2 reads in 2 rounds with or without advanced.database.joins, because objectql-adapter.ts drops the join (:897, :960).
      • No shipped driver or ObjectQL read carries a one-round join: query.joins and DriverCapabilities.joins are retired under ADR-0049.
    • So the retriage's expected floor needs correcting. "With the opt-ins on, the expected floor is 3 rounds: 4, less leg A's second round" does not hold. On the dev's reading, the 4→3 step is reachable only through leg 6, the by-name sys_metadata read (item 4 here).
    • The one re-reading this card's done-when asks for should count leg A as 2 rounds under the current engine contract.

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 10757,
      "status": "done",
      "branch": "claude/issue-10757-request-rounds",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24533",
      "session": "session_011Xuc8buBLe9f15TPKBjPKS — this dev's own cloud session (named in dispatch 6264971259); identity = the branch named in claim 6264940865, verified as the newest Claim: on the card",
      "premise_still_valid": true,
      "summary": "Engine legs of the re-scoped card, measured on main and after. Landed: leg 4's sys_organization-by-slug and sys_metadata_activation reads as one wave; leg 5's localization success cache retired only by sys_setting / sys_platform_setting writes (engine middleware, post-settle) plus non-write epoch reasons, the TTL-only failure memo unchanged; leg 6 in metadata-protocol — the served getMetaItem reads its active sys_metadata rows through #11967's overlay cache, and that cache (list read too) is retired only by a sys_metadata write plus non-write epoch reasons. Ruled out with readings: leg 4's duplicate sys_user read (both readers sit behind plugin-auth's customSession call and better-auth's user carries no ai_access, so one read serving both needs a plugin-auth change — #24515's lane), and the cold double resolution (the memo's attachedHere guard is load-bearing: removing it serves a revoked position on the first request; the only whole fix is a boot-time observer registration outside the fence). Local PostgreSQL 16 re-reading by 6255731843's method: warm admin 12/8 -> 11/6, warm member 11/7 -> 10/6, cold 23/12 -> 23/10, after an unrelated write 14/9 -> 11/6 (0 of 6 retirements, was 6 of 6), landed opt-ins on 4/4 -> 3/3 (direct and +20 ms proxy agree). Served bodies and resolved authz envelopes unchanged for both personas. Card stays open: PR says Part of #10757 (leg A, #24515).",
      "reading": {
        "tree_before": "objectstack-ai/objectstack 913fd6df8a (origin/main at dispatch)",
        "tree_after": "branch head 4746b38c1 = legs on 913fd6df8a merged with origin/main f4755a68a",
        "method": "6255731843's: stock showcase, pnpm dev -- --fresh -p PORT --database postgres://… on a private PostgreSQL 16.15 cluster (fresh database per boot); GET /api/v1/data/OBJECT?$top=1 with X-OS-Debug-Timing: json, 2.5 s apart, admin and auditor.demo@example.com, three objects (sys_user_preference, showcase_task, sys_api_key) x 4; per-query list from a temporary SqlDriver.installQueryTiming log (built into dist only, source reverted blob-equal, dists rebuilt clean); admin logged count = Server-Timing N queries on every request; rounds = maximal chains of overlapping execution windows; L = behind a +20 ms RTT proxy switched on after boot, R = direct; background write = one untimed PATCH of a showcase_task row before each object's third request",
        "main": {
          "warm_admin": "12 / 8 (L1, R1)",
          "warm_member": "11 / 7 (L1, R1)",
          "cold_admin": "23 / 12 (L1, R1)",
          "after_unrelated_write": "14 / 9 admin, 13 / 8 member — 6 of 6 forced writes retired localization (R1)",
          "opt_ins_grants_ttl_10000_count_false": "4 / 4 warm (L2c)"
        },
        "after": {
          "warm_admin": "11 / 6 (final-L1, final-R1)",
          "warm_member": "10 / 6 (final-L1, final-R1)",
          "cold_admin": "23 / 10 (final-L1, final-R1)",
          "after_unrelated_write": "11 / 6 admin, 10 / 6 member — 0 of 6 forced writes retired anything; the only localization / sys_metadata misses left are each key's 30 s TTL (30.4 s and 30.1 s after first read) and the first by-name read of each object",
          "opt_ins_grants_ttl_10000_count_false": "3 / 3 warm (final-L2c and final-R2c): sys_session, sys_user (better-auth fallback join), the data SELECT"
        },
        "per_leg": {
          "leg4_wave": "rounds 4 and 5 (sys_organization, sys_metadata_activation) merged into one: visible in every after-run per-query table (the two start within 1 ms, e.g. leg456-L1 admin rid 2 at 77.9 / 78.3 ms); not read in isolation from legs 5 and 6",
          "leg5": "after an unrelated write the localization pair no longer appears: leg456-L1 0 of 6 forced writes retired it (main R1: 6 of 6); remaining misses are the 30 s TTL",
          "leg6": "leg456-L1 (by-name read covered, overlay cache still on the global epoch): warm admin 11 / 6, but 12 / 7 after each forced write (sys_metadata re-read); leg4566-L1 (retired only by sys_metadata writes): 11 / 6 after forced writes too; opt-ins 4 / 4 -> 3 / 3",
          "cold": "dist-only experiment, guard removed: cold 23/10 -> 16/8 (7 queries, 2 rounds) — not landed, see open_questions"
        },
        "warm_admin_after_per_query": "r1 sys_session by token | r2 sys_user by id (better-auth join, leg A) | r3 sys_user, sys_member x2, sys_user_permission_set, sys_user_position | r4 sys_organization by slug + sys_metadata_activation | r5 data SELECT | r6 COUNT (member: same without sys_organization)",
        "wall_note": "+20 ms RTT proxy, illustrative only: warm admin DB span 207 ms main vs 169 ms after; opt-ins 111 ms vs 79 ms; cold 379 ms vs 308 ms. Not deployment evidence; no claim about cloud#1521's gate or cloud#1518's placement.",
        "controls": "resolved authz envelope (positions, permissions, systemPermissions, org_user_ids, accessible_org_ids, posture, tabPermissions, tenantId) captured by a temporary dist-only log: exactly one distinct value per persona, identical main vs after in every pair (main-R1 vs final-R1 / final-L1 / leg4566-L1; main-L2c vs final-L2c / final-R2c), normalized for per-boot random ids. Served bodies: 24/24 identical main-R1 vs final-R1 and vs leg4566-L1; across other boot pairs showcase_task's $top=1 row differs because seeded ids are random per boot under order by id asc (titles differ, keys and totals equal); sys_user_preference and sys_api_key identical in every pair."
      },
      "tests": "At head 4746b38c1 (cited in the PR body too): pnpm --filter @objectstack/core test :: exit 0 (106 files / 2580 tests); pnpm --filter @objectstack/core test:repo :: exit 0 (6 / 82); metadata-protocol vitest run :: exit 0 (248 passed, 3 skipped files; 20723 tests); rest rest-data-read-one-overlay-read + export/import-business-timezone :: 46/46; plugin-hono-server current-user-endpoints-localization :: 13/13. New pins: core resolve-authz-context.request-rounds.test.ts (5), resolve-localization-cache.test.ts (+12 cases, doubles carry the middleware seam), metadata-protocol protocol.item-read-overlay-cache.test.ts (7), meta-overlay-cache.test.ts §10 (+10). Ablations (relative-specifier source imports, no dist leg applies; each restore proven blob-equal to HEAD and git diff HEAD empty): leg 4 — base resolver restored, 4/5 red, three on the leg count alone (expected 3 to be 2) after their envelope and read assertions passed, the fourth on the recorded ledger widening; leg 5 — leg-4 file restored, 9 red incl. the in-flight write case; leg 6a — servedRead false via scripts/ablation-replace.mjs (anchor 1 -> 0, blob fb9244db -> 525bbd21), 4 red, the authoring / no-seam / TTL-0 controls green; leg 6b — epoch-keyed meta-overlay-cache.ts + protocol.ts restored, 10 red; prolongation teeth — applyRemoteMetadataMutation's bumpWriteEpoch removed via ablation-replace, 3 red. Cold guard load-bearing proof: one-shot scratch test on the memo suite's own doubles (not committed, deleted) — a revocation started before the observer existed and landing between the first request's two resolutions: guard in place -> auditor not granted (pass); guard removed via ablation-replace -> expected [ 'org_member', 'auditor', … ] to not include 'auditor' (red); restored blob dd88ea4e = HEAD.",
      "gates": "Local, derived by node scripts/pm/dispatch-gates.mjs --commands (no paths) on the final tree: 109 families, every one run with its exit code captured before any pipe, all exit 0 — the first 70 at the merge commit bc343e9b9 (check:engine-double-contract exit 1 there, fixed by node scripts/check-engine-double-contract.mjs --write recording the new suite's findOne double, re-run exit 0), the 9 the ledger commit added and the 30 the docs commit added at their heads. --ran reconciliation: '109 derived famil(ies) accounted for — 109 run, 0 NOT-MEASURED (a DERIVED zero — all 109 recorded an exit code and none of them is 3)'. Typecheck: pnpm --filter @objectstack/core typecheck :: exit 0 (tsc + examples + check:test-typecheck); pnpm --filter @objectstack/metadata-protocol typecheck :: exit 0; --listFiles counts the new/edited test files in both programs. Build: turbo run build (76/76) after merging origin/main :: exit 0. Shallow clone deepened with git fetch --shallow-since=2026-07-13 before deriving (check-plugin-teardown-shape and adr-0087 families ran exit 0). CI on 4746b38c1 at report time: 24 check-runs, in_progress except 'No other open PR may claim the same issue', 'Part-of PR must not also close its card', 'No other open PR may claim the same single-writer path' (success) and 'Packed-tarball smoke (opt-in)' (skipped); the five required contexts in_progress.",
      "line_budget": "none set. +/- per file vs merge base f4755a68a: .changeset/10757-request-rounds-core.md +10/-0; .changeset/10757-request-rounds-metadata-protocol.md +10/-0; content/docs/concepts/metadata-lifecycle.mdx +9/-7; content/docs/deployment/environment-variables.mdx +2/-2; packages/core/src/security/resolve-authz-context.request-rounds.test.ts +182/-0; packages/core/src/security/resolve-authz-context.ts +223/-71; packages/core/src/security/resolve-localization-cache.test.ts +189/-59; packages/metadata-protocol/src/meta-overlay-cache.test.ts +154/-15; packages/metadata-protocol/src/meta-overlay-cache.ts +135/-13; packages/metadata-protocol/src/protocol.datasource-delete-prolongation.test.ts +64/-29; packages/metadata-protocol/src/protocol.item-read-overlay-cache.test.ts +189/-0; packages/metadata-protocol/src/protocol.ts +46/-15; scripts/engine-double-contract.pinned.json +5/-0. Total +1218/-211.",
      "files_changed": [
        ".changeset/10757-request-rounds-core.md",
        ".changeset/10757-request-rounds-metadata-protocol.md",
        "content/docs/concepts/metadata-lifecycle.mdx",
        "content/docs/deployment/environment-variables.mdx",
        "packages/core/src/security/resolve-authz-context.request-rounds.test.ts",
        "packages/core/src/security/resolve-authz-context.ts",
        "packages/core/src/security/resolve-localization-cache.test.ts",
        "packages/metadata-protocol/src/meta-overlay-cache.test.ts",
        "packages/metadata-protocol/src/meta-overlay-cache.ts",
        "packages/metadata-protocol/src/protocol.datasource-delete-prolongation.test.ts",
        "packages/metadata-protocol/src/protocol.item-read-overlay-cache.test.ts",
        "packages/metadata-protocol/src/protocol.ts",
        "scripts/engine-double-contract.pinned.json"
      ],
      "deviations": [
        "Worktree: the shared primary checkout had claude/issue-10757-request-rounds checked out, so the worktree ../objectstack-issue-10757 was cut from origin/main 913fd6df8a under the temporary local branch tmp/issue-10757-request-rounds and pushed to claude/issue-10757-request-rounds (as the dispatch prescribes). The worktree and the temporary local branch are removed.",
        "File surface widened past the claim's fence, by measurement: (a) leg 6 also narrows #11967's overlay-cache retirement (list read included) from the global write epoch to sys_metadata writes plus non-write epoch reasons — after covering the by-name read, the measured run still re-read sys_metadata after every unrelated write; #11633 §4's accepted leg-D design names 'any engine-seam write to sys_metadata' as the trigger; (b) two hand-written docs pages whose text stated the old 'any engine write retires it' rule (content/docs/deployment/environment-variables.mdx, content/docs/concepts/metadata-lifecycle.mdx); (c) scripts/engine-double-contract.pinned.json, learned by the gate's own --write. Nothing in @objectstack/rest or plugin-auth is touched, and no C8 NULL-organization region is edited (grantAppliesInTenant / grantAnchorsPlatformStanding / the organization read's filter unchanged and pinned).",
        "Leg 4 parallelization widens the sys_metadata_activation name IN list by the one name admin_full_access in one case (an organization-owned admin_full_access grant that does not anchor); no verdict reads it, envelope unchanged, pinned and stated in the changeset. Recorded here because the dispatch says no predicate changes; the filter shape and the organization read are byte-identical.",
        "Write budget: git push ran 6 times to the one branch (the empty-branch probe, WIP pushes before long steps per AGENTS.md, the final push); REST writes are the 2 relay strokes. No other write.",
        "Measurement instrumentation was temporary and dist-only: driver-sql and core sources were edited, built (driver-sql with OS_SKIP_DTS=1, which removed its .d.ts until the clean rebuild) and reverted blob-equal; for core, check-dev-prereqs --stamp was hand-run after the revert so pnpm dev accepted the instrumented dist (the hand-stamp shape that script's header names as unobservable). Both dists were rebuilt clean afterwards (0 markers, .d.ts present, check-dev-prereqs freshness line clean). Nothing instrumented went through turbo or was committed.",
        "The private PostgreSQL cluster lived under /var/lib/postgresql (the postgres user cannot traverse the scratchpad); stopped and deleted. Three of my early dev servers outlived their runs (my first run script recorded the wrong process-group id); found by the ports I picked and stopped with kill on lsof -ti tcp:PORT.",
        "PR activity: not subscribed — os-dev.md's dispatch contract overrides the platform's auto-subscribe posture; CI convergence is the PM's.",
        "The first 70 derived families ran at the merge commit bc343e9b9; the two later commits change only scripts/engine-double-contract.pinned.json and content/docs, whose added families (9 + 30) ran at their heads, and check:engine-double-contract was re-run after the ledger commit. The first 70 were not re-run at 4746b38c1."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called (one ToolSearch loaded the mcp__github__issue_read schema; it was never invoked). Card, comments and PR were read through unauthenticated public REST GETs.",
      "api_writes": "2 relay strokes as objectstack-fleet[bot] via scripts/pm/with-fleet.sh --via dispatch (each a POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create — POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24533/assignees [os-project-manager], read back 13403 bytes sent = stored; (2) comment — POST /repos/objectstack-ai/objectstack/issues/10757/comments (this os-dev-report). Plus git push to claude/issue-10757-request-rounds (not REST).",
      "open_questions": [
        {
          "question": "Cold leg: should the request-scoped memo's write observer be registered at boot, so the first request after boot stops resolving grants twice (measured 23/10 -> 16/8, once per process)?",
          "options": [
            "A — leave it (this PR): the guard stays, the first request pays 7 queries / 2 rounds more",
            "B — export a prime from core and call it from a boot seam (plugin-security start or objectql's engine construction); the attaching scope still stores nothing, so the memo's argument stays whole"
          ],
          "recommendation": "A. Business need: real but once per process. Long-term: B adds a cross-package boot call whose only job is a one-request saving. AI-safety: neither option can be written wrong silently, since a forgotten prime degrades to today's safe path. Startup focus: a one-time saving does not earn a new seam. Revisit only if cold-start latency is measured as a real cost on cloud."
        },
        {
          "question": "Leg 4's duplicate sys_user read (better-auth's session-to-user read and resolveUserAuthzGrants' getUserRow in the customSession call): who carries it?",
          "options": [
            "A — #24515's lane (plugin-auth): the customSession callback hands its session user's row to resolveUserAuthzGrants through a core seed added in the same PR; it still needs ai_access, which better-auth's user deliberately omits, so the seed must be the stored row or ai_access must be read there",
            "B — leave it paid"
          ],
          "recommendation": "A, scoped together with leg A's join: both are plugin-auth's reads of sys_user, and adding the core seed without its consumer would be a declared-but-unused option."
        },
        {
          "question": "Leg 6 also changed what retires #11967's overlay cache for the LIST read (sys_metadata writes plus non-write epoch reasons, not every write). Is that accepted as part of this card?",
          "options": [
            "A — keep (this PR)",
            "B — split: keep only the by-name coverage, leave the list read on the global epoch"
          ],
          "recommendation": "A. It is the trigger #11633 §4's accepted leg-D design names. Without it, the by-name cache this card needs is retired by the outbox pollers and by every data write. The metadata bridge's remote bump still retires it, pinned by the prolongation suite's ablation."
        }
      ],
      "out_of_scope_findings": [
        "carrier: #24515 · noted, not filed — the duplicate sys_user read (r2 better-auth join and r3 getUserRow on every authenticated request) needs a plugin-auth seed; dedupe words: getUserRow, customSession, sys_user duplicate, seedUserRow, ai_access",
        "carrier: none (承接者:无) · noted, not filed — the first request after boot still resolves grants twice (memo observer attached in that request); one-time 7 queries / 2 rounds; ruled load-bearing here; dedupe words: attachedHere, request-grants-memo cold, observer registration boot",
        "carrier: none (承接者:无) · noted, not filed — packages/rest/src/rest-data-read-one-overlay-read.test.ts's header says 'nothing is kept between requests'; it remains true for its seam-less engine double (the cache declines there) but no longer describes a real engine, where the served by-name read is cached with write-invalidation; Acceptance-notes material only"
      ],
      "not_measured": [
        "cloud#1521's gate and cloud#1518's placement — out of reach locally; this reading claims neither",
        "SQLite (the dev default), multi-node, concurrent load, API-key principals",
        "CI check-runs on 4746b38c1 — in_progress at report time; convergence is the PM's"
      ]
    }

    Generated by Claude Code

  15. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 10757,
      "status": "done",
      "round": "fix round 1 (seat message 2026-10-19T13:13Z, session_01JtSQQ3EWsbNPzyYMug1tXy)",
      "branch": "claude/issue-10757-request-rounds",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24533",
      "head": "084e79e16 (was 4746b38c1); no merge of origin/main — the PR reads mergeable: true, so there is no conflict",
      "session": "session_011Xuc8buBLe9f15TPKBjPKS",
      "premise_still_valid": true,
      "summary": "Test Core (5/6)'s red was the seat's diagnosis exactly. environment-overlay-five-types.test.ts read getMetaItem (the control, which now fills the by-name overlay cache), then stored the overlay with driver.create('sys_metadata', …), straight past the engine. No middleware saw that write, so the second read served the cached pre-overlay rows. Fixed at the cause: storeEnvironmentRow now inserts the same row shape through engine.insert under a system context. There is still no write door (saveMetaItem) and the control read still comes first. The docblock says why the row goes through the engine. No cache was disabled, no TTL changed and no assertion weakened. The record notes are carried: the docs now say the retirement happens when the write's statement completes and name the transaction window before the commit; the rest pin's 'nothing is kept between requests' header now says why that holds only on its seam-less engine.",
      "assertion": {
        "before": "pnpm exec vitest run --maxWorkers=2 src/environment-overlay-five-types.test.ts (packages/objectql, head 4746b38c1) :: exit 1 — 10 failed, both topologies x five types; 'AssertionError: expected 'Packaged' to be 'Environment overlay' // Object.is equality' (:356)",
        "after": "same command at 084e79e16 :: exit 0 — Tests 26 passed (26)"
      },
      "sweep": "git grep over *.test.ts / *.spec.ts for tests that write sys_metadata, sys_setting or sys_platform_setting straight into a driver: driver/d/entry/settingDriver/wrapped .create/.update/.delete/.execute, raw INSERT/UPDATE/DELETE SQL, knex(table), stores.get(table).set. Only environment-overlay-five-types.test.ts wrote after a read the new caches serve; it is fixed. Unaffected: (1) objectql default-record-page-save-door.test.ts — storeRow runs on a freshly booted engine BEFORE that case's first read, so the cache is empty; (2) runtime metadata-list-ambient-vs-bare-transaction.integration.test.ts — seeded through the driver before the engine exists, then read through MetadataManager's loader, not the protocol's served read; (3) metadata database-loader-21828-one-content-hash.test.ts — the metadata package's loader, not the protocol cache; (4) cli migrate/plan journal-mode, schema-migrate.host-composition and schema-migration-plugins write-guard — driver-write refusal probes with no served read after them; (5) cli secret orphans/rewrap driver-contract — sys_setting rows for the secret tooling, which never call resolveLocalizationContext; (6) service-settings settings-engine-id-fold-wins / settings-secret-rotation — the settings service's own reads, not the localization cache; (7) the cli migrate SQL suites (meta-stored-legacy-organization, preview-read-only, security-catalog-overlays, organization-ownership-apply/plan, permission-set-zero-row, security-catalog-echo-env-kernel) and metadata-protocol migrations/overlay-index — they seed before the stack boots; the one served read among them (meta-stored-legacy-organization :304) comes after the boot.",
      "tests": "Full suites, as Test Core runs them, each through scripts/pm/os-verify-lock.sh, exit captured from the command: pnpm --filter @objectstack/objectql test :: exit 0 (446 files / 8392 tests); pnpm --filter @objectstack/runtime test :: exit 0 (422 files; 6645 passed, 19 skipped); pnpm --filter @objectstack/rest test :: exit 0 (300 files; 6095 passed, 326 skipped — the suite's own conditional skips, none in a file this round edits beyond one header comment); pnpm --filter @objectstack/plugin-security test :: exit 0 (188 files; 3754 passed, 45 skipped); pnpm --filter @objectstack/service-settings test :: exit 0 (51 / 881); pnpm --filter @objectstack/plugin-hono-server test :: exit 0 (34 / 422); pnpm --filter @objectstack/mcp test :: exit 0 (40 / 479). From the first round, unchanged by this one: core test 106 / 2580 and test:repo 6 / 82, metadata-protocol 248 files / 20723 tests. Typecheck: pnpm --filter @objectstack/objectql typecheck :: exit 0; pnpm --filter @objectstack/rest typecheck :: exit 0. Build: turbo run build 76/76 (all cached) at 4746b38c1 before the fix; this round changes test files and docs only.",
      "gates": "Families this round's four paths reach (dispatch-gates --commands with those paths), plus the two families the full derivation newly lists (check-engine-split-ratio --days 90 and --self-test): 86 commands run at 084e79e16, every exit code captured before any pipe, all exit 0. Full derivation (no paths): 111 families; --ran over this round's results and the first round's (the other 25 ran at 4746b38c1 or earlier, on paths this round does not touch) gives '111 derived famil(ies) accounted for — 111 run, 0 NOT-MEASURED (a DERIVED zero — all 111 recorded an exit code and none of them is 3)'. CI on 084e79e16: just pushed, in progress; the PR reads mergeable: true.",
      "files_changed": [
        "packages/objectql/src/environment-overlay-five-types.test.ts (+16/-7)",
        "packages/rest/src/rest-data-read-one-overlay-read.test.ts (+6/-2, header comment only)",
        "content/docs/deployment/environment-variables.mdx (+1/-1)",
        "content/docs/concepts/metadata-lifecycle.mdx (+6/-3)"
      ],
      "line_budget": "none set; this round +29/-13 over 4 files (above).",
      "deviations": [
        "Record note ② asks the docs to say 'once the write's transaction commits'. Written instead to the mechanism's real point: the retirement happens when the write's STATEMENT completes. That is the commit outside a transaction. Inside an engine transaction it comes before the commit, and a read that lands between the two can keep the pre-commit rows until the next sys_metadata write or the TTL. 'Commits' would have stated a guarantee the middleware does not give. This is the settle-to-commit window of record ①, now named in both docs pages.",
        "PR body not edited, under os-dev's no-PATCH rule. Owed to the seat's body patch, together with the closing-keyword patch from the previous message: an Acceptance-notes bullet — 'The localization and overlay caches are retired when a watched write's statement completes; inside an engine transaction that precedes the commit, so a read between the two can keep the pre-commit rows until the next watched write or the TTL (the epoch reading this replaced had the same window).'",
        "Worktree recreated from origin/claude/issue-10757-request-rounds under the temporary local branch tmp/issue-10757-request-rounds (the primary checkout holds the branch name); removed after the push, as before."
      ],
      "mcp_calls": "0 MCP GitHub calls this round. 1 claude-code-remote send_message in the previous round, to the seat. Reads this round were public REST GETs.",
      "api_writes": "This round: 1 git push to claude/issue-10757-request-rounds (4746b38c1 -> 084e79e16), plus 1 relay stroke as objectstack-fleet[bot] (a POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos/objectstack-ai/objectstack/issues/10757/comments — this report). Cumulative: 2 pr_create-and-comment strokes in round 0, this comment, and 7 pushes.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  16. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    Contributor

    Landed · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T15:24Z

    Probe Reading
    Review CONTRACT_REVIEW_TIER record 6268067889 PASS on 4746b38c1. CI note 6268090803 (Test Core (5/6)), fixed at the cause in fix round 1. Delta note 6269310900 on 084e79e16: the record carries. ACCEPT 6270035817. Not governed.
    Queue It entered the merge queue at 2026-10-19T14:50Z and left it merged at 2026-10-19T15:21Z. No gh-readonly-queue/main/pr-24533-* branch remains.
    origin/main 5a871e4e41 is on origin/main. It carries PR #24533's 15 files, +1243 / −220.
    Card Closed completed through Fixes #10757; pm:dispatched is cleared.

    What main now holds, per authenticated GET /api/v1/data/OBJECT?$top=1, by 6255731843's method on local PostgreSQL:

    Downstream:

    The dev session session_011Xuc8buBLe9f15TPKBjPKS is archived.


    Generated by Claude Code

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

Metadata

Metadata

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions