Repository navigation
Commit aa94566
Fixes #22853
Clause-②: no
## What was wrong (measured on `origin/main` `69d4218058`)
`objectstack build` decided whether to write `objectstack-runtime.*.mjs`
from `lowering.count - lowering.bodyExtracted` plus the extraction
warnings (`compile.ts`, the `needsBundle` line). `count` is per
callable, `bodyExtracted` per lowered slot, so a callable lowered at two
slots cancelled a `functions` entry beside it. A tsx probe over
`lowerCallables` at the base:
| shape | count | bodyExtracted | old `needsBundle` |
`bodylessCallables` (PR #22849) |
|---|---|---|---|---|
| card: hook + `functions` entry, top level AND `packages[0].manifest` |
2 | 2 | false | `function 'pb_score'` |
| addendum: one handler shared by two hooks + a `functions` entry | 2 |
2 | false | `function 'pb_score'` |
| one function as a lowered hook handler AND a `functions` entry | 1 | 1
| false | **empty** |
| one function as a hook handler (hook face refuses it) AND an action
target (action face lowers it) | 1 | 1 | true (warning) | **empty** |
| CONTROL: all body-only, one walk / two walks | 2 / 1 | 2 / 2 | false |
empty |
At the door (`bin/run-dev.js build`, the ablation leg below), the first
three printed `Skipping legacy runtime bundle (all 2 callables are
body-only)` (twice) and `(all 1 callables are body-only)`, exited 0 and
wrote no module.
**PM mechanism assumption 2 holds.** `compile.ts` alone covers the
card's shape and the addendum's, but not the third row:
`bodylessCallables` dropped every ref some slot lowered
(`[...unlowered].filter(([ref]) => !loweredRefs.has(ref))`), so a
function lowered for its hook read as body-only although its `functions`
entry has no body slot. The fourth row is the same hole in the other
direction: a refused hook was dropped because the action lowered the
same function, so `--strict-body` passed a hook that ships with no body.
## The fix
- **`packages/cli/src/utils/lower-callables.ts` (the producer of the
population).** Each slot that leaves a callable unlowered now says which
kind it is: `refused`, `functions-entry` or `authored-body`. A `refused`
or `functions-entry` slot has no body at all, so another slot lowering
the same function does not take the callable off `bodylessCallables`.
Only an `authored-body` slot is taken off by a lowering elsewhere (every
slot then has a body), which keeps PR #22849's verdict for that case. A
callable bound at several slots reports its strongest row (refusal, then
`functions` entry, then authored body), so the row is the extractor's
refusal whenever one exists. New export `isExtractionRefusal(row)`, a
type guard over the same rows.
- **`packages/cli/src/commands/compile.ts`.** `needsBundle =
lowering.bodylessCallables.length > 0`. `stillNeeded` and the `||
bodyExtractionWarnings.length > 0` term are gone: every refusal is a row
of the list, so the term could add nothing. The default-path warning,
its summary line and the `--no-runtime-bundle` message count
`refusedCallables` (the list's refusal rows), each refused handler once.
- **Landing point.** The claim's surface named `lower-callables.ts` only
if a single-walk member needed the population to change. Measured: one
does (row 3), so it lands there. `extract-hook-body.ts`,
`detect-free-identifiers.ts` and `hook-body.ts` are untouched. No
`packages/spec` change.
**The decision only moves from skip to write.** If `count -
bodyExtracted > 0`, some callable has no lowered slot, and every
registered callable is either lowered somewhere or noted unlowered, so
it is a row. Every refusal is a row too. A unit pin asserts the
implication over eight shapes.
## What moves for consumers (changeset `minor`, `Clause-②: no
(narrowing)` in its body)
- `objectstack build` writes the module for the shapes above. Nothing it
wrote before is skipped.
- `--no-runtime-bundle` now refuses those stacks (exit 1); it passed
them while the entry had no implementation.
- `--strict-body` now refuses a function bound at a lowered slot AND as
a `functions` entry or at a refused slot. PR #22849 pinned the third row
as passing (`a callable lowered at ANY slot is not a row`); triage's
grade on this card rules it the other way, so that pin is replaced.
- The default-path warning counts a refused handler once, not once per
slot. `--json` `bodyExtractionWarnings` is unchanged (still one record
per slot).
## Tests (all at `b464a20646`)
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2`: 281 files / 4245 tests passed, 2 failed at
`5cbd9c5ceb`. Both were source-text pins on `compile.ts`:
`truncation-remainder-notices` (the precedent loop's list is now
`refusedCallables`) and `validate-build-gate-parity`
(`isExtractionRefusal` classified under local helpers). After that
update, the 4 affected unit files passed: 112 tests.
- New unit pins in `test/lower-callables.test.ts`: the `functions` entry
beside a lowered hook is a row; a refused slot beside a lowered action
is a row; an authored-body slot IS taken off by a lowering elsewhere;
strongest-row precedence; a two-walk all-clean CONTROL;
old-decision-implies-new over eight shapes.
- New integration pin `test/build-runtime-bundle-bodyless.test.ts`
(spawns `os build`; queue tier, not nightly): `--project integration`
6/6 passed. The two-level stack and the shared-handler stack write the
module, the artifact points at it, and `pb_score` runs through
`@objectstack/runtime`'s `loadArtifactBundle` (answers 1). The
hook-and-`functions` stack writes the module, and its `functions` entry
resolves to a callable. CONTROLS: an all-body-only stack (one walk and
two walks) still prints the skip line and leaves no module in `dist/`. A
refused hook walked twice reads `1 handler could not be lowered` and one
bullet.
- Also run: `build-strict-body-count.test.ts` (integration, green);
nightly `hook-body-build-reach.e2e`, `build-json-failure-warnings.e2e`,
`build-multi-package-artifact.e2e` under `OS_TEST_TIERS=nightly`: 25/25.
- `pnpm --filter @objectstack/cli typecheck`: exit 0. `tsc -p
tsconfig.test.json --listFiles` includes both new or edited test files,
with no errors in them.
**Ablations** (each through `scripts/ablation-replace.mjs`: anchor hit 1
to 0, blob changed, restored to the HEAD blob with `git diff HEAD`
empty, plus a trap leg that re-proves both files. All are source edits
run through tsx, so there is no `dist/` leg):
- A, the population filter reverted to `!loweredRefs.has(ref)`: 3 red
(the two new unit pins and the door's hook-and-`functions` pin), 28
green.
- B, `needsBundle` reverted to the old arithmetic: 3 red at the door
(two-level, shared-handler, hook-and-`functions`), each printing the
skip line; CONTROLS and the warning pin green.
- C, the warning count reverted to `bodyExtractionWarnings.length`: the
double-walked refusal reads `2 handlers could not be lowered`, red.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on the final tree derives the same 65
commands as the dispatch. All 65 exit 0 at `b464a20646`. Reconciled: `✓
dispatch-gates --ran: 65 derived famil(ies) accounted for — 65 run, 0
NOT-MEASURED (a DERIVED zero — all 65 recorded an exit code and none of
them is 3).` Three gates first exited 3 (PREREQUISITE NOT MET: unbuilt
packages, then a contended build OOM-killed at 137). After a targeted
rebuild they read: `check:dual-build-cjs-loads` 107 entry points load;
`check:i18n-coverage` `OK (13 config(s), 621 baselined untranslated
string(s), none new)`; `check:type-check-debt` `surplus: none`. Also
run: the four roster gates whose roster sits under a touched directory
(`check-changeset-fixed`, `check:authz-resolver`,
`check:error-code-casing`, `check:filter-alias-parity`), all exit 0.
**Lint, proven narrowing.** The 6 touched code files (2 source, 4 test),
`eslint --no-inline-config --format json`: 6 files, 0 errors, 0
warnings. Population: `--print-config` resolves a config (5 to 6 rules)
for every file. Invariance: `parserOptions.project` and `projectService`
are null for each file (this repo never enables type-aware linting), so
this diff cannot move the verdict on an untouched file.
## Acceptance notes
- **Found beside this card, not fixed here (class (a), measured at the
door on this branch):** `lowerCallables` re-keys a `functions` map entry
by the ref it registered.
- `functions: { pr_score: tidy }` beside hook `pr_tidy` bound to the
same `tidy` ships `functions: { pr_tidy: 'pr_tidy' }`, so the authored
name `pr_score` is gone.
- A hook `pc_score` (refused) beside a distinct `functions: { pc_score:
scoreFn }` ships `functions` keys `pc_score__2` (a bare string, no
callable after `loadArtifactBundle`) and `pc_score`, which is `scoreFn`.
The hook's handler ref `pc_score` therefore resolves to the `functions`
entry's code.
- The runtime module's entry collector keys hooks by name and functions
by authored key, so the two collide. Reported for the seat to file.
- `--json` `bodyExtractionWarnings` and `handlersBundled` keep their
per-slot / per-callable meaning. They are machine-read payload keys and
were left alone.
- The PR body's `Clause-②: no` is the claim's line verbatim. The
changeset declares `(narrowing)` because `--no-runtime-bundle` and
`--strict-body` refuse stacks they passed. The report names this for the
seat.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8bc7836 commit aa94566
7 files changed
Lines changed: 449 additions & 46 deletions
File tree
- .changeset
- packages/cli
- src
- commands
- utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| |||
328 | 328 | | |
329 | 329 | | |
330 | 330 | | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
331 | 337 | | |
332 | 338 | | |
333 | 339 | | |
| |||
379 | 385 | | |
380 | 386 | | |
381 | 387 | | |
382 | | - | |
383 | | - | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
384 | 394 | | |
385 | 395 | | |
386 | 396 | | |
387 | 397 | | |
388 | 398 | | |
389 | | - | |
| 399 | + | |
390 | 400 | | |
391 | 401 | | |
392 | 402 | | |
| |||
1019 | 1029 | | |
1020 | 1030 | | |
1021 | 1031 | | |
1022 | | - | |
1023 | | - | |
1024 | | - | |
1025 | | - | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
1026 | 1046 | | |
1027 | 1047 | | |
1028 | 1048 | | |
1029 | 1049 | | |
1030 | 1050 | | |
1031 | 1051 | | |
1032 | 1052 | | |
1033 | | - | |
| 1053 | + | |
1034 | 1054 | | |
1035 | 1055 | | |
1036 | 1056 | | |
| |||
1176 | 1196 | | |
1177 | 1197 | | |
1178 | 1198 | | |
1179 | | - | |
| 1199 | + | |
1180 | 1200 | | |
1181 | | - | |
| 1201 | + | |
1182 | 1202 | | |
1183 | | - | |
| 1203 | + | |
1184 | 1204 | | |
1185 | 1205 | | |
1186 | 1206 | | |
| |||
0 commit comments