Found while verifying the 17.0.0-rc.1 checklist on #3909 (F2, "switching a rule off actually withdraws access", #3850). Verified on main @ 1ee48bc60, showcase under os serve --dev, SQLite datasource.
Repro
- Object
showcase_private_note (sharingModel: 'private'). Admin owns note N; member B cannot read it (404 — correct baseline).
- Admin authors a criteria rule at runtime:
POST /api/v1/sharing/rules {type:'criteria', name:'rc1_rule_livetest', object:'showcase_private_note', criteria:{title:'rc1 rule target'}, accessLevel:'read', recipientType:'user', recipientId:<B>, active:true} → 200.
- Admin touches N (PATCH) → the rule materializes a
sys_record_share row (source:'rule', source_id:<rule id>); B reads N → 200. ✅ so far.
- Admin saves the same rule with
active:false → 200; GET /sharing/rules/rc1_rule_livetest confirms active:false.
- B reads N → still 200.
- Admin touches N again (the same event that materialized the grant) → B still 200; the
source:'rule' share row is still there.
- Full server restart on the same datastore → B still 200. The rule reads
active:false, the orphaned grant row still answers:
GET /sharing/rules/rc1_rule_livetest → {"active":false,"id":"srule_05574dc1-…"}
GET /data/showcase_private_note/<N>/shares → [{"source":"rule","source_id":"srule_05574dc1-…","level":"read"}]
Once a rule's grant is materialized it is permanent: deactivating the rule never withdraws it — not on the next record touch, not at boot. (And the rule cannot be deleted over REST either — the DELETE route 500s unconditionally, filed separately as its own issue; with both paths dead there is NO way to withdraw a rule grant short of hand-editing sys_record_share.)
Expected
Deactivating (or deleting) a sharing rule withdraws the grants it materialized — that is the #3850 posture the v17 release notes advertise ("switching a rule off actually withdraws access"). At minimum the evaluator/materializer must treat source:'rule' rows whose source_id resolves to an active:false (or missing) rule as void, and the reconciliation that runs on record-touch/boot should sweep them.
Part of the #3909 rc.1 verification (section F2).
Found while verifying the 17.0.0-rc.1 checklist on #3909 (F2, "switching a rule off actually withdraws access", #3850). Verified on
main@1ee48bc60, showcase underos serve --dev, SQLite datasource.Repro
showcase_private_note(sharingModel: 'private'). Admin owns note N; member B cannot read it (404 — correct baseline).POST /api/v1/sharing/rules{type:'criteria', name:'rc1_rule_livetest', object:'showcase_private_note', criteria:{title:'rc1 rule target'}, accessLevel:'read', recipientType:'user', recipientId:<B>, active:true}→ 200.sys_record_sharerow (source:'rule',source_id:<rule id>); B reads N → 200. ✅ so far.active:false→ 200;GET /sharing/rules/rc1_rule_livetestconfirmsactive:false.source:'rule'share row is still there.active:false, the orphaned grant row still answers:Once a rule's grant is materialized it is permanent: deactivating the rule never withdraws it — not on the next record touch, not at boot. (And the rule cannot be deleted over REST either — the DELETE route 500s unconditionally, filed separately as its own issue; with both paths dead there is NO way to withdraw a rule grant short of hand-editing
sys_record_share.)Expected
Deactivating (or deleting) a sharing rule withdraws the grants it materialized — that is the #3850 posture the v17 release notes advertise ("switching a rule off actually withdraws access"). At minimum the evaluator/materializer must treat
source:'rule'rows whosesource_idresolves to anactive:false(or missing) rule as void, and the reconciliation that runs on record-touch/boot should sweep them.Part of the #3909 rc.1 verification (section F2).