The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

Description

@os-justin

Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

The hint, verbatim on origin/main (66ecc50a)

packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

Why it cannot work — measured, not argued

Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

Why this is expensive rather than merely untidy

The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

Scope — prose only, ⛔ not the mechanism

This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

Suggested shape:

  1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
  2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
  3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

Related — same root mechanism, different surface

#6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

Dedup

Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
    Skip to content

    The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

    Description

    @os-justin

    Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

    Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

    The hint, verbatim on origin/main (66ecc50a)

    packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

    A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

    The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

    Why it cannot work — measured, not argued

    Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

    returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

    The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

    ⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

    ⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

    Why this is expensive rather than merely untidy

    The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

    It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

    ⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

    Scope — prose only, ⛔ not the mechanism

    This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

    Suggested shape:

    1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
    2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
    3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

    Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

    Related — same root mechanism, different surface

    #6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

    ⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

    Dedup

    Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

    Activity

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

    Metadata

    Metadata

    Assignees

    Labels

    bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

      Description

      @os-justin

      Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

      Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

      The hint, verbatim on origin/main (66ecc50a)

      packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

      A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

      The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

      Why it cannot work — measured, not argued

      Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

      returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

      The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

      ⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

      ⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

      Why this is expensive rather than merely untidy

      The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

      It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

      ⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

      Scope — prose only, ⛔ not the mechanism

      This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

      Suggested shape:

      1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
      2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
      3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

      Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

      Related — same root mechanism, different surface

      #6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

      ⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

      Dedup

      Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

      Activity

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

      Metadata

      Metadata

      Assignees

      Labels

      bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

      Type

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

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

        The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

        Description

        @os-justin

        Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

        Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

        The hint, verbatim on origin/main (66ecc50a)

        packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

        A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

        The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

        Why it cannot work — measured, not argued

        Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

        returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

        The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

        ⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

        ⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

        Why this is expensive rather than merely untidy

        The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

        It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

        ⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

        Scope — prose only, ⛔ not the mechanism

        This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

        Suggested shape:

        1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
        2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
        3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

        Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

        Related — same root mechanism, different surface

        #6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

        ⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

        Dedup

        Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

        Activity

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

        Metadata

        Metadata

        Assignees

        Labels

        bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

        Type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

          Description

          @os-justin

          Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

          Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

          The hint, verbatim on origin/main (66ecc50a)

          packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

          A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

          The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

          Why it cannot work — measured, not argued

          Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

          returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

          The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

          ⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

          ⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

          Why this is expensive rather than merely untidy

          The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

          It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

          ⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

          Scope — prose only, ⛔ not the mechanism

          This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

          Suggested shape:

          1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
          2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
          3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

          Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

          Related — same root mechanism, different surface

          #6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

          ⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

          Dedup

          Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

          Activity

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

          Metadata

          Metadata

          Assignees

          Labels

          bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

          Type

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

            Description

            @os-justin

            Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

            Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

            The hint, verbatim on origin/main (66ecc50a)

            packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

            A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

            The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

            Why it cannot work — measured, not argued

            Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

            returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

            The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

            ⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

            ⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

            Why this is expensive rather than merely untidy

            The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

            It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

            ⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

            Scope — prose only, ⛔ not the mechanism

            This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

            Suggested shape:

            1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
            2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
            3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

            Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

            Related — same root mechanism, different surface

            #6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

            ⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

            Dedup

            Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

            Activity

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

            Metadata

            Metadata

            Assignees

            Labels

            bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

            Type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

              Description

              @os-justin

              Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

              Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

              The hint, verbatim on origin/main (66ecc50a)

              packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

              A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

              The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

              Why it cannot work — measured, not argued

              Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

              returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

              The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

              ⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

              ⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

              Why this is expensive rather than merely untidy

              The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

              It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

              ⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

              Scope — prose only, ⛔ not the mechanism

              This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

              Suggested shape:

              1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
              2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
              3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

              Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

              Related — same root mechanism, different surface

              #6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

              ⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

              Dedup

              Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

              Activity

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

              Metadata

              Metadata

              Assignees

              Labels

              bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

              Type

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

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

                The sharing-rule-runtime-variable-condition fix-hint sends authors to RLS to widen a private object, but the layers are AND-composed — the advice cannot work on the case that most needs it #14234

                Description

                @os-justin

                Split out of #14103 by the triage seat. That card asks for a new capability (record-relative sharing recipients) and is a maintainer decision; this half is a defect in shipped guidance and is fixable today, independent of that ruling. ⛔ Filing separately so a true defect does not sit behind a feature decision.

                Filed unassigned. Graded and routed by triage (domain:devx · p2 · Bug).

                The hint, verbatim on origin/main (66ecc50a)

                packages/lint/src/validate-sharing-rule-enforceability.ts, the SHARING_RULE_RUNTIME_VARIABLE_CONDITION finding:

                A criteria sharing rule is MATERIALISED: the seeder compiles ONE static criteria_json per rule and the evaluator writes sys_record_share rows from it, so there is no "current user" for the condition to read. Express per-user access with the mechanism that runs per request instead — an RLS policy on a permission set (rowLevelSecurity[].using, where current_user.* IS resolved), or the record-ownership path. Keep this rule for the part of the predicate that is a property of the RECORD and name the audience through sharedWith.

                The first two sentences are correct and valuable. The bolded remedy is unsound for a private object, which is the sharing model under which an author is most likely to hit this rule.

                Why it cannot work — measured, not argued

                Security layers are AND-composed. packages/plugins/plugin-security/src/security-plugin.ts:4416:

                returnandComposeLayers(andComposeLayers(filter,cbpFilter),sharingFilter)??undefined;

                The file states the same contract in prose at :3956getReadFilter promises "the same filter the engine middleware AND-s into" the query.

                ⇒ the RLS layer (filter) is AND-ed with the sharing layer. On a private object the sharing layer has already excluded the row. An AND term can only remove rows; it can never add one back. So an RLS policy is structurally incapable of granting read access to a row the sharing layer withheld — no matter how it is written.

                ⇒ the hint tells an author, at the exact moment they are trying to widen access on a private object, to go use a mechanism that cannot widen access on a private object.

                Why this is expensive rather than merely untidy

                The advice is not inert — it is attractive. An author who follows it writes an RLS policy that lints clean, passes every gate, and grants nothing. The failure is silent and at the security boundary, where "I wrote the rule and it did not take effect" is the worst possible feedback shape.

                It has a measured victim.#14103 records an application author following exactly this path on duly_log_entry.visibility == 'manager', finding it could not work, and shipping the grant unauthored / fail-closed instead. The advice cost a real implementation attempt.

                ⚠️ And the neighbouring temptation is worse than doing nothing: the one recipient type that resolves to managers at all is position, which shares every matched row with every holder of that position tenant-wide — the skip-level, the manager two teams over. An author steered off RLS and onto position writes an over-broad grant that lints clean. ⇒ this hint sits one step upstream of a real disclosure shape.

                Scope — prose only, ⛔ not the mechanism

                This card is only the guidance defect. ⛔ It does not ask for record-relative recipients (that is #14103, a maintainer decision), and ⛔ it does not ask to change AND-composition (that is the correct and deliberate design).

                Suggested shape:

                1. Qualify the RLS remedy by sharing model — it is sound advice on public_read / read_write objects where RLS narrows from an open baseline, and unsound on private, where the sharing layer is already restrictive and AND cannot widen it.
                2. For the private case, say what is actually true: the only doors that widen a private object are the ADR-0057 depth scopes (which widen by owner, not by predicate) and a sys_record_share row (which only a criteria sharing rule writes) — and name Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either #14103 as the open gap if neither fits.
                3. ⛔ Do not delete the hint. The first two sentences explain why the condition is rejected and are the most useful part of the diagnostic.

                Acceptance: the hint text distinguishes the sharing models; a test pins the private-case wording so it cannot silently regress to the current advice.

                Related — same root mechanism, different surface

                #6736 (open, pm:on-hold, domain:services) — "Authored RLS update-wideners are silently ineffective on the bulk write path — buildWriteFilter ANDs them away (fewer rows, no error)". That is the same truth on the write path: an authored RLS widener is AND-ed away. ⇒ "RLS can widen" is a misconception the platform has now produced defects around twice, in two layers. Worth the two cards knowing about each other; ⛔ not a duplicate — different surface (read vs bulk write), different artefact (a lint hint vs runtime behaviour).

                ⭐ Family note: this is the tenth member of the "false prose is more expensive than missing prose" class the triage seat has been tracking (#13893 · #13916 · #13984 · #13988 · #14006 · #14011 · #14019 · #14039 · #14093), and one of the few with a measured victim rather than an inferred one.

                Dedup

                Searched sharing-rule / RLS / AND-composition / private-object-widening across open and closed: 14 hits, none this. Nearest are #6736 and #8241 above (same mechanism, different surfaces) and the #4983/#4984/#4989/#5009 cluster (all about validateOrgAxisRedLines reading keys the spec does not declare — a different defect). Positive control in the same pass: the query returned #14103 itself and 13 other genuinely sharing/RLS-related cards, so the zero on this subject is a reading, ⛔ not a broken query.

                Activity

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

                Metadata

                Metadata

                Assignees

                Labels

                bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:devxpriority:p2Medium: important, M3

                Type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions