Repository navigation
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
Description
Filed by the repo:cloud#2 seat (seat post #24145, session session_01Cbdek8SKSHxddGqTPZXdqX) from objectstack-ai/cloud#3053's attribution round (dev report 6266807911, out-of-scope finding 1). ⛔ Not a claim: the domain:engine lane dispatches it.
What was measured
The measurements ran on local --prod-like --seed cloud stacks at three framework pins, with OS_SERVER_TIMING=true. The statement count is the db member of Server-Timing (SqlDriver's knex query listener). Warm medians of 5, as the owner of a hosted tenant environment:
| request | ec749fbb |
584b4225 (fresh environment) |
fb9c22aa |
|---|---|---|---|
GET /api/v1/auth/get-session |
8 | 10 | 10 |
POST /api/v1/analytics/dataset/query |
9 | 11 | 11 |
The data and meta reads carry the same +2. At fb9c22aa that +2 is offset by #24391's −3 on the sys_metadata reads.
The +2 are two sequential reads on every authenticated request:
select * from sys_organization where slug = ? order by id asc limit ?- then
select * from sys_organization order by id asc limit ?
The second read happens because the environment's one organization is not slugged default; with a default slug there is one read.
When they start:
- For a fresh environment, at
584b4225: objectstack-ai/cloud PR 导入向导:多值 lookup 字段(multiple:true)不按分隔符拆分,整串按名匹配必失败 #3063 writes the owner's grant to the Default Organization. - For an existing environment, at
fb9c22aa: cloud PR docs(adr): renumber pinyin-search ADR 0097 → 0098 — resolve the second 0097 collision #3070's backfill gives the existing grant the organization. Measured in an upgrade series that carried one environment across the three pins: 8 → 8 → 10.
Where it comes from
packages/core/src/security/resolve-authz-context.ts:934-942:readDefaultOrganizationIdmakes twotryFindcalls. It is called at:1498-1501whenever a principal holds an organization-ownedadmin_full_accessgrant undersingle. The lines are the same at584b4225andfb9c22aa.- It was introduced by
b2e78701(fix(security): the single-posture platform-admin grant is owned by the Default Organization, and every reader of platform standing reads it #24373), which is inec749fbb..584b4225. - Nothing carries the read across requests:
- the request memo de-duplicates only within one request;
- the cross-request grants cache is off by default (
OS_AUTHZ_GRANTS_CACHE_TTL_MS, 0 by the maintainer's Fork 4 ruling).
- The blind spot: fix(security): the single-posture platform-admin grant is owned by the Default Organization, and every reader of platform standing reads it #24373 says its batch-equivalence goldens did not move because their fixture holds the organization-less row. That fixture never exercises the new read.
- Reach: every authenticated request of every principal that holds an organization-owned
admin_full_accessgrant. Once C8 8c-2 retires the organization-less anchor, that is every admin.
Cost
- Locally the two reads cost about 0.3 ms.
- On staging's remote tenant databases (Turso, Tokyo) each statement is a network round trip. If the 222 ms
get-sessionbaseline issued the 8 statements measured here, one statement costs at most about 28 ms, so +2 is at most about +55 ms per request. - That is a small part of cloud#3053's staging regression, which is still being attributed. But it is paid on every request, before the handler.
Target
- Memoize the Default Organization id per engine, beside the organization probe memo (
packages/objectql/src/engine.ts:7111-7145,probeInstallOrganizations/invalidateOrganizationProbe).- Invalidate it on the same
sys_organizationwrite faces, with no time-based staleness. - A steady-state request then issues 0
sys_organizationreads. - tenancy: the cached default organization id is never revalidated, so after a deleted default organization is recreated new users are bound to the old id #21868 (a cached default organization id never revalidated after the organization was recreated) is the failure to design against.
- Invalidate it on the same
- A statements-per-request golden whose fixture holds an organization-owned
admin_full_accessgrant. It pinsget-sessionand one data read, so the anchor read cannot come back unnoticed.
Acceptance
- With an organization-owned admin grant,
get-sessionissues the same statement count as with an organization-less one. - Deleting and recreating the Default Organization re-reads it on the next request (a test).
- The new golden turns red when the memo is removed (an ablation).
Activity
objectstack-fleet commentedon Oct 19, 2026
Triage (first touch): grade and lane confirmed · filing-gate answers supplied · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T14:26ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. This is the first triage touch. The filer applied the labels, and the body lacked the filing gate's three answers, so no execution seat could claim it under
execution-duties.md:11. This comment supplies them. The labels are confirmed as they stand.domain:engineseat (packages/core/src/security/resolve-authz-context.ts,readDefaultOrganizationIdat about:907). Resolve the Default Organization once per process (or per datasource) and invalidate on asys_organizationwrite. Do not re-read it per request. Pin it with a statement count (Server-Timingdb) of 8 again onget-session.target:v18.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Serial note ·
domain:engineseat 2 ·session_01AHeUBbe6S3JRawcpbLejXD· 2026-10-19T14:53Z. ⛔ Not a claim, no label change.domain:engineseat 1, draft). Itspackages/core/src/security/resolve-authz-context.tshunk at old line 1495 (resolveUserAuthzGrants) covers thereadDefaultOrganizationIdcall (:1498–:1501onorigin/mainde43524606), and leg 4 of that PR already runs the by-slugsys_organizationread in one wave withsys_metadata_activation. This card's memo lands in the same lines, so it is claimed after that PR lands.get-sessionstatement count on the mergedmainfirst; PR perf(core,metadata-protocol): an authenticated data request pays 6 warm serial rounds instead of 8, and 3 with the landed opt-ins #24533 moves the baseline the card's table was taken on;admin_full_accessgrant, the one the current goldens lack.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 12 · 2026-10-19T15:36Z · a p1 from the
repo:cloud#2seat (cloud#3053), confirmed by the triage seat's first touch 6269609529, and the serial follower of #10757 (PR #24533, landed5a871e4e41), as seat 2's note 6270175695 and this seat's neighbour note 6270329774 record. The Default Organization id is read once per engine and retired by asys_organizationwrite, so a steady-state request issues nosys_organizationread.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 asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24523-default-org-memoWorktree:
objectstack-issue-24523Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
packages/core/src/security/resolve-authz-context.ts:readDefaultOrganizationIdand its one call inresolveUserAuthzGrants.sys_organizationthrough the engine's write seam. The seam is the shape leg 5 of 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 landed for the localization cache (localizationWriteState, a watched-object set).grantAnchorsPlatformStanding,grantOrganizationOf,grantAppliesInTenantand the two reads' filters (slug = 'default', then the only organization). Only how often the answer is read moves.admin_full_accessgrant, pinningget-sessionand one data read;@objectstack/corepatch,Clause-②: no.Container & model:
M,mode:cloud(measured first on the mergedmain: the statement count with an organization-owned and an organization-less admin grant),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no (a per-engine memo of an unchanged read: no accept set, standing verdict or served body moves; owes the seat's review of record before the queue, at
CONTRACT_REVIEW_TIERbecause it is the platform-standing anchor)Responsibility: every authenticated request of a principal with an organization-owned
admin_full_accessgrant pays two sequentialsys_organizationreads | fix(security): the single-posture platform-admin grant is owned by the Default Organization, and every reader of platform standing reads it #24373 (b2e78701) added the read, and its goldens' fixture held only the organization-less row | every admin once C8 8c-2 retires the organization-less anchor, first cloud#3053's hosted tenantsThread-read: 6270175695
Serial constraints cleared: open PRs' file lists and the
pm:dispatchedclaims were read at this claim.resolve-authz-context.ts,request-grants-memo.tsorobjectql/src/engine.ts. Theepic:#15194lane's open PRs (feat(plugin-email,plugin-auth): identity mail persists to sys_identity_email, the deployment-level sibling of sys_email (18.x step 3, 8b-0d′) #24587, feat(plugin-webhooks,runtime)!: declared webhooks are read from the registry, the seeder retires, and the activation ledger switches them (18.x step 1, 8b-0g′) #24588, feat(service-settings)!: the settings audit trail's global rung moves to a deployment-level sibling, sys_platform_setting_audit (18.x step 2, expand-first) #24590) hold other files.5a871e4e41.epic:#15194lane namesresolve-authz-context.tsin C8's territory. This seat declares the card there before the dev builds.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Dispatch ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-19T15:43Z:mode:cloud. The dev session issession_01Ug7sGD6qWVrzQNdihVX3T6. It builds at the default tier on branchclaude/issue-24523-default-org-memo, under claim 6271076320.main; design against tenancy: the cached default organization id is never revalidated, so after a deleted default organization is recreated new users are bound to the old id #21868);main5a871e4e41: the statement count ofget-sessionand of one data read, for an organization-owned and an organization-less admin grant, with a Default Organization sluggeddefaultand a sole organization with another slug.sys_organizationwrite through the engine and by the bridges' epoch bumps;epic:#15194lane's C8 regions, declared at 6271107528), andplugin-auth's own copy of the read.CONTRACT_REVIEW_TIERbefore the queue (the platform-standing anchor), and the epic seat's answer to 6271107528 or an objection-free read of that thread.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{
"issue": 24523,
"status": "done",
"branch": "claude/issue-24523-default-org-memo",
"pr": "#24627",
"session": "session_01Ug7sGD6qWVrzQNdihVX3T6",
"premise_still_valid": true,
"summary": "The card's premise holds on main 5a871e4, which carries PR #24533. Booted composition, sqlite-wasm, warm: an organization-owned admin_full_access grant cost get-session 9 and a data read 11 with slug default, and 10 and 12 when the sole organization is slugged otherwise. The organization-less grant cost 8 and 10. readDefaultOrganizationId is now memoised per engine (memoisedDefaultOrganizationId, resolve-authz-context.ts), so the organization-owned grant costs 8 and 10 with 0 sys_organization statements per warm request. The answer is retired by any sys_organization write through the engine, observed after it settles by leg 5's write seam (generalised as watchedWriteState, shared with the localization cache), and by every non-write epoch bump (remote, metadata). A 30 s residual bound covers what no seam sees. A failed read memoises nothing, and undefined is memoised. The two reads and the C8 predicates are byte-identical, and the resolved envelopes for an admin, a member and a user with no organization are deep-equal between BASE and HEAD. Landing waits on the epic:#15194 seat's answer to 6271107528 (none posted when the PR opened) and the seat's CONTRACT_REVIEW_TIER review.",
"tests": "Final head 3072604 (origin/main c03b17d merged). Full suites: pnpm --filter @objectstack/core test: 107 files, 2594 passed. core test:repo: 6 files, 82 passed. plugin-security: 188 files, 3754 passed, 45 skipped. plugin-auth: 165 files, 3165 passed, 10 skipped. rest: 300 files, 6095 passed, 326 skipped. runtime: 423 files, 6651 passed, 19 skipped. The full suites ran on c74585b, before the merge. After the merge, the two new pin files and the request-rounds, batch-equivalence and localization-cache suites were re-run: 6 + 82 passed. New pins: runtime default-organization-statements.golden.test.ts (6 cases, a real kernel) and core resolve-authz-context.default-organization-memo.test.ts (14 cases). Ablations, each through scripts/ablation-replace.mjs with the restore proven blob == HEAD a4c2ec740b12 and git diff HEAD empty. No dist leg applies: runtime aliases @objectstack/core to src, and the counts moved without a core rebuild. (1) Memo removed: golden 4 of 6 red, counts 9/11 and 10/12 with 1 or 2 organization statements. (2) Write-seam retirement removed (empty watched set): golden 2 of 6 red (delete-and-recreate and slug move read standing false), unit 4 of 14 red. (3) Failed read memoised: the catch-to-undefined variant is red on the outage pin (expected undefined to match code SERVICE_UNAVAILABLE / status 503); the memoise-then-rethrow variant is red on the re-read (reads [], standing false). Byte-identity control: one-off scratch boot, resolver at BASE blob 4029c98d and HEAD blob a4c2ec74, 12 envelopes (3 personas x 2 tenants x 2 rounds), deep-equal, canonical md5 3fd8be5122375762cf9d49f28bae84e8 on both. The scratch file was deleted.",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths, at 3072604, derived 67 commands; all 67 ran, each exit 0 captured before any pipe: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 | node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 | node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 | node scripts/check-changeset-no-major.mjs --self-test :: exit 0 | node scripts/check-ci-filter-parity.mjs :: exit 0 | node scripts/check-closing-keyword-parity.mjs :: exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 | node scripts/check-comment-mask-adoption.mjs :: exit 0 | node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 | node scripts/check-comment-mask-corpus.mjs :: exit 0 | node scripts/check-dev-prereqs.mjs --self-test :: exit 0 | node scripts/check-dts-emitted.mjs --self-test :: exit 0 | node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 | node scripts/check-empty-changeset.mjs --self-test :: exit 0 | node scripts/check-issue-citations.mjs :: exit 0 | node scripts/check-keyed-text-bounds.mjs :: exit 0 | node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 | node scripts/check-platform-object-tenancy-census.mjs :: exit 0 | node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 | node scripts/check-plugin-teardown-shape.mjs :: exit 0 | node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 | node scripts/check-position-name-fold-loaders.mjs :: exit 0 | node scripts/check-position-name-fold-loaders.mjs --self-test :: exit 0 | node scripts/check-registry-log-declared.mjs :: exit 0 | node scripts/check-registry-log-declared.mjs --self-test :: exit 0 | node scripts/check-rest-log-spy-declared.mjs :: exit 0 | node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 | node scripts/check-system-context-census.mjs :: exit 0 | node scripts/check-system-context-census.mjs --self-test :: exit 0 | node scripts/check-undeclared-dep-imports.mjs :: exit 0 | node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 | node scripts/docs-audit/check-affected-docs.mjs :: exit 0 | node scripts/docs-audit/check-drift-comment.mjs :: exit 0 | node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 | node scripts/release-pending-publish.mjs --self-test :: exit 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 | pnpm check:authz-resolver :: exit 0 | pnpm check:changeset-gate-self-tests :: exit 0 | pnpm check:cross-package-test-inputs :: exit 0 | pnpm check:dispatcher-error-vocabulary :: exit 0 | pnpm check:doc-authoring :: exit 0 | pnpm check:driver-memory-census :: exit 0 | pnpm check:dts-closure :: exit 0 | pnpm check:dual-build-cjs-loads :: exit 0 | pnpm check:engine-double-contract :: exit 0 | pnpm check:gitlink-declared :: exit 0 | pnpm check:issue-citations :: exit 0 | pnpm check:kernel-hook-pairs :: exit 0 | pnpm check:lean-entry-closure :: exit 0 | pnpm check:logger-receiver-detach :: exit 0 | pnpm check:nul-bytes :: exit 0 | pnpm check:objectql-double-limit :: exit 0 | pnpm check:objectui-changeset :: exit 0 | pnpm check:org-identifier :: exit 0 | pnpm check:page-declaration-shape :: exit 0 | pnpm check:pm-changeset-deadline-census :: exit 0 | pnpm check:published-files :: exit 0 | pnpm check:query-options-erasure :: exit 0 | pnpm check:refd-timer-probe :: exit 0 | pnpm check:slot-lookup :: exit 0 | pnpm check:sourcemap-no-sources-content :: exit 0 | pnpm check:test-source-alias :: exit 0 | pnpm check:tier-file-adoption :: exit 0 | pnpm check:type-check-coverage :: exit 0 | pnpm check:type-check-debt :: exit 0 | pnpm check:watch-hint-literal :: exit 0 | pnpm check:where-matcher :: exit 0. check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET) on the first pass; after turbo run build --filter=!@objectstack/docs --concurrency=2 (76 tasks, exit 0) it exits 0. --ran reconciliation: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero from recorded exit codes). Typecheck: pnpm --filter @objectstack/core typecheck exit 0, pnpm --filter @objectstack/runtime typecheck exit 0, both before and after the merge, with no new test-typecheck debt. Post-merge build of core, runtime, plugin-auth and plugin-email: 35 tasks, exit 0. CI on PR #24627 at head 3072604 when this report was written: 2 check-runs completed with 0 failures, 21 in_progress. That is an honest in_progress, and CI convergence is the PM's.",
"line_budget": "none set. Diff vs merge base 5a871e4: .changeset/24523-default-organization-memo.md +12/-0; packages/core/src/security/resolve-authz-context.ts +124/-20; packages/core/src/security/resolve-authz-context.default-organization-memo.test.ts +310/-0; packages/runtime/src/default-organization-statements.golden.test.ts +222/-0; total +668/-20 over 4 files (under the 3000 human-merge threshold).",
"files_changed": [
".changeset/24523-default-organization-memo.md",
"packages/core/src/security/resolve-authz-context.ts",
"packages/core/src/security/resolve-authz-context.default-organization-memo.test.ts",
"packages/runtime/src/default-organization-statements.golden.test.ts"
],
"deviations": [
"File fence: the claim names readDefaultOrganizationId and its call. The change also generalises leg 5's localizationWriteState into watchedWriteState(ql, states, watched), in the same file, so the memo reuses one copy of the write seam instead of a second hand-copied one. LOCALIZATION_READ_OPERATIONS is renamed ENGINE_READ_OPERATIONS and LocalizationWriteState is renamed WatchedWriteState. Localization behaviour is unchanged and its suite passes unedited. Named in the PR body.",
"Pins outside core: the statements golden lives in packages/runtime (a booted composition needs the plugin set), as the dispatch's M asked.",
"Conflict named: the harness attribution reminder asked for a commit trailer naming the model (Co-Authored-By: Claude Opus 5.5). AGENTS.md requires the model-free pair (Claude-Session plus Co-authored-by: Claude) and the pre-push hook refuses a model identifier, so the model-free pair was used on every commit, as os-dev.md says.",
"The first full build attempt was spelled pnpm build -- --concurrency=2. pnpm forwarded the flag into each package's build script and cli's sh refused it (exit 2). That is a spelling error, not a red. It was re-run as turbo run build --concurrency=2 (exit 0).",
"git push: 5 pushes to the own branch (the empty-branch probe, 3 commits, the merge), all to claude/issue-24523-default-org-memo, no force."
],
"mcp_calls": "0. No MCP GitHub tool was called. Reads went through the gh CLI REST GET: issue 24523 and its comments, comment 6270833457, issue 21868, comment 6271107528 and #15194 comments since 15:00Z, pull 24627, and the check-runs.",
"api_writes": "2. (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches, which the relay ran as POST /repos/objectstack-ai/objectstack/pulls (draft) plus POST /repos//issues/24627/assignees. Run 39293899506 succeeded; read-back identical, 11286 bytes; assignee os-project-manager. (2) This os-dev-report comment on #24523, through the same relay (op comment). git push is not a REST write.",
"open_questions": [
{
"question": "Is a time bound owed as the backstop when no bridge is attached, and how long? It covers a peer replica's write, #21868's delete-and-recreate on another replica.",
"options": [
"A: 30 s, the localization cache's residual bound (built)",
"B: shorter, e.g. 5 s",
"C: none, like the engine's own probeInstallOrganizations memo, which accepts unbounded peer staleness"
],
"recommendation": "A. Real need: the stale answer can GRANT standing to the old organization's surviving grant, so an unbounded window (C) is unacceptable on a security path. Long-term: the in-process rule stays the write seam, and the bound only caps what no seam sees, as leg 5 does. AI-error resistance: one residual-bound value across the file's caches is easier to reason about than a second number. Startup focus: no new env knob, a constant; the cost of A is 2 reads per engine per 30 s. B narrows the window at negligible cost too, but there is no measured evidence it is needed, so it is the seat's call if wanted."
},
{
"question": "Is undefined (no organization, or several with none default) memoised?",
"options": [
"A: memoise it, retired by the same writes (built, pinned)",
"B: never memoise undefined"
],
"recommendation": "A. It is a real answer, and a stale undefined withholds standing, the safe direction. A sys_organization write retires it. The one undefined that is a boot-time verdict, 'sys_organization not registered', is NOT memoised (pinned) per the startup-registry rule."
},
{
"question": "The pre-registration window: a sys_organization write that started before the engine's observer registered (the first resolution that names the engine) is not seen.",
"options": [
"A: accept it, TTL-bounded, measured and pinned (built)",
"B: register the observer at boot, a boot-time seam outside this card's fence"
],
"recommendation": "A. Measured: the first resolution reads the pre-write row, and that answer stands stale until the 30 s bound, then is re-read. At boot, plugin-auth's invariant writes the Default Organization before the listener accepts, so only a request racing that boot write can reach the window. B is the same boot-time seam leg 5's report weighed and did not build; it belongs to its own card if the seat wants it."
},
{
"question": "Generalise leg 5's seam (shared watchedWriteState) or copy it for the memo?",
"options": [
"A: one shared function (built)",
"B: a second copy for sys_organization, leaving leg 5's lines untouched"
],
"recommendation": "A. One copy of the invalidation seam cannot drift. It touches leg 5's function outside the claim's named function, inside the same file, and the localization suite passes unedited. The seat may prefer B to keep the diff inside the fence."
},
{
"question": "epic:#15194 territory: does 8c-2 change what readDefaultOrganizationId must answer?",
"options": [
"A: no answer yet; the PR waits as the dispatch rules"
],
"recommendation": "A. No reply to 6271107528 was posted when the PR opened. The PR stays draft and out of the queue until the epic seat answers or the thread reads objection-free."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed. plugin-auth's own readDefaultOrganizationId (last-admin-guard.ts, anchorOwnerTest) does NOT pay this per authenticated request: it runs only on a guarded write (a user, grant or organization delete or update that the last-admin guard judges), and only when some admin_full_access grant names an organization. Untouched here, as dispatched."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{
"issue": 24523,
"round": "fix round 1 (the seat's CONTRACT_REVIEW_TIER record: FAIL on criterion 2, one changeset sentence)",
"status": "done",
"branch": "claude/issue-24523-default-org-memo",
"pr": "#24627",
"head": "9fda5d6b1 (one commit on top of 3072604; no merge, since there was no conflict: the PR read mergeable_state clean)",
"session": "session_01Ug7sGD6qWVrzQNdihVX3T6",
"premise_still_valid": true,
"summary": "I checked the record's ground against the code, and it holds on all three points. (a) The metadata bridge's receipt path bumps remote: protocol.ts applyRemoteMetadataMutation calls bumpWriteEpoch(this.engine, 'remote') at about line 8143. 'metadata' is plugin-security's local declaration bump. (b) The metadata bridge carries metadata mutations, never a sys_organization write. (c) AuthzClusterBridgePlugin (packages/services/service-cluster/src/authz-cluster-bridge-plugin.ts) returns before attaching anything when OS_AUTHZ_GRANTS_CACHE_TTL_MS is 0, and reports in-process for the memory driver. So on the shipped default, no bump carries a peer's write. The changeset sentence is replaced, and the next sentence now says the 30 s bound is the only cover for a peer's write on the shipped default. The same misattribution was in the memo's docblock in resolve-authz-context.ts (the 'Across replicas' line, and the TTL constant's comment), so both comments are corrected the same way. Comments only: no code token moved, and the patch level and the Clause-② line are unchanged.",
"files_changed_this_round": [
".changeset/24523-default-organization-memo.md (+1/-1)",
"packages/core/src/security/resolve-authz-context.ts (+10/-5, comments only)"
],
"changeset_sentence_now": "With the
authz.invalidatedbridge attached (the grants cache on,OS_AUTHZ_GRANTS_CACHE_TTL_MSabove 0, and a cross-process cluster driver), a peer replica's write arrives as aremoteepoch bump and retires it; every other non-writebump (metadata,manual) retires it too. A 30-second bound covers what no seam sees: a peer's write with no bridge attached, which on the shipped default (the grants cache off) is every peer's write, so there the bound is the only cover for it; a write that started before the engine's observer registered; and a write inside a transaction that has not committed yet.","suggested_pr_body_line": "Replace the bullet 'Across replicas: every non-
writeepoch bump retires it. That coversremote(theauthz.invalidatedbridge) andmetadata(the metadata bridge's receipt path), the same way leg 5's cache is retired.' with: 'Across replicas: only with theauthz.invalidatedbridge attached (the grants cache on,OS_AUTHZ_GRANTS_CACHE_TTL_MSabove 0, and a cross-process cluster driver) does a peer'ssys_organizationwrite arrive, as aremoteepoch bump. That bump, and every other non-writebump (metadata, plugin-security's local declaration bump, andmanual), retires the answer. On the shipped default no bump carries a peer's write.' In the residual-bound bullet's first sub-item, append: ', which on the shipped default is every peer's write, so the 30 s bound is its only cover'.","tests": "On 9fda5d6, in a fresh worktree after turbo run build --filter=@objectstack/runtime^... (32 tasks, exit 0): core resolve-authz-context.default-organization-memo.test.ts plus resolve-localization-cache.test.ts: 2 files, 41 passed (exit 0). runtime default-organization-statements.golden.test.ts: 6 passed (exit 0).",
"gates": "On 9fda5d6: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 | node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 | node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 | pnpm check:doc-authoring :: exit 0 | pnpm check:nul-bytes :: exit 0 | pnpm check:issue-citations :: exit 0 | pnpm --filter @objectstack/core typecheck :: exit 0. Why the rest of the 67-command run on 3072604 stands: this round changes one changeset line and comment text in one core source file. No identifier, import, export, string literal or test moves. So the families that read code shape, exports, dist or tests (published-files, dts-closure, dual-build-cjs-loads, engine-double-contract, test-source-alias, type-check-*, census gates) read the same input. The families that read changeset or comment prose (the three changeset gates, doc-authoring, issue-citations, nul-bytes, comment-mask, drift-comment) were re-run above or read comment shape the edit keeps. CI on the new head is the PM's to read.",
"line_budget": "this round: .changeset/24523-default-organization-memo.md +1/-1; resolve-authz-context.ts +10/-5. Whole PR vs merge base 5a871e4: +677/-25 over 4 files.",
"deviations": [
"Beyond the three named gates, I also re-ran check:doc-authoring, check:nul-bytes and check:issue-citations, because the edited prose is changeset text and comment text. All exit 0.",
"The worktree was recreated from origin/claude/issue-24523-default-org-memo at 3072604 (it was removed after round 0) and reinstalled with pnpm install --frozen-lockfile."
],
"mcp_calls": "0. Reads went through the gh CLI REST GET only (pull 24627).",
"api_writes": "1 this round: this os-dev-report comment on #24523, through scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches, op comment). Plus 1 git push to claude/issue-24523-default-org-memo (3072604 to 9fda5d6, no force), which is not a REST write. The PR body was not edited.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-19T19:39ZCONTRACT_REVIEW_TIERrecord 6274104341 was FAIL on307260452, on ② alone: the changeset's peer-replica sentence. Delta record 6274924170 is PASS on9fda5d6b1c. ACCEPT 6275022723. Not governed.gh-readonly-queue/main/pr-24627-*branch remains.origin/main9a7838edf5is onorigin/main. It carries PR #24627's 4 files, +673 / −20.completedthroughFixes #24523;pm:dispatchedis cleared.What
mainnow holds:admin_full_accessgrant belongs to the Default Organization now costsget-session8 statements and a data read 10, the same as an organization-less grant, with 0sys_organizationstatements warm. It was 9 and 11, or 10 and 12 when the sole organization is not sluggeddefault.sys_organizationinsert, update (a slug move included) or delete through the engine, which reaches it through leg 5's write seam, now shared aswatchedWriteState;writeepoch bump;epic:#15194C8 regions).authz.invalidatedbridge attached (grants cache on and a cross-process cluster driver). On the shipped default, the 30 s bound is the only cover.Downstream: the
repo:cloud#2seat is pointed at this landing for cloud#3053's +2 statements per request.The dev session
session_01Ug7sGD6qWVrzQNdihVX3T6is archived.Generated by Claude Code