Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN by claude[bot] · Pull Request #14136 · objectstack-ai/objectstack · GitHub
Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN by claude[bot] · Pull Request #14136 · objectstack-ai/objectstack · GitHub
Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN by claude[bot] · Pull Request #14136 · objectstack-ai/objectstack · GitHub
Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN by claude[bot] · Pull Request #14136 · objectstack-ai/objectstack · GitHub
Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN by claude[bot] · Pull Request #14136 · objectstack-ai/objectstack · GitHub
Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN by claude[bot] · Pull Request #14136 · objectstack-ai/objectstack · GitHub
Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN by claude[bot] · Pull Request #14136 · objectstack-ai/objectstack · GitHub
Skip to content

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN - #14136

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason
Sep 1, 2026
Merged

fix(plugin-sharing): carry compileCelToFilter's reason and detail into the sharing-rule seeder's skip WARN#14136
os-steve merged 2 commits into
mainfrom
claude/issue-13943-sharing-seeder-compile-reason

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13943

What

celToFilter in the sharing-rule seeder collapsed compileCelToFilter's discriminated refusal { ok: false, reason, detail } to null one 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) and detail (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 sibling celToFilterOutcome keeps the cause; celToFilter stays 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 gained reason and detail.

The call site's own match-all drop (isMatchAllCriteria, where the compiler answers ok so there is no compiler detail to carry) names itself as reason: 'match-all-criteria' — the empty-membership precedent 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 vacuous ok filter), 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)

  • Published signature of celToFilter: unchanged.
  • Surface delta: +1 module-level export (celToFilterOutcome) in bootstrap-declared-sharing-rules.ts. The package's npm surface is unchanged: @objectstack/plugin-sharing exports only ./dist/index.*, and src/index.ts does not re-export this module (verified: zero bootstrap/celToFilter hits in src/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's warnedOverLimit memo. This seam does not: bootstrapDeclaredSharingRules has exactly one production caller, sharing-plugin.ts (seedDeclaredRules), invoked (a) once per organization from the boot loop over resolveRuleSeedPasses (sharing-plugin.ts:679) and (b) once per newly created organization from the sys_organization insert 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 #13943 describe block.
  • Negatives (one test each, per the card):
    • a rule whose condition lowers cleanly emits no log line of any kind (warns asserted empty, not just "no skip WARN") and seeds as before;
    • a rule with a missingcondition takes today's path: same skip branch, same WARN string, never seeded — the meta now names the compiler's own refusal (parse-error / empty expression).
  • Positive: an untranslatable condition (size(record.tags) > 0) is skipped with reason: 'unsupported' and non-empty detail in the WARN meta; celToFilterOutcome unit-tested on refusal / missing / success; wrapper delegation pinned byte-identical on both paths.
  • Ablation (direction predicted first, then observed to match): mutated the WARN meta back to { 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: blob c2ccce614 vs HEAD blob d93189485; marker counts reason: cause.reason 1 to 0, plain-meta shape 0 to 1. Restore proven by state: git hash-object equals the HEAD blob and git diff HEAD is empty. No dist leg: the suite resolves the subject via same-package relative import (source, not dist/), so no rebuild is part of either leg.
  • Typecheck: package typecheck green (both tsc programs, exit 0), and tsc --listFiles proves bootstrap-declared-sharing-rules.ts is inside the program (1 hit). The test file sits outside every tsc program in this package (pre-existing posture: tsconfig.json excludes **/*.test.ts; the test layer is carried as a TEST_DEBT ledger entry of 3 in scripts/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.
  • Gate union re-derived from the actual diff on the merged tree (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, tree dbe2d6ea6, 3 paths): 30 path-derived families + convention-triggered set. Run at dbe2d6ea6: 34 gates exit 0 by their own verdict lines (including check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:i18n and check:i18n-stale-fill after building their declared prerequisite closure, targeted eslint over both edited files, check:nul-bytes).
  • Declared NOT MEASURED locally (CI owns the farm):check:dual-build-cjs-loads exit 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-completeness exit 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

…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
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4916fc4b948d683da065bbc0ca9efa0b6c42759packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9015c51468b7f152713e4c54f788864f9b9c91a — the merge of head dbe2d6ea6c4461f88ecb8f2711e7afab7f41d20f into base e4916fc4b948d683da065bbc0ca9efa0b6c42759, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 09:35
@os-steve
os-steve added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 8d06347Sep 1, 2026
34 checks passed
@os-steve
os-steve deleted the claude/issue-13943-sharing-seeder-compile-reason branch September 1, 2026 09:58
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.

[finding] the sharing-rule seeder discards compileCelToFilter's reason: a skipped rule logs the fact without the why

2 participants

@os-steve@claude