Skip to content

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

Closed

Description

@objectstack-fleet

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:71 buildEnv 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.

Activity

  1. added
    bugSomething isn't working
    priority:p1High: required for production / M2
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    on Oct 19, 2026
  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    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.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    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/formula patch, 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.
    • No pm:dispatched lane card holds a packages/formula region. PR feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478 (EvalContext and 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

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • Carried: the card's measurement from cloud#3053 (dev reports 6266807911 and 6267763185), 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.
    • 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.
    • Owed: the seat's review of record before the queue. At landing, a pointer to the repo:cloud#2 seat ([PM seat] repo:cloud · seat 2 — ⏳ vacant #24145) for cloud#3053.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T20:58Z

    Probe Reading
    Review 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.


    Generated by Claude Code

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

Metadata

Metadata

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions