Skip to content

[finding] two §1c-adjacent silent wrong answers that are NOT on the retry path — cross-type in membership, and == against a Date-valued binding #7168

Closed

Description

@os-zhuang

Out-of-scope findings measured while implementing #7098 (PR #7166). Recorded per Prime Directive #10, unassigned.

#7098 was scoped to what the ADR-0032 §1c retry rewrites once armed. Both facts below are the same shape — a silently wrong boolean, { ok: true }, no fault, no log line — but neither is on the retry path at all, so neither could be folded into that fix. Closing either one means changing the clean path, which changes answers for expressions that never faulted; that is a different and larger decision, which is why these are filed rather than fixed.

Fact 1 — a string-serialized numeric field never matches a numeric list

record.n in [1, 7]        with { n: "7" }   ->  { ok: true, value: false }

CEL membership is total across types, so this answers rather than faulting. Because it never faults, the §1c retry never arms, and the value is false both before and after PR #7166 — measured on origin/main and on the fix branch.

This matters because it is drawn from exactly the pool §1c exists for: Field.rating → "5.0", Field.currency → "250000.00". An author writing record.status_code in [1, 2, 3] against a string-serialized numeric field gets a silent false for every row.

Note the interaction with #7098, which is why it surfaced now: before PR #7166 this expression's answer depended on whether an unrelated conjunct faulted — record.n >= 4 && record.n in [1, 7] returned true (the scope-wide hydration retyped n) while record.n in [1, 7] alone returned false. PR #7166 makes the answer consistently false in both positions. That is the correct outcome for that card — the in result was never a fault to rescue — but it leaves this fact standing on its own.

Fact 2 — == against a Date-VALUED binding is not covered by #3183's rewrite

record.d == record.dt     with { d: "2026-06-20", dt: <Date 2026-06-20T00:00:00Z> }
  ->  { ok: true, value: false }

rewriteTemporalEquality (#3183) wraps a field operand in date(...) only when its counterpart is a temporal call — today(), now(), daysFromNow(), daysAgo(). A counterpart that is a Date-valued binding is not wrapped, so the comparison stays string == Timestamp and answers a silent false.

The reachable shape is a mixed-provenance comparison, e.g. record.due == previous.due in a hook or validation scope where previous arrives from the driver as a Date and record from a JSON payload as a "YYYY-MM-DD" string. Both sides are the same logical field and the same calendar day, and the predicate answers false.

Class

Observation-class, each with a demonstrated wrong value. Frequency is unmeasured and none is claimed — the trigger is measured, the incidence is not. No production report is attached to either.

A grading seat may reasonably split these: fact 1 is a data-shape mismatch with a broad blast radius and no obvious fix that does not change clean-path answers; fact 2 is a bounded gap in an existing rewrite whose per-occurrence machinery already exists and could plausibly be extended to a Date-valued counterpart the same way it handles a temporal call.

Related

#7098 / PR #7166 (what the retry rewrites — this is the clean path instead) · #3183 (rewriteTemporalEquality) · #1530 / #1534 (why §1c exists) · #6679 / PR #7097 (what arms the retry) · ADR-0032 §1c.

Activity

  1. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    Triage: finding (domain:engine-core stays — clean-path CEL evaluation in the formula/objectql family).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    Findings-triage re-check (quota round): HOLD maintained (finding stands, no label change).

    Premise re-verified on origin/main @ 2c28df9: rewriteTemporalEquality still wraps only temporal calls (pinned at packages/formula/src/cel-engine.test.ts:528–530), so Fact 2's Date-valued-binding gap stands; and PR #7166's landing means Fact 1's cross-type in now answers a consistent silent false in all positions, exactly as this card records. Both facts remain clean-path semantics — closing either changes answers for expressions that never faulted, which is a larger semantics ruling, not a mechanical fix. Held; when a disposition pass wants to act on this, it should go to the decision box rather than straight to the queue.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. claude commented on Aug 11, 2026

    @claude
    Contributor

    Findings round (quota re-verification): HOLD maintained (finding + domain:engine-core unchanged). Re-checked @ origin/main c546c89: the rewriteTemporalEquality pin block is live at packages/formula/src/cel-engine.test.ts:528-530 (temporal-call rewrite only), so Fact 2's Date-valued-binding gap stands; Fact 1's consistent silent false for cross-type in is unchanged since PR #7166. Both remain clean-path semantics — the exit is a semantics ruling via the decision box, not a mechanical fix. Held.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  4. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    ContributorAuthor

    Findings triage round, 2026-08-11 (maintainer-directed). Graded: hold — both facts change CLEAN-path answers for expressions that never faulted, which is a semantics decision larger than a queue card (the body says so itself). Restart: the next ADR-0032 §1c family card, where these two measured cases enter the ruling as evidence rather than being decided in isolation.


    Generated by Claude Code

  5. added theissue type on Aug 12, 2026
  6. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Findings-round escalation: needs-user-decision (domain:engine-core, type Bug). Three consecutive holds have each concluded "the exit is the decision box" while parking behind an uncheckable restart ("the next §1c family card") — a fourth hold would just repeat that. Escalating with the two facts separated, because they price differently.

    Premise (re-checked)

    git grep -n rewriteTemporalEquality origin/main -- packages/formula/src → the temporal-equality rewrite pin block is live (cel-engine.test.ts:528-551) and wraps only temporal calls (today()/now()/…). Both silent-wrong-boolean facts stand: (1) cross-type in membership answers false instead of erroring/coercing; (2) == against a Date-valued binding (not a temporal call) misses the rewrite and answers false.

    Question

    Clean-path CEL evaluation semantics — these change answers for expressions that never faulted, so neither an implementer nor a PM can rule them.

    Options & recommendation

    • Fact 2 (Date-valued binding ==): fix — extend rewriteTemporalEquality to Date-valued bindings. Bounded, uses existing machinery, closes a same-family gap the platform already decided to close for temporal calls.

    • Fact 1 (cross-type in): record-and-defer — any change (error vs coerce) moves clean-path answers broadly, and there is zero measured pull; defer until a measured victim decides the direction.

    • Platform long-term coherence: Fact 2 extension shrinks an existing special case; Fact 1 would introduce a new cross-type coercion policy — bigger than this card.

    • Measured business pull: zero on both; that is an argument for the asymmetric treatment above, not for silence — these are silent wrong booleans in published expression semantics.

    • AI-agent error-resistance: silent-false is the worst failure class for AI-authored filters; but changing eval semantics without a victim risks flipping pinned behaviour nobody can name.

    • Startup scope discipline: Fact 2 bounded (one rewrite arm + pins); Fact 1 unbounded — defer.

    本评论来自分诊座位(scheduled session session_01DLzE5XVRASxFotEU1h9cjH),不构成认领。


    Generated by Claude Code

  7. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Maintainer ruling recorded — asymmetric treatment adopted. Provenance: maintainer, 2026-08-12 (live, in the PM session that ran this findings round), replying to the round report that tabled this card's four-lens recommendation: 「同意你的决策」.

    Binding ruling:

    • Fact 2 (== against a Date-valued binding): FIX — extend rewriteTemporalEquality to cover Date-valued bindings, the same-family gap the platform already closed for temporal calls.
    • Fact 1 (cross-type in membership): RECORD-AND-DEFER — no change to clean-path cross-type semantics without a measured victim. Reopen trigger: the first measured wrong answer attributable to cross-type in. Not filed as a separate card (zero pull); this comment is the durable record.

    Dispatch constraints (engine-core seat): scope = Fact 2 only; PR uses Fixes #7168 (Fact 1's deferral is recorded here and survives closure). Extend the existing pin block (cel-engine.test.ts:528-551 today, locate by content) with the Date-valued-binding arm — and keep the negative pins: expressions that SHOULD stay false-on-mismatch stay false. needs-user-decision → pm:queue swapped with this record.

    本评论来自分诊座位(scheduled session session_01DLzE5XVRASxFotEU1h9cjH),不构成认领。


    Generated by Claude Code

  8. self-assigned this
    on Aug 12, 2026
  9. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_014C8pAprWdmtecFsEprZax4
    Branch: claude/issue-7168-temporal-eq-date-binding
    Worktree: objectstack-issue-7168
    Domain: domain:engine-core
    File surface: packages/formula/src (rewriteTemporalEquality) + its pin block in cel-engine.test.ts (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus
    Serial constraints cleared: packages/formula — no card has touched it this shift; in-flight #8093 (producer unlocated, expected objectql/metadata-protocol) and #8118 (objectql + plugin-webhooks) are both in different packages. ⛔ Nothing in this lane holds formula.

    Model rationale — stated because I am NOT escalating, and that needs a reason

    opus, ⛔ not claude-fable-5. The mandatory clause covers a change that alters contract accept/reject behaviour or widens a public face; Fact 2 does neither. It changes the answer a comparison returns, in packages/formula — not the set of expressions the platform accepts, and not packages/spec. The clause's stated rationale is that an error on the single contract surface poisons every downstream consumer; that is not this seam.

    ⭐ The stronger argument is the maintainer's own: this card was ruled by splitting it. Fact 1 — the genuine clean-path semantics decision — was deferred by name, and Fact 2 was called a FIX on the grounds that it closes "the same-family gap the platform already decided to close for temporal calls". The decision half was removed from this card by the ruling itself; what remains is the bounded half. ⛔ Escalating the tier reflexively here would be treating "it touches evaluation" as if it were the criterion, when the criterion is the acceptance surface.

    Seat #6019. The 11:15Z maintainer ruling is carried into the dispatch verbatim and is not reopenable — including its ⛔ scope fence (Fact 2 only) and its instruction that Fact 1's deferral is recorded on this card and survives closure.


    Generated by Claude Code

  10. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    ContributorAuthor
    {
      "issue": 7168,
      "status": "done",
      "branch": "claude/issue-7168-temporal-eq-date-binding",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8160",
      "premise_still_valid": true,
      "summary": "Fact 2 only, per the 11:15Z ruling. Extended the temporal-equality rewrite so a field operand is coerced with date(...) when its counterpart is a Date-valued BINDING, not just a temporal call — record.due == previous.due with a hydrated Date and a JSON date string now answers true instead of a silent false (and != stops answering a silent true). PM mechanism assumption FALSIFIED, and it changed the implementation: a binding's runtime type is NOT visible in the AST at all, so the temporal-call arm's shape does not carry. That arm is a pure function of the source and is memoized under it; this verdict is a property of the ROW. The arm therefore reads the evaluation scope — reusing rewriteFaultedOperands' (#7098) scopePath/resolveScopePath/coercionFor primitives — runs only in evaluate() where a scope exists, and is never cached against the source (caching it would apply row 1's verdict to row 2; that trap is pinned). What IS cached per source is the analysis of which occurrences could depend on a scope at all, so the hot path is unchanged for sources without one. The fence is held by requiring the counterpart to be a real Date AND this operand to be an ISO-8601 string that parses: date() is toDate, and new Date(\"5\") and new Date(\"05\") both parse to 2001-05-01, so an unconditional wrap would invent equalities and convert silent-wrong-false into silent-wrong-true. Fact 1 (cross-type in membership) is untouched and NOT filed as a card, per the ruling; the PR body states in its own section that the Fixes #7168 closure does not retract Fact 1's deferral.",
      "tests": "REVERSE VERIFICATION (direction predicted before running: new arm red, negative pins green on BOTH sides — observed exactly that). Source reverted to origin/main via patch file, new tests kept: 'Tests  5 failed | 80 passed (85)'. The 5 failures are all and only the new arm — 'wraps the ISO-string operand when its counterpart is a Date-valued binding', 'composes with the temporal-call arm on one expression', 'a same-day mixed-provenance comparison answers true instead of a silent false', 'a datetime string matches the same INSTANT', 'the verdict is per ROW, not cached against the source' — with the card's literal defect as 'AssertionError: expected { ok: true, value: false } to deeply equal { ok: true, value: true }'. Every negative pin passed on origin/main AND with the fix. WITH FIX: pnpm --filter @objectstack/formula test -> 'Test Files  25 passed (25) / Tests  643 passed (643)'; typecheck (tsc --noEmit) clean; eslint on both changed files exit 0. DOWNSTREAM (prefix filter = consumers): @objectstack/objectql 'Test Files  191 passed / Tests  3390 passed', @objectstack/service-automation 'Test Files  79 passed / Tests  940 passed'. GATES: check:driver-conformance 'OK — 40 covered cell(s), 0 in the DEBT ledger'; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s), none new; baseline key set verified against 37b82ed: no files added'; check:nul-bytes 'OK (scanned 7414 text file(s))' plus a manual control-byte grep over all three changed files (no hits); check:type-check-debt --re-measure 'OK — 33 ledger entr(ies) re-measured, 1966 raw tsc error(s) total, none above its recorded number' after a full closure build (pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*', 70/70 successful — its first run exit-1'd as the missing-module cascade the dispatch warned about). No baseline raised; --lower not run despite the gate suggesting it for 9 pre-existing surplus entries, none of them in packages/formula. Gates re-derived for the final surface with scripts/pm/dispatch-gates.mjs: same single path-matched family.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Notes for the PM, beyond the JSON:

    • The dispatch's flagged design question is answered by measurement, not by analogy. The body's expectation that Fact 2's machinery "could plausibly be extended the same way it handles a temporal call" is half right: the date(...) coercion is indeed the right one (measured — the manual wrap turns the failing comparison true), but the dispatch mechanism could not be reused. A temporal call is recognisable in the AST; a Date-valued binding is not, and is indistinguishable from a string binding until a row is in hand. The scope-aware path already existed for the §1c retry ([finding] the ADR-0032 §1c hydration retry re-interprets sub-comparisons that already evaluated cleanly — hydrateOverloadStrings's docblock claims it cannot #7098) and is what this reuses.
    • One trap worth naming: the existing rewrite is memoized per source string, which is sound only because it is a pure function of the source. Extending it in place without separating the scope-dependent arm would have made the same expression answer with a previous row's verdict. There is a dedicated pin for that (the verdict is per ROW, not cached against the source), and it is one of the 5 that go red on origin/main.
    • Deliberately not "fixed": a date-ONLY string against a Date carrying wall-clock time stays false. date() parses rather than truncating to a calendar day, and those are genuinely different instants — day-truncating both sides would flip correct falses to wrong trues for real datetime comparisons. Pinned as such.
    • Nothing filed. No out-of-scope findings surfaced, and Fact 1 was deliberately not filed as a card, per the ruling's explicit instruction.
    • CI was not waited on: reporting at draft-PR time per the 2026-08-10 maintainer decision. Gate conclusions are the PM's read.

    Generated by Claude Code

  11. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    ContributorAuthor

    Supersedes the earlier report — the PM added the docs paragraph to scope after it was posted, and main advanced under the branch. Same status, larger file surface.

    {
      "issue": 7168,
      "status": "done",
      "branch": "claude/issue-7168-temporal-eq-date-binding",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8160",
      "premise_still_valid": true,
      "summary": "Fact 2 only, per the 11:15Z ruling. Extended the temporal-equality rewrite so a field operand is coerced with date(...) when its counterpart is a Date-valued BINDING, not just a temporal call — record.due == previous.due with a hydrated Date and a JSON date string now answers true instead of a silent false (and != stops answering a silent true). PM mechanism assumption FALSIFIED, and it changed the implementation: a binding's runtime type is NOT visible in the AST at all, so the temporal-call arm's shape does not carry. That arm is a pure function of the source and is memoized under it; this verdict is a property of the ROW. The arm therefore reads the evaluation scope — reusing rewriteFaultedOperands' (#7098) scopePath/resolveScopePath/coercionFor primitives — runs only in evaluate() where a scope exists, and is never cached against the source (caching it would apply row 1's verdict to row 2; that trap is pinned). What IS cached per source is the analysis of which occurrences could depend on a scope at all, so the hot path is unchanged for sources without one. The fence is held by requiring the counterpart to be a real Date AND this operand to be an ISO-8601 string that parses: date() is toDate, and new Date(\"5\") and new Date(\"05\") both parse to 2001-05-01, so an unconditional wrap would invent equalities and convert silent-wrong-false into silent-wrong-true. Per the PM's addition, the formulas.mdx callout describing this exact rewrite was extended to name the binding counterpart plus the fence — verified implicated by reading it, not taken on trust. Fact 1 (cross-type in membership) is untouched, NOT filed as a card, and NOT mentioned in the docs edit, per the ruling; the PR body states in its own section that the Fixes #7168 closure does not retract Fact 1's deferral.",
      "file_surface": [
        "packages/formula/src/cel-engine.ts",
        "packages/formula/src/cel-engine.test.ts",
        ".changeset/temporal-equality-date-valued-binding.md",
        "content/docs/data-modeling/formulas.mdx  (domain:devx surface — PM is filing the declaration)"
      ],
      "tests": "REVERSE VERIFICATION (direction predicted before running: new arm red, negative pins green on BOTH sides — observed exactly that). Source reverted to origin/main via patch file, new tests kept: 'Tests  5 failed | 80 passed (85)'. The 5 failures are all and only the new arm — 'wraps the ISO-string operand when its counterpart is a Date-valued binding', 'composes with the temporal-call arm on one expression', 'a same-day mixed-provenance comparison answers true instead of a silent false', 'a datetime string matches the same INSTANT', 'the verdict is per ROW, not cached against the source' — with the card's literal defect as 'AssertionError: expected { ok: true, value: false } to deeply equal { ok: true, value: true }'. Every negative pin passed on origin/main AND with the fix. WITH FIX, re-run AFTER the merge of origin/main: pnpm --filter @objectstack/formula test -> 'Test Files  25 passed (25) / Tests  643 passed (643)'; typecheck (tsc --noEmit) clean; eslint on both changed source files exit 0. DOWNSTREAM (prefix filter = consumers): @objectstack/objectql 'Test Files  191 passed / Tests  3390 passed', @objectstack/service-automation 'Test Files  79 passed / Tests  940 passed'. GATES, all re-derived for the final 4-file surface via scripts/pm/dispatch-gates.mjs (the .mdx pulled in a docs family the first derivation did not have): check:driver-conformance 'OK — 40 covered cell(s)'; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s), none new; baseline key set verified against a0fdc56: no files added'; check:nul-bytes 'OK (scanned 7426 text file(s))' plus a manual control-byte grep over every changed file (no hits); check:type-check-debt --re-measure 'OK — 33 ledger entr(ies) re-measured in 256.5s, none above its recorded number' after a full closure build (70/70 successful); check:doc-formula-expressions 'OK — 22 record-scoped formula example(s) across 394 files / 1410 TS blocks judged clean'; check:docs-audit-scope OK incl. 'release-owned pages are in scope and read-only'; check:quick-reference-counts OK; check:role-word OK; check:objectui-changeset OK; check-changeset-no-major 'introduces no major bump'. No baseline raised; --lower not run despite the gate suggesting it for 9 pre-existing surplus entries, none in packages/formula.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Notes for the PM, beyond the JSON:

    • The docs paragraph was genuinely implicated — confirmed by reading it, not assumed. Its mechanism sentence is "the engine now rewrites temporal == / != comparisons (coercing the field operand with date(...))", and the only counterpart it names is today(). That is precisely the function this PR extends. Two of its other claims were re-checked against my diff and remain true, so they were left alone: ordering is untouched, and string-literal equality is untouched — both are pinned negatives here.
    • One thing you should know I did NOT do: check:objectui-pin-fresh came back red. It is not mine — main advanced 7 commits mid-task and one of them bumped .objectui-sha (6314e87 to 6d77acf), which my branch predates. I did not hand-edit that pin (another lane's artifact, Release process: prevent frontend (objectui) changesets being missed when the console pin lags #3340); I merged origin/main instead, which took their landed change legitimately. Gate green afterwards. Because a merge invalidates earlier measurements, the formula suite, typecheck, ESLint and both named ratchets were all re-run against the merged tree — including a second full closure build before re-measuring the debt ledger.
    • The dispatch's flagged design question is answered by measurement, not by analogy. The date(...) coercion is the right one (measured — the manual wrap turns the failing comparison true), but the dispatch mechanism could not be reused: a temporal call is recognisable in the AST, a Date-valued binding is not, and is indistinguishable from a string binding until a row is in hand.
    • Deliberately not "fixed": a date-ONLY string against a Date carrying wall-clock time stays false. date() parses rather than truncating to a calendar day, and those are genuinely different instants — day-truncating both sides would flip correct falses to wrong trues for real datetime comparisons. Pinned as such, and stated in the changeset.
    • Nothing filed. No out-of-scope findings surfaced, and Fact 1 was deliberately not filed, per the ruling's explicit instruction.
    • CI not waited on, per the 2026-08-10 maintainer decision; gate conclusions are yours to read.

    Generated by Claude Code

  12. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    ContributorAuthor

    ACCEPTED on substance — PR #8160. ⛔ Ready-flip held: CI restarted after the merge and has not converged.

    Engine-core seat #6019, session_014C8pAprWdmtecFsEprZax4.

    ⭐ My mechanism assumption was falsified, and the falsification changed the implementation

    I wrote: "I have not verified how a Date-valued binding is distinguishable from a string binding at rewrite time… if the AST cannot see it, say so and describe what you did instead."

    The AST cannot see it at all. A binding's runtime type is not in the source, so the temporal-call arm's shape does not carry: that arm is a pure function of the source and is memoized under it, while this verdict is a property of the ROW. So the new arm reads the evaluation scope (reusing #7098's scopePath / resolveScopePath / coercionFor primitives), runs only inside evaluate() where a scope exists, and is never cached against the source.

    ⭐ And the trap that follows is pinned rather than merely avoided: caching it would apply row 1's verdict to row 2. There is a test named for exactly that. What is cached per source is the cheaper analysis of which occurrences could depend on a scope at all, so the hot path is unchanged for sources without one.

    ⛔ This is the opposite of forcing the analogy the card's filer expected ("the machinery already exists and could plausibly be extended the same way"). The dispatch flagged that expectation as the filer's, not a measurement — and it turned out not to hold.

    ⭐ The fence is the part I would have accepted a weaker version of

    I asked that expressions which should stay false stay false, and warned the over-broad failure converts silent-wrong-false into silent-wrong-true. The dev did not just assert it — it found the concrete mechanism:

    date() is toDate, and new Date("5") and new Date("05") both parse to 2001-05-01, so an unconditional wrap would invent equalities.

    So the arm requires the counterpart to be a real Date and this operand to be an ISO-8601 string that parses. ⚠️ Without that second condition this card would have shipped the same defect pointing the other way — and "5" is not a contrived input for a text field being compared to a date.

    The reverse verification is the shape I ask for

    Direction predicted before running; observed exactly. Source reverted to origin/main, new tests kept: 5 failed / 80 passed — the five failures are all and only the new arm, and the card's literal defect appears as expected { ok: true, value: false } to deeply equal { ok: true, value: true }. Every negative pin passed on origin/main AND with the fix — which is what makes them regression pins rather than restatements.

    Downstream consumers re-run after the merge: objectql 3390 passed, service-automation 940 passed.

    Verified independently

    check reading
    scope fence Fact 1 untouched, not filed as a card, and not mentioned in the docs edit — exactly as the ruling directed. The PR body states in its own section that the Fixes #7168 closure ⛔ does not retract Fact 1's deferral, which is the thing a future reader would otherwise get wrong
    the docs half the formulas.mdx callout was extended to name the binding counterpart plus the fence, after reading the prose to confirm it was implicated — ⛔ not taken on my word. That is the right order: I flagged it, the dev verified it
    path face packages/formula/src/cel-engine.ts + its test + changeset + content/docs/data-modeling/formulas.mdx — ⛔ no docs/adr/**, no skills root. Clean. The .mdx is domain:devx's and the declaration is filed on #6023
    gates re-derived for the final 4-file surface — the .mdx pulled in a docs family the first derivation did not have (check:doc-formula-expressions, check:docs-audit-scope, check:quick-reference-counts, check:role-word). ⭐ Re-deriving after the surface changed is exactly the discipline; the first list would have missed four gates
    ratchets both green on a full built closure; ⛔ no baseline raised, --lower not run despite the gate suggesting it for nine pre-existing entries — none in packages/formula

    ⚠️ Third sighting of check:objectui-pin-fresh, and this one resolved itself: main advanced 7 commits mid-task and one of them bumped .objectui-sha (6314e87 → 6d77acf). The dev merged rather than hand-editing another lane's pin artifact — ⛔ the correct call — and re-ran every measurement against the merged tree. So the bump I raised with the queue steward has since landed from elsewhere; ⛔ I am not claiming credit for a fix I did not cause, only recording that the condition changed.

    Why it is not flipped

    CI restarted at 19:05Z on the merge commit: 11 concluded, 0 failure; TypeScript Type Check and ESLint in_progress, filter still queued — so the Test Core and Dogfood shards have not even been scheduled yet. ⛔ Not flipping on a partially-scheduled run. Next patrol reads each job's own conclusion; all-green ⇒ ready + enqueue SQUASH.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions