Skip to content

Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

Description

@os-trump

Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

The constraint, as app authors have to state it

An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

  • extractHookBodythrows, naming the free identifiers:
    [hook-body-extract] hook 'x': handler references identifier(s) not in
    scope at runtime: refuse. Module-scope helpers/imports aren't shipped
    with a metadata-only body, so this handler will be BUNDLED instead …
    
  • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

So the failure is invisible at exactly the moment it is cheapest to fix.

What the app pays, today

Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

inlined copyits pin
the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

What would make the mistake impossible

Two asks, either of which retires most of the prose. They are independent.

  1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

  2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

Not asking for

Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

Refs objectstack-ai/hotcrm#1184.

Metadata

Metadata

Assignees

No one assigned

    Labels

    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)) { // 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" + '
      Hook-body lowering fails OPEN: `tryExtractBody` swallows `extractHookBody`'s refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) · Issue #13651 · objectstack-ai/objectstack · GitHub
      Skip to content

      Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

      Description

      @os-trump

      Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

      The constraint, as app authors have to state it

      An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

      That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

      • extractHookBodythrows, naming the free identifiers:
        [hook-body-extract] hook 'x': handler references identifier(s) not in
        scope at runtime: refuse. Module-scope helpers/imports aren't shipped
        with a metadata-only body, so this handler will be BUNDLED instead …
        
      • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

      So the failure is invisible at exactly the moment it is cheapest to fix.

      What the app pays, today

      Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

      inlined copyits pin
      the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
      the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
      the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
      the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
      the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
      the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
      the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
      the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

      Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

      And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

      What would make the mistake impossible

      Two asks, either of which retires most of the prose. They are independent.

      1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

      2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

      Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

      Not asking for

      Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

      Refs objectstack-ai/hotcrm#1184.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        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)) { // 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('^' + ".*" + ' Hook-body lowering fails OPEN: `tryExtractBody` swallows `extractHookBody`'s refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) · Issue #13651 · objectstack-ai/objectstack · GitHub
          Skip to content

          Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

          Description

          @os-trump

          Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

          The constraint, as app authors have to state it

          An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

          That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

          • extractHookBodythrows, naming the free identifiers:
            [hook-body-extract] hook 'x': handler references identifier(s) not in
            scope at runtime: refuse. Module-scope helpers/imports aren't shipped
            with a metadata-only body, so this handler will be BUNDLED instead …
            
          • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

          So the failure is invisible at exactly the moment it is cheapest to fix.

          What the app pays, today

          Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

          inlined copyits pin
          the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
          the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
          the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
          the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
          the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
          the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
          the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
          the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

          Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

          And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

          What would make the mistake impossible

          Two asks, either of which retires most of the prose. They are independent.

          1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

          2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

          Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

          Not asking for

          Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

          Refs objectstack-ai/hotcrm#1184.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            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)) { // 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('^' + ".*" + ' Hook-body lowering fails OPEN: `tryExtractBody` swallows `extractHookBody`'s refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) · Issue #13651 · objectstack-ai/objectstack · GitHub
              Skip to content

              Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

              Description

              @os-trump

              Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

              The constraint, as app authors have to state it

              An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

              That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

              • extractHookBodythrows, naming the free identifiers:
                [hook-body-extract] hook 'x': handler references identifier(s) not in
                scope at runtime: refuse. Module-scope helpers/imports aren't shipped
                with a metadata-only body, so this handler will be BUNDLED instead …
                
              • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

              So the failure is invisible at exactly the moment it is cheapest to fix.

              What the app pays, today

              Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

              inlined copyits pin
              the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
              the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
              the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
              the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
              the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
              the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
              the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
              the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

              Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

              And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

              What would make the mistake impossible

              Two asks, either of which retires most of the prose. They are independent.

              1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

              2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

              Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

              Not asking for

              Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

              Refs objectstack-ai/hotcrm#1184.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                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)) { // 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" + ' Hook-body lowering fails OPEN: `tryExtractBody` swallows `extractHookBody`'s refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) · Issue #13651 · objectstack-ai/objectstack · GitHub
                  Skip to content

                  Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

                  Description

                  @os-trump

                  Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

                  The constraint, as app authors have to state it

                  An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

                  That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

                  • extractHookBodythrows, naming the free identifiers:
                    [hook-body-extract] hook 'x': handler references identifier(s) not in
                    scope at runtime: refuse. Module-scope helpers/imports aren't shipped
                    with a metadata-only body, so this handler will be BUNDLED instead …
                    
                  • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

                  So the failure is invisible at exactly the moment it is cheapest to fix.

                  What the app pays, today

                  Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

                  inlined copyits pin
                  the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
                  the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
                  the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
                  the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
                  the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
                  the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
                  the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
                  the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

                  Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

                  And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

                  What would make the mistake impossible

                  Two asks, either of which retires most of the prose. They are independent.

                  1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

                  2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

                  Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

                  Not asking for

                  Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

                  Refs objectstack-ai/hotcrm#1184.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    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)) { // 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('^' + ".*" + ' Hook-body lowering fails OPEN: `tryExtractBody` swallows `extractHookBody`'s refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) · Issue #13651 · objectstack-ai/objectstack · GitHub
                      Skip to content

                      Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

                      Description

                      @os-trump

                      Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

                      The constraint, as app authors have to state it

                      An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

                      That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

                      • extractHookBodythrows, naming the free identifiers:
                        [hook-body-extract] hook 'x': handler references identifier(s) not in
                        scope at runtime: refuse. Module-scope helpers/imports aren't shipped
                        with a metadata-only body, so this handler will be BUNDLED instead …
                        
                      • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

                      So the failure is invisible at exactly the moment it is cheapest to fix.

                      What the app pays, today

                      Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

                      inlined copyits pin
                      the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
                      the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
                      the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
                      the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
                      the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
                      the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
                      the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
                      the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

                      Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

                      And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

                      What would make the mistake impossible

                      Two asks, either of which retires most of the prose. They are independent.

                      1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

                      2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

                      Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

                      Not asking for

                      Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

                      Refs objectstack-ai/hotcrm#1184.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        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)) { // 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('^' + ".*" + ' Hook-body lowering fails OPEN: `tryExtractBody` swallows `extractHookBody`'s refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) · Issue #13651 · objectstack-ai/objectstack · GitHub
                          Skip to content

                          Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

                          Description

                          @os-trump

                          Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

                          The constraint, as app authors have to state it

                          An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

                          That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

                          • extractHookBodythrows, naming the free identifiers:
                            [hook-body-extract] hook 'x': handler references identifier(s) not in
                            scope at runtime: refuse. Module-scope helpers/imports aren't shipped
                            with a metadata-only body, so this handler will be BUNDLED instead …
                            
                          • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

                          So the failure is invisible at exactly the moment it is cheapest to fix.

                          What the app pays, today

                          Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

                          inlined copyits pin
                          the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
                          the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
                          the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
                          the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
                          the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
                          the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
                          the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
                          the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

                          Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

                          And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

                          What would make the mistake impossible

                          Two asks, either of which retires most of the prose. They are independent.

                          1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

                          2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

                          Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

                          Not asking for

                          Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

                          Refs objectstack-ai/hotcrm#1184.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            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)) { // 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); } })(); })(); Hook-body lowering fails OPEN: `tryExtractBody` swallows `extractHookBody`'s refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) · Issue #13651 · objectstack-ai/objectstack · GitHub
                              Skip to content

                              Hook-body lowering fails OPEN: tryExtractBody swallows extractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651

                              Description

                              @os-trump

                              Filed from the reference app while slimming src/objects/ comments (objectstack-ai/hotcrm#1184). This constraint is written out, by hand, in twelve files of that one directory. It is the single most-repeated platform note in the app, which is the signal that it wants to be a platform affordance rather than prose.

                              The constraint, as app authors have to state it

                              An L2 hook handler is lowered to a metadata-only body.source and evaluated inside QuickJS with no module scope. A handler body that references an import, a top-level const, or even the factory function's own parameter is a ReferenceError at runtime — not a closure.

                              That much is documented and defensible. The part that costs is what happens when an author gets it wrong:

                              • extractHookBodythrows, naming the free identifiers:
                                [hook-body-extract] hook 'x': handler references identifier(s) not in
                                scope at runtime: refuse. Module-scope helpers/imports aren't shipped
                                with a metadata-only body, so this handler will be BUNDLED instead …
                                
                              • …and the CLI build catches that throw and silently bundles the closure instead. Nothing goes red. The hook keeps working locally. It has simply stopped being shippable as pure metadata, and the build says so in one line nobody reads.

                              So the failure is invisible at exactly the moment it is cheapest to fix.

                              What the app pays, today

                              Because the degradation is silent, the reference app cannot rely on the build to catch it, and instead hand-copies every shared value into every handler body and then writes a bespoke test to pin each copy:

                              inlined copyits pin
                              the priority-rank map, twice (case.hook.ts, task.hook.ts)test/priority-rank-parity.test.ts
                              the 16-cell SLA matrix (case.hook.ts, duplicating _case-sla.ts)test/case-sla-matrix.test.ts
                              the country-to-territory table (account.hook.ts, duplicating _territory.ts)test/territory-single-source.test.ts — parses the table out of the LOWERED body
                              the refuse() refusal envelope, in every guard (duplicating _refusal.ts)test/refusal-envelope.test.ts
                              the campaign metric recompute, four timestest/campaign-member-lifecycle.test.ts — asserts all four are character-identical
                              the position-pool names, twice (_case-assignment.ts)test/case-assignment.test.ts
                              the claimable-status set (_case-assignment.ts)test/unassigned-case-triage-reach.test.ts
                              the reference-cleanup shape predicate, three timestest/freeze-guard-reference-cleanup.test.ts

                              Every row is the same shape: a constant declared once, stored N times, and a test whose only job is to notice when the copies drift. That is a platform gap being paid for in app-side test infrastructure.

                              And the app has to say so every time, because the alternative reads as gratuitous duplication to the next author. The comment mass is a direct function of the missing affordance.

                              What would make the mistake impossible

                              Two asks, either of which retires most of the prose. They are independent.

                              1. Fail loudly instead of degrading. The build should refuse — or at minimum emit a lint diagnostic that a gate can fail on — when a registered hook's handler cannot be lowered. Silently swapping a metadata hook for a bundled closure changes the deployment shape of the app; that is not a warning-in-a-log event. (extractHookBody already computes the exact answer; only the catch site drops it.)

                              2. Give a hook body a declared constant surface. Anything that lets a handler name a value the platform ships into the sandbox with it — a constants: key on the hook whose object is serialised into the lowered body, or an author-time inliner that resolves a whitelisted module-scope const at lowering time — collapses every row of the table above into one declaration and deletes its pin.

                              Ask 1 alone turns a silent class into a compile-time one, which is the bigger win. Ask 2 removes the duplication that ask 1 makes visible.

                              Not asking for

                              Reaching module scope at runtime. The body-only sandbox is a deliberate boundary and the app is not trying to cross it — the ask is that the boundary be enforced at author time instead of discovered at review time, and that a legitimate constant not have to be smuggled across it by hand.

                              Refs objectstack-ai/hotcrm#1184.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                Type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions