You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
formula: a CEL environment with the full stdlib is built on every compile and every evaluate (buildEnv → registerStdLib), reached per row by formula fields — about 7% of a read's CPU #24536
Filed by the repo:cloud#2 seat (seat post #24145, session session_01Cbdek8SKSHxddGqTPZXdqX) from objectstack-ai/cloud#3053 rounds 1–2 (dev reports 6266807911 and 6267763185). ⛔ Not a claim: the lane dispatches it.
Why it matters. Concurrent requests to one hosted tenant runtime serialize on per-request CPU in its single Node event loop:
8 distinct reads fired together take 5.4–8.9× one read;
a dashboard's 35-call analytics wave takes about the sum of its members;
inside a batch the handler leg grows from 10–17 ms to 100–450 ms, while db stays at about 2 ms.
This is measured on local hosted-shaped rigs at framework 584b4225 and fb9c22aa with HotCRM installed. It is the same at both pins, so it is not a regression; it is the steady cost that a near database exposes. The CPU profile is from --cpu-prof, with 1,400 analytics calls and 150 batches of 8 reads, every response 200. This card is one named slice of that CPU.
What was measured (equal at 584b4225 and fb9c22aa)
The call chain:packages/formula/src/cel-engine.ts:71buildEnv calls registerStdLib (:77). It is called at :1751 (compile) and :1805 (evaluate).
How data reads reach it: per row, through objectql applyFormulaPlan (HotCRM's formula fields, e.g. crm_account.display_title).
Cost:
buildEnv 1.10 ms per HotCRM top=1 read (7.1%);
0.88 ms per analytics call (6.6%).
Target
Build the stdlib environment once per timezone and bind now and the subject per evaluation, or cache compiled programs per source plus environment key. Evaluation results stay identical.
Acceptance
A test counts environment builds per evaluate. Repeated evaluations of one source build no new environment.
The formula and CEL suites stay green.
Ablation: removing the cache turns the count test red.
Triage (first touch): grade and lane confirmed · filing-gate answers supplied · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T14:25Z
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. This is the first triage touch. The filer applied the labels, and the body lacked the filing gate's three answers, so no execution seat could claim it under execution-duties.md:11. This comment supplies them. The labels are confirmed as they stand.
Filing gate ①, class (a): a measured per-row CPU cost on the data read path, not a regression, and a named slice of objectstack-ai/cloud#3053 (p1).
Reader who acts: the domain:engine seat (packages/formula). Build the CEL environment once per (now-binding, timezone, permission-binding) shape and reuse it, instead of buildEnv → registerStdLib on every compile and evaluate. Pin it with a call count plus an ablation.
Claim: PM loop round 12 · 2026-10-19T16:55Z · a p1 from the repo:cloud#2 seat (cloud#3053 rounds 1–2), confirmed by the triage seat's first touch 6269577066, taken by the slot the maintainer's new concurrency order of 3 opens (「并发3」). The formula CEL environment is built once and reused: now, the timezone and the permission binding are bound per evaluation, never by rebuilding the stdlib. Results stay identical.
Session: session_01JtSQQ3EWsbNPzyYMug1tXy (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24536-cel-env-reuse
Worktree: objectstack-issue-24536
Domain: domain:engine
Seat: domain:engine#1 (seat post #6367)
File surface:
The environment,packages/formula/src/cel-engine.ts: buildEnv and its two hot callers, celEngine.compile and celEngine.evaluate.
The evaluate path reuses one environment. Per evaluation, now, the timezone and the PermissionBinding are bound through a slot the stdlib's closures read. The slot is set and restored in a finally, so a thrown evaluation never leaves its subject behind.
The compile path reuses one parse environment, as canonicalParseEnv already does.
A compiled-program cache per source is optional. It is taken only if measured, and its key carries every input that changes the program.
The stdlib,packages/formula/src/stdlib.ts: only as far as its closures read the per-evaluation slot. ⛔ The exported registerStdLib keeps its signature and its behaviour for every caller that passes now, the timezone and a binding.
Pins beside each, and the changeset: @objectstack/formulapatch, Clause-②: no.
Container & model: M, mode:cloud (measured first: environment builds per read and the per-evaluate cost, on main and after), model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier: "no path-derived mandate")
Clause-②: no (an internal cache: no accept set moves, no evaluation result moves, no exported signature changes; owes the seat's review of record before the queue)
Responsibility: buildEnv → registerStdLib runs on every compile and every evaluate, per row through objectql's applyFormulaPlan, about 7% of a HotCRM read's CPU | the stdlib was always built per call; the cost shows once the database is near | every hosted tenant read of an object with formula fields, first cloud#3053's concurrent-read serialization
Thread-read: 6269577066
Serial constraints cleared: open PRs' file lists and the pm:dispatched claims were read at this claim.
Of the 15 open PRs, only the release PR chore: version packages #24524 touches packages/formula, and only its CHANGELOG.md and package.json. No open PR touches cel-engine.ts or stdlib.ts.
Dispatch · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T16:57Z: mode:cloud. The dev session is session_01QNSdgij7mWezAKUbkzSmmR. It builds at the default tier on branch claude/issue-24536-cel-env-reuse, under claim 6272538729.
Measured first, on main: environment builds per evaluate and per read of an object with formula fields, and a per-evaluate microbenchmark. The dev also reads what a cel-js Environment holds per evaluation before sharing one.
What lands:
one reused evaluation environment, with now, the timezone and the permission binding bound per evaluation through a slot that is restored in a finally;
a reused compile environment;
a per-source program cache, only if the measurement shows it is worth its code.
Not built: any change to an evaluation's result or error, or to the exported registerStdLib family's signatures.
{
"issue": 24536,
"status": "done",
"branch": "claude/issue-24536-cel-env-reuse",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24629",
"session": "session_01QNSdgij7mWezAKUbkzSmmR",
"premise_still_valid": true,
"summary": "The premise held on origin/main c03b17d784: every celEngine.evaluate and every celEngine.compile built a new cel-js Environment and registered the whole stdlib into it, which was about 135 µs of a 141.7 µs evaluate (a prebuilt environment parses and evaluates the same source in about 7 µs). A 25-row read with three formula fields built 78 environments. Now evaluate reuses one environment, and the stdlib reads now, the timezone and the PermissionBinding from an unexported per-evaluation slot (activeBinding). The slot is saved, set and restored in a finally. compile reuses canonicalParseEnv. A bounded program cache (500 entries, FIFO), keyed on the rewritten source, was taken because the measurement showed parse and check were most of the remaining cost. Results: evaluate 141.7 to 3.2 µs on one source x 20,000, 0 environment builds per evaluate or per read, and the served body of a formula read is byte-identical. index.ts, the registerStdLib / registerPermissionPredicate / registerCapabilityPredicate signatures and behaviour, celEngine's members, the stdlib drift pin and the unknown-function oracle are all unchanged.",
"tests": "At HEAD 1040a3a0c (after merging origin/main 8fbbd5082) unless noted. pnpm --filter @objectstack/formula test: 50 files, 1363 passed, exit 0. pnpm --filter @objectstack/formula typecheck (tsc --noEmit + check:test-typecheck): exit 0. pnpm --filter @objectstack/objectql test: 447 files, 8414 passed, exit 0 (at 826ce6ae, pre-merge; the merge touched neither package). pnpm --filter @objectstack/objectql test:repo: 1 file, 5 passed. pnpm --filter @objectstack/core test:repo: 6 files, 82 passed. pnpm --filter '@objectstack/objectql...' build: exit 0 post-merge. New pins: src/cel-env-reuse.test.ts counts environment constructions through a vi.mock subclass of cel-js Environment. It has a positive control; repeated evaluate, varied now/tz/subject/permissions, and repeated compile each build 0; and program-cache cases (#7168 rewrite on alternating rows, overload retry on alternating rows, parse/check/runtime/unknown-function faults identical on every evaluation). src/cel-eval-binding-isolation.test.ts: an unbound evaluation after a bound one, and after a thrown one, answers exactly as a no-binding buildEnv environment; a nested evaluation, run through a scope getter, leaves the outer binding, now() and timezone intact whether the inner one returns or throws; now and timezone are per evaluation. Ablations via scripts/ablation-replace.mjs, each with anchor x1 to x0, blob moved, and restore blob == HEAD 5c934212c2f8 with git diff HEAD empty. The formula suite imports src, so no dist leg. A1, evaluation env reuse removed: 2 failed / 17 passed (both count pins). A2, compile env reuse removed: 1 failed / 9 passed (the compile count pin). B, the finally restore deleted: 3 failed / 6 passed (all three nested pins). B's first attempt passed an empty --replacement; the tool refused it (count 0 to 0) and restored, and it was re-run with --delete. Measurement M was taken with a throwaway vitest probe, not committed: ObjectQL + ObjectStackProtocolImplementation + a stub driver, 25 rows, 3 formula fields, counting stdlib and buildEnv registrations. Builds per evaluate went 1 to 0, builds per read 78 to 0. evaluate went 141.7 to 9.1 µs after env reuse and to 3.2 µs after the program cache; compile went 159.2 to 5.2 µs. These are shared-box absolutes. findData body: 3,625 bytes, string-identical before and after.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths at HEAD 1040a3a0c, derived 66 commands (the dispatch's path-scoped lead said 49). All 66 ran, each exit captured before any pipe: 66 x exit 0. pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist); it was built for (pnpm build under the verify lock, 76/76 tasks) and re-run, exit 0. pnpm check:type-check-debt overran one 590 s foreground chunk, so it was re-run alone (nohup + tail --pid): exit 0, '1 ledger entr(ies) re-measured … none above its recorded number'. The clone was deepened first (git fetch --shallow-since=2026-07-13): check-plugin-teardown-shape --self-test and the rest exited 0. Reconciliation, dispatch-gates --ran with 'command :: exit code' lines: '✓ dispatch-gates --ran: 66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED (a DERIVED zero — all 66 recorded an exit code and none of them is 3).' Typecheck of every touched package: only @objectstack/formula, exit 0. scripts/check-platform-checklist.mjs was not run: no exported symbol was renamed or deleted, only two package-internal exports added in stdlib.ts. CI on PR #24629 head 1040a3a0c at report time: 'No other open PR may claim the same issue' success; Packed-tarball smoke skipped; the other 21 runs in_progress, not waited on.",
"mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
"api_writes": "2 relay strokes as objectstack-fleet[bot]: (1) pr_create, fleet-write request fw-20261019T180145Z-f67aaf, run 39299127722 success: POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24629/assignees (os-project-manager). Read back: 10594 bytes sent and stored identical, assignee listed, draft true, body diffed identical. (2) this os-dev-report: POST /repos/objectstack-ai/objectstack/issues/24536/comments. Plus git push to claude/issue-24536-cel-env-reuse, which is not REST: 1 empty-branch probe and 4 pushes of commits (79fde52c/b5ec0a02, 8eb3b5a8/826ce6ae, the merge 1040a3a0c).",
"line_budget": "none set. Diff vs origin/main 8fbbd5082 (three-dot): .changeset/24536-cel-env-reuse.md +12/-0; packages/formula/src/cel-engine.ts +139/-52; packages/formula/src/cel-env-reuse.test.ts +123/-0; packages/formula/src/cel-eval-binding-isolation.test.ts +140/-0; packages/formula/src/stdlib.ts +68/-9. Total +482/-61.",
"files_changed": [
".changeset/24536-cel-env-reuse.md",
"packages/formula/src/cel-engine.ts",
"packages/formula/src/cel-env-reuse.test.ts",
"packages/formula/src/cel-eval-binding-isolation.test.ts",
"packages/formula/src/stdlib.ts"
],
"deviations": [
"Not needed: objectql (applyFormulaPlan). The fix landed entirely in packages/formula, inside the claim's file surface.",
"stdlib.ts gained two package-internal exports, registerBoundStdLib and StdLibBinding, so cel-engine.ts can build the shared environment. index.ts re-exports neither and is untouched. The slot itself is an unexported module binding in cel-engine.ts.",
"Timezone: bound per evaluation through the slot. No per-timezone environment cache exists.",
"Commit shape: the three suggested commits (eval env + slot, compile env, program cache) plus a changeset commit and a merge of origin/main. The commit trailers are the model-free AGENTS.md pair. The harness's attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer; AGENTS.md / os-dev.md were followed instead, as they take precedence.",
"The dispatch prompt and os-dev.md did not conflict on anything material. os-dev.md's three-write budget (git push, pr_create, report comment) was met, with git push counted as one leg across several pushes to the own branch."
],
"open_questions": [
{
"question": "Program cache: take it or not? The card made it optional, to be taken only if parsing was still the larger remaining cost.",
"options": [
"A take it: bounded 500, FIFO, keyed on the rewritten source, no invalidation needed",
"B leave it out"
],
"recommendation": "A, taken. Real business need: measured 9.1 µs of which about 6 was parse and check, and objectql evaluates one source once per row, so the cut to 3.2 µs scales with page size. Long-term soundness: it is cel-js's own parse-once, evaluate-many API on an environment whose registrations never change, and the bound mirrors temporalRewriteCache. AI error: neutral; no authoring surface changes. Startup focus: about 30 lines, no new export, no new gate. Settled by the dev; the seat may overrule."
},
{
"question": "Timezone: a per-timezone environment cache, or bound per evaluation?",
"options": [
"A bind it per evaluation through the slot (one environment)",
"B a small bounded cache keyed on the timezone"
],
"recommendation": "A, taken. One environment serves every zone, with nothing to bound and no second key. The slot already has to carry now and the binding, and the pins prove the timezone is per evaluation, nested evaluations included."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed: collectCelRootIdentifiers in packages/formula/src/cel-engine.ts still builds a fresh environment per call (buildEnv(() => new Date(0)).parse). Runtime callers include objectql validation/rule-validator.ts and plugin-approvals approval-service.ts. Not measured, and outside the claim's file surface; the canonicalParseEnv ??= shape fits. In PR Acceptance notes. Dedupe words: collectCelRootIdentifiers buildEnv per call, rule-validator CEL environment, canonicalParseEnv reuse."
]
}
{
"issue": 24536,
"status": "done",
"branch": "claude/issue-24536-cel-env-reuse",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24629",
"session": "session_01QNSdgij7mWezAKUbkzSmmR",
"round": "fix round 1, answering Contract review 6275342648 (FAIL on 1040a3a0c)",
"head": "95f434429ab0071ec19d6e1b000e25dedc5a88a6",
"premise_still_valid": true,
"summary": "The record's ground holds against cel-js 8.0.0. One cached AST per source, plus the iterCtx / bindCtx overlays that OverlayContext.reuse re-parents, let a same-source evaluation re-entered from inside a comprehension or a cel.bind read the inner run's current_user. That reproduced red on 1040a3a0c. The fix applies a rule equivalent to the seat's, and stricter: a cached program serves one run at a time. runningPrograms marks a program while it runs, and a run that finds its source's program already running parses a private one and stores nothing. Why it is equivalent: the hazard is two runs of ONE AST at once, and that is exactly what the rule excludes. Different sources share no AST nodes; what they do share is the registry's type-keyed caches. A nested evaluation of a DIFFERENT source, and every top-level evaluation, stay on the cache. The rule keys on the program, not on the slot being non-UNBOUND, so it holds whether or not the slot is set. The async path was read and measured. reuse makes a fresh overlay once ctx.async is set, which happens only after a step yields a Promise. cel-js goes async only for a function registered as async, and the evaluation environment registers none. A Promise-valued scope value is refused ('Unsupported type: Promise', runtime), measured on record.x, record.x == 2, [record.x].exists(…) and a comprehension over Promise-valued fields. So every run is synchronous, and a run ends when the program returns or throws; no async branch is built. Prose corrected: the evaluationPrograms comment now names the iterCtx / bindCtx overlays and the runtime argTypes writes, and says a cached program keeps its last evaluation's scope reachable until the next evaluation of that source. The activeBinding doc now carries one sentence on Promise data, corrected from the record's ③ reading by the measurement: a Promise value is refused, so no continuation reads the slot. unknown-function.ts's stale 'both call buildEnv' is fixed. The changeset makes no 'never a value' claim, and its 'results … unchanged' is true again, so it is unchanged. The microbenchmark's top-level number did not move: 2.9 µs per evaluate, against 3.2 µs on 1040a3a0c, which is within box noise.",
"files_changed_this_round": [
"packages/formula/src/cel-engine.ts (+44/-4)",
"packages/formula/src/cel-program-reentry.test.ts (+81/-0, new)",
"packages/formula/src/unknown-function.ts (+1/-1)"
],
"tests": "New pin packages/formula/src/cel-program-reentry.test.ts. The same source is nested through a scope getter that evaluates it for BOB: inside exists(…), inside map(…) and inside cel.bind(…). Each answers for the outer subject ALICE, equal to a fresh buildEnv parse of the same scope, with the cache warmed first. A fourth case checks the inner run answers for BOB. RED on the old head: cel-engine.ts restored to 1040a3a0c in the tree only (git restore --source=1040a3a0c, guard count on disk 0) gave 3 failed / 1 passed (exists, map, cel.bind). Restored with git checkout HEAD, blob 021ae582c7bb == HEAD. Ablation through scripts/ablation-replace.mjs, guard 'else if (runningPrograms.has(program))' to 'else if (false)', anchor x1 to x0, blob 021ae582c7bb to a2f41fe69b45: 3 failed / 1 passed. Restored: blob == HEAD, git diff HEAD empty, git status clean. GREEN at 95f434429: the three pin files (reentry, env-reuse, binding-isolation) 23 passed. pnpm --filter @objectstack/formula test: 51 files, 1367 passed, exit 0. pnpm --filter @objectstack/formula typecheck: exit 0. pnpm --filter @objectstack/core test:repo: 6 files, 82 passed, exit 0. pnpm --filter @objectstack/formula build: exit 0. The microbenchmark (the same throwaway objectql probe, uncommitted and deleted) gave 2.88 µs per evaluate on one source x 20,000, against 3.18 µs at 1040a3a0c, so the top-level number does not move beyond noise. Builds per evaluate: 0. Builds per 25-row read: 0. findData body string-identical to origin/main c03b17d784's.",
"gates": "Derived for this round's files: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack packages/formula/src/cel-engine.ts packages/formula/src/unknown-function.ts packages/formula/src/cel-program-reentry.test.ts gave 55 commands. All 55 exited 0, each exit captured before any pipe. pnpm check:dual-build-cjs-loads first exited 3 (no dist in the fresh worktree); after pnpm build (76/76 tasks) it was re-run, exit 0. pnpm check:type-check-debt: exit 0, '1 ledger entr(ies) re-measured … none above its recorded number'. Reconciliation: '✓ dispatch-gates --ran: 55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED (a DERIVED zero — all 55 recorded an exit code and none of them is 3).' Why the rest of the 66-command run stands: the no-path derivation at 95f434429 is the same 66 commands as at 1040a3a0c (sorted diff empty). The 11 outside this round's 55 are the changeset and release families: check-adr-0087-registration x2, check-changeset-no-major x2, check-empty-changeset x2, release-rehearsal-clone --self-test, release-pending-publish --self-test, check:changeset-gate-self-tests, check:objectui-changeset, check:pm-changeset-deadline-census. They read .changeset/24536-cel-env-reuse.md, which this round did not touch, and they exited 0 at 1040a3a0c. origin/main was not merged: git merge-tree against it reports no conflict.",
"suggested_pr_body_passage": "## What a cel-js `Environment` holds, and what a cached program holds\n\nRead in `@marcbachmann/cel-js` 8.0.0 (`lib/evaluator.js`, `lib/registry.js`, `lib/parser.js`, `lib/type-checker.js`, `lib/operators.js`, `lib/macros.js`).\n\n**The environment** carries no per-evaluation state:\n- each evaluation builds its own `RootContext`, which holds the variable values and the only value-level cache, `#convertCache`;\n- `Parser.parse` resets its input and its limit counters on every call;\n- the registry's caches (`Candidates` match and check, type declarations, list, map and optional types, `TypeDeclaration#matchesCache`) are keyed on types;\n- the registry locks only when it is cloned, and nothing registers on the shared environment after it is built.\n\n**A parsed program is not free of evaluation state.** cel-js keeps per-run state on the AST:\n- a comprehension (`exists`, `all`, `exists_one`, `map`, `filter`) keeps its iteration context on the AST (`args.iterCtx`, `operators.js:667`), and `cel.bind` keeps its binding context there (`bindCtx`, `macros.js:195`);\n- each run re-parents that overlay to its own scope (`OverlayContext.reuse`, `registry.js:1423`), and every non-iteration variable read inside the step goes through that parent;\n- `callFn` / `callRecFn` write the arguments' runtime types into `ast.argTypes` on every call (`operators.js:302`, `:319`).\n\nIn sequential use each of these is overwritten before it is read, so a cached program answers exactly as a fresh parse does. Two runs of ONE AST at once do not: a run re-entered from inside its own comprehension would read the inner run's scope. So a cached program serves one run at a time (`runningPrograms`), and a nested evaluation of the same source parses a private program. Runs are synchronous: this environment registers no async function, and cel-js refuses a Promise-valued scope value (`Unsupported type: Promise`, measured). A cached program also keeps its last evaluation's scope (the `RootContext` its overlays point at, and their `iterValue` / `accuValue`) reachable until the next evaluation of that source, bounded by the cache's 500 entries.\n\nThe three inputs that did vary per evaluation (now, timezone, binding) are the ones moved into the slot. **Timezone: bound per evaluation, not a cache keyed on it.** With the timezone in the slot, one environment serves every zone, so no per-timezone cache exists to bound.",
"pr_body_patch_note": "Replace the PR body's whole section '## What a cel-js `Environment` holds (read before sharing one)', through the paragraph ending 'no per-timezone cache exists to bound.', with suggested_pr_body_passage. In the body's '## What changes' item 3, append one bullet: '**One run at a time:** a cached program is marked while it runs, and a nested evaluation of the same source parses a private program (commit 95f434429).' The dev did not edit the PR body.",
"mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
"api_writes": "1 relay stroke this round: this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/24536/comments. Plus 1 git push, which is not REST: 1040a3a0c to 95f434429 on claude/issue-24536-cel-env-reuse. Reads were REST GETs only: the PR comments and the commit check-runs.",
"line_budget": "none set. This round vs 1040a3a0c: cel-engine.ts +44/-4, cel-program-reentry.test.ts +81/-0, unknown-function.ts +1/-1. Whole PR vs origin/main (three-dot): .changeset/24536-cel-env-reuse.md +12/-0; cel-engine.ts +179/-52; cel-env-reuse.test.ts +123/-0; cel-eval-binding-isolation.test.ts +140/-0; cel-program-reentry.test.ts +81/-0; stdlib.ts +68/-9; unknown-function.ts +1/-1.",
"files_changed": [
".changeset/24536-cel-env-reuse.md",
"packages/formula/src/cel-engine.ts",
"packages/formula/src/cel-env-reuse.test.ts",
"packages/formula/src/cel-eval-binding-isolation.test.ts",
"packages/formula/src/cel-program-reentry.test.ts",
"packages/formula/src/stdlib.ts",
"packages/formula/src/unknown-function.ts"
],
"deviations": [
"Guard rule: the program-keyed equivalent, not the slot-keyed one. Equivalence is argued in summary. It also keeps nested evaluations of OTHER sources on the cache.",
"The record's ③ Promise reading (a .then continuation reads UNBOUND) does not match a measurement. A Promise-valued scope value is refused by cel-js before any continuation exists. The slot's doc sentence states what was measured instead, so no Promise-release code was built.",
"unknown-function.ts is outside the claim's file surface. It carries a one-line prose fix the seat offered in its ③ (non-blocking)."
],
"open_questions": [],
"out_of_scope_findings": []
}
TIER_DEFAULT record 6275342648 was FAIL on 1040a3a0c: a cached program was not re-entrant. Delta record 6276125243 is PASS on 95f434429a. ACCEPT 6276543757. Not governed.
Queue
It entered the merge queue at 2026-10-19T20:27Z and left it merged at 2026-10-19T20:56Z. No gh-readonly-queue/main/pr-24629-* branch remains.
origin/main
4840720d4e is on origin/main. It carries PR #24629's 7 files, +604 / −62.
Card
Closed completed through Fixes #24536; pm:dispatched is cleared.
What main now holds:
One CEL evaluation environment, built once.celEngine.evaluate no longer builds a cel-js Environment and registers the whole stdlib per call. now, the timezone and the permission binding are bound per evaluation through a module slot, restored in a finally. Measured: 141.7 → about 3 µs per evaluate, and 78 → 0 environment builds per 25-row read.
compile reuses the shared parse environment (canonicalParseEnv).
A per-source program cache: 500 entries, oldest out first, keyed on the source after the rewrites. A program serves one run at a time, so a nested evaluation of the same source parses a private program.
Unchanged: every evaluation result, error kind and message; the stdlib drift pin and the unknown-function oracle; the signatures of registerStdLib, registerPermissionPredicate and registerCapabilityPredicate. @objectstack/formula is patch.
Downstream: the repo:cloud#2 seat is pointed at this landing for cloud#3053's buildEnv slice.
The dev session session_01QNSdgij7mWezAKUbkzSmmR is archived.
Activity
objectstack-fleet commentedon Oct 19, 2026
Triage (first touch): grade and lane confirmed · filing-gate answers supplied · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T14:25ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. This is the first triage touch. The filer applied the labels, and the body lacked the filing gate's three answers, so no execution seat could claim it under
execution-duties.md:11. This comment supplies them. The labels are confirmed as they stand.domain:engineseat (packages/formula). Build the CEL environment once per (now-binding, timezone, permission-binding) shape and reuse it, instead ofbuildEnv→registerStdLibon every compile and evaluate. Pin it with a call count plus an ablation.CEL_STDLIB_FUNCTIONSadvertises 35 names but the CEL environment registers 72 — any consumer keying on the exported constant is blind to 37 real functions #13831 (closed: the stdlib name list, a different defect). No duplicate.target:v18.origin/mainba4afcc239:packages/formula/src/cel-engine.ts:72buildEnv, which callsregisterStdLibat:78. The premise holds.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 12 · 2026-10-19T16:55Z · a p1 from the
repo:cloud#2seat (cloud#3053 rounds 1–2), confirmed by the triage seat's first touch 6269577066, taken by the slot the maintainer's new concurrency order of 3 opens (「并发3」). The formula CEL environment is built once and reused:now, the timezone and the permission binding are bound per evaluation, never by rebuilding the stdlib. Results stay identical.Session:
session_01JtSQQ3EWsbNPzyYMug1tXy(the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)Account:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24536-cel-env-reuseWorktree:
objectstack-issue-24536Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
packages/formula/src/cel-engine.ts:buildEnvand its two hot callers,celEngine.compileandcelEngine.evaluate.now, the timezone and thePermissionBindingare bound through a slot the stdlib's closures read. The slot is set and restored in afinally, so a thrown evaluation never leaves its subject behind.canonicalParseEnvalready does.packages/formula/src/stdlib.ts: only as far as its closures read the per-evaluation slot. ⛔ The exportedregisterStdLibkeeps its signature and its behaviour for every caller that passesnow, the timezone and a binding.@objectstack/formulapatch,Clause-②: no.Container & model:
M,mode:cloud(measured first: environment builds per read and the per-evaluate cost, onmainand after),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no (an internal cache: no accept set moves, no evaluation result moves, no exported signature changes; owes the seat's review of record before the queue)
Responsibility:
buildEnv→registerStdLibruns on every compile and every evaluate, per row through objectql'sapplyFormulaPlan, about 7% of a HotCRM read's CPU | the stdlib was always built per call; the cost shows once the database is near | every hosted tenant read of an object with formula fields, first cloud#3053's concurrent-read serializationThread-read: 6269577066
Serial constraints cleared: open PRs' file lists and the
pm:dispatchedclaims were read at this claim.packages/formula, and only itsCHANGELOG.mdandpackage.json. No open PR touchescel-engine.tsorstdlib.ts.pm:dispatchedlane card holds apackages/formularegion. PR feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478 (EvalContextand the stdlib's permission predicates) and PR fix(lint,objectql)!: a formula reading a field its lookup target does not declare is refused at save, and no longer nulls its sibling formulas #24466 (the formula one-hop read) landed and hold nothing.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Dispatch ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-19T16:57Z:mode:cloud. The dev session issession_01QNSdgij7mWezAKUbkzSmmR. It builds at the default tier on branchclaude/issue-24536-cel-env-reuse, under claim 6272538729.6266807911and6267763185), and feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478's landed contract for the stdlib's permission predicates.main: environment builds per evaluate and per read of an object with formula fields, and a per-evaluate microbenchmark. The dev also reads what a cel-jsEnvironmentholds per evaluation before sharing one.now, the timezone and the permission binding bound per evaluation through a slot that is restored in afinally;registerStdLibfamily's signatures.repo:cloud#2seat ([PM seat] repo:cloud · seat 2 — ⏳ vacant #24145) for cloud#3053.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24536, "status": "done", "branch": "claude/issue-24536-cel-env-reuse", "pr": "https://github.com/objectstack-ai/objectstack/pull/24629", "session": "session_01QNSdgij7mWezAKUbkzSmmR", "premise_still_valid": true, "summary": "The premise held on origin/main c03b17d784: every celEngine.evaluate and every celEngine.compile built a new cel-js Environment and registered the whole stdlib into it, which was about 135 µs of a 141.7 µs evaluate (a prebuilt environment parses and evaluates the same source in about 7 µs). A 25-row read with three formula fields built 78 environments. Now evaluate reuses one environment, and the stdlib reads now, the timezone and the PermissionBinding from an unexported per-evaluation slot (activeBinding). The slot is saved, set and restored in a finally. compile reuses canonicalParseEnv. A bounded program cache (500 entries, FIFO), keyed on the rewritten source, was taken because the measurement showed parse and check were most of the remaining cost. Results: evaluate 141.7 to 3.2 µs on one source x 20,000, 0 environment builds per evaluate or per read, and the served body of a formula read is byte-identical. index.ts, the registerStdLib / registerPermissionPredicate / registerCapabilityPredicate signatures and behaviour, celEngine's members, the stdlib drift pin and the unknown-function oracle are all unchanged.", "tests": "At HEAD 1040a3a0c (after merging origin/main 8fbbd5082) unless noted. pnpm --filter @objectstack/formula test: 50 files, 1363 passed, exit 0. pnpm --filter @objectstack/formula typecheck (tsc --noEmit + check:test-typecheck): exit 0. pnpm --filter @objectstack/objectql test: 447 files, 8414 passed, exit 0 (at 826ce6ae, pre-merge; the merge touched neither package). pnpm --filter @objectstack/objectql test:repo: 1 file, 5 passed. pnpm --filter @objectstack/core test:repo: 6 files, 82 passed. pnpm --filter '@objectstack/objectql...' build: exit 0 post-merge. New pins: src/cel-env-reuse.test.ts counts environment constructions through a vi.mock subclass of cel-js Environment. It has a positive control; repeated evaluate, varied now/tz/subject/permissions, and repeated compile each build 0; and program-cache cases (#7168 rewrite on alternating rows, overload retry on alternating rows, parse/check/runtime/unknown-function faults identical on every evaluation). src/cel-eval-binding-isolation.test.ts: an unbound evaluation after a bound one, and after a thrown one, answers exactly as a no-binding buildEnv environment; a nested evaluation, run through a scope getter, leaves the outer binding, now() and timezone intact whether the inner one returns or throws; now and timezone are per evaluation. Ablations via scripts/ablation-replace.mjs, each with anchor x1 to x0, blob moved, and restore blob == HEAD 5c934212c2f8 with git diff HEAD empty. The formula suite imports src, so no dist leg. A1, evaluation env reuse removed: 2 failed / 17 passed (both count pins). A2, compile env reuse removed: 1 failed / 9 passed (the compile count pin). B, the finally restore deleted: 3 failed / 6 passed (all three nested pins). B's first attempt passed an empty --replacement; the tool refused it (count 0 to 0) and restored, and it was re-run with --delete. Measurement M was taken with a throwaway vitest probe, not committed: ObjectQL + ObjectStackProtocolImplementation + a stub driver, 25 rows, 3 formula fields, counting stdlib and buildEnv registrations. Builds per evaluate went 1 to 0, builds per read 78 to 0. evaluate went 141.7 to 9.1 µs after env reuse and to 3.2 µs after the program cache; compile went 159.2 to 5.2 µs. These are shared-box absolutes. findData body: 3,625 bytes, string-identical before and after.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths at HEAD 1040a3a0c, derived 66 commands (the dispatch's path-scoped lead said 49). All 66 ran, each exit captured before any pipe: 66 x exit 0. pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist); it was built for (pnpm build under the verify lock, 76/76 tasks) and re-run, exit 0. pnpm check:type-check-debt overran one 590 s foreground chunk, so it was re-run alone (nohup + tail --pid): exit 0, '1 ledger entr(ies) re-measured … none above its recorded number'. The clone was deepened first (git fetch --shallow-since=2026-07-13): check-plugin-teardown-shape --self-test and the rest exited 0. Reconciliation, dispatch-gates --ran with 'command :: exit code' lines: '✓ dispatch-gates --ran: 66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED (a DERIVED zero — all 66 recorded an exit code and none of them is 3).' Typecheck of every touched package: only @objectstack/formula, exit 0. scripts/check-platform-checklist.mjs was not run: no exported symbol was renamed or deleted, only two package-internal exports added in stdlib.ts. CI on PR #24629 head 1040a3a0c at report time: 'No other open PR may claim the same issue' success; Packed-tarball smoke skipped; the other 21 runs in_progress, not waited on.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write.", "api_writes": "2 relay strokes as objectstack-fleet[bot]: (1) pr_create, fleet-write request fw-20261019T180145Z-f67aaf, run 39299127722 success: POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24629/assignees (os-project-manager). Read back: 10594 bytes sent and stored identical, assignee listed, draft true, body diffed identical. (2) this os-dev-report: POST /repos/objectstack-ai/objectstack/issues/24536/comments. Plus git push to claude/issue-24536-cel-env-reuse, which is not REST: 1 empty-branch probe and 4 pushes of commits (79fde52c/b5ec0a02, 8eb3b5a8/826ce6ae, the merge 1040a3a0c).", "line_budget": "none set. Diff vs origin/main 8fbbd5082 (three-dot): .changeset/24536-cel-env-reuse.md +12/-0; packages/formula/src/cel-engine.ts +139/-52; packages/formula/src/cel-env-reuse.test.ts +123/-0; packages/formula/src/cel-eval-binding-isolation.test.ts +140/-0; packages/formula/src/stdlib.ts +68/-9. Total +482/-61.", "files_changed": [ ".changeset/24536-cel-env-reuse.md", "packages/formula/src/cel-engine.ts", "packages/formula/src/cel-env-reuse.test.ts", "packages/formula/src/cel-eval-binding-isolation.test.ts", "packages/formula/src/stdlib.ts" ], "deviations": [ "Not needed: objectql (applyFormulaPlan). The fix landed entirely in packages/formula, inside the claim's file surface.", "stdlib.ts gained two package-internal exports, registerBoundStdLib and StdLibBinding, so cel-engine.ts can build the shared environment. index.ts re-exports neither and is untouched. The slot itself is an unexported module binding in cel-engine.ts.", "Timezone: bound per evaluation through the slot. No per-timezone environment cache exists.", "Commit shape: the three suggested commits (eval env + slot, compile env, program cache) plus a changeset commit and a merge of origin/main. The commit trailers are the model-free AGENTS.md pair. The harness's attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer; AGENTS.md / os-dev.md were followed instead, as they take precedence.", "The dispatch prompt and os-dev.md did not conflict on anything material. os-dev.md's three-write budget (git push, pr_create, report comment) was met, with git push counted as one leg across several pushes to the own branch." ], "open_questions": [ { "question": "Program cache: take it or not? The card made it optional, to be taken only if parsing was still the larger remaining cost.", "options": [ "A take it: bounded 500, FIFO, keyed on the rewritten source, no invalidation needed", "B leave it out" ], "recommendation": "A, taken. Real business need: measured 9.1 µs of which about 6 was parse and check, and objectql evaluates one source once per row, so the cut to 3.2 µs scales with page size. Long-term soundness: it is cel-js's own parse-once, evaluate-many API on an environment whose registrations never change, and the bound mirrors temporalRewriteCache. AI error: neutral; no authoring surface changes. Startup focus: about 30 lines, no new export, no new gate. Settled by the dev; the seat may overrule." }, { "question": "Timezone: a per-timezone environment cache, or bound per evaluation?", "options": [ "A bind it per evaluation through the slot (one environment)", "B a small bounded cache keyed on the timezone" ], "recommendation": "A, taken. One environment serves every zone, with nothing to bound and no second key. The slot already has to carry now and the binding, and the pins prove the timezone is per evaluation, nested evaluations included." } ], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed: collectCelRootIdentifiers in packages/formula/src/cel-engine.ts still builds a fresh environment per call (buildEnv(() => new Date(0)).parse). Runtime callers include objectql validation/rule-validator.ts and plugin-approvals approval-service.ts. Not measured, and outside the claim's file surface; the canonicalParseEnv ??= shape fits. In PR Acceptance notes. Dedupe words: collectCelRootIdentifiers buildEnv per call, rule-validator CEL environment, canonicalParseEnv reuse." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24536, "status": "done", "branch": "claude/issue-24536-cel-env-reuse", "pr": "https://github.com/objectstack-ai/objectstack/pull/24629", "session": "session_01QNSdgij7mWezAKUbkzSmmR", "round": "fix round 1, answering Contract review 6275342648 (FAIL on 1040a3a0c)", "head": "95f434429ab0071ec19d6e1b000e25dedc5a88a6", "premise_still_valid": true, "summary": "The record's ground holds against cel-js 8.0.0. One cached AST per source, plus the iterCtx / bindCtx overlays that OverlayContext.reuse re-parents, let a same-source evaluation re-entered from inside a comprehension or a cel.bind read the inner run's current_user. That reproduced red on 1040a3a0c. The fix applies a rule equivalent to the seat's, and stricter: a cached program serves one run at a time. runningPrograms marks a program while it runs, and a run that finds its source's program already running parses a private one and stores nothing. Why it is equivalent: the hazard is two runs of ONE AST at once, and that is exactly what the rule excludes. Different sources share no AST nodes; what they do share is the registry's type-keyed caches. A nested evaluation of a DIFFERENT source, and every top-level evaluation, stay on the cache. The rule keys on the program, not on the slot being non-UNBOUND, so it holds whether or not the slot is set. The async path was read and measured. reuse makes a fresh overlay once ctx.async is set, which happens only after a step yields a Promise. cel-js goes async only for a function registered as async, and the evaluation environment registers none. A Promise-valued scope value is refused ('Unsupported type: Promise', runtime), measured on record.x, record.x == 2, [record.x].exists(…) and a comprehension over Promise-valued fields. So every run is synchronous, and a run ends when the program returns or throws; no async branch is built. Prose corrected: the evaluationPrograms comment now names the iterCtx / bindCtx overlays and the runtime argTypes writes, and says a cached program keeps its last evaluation's scope reachable until the next evaluation of that source. The activeBinding doc now carries one sentence on Promise data, corrected from the record's ③ reading by the measurement: a Promise value is refused, so no continuation reads the slot. unknown-function.ts's stale 'both call buildEnv' is fixed. The changeset makes no 'never a value' claim, and its 'results … unchanged' is true again, so it is unchanged. The microbenchmark's top-level number did not move: 2.9 µs per evaluate, against 3.2 µs on 1040a3a0c, which is within box noise.", "files_changed_this_round": [ "packages/formula/src/cel-engine.ts (+44/-4)", "packages/formula/src/cel-program-reentry.test.ts (+81/-0, new)", "packages/formula/src/unknown-function.ts (+1/-1)" ], "tests": "New pin packages/formula/src/cel-program-reentry.test.ts. The same source is nested through a scope getter that evaluates it for BOB: inside exists(…), inside map(…) and inside cel.bind(…). Each answers for the outer subject ALICE, equal to a fresh buildEnv parse of the same scope, with the cache warmed first. A fourth case checks the inner run answers for BOB. RED on the old head: cel-engine.ts restored to 1040a3a0c in the tree only (git restore --source=1040a3a0c, guard count on disk 0) gave 3 failed / 1 passed (exists, map, cel.bind). Restored with git checkout HEAD, blob 021ae582c7bb == HEAD. Ablation through scripts/ablation-replace.mjs, guard 'else if (runningPrograms.has(program))' to 'else if (false)', anchor x1 to x0, blob 021ae582c7bb to a2f41fe69b45: 3 failed / 1 passed. Restored: blob == HEAD, git diff HEAD empty, git status clean. GREEN at 95f434429: the three pin files (reentry, env-reuse, binding-isolation) 23 passed. pnpm --filter @objectstack/formula test: 51 files, 1367 passed, exit 0. pnpm --filter @objectstack/formula typecheck: exit 0. pnpm --filter @objectstack/core test:repo: 6 files, 82 passed, exit 0. pnpm --filter @objectstack/formula build: exit 0. The microbenchmark (the same throwaway objectql probe, uncommitted and deleted) gave 2.88 µs per evaluate on one source x 20,000, against 3.18 µs at 1040a3a0c, so the top-level number does not move beyond noise. Builds per evaluate: 0. Builds per 25-row read: 0. findData body string-identical to origin/main c03b17d784's.", "gates": "Derived for this round's files: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack packages/formula/src/cel-engine.ts packages/formula/src/unknown-function.ts packages/formula/src/cel-program-reentry.test.ts gave 55 commands. All 55 exited 0, each exit captured before any pipe. pnpm check:dual-build-cjs-loads first exited 3 (no dist in the fresh worktree); after pnpm build (76/76 tasks) it was re-run, exit 0. pnpm check:type-check-debt: exit 0, '1 ledger entr(ies) re-measured … none above its recorded number'. Reconciliation: '✓ dispatch-gates --ran: 55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED (a DERIVED zero — all 55 recorded an exit code and none of them is 3).' Why the rest of the 66-command run stands: the no-path derivation at 95f434429 is the same 66 commands as at 1040a3a0c (sorted diff empty). The 11 outside this round's 55 are the changeset and release families: check-adr-0087-registration x2, check-changeset-no-major x2, check-empty-changeset x2, release-rehearsal-clone --self-test, release-pending-publish --self-test, check:changeset-gate-self-tests, check:objectui-changeset, check:pm-changeset-deadline-census. They read .changeset/24536-cel-env-reuse.md, which this round did not touch, and they exited 0 at 1040a3a0c. origin/main was not merged: git merge-tree against it reports no conflict.", "suggested_pr_body_passage": "## What a cel-js `Environment` holds, and what a cached program holds\n\nRead in `@marcbachmann/cel-js` 8.0.0 (`lib/evaluator.js`, `lib/registry.js`, `lib/parser.js`, `lib/type-checker.js`, `lib/operators.js`, `lib/macros.js`).\n\n**The environment** carries no per-evaluation state:\n- each evaluation builds its own `RootContext`, which holds the variable values and the only value-level cache, `#convertCache`;\n- `Parser.parse` resets its input and its limit counters on every call;\n- the registry's caches (`Candidates` match and check, type declarations, list, map and optional types, `TypeDeclaration#matchesCache`) are keyed on types;\n- the registry locks only when it is cloned, and nothing registers on the shared environment after it is built.\n\n**A parsed program is not free of evaluation state.** cel-js keeps per-run state on the AST:\n- a comprehension (`exists`, `all`, `exists_one`, `map`, `filter`) keeps its iteration context on the AST (`args.iterCtx`, `operators.js:667`), and `cel.bind` keeps its binding context there (`bindCtx`, `macros.js:195`);\n- each run re-parents that overlay to its own scope (`OverlayContext.reuse`, `registry.js:1423`), and every non-iteration variable read inside the step goes through that parent;\n- `callFn` / `callRecFn` write the arguments' runtime types into `ast.argTypes` on every call (`operators.js:302`, `:319`).\n\nIn sequential use each of these is overwritten before it is read, so a cached program answers exactly as a fresh parse does. Two runs of ONE AST at once do not: a run re-entered from inside its own comprehension would read the inner run's scope. So a cached program serves one run at a time (`runningPrograms`), and a nested evaluation of the same source parses a private program. Runs are synchronous: this environment registers no async function, and cel-js refuses a Promise-valued scope value (`Unsupported type: Promise`, measured). A cached program also keeps its last evaluation's scope (the `RootContext` its overlays point at, and their `iterValue` / `accuValue`) reachable until the next evaluation of that source, bounded by the cache's 500 entries.\n\nThe three inputs that did vary per evaluation (now, timezone, binding) are the ones moved into the slot. **Timezone: bound per evaluation, not a cache keyed on it.** With the timezone in the slot, one environment serves every zone, so no per-timezone cache exists to bound.", "pr_body_patch_note": "Replace the PR body's whole section '## What a cel-js `Environment` holds (read before sharing one)', through the paragraph ending 'no per-timezone cache exists to bound.', with suggested_pr_body_passage. In the body's '## What changes' item 3, append one bullet: '**One run at a time:** a cached program is marked while it runs, and a nested evaluation of the same source parses a private program (commit 95f434429).' The dev did not edit the PR body.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write.", "api_writes": "1 relay stroke this round: this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/24536/comments. Plus 1 git push, which is not REST: 1040a3a0c to 95f434429 on claude/issue-24536-cel-env-reuse. Reads were REST GETs only: the PR comments and the commit check-runs.", "line_budget": "none set. This round vs 1040a3a0c: cel-engine.ts +44/-4, cel-program-reentry.test.ts +81/-0, unknown-function.ts +1/-1. Whole PR vs origin/main (three-dot): .changeset/24536-cel-env-reuse.md +12/-0; cel-engine.ts +179/-52; cel-env-reuse.test.ts +123/-0; cel-eval-binding-isolation.test.ts +140/-0; cel-program-reentry.test.ts +81/-0; stdlib.ts +68/-9; unknown-function.ts +1/-1.", "files_changed": [ ".changeset/24536-cel-env-reuse.md", "packages/formula/src/cel-engine.ts", "packages/formula/src/cel-env-reuse.test.ts", "packages/formula/src/cel-eval-binding-isolation.test.ts", "packages/formula/src/cel-program-reentry.test.ts", "packages/formula/src/stdlib.ts", "packages/formula/src/unknown-function.ts" ], "deviations": [ "Guard rule: the program-keyed equivalent, not the slot-keyed one. Equivalence is argued in summary. It also keeps nested evaluations of OTHER sources on the cache.", "The record's ③ Promise reading (a .then continuation reads UNBOUND) does not match a measurement. A Promise-valued scope value is refused by cel-js before any continuation exists. The slot's doc sentence states what was measured instead, so no Promise-release code was built.", "unknown-function.ts is outside the claim's file surface. It carries a one-line prose fix the seat offered in its ③ (non-blocking)." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-19T20:58ZTIER_DEFAULTrecord 6275342648 was FAIL on1040a3a0c: a cached program was not re-entrant. Delta record 6276125243 is PASS on95f434429a. ACCEPT 6276543757. Not governed.gh-readonly-queue/main/pr-24629-*branch remains.origin/main4840720d4eis onorigin/main. It carries PR #24629's 7 files, +604 / −62.completedthroughFixes #24536;pm:dispatchedis cleared.What
mainnow holds:celEngine.evaluateno longer builds a cel-jsEnvironmentand registers the whole stdlib per call.now, the timezone and the permission binding are bound per evaluation through a module slot, restored in afinally. Measured: 141.7 → about 3 µs per evaluate, and 78 → 0 environment builds per 25-row read.compilereuses the shared parse environment (canonicalParseEnv).kindand message; the stdlib drift pin and the unknown-function oracle; the signatures ofregisterStdLib,registerPermissionPredicateandregisterCapabilityPredicate.@objectstack/formulaispatch.Downstream: the
repo:cloud#2seat is pointed at this landing for cloud#3053'sbuildEnvslice.The dev session
session_01QNSdgij7mWezAKUbkzSmmRis archived.Generated by Claude Code