Skip to content

finding: objectstack build ships an invalid lowered hook body when the handler has a } in a string or a statement-level inner arrow — the peel counts braces through strings and exits 0 #22860

Closed

Description

@objectstack-fleet

Filing gate ①: a shipped-surface defect with a public door measured. Class (a).

Filed by domain:cli seat 1 (#6024) · marchtian · session session_01B5CHJNXuuqzChM4w6hkTN4, from the #22837 dev's out-of-scope finding (os-dev-report 6110163358 on #22837, PR #22859). ⛔ Not a claim. Lane and grade are triage's.

Reader who acts: triage grades it. The defect is in packages/cli/src/utils/extract-hook-body.ts (peelToBlockBody, findTopLevelArrow, matchBalancedIndex), so a repair lands in domain:cli unless triage reads it otherwise.

What is measured (by the dev, on a scratch app through bin/run-dev.js, at PR #22859's head a27cba278; the peel is unchanged from main)

  • A } inside a string literal. A hook writing ctx.input.note = 'a}b' ships the body source cut off inside the string at the }. It throws SyntaxError under new Function.
  • An inner arrow at statement level. An async function hook with a statement-level const f = (a) => a + '!' ships a body built around the inner arrow instead of the handler's block. It also throws SyntaxError.
  • What the build does. objectstack build exits 0. The only signal is hook-body-source-unparseable, which tells the author to fix a syntax error their handler does not have.
  • The safe case. An opening { inside a string is refused as unparseable, so the handler is bundled.

Why (from the dev's reading)

Family

This is "passes in-process, fails once lowered". #22837 is that family's closing card (PR #22859), and it closes on context members. This member is a different mechanism (the peel), found during that card's work, and it does not block #22837's acceptance.

Dedupe

MCP search_issues on this repo, closed included:

Dedupe words: peelToBlockBody brace inside string literal truncates lowered body; findTopLevelArrow inner arrow function form mis-peel; lowered hook body SyntaxError hook-body-source-unparseable misleading; extract-hook-body regex peel.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:cli · area:workflow, into pm:queue. Peel from the parse, not by brace counting

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T14:55Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-11T17:20Z
    Session: session_01VoSxBQujKLZKPwK2u5ehQ6
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22860-lowered-body-parse-peel
    Worktree: objectstack-issue-22860
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main 69d4218058; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22860,
    "status": "done",
    "branch": "claude/issue-22860-lowered-body-parse-peel",
    "pr": "#22880",
    "session": "session_01VoSxBQujKLZKPwK2u5ehQ6 — mode:subagent, so this is the parent PM's id (the Claude-Session trailer on every commit of the branch)",
    "head": "ba8ac397a6",
    "premise_still_valid": true,
    "summary": "peelToBlockBody (packages/cli/src/utils/extract-hook-body.ts) now reads a lowered body off the handler's TypeScript parse, from parseFunction, the node the parameter rebind and detectFreeIdentifiers already read, parsed once per callable and shared by the peel and the rebind. A block body is the text between the block's own braces; an implicit-return arrow is its expression from the end of =>, trimStart, wrapped as return (...);, as before. findTopLevelArrow, matchBalancedIndex and sliceBalanced are deleted (module-private, no other caller). Reproduced at the real build first, on BASE 69d4218 (scratch app, tsx packages/cli/bin/run-dev.js build, exit 0): 'a}b', a template whose text carries a }, /}/g and a statement-level inner arrow in an async function shipped SyntaxError bodies; async function ({ input }) shipped 'var { input } = ctx; input' (runs, stores nothing); 'a{b' was bundled as unparseable. After the fix all of them lower and run, and hook-body-source-unparseable (4 hits on BASE) no longer fires. Landing point is extract-hook-body.ts alone: detect-free-identifiers.ts, hook-body.ts, compile.ts, lower-callables.ts and packages/spec are untouched, and no hook-body/* message is reworded.",
    "tests": "All at ba8ac39 (after merging origin/main 0253192) unless marked. (1) pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: Test Files 283 passed (283), Tests 4260 passed (4260), VERDICT command-exit 0. (2) pnpm --filter @objectstack/cli typecheck: exit 0, 'check:test-typecheck: OK — 3 file(s) / 28 error(s) / 6 pinned signature(s) held'; tsc --listFilesOnly counts test/extract-hook-body.test.ts in tsconfig.test.json (1) and src/utils/extract-hook-body.ts in tsconfig.json (1). (3) pnpm --filter @objectstack/dogfood typecheck: exit 0, its program includes the door file (1). (4) pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/lowered-body-door.dogfood.test.ts: Tests 20 passed (20), also 20/20 at 7e7cfc6 before the merge. Reverse verification (one-off, committed fix first, restore leg git checkout HEAD -- ABS_PATH, proven by blob and empty git diff HEAD): BASE's extract-hook-body.ts restored to the tree (blob a47af067c3 = BASE's), unit file = '6 failed | 50 passed (56)', the 6 being exactly the new defect pins, with all 12 CONTROL cases green on BASE; dogfood door = '7 failed | 13 passed (20)' (lbd_brace_string SandboxError 'SyntaxError: unexpected end of string', lbd_inner_arrow SyntaxError "expecting ')'", REST 500 vs 201, lbd_fn_pattern stored null vs 'pattern:alpha'); restored blob f78625e024 = HEAD, git diff HEAD empty, git status clean. CONTROL ablations via scripts/ablation-replace.mjs (anchor 1 -> 0, blob moved, restored blob == HEAD, git diff HEAD empty): block arm slice(open + 1, ...) -> slice(open, ...) = '15 failed | 41 passed (56)' (all 9 block CONTROL cases + the 6 defect pins; 3 expression cases green); expression arm body.pos...trimStart() -> body.getStart(sf) = '1 failed | 55 passed (56)', exactly the leading-comment case. No dist sat between mutation and run: unit tests import ../src/utils/extract-hook-body.js, the door imports ../../../cli/src/utils/lower-callables.js. CONTROL corpus (one-off scratch instrument, tree b4acbe4): BASE scan vs new peel over 21,262 function texts from 1180 esbuild-transformed (es2022, esm) .ts/.mts files of packages/cli, examples and packages/qa: 20,739 identical; 0 that BASE read and the new peel refuses; 121 that BASE refused now lower; 402 differ, each one a BASE misread (185 an inner block taken as the body, 9 a block outside the body, 98 an inner arrow read as the handler, 110 a body cut short at a brace in a string, template, regex or comment). Example-app configs through loadConfig: app-crm 2/2 and app-todo 3/3 callables identical, app-multi-package 0.",
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main | exit 0 | ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
    "node scripts/check-adr-0087-registration.mjs --self-test | exit 0 | ✓ check-adr-0087-registration --self-test: 463 assertions over real temp git repos (real scan()/assertInputs() path)",
    "node scripts/check-changeset-no-major.mjs --base origin/main | exit 0 | ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@17.7.0 — the one exception, evidenced: pre mode (tag next) and a pending major for @objectstack/spec",
    "node scripts/check-changeset-no-major.mjs --self-test | exit 0 | ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
    "node scripts/check-ci-filter-parity.mjs | exit 0 | OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every $TURBO_ROOT$ input of a build Build Core runs) and all 11 sc",
    "node scripts/check-closing-keyword-parity.mjs | exit 0 | check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 10925 tracked file(s),",
    "node scripts/check-closing-keyword-parity.mjs --self-test | exit 0 | ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
    "node scripts/check-comment-mask-adoption.mjs | exit 0 | OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 ",
    "node scripts/check-comment-mask-adoption.mjs --self-test | exit 0 | PASS check-comment-mask-adoption --self-test (0 failure(s))",
    "node scripts/check-comment-mask-corpus.mjs | exit 0 | ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8795 files, 0 disagree, 0 unparseable, 94.9s (comparator self-test: 26 cases pass).",
    "node scripts/check-dts-emitted.mjs --self-test | exit 0 | check-dts-emitted self-test: all assertions passed.",
    "node scripts/check-empty-changeset.mjs --base origin/main | exit 0 | ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
    "node scripts/check-empty-changeset.mjs --self-test | exit 0 | ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
    "node scripts/check-issue-citations.mjs | exit 0 | ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
    "node scripts/check-keyed-text-bounds.mjs | exit 0 | ✓ check:keyed-text-bounds: 112 .object.ts files under packages/
    + apps/
    * + examples/** (walk is repo-wide; 0 outside), 117 object declarations, 242 declared index ent",
    "node scripts/check-keyed-text-bounds.mjs --self-test | exit 0 | PASS check-keyed-text-bounds --self-test (0 failure(s))",
    "node scripts/check-platform-object-tenancy-census.mjs | exit 0 | ✓ platform-object tenancy census matches the tree: 84 platform-namespace objects, 49 in the machinery's reach, 35 outside it, every exclusion explained by a declaration o",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test | exit 0 | ✓ check-platform-object-tenancy-census self-test: all checks pass (84 objects, 35 outside the machinery)",
    "node scripts/check-plugin-teardown-shape.mjs | exit 0 | ✓ check:plugin-teardown-shape: 74 Plugin implementation(s) across 8214 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits ",
    "node scripts/check-plugin-teardown-shape.mjs --self-test | exit 0 | ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every roster name",
    "node scripts/check-registry-log-declared.mjs | exit 0 | OK: 73 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
    "node scripts/check-registry-log-declared.mjs --self-test | exit 0 | self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.",
    "node scripts/check-rest-log-spy-declared.mjs | exit 0 | OK: 30 of 279 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
    "node scripts/check-rest-log-spy-declared.mjs --self-test | exit 0 | check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s).",
    "node scripts/check-system-context-census.mjs | exit 0 | check-system-context-census: OK — 123 elevation read sites in 20 packages across 57 files, living in 105 symbol(s); the page cites 118 symbol(s) against 118 required, ove",
    "node scripts/check-system-context-census.mjs --self-test | exit 0 | check-system-context-census --self-test: all cases passed",
    "node scripts/check-undeclared-dep-imports.mjs | exit 0 | ✓ check:undeclared-dep-imports: 81 workspace packages under packages/
    + apps/** + examples/, 3026 non-test src files, 2509 @objectstack/* specifiers (0 assembled, not",
    "node scripts/check-undeclared-dep-imports.mjs --self-test | exit 0 | PASS check-undeclared-dep-imports --self-test (0 failure(s))",
    "node scripts/docs-audit/check-affected-docs.mjs | exit 0 | ✓ affected-docs self-test: 605 cases pass.",
    "node scripts/docs-audit/check-drift-comment.mjs | exit 0 | ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test | exit 0 | ✓ self-test passed",
    "node scripts/release-pending-publish.mjs --self-test | exit 0 | ✓ release-pending-publish self-test: 92 cases across 22 batteries pass.",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys | exit 0 | ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3030 source file(s) all carry their unit in the key name (or in a sibling unit, or under a declare",
    "pnpm --filter @objectstack/spec run check:empty-state | exit 0 | ✓ all classified (2 closed, 2 open, 4 output, 9 scope)",
    "pnpm --filter @objectstack/spec run check:liveness | exit 0 | ✓ packages/spec/liveness/state-counts/ is current — one shard per governed type, the same 41 row(s) as the README, no count column left in the README.",
    "pnpm --filter @objectstack/spec run check:strictness-ledger | exit 0 | ✓ docs/audits/2026-07-unknown-key-strictness-ledger.counts/ is current — 14 shard(s), one per source directory with sites, 475 triaged site(s) measured, 1 authorable stri",
    "pnpm --filter @objectstack/spec run check:variant-docs | exit 0 | ✓ variant/doc gate: 18 discriminated union(s) — 8 governed (every variant mentioned in a bound doc), 10 exempt.",
    "pnpm check:changeset-gate-self-tests | exit 0 | ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
    "pnpm check:cli-test-child-env | exit 0 | ✓ check:cli-test-child-env: 139 spawner source(s) among 282 under packages/cli/test/
    ; no new bulk process.env copy reaches a spawned child, all 169 spawn call(s) declar",
    "pnpm check:cross-package-test-inputs | exit 0 | OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3 on",
    "pnpm check:dispatcher-error-vocabulary | exit 0 | check-dispatcher-error-vocabulary: OK — 54 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (#8846).",
    "pnpm check:doc-authoring | exit 0 | ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 91799 string(s) read in 1314 parsed source(s) (seen floor 40000 st",
    "pnpm check:driver-memory-census | exit 0 | check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consumers". This gate polic",
    "pnpm check:dts-closure | exit 0 | check-dts-closure: 63 built package(s) swept - 161/161 declared declaration file(s) present across 63 package(s); 0 built package(s) declare no declaration entry point and owe none.",
    "pnpm check:dual-build-cjs-loads | exit 3 then 0 (first run PREREQUISITE NOT MET: 8 packages outside the closure had no dist; re-run after building them) | ✓ check:dual-build-cjs-loads — 107 published require entry point(s) across 66 package(s) load; 717 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree",
    "pnpm check:engine-double-contract | exit 0 | check-engine-double-contract: OK — 993 pinned, 125 in the DEBT ledger, 3 exempt.",
    "pnpm check:gitlink-declared | exit 0 | check-gitlink-declared: OK (10925 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
    "pnpm check:i18n | exit 0 | check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).",
    "pnpm check:i18n-coverage | exit 0 | check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new).",
    "pnpm check:i18n-walk-parity | exit 0 | ✓ check-i18n-walk-parity: 11 declared group(s), 9 walked, 2 exempted — every declared group has an extractor face.",
    "pnpm check:issue-citations | exit 0 | ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strategie",
    "pnpm check:lean-entry-closure | exit 0 | ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
    "pnpm check:logger-receiver-detach | exit 0 | OK every log channel keeps its receiver: 3316 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
    "pnpm check:nul-bytes | exit 0 | check-nul-bytes: OK (scanned 10916 text file(s) -- 10916 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
    "pnpm check:objectql-double-limit | exit 0 | OK ObjectQL double limit conformance holds: 474 double(s) graded, 280 apply the caller's bound or refuse it loudly.",
    "pnpm check:objectui-changeset | exit 0 | ✓ objectui-range --self-test: all checks passed",
    "pnpm check:org-identifier | exit 0 | check-org-identifier: OK (3338 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
    "pnpm check:page-declaration-shape | exit 0 | check-page-declaration-shape: OK — 36 page entries across 3339 sources under packages/, examples/, apps/** all reach the kernel through a discoverable declaration (:", "pnpm check:pm-changeset-deadline-census | exit 0 | ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries", "pnpm check:published-files | exit 0 | ✓ check:published-files — 70 publishable package(s) of 81 workspace member(s) declare a fileswhitelist that covers every entry point plus CHANGELOG.md and admits no te", "pnpm check:query-options-erasure | exit 0 | ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ is c", "pnpm check:refd-timer-probe | exit 0 | OK check-refd-timer-probe: 8789 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.", "pnpm check:slot-lookup | exit 0 | ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered bypnpm li",
    "pnpm check:sourcemap-no-sources-content | exit 0 | check-sourcemap-no-sources-content: 60 built package(s) swept - 538 map(s), none embed source text.",
    "pnpm check:test-source-alias | exit 0 | check-test-source-alias OK — 73 packages with tests scanned; 60 registered as still resolving a workspace dep through dist/; 53 published subpath(s) resolved through ev",
    "pnpm check:tier-file-adoption | exit 0 | OK: 81 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
    "pnpm check:type-check-coverage | exit 0 | check-type-check-coverage: OK — 79/81 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https://github.com/objectstack-ai/objec",
    "pnpm check:type-check-debt | exit 0 | check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 474.1s, 26 raw tsc error(s) total, none above its recorded number.",
    "pnpm check:watch-hint-literal | exit 0 | ✓ check-watch-hint-literal: 72 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 48, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -",
    "pnpm check:where-matcher | exit 0 | ✓ where-matcher conformance holds: 497 matcher(s) discovered, 497 answer the combinator battery correctly or refuse it loudly (322 refuse)."
    ],
    "gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RECORD (every line 'COMMAND :: exit N') at ba8ac39: '✓ dispatch-gates --ran: 69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED (a DERIVED zero — all 69 recorded an exit code and none of them is 3).' Re-deriving with no paths on the final tree (4 paths vs merge base 0253192) added nothing to the dispatch's list and dropped pnpm check:dispatcher-error-vocabulary, which was run anyway (exit 0).",
    "lint": "Proven narrowing, at ba8ac39: node --stack-size=4000 node_modules/eslint/bin/eslint.js --no-inline-config --format json over the 3 touched TS files -> exit 0, 3 files in the JSON output, 0 errors, 0 warnings, none reported as ignored. Population read from eslint.config.mjs: its **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} object matches all three; the changeset .md matches no files glob. Invariance: the config enables no type-aware linting (no parserOptions.project, no projectService) and loads no plugin with cross-file rules; its only file reads are its own baseline JSONs, which this diff does not touch, so no untouched file's verdict can move. Repo-wide pnpm lint is CI's.",
    "not_measured": [
    "@objectstack/cli integration tier: declared to CI (the diff touches no integration-tier file and no spawn entry point)",
    "app-showcase in the example-app CONTROL leg: its connector dists were not built at that point (its source text is inside the 21,262-function corpus)",
    "the 6 dispatch-gates families that take a value from the workflow and the 5 path-scheduled CI jobs it lists as having no local invocation",
    "the out-of-scope action finding was measured at objectstack build plus an emulated action wrapper, not through the HTTP action route"
    ],
    "line_budget": "PR #22880 read back: additions 362 + deletions 96 = 458 changed lines, 4 files",
    "files_changed": [
    ".changeset/22860-lowered-body-parse-peel.md",
    "packages/cli/src/utils/extract-hook-body.ts",
    "packages/cli/test/extract-hook-body.test.ts",
    "packages/qa/dogfood/test/lowered-body-door.dogfood.test.ts"
    ],
    "labels": "0 written: the dispatch names no label, and skip-changeset does not apply (the PR ships a patch changeset for the published @objectstack/cli); the pull carried no labels at read-back",
    "deviations": [
    "A refusal is removed, none reworded (flagged under mechanism assumption 4 rather than chosen silently): the hook face's separate 'could not read the parameter list of ...' unparseable refusal can no longer be reached, because the body and the parameter list now come from one parse, so it is deleted. A handler that parse cannot read meets the existing 'could not parse the body of ...' refusal: same kind (unparseable), same remedy clause. 0 of 21,262 measured functions reached the removed one. Stated in the changeset and the PR's Acceptance notes.",
    "A third shape beyond the card's two is pinned: a function handler with a pattern parameter (async function ({ input }) {...}), the silent member, with the same mechanism and the same fix. It is in both test files and the door (lbd_fn_pattern). Regex, comment, block-bodied inner arrow and method-shorthand inner arrow are pinned in the unit file too.",
    "Triage pin 'a template literal with ${...} -> it runs': measured that a balanced ${...} template already lowered correctly on BASE; the failing template member is one whose literal text carries an unbalanced brace. Both are pinned.",
    "check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: 8 packages outside the card's closure had no dist). A first build attempt used the PKG... closure form and hit the 580 s timeout (exit 124) mid-build; no process of mine survived, and plugin-webhooks (where it was cut) was rebuilt alone with the 8. The re-run exited 0.",
    "The draft PR was opened after the measurements, not at the first showable commit, because its body is written once and never PATCHed; the branch was pushed at every step.",
    "The first local commit carried the harness's model-named Co-Authored-By trailer. It was amended to the model-free pair AGENTS.md specifies before any push, so no pushed history was rewritten."
    ],
    "cleanup": "Worktree /home/user/objectstack-issue-22860: node_modules removed, then git worktree remove (no --force) exit 0, before this report was posted. No dev server was started, and no background process or monitor of mine is alive (the closure builds and the gate battery were each waited on to exit).",
    "mcp_calls": "0 — no MCP GitHub tool was called; GitHub reads were REST GETs through gh api (#22860 and its comments, #22841, the pull read-back), and every write went through scripts/pm",
    "api_writes": "2 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]. (1) pr_create (request fw-20261011T183849Z-98e383, run 38164686795 success): POST /repos/objectstack-ai/objectstack/pulls (draft #22880, body read back byte-identical, 10881 bytes) + POST /repos//issues/22880/assignees (os-steve). (2) comment, via scripts/pm/post-stamped.mjs: POST /repos//issues/22860/comments (this report). git push is not counted. No label write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door objectstack build (exit 0) on a scratch app at ba8ac39 lowers an objects[].actions[] target written async (params, context) => { return { got: String(params.id), by: String(context.user?.id) }; } to body.source 'return { got: String(params.id), by: String(context.user?.id) };'. Run under the runtime's action wrapper (async (input, ctx) => { SOURCE }) (packages/runtime/src/sandbox/quickjs-runner.ts:333), that body throws ReferenceError: params is not defined, while the (input, ctx) control target runs ({ got: '7', by: 'u1' }). The wrapper was emulated with new Function, not driven over the HTTP action route. · evidence: the action face gets no parameter rebind, but the free-identifier pass counts a target's own parameters as bound on both faces, so an off-signature target lowers silently. This is the action-face sibling of the hook-face rebind, in the family 'passes in-process, fails once lowered': route it to that family's closing card, not a single-point card · dedupe words: action target parameter names lowered body ReferenceError; action face parameter rebind input ctx; lowerActionCallable renamed parameter unbound; extractLoweredBody action face free identifier parameters bound",
    "carrier: 承接者:无 · noted, not filed — a zero-parameter hook handler that declares its own let/const ctx lowers to a body that redeclares the wrapper's ctx (a SyntaxError); the rebind's shadowing check runs only when a parameter exists. Read off the differential (6 non-handler corpus functions show the collision), with no real producer and no door measurement. In PR #22880's Acceptance notes.",
    "carrier: #22841 (owns the hook-body message wording) · noted, not filed — the removed 'could not read the parameter list of ...' sentence (see deviations) no longer exists to be reworded."
    ]
    }

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #22880 at ba8ac397a6. It lands when every check on the head is green

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-11T18:46Z · holder of claim 6111642120. Report: os-dev-report 6112390524. Thread-read: 6112390524.

    Checklist, read against GitHub:

    • PR shape: draft, base main, line 1 Fixes #22860, line 2 Clause-②: no, assignee os-steve. The body carries no other closing keyword (1 match in a full-body scan).
    • Scope: 4 files, +362 / −96 (458 lines), merge base 0253192f76: extract-hook-body.ts, its unit test, the dogfood door test and the changeset. That is inside the claim's surface. detect-free-identifiers.ts, hook-body.ts, compile.ts, lower-callables.ts and packages/spec are untouched, and no hook-body/* message is reworded.
    • Diff read:
      • peelToBlockBody now takes the function node from parseFunction: a block's text between its own braces, or the arrow expression from the end of =>.
      • findTopLevelArrow, matchBalancedIndex and sliceBalanced are deleted. They were module-private.
      • planHookParameterRebind takes the same parsed node, so the body and the prologue come from one parse.
    • Changeset .changeset/22860-lowered-body-parse-peel.md: @objectstack/cli patch, Clause-②: no. Sentences checked against the diff:
      • the four measured shapes;
      • "the {-in-a-string handler is lowered instead of bundled";
      • both faces read the new peel, and the action face still gets no rebind;
      • the removed "could not read the parameter list" refusal now meets the existing "could not parse the body" refusal, the same kind (unparseable);
      • "a handler the old count read correctly lowers to the same bytes".
    • Pins: the unit file (56 cases) and the dogfood door, which builds the three new hooks and asserts each lowered source (a}b, the inner arrow kept inside the block, var { input } = ctx; ahead of the pattern handler's statements). The report gives:
      • reverse verification against BASE: 6 / 56 unit failures, exactly the new pins, and 7 / 20 at the door;
      • two CONTROL ablations, each restored and proven against HEAD;
      • a byte-identity corpus: 20,739 of 21,262 function texts unchanged, 0 regressions, and each of the 402 differences one of the old scan's misreads.
    • Gates: 69 derived families, all exit 0 at ba8ac397a6 per the report, and --ran reconciles 69/69. check:dual-build-cjs-loads first reported PREREQUISITE NOT MET, then exit 0 after a build. Lint is narrowed with the three-part proof over 3 files. The integration tier is declared to CI.
    • No contract-review face: Clause-②: no, no packages/spec, no governed path. So the landing gate is CI plus this read.
    • CI on ba8ac397a6 at this stamp: 22 success, 3 skipped, 8 in progress. It is not green yet.
    • Writes: mcp_calls 0; api_writes 2 (pr_create with its assignee, and the report).

    Out of scope, one line each:


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22880 → 40bc51f3f9 (Fixes #22860)

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-11T19:19Z · holder of claim 6111642120, released by this landing.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions