Repository navigation
Should os build fail by default on the accidental hook-body-lowering class? — deferred until the new lint rule has produced a real population number #13838
Description
Filed by the domain:cli execution PM seat (#6024), session session_01UngCYXF98BVpYA9hfz6NYk, as the recorded remainder of #13651 / PR #13834. ⛔ Not work today — see the trigger.
The question
#13651 (ask 1) made the accidental hook-body-lowering class loud in os lint: a handler that is expressible as a metadata body but names a module-scope const, helper or import now fails os lint as hook-body/not-lowerable (severity error). ⛔ It deliberately did not change what os build accepts — the fallback-to-bundling catch is kept and pinned.
⇒ Should os build's default eventually fail on that same class?
| A | keep os build at warn-and-bundle indefinitely |
| B | flip the default to fail after a window, once the lint rule has shown a real population |
| C | flip it now |
Implementer's recommendation and mine: B. ⛔ C is unjustifiable on today's evidence, and the reason is the sharpest reading #13651 produced.
⭐ Why the measured zero is not permission to flip
#13651 measured the silent-downgrade population at 0 — 44 callables lowered, 41 bodies extracted, 0 downgraded, across every config in this tree including the reference app.
The reference app reads zero BECAUSE IT ALREADY PAID: all 39 callables are written defensively, constants hand-copied into each handler, guarded by eight bespoke pin tests.
And the bound the implementer stated without being asked: a zero inside this tree says nothing about apps outside it, on a published CLI. ⇒ The set the flip would actually affect is precisely the set nobody has measured. Flipping a contract on a number drawn from the wrong population is guessing.
⭐ The lint rule is the instrument that will produce the real number. Deciding before it has reported is the same mistake in miniature that #13411 was dispatched to stop: choosing a repair before the measurement that sizes it.
Restart-when — a predicate, not a reminder
Restart-when: re-run #13651's census on the real build path (loadConfig → normalizeStackInput → lowerCallables, classified off the reason strings extractHookBody actually produces, ⛔ nothing re-implemented) and fire when silent_downgrade_warnings > 0 in any measured tree.
⭐ The line above was added 2026-09-11T13:0xZ by the domain:cli seat (#6024) as an H9 repair, and it encodes THIS section's own predicate — ⛔ no condition was invented, widened or narrowed. The heading below reads Restart-when followed by an em dash, which is prose: the hold-hygiene predicate and the unlock scan both need the key, a colon and a value on ONE line, so for 11 days this card's trigger existed for readers and not for the machine. See the audit comment.
Re-run #13651's own census (the real build path: loadConfig → normalizeStackInput → lowerCallables, classification read off the reason strings extractHookBody actually produced — ⛔ nothing re-implemented) across the measured configs.
This card wakes when silent_downgrade_warnings > 0 in any measured tree — i.e. the lint rule has a population to point at.
- ⛔ It does not wake on "the rule shipped". Shipping is not reporting. The number is the trigger.
⚠️ It cannot see downstream apps, which are the population that matters most. If a downstream report arrives by any other route — an issue, a support thread, a dogfood run on a tree outside this repo — that is also a wake, and is worth more than the in-tree number. ⛔ Do not treat the in-tree census as the whole instrument; it is the only half that is automatable from here.
⭐ Re-establish the instrument with the same positive control #13651 used before trusting any new zero: a fixture with three known outcomes must return exactly free-identifiers, forbidden-token and extracted. A zero from an unproven instrument is not a reading.
⛔ What this card is NOT
- ⛔ Not a re-litigation of Hook-body lowering fails OPEN:
tryExtractBodyswallowsextractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651's choice. Option 2 (the lint diagnostic) was correct and is landed; this is the next question, not a second opinion on that one. - ⛔ Not the deliberate-bundle declaration. That was ruled A — leave it on Hook-body lowering fails OPEN:
tryExtractBodyswallowsextractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651: two escape channels already exist and are already silent (an explicitbodyon the hook, or the top-levelfunctions:map, which 3 callables already use warning-free). A new authorable key ispackages/speccontract expansion and belongs with Give a lowered hook body a declared constant surface — every shared constant is currently hand-copied into each handler and pinned by a bespoke drift test (8 rows in the reference app) #13718, ⛔ not here. - ⛔ Not blocking anything.
⚠️ A flip would be its own clause-② act with its own decision —os build's accept set is a different contract fromos lint's. ⛔ Do not ride it in on another card.
Refs
- Hook-body lowering fails OPEN:
tryExtractBodyswallowsextractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651 / PR feat(cli): make the silent hook-body downgrade loud —os lintrefuses an accidental scope leak (ask 1) #13834 — the lint rule, the population measurement, and the reasoning this card defers - Give a lowered hook body a declared constant surface — every shared constant is currently hand-copied into each handler and pinned by a bespoke drift test (8 rows in the reference app) #13718 — the declared constant surface (
needs-user-decision,domain:spec); the input it asked for is Hook-body lowering fails OPEN:tryExtractBodyswallowsextractHookBody's refusal into a warnings array and bundles the closure — make it loud (ask 1; the constant-surface half is split out) #13651's census - Hook-body build gates: @capabilities override dead through os build; require() pattern dead; default build silently bundles forbidden patterns #10678 — made the default path print the recorded warnings;
⚠️ the reason this is warn-and-bundle rather than literally silent
Generated by Claude Code
Activity
claude commentedon Sep 7, 2026
Population figures this card is waiting on, re-derived locally (from #16095)
This is a measurement hand-off only. Nothing about what
os buildaccepts is changed by the linked work, and the two cards are deliberately not merged.#16095 needed the same population number and could not reuse the figure in its own description: the widely quoted "39 of 39 hooks are handler-authored, 24 of 31 actions are body-authored" was measured against the reference CRM's pinned
@objectstack/lint@17.3.0, which is outside the platform repository and is not reproducible in this tree.Re-derived against this repository's own four example apps (
examples/app-crm,app-showcase,app-todo,app-multi-package), loading each real config throughloadConfigandnormalizeStackInput:Two readings worth carrying into this card:
Every inline handler in the table lowers cleanly:
lowerCallablesproduced abody.language === 'js'for all 6 hooks, so none of them is in the refused-extraction class.Method note, since the number is the point here: the counts come from the normalized stack rather than from source-text grepping, so a hook registered through a package manifest or an object's
actionsarray is counted the same way the compiler counts it.🤖 Generated with Claude Code
https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Generated by Claude Code
claude commentedon Sep 11, 2026
H9 repaired — this hold's trigger existed for readers and not for the machine, for 11 days
domain:cliexecution PM seat (#6024), body edited 2026-09-11T13:00:00Z (APIupdated_at). ⛔ No label moved: this card stayspm:on-hold, and ⛔ no condition was invented, widened or narrowed.What was wrong
This card was filed by this same seat on 2026-08-31 with a section headed
## Restart-when — a predicate, not a reminder, and the section is good — it names the instrument, the code path and the numeric threshold. But a heading is prose. The hold-hygiene check and the unlock scan both read a line whose key is followed immediately by a colon and a value, anchored at the start of the line; this card had the key followed by an em dash, inside a##heading. Measured with the patrol's own reader before the edit: 0 values in the body, 0 in the comment channel, against a firing positive control.⇒ For 11 days this was a
pm:on-holdcard with no machine-fireable exit — the shape the state model forbids outright («两种拼写之外的 hold ⛔ 不合法»).What the edit added
The section's own predicate, on one readable line directly beneath its heading:
Read back through the same reader: 1 value. Labels unchanged (
domain:cli, pm:on-hold, priority:p3).⛔ No trigger-file line was added, deliberately. The sibling repair on #13776 gained two, because that card's predicate is literally a file-content predicate. This card's predicate is a measured population number, not a file edit: touching
packages/cli/src/hook-body.tsdoes not makesilent_downgrade_warningsrise, and nominating it would fence every future dispatch on that file behind a hold it cannot discharge. ⇒ the one required line, and nothing beyond what this card already ruled.Why this is worth a comment rather than a silent fix
This is the fourth instance of one defect class disposed in this lane today, across three different directives: a decorated key (#17680), a key written mid-line (#16613), a condition left in prose with no key at all (#15638), and now a key followed by an em dash instead of a colon. ⇒ A state whose only exit is machine-gated is only as real as the line the machine reads, and every one of these was written by a seat that believed it had recorded the condition.
PM dispatch seat ·
domain:cli· sessionsession_01TSf4DV7ziu4V5j73e46b7c· half-state patrol H9 dispositionGenerated by Claude Code
fix(pm): H9's issue-ref class requires the reference at the declarati…
objectstack-fleet commentedon Sep 23, 2026
关
not_planned—— 维护者逐张复核 on-hold 卡时同意关闭;推理与重开条件记录在此分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T10:09Z。维护者 2026-09-23 在分诊会话里逐张复核pm:on-hold卡,对第七组的回复原文:「其他同意」。本卡在那一组里的建议是关闭(与 #13718 是一对,#13718 同批按「甲」关闭)。本卡的问题,保留原样
os build是否应当默认拒绝一个无法编进沙箱体的钩子 —— 今天os lint报错,os build只警告、照样打包。本卡正确地拒绝在没有人口数的情况下拍板,等新的 lint 规则报出真实数量。为什么关
5568752126的重新推导已经说明这个 0 的性质:本仓 4 个示例应用里 6 个钩子全部能干净地降低,没有一个落在拒绝类里;而参考应用早已手抄常量「绕开」了这个类。⇒ 真正有意义的人口在下游应用,本仓与本席都测不到。priority:p3。重开条件
任一满足即带着数据重开,回决裁批决定
os build的默认严格度:os build警告后照样打包,上线后行为与作者预期不符;loadConfig→normalizeStackInput→lowerCallables,按extractHookBody实际产出的原因字符串分类),拒绝类人口 > 0。关闭理由:
not_planned,同时摘掉pm:on-hold。Generated by Claude Code