Repository navigation
perf(core,metadata-protocol): an authenticated data request pays 6 warm serial rounds instead of 8, and 3 with the landed opt-ins - #24533
Conversation
…ead are one wave Part of the #10757 engine legs (leg 4). resolveUserAuthzGrants awaited the sys_organization by-slug read, then the sys_metadata_activation read; both depend on wave 1 only. The ledger is now asked about every name that may apply while the anchor is open, so the two reads run together. Envelopes and read predicates are unchanged; the organization-less readings are called, not edited. Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
…rom retires its cache Part of the #10757 engine legs (leg 5). The localization success cache was retired by the engine write epoch, which every write to any object advances, so the two idle outbox pollers made warm requests pay the settings read again. It now registers the leg-B shape of seam: an unkeyed engine middleware that retires the bucket after a write to sys_setting or sys_platform_setting settles, plus every non-write epoch reason. The settings change seam, the TTL and the TTL-only failure memo are unchanged. Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
…te verb, as the engine does Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
…s through the overlay cache Part of the #10757 engine legs (leg 6). The REST API-exposure gate reads the object by name on every /data request, and getMetaItem's active-row read was the one sys_metadata round trip #11967's overlay cache did not cover. The served face now reads those rows through the same cache (pre-read epoch, TTL, clone both ways) under a key naming the item; the protocol's own authoring reads keep reading the store. Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
…y cache Part of the #10757 engine legs (leg 6). The overlay rows (list read and the served by-name read) are read from sys_metadata alone, but the cache was retired by the engine write epoch, which every write to any object advances. It now uses the leg-B seam: an unkeyed engine middleware that retires it after a sys_metadata write settles, plus every non-write epoch reason (the metadata bridge's 'remote' bump included). The doubles carry the middleware seam a real engine has, so the prolongation arms keep exercising a live cache. Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
…round legs Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
…by-name cache suite's findOne double Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
…e tables they read Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 31 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4ba5a2eed74f36111e6dc64494e0eeffbef6aafa && git checkout 4ba5a2eed74f36111e6dc64494e0eeffbef6aafa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ba4afcc2391922a86678bf5e47b1a0e4d2c87ff1 084e79e1659a99bb77bfe015c654c0b57f9ebfb5 && git checkout -B drift-repro ba4afcc2391922a86678bf5e47b1a0e4d2c87ff1 && git merge --no-ff 084e79e1659a99bb77bfe015c654c0b57f9ebfb5
node scripts/docs-audit/affected-docs.mjs --json ba4afcc2391922a86678bf5e47b1a0e4d2c87ff1
|
Contract reviewServed-tier: Inputs: card #10757 (body and all 17 comments, the ruling 6250819728, the reading 6255731843, the release 6255757782, the retriage 6256237052, the claim 6264940865, the dispatch 6264971259, the leg-A note 6265900988, the ① Derived judgmentsLeg 5 — the localization cache's retirement set is exactly the set of writes that can change its answer. Right.
Leg 6 — the overlay cache's retirement set. Right, with one residual window named below (TTL-bounded, not a bypass, and narrower than before).
Leg 6's by-name coverage — byte-identical for every caller. Right. Leg 4 — the merged wave. Right; the envelope and the C8 regions are byte-identical.
The cold leg — the The duplicate The re-reading against the retriage's target. Done-when met; ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
CI note ·
Generated by Claude Code |
…ine, as every production writer does The served by-name read is cached and retired by the engine's sys_metadata write seam; a row put straight into the driver after the control read was a write no production path makes. The docs name the statement-to-commit window of that retirement, and the rest overlay-read pin's header says why nothing is kept on its seam-less engine. Claude-Session: https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS Co-authored-by: Claude <noreply@anthropic.com>
Delta note on record 6268067889Head: This note covers one commit on top of the record's head ( Inputs:
The red,
The sweep: the dev covered tests that write The record's two notes:
PR body: the seat patches in a fix-round bullet under Verification and the transaction-window bullet under Acceptance notes, using the dev's wording from 6269271222. Full suites at this head, from the dev, each exit 0:
The Reviewed-by: VERDICT: PASS (record 6268067889 carries) Generated by Claude Code |
席位核对 ·
|
| Item | Reading |
|---|---|
| Shape | Draft PR → main. The body opens Fixes #10757; leg A was ruled out at 6265874149 (#24515 closed not_planned). 15 files, +1243/−220. Not governed: check-governed-merges on the final file list matched 0 of 15 paths. |
| Scope | Leg 4: the activation-ledger read joins the first wave, one round fewer per authenticated request. Leg 5: the localization cache is retired only by writes to sys_setting or sys_platform_setting, plus every non-write epoch bump. The failure entry keeps its TTL-only rule. Leg 6: the served by-name getMetaItem read, which the REST exposure gate makes on every /data request, takes its stored rows from the overlay cache. Only sys_metadata writes retire that cache. The cold double resolution: ruled out; the attachedHere guard is load-bearing. The duplicate sys_user read: ruled out to plugin-auth; it saves a query, not a round. |
| Reading | By 6255731843's method on local PostgreSQL: with the landed opt-ins on, 3 rounds for both personas; stock, 11 queries / 6 rounds admin and 10 / 6 member. The served bodies and the resolved authz envelopes are byte-identical before and after. |
| Review | CONTRACT_REVIEW_TIER record 6268067889 PASS on 4746b38c1. CI note 6268090803 (Test Core (5/6)). Delta note 6269310900 on this head: the record carries. |
| Declaration | Clause-②: no on the claim, the PR body and both changesets (@objectstack/core patch, @objectstack/metadata-protocol patch). |
| Cross-lane | epic:#15194: territory declaration 6264887348. The C8 regions are byte-identical and pinned (record ①). The thread has two later rulings and no objection, which meets the declaration's own condition. domain:devx: 6267689991 and 6267701042. domain:spec: 6267709075 and 6267716787. domain:cli: 6269996668 and 6270008721, for the fix round's one comment-only edit to a packages/rest test header. No objection on any of them, or on the card. |
| Gates | Dev: 111 derived families over both rounds, 111 run, every exit 0. Core test:repo 82/82. Full suites at this head, each exit 0: objectql, runtime, rest, plugin-security, service-settings, plugin-hono-server and mcp. Core and metadata-protocol stand from the first round. |
| CI | At this read on 084e79e165: ci-failure GREEN, all 41 check-runs completed, none failed. Mergeable, clean. |
Disposition:
open_questions: all accepted, per record 6268067889 ③:- the cold prime is left as it is;
- the duplicate
sys_userread has no carrier; - the list read keeps its narrowed retirement.
deviations: all accepted (record 6268067889 ③ and delta 6269310900):- the fence widened to the list read's retirement, the two docs pages and the ledger row;
- the measurement-only dist instrumentation, reverted blob-equal;
- the first families measured at the merge commit;
- "statement completes" in place of the record's "commits", which is the more exact statement.
out_of_scope_findings: none filed; none meets a filing gate (record ③). The transaction window before asys_metadatapublish commits is stated in both docs pages and the PR's acceptance notes. It is bounded by the TTL and narrower than the epoch design it replaces.- Downstream:
- authz: every authenticated request re-reads the Default Organization (two sequential sys_organization reads) once the admin's admin_full_access grant is organization-owned #24523 (p1) moves the same
readDefaultOrganizationIdcall site; the seat claims it after this landing. - The
repo:cloud#2seat is pointed at the landing for cloud#1518 and cloud#1521, with no claim about their gate or placement.
- authz: every authenticated request re-reads the Default Organization (two sequential sys_organization reads) once the admin's admin_full_access grant is organization-owned #24523 (p1) moves the same
Landing: ordinary queue (pr_ready + automerge_enable through the relay). Fixes #10757 then closes the card.
Generated by Claude Code
Fixes #10757. This PR carries the card's engine legs (leg 4, leg 5, the cold double resolution and leg 6) under claim 6264940865; leg A is ruled out at 6265874149 (#24515 closed not_planned), so every item is landed or ruled out once this lands.
Clause-②: no (no accept set, served body or authz answer moves: fewer queries and rounds for the same answer; owes the seat's review of record before the queue, at
CONTRACT_REVIEW_TIERby the seat's choice, since it changes when authz and localization caches are trusted)What changed
An authenticated
GET /api/v1/data/OBJECT?$top=1on the stock showcase (local PostgreSQL 16) now pays fewer rounds:$count=false): 3 / 3, was 4 / 4.The served bodies and the resolved authz envelopes are unchanged.
Leg 4, landed (
packages/core/src/security/resolve-authz-context.ts). The Default Organization read (sys_organizationby slug) and the activation-ledger read (sys_metadata_activation) are now one wave. Before, they were awaited one after the other.admin_full_accessgrant that turns out not to anchor platform standing addsadmin_full_accessto the ledger'sname INlist. Nobody reads that verdict, and the envelope does not move.{ slug: 'default' }with limit 1.grantAppliesInTenantandgrantAnchorsPlatformStanding(the NULL-organization readings that the C8 lane holds, 6264887348) are called as before and not edited. A pin asserts the organization read byte-for-byte.Leg 4, the duplicate
sys_userread: ruled out here. The two reads are better-auth's session-to-user read (plugin-auth's adapter) andgetUserRowinside thecustomSessioncallback'sresolveUserAuthzGrantscall.getUserRowis already memoized within one resolution.plugin-auth. better-auth's user deliberately does not carryai_access(auth-manager.ts, theuser:config note). So one read can serve both consumers only through aplugin-authchange (domain:services); no card carries it now (carrier: none), since plugin-auth: an authenticated request readssys_sessionthensys_userin two sequential rounds, because better-auth'sadvanced.database.joinsis left unset and its fallback join runs (leg A of #10757) #24515 closednot_planned.open_questions.Leg 5, landed (the same file,
localizationCache). A success entry is now retired only by:sys_settingorsys_platform_setting, the two tablesSettingsService.getManyand the direct fallback read, observed after it settles by an unkeyed engine middleware;writeepoch reason (remote,metadata,manual);localizationsettings change seam;Before, any write to any object retired it. On an idle boot, the outbox pollers' claim updates were such writes.
resolveLocalizationContext's 30s failure cache can answer a deliberate post-bind re-read with the pre-bind value #11877) keeps its TTL-only rule. A pin proves that a watched write and aremotebump do not retire it.registerMiddleware. With TTL0nothing is registered on the engine.Cold, ruled out with its reading (
request-grants-memo.ts, theattachedHereguard incommit). The guard is load-bearing:auditorposition. With the guard removed, it is servedauditor.Leg 6, landed in
packages/metadata-protocol(this lane). Nothing changes in@objectstack/rest, so nothing is declared todomain:cli.getMetaItem, the served face thatenforceApiAccessreads on every/datarequest, now reads the item's activesys_metadatarows through authz caching leg D:getMetaItemsoverlay cache keyed on the registry epoch — resolve the SchemaRegistry-hydration trap explicitly #11967's overlay cache, under a key naming the item. The protocol's own authoring reads still read the store (the private body, e.g. the destructive-change check before an object save).sys_metadatawrite observed after it settles, by every non-writeepoch reason (the metadata bridge'sremotebump included), and by the TTL. Design: cross-request caching for the authenticated request path (tranche 2 of #10757) — write-invalidation-first, short-TTL fallback, configurable staleness window #11633 §4's leg-D design names this trigger: "any engine-seam write tosys_metadata". Before, any write to any object retired it, so the outbox pollers and every user's data write made the next/datarequest readsys_metadataagain.Docs: the
OS_LOCALIZATION_CACHE_TTL_MSandOS_METADATA_OVERLAY_CACHE_TTL_MSrows (content/docs/deployment/environment-variables.mdx) and the overlay-cache note (content/docs/concepts/metadata-lifecycle.mdx) now state the new retirement rules.Reading: local PostgreSQL 16.15, method of 6255731843
pnpm dev -- --fresh -p PORT --database postgres://…, a fresh database per boot. Each request isGET /api/v1/data/OBJECT?$top=1withX-OS-Debug-Timing: json, 2.5 s apart. Per persona (admin, andauditor.demo@example.com), three objects (sys_user_preference,showcase_task,sys_api_key), four requests each.SqlDriver.installQueryTiming, applied to the source, built intodist/and then reverted. The source is blob-equal toHEADand the dists were rebuilt clean afterwards. The admin's logged count equalsServer-Timing'sN querieson every request.PATCHof ashowcase_taskrow. That object is unrelated to localization and tosys_metadata.L= behind a +20 ms RTT proxy (switched on after boot),R= direct.main=913fd6df8a. After = this branch merged withorigin/mainf4755a68a, head4746b38c1.mainL1 / R1$count=false), warm (L2c / R2c)The warm admin request after, per query (final-L1,
sys_user_preference#2):sys_sessionby token (better-auth)sys_userby id (better-auth's fallback join, leg A)sys_user,sys_member×2,sys_user_permission_set,sys_user_positionsys_organizationby slug andsys_metadata_activation(was rounds 4 and 5)sys_metadataby name (was round 6). The localization pair is gone too, except at its 30 s TTL (the two remaining misses in each run land 30.4 s and 30.1 s after the key's first read).sys_organization.sys_session,sys_user, the SELECT.mainand 169 ms after. With the opt-ins on it is 111 ms and 79 ms.Controls (byte identity, normalized for per-boot random ids and timestamps):
org_user_ids,accessible_org_ids, posture, tab permissions) is one distinct value per persona, and it is identical onmainand after, in every pair compared (main-R1 against final-R1 and final-L1; main-L2c against final-L2c and final-R2c).showcase_taskpicks a different seeded row underorder by id asc limit 1, because seeded ids are random per boot. The titles differ; the keys and totals do not.sys_user_preferenceandsys_api_keyare identical in every pair.Pins and ablations
Each ablation mutates only this package's source, and the subject is imported by a relative specifier, so no rebuild leg applies. Each restore was checked blob-equal to
HEAD.Leg 4:
resolve-authz-context.request-rounds.test.ts(new, 5 cases). The envelope written out, the reads issued, and 2 legs.expected 3 to be 2), with the envelope and reads asserted green before it. The fourth is the recorded widening, red on the ledger read.Leg 5:
resolve-localization-cache.test.ts. Its doubles now carry the engine's middleware seam. New pins:sys_http_delivery,sys_notification_delivery,showcase_taskandsys_sessiondo not retire the entry, with zero reads;sys_platform_settingwrite retires it;metadataandremotebumps retire it;0registers nothing;Ablation: the leg-4 version of the file restored. 9 red, the in-flight case among them.
Leg 6:
protocol.item-read-overlay-cache.test.ts(new, 7 cases) and a newmeta-overlay-cache.test.ts§10.servedReadset tofalse. 4 red; the authoring-read, no-seam and TTL-0 controls stay green.meta-overlay-cache.tsandprotocol.tsrestored. 10 red.applyRemoteMetadataMutation'sbumpWriteEpochremoved. 3 red.Pin sweep:
request-grants-memo*does not move. Theresolve-authz-context.batch-equivalencegoldens do not move (no fixture holds an organization-ownedadmin_full_access).rest-data-read-one-overlay-read.test.tsdoes not move: its engine has no write-epoch seam, so the cache declines there, as before. The rest/hono localization pins pass unchanged.resolve-localization-cache.test.ts,meta-overlay-cache.test.tsandprotocol.datasource-delete-prolongation.test.ts, each to carry the seam a real engine has.scripts/engine-double-contract.pinned.jsonlearned the new suite'sfindOnedouble (--write).Verification (head
4746b38c1)pnpm --filter @objectstack/core typecheck: exit 0.pnpm --filter @objectstack/metadata-protocol typecheck: exit 0.--listFilescounts the new and edited test files in both programs.pnpm --filter @objectstack/core test: exit 0, 106 files / 2580 tests.pnpm --filter @objectstack/core test:repo: exit 0, 6 / 82.metadata-protocolvitest: exit 0, 248 files passed and 3 skipped, 20723 tests.turbo run build(all 76 packages) after mergingorigin/main: exit 0.node scripts/pm/dispatch-gates.mjs --commands(no paths) derived 109 families. Every one was run with its exit code captured before any pipe, and all exited 0 (check:engine-double-contractafter the ledger write).--ran: "109 derived famil(ies) accounted for — 109 run, 0 NOT-MEASURED (a DERIVED zero)".084e79e16), forTest Core (5/6):environment-overlay-five-types.test.tsstored its overlay row withdriver.createafter a control read that now fills the by-name overlay cache, so no engine middleware saw the write. The row now goes throughengine.insertunder a system context, as every production writer does. No cache was disabled, no TTL changed and no assertion was weakened. Before: exit 1, 10 failed. After: 26 / 26. A sweep of the tests that writesys_metadata,sys_settingorsys_platform_settingstraight into a driver found no other one that reads before it writes. Full suites at084e79e16, each exit 0: objectql 446 / 8392, runtime 422 files, rest 300 files, plugin-security 188 files, service-settings 51 / 881, plugin-hono-server 34 / 422, mcp 40 / 479. objectql and rest typecheck exit 0.--ranover both rounds: "111 derived famil(ies) accounted for — 111 run, 0 NOT-MEASURED (a DERIVED zero)".Acceptance notes
sys_userread (needs aplugin-authseed; carrier: none); the cold double resolution (a boot-time observer registration); and the COUNT unless$count=false.0(Design: cross-request caching for the authenticated request path (tranche 2 of #10757) — write-invalidation-first, short-TTL fallback, configurable staleness window #11633 Fork 4, the maintainer's), leg B still costs 2 rounds per warm request. This PR does not move it.metadata-lifecycle.mdxandenvironment-variables.mdx.🤖 Generated with Claude Code
https://claude.ai/code/session_011Xuc8buBLe9f15TPKBjPKS
Generated by Claude Code