Skip to content

fix(app-showcase): retire the sharing rules no gate can consult, keep the demo where it enforces - #9697

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-9237-inert-showcase-sharing-rules
Aug 18, 2026
Merged

fix(app-showcase): retire the sharing rules no gate can consult, keep the demo where it enforces#9697
os-project-manager merged 2 commits into
mainfrom
claude/issue-9237-inert-showcase-sharing-rules

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#9237

Premise re-verified on current main (c07d6e8b9), then measured past the card

Booted examples/app-showcase (pnpm dev -- --fresh -p 39237). The card's two WARNs are real and reproduce verbatim:

⚠ Boot diagnostics — 3 warnings logged during startup:
WARN SharingServicePlugin: boot rule backfill failed for rule {"rule":"share_open_tasks_with_manager","error":"SHARING_NOT_ENABLED: 'showcase_task' is not under record-sharing enforcement (public sharing model or no 'owner_id' field); a share row on it would never be consulted"}
WARN SharingServicePlugin: boot rule backfill failed for rule {"rule":"share_red_projects_with_execs","error":"SHARING_NOT_ENABLED: 'showcase_project' is not under record-sharing enforcement (public sharing model or no 'owner_id' field); a share row on it would never be consulted"}

The card's count is short, and the missing one is the worse case. Three rules sat on those two objects, not two. share_high_value_red_projects_with_managers is in exactly the same state and produced no diagnostic at all: its compound condition (health == 'red' && budget > 100000) matched no seeded row — the only red project, "Compliance Audit", carries budget: 90_000 — so reconcile built an empty desired set, never called grant, and never threw. A rule can be just as dead without a warning to notice, which is why the guard added here reads the declaration rather than the boot log.

Why re-homing them onto another public object was not an option

showcase_project and showcase_task declare sharingModel: 'public_read_write' by deliberate ADR-0090 D1 grandfather stamp. Sharing only ever WIDENS the OWD baseline and there is nothing above public_read_write, so SharingService.inertGrantReason returns a verdict and assertNotInertGrant (ADR-0111 D7) refuses every grant those rules reconcile. That OWD is also load-bearing well beyond the security demo — it is what lets a showcase_contributor PATCH a project row it did not create, the write floor pinned by owd-public-read-write-write-floor.dogfood.test.ts — so it was left untouched.

That leaves ADR-0049 enforce-or-remove with one honest move. All three are removed, not re-homed onto another public object: share_open_tasks_with_manager was itself a repair (it replaced the owner-based share_contributor_tasks_with_manager, silently skipped at seed time under ADR-0078) and that repair moved the inertness instead of removing it. The lesson is written into the file rather than repeated.

The two capabilities the retired rules carried are kept. A position recipient and a compound CEL condition (ADR-0058 D3) now live on share_key_account_qualified_contacts_with_managers, targeting showcase_contact — OWD private, and the showcase_manager set grants it allowRead, which is the object-level bit a share row still needs before any record-level widening can be observed.

Acceptance — measured in the artifact the card is about

Re-booted the fixed tree (pnpm dev -- --fresh -p 39238):

⚠ Boot diagnostics — 1 warning logged during startup:
WARN [metadata-protocol] seed/API tenancy split detected … showcase_field_zoo.f_autonumber (#8686)

Three warnings to one; zero sharing WARNs (grep -c "boot rule backfill failed" = 0). The one that remains is the pre-existing, unrelated #8686 autonumber tenancy split, present on main before this change.

A second boot against a persistent OS_HOME shows the fixture doing something it has never done on a stock boot — materialising a rule-sourced grant:

sys_sharing_rule 2 rows: share_new_inquiries_with_field_ops (showcase_inquiry, unit_and_subordinates)
share_key_account_qualified_contacts_with_managers (showcase_contact, position:manager)
criteria_json {"$and":[{"stage":"qualified"},{"company":"Northwind"}]}
sys_record_share source=rule 1 row : showcase_contact / Nora West → the manager holder, access_level read

Three seeded contacts are qualified (Nora West, Carl Fox, 张伟) and exactly one of those is at Northwind, so the AND is demonstrated on live seed data in both directions: a row satisfying either clause alone is not shared. Before this change the stock showcase materialised zero rule-sourced share rows on any boot.

Reverse verification

Prediction recorded before running, then observed exactly:

predictedobserved
inert-wirings §6 OWD armred, naming 3 offendersred, naming share_red_projects_with_execs → showcase_project, share_high_value_red_projects_with_managers → showcase_project, share_open_tasks_with_manager → showcase_task
§6 audience-read armgreengreen
every other showcase testgreengreen
totals1 failed / 336 passed1 failed / 336 passed

Ablation = git restore --source=origin/main of the authored rules with the new guard kept; the fix was committed first, so restoring was git checkout HEAD -- ….

336 pre-existing tests stayed green under the ablation — including no-startup-warnings.test.ts, the #3420 "the official examples must boot with ZERO warnings" guard, which is scoped to un-acknowledged password fields and cannot see this. That is why the defect survived.

The guard

inert-wirings.test.ts gains section 6, alongside the four declared-but-inert wirings already pinned there. It fails the build in both shapes: a rule anchored where the OWD leaves nothing to widen, and a rule whose audience holds no allowRead on the object it shares — the second arm being what stops the retired rules from being "fixed" by moving them onto a private object their recipient cannot read either. The runtime's other inertness arm (no owner_id) is deliberately not reproduced: owner_id is injected by the schema registry, so it is absent from the authored metadata the guard reads and present on the schema the runtime judges.

Scoped honestly for what it is — a narrow declaration-consistency check on this app's sharing rules, not coverage of the whole declared-but-inert channel. The platform-wide os lint rule that would close the class for every app is filed separately rather than smuggled in here.

Consumers moved with the rules

  • showcase-d3-d4-capabilities.dogfood.test.ts — the ADR-0058 D3 cited proof, retargeted onto contacts and given the half it could never carry: its header claimed the compound condition "compiles … and enforces" while the object it ran on made every grant refusable, so only the compile-and-match half was ever asserted. A new case grants on showcase_contact (accepted) and on showcase_project (refused SHARING_NOT_ENABLED), asserting the code and the object.
  • showcase-declarative-rbac-seeding.dogfood.test.ts — the retirement assertion now covers all three generations of this ADR-0078 lesson.
  • org-scoped-sharing-rule-listing.dogfood.test.ts — the seeded-rule name list.
  • docs/qa/platform-checklist/areas/access-security.jsonaccess-security.record-share-grant-revoke's evaluate clause was unrunnable as written and had been since it was authored: it drove share_red_projects_with_execs, whose evaluate is refused rather than returning a reconcile result. Retargeted, with a step that assigns the manager position so the recipient expansion is non-empty. Recorded as revision 3 on the item.

No golden was re-baselined; access-matrix.json is untouched, and no sharingModel changed.

Verification

Gate union re-derived from the actual changed paths off git merge-base (node scripts/pm/dispatch-gates.mjs), run at head 17fc6a40b — the final commit. All green:

check:changeset-gate-self-tests · check:cross-package-test-inputs · check:objectui-changeset · check:test-source-alias · check:type-source-resolution · check-adr-0087-registration · check-changeset-no-major · check-empty-changeset · check:nul-bytes · check:platform-checklist · spec check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs · check:engine-double-contract · check:where-matcher · check:query-options-erasure · check:type-check-coverage · check:type-check-debt · docs-audit/check-affected-docs

Package suites: @objectstack/example-showcase typecheck + 337 tests pass (21 files); @objectstack/dogfood typecheck passes and the three changed suites pass (14 tests).


Generated by Claude Code

…ome the position/compound demo
`showcase_project` and `showcase_task` are `sharingModel: 'public_read_write'`
by deliberate ADR-0090 D1 declaration, so sharing has nothing left to widen
there and `assertNotInertGrant` refuses every grant a rule on them would
materialise. Two rules failed their boot backfill on every boot and a third was
dead without any diagnostic. ADR-0049 enforce-or-remove: removed.
The capabilities they carried — a `position` recipient and a compound CEL
condition (ADR-0058 D3) — move to `showcase_contact` (OWD `private`, and the
`showcase_manager` set grants it `allowRead`), where the grant is real.
inert-wirings.test.ts gains the guard that fails the build on the next such
declaration, in both of its shapes.
…hat enforces
The ADR-0058 D3 proof gains the half it could never carry on a public-OWD
object: that the grant its compound rule reconciles is one the gates CONSULT,
with the refusal on showcase_project as the contrast.
The platform-checklist evaluate clause was unrunnable as written — it drove a
rule whose evaluate is refused SHARING_NOT_ENABLED rather than returning a
reconcile result. It now drives the surviving rule, and a step assigns the
manager position so the recipient expansion is non-empty.
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

@os-project-manager
os-project-manager marked this pull request as ready for review August 18, 2026 15:57
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM ruling — A, as shipped. Flipping to ready and arming auto-merge.

The measurement that decided this card, and it is not the one the card asked for

I flagged at dispatch that the card names two rules while three sit on those two objects, and that establishing the true set "changes the deliverable." It did, and in the direction that matters most:

share_high_value_red_projects_with_managers produced no diagnostic at all — its compound condition matched no seeded row (the only red project, "Compliance Audit", carries budget: 90_000 against a > 100000 threshold), so reconcile built an empty desired set, never called grant, and never threw.

The WARN is a function of the seed data, not of the declaration. A rule can be exactly as dead with no warning to notice — and the third rule was the one nobody would have found. That single fact is what makes the guard read the authored declaration rather than the boot log, and it is why option C is refused on evidence rather than on taste: a waiver keyed on the boot log would have accounted for two of three and hidden the worst one. That is the ADR-0078 defect wearing a hat, and the card's own history shows it happening once already.

Why not B, now that the blast radius is measured rather than feared

I set the OWD fence as stop-and-report precisely so this would be decided on numbers. You brought them: flipping showcase_project / showcase_task is a reversal of #8023 — whose entire subject is that public_read_write is what lets a showcase_contributor PATCH a row it did not create — moves 4 access-matrix.json entries, and sits upstream of 22 dogfood suites including the P0 showcase-crud-persona-matrix that #9308 reasons about.

⭐ And the argument that settles it independently of cost: B would still leave the third rule silently matching nothing. An option that does not fix the worst case is not the safer option, it is the more expensive one.

Under ADR-0049 enforce-or-remove, an object deliberately declared public_read_write is a place where sharing cannot be enforced. Removal is the enforce-or-remove answer there, not a coverage loss — and nothing that worked was dropped: both capabilities the retired rules advertised (a position recipient, a compound CEL condition under ADR-0058 D3) now materialise a real grant instead of failing a backfill.

⭐ Removing rather than re-homing is also the right reading of this file's history. share_open_tasks_with_managerwas itself the repair for an inert type: 'owner' rule under ADR-0078 — and that repair moved the inertness instead of removing it. This is the third generation of one lesson; ending it is worth more than preserving a narrative about projects and tasks.

The acceptance is in the artifact, and it is stronger than "the warnings went away"

Boot diagnostics 3 → 1 with zero sharing WARNs is the card's literal ask. The part worth more: a persistent-OS_HOME boot shows sys_record_share holding one rule-sourced row — Nora West, the one qualified Northwind contact of three qualified — where the stock showcase previously materialised zero rule-sourced share rows on any boot. The AND is demonstrated narrowing in both directions on live seed data. The fixture now does the thing it advertised.

And the why-it-survived measurement: 336 pre-existing tests stayed green under the ablation, including no-startup-warnings.test.ts — the #3420 "official examples must boot with ZERO warnings" guard, which is scoped to un-acknowledged password fields and cannot see this.

Two things you found beyond the card, both correctly handled

  • showcase-d3-d4-capabilities.dogfood.test.ts claimed the compound condition "compiles … and enforces" while the object it ran on made every grant refusable — so only the compile half was ever asserted. The cited proof for ADR-0058 D3 was half a proof. Now grants on showcase_contact (accepted) and on showcase_project (refused SHARING_NOT_ENABLED), asserting the code and the object.
  • access-security.record-share-grant-revoke's evaluate clause was unrunnable as written and had been since it was authored — it drove a rule whose evaluate is refused rather than returning a reconcile result. Retargeted at revision 3.

That second one owes a follow-up and I am recording it rather than leaving it: under #9308's wave-sequencing ruling, QA wave #9296's rounds R1–R3 stay pinned at e4e5c6e3 and seed changes land on main independently, with the affected items getting a dedicated follow-up run at a new sha. That item becomes runnable for the first time when this merges, so the follow-up is owed on it. ⚠️ It is not a blocker for this PR and I am not holding the merge for it.

Scope framing — held

The guard is named for what it is: a narrow declaration-consistency check on this app's sharing rules, not coverage of the declared-but-inert channel. The platform-wide os lint rule that would close the class for every app is #9698, filed separately rather than smuggled in. Deliberately not reproducing the runtime's owner_id arm — because owner_id is injected by the schema registry and so is absent from the authored metadata the guard reads — is the kind of limit worth stating rather than papering over.

Flipped and armed.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

app-showcase: two sharing rules declared on objects without record sharing enabled — boot backfill fails, rules never take effect

1 participant

@os-project-manager