Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136
Conversation
…to the seeder's skip WARN
The sharing-rule seeder collapsed the compiler's discriminated refusal
{ ok: false, reason, detail } to null one line before the WARN that
needed it. celToFilterOutcome keeps the cause (the rls-compiler
compileExpressionOutcome shape, one seam over); celToFilter stays at its
published signature and delegates. The skip decision is unchanged
(ADR-0049: never seeded as match-all).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs…aring-seeder-compile-reason
📓 Docs Drift Check6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c9015c51468b7f152713e4c54f788864f9b9c91a && git checkout c9015c51468b7f152713e4c54f788864f9b9c91a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e4916fc4b948d683da065bbc0ca9efa0b6c42759 dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f && git checkout -B drift-repro e4916fc4b948d683da065bbc0ca9efa0b6c42759 && git merge --no-ff dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f
node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#13943
What
celToFilterin the sharing-rule seeder collapsedcompileCelToFilter's discriminated refusal{ ok: false, reason, detail }tonullone line before the only WARN that could surface it — so an operator whose declared rule was silently not granting learned that the condition did not translate, and got the source text back, but never why. The seeder's skip WARN now carries bothreason(the aggregatable category) anddetail(the concrete fault: the refused shape, the variable path, the parse bound) in its meta, per the scope ruling on the card.Shape follows PR #13942 one seam over (
plugin-security/src/rls-compiler.ts): a new siblingcelToFilterOutcomekeeps the cause;celToFilterstays exactly at its published signature ((cel: unknown) => Record | null) and delegates. The skip decision is byte-identical — an unlowerable or match-all condition is still never seeded as a permissive match-all (ADR-0049, this card's control surface, untouched). The WARN message string is also byte-identical; only its meta gainedreasonanddetail.The call site's own match-all drop (
isMatchAllCriteria, where the compiler answersokso there is no compiler detail to carry) names itself asreason: 'match-all-criteria'— theempty-membershipprecedent from #13942, so no skip line is left with a fact and no reason. That arm is defensive (the compiler does not currently produce a vacuousokfilter), which is why it has no end-to-end test; its cause composition is visible in the diff.Tier declaration — Clause-②: yes (pre-emptive, per dispatch)
celToFilter: unchanged.celToFilterOutcome) inbootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged:@objectstack/plugin-sharingexports only./dist/index.*, andsrc/index.tsdoes not re-export this module (verified: zerobootstrap/celToFilterhits insrc/index.ts) — the export is package-internal, consumed by the module's caller and the package's own tests.Dedup — measured, and deliberately not added
#13942's seam ran on every read and reused
cel-to-filter.ts'swarnedOverLimitmemo. This seam does not:bootstrapDeclaredSharingRuleshas exactly one production caller,sharing-plugin.ts(seedDeclaredRules), invoked (a) once per organization from the boot loop overresolveRuleSeedPasses(sharing-plugin.ts:679) and (b) once per newly created organization from thesys_organizationinsert middleware (sharing-plugin.ts:704). WARN volume is therefore bounded by (skipped declared rules) x (organizations) per boot, plus (skipped declared rules) per organization created — not per request, not per read. A memo would suppress precisely the per-organization lines that tell an operator which organization's seeding skipped a rule. No dedup added.Verification (all readings at merged HEAD
dbe2d6ea6)pnpm --filter @objectstack/plugin-sharing exec vitest run src/sharing-rule.test.ts: Test Files 1 passed, Tests 112 passed (112) — includes the new#13943describe block.warnsasserted empty, not just "no skip WARN") and seeds as before;conditiontakes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error/empty expression).size(record.tags) > 0) is skipped withreason: 'unsupported'and non-emptydetailin the WARN meta;celToFilterOutcomeunit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.{ rule, condition }(discarding the cause). Predicted RED on the two cause-asserting tests, GREEN on the clean-path negative and the outcome unit test (declared controls — green in both directions, not ablation evidence). Observed on the mutated tree: vitest exit 1, exactly the two predicted tests failed, 110 passed. Mutation proven on disk: blobc2ccce614vs HEAD blobd93189485; marker countsreason: cause.reason1 to 0, plain-meta shape 0 to 1. Restore proven by state:git hash-objectequals the HEAD blob andgit diff HEADis empty. No dist leg: the suite resolves the subject via same-package relative import (source, notdist/), so no rebuild is part of either leg.typecheckgreen (both tsc programs, exit 0), andtsc --listFilesprovesbootstrap-declared-sharing-rules.tsis inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture:tsconfig.jsonexcludes**/*.test.ts; the test layer is carried as aTEST_DEBTledger entry of 3 inscripts/check-type-check-coverage.mjs). Re-measured by the ledger's own method (tsc with the exclusion lifted): still exactly 3 errors, the identical pre-existing set — the new test lines are type-clean and the ratchet does not move.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, treedbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run atdbe2d6ea6: 34 gates exit 0 by their own verdict lines (includingcheck:engine-double-contract,check:where-matcher,check:query-options-erasure,check:type-check-coverage,check:i18nandcheck:i18n-stale-fillafter building their declared prerequisite closure, targeted eslint over both edited files,check:nul-bytes).check:dual-build-cjs-loadsexit 3 — its own text: "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ … This is NOT a pass: nothing was measured" (population is every publishable package's dist; CI's required Build Core job builds exactly that).check-test-completenessexit 3 — "Nothing was measured: this gate exited before parsing a single summary line" (it reads the CI test farm's summary output, which does not exist locally). Neither is reported as pass or red.skills/**untouched. No spec/generated artifacts touched. Changeset included (patch,@objectstack/plugin-sharing).Generated by Claude Code
Generated by Claude Code