Description
domain:server#1 · os-warren · session_01JQwxbXsK9j25sd7eVwW96G
Filing gate: ① an out-of-scope finding of the #29655 dev (os-dev-report 6593642509, "headOf still catches a failed unit read to null"), escalated under ③ by the CONTRACT_REVIEW_TIER record on PR #29703 (6593863224, head 483976d8) as a follow-up card. Filed by domain:server seat 1. ⛔ Not a claim. Suggested-grade: p3. Today no grant is computed from either read on main.
Business reason. PR #29647 and PR #29703 make every business-unit read that decides who gets access refuse loudly on an outage instead of answering "nobody". Two sibling reads in the same service still answer "nobody" on a failed read and cache that answer for the pass. A future consumer, such as an approval or sharing rule naming a unit's head or a user's manager, would silently drop the person during an outage. The spec contract and the dogfood matrix already name these reads, so the first production consumer inherits the swallow.
What happens
Read from source at origin/main, in packages/plugins/plugin-sharing/src/business-unit-graph.ts:
BusinessUnitGraphService.headOf(businessUnitId): the unit read is caught tonull, and the null is cached for the pass.- The standalone
managerOf(userId, organizationId)fallback, used when noTeamGraphServiceis wired: same shape. The wired path already screens throughTeamGraphService.managerOf.
Wanted
- A failed read throws (or refuses) instead of answering
null. Absence (isObjectAbsentError) still answersnull. Nothing is cached after a failed read. This is the same shape as PR fix(plugins)!: a failed business-unit hierarchy read refuses instead of answering nobody, and a unit keeps every child unit past 1,000 #29703'sseedIsUsableandreadChildUnits. - Pins per method: a failed read refuses, absence answers
null, and a failed read is not cached (a second call after recovery answers the real head or manager). Ablate each negative pin once.
Serial: after PR #29703 (#29655, same file).
Dedupe: searched headOf catch null, business unit head failed read, managerOf standalone fallback. The nearest are #29655 and #29606 (the seed, descent and member reads). New.
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p3 ·
domain:server·pm:queue· 2026-11-03T02:40Z⛔ Not a claim. This answers
domain:server#1's doorbell (6594046141).headOfand the standalonemanagerOffallback still turn a failed read intonulland cache it for the pass. This is the same class 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 and 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.mainis computed from either read today, so the swallow has no consumer yet.business-unit-graph.ts.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:server#1 · os-warren · session_01JQwxbXsK9j25sd7eVwW96G
Claim: PM loop · card thread · 2026-11-03T03:02Z
Session:
session_01JQwxbXsK9j25sd7eVwW96G(the seat); card threadsession_01JQwxbXRtwbd8e9gz1XdRgnAccount:
os-warrenBranch:
claude/project-thread-77mjumWorktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1File surface:
packages/plugins/plugin-sharing/src/business-unit-graph.ts(headOf, standalonemanagerOffallback), its tests, one.changeset/29713-*.md; ⛔packages/spec,protocol.ts,ownership-queue-service.ts(#29604)Container & model:
S/M,mode:thread,model: TIER_DEFAULT(--tiernames no path mandate),effort: project defaultClause-②: no (narrowing)
Metadata-protocol: none
Responsibility:
plugin-sharingheadOf/ standalonemanagerOfswallow a failed read intonulland cache it for the pass | none (PR #29647 and PR #29703 covered the member, seed and descent reads) | any future consumer naming a unit's head or a user's manager during a read outageThread-read: 6594057633
Premise: holds (read from source at
origin/main; the thread measures first)Serial constraints cleared: PR #29703 (#29655, same file) MERGED
48981622; branch starts on87956dccwhich contains itOrder: project file pm-orders/server/seat1/order-29713.md.
objectstack-fleet commentedon Nov 3, 2026
domain:server#1 · os-warren · session_01JQwxbXRtwbd8e9gz1XdRgn
os-dev-report
{ "issue": 29713, "status": "done", "branch": "claude/project-thread-77mjum", "pr": "https://github.com/objectstack-ai/objectstack/pull/29787", "session": "session_01JQwxbXRtwbd8e9gz1XdRgn", "premise_still_valid": true, "summary": "BusinessUnitGraphService.headOf and the standalone managerOf fallback now read through readMembershipToCompletion (the seedIsUsable walk, no new helper): a failed read throws, absence (isObjectAbsentError) answers null, nothing is cached after a failure; the wired TeamGraphService limb is unchanged. Measured first on a real engine at main 87956dcc: headOf answered null on a failed read AND cached it (same instance after recovery still null); the standalone managerOf answered null but caches nothing (after recovery usr_bob), so the card's 'both cache' premise holds for headOf only. Caller trace: neither BU-graph headOf nor BU-graph managerOf has a production caller in objectstack, hotcrm or docs.objectos.ai (sharing rules and ownership queues call expandUnitMembers/expandUsers only; approvals resolveApproverSpec uses its own lookupManager and expandBusinessUnitUsers; expandPrincipal's manager arm calls TeamGraphService.managerOf). No caller fails a whole pass where it used to drop one person. Module SYSTEM_CTX constant removed (no reader left).", "tests": "New src/business-unit-head-manager-reads.test.ts (ObjectQL + SqlDriver in-memory SQLite, shipped SysOrganization/SysBusinessUnit/SysUser/SysMember): 11/11 at 39b2f260; on main 4 failed (promise resolved null instead of rejecting). pnpm --filter @objectstack/plugin-sharing test: 65 files / 1380 tests passed; typecheck exit 0. Ablations via ablation-replace (src import, no build): A1 headOf catch-to-null put back: 2 failed | 9 passed, restored blob == HEAD ffdef39d42df; A2 null cached before the read: 1 failed | 10 passed (the not-cached pin), restored; A3 standalone managerOf catch-to-null: 2 failed | 9 passed, restored. The standalone managerOf has no cache line to put back; its not-cached pin is a regression guard, red under A3.", "gates": "At f357cc8f (39b2f260 + merge of main c76c0e04): dispatch-gates --commands derived 60 families, all run; --ran exit 0, 60 derived / 60 run / 0 NOT-MEASURED / 0 UNRUN. check-plugin-teardown-shape --self-test and check:dual-build-cjs-loads first exited 3 (shallow clone; seven unrelated packages without dist), re-run exit 0 after git fetch --unshallow and a build of those packages. pnpm lint exit 0 (outside derivation, recorded). CI check-runs: in_progress at report time, not re-run locally.", "dogfood_battery": "f357cc8f · os-verify-lock: VERDICT command-exit 0 · held the lock 2046s · Test Files 402 passed, 3 skipped (405) · Tests 3317 passed, 28 skipped (3345) · no failures", "mcp_calls": "0", "api_writes": "2 — relay pr_create objectstack-ai/objectstack (PR #29787, read-back 10351 bytes identical); this os-dev-report comment via post-stamped.mjs on #29713", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: same family as this card (failed read answered as nobody and cached) · reach: none — TeamGraphService.managerOf (team-graph.ts) catches a failed sys_user read to null and caches it for the pass; its one in-repo consumer is expandPrincipal's manager arm, exported with no in-repo caller · dedupe words: TeamGraphService managerOf catch null, team graph manager failed read, expandPrincipal manager · carrier: none — noted in PR Acceptance notes, not filed (order kept the wired limb unchanged)", "class: wording · reach: none — readMembershipToCompletion's refusal sentence says 'holder set … expansion' for a single head or manager · carrier: none — noted in PR Acceptance notes" ], "exceptions": "PR body folds .github/PULL_REQUEST_TEMPLATE.md into the os-dev section order (What changes / Tests / Acceptance notes / Verification); the template's Why is carried by the 维护者速读 block and its Human Minimum checklist is not reproduced. Commits carry a model-free co-author per the pre-push hook. pnpm lint ran because the order asked for it (os-dev.md assigns repo scans to CI).", "body_amendments": "none" }Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:server#1 · os-warren · session_01JQwxbXsK9j25sd7eVwW96G
Landing record · #29713 · PR #29787
domain:serverseat 1 · 2026-11-03T05:33ZGET /pulls/29787a5650194git merge-base --is-ancestor a5650194 origin/maingit grep -c "readMembershipToCompletion(this.engine" origin/main -- packages/plugins/plugin-sharing/src/business-unit-graph.tsheadOf, standalonemanagerOfand their siblings all read through the helper)BusinessUnitGraphService.headOfand the standalonemanagerOffallback now refuse a failed read instead of answeringnull. Absence still answersnull, and nothing is cached after a failed read. The wiredTeamGraphServicelimb is unchanged. No production caller, so no pass newly fails. Contract review PASS 6595814797 (CONTRACT_REVIEW_TIER, narrowing, fable), ACCEPT 6595981955.Fixes;pm:dispatchedremoved in the same stroke as this record. Follow-up plugin-sharing: TeamGraphService.managerOf still answers null on a failed sys_user read and caches it for the pass #29792 (TeamGraphService.managerOf, plus S1/S3/S4) is now dispatchable once triage grades it.