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
plugin-sharing: TeamGraphService.managerOf still answers null on a failed sys_user read and caches it for the pass #29792
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 expandPrincipalmanager 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 failed sys_user read to null and caches it under org::userId for the pass.
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.ts hard-codes toBeNull() for the wired limb on a failed read. It pins this swallow, so drop that one line when this lands.
S4.holderReadRefusal says "holder set" and "expansion" even for a single head or manager. Give it a third lookup arm rather than a local sentence.
S1 (spec prose, packages/spec). The JSDoc "best-effort" on IBusinessUnitGraphService.headOf and ITeamGraphService.managerOf (packages/spec/src/contracts/sharing-service.ts) should read "refuses on a failed read; null when none". Triage decides whether this rides as a cross-domain single PR or goes to a domain:spec carrier.
Before changing anything, trace every caller of TeamGraphService.managerOf and expandPrincipal. 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.
S1 route: one cross-domain PR in 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 on sharing-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.
Claim: PM loop · card thread · 2026-11-03T05:40Z
Session: session_01JQwxbXsK9j25sd7eVwW96G (the seat); card thread session_01JQwxbXvyuTnWc7sv5i5DBw
Account: os-warren
Branch: claude/project-thread-ae3krw
Worktree: the card thread's own container checkout
Domain: domain:server (one cross-domain PR carrying the packages/spec JSDoc, per triage 6596322022)
Seat: domain:server#1
File surface: packages/plugins/plugin-sharing/src/team-graph.ts (TeamGraphService.managerOf, holderReadRefusallookup arm), packages/plugins/plugin-sharing/src/business-unit-graph.ts (point headOf / standalone managerOf at the lookup arm), their tests incl. business-unit-head-manager-reads.test.ts (S3), packages/spec/src/contracts/sharing-service.ts (JSDoc only, S1), one .changeset/29792-*.md
Container & model: S/M, mode:thread, model: TIER_DEFAULT; the seat runs the CONTRACT_REVIEW_TIER review (narrowing + spec JSDoc), effort: project default
Clause-②: no (narrowing)
Metadata-protocol: none
Responsibility: TeamGraphService.managerOf swallows a failed sys_user read into null and caches it for the pass | none (PR #29647, #29703 and #29787 covered the member, unit, head and standalone-manager reads) | the expandPrincipalmanager arm and the wired BusinessUnitGraphService.managerOf limb during a read outage
Thread-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 on eb05ea2f which contains it
{
"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"
}
1 (TeamGraphService.managerOf reads through the lookup arm)
Landed:TeamGraphService.managerOf now refuses a failed sys_user read instead of answering null. Absence still answers null, and nothing is cached after a failed read. holderReadRefusal has a lookup arm, which headOf and the standalone managerOf use too. The spec JSDoc says "refuses on a failed read; null when none". Contract review PASS 6597555062 (CONTRACT_REVIEW_TIER), ACCEPT 6597757705.
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.