Repository navigation
refactor(formula): remove the never-bound EvalContext.api declaration - #18736
Conversation
…ontext `EvalContext.api` declared `exists` / `count` / `lookup` and promised `os.exists / os.count / os.lookup`, "implemented opportunistically by call sites that have a query engine". `buildScope()` read `ctx.api` zero times, so no call site could bind them however well it cooperated: a predicate calling one faulted with `found no matching overload for 'dyn.lookup(string, dyn)'`, and an unevaluable predicate refuses the write it guards — a validation rule authored against the declaration locked every write on its object. The declaration is removed rather than implemented, with the reason written at the deletion site. No shim, no alias, no reserved spelling. Claude-Session: https://claude.ai/code/session_01CqmCgU5RGDoJYhHUMVp2af Co-authored-by: Claude <noreply@anthropic.com>
Clause-② is declared on the card and on the PR body; the changeset body carries the same line. The bump is `minor` rather than `major`: the removed declaration never delivered the semantics it reported, so nothing a consumer could depend on at runtime moves — the whole of the break is a compile error on an `EvalContext` literal that carries `api`. Claude-Session: https://claude.ai/code/session_01CqmCgU5RGDoJYhHUMVp2af Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 7 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 43bff01ddc6bda3558ad94763e72262c6697163e && git checkout 43bff01ddc6bda3558ad94763e72262c6697163e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4691ad17d273f23d90bd5ef495728dd2a12ee020 2b053aebd7ba4bd24fe469c91790308d31333b28 && git checkout -B drift-repro 4691ad17d273f23d90bd5ef495728dd2a12ee020 && git merge --no-ff 2b053aebd7ba4bd24fe469c91790308d31333b28
node scripts/docs-audit/affected-docs.mjs --json 4691ad17d273f23d90bd5ef495728dd2a12ee020 |
…disposition The launch window refuses `major`, so the level cannot carry breaking-ness: `.github/workflows/pr-automation.yml` puts it on the `**BREAKING**` banner and the ADR-0087 disposition instead. The bump stays `minor`. The disposition is `runtime-interface-only`, named at `packages/formula/src/types.ts#EvalContext` and verified by the gate: a published runtime TypeScript interface with no Zod schema, no `packages/spec` declaration and no stored representation, so the ledger has nothing to project and the compiler is the channel that reaches every affected caller. `type-surface-only` was tried first and refused, twice, on measurement: the dotted member spelling does not resolve through an interface, and at the interface the base side is already concrete, which is the shape predicate 4 exists to refuse. Claude-Session: https://claude.ai/code/session_01CqmCgU5RGDoJYhHUMVp2af Co-authored-by: Claude <noreply@anthropic.com>
|
Pre-landing checks, all three re-taken at this stroke — ⛔ none carried over.
Governed-surface predicate, derived by the script from this PR's own file list:
⇒ Going ready and into the merge queue (SQUASH). This seat follows it to MERGED. Generated by Claude Code |
…side is an instruction (objectstack-ai#18834) Fixes objectstack-ai#18745 ADR-0087's `no-migration-prescription` detector missed a framed FROM → TO line again, so an exemption the categories inherit can be held by SILENCE rather than by a finding. This adds the one arm that was missing, answers the three questions the claim could not, and hands back the one reading that is not this PR's to act on. This is ⛔ NOT a defect in PR objectstack-ai#18736. That PR's exemption rests on four positively verified predicates and the gate printed `-- verified`. What recurred is the INSTRUMENT. Clause-②: no — one repo gate script, no published byte moves (measured below). ## Q1 — which spelling was missed, beside the branch that should have caught it Anchored on SYMBOLS and reported where they actually are. In `scripts/check-adr-0087-registration.mjs`: `findMigrationPrescription` is at **:1562** on `origin/main` and at **:1685** on this branch. The card's `:204-214` for the header paragraph is NOT stale — `That argument is correct and it was accepted` really does open at **:204** on `origin/main`, and still does here (this PR appends its cross-reference after that paragraph rather than editing it). ``` MISSED: **Migration — `api: { … }` → delete the property.** .changeset/18318-evalcontext-no-query-api.md BRANCH: 2, `framed-line` (REWRITE_RE under MIGRATION_FRAMING_RE) ``` Predicate by predicate on that one line, each with its own non-zero control: | predicate | on the missed line | non-zero control | |---|---|---| | `MIGRATION_FRAMING_RE` | `true` | `false` on `plain prose` | | FROM side is an `OPERAND` | `true` (a backticked span) | — | | an `ARROW` is present | `true` | — | | `REWRITE_RE` (needs an `OPERAND` on BOTH sides) | **`false`** | `true` on the same line with `` `os.exists` `` as the TO side | So it failed on exactly ONE predicate. `delete the property` is an INSTRUCTION, not a name — and `OPERAND` is a backticked code span or a dotted/slashed identifier path. A removal has no new name to put on the right, so the most careful prescription an author can write for one is invisible to every arm. ## Q2 — the objectstack-ai#8277 miss in the gate's header is a DIFFERENT hole Measured on `fc71b84:.changeset/lucky-schools-smash.md` through the exported detector. | | objectstack-ai#8277 (the header's case) | objectstack-ai#18745 (this round) | |---|---|---| | the prescription | `read` `` `result.driverFault?.message` `` `where you read` `` `result.error` `` | `` `api: { … }` `` → `delete the property` | | arrow on that line | no | yes | | migration framing on that line | no | yes | | FROM side an operand | n/a — no pair at all | yes | | what it fails on | there is no rewrite PAIR to match | the TO side is not an operand | | after this PR | still `null` | `framed-removal` | objectstack-ai#8277 is the **IMPERATIVE-SENTENCE** residue this gate's own header already lists as deliberate ("no arrow, no table, just …"), and this PR does not move it one inch. RM11 and RM12 pin that in the self-test, so a later author widening toward it argues with a red test rather than with prose. Closing the second hole does not close the first, and a repair that claimed to would be the over-matching criterion the residue paragraph says to falsify before implementing. ## Q3 — does tightening red the existing changeset stock? ONE reading, handed back Measured over the 414 changesets in `.changeset/` at `origin/main`: ``` BEFORE hits 28 / 414 {framed-line: 1, from-to-label: 25, header-framed-table: 2} AFTER hits 29 / 414 {framed-line: 1, from-to-label: 25, header-framed-table: 2, framed-removal: 1} ``` Exactly one row moves, and it is the card's own: `18318-evalcontext-no-query-api.md`, `null` → `framed-removal`. It holds `not-required (runtime-interface-only …)`, a category that INHERITS the prescription refusal, so it WOULD be refused if the gate re-judged it. It does not today: this gate judges only changesets a diff adds or modifies, and this PR touches none. `pnpm check:adr-0087-registration` is green on this branch.⚠️ **That reading is handed back, not acted on.** ⛔ No other author's changeset is edited here. The order is the seat's to decide. ## LIT — the behaviour flip, on the REAL missed text Not "the gate is still green". The same 414-changeset verdict table, before and after: ``` 2,4c2,4 < HIT (prescription found): 28 < BY BRANCH: {"framed-line":1,"from-to-label":25,"header-framed-table":2} --- > HIT (prescription found): 29 > BY BRANCH: {"framed-line":1,"from-to-label":25,"header-framed-table":2,"framed-removal":1} 191c191 < null 18318-evalcontext-no-query-api.md --- > framed-removal 18318-evalcontext-no-query-api.md ``` End to end through `scan()`, the card's own scenario: the real missed text carrying `not-required (runtime-interface-only packages/formula/src/types.ts#EvalContext)` is now REFUSED with `Evidence (framed-removal)` (`RM-E2E1`), and the SAME claim on the SAME symbol with the removal sentence taken out is still ACCEPTED (`RM-E2E1b`) — which is what makes the red attributable to the prescription refusal rather than to a hand-built fixture failing some `runtime-interface-only` predicate for an unrelated reason. ## DARK — the spellings it already recognised keep their verdict The other **413** rows of that table are byte-identical before and after, and every pre-existing branch keeps its count (`framed-line` 1, `from-to-label` 25, `header-framed-table` 2). That is structural, not measured-and-remembered: the new hit is held back and consulted LAST, so the function is exactly `arrowBranches ?? framedTable ?? headerFramedTable ?? framedRemoval` and no body an older arm reads can change its branch or its evidence line. Self-test case counts: **384 → 405** assertions, exit 0 both times. Two new batteries (`RM` end-to-end, 9 cases; `RM1-RM12` unit pins, 12) are declared in `SELF_TEST_BATTERIES` with their own floors. ## The class is closed, and derived from the stock rather than invented Measured over EVERY `.changeset/*.md` blob in this repository's history — 5524 unique blobs — the framed arrow lines whose right side is not an operand number **three**. Every candidate vocabulary tried (bare imperatives; plus `unset`/`omit`; plus `none`/`nothing`; plus the past participles; plus the Chinese arm) flags the **same two** changesets and no others — `18318-evalcontext-no-query-api.md` and `drop-dead-env-template-flag.md` (`**Migration.** …create --template… → drop the flag`), both genuine removal prescriptions on inspection. Zero false positives at any width, so the NARROW class ships: a wider one buys nothing measured and each extra word is a claim somebody later has to defend. The third non-operand right side, `lookup-reference-target-gate.md`, is running prose about resolution rungs, and this arm leaves it alone. `none` / `nothing` in arrow position was measured and REFUSED: the stock evidences that spelling only as a table CELL, which branch 4 already reads, and in arrow position it has a competing reading an imperative verb does not (`… → none of this changes what you write` is a running sentence). RM8 pins the refusal. ## Reverse verification (ablation) Committed first, then mutated on disk, then restored and proven: Committed first, then mutated on disk, then restored and proven — the mutation is `return table ?? headerTable ?? removal;` back to `return table ?? headerTable;`, which disables the arm and nothing else: ``` HEAD blob: 84cce40 working-tree blob BEFORE: 84cce40 BEFORE: arm-live count=1 ablated count=0 AFTER MUTATION: arm-live count=0 ablated count=1 on-disk proof, blob AFTER: 6d8d731286f2d643873ce63658b55959bec9a3bc ABLATED self-test exit: 1 -- 16 failure(s) RM-E2E1 ... : expected RED, got green RM-E2E2 ... : expected RED, got green RM1 / RM2 / RM3 ... and check-adr-0087-registration itself: 1 input problem -- the framing-scan-rot control restored blob: 84cce40 restore proven: byte-identical to HEAD, git diff HEAD empty RESTORED self-test exit: 0 -- 405 assertions ``` The ablated run NAMES the RM cases, so they are wired to the arm rather than passing for some other reason, and the new framing-scan-rot control fires on the live run too. The script is read by `node` directly and no package builds it, so there is no `dist/` leg to this ablation. ## Changeset — measured, not inferred from the path `skip-changeset`. The repo-root manifest is `"private": true`, so nothing at repo root is published; the edited file is tracked only at `scripts/check-adr-0087-registration.mjs` (plus its ADR anchor JSON), and **0 of the 70** workspace manifests carrying a `files[]` name a path that could ship it — non-zero control: 70 manifests were read, e.g. `packages/adapters/hono` ships `dist,README.md,CHANGELOG.md`. ## Acceptance notes Gates derived with `scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (32 commands) and all 32 run on this branch: **31 exit 0**. The 32nd, `pnpm check:pm-dispatch-gates`, is two batteries chained and exceeded the 480s window it was given (exit 124 = timeout, NOT MEASURED); re-run on its own, `node scripts/pm/check-dispatch-gates.mjs` is **exit 0, 1809 cases, 535.2s on this box**. `check-widening-tells --declaration no` reports this file as NOT MEASURED (no declared surface covers `scripts/`), which is ⛔ not a clearance and is stated as such. Repo-wide scans (`pnpm lint` and the artifact-roster families the deriver lists as silent) are CI's run, not this PR's claim. Noted, ⛔ not filed, and ⛔ not folded in — recorded so they are not lost: - `check-adr-0087-registration`'s predicate-4 refusal text for `type-surface-only` prints "fix: register the migration" without naming `runtime-interface-only`. Already recorded on the card by the filing seat; the carrier is whoever next edits that message. Not touched here — it is a refusal-message improvement, not a wrong verdict. - `.changeset/unscoped-multi-update-refusal.md` writes its TO side as a BOLD-WRAPPED code span (`** ` + backticked name + ` **`), which `OPERAND` does not reach because `\s*` after the arrow cannot cross the asterisks. It is caught today by another line in the same body, so no verdict rests on it, and widening `OPERAND` is a different claim with its own false-positive surface. Carrier: whoever next measures the operand vocabulary. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…removal note (objectstack-ai#18905) Fixes objectstack-ai#18842 Clause-②: no One line of prose in one pending changeset. No code, no package, no export, no published surface moves. ## What was contradicting what `.changeset/18318-evalcontext-no-query-api.md` carries an `adr-0087` disposition marker — an HTML comment, spelled out here in words because the platform rewrites that shape — declaring `not-required`, category `runtime-interface-only`, naming the symbol reference `packages/formula/src/types.ts` hash `EvalContext`. That disposition inherits the `no-migration-prescription` refusal (objectstack-ai#8299). After PR objectstack-ai#18834 landed the RETIREMENT arm in `scripts/check-adr-0087-registration.mjs`, the body's own lead line reads as a framed migration prescription: ``` **Migration — `api: { … }` → delete the property.** ``` So the changeset's head and its foot now say opposite things, and the gate refuses the claim the moment a diff makes it judge the file. **The refusal is right on its own terms and the detector is untouched here.** What is wrong is the disposition surface, so that is the only thing this PR moves. **In fairness to the author of objectstack-ai#18318 / PR objectstack-ai#18736: they did nothing wrong.** All four `runtime-interface-only` predicates were verified positively, and that author handed the detector miss up rather than letting it carry their claim. The defect was in the detector, closed by objectstack-ai#18745 / PR objectstack-ai#18834; what is left is that the new reading contradicts this one changeset's framing. That is why this diff edits somebody else's release note. ## The edit ``` - **Migration — `api: { … }` → delete the property.** + **Your fix — delete the `api: { … }` property.** ``` Five sentences follow it in the same paragraph and are byte-identical. ## Why the two sentences say the same thing to a consumer Token for token: | element | before | after | |:---|:---|:---| | the operand | `api: { … }` | `api: { … }` — unchanged, still a code span | | the verb | delete | delete | | the object | the property | the property | | the arrow | `→` | dropped | | the label | Migration | Your fix | The arrow is this repo's FROM/TO pair notation, and a pair notation reserves its right-hand place for a replacement NAME. There is no replacement name here — which is exactly why the author had to write an instruction in that position. Dropping a two-place notation that only ever had one place filled removes no information; the instruction that stood in the second place is still there, as the sentence's own verb. The label is the substantive half. In this repository "migration" names what the ADR-0087 ledger carries and what `objectstack migrate meta` performs, and the disposition two lines below states in writing that no such thing exists here: no Zod schema, no `packages/spec` declaration, no stored representation, nothing to rewrite. The paragraph was never a metadata migration; it is the one-line fix for a TypeScript consumer, delivered by the compiler at their own call site. AGENTS.md calls that element "the one-line fix", and the new label names it. Everything a consumer needs is still on the page and unchanged: what to delete, that there is no replacement key, that nothing is re-pointed, that no predicate result changes, that TypeScript is where they hear about it, that reading a related record's field stays unexpressible, and that no schedule is implied. ## LIT — the refusal was live before, and the claim is positively verified after Both legs run the shipping gate's own `scan()`, with base set to the parent of the commit that ADDED this changeset (`09e16a5745`, parent of `9be2b594af` / PR objectstack-ai#18736), so the row arrives as `A` and is judged instead of inherited as stock. ``` BEFORE (84ba4a8) LIT-TARGET-PROBLEMS 1 LIT-TARGET-JUDGED-OK 0 `not-required (runtime-interface-only)` contradicts the changeset's own body, which carries a migration prescription. Evidence (framed-removal): **Migration — `api: { … }` → delete the property.** ... AFTER (66f12e4) LIT-TARGET-PROBLEMS 0 LIT-TARGET-JUDGED-OK 1 verdict: not-required, category: runtime-interface-only, ids: packages/formula/src/types.ts#EvalContext, detail: packages/formula/src/types.ts#EvalContext (interface) ``` The after leg is not silence: the gate judges the changeset and verifies the four `runtime-interface-only` predicates at HEAD. ## DARK — the whole stock, classified before and after `findMigrationPrescription` imported from the gate and run over every `.changeset/*.md` in the tree, both legs: ``` stock 428 BEFORE {framed-line:1, framed-removal:1, from-to-label:26, header-framed-table:2, null:398} AFTER {framed-line:1, from-to-label:26, header-framed-table:2, null:399} ``` A row-by-row diff of all 428 rows shows exactly one line changed: ``` < 18318-evalcontext-no-query-api.md framed-removal **Migration — `api: { … }` → delete the property.** ... > 18318-evalcontext-no-query-api.md null ``` The other 427 rows are byte-identical, branch and evidence line included. ## Gates All 18 families derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` were run and reconciled with `--ran` (18 derived, 18 run, 0 NOT-MEASURED), plus `node scripts/check-changeset-fixed.mjs`, whose roster sits under `.changeset/` and which the derivation flags as neither-evidence for a path in that directory. Green, exit 0, except the one below. `node scripts/check-empty-changeset.mjs --base origin/main` — **exit 1, and it cannot be made green by this PR.** That gate refuses any diff that CHANGES a changeset it did not ADD, and it names the two shapes that produce the refusal. This is the second one: > DELIBERATE CORRECTION -- your change may have made this PENDING release note false, > and you rewrote it in the same stroke. Remedy: do NOT restore it -- say so on the PR > and get it confirmed; restoring it from the base would put the false sentence back. > ... this gate stays red either way, and staying red is what puts the decision in > front of a person instead of routing around it. So, saying so, as the gate asks: the note is `.changeset/18318-evalcontext-no-query-api.md`; what changed under it is its own ADR-0087 disposition reading, not any behaviour; and the rewrite is the lead line quoted above. Restoring it from the base would put back the contradiction this PR exists to remove. `Check Changeset` will therefore be red on two of its steps: this one, and "Require a changeset (or the skip-changeset label)", which counts `--diff-filter=A` rows and sees none. It is not one of the seven required contexts. **The `skip-changeset` label is deliberately NOT applied**, and that is a decision worth a second pair of eyes rather than a default: the label exempts the WHOLE job, so it would clear the deliberate-correction refusal along with the missing-changeset one — the routing-around the gate's own text forbids. Leaving it off keeps the decision visible. If the maintainer or the dispatching seat judges otherwise, the label is one additive write away. ## Acceptance notes Noted, not filed. - The card frames the future refusal as arriving when "any PR touches `packages/formula/src/types.ts`". That is not how the gate selects rows: `scan()` reads `git diff --name-status --diff-filter=AMR merge-base HEAD -- .changeset/*.md`, so the source file a PR touches never enters the selection. The reachable trigger is a PR whose merge base predates `9be2b594af` and which has since merged `main` — the changeset then arrives as an `A` row and is judged, which is precisely the leg this PR uses as its LIT probe. The direction of the card is right and its consequence is real; only the stated trigger is. - `runtime-interface-only` inherits a refusal written for metadata prescriptions, while the class it serves delivers through the compiler. objectstack-ai#13080 recorded the same mismatch for `type-surface-only` and bought an exemption with predicate 4. No card is filed: this is an observation about a boundary, not a reproducible defect, not a violation of a declared contract, and not a trap that makes an author write metadata the runtime rejects. Prospective carrier: none — no open PR or queued issue touches this seam. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #18318
Clause-②: yes
Execution of the maintainer ruling on batch #148 item 1, letter B (comment 5716041368, 「同意」):
EvalContextdeclared a kernel query API —api?: { exists, count, lookup }, docblocked asos.exists / os.count / os.lookup, "implemented opportunistically by call sites that have a query engine" — andbuildScope()never read it. The declaration is removed, with the reason written at the deletion site. No shim, no alias, no reserved spelling; the three functions are not implemented here, and the real capability need (reading a related record's field inside a predicate) is tracked on its own card, #18682, which nothing in this PR advances or blocks.The premise, re-measured on the branch base (df1b275)
ctx.api/.apiinpackages/formula/srcctx.userinstdlib.ts: 3 hitsos.exists/os.count/os.lookupin the tree, CHANGELOGs anddist/excludedSo the card's reading holds exactly: the member was declared, documented, and bound nowhere.
The LEVEL is
minor, and that is a reading rather than an assumptionThe question the grade turns on: can a consumer set
apitoday and get anything out of it? No.buildScope()bindsrecord,previous,input,os.user/os.org/os.env, andextra— it has never readctx.api, so every implementation ever passed there was discarded before evaluation.os.lookupfails whether or not the call site suppliesapi— pinned as a test in this PR.EvalContextliteral that carriesapi, and the sweep below finds zero such literals in this repo and zero in the pinned sibling.This is the shape PR #18717 graded
minorin the same round — what changed never delivered the semantics it reported, so depending on it meant depending on nothing. The launch-windowmajorban (scripts/check-changeset-no-major.mjs) also applies and is recorded here as a constraint, not the justification. The changeset is@objectstack/formula: minorand carries the migration line it owes:api: { … }→ delete the property; there is no replacement key and no result changes.Consumer sweep — derived by TYPE, with a firing control
⛔ Not by text. A text grep is structurally useless here: the identically-named
ctx.apion the hookScopedContext(packages/spec/src/contracts/scoped-context.ts) is a live, unrelated surface this PR does not touch, which is why the dispatching seat's ownapi: *{grep returned 234 files and was reported as an instrument failure.Population — the 19 workspace packages that declare
@objectstack/formula. Every one has atypecheckscript, so no filter matched zero scripts and exited 0 without running anything.Measurement leg (at
d706b60b57, afterturbo run build— 73 tasks successful, so each package resolves formula through its rebuiltdist/index.d.ts): all 19 packages typecheck green,error TScount 0.Firing control — the same 19-package run with a REQUIRED member injected into
EvalContext(__ablationProbeFiringControl: 'fire'), formula rebuilt, and the marker proved present indist/bynode scripts/ablation-dist-preflight.mjs @objectstack/formula '__ablationProbeFiringControl'before the colour was read:error TS2345supply sites across 13 files, plus two more files caught a layer down by the test-typecheck ledger gate (platform-objects,plugin-auth) — e.g.metadata-protocol/src/seed-loader.ts:1026,objectql/src/validation/rule-validator.ts(6 sites),service-automation/src/engine.ts,plugin-approvals/src/approval-service.ts.EvalContextchanges incompatibly. The green above is a measurement, not a silence.Restore leg:
git checkout HEAD -- packages/formula/src/types.ts,git hash-objectequal to the HEAD blob27f991bbd7…, whole-treegit status --porcelainempty, formula rebuilt, andablation-dist-preflight … --absentgreen — so no mutated byte survives indist/for a later run to read.The opposite direction the card asked about — does anything CONSTRUCT an
EvalContextcarryingapi? — is answered by the same green leg: after the removal such a literal is a compile error, and none exists.Pinned sibling (Post-Task Checklist step 4, Console Pin Gate): objectui at
.objectui-sha=53ded82bf7a494f54e344e19099dbf00854b8694namesEvalContextzero times; control in the same tree — four distinctfrom '@objectstack/formula'import lines (ExpressionEngine,validateExpression,collectCelRootIdentifiers,firstUndeclaredReference) acrosspackages/app-shell. Nothing there is broken by this removal and no pin bump is owed.Tests
packages/formula: 31 files / 876 tests pass,pnpm typecheckgreen. The newsrc/eval-context-no-query-api.test.tspins the retirement in both directions — a type-level pin (@ts-expect-erroronapiin anEvalContextliteral) and two runtime pins (buildScopebinds nothing from a context still carrying one; a predicate callingos.lookupis refused even when an implementation is supplied).The type-level pin is verified live rather than assumed: formula's test-typecheck ledger reports the same 3 files / 7 errors / 4 signatures as before this PR. That ledger is exact and shrink-only, so an
@ts-expect-errorthat had become unused would arrive as TS2578 and red it. It stayed silent, which means the directive is used —apireally is rejected.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 57 gate families from the change set itself (the 3 paths above). All 57 were run at this branch's head and every one exited 0;--ranreconciliation with the exit codes recorded reports 57 derived / 57 run / 0 NOT-MEASURED / 0 UNRUN. The artifact-roster, wide-population and path-scheduled families the same tool prints outside that total remain CI's.pnpm lintis CI's repo-wide run; the local delivery is a declared narrowing with its three readings: ① the population eslint's own config admits is 6819 files (git ls-filesfiltered to lintable extensions, each asked throughESLint#isPathIgnored; 0 ignored) — noteeslint.config.mjs's own header records 4659 from an older tree, a drift this PR does not touch; ② the narrowed run linted 2 files (--format json, result length), 0 errors / 0 warnings; ③ the config enables no type-aware linting (noparserOptions.project, noprojectService, no typed@typescript-eslintrules — its header says so and the config carries no such key), so this diff cannot move any untouched file's verdict.skip-changesetmeasured rather than assumed:@objectstack/formula'sfiles[]isdist,README.md,CHANGELOG.md; after the build,packages/formula/dist/index.d.tscarries theEvalContextinterface withgrep -c "api?:"= 0 — positive controlgrep -c "EvalContext"= 10 in the same file. Published bytes move, so the label would be wrong and a changeset is owed and present.Acceptance notes
eslint.config.mjs's header states "across all 4659 linted files" while the population reads 6819 today. A stale prose count in a config header — an observation, not a defect, a contract violation or an authoring trap. Carrier: the next PR that edits that header.ctx.apion the hookScopedContextis untouched and stays live; nothing in this PR narrows it.needs:contract-reviewbelongs to the dispatching seat, which hangs it on both carriers. This PR neither hangs, removes nor waits on it.Generated by Claude Code