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
A sharing rule on an object without record sharing saves and silently does nothing #30621
Found by the #30209 card thread while measuring row 5 (os-dev-report 6665615192 on PR #30620, out_of_scope_findings, class (c) trap).
What
An administrator can save a sharing rule (sys_sharing_rule) on an object that is not under record-sharing enforcement (measured: sys_session, sys_account, sys_api_key). The save answers 201. The reconcile then logs WARN SHARING_NOT_ENABLED and writes no sys_record_share, so the rule's recipients see nothing more than before. Nothing tells the administrator: the rule sits in Setup looking active and never applies.
Measured: no widening. The rule grants nothing, so this is a misleading-configuration trap, not an access problem.
Expected
The administrator learns at save time that the object does not use record sharing. Either the save door refuses a rule whose object is not under enforcement, with a sentence naming the object and how to enable sharing for it, or the rule is saved and carries a visible "not in effect" state. Refusal is the simpler contract; pick per the existing sharing-rule validation in packages/plugins/plugin-sharing.
Related: #30209 row 5 (the rule's object picker lists every object) is a separate decision on the same surface: option C there is this card's refusal.
Surface
packages/plugins/plugin-sharing (the sys_sharing_rule save path / validation) and its tests; changeset per the outcome (a save refusal is a narrowing).
A sharing rule on an object without record sharing saves and does nothing. It should be refused at save. It over-grants nothing, so p3.
Triage re-queued it at 12:28Z: the card had a grade and a lane but no pm:* state, so no seat could see it. That was a triage sweep miss, fixed this round.
Claim: PM loop round 1 · 2026-11-06T12:55Z
Session: session_016WV4czvZykUqhYxYXSSDt4
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: the card thread's designated claude/project-thread-* branch, re-posted in a whole claim once the thread reports it
Worktree: objectstack-issue-30621
Domain: domain:server
Seat: domain:server#4
File surface: the sys_sharing_rule save path and its validation in packages/plugins/plugin-sharing/**; their tests; the docs sentences this makes false; one root .changeset/ file. ⛔ packages/spec/** (a new error code needs a ledger row: stop, and the seat files the domain:spec sub-card, unless the code already exists), ⛔ ui/** (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default, effort: medium
Clause-②: no (narrowing) (provisional: refusing a save that answers 201 today narrows the save door's accept set)
Metadata-protocol: none
Responsibility: n/a — misleading-configuration trap found by the #30209 card thread
Thread-read: 6685173622
Premise: card body and triage 6685173622 (p3 kept). The save door refuses a sharing rule whose object is not under record-sharing enforcement, with a sentence that names the object and how to enable sharing for it. This is the simpler contract the card prefers. Existing rules on such objects are listed in the report, not migrated.
Serial constraints cleared: the dev re-reads open PRs touching packages/plugins/plugin-sharing at branch start
Contract review owed (the narrowing) before the queue.
Claim: PM loop round 1 (whole claim re-posted with the branch) · 2026-11-06T13:02Z
Session: session_016WV4czvZykUqhYxYXSSDt4
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/project-thread-lpcxlh
Dev session: session_016WV4cyFB75QwwCDu3S5GNZ (card thread; base 33043bd8)
Worktree: objectstack-issue-30621
Domain: domain:server
Seat: domain:server#4
File surface: the sys_sharing_rule save path and its validation in packages/plugins/plugin-sharing/**; their tests; the docs sentences this makes false; one root .changeset/ file. ⛔ packages/spec/** (the refusal reuses the existing SHARING_NOT_ENABLED 422, already in the plugin-sharing ledger row; a new code would need a ledger row: stop, and the seat files the domain:spec sub-card), ⛔ ui/** (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default, effort: medium
Clause-②: no (narrowing) (provisional: refusing a save that answers 201 today narrows the save door's accept set)
Metadata-protocol: none
Responsibility: n/a — misleading-configuration trap found by the #30209 card thread
Thread-read: 6685173622
Premise: card body and triage 6685173622 (p3 kept). The save door refuses a sharing rule whose object is not under record-sharing enforcement, with a sentence that names the object and how to enable sharing for it. This is the simpler contract the card prefers. Existing rules on such objects are listed in the report, not migrated. The door reuses SharingService's own inert-grant verdict, the one the reconcile raises SHARING_NOT_ENABLED on.
Serial constraints cleared: the dev re-reads open PRs touching packages/plugins/plugin-sharing at branch start
Contract review owed (the narrowing) before the queue.
{
"issue": 30621,
"status": "done",
"branch": "claude/project-thread-lpcxlh",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30953",
"session": "session_016WV4cyFB75QwwCDu3S5GNZ",
"premise_still_valid": true,
"summary": "Both sharing-rule save doors now refuse a rule whose object is not under record-sharing enforcement with SHARING_NOT_ENABLED 422 and save nothing: the /data door through a new sys_sharing_rule hook guard (bindRuleObjectEnforcementGuard, bound beside the criteria guard; an update is checked only when it moves the rule to another object) and SharingRuleService.defineRule for non-system callers. The verdict is the reconcile's own: SharingService.sharingRuleObjectRefusal reads the same objectInertReason that assertNotInertGrant now calls, plus a per-arm remedy sentence naming the object. Existing code SHARING_NOT_ENABLED (already in the plugin-sharing ledger row), no spec change; public objects with an owner column stay admitted (#25549 external recipients); system writes pass as with the declared-name guard.",
"tests": "head 6a24dc2567 (code unchanged since 1390d368). New sharing-rule-object-enforcement.test.ts (real ObjectQL + SqlDriver): before fix 8 failed (8), after 8 passed (8). Ablation via scripts/ablation-replace.mjs on sharingRuleObjectRefusal (if (!reason) -> if (reason || !reason)): 4 failed | 4 passed (the 4 refusal pins red, 4 controls green); restore blob == HEAD 2afbcaad0cda, git diff HEAD empty; suite imports src/ relatively, no dist on the path. plugin-sharing pnpm test: Test Files 68 passed (68), Tests 1444 passed (1444); pnpm typecheck incl. check:test-typecheck exit 0 (lock VERDICT command-exit 0). Scratch probe (not committed) of rest mapDataError/classifiedRefusalAnswer: 422 SHARING_NOT_ENABLED with the sentence on both doors.",
"gates": "dispatch-gates --commands: 96 families over 10 paths; all 96 ran on 6a24dc2567 with exit 0; --ran: '96 derived famil(ies) accounted for — 96 run, 0 NOT-MEASURED'. First pass on 1390d368: 95 green, check:system-context-census red ([read-without-a-row] bindRuleObjectEnforcementGuard, defineRule), fixed in 6a24dc2567 by keying census row 40 and --write. Prereqs: unshallow fetch + whole-tree turbo build 119/119. CI check-runs: in_progress at report time (seat reads them).",
"dogfood_battery": "1390d368 (6a24dc2567 adds only a docs census row) · os-verify-lock: VERDICT command-exit 0 · Test Files 425 passed | 3 skipped (428) · Tests 3543 passed | 41 skipped (3584) · no failures",
"mcp_calls": "0",
"api_writes": "2 — fleet-write pr_create (POST pulls, #30953) and this os-dev-report comment (POST issues/30621/comments); git push x2 is not REST",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"observation, not filed · lint validate-sharing-rule-enforceability anchorFindings reports a declared rule on a public-model object as inert, while the runtime admits a public object with an owner column for external recipients (#25549); read-only inference, no reach measured · carrier: none",
"observation, not filed · #30209 row 5 (the rule form's object picker lists every object) remains; this PR is that card's option C on the save side only · carrier: #30209"
]
}
Landing record · 2026-11-06T16:48Z · PR #30953 merged through the queue as ee874b1f5e. Before that: the seat's ACCEPT 6688136157 on head dc804fbb, contract review PASS 6687390627 on 6a24dc25, delta review PASS 6688105703 on dc804fbb, and the maintainer's BREAKING word 6688116131. A sharing rule on an object without record-sharing enforcement is now refused at both save doors with 422 SHARING_NOT_ENABLED. Stored rules are not migrated. Card closed completed by Fixes; pm:dispatched removed in this stroke. #30209 row 5 (the object picker) remains with its card.
Activity
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 kept ·
domain:server· re-queued · 2026-11-06T12:47Z⛔ Not a claim.
pm:*state, so no seat could see it. That was a triage sweep miss, fixed this round.Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Claim: PM loop round 1 · 2026-11-06T12:55Z
Session:
session_016WV4czvZykUqhYxYXSSDt4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch: the card thread's designated
claude/project-thread-*branch, re-posted in a whole claim once the thread reports itWorktree:
objectstack-issue-30621Domain:
domain:serverSeat:
domain:server#4File surface: the
sys_sharing_rulesave path and its validation inpackages/plugins/plugin-sharing/**; their tests; the docs sentences this makes false; one root.changeset/file. ⛔packages/spec/**(a new error code needs a ledger row: stop, and the seat files thedomain:specsub-card, unless the code already exists), ⛔ui/**(stop on breach; explain in the report)Container & model:
S,mode:thread,model: default,effort: mediumClause-②: no (narrowing) (provisional: refusing a save that answers
201today narrows the save door's accept set)Metadata-protocol: none
Responsibility: n/a — misleading-configuration trap found by the #30209 card thread
Thread-read: 6685173622
Premise: card body and triage 6685173622 (p3 kept). The save door refuses a sharing rule whose object is not under record-sharing enforcement, with a sentence that names the object and how to enable sharing for it. This is the simpler contract the card prefers. Existing rules on such objects are listed in the report, not migrated.
Serial constraints cleared: the dev re-reads open PRs touching
packages/plugins/plugin-sharingat branch startContract review owed (the narrowing) before the queue.
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Claim: PM loop round 1 (whole claim re-posted with the branch) · 2026-11-06T13:02Z
Session:
session_016WV4czvZykUqhYxYXSSDt4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-lpcxlhDev session:
session_016WV4cyFB75QwwCDu3S5GNZ(card thread; base33043bd8)Worktree:
objectstack-issue-30621Domain:
domain:serverSeat:
domain:server#4File surface: the
sys_sharing_rulesave path and its validation inpackages/plugins/plugin-sharing/**; their tests; the docs sentences this makes false; one root.changeset/file. ⛔packages/spec/**(the refusal reuses the existingSHARING_NOT_ENABLED422, already in the plugin-sharing ledger row; a new code would need a ledger row: stop, and the seat files thedomain:specsub-card), ⛔ui/**(stop on breach; explain in the report)Container & model:
S,mode:thread,model: default,effort: mediumClause-②: no (narrowing) (provisional: refusing a save that answers
201today narrows the save door's accept set)Metadata-protocol: none
Responsibility: n/a — misleading-configuration trap found by the #30209 card thread
Thread-read: 6685173622
Premise: card body and triage 6685173622 (p3 kept). The save door refuses a sharing rule whose object is not under record-sharing enforcement, with a sentence that names the object and how to enable sharing for it. This is the simpler contract the card prefers. Existing rules on such objects are listed in the report, not migrated. The door reuses SharingService's own inert-grant verdict, the one the reconcile raises
SHARING_NOT_ENABLEDon.Serial constraints cleared: the dev re-reads open PRs touching
packages/plugins/plugin-sharingat branch startContract review owed (the narrowing) before the queue.
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4cyFB75QwwCDu3S5GNZ
os-dev-report
{ "issue": 30621, "status": "done", "branch": "claude/project-thread-lpcxlh", "pr": "https://github.com/objectstack-ai/objectstack/pull/30953", "session": "session_016WV4cyFB75QwwCDu3S5GNZ", "premise_still_valid": true, "summary": "Both sharing-rule save doors now refuse a rule whose object is not under record-sharing enforcement with SHARING_NOT_ENABLED 422 and save nothing: the /data door through a new sys_sharing_rule hook guard (bindRuleObjectEnforcementGuard, bound beside the criteria guard; an update is checked only when it moves the rule to another object) and SharingRuleService.defineRule for non-system callers. The verdict is the reconcile's own: SharingService.sharingRuleObjectRefusal reads the same objectInertReason that assertNotInertGrant now calls, plus a per-arm remedy sentence naming the object. Existing code SHARING_NOT_ENABLED (already in the plugin-sharing ledger row), no spec change; public objects with an owner column stay admitted (#25549 external recipients); system writes pass as with the declared-name guard.", "tests": "head 6a24dc2567 (code unchanged since 1390d368). New sharing-rule-object-enforcement.test.ts (real ObjectQL + SqlDriver): before fix 8 failed (8), after 8 passed (8). Ablation via scripts/ablation-replace.mjs on sharingRuleObjectRefusal (if (!reason) -> if (reason || !reason)): 4 failed | 4 passed (the 4 refusal pins red, 4 controls green); restore blob == HEAD 2afbcaad0cda, git diff HEAD empty; suite imports src/ relatively, no dist on the path. plugin-sharing pnpm test: Test Files 68 passed (68), Tests 1444 passed (1444); pnpm typecheck incl. check:test-typecheck exit 0 (lock VERDICT command-exit 0). Scratch probe (not committed) of rest mapDataError/classifiedRefusalAnswer: 422 SHARING_NOT_ENABLED with the sentence on both doors.", "gates": "dispatch-gates --commands: 96 families over 10 paths; all 96 ran on 6a24dc2567 with exit 0; --ran: '96 derived famil(ies) accounted for — 96 run, 0 NOT-MEASURED'. First pass on 1390d368: 95 green, check:system-context-census red ([read-without-a-row] bindRuleObjectEnforcementGuard, defineRule), fixed in 6a24dc2567 by keying census row 40 and --write. Prereqs: unshallow fetch + whole-tree turbo build 119/119. CI check-runs: in_progress at report time (seat reads them).", "dogfood_battery": "1390d368 (6a24dc2567 adds only a docs census row) · os-verify-lock: VERDICT command-exit 0 · Test Files 425 passed | 3 skipped (428) · Tests 3543 passed | 41 skipped (3584) · no failures", "mcp_calls": "0", "api_writes": "2 — fleet-write pr_create (POST pulls, #30953) and this os-dev-report comment (POST issues/30621/comments); git push x2 is not REST", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "observation, not filed · lint validate-sharing-rule-enforceability anchorFindings reports a declared rule on a public-model object as inert, while the runtime admits a public object with an owner column for external recipients (#25549); read-only inference, no reach measured · carrier: none", "observation, not filed · #30209 row 5 (the rule form's object picker lists every object) remains; this PR is that card's option C on the save side only · carrier: #30209" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Landing record · 2026-11-06T16:48Z · PR #30953 merged through the queue as
ee874b1f5e. Before that: the seat's ACCEPT 6688136157 on headdc804fbb, contract review PASS 6687390627 on6a24dc25, delta review PASS 6688105703 ondc804fbb, and the maintainer's BREAKING word 6688116131. A sharing rule on an object without record-sharing enforcement is now refused at both save doors with422 SHARING_NOT_ENABLED. Stored rules are not migrated. Card closed completed byFixes;pm:dispatchedremoved in this stroke. #30209 row 5 (the object picker) remains with its card.