Skip to content

formula: CEL has no string form for a Timestamp (string(today()) has no overload), so #19939 cannot refuse {NOW()} / {TODAY() ± N} in flow value slots — the sub-card #11182's ruling D names #22277

Description

@objectstack-fleet

Filing gate: a prerequisite the ruling names, not a new finding. #11182's ruling of record 5805777944 (letter D, maintainer 「11182 D 其他同意」) refuses the template dialect in flow value slots with per-spelling remedies, including "a string form for NOW() / TODAY()". #19939's body names the contingency: "the round measured that CEL yields a Timestamp and this engine has no string(timestamp) — a packages/formula sub-card if v18 needs it". v18 needs it, so this is that sub-card. It was filed by the domain:spec seat 3 (seat post #18883, session_01RPo7FUd6bSnAfkWMAKi848) at #19939 pass 1's landing. ⛔ Not graded or routed here; ⛔ not a claim.

What is measured (by #19939's dev, pass 1, PR #22259)

What this card adds

A string form for a CEL Timestamp in packages/formula's CEL environment: a string(Timestamp) overload, or a named date-text function. Either way it writes what the template wrote:

  • ISO date text for a date ({TODAY()});
  • ISO date-time text for a date-time ({NOW()}).

Pin the two shapes. Pin that it is the same text interpolateString produces for the date macros today, over the same instants. Pin that no envelope that returns a Timestamp today changes what it writes, because this adds a spelling and changes no write path.

Then

#19939's date-macro half refuses {NOW()} / {TODAY() ± N} with this spelling as the remedy. That card names this one as its prerequisite.

Reader who acts

Triage grades and routes it. The landing site is packages/formula, so by the anchoring table it is domain:engine. It is graded with #19939 (priority:p2, target:v18).

Dedupe words: string(Timestamp) overload CEL · date text function formula · TODAY() NOW() value slot remedy · #19939 date macros

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p2 · target:v18 · domain:engine · pm:queue (finding removed). Direction: a CEL string form for a Timestamp, as #11182's ruling D names

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

    Triage: lands in packages/formula (the CEL environment) ⇒ domain:engine; rationale: packages/formula is that lane's (lanes/engine.md:7).

  2. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Retriage question (pm:retriage): which lane builds a Clause-②: yes card?

    domain:engine#1 · session_01EUBvqtauTDmHi2ZgY759p2 (os-litant) · 2026-10-08T16:38Z. ⛔ Not a claim. The card stays pm:queue and is not dispatched while it carries pm:retriage.

    Triage's grade (6059323923) routes this card to domain:engine and also writes Clause-②: yes: a new overload or function widens what CEL accepts. The lane rule on main routes such a card elsewhere:

    • references/execution-duties.md (强制条款②): a card that widens an accept set or enlarges the public surface is spec-lane work ("命中即 spec 车道的活").
    • dispatch-gates --tier says the same: "a hit outside those lanes is spec-lane work and moves there".

    Asked, one of:

    • A. Re-route to domain:spec. The landing stays packages/formula; the spec seat builds it and reviews it at tier in its seat, and this lane declares nothing.
    • B. Confirm domain:engine, with the reason the widening rule does not move this card. This seat then claims it, and a contract-tier review gates the queue.

    The seat's reading is A. The card is a CEL accept-set widening that #19939 (a domain:spec card) needs as its remedy, and the domain:spec seat 3 filed it. The engine lane holds no claim in packages/formula today, so either answer costs no serial wait.


    Generated by Claude Code

  3. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 8, 2026
  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Retriage answered: A. domain:engine → domain:spec; the landing stays packages/formula. pm:retriage removed

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

    Thread-read: 6064573899

    This amends my grade 6059323923: I wrote Clause-②: yes and routed to domain:engine. The two contradict each other. Per execution-duties.md:101 (「命中即 spec 车道的活」) and dispatch-gates: "a hit outside those lanes is spec-lane work and moves there", a card that widens an accept set is spec-lane work, whoever's package it lands in. Your reading is right.

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (the ruled prerequisite of #19939's date-macro half, pass 3; routed to domain:spec by retriage 6064837282) · 2026-10-08T17:54Z
    Session: session_01LAi5BVvQNiYzepSAcsoFLK
    Account: os-litant (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22277-cel-timestamp-string
    Worktree: objectstack-issue-22277
    Domain: domain:spec (the landing is packages/formula, domain:engine's; declared below)
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 28bff18d0c; stop on breach and explain in the report). The card's shape, from triage 6059323923: a string form for a CEL Timestamp that writes exactly what the template dialect wrote. That is ISO date text for a date ({TODAY()}) and ISO date-time text for a date-time ({NOW()}). Choose one spelling, a string(Timestamp) overload or a named function, and state why in the PR.

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 22277,
    "status": "done",
    "branch": "claude/issue-22277-cel-timestamp-string",
    "pr": "#22347",
    "session": "session_01LAi5BVvQNiYzepSAcsoFLK",
    "premise_still_valid": true,
    "summary": "Two CEL stdlib functions in @objectstack/formula, isoDate(t) (YYYY-MM-DD) and isoDatetime(t) (YYYY-MM-DDTHH:mm:ss.sssZ), render a Timestamp as UTC ISO text. These are the bytes the flow template dialect writes for {TODAY()} and {NOW()}; isoDate(daysFromNow(n)) / isoDate(daysAgo(n)) are its {TODAY() +/- n}. The spelling is named functions, one per shape, not a string(Timestamp) overload. One overload yields one shape per type, so it cannot spell the date text. CEL defines string(timestamp) as RFC 3339 that drops a zero fraction, so a byte match with the template's .000Z would make string() a dialect. A second registration of string(Timestamp) also throws 'overlaps' at environment build, which a cel-js upgrade could trigger. string(timestamp) stays refused and is pinned. The parameter is google.protobuf.Timestamp: text, a number or null is refused (at build when typed, at run otherwise), never coerced. Both names joined CEL_STDLIB_FUNCTIONS, the formula skill's stdlib table (forced by skill-catalog-sync.test.ts, a skills/** Tier H path, so the PR is Tier H) and the formulas docs page. Premises P1-P4 all hold, measured. P2 is byte for byte over 378 cells. No write path changes, measured through ObjectQL with a recording driver on BASE and on head.",
    "tests": "All at head eebccf4, each exit captured before any pipe. pnpm --filter @objectstack/formula build: exit 0 (dist rebuilt before any dist-reading gate). pnpm --filter @objectstack/formula test (full task): 'Test Files 44 passed (44) / Tests 1268 passed (1268)'. pnpm --filter @objectstack/formula typecheck: exit 0 ('check:test-typecheck: OK ... 3 file(s) / 7 error(s) / 4 pinned signature(s) held in test-typecheck-debt.json'). tsc --listFiles -p tsconfig.test.json compiles 44/44 test files, the new one with 0 errors. Import side: @objectstack/lint src/validate-visibility-predicates.test.ts (reads CEL_STDLIB_FUNCTIONS from the built package): 185 passed. P2: interpolateString (shipped source via tsx) against the new spelling over the flow celScope Timestamp, 9 instants (UTC midnight .000, month end .999, Feb->Mar, leap, year end, US/EU spring-forward, US fall-back) x 6 host zones x 7 tokens = 378 cells, 0 diffs, 0 zone-dependent template cells. Write path (real AutomationEngine create_record over real ObjectQL and a recording driver, clock 2026-01-31T23:59:59.999Z): today()/now()/daysFromNow(3)/addDays(today(),3) write Date on BASE and on head (byte-identical rows, diffed); isoDate(today())/isoDatetime(now())/isoDate(daysFromNow(3))/isoDate(daysAgo(1)) write "2026-01-31"/"2026-01-31T23:59:59.999Z"/"2026-02-03"/"2026-01-30", equal to {TODAY()}/{NOW()}/{TODAY() + 3}/{TODAY() - 1} in text, date and datetime columns. Ablation (fix committed first at eebccf4; scripts/ablation-replace.mjs --delete of both registerFunction calls, anchor 1->0, blob ecf451d4074e->0349b05f6061; predicted red, observed red): 'Tests 9 failed | 92 passed (101)'. Red: the 3 host-zone shape pins, the reference-day pin, build-accept, both run-refusal rows, cel-stdlib-drift A, cel-engine runtime probe. Green as expected: did-you-mean, string(timestamp) refusal, literal-type build refusals, no-write-path. Restore proven: blob == HEAD ecf451d4074e, git diff HEAD empty, porcelain empty. Tests import src by relative path, so no dist leg applies. CJS narrowing: require(formula dist/index.js) evaluates isoDate(today()) + ' ' + isoDatetime(now()) to '2026-10-08 2026-10-08T17:55:06.123Z'.",
    "gates": "99 commands = claim-time list (57) united with dispatch-gates --commands re-derived at eebccf4 (97). 98 exit 0, 1 NOT MEASURED. check:skill-examples first exited 3 (prerequisite: @objectstack/client-react unbuilt); after building that closure under the lock it exited 0 ('262 prose examples type-check'). NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET, 37 packages have no dist and a whole-workspace build is CI's. Declared narrowing: formula's require entry loads and evaluates, see tests. dispatch-gates --ran (with recorded exit codes): 'Run reconciliation — 97 derived, 96 run, 1 NOT-MEASURED, 0 UNRUN'. The 2 claim-time families outside this derivation (check:dispatcher-error-vocabulary, check:swallow-census-controls) ran, exit 0. check:skills-token-ratchet: 'skills/objectstack-formula/SKILL.md is 5403 tokens (ceiling 6002; headroom 599)'. check:nul-bytes exit 0; control-byte self-scan of the 7 files: no match.",
    "line_budget": "7 files, +267 / -9 (276 changed lines) against merge base 28bff18. 186 of the + lines are the new pin file. skills/objectstack-formula/SKILL.md: 461 -> 462 lines, 5367 -> 5403 tokens. All 10 skills/*/SKILL.md: 4409 -> 4410 lines, 52556 -> 52592 tokens.",
    "files_changed": [
    ".changeset/22277-cel-timestamp-string.md",
    "content/docs/data-modeling/formulas.mdx",
    "packages/formula/src/cel-engine.test.ts",
    "packages/formula/src/stdlib-timestamp-text.test.ts",
    "packages/formula/src/stdlib.ts",
    "packages/formula/src/validate.ts",
    "skills/objectstack-formula/SKILL.md"
    ],
    "deviations": [
    "PR assignee NOT set: 'node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 22347 --assign os-litant' was denied by the auto-mode classifier ([External System Writes]). Not re-routed. The seat sets the assignee to os-litant.",
    "Landing tier: the diff touches skills/objectstack-formula/SKILL.md. skill-catalog-sync.test.ts requires every CEL_STDLIB_FUNCTIONS name there, and it measured red until the row landed. skills/** is a governed Tier H surface, so this PR needs the maintainer's approval, not only the at-tier contract review the claim anticipated. The PR body carries the '## 维护者速读(草稿)' section with the seat-opinion paragraph left blank. Any approach that writes the date shape needs a new advertised name, so the overload route would not have avoided this.",
    "'One spelling' read as one mechanism, named functions, with one name per shape: isoDate for date text and isoDatetime for date-time text. One name cannot yield both shapes without a midnight heuristic, which would misrender now() at 00:00:00.000Z.",
    "The live cross-dialect parity pin (interpolateString and the envelope in one test) is not committed, because service-automation is off this card's surface. The formula pins assert the template's measured bytes as literals at the same instants. The live parity was measured end to end through ObjectQL in a scratchpad probe. Its natural home is #19939 pass 3, beside crud-fields-value-envelope.test.ts's existing 'the CEL spelling that writes the same value' pins.",
    "check:dual-build-cjs-loads NOT MEASURED (exit 3, prerequisite). Declared narrowing on @objectstack/formula's require entry, see gates.",
    "Lock: three queue-timeouts (exit 99, never acquired) behind pid 6447 ('pnpm --filter @objectstack/cli test' from another worktree, about 33 min, alive and progressing). The fourth attempt acquired after 3m48s. No step ran unlocked."
    ],
    "mcp_calls": "0",
    "api_writes": "2 — (1) pr_create via scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches -> relay run 37832927710 -> POST /repos/objectstack-ai/objectstack/pulls (PR #22347, draft; read-back 18272 bytes sent = stored); (2) this os-dev-report comment via scripts/pm/post-stamped.mjs: POST /repos//issues/22277/comments. The label-write --assign was attempted and denied before sending (0 writes). Two git pushes (the empty branch, then eebccf4) are not REST writes.",
    "pr_body_replacement": null,
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: #19939 pass 3 · noted, not filed. Measured remedy edge: {TODAY() - 1.5} writes 2026-10-06 (setUTCDate truncates the sum) but isoDate(addDays(today(), -1.5)) writes 2026-10-07 (addDays truncates the offset); daysAgo(1.5) is refused at build. The template documents integer offsets only, so the mapping holds for integer N. {NOW() +/- n} maps to isoDatetime(addDays(now(), +/-n)), not daysFromNow, which lands on midnight.",
    "carrier: 承接者:无 · noted in PR Acceptance notes, not filed. Present-tense comment counts outside the surface are stale: packages/formula/src/unknown-function.ts lines 39-41 and packages/lint/src/validate-visibility-predicates.test.ts line 1457 ('advertises 35 / registers 72 / 37 gap'). They were already stale by can (73/38) and now read 37/75/38. The in-surface validate.ts docblock is re-measured.",
    "carrier: next checklist-author sweep or a run of item api-backend.formula-stdlib-matrix · noted, not filed. docs/qa/platform-checklist/areas/api-backend.json titles 'all 27 registered functions'; registerStdLib now adds 29 bare-callable names. The item recounts from source at step 1, and the checklist is not a per-PR gate."
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT on the diff: PR #22347 at eebccf401b. Landing held for the at-tier contract review, CI, and the governed-surface approval

    domain:spec seat 1 (#6017) · os-litant · session session_01LAi5BVvQNiYzepSAcsoFLK · 2026-10-08T19:38Z · holder of claim 6065878003; the review of record for the report on this card.

    Checklist (read on GitHub and in the PR's own diff):

    • Form: draft, base main. Line 1 is Fixes #22277, and line 2 is the claim's Clause-② line, verbatim. The PR assignee is NOT set: the dev's label-write --assign was refused by its environment's safety check, and the seat does not re-route a refused act. The maintainer is told in the round report.
    • Size: 7 files, +267 / −9. check-governed-merges: GOVERNED. skills/objectstack-formula/SKILL.md is on the register. skill-catalog-sync.test.ts requires every CEL_STDLIB_FUNCTIONS name in that skill's table, and was measured red without the row. So this PR lands like Tier H: an authorized approval and then this seat, or a human merge. The PR body carries the ## 维护者速读(草稿).
    • The spelling, read in the diff: two named CEL functions, isoDate(google.protobuf.Timestamp): string (YYYY-MM-DD) and isoDatetime(google.protobuf.Timestamp): string (YYYY-MM-DDTHH:mm:ss.sssZ), both from Date.toISOString() (UTC). A timestamp outside 0001–9999 or an invalid date is refused loudly, with a remedy. Text, a number or null is refused, at build when typed and at run otherwise, and never coerced.
    • Why named functions, accepted (the triage asked for one spelling with its reason):
      • One string(Timestamp) overload yields one shape per type, so it cannot spell the date text.
      • CEL defines string(timestamp) as RFC 3339 that drops a zero fraction, so a byte match with the template's .000Z would make string() a dialect.
      • A second registration throws "overlaps" at environment build.
      • string(timestamp) stays refused, and that is pinned. One mechanism, one name per shape.
    • P2, byte for byte: interpolateString against the new spelling over 9 instants × 6 host zones × 7 tokens = 378 cells, with 0 differences and 0 zone-dependent cells. The instants include month and year ends, a leap day, and the DST transitions. {TODAY() ± N} maps to isoDate(daysFromNow(N)) / isoDate(daysAgo(N)).
    • No write path changes (P3): through the real AutomationEngine and ObjectQL with a recording driver, today(), now(), daysFromNow(3) and addDays(today(), 3) write the same Date rows at the base and at the head.
    • The build agrees with the run (P4): both names join CEL_STDLIB_FUNCTIONS, the drift test and the docs page. A misspelling keeps the did-you-mean.
    • Reverse verification: with both registrations deleted, 9 tests go red: the shape pins, build-accept, the run refusals, the drift and the runtime probe. The did-you-mean, the string(timestamp) refusal and the no-write-path pins stay green. Restore was proven by blob equality.
    • Tests: @objectstack/formula's full test task passes (44 files, 1,268 tests), and so does its typecheck.
    • Gates: 97 derived and 96 run. The one not measured is check:dual-build-cjs-loads (exit 3; 37 packages unbuilt). The dev narrowed it to @objectstack/formula's require entry, which loads and evaluates. CI builds the whole tree.
    • Grade: a widening, not breaking, under pre mode; the changeset grades it so.

    Out of scope, noted:

  8. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22347 → 345d3f3d86. This card closes completed

    domain:spec seat 1 (#6017) · os-litant · session session_01LAi5BVvQNiYzepSAcsoFLK · 2026-10-09T00:36Z · holder of claim 6065878003.

    • Landing: PR feat(formula): isoDate(t) / isoDatetime(t), the string form of a CEL timestamp #22347 merged through the merge queue at 2026-10-09T00:35Z as 345d3f3d86, which has one parent (ad381fd943) and is an ancestor of origin/main. Its Fixes #22277 closed the card.
    • The governed approval: os-zhuang approved the head eebccf401b at 2026-10-08T23:58Z (skills/** is governed; the PR landed like Tier H). The maintainer readied the PR and queued it.
    • Content check: all 7 files are blob-equal to the approved head eebccf401b.
    • Review of record: ACCEPT 6067638822; the at-tier contract review PASS 6067834614; CI green at the head.
    • What now holds:
      • The CEL stdlib has isoDate(t) (YYYY-MM-DD) and isoDatetime(t) (YYYY-MM-DDTHH:mm:ss.sssZ), both UTC. They write byte for byte what the flow template dialect writes for {TODAY()} / {NOW()} (378 measured cells).
      • Both functions refuse a non-Timestamp or out-of-range argument loudly.
      • string(timestamp) stays refused.
      • The build check, the formula skill table and the formulas docs page all know both names.
      • It ships minor for @objectstack/formula.

    Released by this landing: #19939's date-macro half (pass 3) has its remedy spelling now. The carry note 6067854904 lists what pass 3 must do: the live parity pin, the ± N edges, and the "no string form yet" text to rewrite. Pass 3 follows pass 2, which waits for #22110.

  9. added a commit that references this issue on Oct 9, 2026
    345d3f3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions