Skip to content

fix(plugins)!: business-unit recipients and approvers reach every member past 10,000 rows, and a failed member read refuses instead of answering nobody - #29647

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-kbsbfm
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-kbsbfm

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread

Fixes #29606

domain:server#4 · os-warren · session_01JQwxbWdszDuXCYxfoR2ynB

Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: updated here. content/docs/automation/approvals.mdx now names department and the sys_business_unit_member read beside the team, position and tier reads for a failed holder read, and says that an expression value whose resolveAs read failed logs the same line. No skill teaches a bound on business-unit members or what a failed member read answers.

维护者速读

  • 改了什么:共享规则与审批里「业务单元成员」(本单元 / 含下级单元) 现在分页读完全部成员;读失败不再当作「没人」,而是明确报错。开机回填 sys_user.primary_business_unit_id 也读完全部主成员关系,读失败或读不完整就不写并记录原因。审批表达式 resolveAs 展开失败时,现在和静态审批人一样打出「could not be expanded」告警并带上错误。
  • 为什么改:这些读只读一次、上限 1 万行,读失败还会变空:共享规则会漏授权,业务单元结构一变,撤销流程会把整条规则的授权全部收回;审批会派给缩水的名单,或者把读失败记成「没人」;回填超过 1 万的用户永远不会被投影,而且什么都不说。
  • 风险与回滚:唯一「从有答案变成报错」的情形是 sys_business_unit_member 的读本身失败(原来答空),属收窄,标题带 !;1 万人以下仍是一次读、结果不变;队列(ownership queue)的业务单元成员规格在读失败时仍贡献 0 人(文件头声明的姿态),只是现在会记一次日志,队列文件未改动。回滚即 revert 本 PR。
  • 先看:packages/plugins/plugin-sharing/src/business-unit-graph.ts(readMembers)、packages/plugins/plugin-approvals/src/approval-service.ts(expandBusinessUnitUsers、warnExpansionFailed)、packages/plugins/plugin-sharing/src/primary-bu-projection.ts(readPrimaryMemberships)。
  • 两分钟验证:pnpm --filter @objectstack/plugin-sharing exec vitest run src/business-unit-member-reads-complete.test.ts,预期 14/14 通过(含 10,001 名成员全部在集合内、读失败抛错、缺表仍答无人、回填 10,001 行全部投影)。
  • 需要你做的:无,席位落地。

What changes

The sys_business_unit_member reads still read once at a 10,000-row bound and turned a failed read into nobody. This extends the family #29362 (PR #29497) and #29503 (PR #29602) closed for the team, role and position reads to the last membership object, through the same shared keysetWalk and isObjectAbsentError (@objectstack/types). No new walk spelling for a holder read: plugin-sharing's member reads go through readMembershipToCompletion (team-graph.ts), plugin-approvals' through readHoldersToCompletion. No new export from any package index.

Item 1, site 1: sharing BusinessUnitGraphService.

Item 1, site 2: sharing primary-business-unit backfill (primary-bu-projection.ts).

  • The boot scan of is_primary rows pages to completion through keysetWalk. A scan that fails, or a walk that cannot vouch for completion, writes nothing and warns with the consequence and the error. Absence of the object answers nothing to project, silently.
  • Refusing here means writing nothing and saying so, not throwing. The scan's only caller is the plugin's boot step, whose catch would log "projection not started" though the hooks were bound. The failed-scan path already answered { updated: 0, refused: 0 } with a warning, and the existing pin ("a refused SCAN answers both counters, not a half-built result") keeps that shape. It uses keysetWalk directly, not readMembershipToCompletion: this is a projection scan rather than a holder read, and the holder helper's refusal sentence speaks of grants.
  • The two one-row reads in the same file (recompute, the beforeDelete stash) are limit: 1 existence probes that a bound cannot cut, so they are unchanged.

Item 1, site 3: approvals expandBusinessUnitUsers.

  • The member read goes through readHoldersToCompletion('sys_business_unit_member', …). A failed read throws, the approver-spec resolver's directory catch takes it, and the department:ID slot falls back to its literal with the could not be expanded warning and its error. Before, a failed read logged "expanded to nobody".

Item 3: resolveAs expansion failure warns.

  • resolveExpressionApprovers caught a failed department / position / team expansion into users = [] and pushed the KIND:VALUE literal with no log line. It now logs the same warning resolveApproverSpec logs, with the error and the expression source.
  • The sentence lives in one private method, warnExpansionFailed, which both resolvers call, so the two cannot drift.

Item 2: measured, reported, not changed. See the report on the card and the Acceptance notes. ⛔ ownership-queue-service.ts is untouched.

Tests

  • New plugin-sharing/src/business-unit-member-reads-complete.test.ts (14 cases, real ObjectQL + SqlDriver over in-memory SQLite, shipped SysBusinessUnit / SysBusinessUnitMember, row ids scrambled against user ids):
    • expandUnitMembers: 10,001 members all in the set and nobody from another unit or organization (2 reads); 9,999 members in one read; a failed read throws naming the unit and the driver error; a failed read is not cached; the object unregistered, or its table dropped, answers [].
    • expandUsers (members split across the unit and its child): 10,001 (2 reads); 9,999 (1 read); a failed read throws; unregistered answers [].
    • Backfill: 10,001 primary rows all projected (2 reads, no warning); 9,999 in one read; a failed scan writes nothing and warns with the error; unregistered projects nothing and says nothing.
  • primary-bu-projection.test.ts: fixture rows now carry their id (the scan seeks by it). The refused-scan case also asserts the warning and its error, and a new case pins that a scan that cannot vouch for completion writes nothing and warns.
  • business-unit-graph.test.ts (fixture triage, no assertion changed): its hand-rolled engine now stamps the id a stored row carries and models $and, $gt, orderBy and limit on the member side, so its 25 cases keep pinning the two tenant screens and the two widths.
  • New plugin-approvals/src/business-unit-holders-complete.integration.test.ts (11 cases, real engine):
    • expandBusinessUnitUsers: 10,001 (2 reads, nobody from another unit or organization); a department approver routes to all 10,001; 9,999 in one read; a failed read throws; what the caller sees is department:bu_root plus the could not be expanded warning with the SQLITE_IOERR error; unregistered answers the literal with no failure warning; dropped table answers [].
    • Item 3: a resolveAs: 'department' control expands to the members with no failure warning; a failed department read keeps { id: 'department:bu_root', subGroup: 'bu_root' } and logs one could not be expanded warning carrying type, value, organizationId, expression and the error; a failed team read through resolveAs warns the same way; an empty unit keeps the literal with no failure warning.
Ablation (source paths: these suites import the files under test relatively, so no build is in the path)
  • Committed the fix first (2324d29f), then a trapped script checked out the three source files at the base (4d8095ba) and confirmed the mutation landed (readMembers 0 hits, keysetWalk in the projection 0, warnExpansionFailed 0, limit: 10000 back in approvals 1).
  • Sharing: Tests 9 failed | 11 passed (20), the red ones being every 10,001, failed-read, not-cached, truncated-scan and quiet-absence case. Approvals: Tests 6 failed | 5 passed (11), the red ones being the 10,001 cases, both failed-read cases and both item-3 warning cases.
  • Restored with git checkout HEAD -- PATH: git diff HEAD 0 bytes, and each file's git hash-object equals its HEAD blob (ddbfccfd…, 8ccd37de…, 6d3a5423…).

Acceptance notes

  • Clause-② and Metadata-protocol are the claim's lines, confirmed from the diff: no export is added or removed from any package index, no schema key moves, and the only answer that changes kind is a failed member read (empty becomes a refusal), the (narrowing) arm. Review face: node scripts/pm/record-recognisers.mjs --template --paths … names no path face and defers to the changeset's Clause-②: line, so it is --face narrowing (CONTRACT_REVIEW_TIER).
  • Item 2, measured on a real engine (scratch tests, not committed):
    • QUEUE_READ_LIMIT can silently drop a holder. One authored queue with 10,001 user member rows: expandMembers answered 10,000 and missed usr_010000, isMember answered false for it, queuesOf answered [] for it, and no warning was logged. 10,001 authored queues: queuesOf missed a member of the 10,001st queue, no warning. 10,001 team member rows: queuesNaming missed the queue past the cap, so rules naming it are not re-evaluated, no warning. ⛔ Not changed: ownership-queue-service.ts belongs to plugins: a queue sharing recipient still revokes grants on a member-table outage, because the queue expander answers "nobody" for every consumer; refuse for the sharing-grant pass and the approver slate (ADR-0142 amendment first) #29604.
    • APPROVER_INDEX_CAP in organizationAdministrators can drop an administrator, loudly. 10,001 sys_member rows with the one administrator on the last row: {"ids":[]}; on the first row: {"ids":["usr_010000"]}. Both runs logged organization-administrator probe hit its window — results may be truncated. The read has no orderBy, so which members fall off is driver order. It is a declared bound with a loud warning, so per the card it is reported and left; the choice is on the card report.
  • Same-family reads left as they are (outside this card's sites, listed in the report for the seat): the sys_business_unit hierarchy reads in both plugins. Sharing descendants (limit: 1000, catch → []) and seedIsUsable (catch → false, and the empty answer is cached for the pass), approvals' seed check (catch → []) and descent (limit: 1000, catch → []). A failed unit read still answers nobody there. The new not-cached pin had to key its fault to the member object for exactly this reason: a fault on the seed check is cached as [].
  • protocol.ts (frozen), packages/spec and ownership-queue-service.ts are untouched.

Verification

Head: 241a137f (merges origin/main fa07650b; the incoming commit touches none of these packages). Every reading below was taken on that head, except the approvals suite, typechecks and the first derived-gate run, which were taken on 0443a73e. The only commit after it changes business-unit-graph.test.ts, and the sharing suite was re-run on 241a137f.

Package suites and typecheck
  • @objectstack/plugin-sharing: Test Files 63/63, Tests 1360/1360 (241a137f); typecheck exit 0.
  • @objectstack/plugin-approvals: Test Files 110/110, Tests 1434/1434; typecheck exit 0.
Full dogfood battery (the title carries ! and the declaration a (narrowing) arm)
  • Closure: turbo run build --concurrency=2 --filter='./packages/**' under the lock, VERDICT command-exit 0, 78/78 tasks (covers the dogfood closure).
  • pnpm --filter @objectstack/dogfood test under the lock on 241a137f: VERDICT command-exit 0, Test Files 398 passed and 3 skipped (401), Tests 3280 passed and 28 skipped (3308), 0 failed.
Derived gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 94 families derived (the same list on 0443a73e and 241a137f). All 94 were run, each with its exit code recorded.
  • Four first answered exit 3 (PREREQUISITE NOT MET) and were re-run after their prerequisite: check-plugin-teardown-shape --self-test after git fetch --unshallow origin main; check:skill-examples, check:dual-build-cjs-loads and check:i18n after turbo run build --filter='./packages/**' (78/78 tasks). All four then exited 0.
  • --ran reconciliation: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero).
  • pnpm lint (repo-wide eslint, --no-inline-config) on 241a137f: exit 0.
  • CI families are not re-run locally; the PR check-runs are the record.

Generated by Claude Code

…ber past 10,000 rows, and a failed member read refuses instead of answering nobody

The sys_business_unit_member reads in plugin-sharing (BusinessUnitGraphService
expandUnitMembers / expandUsers, the primary-business-unit boot backfill) and
plugin-approvals (expandBusinessUnitUsers) read to completion through the shared
keyset walk and refuse a failed or truncated read; absence still answers no
members. An expression approver's failed resolveAs expansion now logs the same
"could not be expanded" warning the static approver types log, with the error.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbWdszDuXCYxfoR2ynB
…t member read

The sys_business_unit_member reads are now a keyset walk (id > cursor, ordered
by id, a page limit). The hand-rolled engine in business-unit-graph.test.ts
stamps the id a stored row carries and models $and, $gt, orderBy and limit, so
its 25 cases keep pinning the two tenant screens and the two widths.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbWdszDuXCYxfoR2ynB
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-approvals, @objectstack/plugin-sharing, touching 16 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/plugins/plugin-sharing/src/team-graph.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-sharing/src/team-graph.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4de4a45dc38105cac2f5a70ac44c0c36649e724b — the merge of head 241a137fd1e3a8f7caf4b530a0e4752b232d8bf3 into base 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4de4a45dc38105cac2f5a70ac44c0c36649e724b && git checkout 4de4a45dc38105cac2f5a70ac44c0c36649e724b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2 241a137fd1e3a8f7caf4b530a0e4752b232d8bf3 && git checkout -B drift-repro 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2 && git merge --no-ff 241a137fd1e3a8f7caf4b530a0e4752b232d8bf3

node scripts/docs-audit/affected-docs.mjs --json 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 3482f3d8d2c48ec8d2e3306f1ce26c0364e800c2 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

Contract-review-claim: session_01JQwxbacSXJQqAztgt4UMrS · head 241a137 · 2026-11-02T23:54Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 241a137fd1e3a8f7caf4b530a0e4752b232d8bf3
Local-runs: none
Scope: packages/plugins/plugin-approvals/src/approval-service.ts, packages/plugins/plugin-approvals/src/business-unit-holders-complete.integration.test.ts, packages/plugins/plugin-sharing/src/business-unit-graph.ts, packages/plugins/plugin-sharing/src/business-unit-graph.test.ts, packages/plugins/plugin-sharing/src/business-unit-member-reads-complete.test.ts, packages/plugins/plugin-sharing/src/primary-bu-projection.ts, packages/plugins/plugin-sharing/src/primary-bu-projection.test.ts, packages/plugins/plugin-sharing/src/team-graph.ts + packages/types/src/keyset-walk.ts, packages/types/src/object-absence.ts, plugin-sharing sharing-rule-service.ts, bu-tree-recompute.ts, queue-hooks.ts, recipient-membership-recompute.ts, sharing-plugin.ts (boot), ownership-queue-service.ts (read-only), .changeset 29362/29503 precedents, content/docs/automation/approvals.mdx

① Derived judgments

  • BusinessUnitGraphService.expandUnitMembers / expandUsers: a failed or cursor-dishonoured sys_business_unit_member read now throws via readMembershipToCompletion, nothing cached — RIGHT. Every caller read: expandRecipient → evaluateResolvedRule (boot-loop and hook-rebind catches), revokeRuleGrantsForRetiredRecipients (bu-tree-recompute L299, queue-hooks L218 per-rule catches), ruleReachesUser → reconcileRuleForUser (per-user catch), OwnershipQueueService.expandSpec (.catch → warnOnce, [], its declared posture; specsReachUser default arm routes through it). No caller that must stay non-throwing now throws; the per-user recompute stops revoking a holder on an outage. Same surface the team read has had since fix(plugins)!: team and position recipients reach every holder past 10,000 rows, and a failed holder read refuses instead of answering nobody #29602.
  • Absence (isObjectAbsentError attributed to the object) still answers [] — RIGHT; every other error stays loud.
  • Backfill: failed/incomplete scan → write nothing, warn with consequence, {0,0} — RIGHT (the boot catch would misreport "not started"); absent object → silent [] — RIGHT. The walk reads the member table while writing sys_user, so it is not self-mutating; ordering sound.
  • Approvals expandBusinessUnitUsers → readHoldersToCompletion: the throw lands in resolveApproverSpec's directory catch → literal + could not be expanded with error — RIGHT. resolveAs catch warns through shared warnExpansionFailed; VALIDATION_FAILED still rethrown — RIGHT.
  • Public surface: no export added or removed; readMembershipToCompletion stays package-internal. The only accept-set change is empty → refusal on a failed member read, as declared.

② Semver level

Matches: patch + ! + Clause-②: no (narrowing) + adr-0087 not-required, the shape of #29362; #29503 said yes only for its new export, which this PR has none of. Check-runs at read time: Lint & Repo Gates, Build Core, Build Docs, Test Core 2/8 and 7/8, Type Check workspace/source/consumer/debt, all Lint families, Changeset, PR-body guards, Governed Surface Queue Guard, Dogfood Regression Gate, Test UI, Live E2E UI = success; Test Core 1,3,4,5,6,8/8 still in_progress; nothing failed.

③ Boundary flags

④ Design judgment

  • business-unit-graph.ts: readMembers uses the shared walk; the $and cursor merge composes with memberScope's $in + organization_id; cache set only after a complete read. Holds. The seed check before it is the unit-table gap above — should-fix, follow-up card.
  • primary-bu-projection.ts: walk.truncated is checked after the loop with no max, so only a no-id or repeated-cursor page trips it; that page is yielded but discarded, so no half projection. Holds. The limit: 1 probes are unchanged — correct.
  • approval-service.ts: holders deduped across pages; the expression loop warns once per failed key, same noise profile as the static types. Holds.
  • team-graph.ts: comment only.
  • Tests: real SqlDriver with row ids scrambled against user ids, so a walk in user order would fail the 10,001 pins; 9,999 single-read controls; not-cached pin keyed to the member object; absence pinned as unregistered and as dropped table; backfill no-id truncation pin. The graph double now models $and/$gt/orderBy/limit and stamps ids, so its 25 cases exercise the walk. The queue posture on a failed BU read is asserted nowhere — acceptable, the file is frozen and the posture pre-exists.
  • No must-fix. Should-fix: file the unit-table carrier; land the plugins: a queue sharing recipient still revokes grants on a member-table outage, because the queue expander answers "nobody" for every consumer; refuse for the sharing-grant pass and the approver slate (ADR-0142 amendment first) #29604 comment before the card closes.

Implemented-by: claude/project-thread-kbsbfm
Reviewed-by: session_01JQwxbacSXJQqAztgt4UMrS

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

席位核对

ACCEPT · domain:server#4 · head 241a137fd1e3a8f7caf4b530a0e4752b232d8bf3 · 2026-11-03T00:10Z · seat review of record at TIER_DEFAULT for the whole PR. The declared narrowing (Clause-②: no (narrowing), face narrowing, no path hits) carries contract review PASS at CONTRACT_REVIEW_TIER on this head (record 6591656678). Not governed: check-governed-merges --pr 29647 0 of 10 paths, +753/−80.

  1. Shape: draft, base main, Fixes #29606. 10 files: one changeset (patch for @objectstack/plugin-sharing and @objectstack/plugin-approvals, ! title, adr-0087 not-required marker), business-unit-graph.ts / primary-bu-projection.ts / team-graph.ts (sharing), approval-service.ts (approvals), approvals.mdx, four test files. Inside the claim surface (6590278926). protocol.ts, ownership-queue-service.ts and packages/spec untouched.
  2. Premise measured: the sys_business_unit_member reads stopped at 10,000 rows and turned a failed read into nobody. The new pins put 10,001 members in the set, refuse a failed read, keep absence as nobody, and project 10,001 backfill rows. The ablation turns 15 pins red (dev report 6591481172).
  3. Gates on this head: check-runs pinned to 241a137f: 38 success, 12 skipped, none failed; mergeable_state clean. Dev local: dispatch-gates 94 derived / 94 run / 0 NOT MEASURED; pnpm lint 0. Dogfood battery (owed by the narrowing): 398 files passed / 3 skipped, 3280 tests passed / 28 skipped, 0 failed.
  4. Accepted deviations: open question 1 (APPROVER_INDEX_CAP drops an administrator on row 10,001, loudly): seat ruling A. It is a declared bound with a loud warning, which the card's item 2 rule leaves alone. No card for the grant-side read (C) now. Open question 2: seat ruling A. The measured queue reading (QUEUE_READ_LIMIT drops holders silently) is carried onto plugins: a queue sharing recipient still revokes grants on a member-table outage, because the queue expander answers "nobody" for every consumer; refuse for the sharing-grant pass and the approver slate (ADR-0142 amendment first) #29604 (comment 6591670782), which owns that file, before Fixes closes this card.
  5. Out of scope: the review's should-fix (a failed sys_business_unit unit-table read still answers nobody and is cached for the pass) is filed as plugins: a failed sys_business_unit hierarchy read still answers nobody (and sharing caches it for the pass), so a business-unit graph write can revoke a rule's grants on an outage #29655. The delegated-admin gate's 1,000-row read fails closed and is noted only.
  6. Landing: not governed, under 3000 lines, contract PASS on this head ⇒ the seat lands it (pr_ready + automerge_enable). At MERGED: landing record on plugins: the business-unit member reads in sharing and approvals still stop at 10,000 rows and answer "nobody" on a failed read; the queue member and approver-index reads stop at their caps #29606 (closed by Fixes), pm:dispatched removed.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 00:10
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 00:10
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit e0187d0 Nov 3, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-kbsbfm branch November 3, 2026 00:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

1 participant