You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
core: the authz prelude reads sys_user_permission_set with no projection, so every formula on it runs per request; PR #24885's one-hop display title would add a sys_user read and a round trip for each principal with a direct grant #24896
① — a measured cost on the authorization hot path, with a named landing point. It blocks PR #24885 (#24775).
Filed by the domain:services PM seat, seat 1 (seat post #6021, session_011TdZ8GjixNZFLz1xJD9zzK), from the os-dev report on #24775 (open question 2). ⛔ Unlabelled and ungraded: grading and routing belong to triage. packages/core is domain:engine's.
Reader, and what they do. Triage grades and routes this card. The engine lane projects the prelude's grant read.
The cost, measured
Where.packages/core/src/security/resolve-authz-context.ts ~:1457: tryFind(ql, 'sys_user_permission_set', { user_id: userId }, 100). The read names no projection, so it computes every formula field of sys_user_permission_set, display_title included.
Measured on the rig of auth-prelude-query-count.pin.test.ts, comparing the old formula with the new:
a principal with no direct grant: 8 reads / 4 round trips both ways;
a principal with one direct grant (an organization admin, a platform admin): 8 / 4 before, 9 / 5 after.
Why no pin caught it. The committed pin measures the no-grant principal, so it stays green and cannot see this.
Cache. The grants cache is off by default (OS_AUTHZ_GRANTS_CACHE_TTL_MS=0).
Done when
The prelude's grant read projects the columns its resolver reads. Which columns that is, is this lane's to measure: a wrong projection would drop a column a security decision reads.
auth-prelude-query-count.pin.test.ts gains a grant-holder case. Its read count is unchanged by a display formula on sys_user_permission_set.
Claim: PM loop round 17 · 2026-10-20T16:08Z · p2, domain:engine (triage's grade), filed by domain:services seat 1 from #24775's dev report; it blocks PR #24885. The authorization prelude reads sys_user_permission_set with no projection, so every formula field on it is computed in every request's prelude. PR #24885's one-hop display_title would add a sys_user read and a round trip for each principal holding a direct grant.
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-24896-prelude-grant-projection
Worktree: objectstack-issue-24896
Domain: domain:engine
Seat: domain:engine#1 (seat post #6367)
File surface:
Measured first:
every column of a sys_user_permission_set row that the prelude reads, through isGrantActive, grantSetNameOf, grantOrganizationOf, nextGrantValidityBoundary and every other consumer of upsRowsAll / upsRows;
packages/core/src/security/resolve-authz-context.ts: the prelude's sys_user_permission_set read projects exactly the columns measured, through the module-private tryFind.
Pins:
the resolved authz context of a grant holder is identical with and without the projection: an active grant, an expired one, an organization-scoped one, a deployment-wide one, and an admin_full_access grant;
auth-prelude-query-count.pin.test.ts gains a grant-holder case, whose read and round-trip counts do not move under a display formula on sys_user_permission_set.
the prelude's other reads (sys_member, sys_user_position): measured and named in the report if they carry the same cost, not built;
the grants cache's default.
Cross-lane:auth-prelude-query-count.pin.test.ts is in packages/plugins/plugin-auth, which is domain:services'. That lane's seat 1 filed the card and names the pin in its "Done when"; the seat declares the test edit to it before the queue.
Container & model: M, mode:cloud (one projection on a security read, an equivalence pin across grant states, and a query-count pin), model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier: "no path-derived mandate")
Clause-②: no (a read's projection: every resolved authz answer is unchanged and pinned so; no accept set, served shape or export moves)
Responsibility: every request by a principal holding a direct permission-set grant pays for formula fields its authorization never reads, and PR #24885 would add a read and a round trip to that | the prelude's grant read names no projection | every principal with a direct grant (organization and platform administrators among them), on every request
Thread-read: none
Serial constraints cleared: open PRs' file lists and the pm:dispatched claims were read at this claim.
No open PR touches resolve-authz-context.ts or auth-prelude-query-count.pin.test.ts.
Dispatch · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-20T16:10Z: mode:cloud. The dev session is session_01BQSBMNgcScNf9TzF8W6YpU. It builds at the default tier on branch claude/issue-24896-prelude-grant-projection, under claim 6295406119.
Read first:
every column of a grant row the prelude reads, each with the line that reads it;
whether an objectql fields projection stops a formula the projection does not name, and its lookup hop;
the prelude's sys_user_permission_set read projects exactly those columns, through the module-private tryFind;
an equivalence pin: a grant holder's whole resolved authz context is identical with and without the projection, across active, expired, organization-scoped, deployment-wide and admin_full_access grants;
a grant-holder case in plugin-auth's auth-prelude-query-count.pin.test.ts, whose counts a one-hop display formula cannot move;
an ablation and a patch changeset for @objectstack/core.
os-dev-report
{
"issue": 24896,
"status": "done",
"branch": "claude/issue-24896-prelude-grant-projection",
"pr": "#24941",
"session": "session_01BQSBMNgcScNf9TzF8W6YpU — this run's own cloud session; dispatched by session_01JtSQQ3EWsbNPzyYMug1tXy (domain:engine seat 1, seat post #6367)",
"premise_still_valid": true,
"summary": "Premise confirmed on main 74fcec1: the prelude's sys_user_permission_set read named no projection. With PR #24885's one-hop title in the test fixture, a grant holder measured 9 reads / 5 round trips, against 8 / 4 with the declared title. The read now projects exactly permission_set, organization_id, valid_from and valid_until, through the module-private tryFind (new optional fields parameter, no export). Under the one-hop title the grant holder stays at 8 / 4. Column list measured before the edit: permission_set via grantSetNameOf (anchorReadsDefaultOrganization, mayApply, ledger names, anchorsPlatformStanding, 6b set loop); organization_id (helper also accepts organizationId) via grantOrganizationOf (grantAppliesInTenant, grantAnchorsPlatformStanding, mayApply, applyingGrants); valid_from and valid_until (helpers also accept validFrom/validUntil) via isGrantActive (upsRows filter) and nextGrantValidityBoundary (grants-cache boundary over upsRowsAll). Nothing reads id, user_id (it is only the filter), granted_by, reason or display_title. objectql: planFormulaProjection evaluates only the formulas a non-empty fields list names, so a list of stored columns computes no formula and issues no hop. Per #22300, the widening happens only when a formula is IN the projection. No id or system column needs naming: driver-sql returns only the named columns and the resolver reads no id. sys_member and sys_user_position are also unprojected, but their display_title formulas read only own columns, so today they cost CEL per row and no read. Named, not built.",
"tests": "All on head 41b4792 after merging origin/main 7169612. (1) Counts on the auth-prelude-query-count rig, grant holder with one organization-owned grant, measured through a temporary stdout line that was then removed (the committed file equals the pre-measurement backup): main core with the declared title, reads=8 legs=4; main core with the one-hop title, reads=9 legs=5 (sys_user 3); projected core with the one-hop title, reads=8 legs=4. (2) New pin packages/core/src/security/resolve-authz-context.grant-projection.test.ts: 25 tests pass. Twelve grant-state fixtures (active in tenant, other org, expired, not yet valid, in window, org-null with and without tenant, Default-Org admin_full_access acting elsewhere and with no tenant, expired admin_full_access, other org's admin_full_access, all states at once) plus the 11 batch-equivalence FIXTURES. Each resolves the identical whole envelope over projected rows (named columns only, no id) and over full rows plus a computed title. Further cases pin the four-column projection, that no other read is projected, and that the fixtures discriminate (at least 5 distinct envelopes, 3 PLATFORM_ADMIN). (3) Count pin: new describe.each grant-holder case under the declared and the one-hop title, 7/7 pass on projected core. (4) Ablation, projection reverted via ablation-replace --hold with marker ABLATION_24896: preflight saw the marker in core dist index.js and index.cjs. Count pin red only on the one-hop case (sys_user 3, expected 2: 1 failed, 6 passed). Equivalence pin red on the projection case (1 failed, 24 passed). Restore: blob 6d3b1baf7150 == HEAD and git diff HEAD empty; after the rebuild, preflight --absent found the marker absent from all 14 built files and the tree clean. (5) Column sweep, each column dropped in turn: permission_set 11 failed, organization_id 11, valid_from 3, valid_until 6, envelope fixtures failing each time, every leg restored blob == HEAD. (6) Full suites, exit 0 each, run under os-verify-lock: @objectstack/core test 108 files / 2623 tests; @objectstack/core test:repo 6 / 82; @objectstack/plugin-security test 196 files, 4075 passed, 45 skipped; @objectstack/plugin-auth test 171 files, 3283 passed, 10 skipped.",
"gates": "Local: pnpm --filter @objectstack/core typecheck exit 0 and pnpm --filter @objectstack/plugin-auth typecheck exit 0, each including check:test-typecheck with no new debt. Full build: turbo run build --concurrency=2 --filter=!@objectstack/docs exit 0 (8m17s). node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 70 families against merge base 7169612. Every line was run with $? captured before any pipe; all 70 exit 0. Two were re-run: pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist) and was re-run on the finished full build, exit 0. pnpm check:lean-entry-closure first exited 1 (ERR_MODULE_NOT_FOUND on spec dist, which the concurrent full build was rewriting) and was re-run after the build, exit 0. Reconciliation: dispatch-gates --ran with every line recorded as cmd :: exit N reads 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 70 carry an exit code, none is 3). check:nul-bytes is in the 70, exit 0. CI: the PR's check-runs were still running at report time and were not awaited, per os-dev.md; the seat reads them at ACCEPT.",
"line_budget": "none set. +/- per file: packages/core/src/security/resolve-authz-context.ts +22/-2; packages/core/src/security/resolve-authz-context.grant-projection.test.ts +211/-0 (new); packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts +63/-2; .changeset/24896-prelude-grant-read-projection.md +9/-0. Total +305/-4.",
"files_changed": [
"packages/core/src/security/resolve-authz-context.ts",
"packages/core/src/security/resolve-authz-context.grant-projection.test.ts",
"packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts",
".changeset/24896-prelude-grant-read-projection.md"
],
"deviations": [
"CROSS-LANE (declared by the seat in the claim): packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts is in domain:services' package. It gains an optional direct grant and a one-hop display_title variant of the grant table on the rig, and a describe.each grant-holder case.",
"Equivalence pin substrate: core cannot import @objectstack/objectql (objectql depends on core), so the equivalence pin resolves on the batch-equivalence recording double. One double answers a projection the way driver-sql does (named columns only, no id); the other ignores it, as the unprojected read did. The real-engine leg (ObjectQL + driver-sql honouring fields and skipping the formula hop) is pinned by the plugin-auth count pin.",
"Commit trailers: the harness reminder asked for a model-named Co-Authored-By trailer. AGENTS.md and os-dev.md require the model-free pair, so the commits carry Claude-Session: and Co-authored-by: Claude (pre-push check:commit-card-trailers passed). Named as a conflict, with the binding file followed.",
"PR subscription: the platform's default is to subscribe to a created PR. os-dev.md says the dispatch contract overrides that, so this session did not subscribe to #24941.",
"Reading: #22300 was read through its landed mechanism and pin (engine.ts planFormulaProjection / withoutFormulaWidening, engine-formula-projection-trim.test.ts), not its issue thread. The threads of #24775, #24523 and #24515 were not opened; #24896's body and claim carried the measurements this card needed. PR #24885's diff was read in full.",
"Shallow clone: deepened with git fetch --shallow-since=2026-07-13 origin main, and the repository still reports shallow. check-engine-split-ratio was not in this derivation. check-plugin-teardown-shape and its --self-test were, both exit 0.",
"git push count: three pushes to the own branch (the empty-branch probe, the fix commit, then the changeset and main-merge commits). The budget names git push as one write category."
],
"mcp_calls": "5 — all reads, no write tool: mcp__github__issue_read get (#24896), mcp__github__issue_read get_comments (#24896), mcp__github__pull_request_read get_diff (#24885), mcp__github__pull_request_read get (#24941, read-back), mcp__github__issue_read get_comments (#24896, read-back of this report comment)",
"api_writes": "2 relay strokes, plus git pushes to the own branch: (1) fleet-write pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/24941/assignees [os-project-manager]; read back as landed and byte-identical (6877 bytes), assignee listed. (2) fleet-write comment, POST /repos//issues/24896/comments (this report). Plus 3 git pushes to refs/heads/claude/issue-24896-prelude-grant-projection (not REST).",
"open_questions": [
{
"question": "How tryFind takes the projection (settled; recorded).",
"options": [
"A: a positional optional fields after organizationId (taken)",
"B: convert tryFind's trailing parameters to an options object"
],
"recommendation": "A. Business need: only this one read needs a projection today (measured); B rewrites every call site for no new reader. Long-term: tryFind is module-private, so either shape can change later at no cost to callers. AI error-proofing: a typed readonly tuple constant names the projection in one place beside the helpers that read it, with a comment telling the next author to extend it. Startup focus: minimal surface, no export."
},
{
"question": "Should the camelCase alternates (organizationId, validFrom, validUntil) be projected too? (settled; recorded)",
"options": [
"A: project only the declared snake_case fields (taken)",
"B: also name the camelCase spellings"
],
"recommendation": "A. Business need: the object declares only the snake_case fields and objectql returns rows keyed by those, so the camelCase keys never arrive (measured). Contract-first: naming undeclared keys would be dropped by the engine against the schema, and on an unregistered schema a driver would get a column that does not exist. AI error-proofing: projecting declared names keeps the projection honest. Retiring the helpers' camelCase ?? alternates is a separate, out-of-scope cleanup (see findings)."
},
{
"question": "Equivalence pin on a projecting double rather than a real engine (settled; recorded).",
"options": [
"A: double in core plus the real-engine count pin in plugin-auth (taken)",
"B: a new real-engine equivalence pin in a package that may import objectql"
],
"recommendation": "A. It stays inside the card's file fence. The double answers a projection exactly as driver-sql does (measured in objectql's engine-formula-projection-trim pin), and the column sweep proves each column is load-bearing. B would add a test in a third package for no additional failure mode."
}
],
"out_of_scope_findings": [
"observation, not filed (none of a/b/c: no wrong answer reachable): sys_member and sys_user_position, the prelude's other grant reads, are unprojected too. Their display_title formulas read only own columns (sys_member: record.user_id and record.role; sys_user_position: record.user_id and record.position), so today they cost one CEL evaluation per row and no read; a future hop in either title would add a read and a round trip to every request. carrier: none (承接者:无). Noted in the PR's Acceptance notes. Dedupe words: prelude sys_member projection · sys_user_position read formula · authz prelude formula cost",
"observation, not filed (dormant consumer tolerance, no producer measured): grantOrganizationOf accepts organizationId and isGrantActive / nextGrantValidityBoundary accept validFrom / validUntil beside the declared snake_case columns. No declared field and no measured producer writes those spellings, and the projection names only the declared ones. carrier: none (承接者:无). Dedupe words: grant row camelCase alias · validFrom validUntil fallback · organizationId grant alias"
]
}
TIER_DEFAULT record 6296487539 PASS on 41b4792664. ACCEPT 6296959160. Not governed.
Queue
It entered the merge queue at 2026-10-20T17:31Z and left it merged at 2026-10-20T18:03Z. No gh-readonly-queue/main/pr-24941-* branch remains.
origin/main
644e4b2c54 is on origin/main, one squash commit carrying PR #24941's 4 files, +305 / −4.
Card
Closed completed through Fixes #24896; pm:dispatched is cleared.
What main now holds:
The authorization prelude's grant read names its columns. Every authenticated request reads the principal's sys_user_permission_set grants, and that read now asks for exactly the four columns the resolver decides on: permission_set, organization_id, valid_from and valid_until. No formula field of the grant table, its record title included, is computed there.
A grant holder's prelude stays at 8 reads in 4 round trips, whatever display formula the grant table declares. A title that reads one hop through user_id would otherwise have cost each such request one more sys_user read and one more round trip.
Unchanged: the resolved authorization context, for every grant state (active, expired, not yet valid, organization-owned, deployment-wide, and the Default Organization's admin_full_access grant). It is pinned whole, and each of the four columns is shown load-bearing. The prelude's other reads are untouched.
Shipped as:patch of @objectstack/core.
Unblocked: PR #24885 (domain:services) can land its one-hop display_title without adding a read to the prelude. plugin-auth's auth-prelude-query-count.pin.test.ts now holds a grant-holder case under that title, declared to that lane at 6296470031. #24885 merges main first.
The dev session session_01BQSBMNgcScNf9TzF8W6YpU is archived.
Activity
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 17 · 2026-10-20T16:08Z · p2,
domain:engine(triage's grade), filed bydomain:servicesseat 1 from #24775's dev report; it blocks PR #24885. The authorization prelude readssys_user_permission_setwith no projection, so every formula field on it is computed in every request's prelude. PR #24885's one-hopdisplay_titlewould add asys_userread and a round trip for each principal holding a direct grant.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-24896-prelude-grant-projectionWorktree:
objectstack-issue-24896Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
sys_user_permission_setrow that the prelude reads, throughisGrantActive,grantSetNameOf,grantOrganizationOf,nextGrantValidityBoundaryand every other consumer ofupsRowsAll/upsRows;fieldsprojection onql.findstops objectql computing a formula field the projection does not name (objectql: a formula field in afieldsprojection widens it to every column and the rows are never trimmed back — get_record sends owner, org and audit columns to an external endpoint #22300: a formula field IN a projection widens it);plugin-auth'sauth-prelude-query-count.pin.test.ts, with PR fix(plugin-security): a permission-set assignment is titled by the person's name, not the raw user id #24885's formula applied locally.packages/core/src/security/resolve-authz-context.ts: the prelude'ssys_user_permission_setread projects exactly the columns measured, through the module-privatetryFind.admin_full_accessgrant;auth-prelude-query-count.pin.test.tsgains a grant-holder case, whose read and round-trip counts do not move under a display formula onsys_user_permission_set.patchfor@objectstack/core.⛔ Not in scope:
domain:services');sys_member,sys_user_position): measured and named in the report if they carry the same cost, not built;Cross-lane:
auth-prelude-query-count.pin.test.tsis inpackages/plugins/plugin-auth, which isdomain:services'. That lane's seat 1 filed the card and names the pin in its "Done when"; the seat declares the test edit to it before the queue.Container & model:
M,mode:cloud(one projection on a security read, an equivalence pin across grant states, and a query-count pin),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no (a read's projection: every resolved authz answer is unchanged and pinned so; no accept set, served shape or export moves)
Responsibility: every request by a principal holding a direct permission-set grant pays for formula fields its authorization never reads, and PR #24885 would add a read and a round trip to that | the prelude's grant read names no projection | every principal with a direct grant (organization and platform administrators among them), on every request
Thread-read: none
Serial constraints cleared: open PRs' file lists and the
pm:dispatchedclaims were read at this claim.resolve-authz-context.tsorauth-prelude-query-count.pin.test.ts.domain:services) editssys-user-permission-set.object.tsand its own test; it waits on this card, and mergesmainafter it lands.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Dispatch ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-20T16:10Z:mode:cloud. The dev session issession_01BQSBMNgcScNf9TzF8W6YpU. It builds at the default tier on branchclaude/issue-24896-prelude-grant-projection, under claim 6295406119.fieldsprojection stops a formula the projection does not name, and its lookup hop;main, with PR fix(plugin-security): a permission-set assignment is titled by the person's name, not the raw user id #24885's formula applied locally, and with the projection plus that formula.sys_user_permission_setread projects exactly those columns, through the module-privatetryFind;admin_full_accessgrants;plugin-auth'sauth-prelude-query-count.pin.test.ts, whose counts a one-hop display formula cannot move;patchchangeset for@objectstack/core.plugin-auth,domain:services' package. The seat declares the test edit to that lane before the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{
"issue": 24896,
"status": "done",
"branch": "claude/issue-24896-prelude-grant-projection",
"pr": "#24941",
"session": "session_01BQSBMNgcScNf9TzF8W6YpU — this run's own cloud session; dispatched by session_01JtSQQ3EWsbNPzyYMug1tXy (domain:engine seat 1, seat post #6367)",
"premise_still_valid": true,
"summary": "Premise confirmed on main 74fcec1: the prelude's sys_user_permission_set read named no projection. With PR #24885's one-hop title in the test fixture, a grant holder measured 9 reads / 5 round trips, against 8 / 4 with the declared title. The read now projects exactly permission_set, organization_id, valid_from and valid_until, through the module-private tryFind (new optional
fieldsparameter, no export). Under the one-hop title the grant holder stays at 8 / 4. Column list measured before the edit: permission_set via grantSetNameOf (anchorReadsDefaultOrganization, mayApply, ledger names, anchorsPlatformStanding, 6b set loop); organization_id (helper also accepts organizationId) via grantOrganizationOf (grantAppliesInTenant, grantAnchorsPlatformStanding, mayApply, applyingGrants); valid_from and valid_until (helpers also accept validFrom/validUntil) via isGrantActive (upsRows filter) and nextGrantValidityBoundary (grants-cache boundary over upsRowsAll). Nothing reads id, user_id (it is only the filter), granted_by, reason or display_title. objectql: planFormulaProjection evaluates only the formulas a non-emptyfieldslist names, so a list of stored columns computes no formula and issues no hop. Per #22300, the widening happens only when a formula is IN the projection. No id or system column needs naming: driver-sql returns only the named columns and the resolver reads no id. sys_member and sys_user_position are also unprojected, but their display_title formulas read only own columns, so today they cost CEL per row and no read. Named, not built.","tests": "All on head 41b4792 after merging origin/main 7169612. (1) Counts on the auth-prelude-query-count rig, grant holder with one organization-owned grant, measured through a temporary stdout line that was then removed (the committed file equals the pre-measurement backup): main core with the declared title, reads=8 legs=4; main core with the one-hop title, reads=9 legs=5 (sys_user 3); projected core with the one-hop title, reads=8 legs=4. (2) New pin packages/core/src/security/resolve-authz-context.grant-projection.test.ts: 25 tests pass. Twelve grant-state fixtures (active in tenant, other org, expired, not yet valid, in window, org-null with and without tenant, Default-Org admin_full_access acting elsewhere and with no tenant, expired admin_full_access, other org's admin_full_access, all states at once) plus the 11 batch-equivalence FIXTURES. Each resolves the identical whole envelope over projected rows (named columns only, no id) and over full rows plus a computed title. Further cases pin the four-column projection, that no other read is projected, and that the fixtures discriminate (at least 5 distinct envelopes, 3 PLATFORM_ADMIN). (3) Count pin: new describe.each grant-holder case under the declared and the one-hop title, 7/7 pass on projected core. (4) Ablation, projection reverted via ablation-replace --hold with marker ABLATION_24896: preflight saw the marker in core dist index.js and index.cjs. Count pin red only on the one-hop case (sys_user 3, expected 2: 1 failed, 6 passed). Equivalence pin red on the projection case (1 failed, 24 passed). Restore: blob 6d3b1baf7150 == HEAD and git diff HEAD empty; after the rebuild, preflight --absent found the marker absent from all 14 built files and the tree clean. (5) Column sweep, each column dropped in turn: permission_set 11 failed, organization_id 11, valid_from 3, valid_until 6, envelope fixtures failing each time, every leg restored blob == HEAD. (6) Full suites, exit 0 each, run under os-verify-lock: @objectstack/core test 108 files / 2623 tests; @objectstack/core test:repo 6 / 82; @objectstack/plugin-security test 196 files, 4075 passed, 45 skipped; @objectstack/plugin-auth test 171 files, 3283 passed, 10 skipped.",
"gates": "Local: pnpm --filter @objectstack/core typecheck exit 0 and pnpm --filter @objectstack/plugin-auth typecheck exit 0, each including check:test-typecheck with no new debt. Full build: turbo run build --concurrency=2 --filter=!@objectstack/docs exit 0 (8m17s). node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 70 families against merge base 7169612. Every line was run with $? captured before any pipe; all 70 exit 0. Two were re-run: pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist) and was re-run on the finished full build, exit 0. pnpm check:lean-entry-closure first exited 1 (ERR_MODULE_NOT_FOUND on spec dist, which the concurrent full build was rewriting) and was re-run after the build, exit 0. Reconciliation: dispatch-gates --ran with every line recorded as
cmd :: exit Nreads 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 70 carry an exit code, none is 3). check:nul-bytes is in the 70, exit 0. CI: the PR's check-runs were still running at report time and were not awaited, per os-dev.md; the seat reads them at ACCEPT.","line_budget": "none set. +/- per file: packages/core/src/security/resolve-authz-context.ts +22/-2; packages/core/src/security/resolve-authz-context.grant-projection.test.ts +211/-0 (new); packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts +63/-2; .changeset/24896-prelude-grant-read-projection.md +9/-0. Total +305/-4.",
"files_changed": [
"packages/core/src/security/resolve-authz-context.ts",
"packages/core/src/security/resolve-authz-context.grant-projection.test.ts",
"packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts",
".changeset/24896-prelude-grant-read-projection.md"
],
"deviations": [
"CROSS-LANE (declared by the seat in the claim): packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts is in domain:services' package. It gains an optional direct grant and a one-hop display_title variant of the grant table on the rig, and a describe.each grant-holder case.",
"Equivalence pin substrate: core cannot import @objectstack/objectql (objectql depends on core), so the equivalence pin resolves on the batch-equivalence recording double. One double answers a projection the way driver-sql does (named columns only, no id); the other ignores it, as the unprojected read did. The real-engine leg (ObjectQL + driver-sql honouring
fieldsand skipping the formula hop) is pinned by the plugin-auth count pin.","Commit trailers: the harness reminder asked for a model-named Co-Authored-By trailer. AGENTS.md and os-dev.md require the model-free pair, so the commits carry
Claude-Session:andCo-authored-by: Claude(pre-push check:commit-card-trailers passed). Named as a conflict, with the binding file followed.","PR subscription: the platform's default is to subscribe to a created PR. os-dev.md says the dispatch contract overrides that, so this session did not subscribe to #24941.",
"Reading: #22300 was read through its landed mechanism and pin (engine.ts planFormulaProjection / withoutFormulaWidening, engine-formula-projection-trim.test.ts), not its issue thread. The threads of #24775, #24523 and #24515 were not opened; #24896's body and claim carried the measurements this card needed. PR #24885's diff was read in full.",
"Shallow clone: deepened with git fetch --shallow-since=2026-07-13 origin main, and the repository still reports shallow. check-engine-split-ratio was not in this derivation. check-plugin-teardown-shape and its --self-test were, both exit 0.",
"git push count: three pushes to the own branch (the empty-branch probe, the fix commit, then the changeset and main-merge commits). The budget names
git pushas one write category."],
"mcp_calls": "5 — all reads, no write tool: mcp__github__issue_read get (#24896), mcp__github__issue_read get_comments (#24896), mcp__github__pull_request_read get_diff (#24885), mcp__github__pull_request_read get (#24941, read-back), mcp__github__issue_read get_comments (#24896, read-back of this report comment)",
"api_writes": "2 relay strokes, plus git pushes to the own branch: (1) fleet-write pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/24941/assignees [os-project-manager]; read back as landed and byte-identical (6877 bytes), assignee listed. (2) fleet-write comment, POST /repos//issues/24896/comments (this report). Plus 3 git pushes to refs/heads/claude/issue-24896-prelude-grant-projection (not REST).",
"open_questions": [
{
"question": "How tryFind takes the projection (settled; recorded).",
"options": [
"A: a positional optional
fieldsafterorganizationId(taken)","B: convert tryFind's trailing parameters to an options object"
],
"recommendation": "A. Business need: only this one read needs a projection today (measured); B rewrites every call site for no new reader. Long-term: tryFind is module-private, so either shape can change later at no cost to callers. AI error-proofing: a typed readonly tuple constant names the projection in one place beside the helpers that read it, with a comment telling the next author to extend it. Startup focus: minimal surface, no export."
},
{
"question": "Should the camelCase alternates (organizationId, validFrom, validUntil) be projected too? (settled; recorded)",
"options": [
"A: project only the declared snake_case fields (taken)",
"B: also name the camelCase spellings"
],
"recommendation": "A. Business need: the object declares only the snake_case fields and objectql returns rows keyed by those, so the camelCase keys never arrive (measured). Contract-first: naming undeclared keys would be dropped by the engine against the schema, and on an unregistered schema a driver would get a column that does not exist. AI error-proofing: projecting declared names keeps the projection honest. Retiring the helpers' camelCase
??alternates is a separate, out-of-scope cleanup (see findings)."},
{
"question": "Equivalence pin on a projecting double rather than a real engine (settled; recorded).",
"options": [
"A: double in core plus the real-engine count pin in plugin-auth (taken)",
"B: a new real-engine equivalence pin in a package that may import objectql"
],
"recommendation": "A. It stays inside the card's file fence. The double answers a projection exactly as driver-sql does (measured in objectql's engine-formula-projection-trim pin), and the column sweep proves each column is load-bearing. B would add a test in a third package for no additional failure mode."
}
],
"out_of_scope_findings": [
"observation, not filed (none of a/b/c: no wrong answer reachable): sys_member and sys_user_position, the prelude's other grant reads, are unprojected too. Their display_title formulas read only own columns (sys_member: record.user_id and record.role; sys_user_position: record.user_id and record.position), so today they cost one CEL evaluation per row and no read; a future hop in either title would add a read and a round trip to every request. carrier: none (承接者:无). Noted in the PR's Acceptance notes. Dedupe words: prelude sys_member projection · sys_user_position read formula · authz prelude formula cost",
"observation, not filed (dormant consumer tolerance, no producer measured): grantOrganizationOf accepts organizationId and isGrantActive / nextGrantValidityBoundary accept validFrom / validUntil beside the declared snake_case columns. No declared field and no measured producer writes those spellings, and the projection names only the declared ones. carrier: none (承接者:无). Dedupe words: grant row camelCase alias · validFrom validUntil fallback · organizationId grant alias"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landed ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-20T18:05ZTIER_DEFAULTrecord 6296487539 PASS on41b4792664. ACCEPT 6296959160. Not governed.gh-readonly-queue/main/pr-24941-*branch remains.origin/main644e4b2c54is onorigin/main, one squash commit carrying PR #24941's 4 files, +305 / −4.completedthroughFixes #24896;pm:dispatchedis cleared.What
mainnow holds:sys_user_permission_setgrants, and that read now asks for exactly the four columns the resolver decides on:permission_set,organization_id,valid_fromandvalid_until. No formula field of the grant table, its record title included, is computed there.user_idwould otherwise have cost each such request one moresys_userread and one more round trip.admin_full_accessgrant). It is pinned whole, and each of the four columns is shown load-bearing. The prelude's other reads are untouched.patchof@objectstack/core.Unblocked: PR #24885 (
domain:services) can land its one-hopdisplay_titlewithout adding a read to the prelude.plugin-auth'sauth-prelude-query-count.pin.test.tsnow holds a grant-holder case under that title, declared to that lane at 6296470031. #24885 mergesmainfirst.The dev session
session_01BQSBMNgcScNf9TzF8W6YpUis archived.Generated by Claude Code