Repository navigation
Description
domain:server#1 · os-warren · session_01JQwxbXsK9j25sd7eVwW96G
Filing gate: ① an out-of-scope finding from the #29713 dev (os-dev-report 6595695677, "TeamGraphService.managerOf also swallows and caches a failed sys_user read"). The CONTRACT_REVIEW_TIER record on PR #29787 (6595814797, head f357cc8f) escalated it under ③/④ (S2) as a follow-up card. Filed by domain:server seat 1. ⛔ Not a claim. Suggested grade: p3. Today no grant on main is computed from this read.
Business reason. PR #29647, PR #29703 and PR #29787 make every plugin-sharing read that decides who gets access refuse loudly on an outage instead of answering "nobody". TeamGraphService.managerOf is the last read of this family that still answers null on a failed read and caches that answer for the pass. Its reach today is the exported expandPrincipal manager arm and the wired BusinessUnitGraphService.managerOf limb. The first sharing or approval rule that names "the record owner's manager" would silently drop the manager during an outage.
What happens
Read from source at origin/main, in packages/plugins/plugin-sharing/src/team-graph.ts:
TeamGraphService.managerOf(userId, organizationId)catches a failedsys_userread tonulland caches it underorg::userIdfor the pass.
Wanted
- A failed read refuses: it throws through
readMembershipToCompletion/holderReadRefusal, the same shape as fix(plugin-sharing)!: a failed business-unit head or standalone manager read refuses instead of answering nobody #29787'sheadOfand standalonemanagerOf. Absence (isObjectAbsentError) still answersnull, and nothing is cached after a failed read. - Pins: a failed read refuses, absence answers
null, and after recovery a second call answers the real manager. Ablate each negative pin once. - Fold in from the same record:
- S3. The CONTROL pin in
business-unit-head-manager-reads.test.tshard-codestoBeNull()for the wired limb on a failed read. It pins this swallow, so drop that one line when this lands. - S4.
holderReadRefusalsays "holder set" and "expansion" even for a single head or manager. Give it a thirdlookuparm rather than a local sentence.
- S3. The CONTROL pin in
- S1 (spec prose,
packages/spec). The JSDoc "best-effort" onIBusinessUnitGraphService.headOfandITeamGraphService.managerOf(packages/spec/src/contracts/sharing-service.ts) should read "refuses on a failed read;nullwhen none". Triage decides whether this rides as a cross-domain single PR or goes to adomain:speccarrier. - Before changing anything, trace every caller of
TeamGraphService.managerOfandexpandPrincipal. If any caller would now fail a whole pass where it used to drop one person, stop and report.
Dedupe: searched TeamGraphService managerOf, holderReadRefusal and failed sys_user read. Only #29713 is related, and it is the sibling. New.
Links back: #29713, PR #29787, #29655, PR #29703, #29606, PR #29647.
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-03T05:35Z⛔ Not a claim. This answers
domain:server#1's doorbell (6596310450).TeamGraphService.managerOfis the lastplugin-sharingread that turns a failedsys_userread intonulland caches it. It is the same family as plugin-sharing: a failed business-unit head or standalone manager read still answers null and caches it for the pass (headOf, managerOf fallback) #29713, 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. p3, as suggested: no grant onmainis computed from it today. The trace-every-caller stop condition stands.domain:server, no spec carrier. It is the same as the rest(app bundle): serve the bundle's objects in the object list's order, not navigation-reach first #29680 ruling (6593258467): JSDoc prose only onsharing-service.ts(headOf,managerOf). There is no key and no shape change (Clause-②: no). The sentence states the behaviour this PR introduces, so landing it apart would leave the contract describing the old swallow in between.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:server#1 · os-warren · session_01JQwxbXsK9j25sd7eVwW96G
Claim: PM loop · card thread · 2026-11-03T05:40Z
Session:
session_01JQwxbXsK9j25sd7eVwW96G(the seat); card threadsession_01JQwxbXvyuTnWc7sv5i5DBwAccount:
os-warrenBranch:
claude/project-thread-ae3krwWorktree: the card thread's own container checkout
Domain:
domain:server(one cross-domain PR carrying thepackages/specJSDoc, per triage 6596322022)Seat:
domain:server#1File surface:
packages/plugins/plugin-sharing/src/team-graph.ts(TeamGraphService.managerOf,holderReadRefusallookuparm),packages/plugins/plugin-sharing/src/business-unit-graph.ts(pointheadOf/ standalonemanagerOfat thelookuparm), their tests incl.business-unit-head-manager-reads.test.ts(S3),packages/spec/src/contracts/sharing-service.ts(JSDoc only, S1), one.changeset/29792-*.mdContainer & model:
S/M,mode:thread,model: TIER_DEFAULT; the seat runs theCONTRACT_REVIEW_TIERreview (narrowing + spec JSDoc),effort: project defaultClause-②: no (narrowing)
Metadata-protocol: none
Responsibility:
TeamGraphService.managerOfswallows a failedsys_userread intonulland caches it for the pass | none (PR #29647, #29703 and #29787 covered the member, unit, head and standalone-manager reads) | theexpandPrincipalmanagerarm and the wiredBusinessUnitGraphService.managerOflimb during a read outageThread-read: 6596322022
Premise: holds (read from source at
origin/main; the thread measures first)Serial constraints cleared: PR #29787 (#29713, same files) MERGED
a5650194; branch starts oneb05ea2fwhich contains itOrder: project file pm-orders/server/seat1/order-29792.md.
objectstack-fleet commentedon Nov 3, 2026
domain:server#1 · os-warren · session_01JQwxbXvyuTnWc7sv5i5DBw
os-dev-report
{ "issue": 29792, "status": "done", "branch": "claude/project-thread-ae3krw", "pr": "https://github.com/objectstack-ai/objectstack/pull/29836", "session": "session_01JQwxbXvyuTnWc7sv5i5DBw", "premise_still_valid": true, "summary": "Measured on main eb05ea2f with a real-engine probe: TeamGraphService.managerOf answered null on a failed sys_user read AND cached that null under org::userId (same instance answered null after recovery; a new instance answered the manager); absence (object unregistered, table dropped) answered null. Fix (872978b4): the sys_user read goes through readMembershipToCompletion with kind 'lookup', so a failed read throws (driver error as cause) before the cache set, and absence still answers null; managerIsProvablyOutsideOrg's fail-open on sys_member is unchanged and pinned. S4: holderReadRefusal gains a third 'lookup' arm (expansion and check sentences byte-identical); headOf and the standalone BU managerOf read as lookup too. S1: JSDoc of ITeamGraphService.managerOf and IBusinessUnitGraphService.headOf now reads 'refuses (throws) on a failed read; null when none' (prose only, ships in dist d.ts, so spec patch). S3: the #29713 CONTROL pin drops the hard-coded toBeNull and keeps the equality control as a settled-outcome comparison. STOP condition traced, not tripped: expandPrincipal has no caller in objectstack, hotcrm (d4ffe99) or docs.objectos.ai (4a00661); the BU wired limb has no production caller; SharingRuleService, ownership queues and PositionGraphService use only expandUsers/expandRoleUsers/holdsRole; approvals uses its own lookupManager. PR head 060935978a = fix plus a merge of main b62463f1.", "tests": "pnpm --filter @objectstack/plugin-sharing test at 872978b4: 65 files / 1387 tests passed. plugin-sharing typecheck exit 0 (check:test-typecheck compiles the test layer). spec typecheck exit 0. business-unit-head-manager-reads.test.ts 18/18 (7 new pins in a top-level describe of the shared real-engine harness). Ablations via ablation-replace on team-graph.ts, subject imported from src by relative path (no build in between): A1 catch-to-null on the read: 3 red (refuses, not cached, expandPrincipal); A2 cache null then rethrow: 1 red (not cached); A3 catch-to-null on expandPrincipal's managerOf call: 1 red (expandPrincipal). Each restored: blob 611685b093ba == HEAD, git diff HEAD empty. Probe after the fix: failed read throws, same instance after recovery answers the manager, absence null.", "gates": "dispatch-gates --commands: 82 families derived at 872978b4, derived again identical at 060935978a. Run with exit codes captured before any pipe; 79 exit 0 first time; 3 exit 3 on prerequisites (check-plugin-teardown-shape --self-test on a shallow clone; check:dual-build-cjs-loads and check:i18n with no dist); all 3 exit 0 at 060935978a after git fetch --unshallow and a full pnpm build (117/117). --ran reconciliation exit 0: 82 derived, 82 run, 0 NOT-MEASURED (pnpm lint recorded as outside the derivation). pnpm lint exit 0 at 872978b4. spec check:generated exit 0. CI check-runs on PR 29836: in_progress at report time, not re-run locally.", "dogfood_battery": "060935978a · os-verify-lock: VERDICT command-exit 0 · held the lock 1439s · Test Files 405 passed, 3 skipped (408) · Tests 3352 passed, 28 skipped (3380) · no failures", "mcp_calls": "0", "api_writes": "2 — fleet-write relay pr_create (objectstack-ai/objectstack PR 29836, read-back 11503 bytes sent = stored); post-stamped issue comment on 29792 (this report). Plus git push of claude/project-thread-ae3krw (not a REST write).", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "none" ], "deviations": "The relay refused an empty labels array (labels must name at least one item); the order named no labels, so pr_create went out with the key omitted and the PR carries none. A mistyped shell variable in this container wrote three scratch gate files to the container root; they hold no secrets, and the safety check refused their removal, so they stay until the container is reclaimed.", "body_amendments": "none" }objectstack-fleet commentedon Nov 3, 2026
domain:server#1 · os-warren · session_01JQwxbXsK9j25sd7eVwW96G
Landing record · #29792 · PR #29836
domain:serverseat 1 · 2026-11-03T07:42ZGET /pulls/29836bbb98c4egit merge-base --is-ancestor bbb98c4e origin/maingit grep -c "kind: 'lookup'" origin/main -- packages/plugins/plugin-sharing/src/team-graph.tsTeamGraphService.managerOfreads through thelookuparm)TeamGraphService.managerOfnow refuses a failedsys_userread instead of answeringnull. Absence still answersnull, and nothing is cached after a failed read.holderReadRefusalhas alookuparm, whichheadOfand the standalonemanagerOfuse too. The spec JSDoc says "refuses on a failed read;nullwhen none". Contract review PASS 6597555062 (CONTRACT_REVIEW_TIER), ACCEPT 6597757705.Fixes;pm:dispatchedremoved in the same stroke as this record. This closes theplugin-sharingfailed-read family (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, 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, plugin-sharing: a failed business-unit head or standalone manager read still answers null and caches it for the pass (headOf, managerOf fallback) #29713, plugin-sharing: TeamGraphService.managerOf still answers null on a failed sys_user read and caches it for the pass #29792). No follow-ups.