Skip to content

Deprecate now() in favour of execution.timestamp (#104) - #456

Merged
jeremymanning merged 1 commit into
mainfrom
fix/deprecate-now-on-main
Aug 3, 2026
Merged

Deprecate now() in favour of execution.timestamp (#104)#456
jeremymanning merged 1 commit into
mainfrom
fix/deprecate-now-on-main

Conversation

@jeremymanning

Copy link
Copy Markdown
Member

Third attempt to land this. #453 merged into feat/runtime-context and #455 into feat/runtime-context-main — both stacked bases that main had already absorbed, so both reported "merged" while the code never reached main. Verified again just now: deprecated_for appeared 0 times in main and now() was still in six examples. This PR is based on main directly, with nothing stacked under it.

What changed

now() reads the clock afresh at every use; execution.timestamp (now in main via #454) is computed once per run. Six catalogue examples used now() to stamp reports — precisely the case that wants one answer:

"timestamp": "{{ now() }}"          →  "{{ execution.timestamp }}"
*Report generated on: {{ now() }}*  →  *...{{ execution.timestamp }}*

A warning, not an error

now() keeps working. Pipelines outside this repo use it, and refusing them to make a style point would be a gratuitous break. Validation says so and names the replacement:

WARNING - Validation warning in template: 'now()' is deprecated: it is read
afresh at every use, so two steps of one run disagree. Use
'execution.timestamp', which is the same for every step.
✓ dep.yaml is valid

Exit 0, and it still runs. Verified through the CLI, not just the unit.

Chose a validation warning over a Python DeprecationWarning: pipeline authors write YAML, not Python, and DeprecationWarning is ignored in this project's pytest config — it would have been invisible to exactly the people who need it. The action-alias precedent uses the Python warning, so this is a deliberate divergence.

One declaration, three consumers

GlobalSpec.deprecated_for drives the message, the generated docs, and the catalogue guard rather than three restatements. find_global_misuse reports it only for a call that would otherwise work — now(1, 2) gets the arity error alone. One problem at a time.

Measured on main

main this PR
blocking suite 696 passed 700 passed, 0 failed
examples calling now() 6 0
catalogue validating 30 / 117 30 / 117

The catalogue count is flat because the six migrated files fail for unrelated reasons — this moves correctness, not the metric.

Mutations — three, all killed

mutation tests that failed
drop deprecated_for from now 2
make the deprecation an error 4
report it for a call that cannot work anyway 1

One flaw caught in my own guard: with no deprecated globals the regex became an empty alternation matching nearly every call, so the test would have reported the whole catalogue as offending. Guarded.

🤖 Generated with Claude Code

`now()` reads the clock afresh at every use, so two steps of one run get
different answers. `execution.timestamp` is computed once per run. Six
catalogue examples stamped their reports with `now()`, which is exactly the
case where one answer is wanted:

    "timestamp": "{{ now() }}"        ->  "{{ execution.timestamp }}"
    *Report generated on: {{ now() }}*

A warning, not an error. `now()` keeps working -- pipelines outside this
repository use it, and refusing them to make a style point would be a
gratuitous break -- but validation now names the replacement:

    'now()' is deprecated: it is read afresh at every use, so two steps of
    one run disagree. Use 'execution.timestamp', which is the same for
    every step.

The pipeline still validates (exit 0) and still runs.

`GlobalSpec` carries `deprecated_for`, so the message, the generated
documentation and the catalogue guard all come from one declaration rather
than three restatements. `find_global_misuse` reports it only for a call
that would otherwise work: a wrong-arity `now(1, 2)` gets the arity error
alone, one problem at a time.

`TemplateValidationError` already carried a severity; the validator now
routes on it, so a warning goes to `warnings` and lets the run proceed while
an error still refuses it.

Measured: blocking suite 696 -> 700 passed, 0 failed. Catalogue unchanged at
30/117 -- the six migrated files fail for unrelated reasons, so this moves
correctness rather than the count.

Three mutations, all killed:
  - drop `deprecated_for` from `now`             -> 2 tests
  - make the deprecation an error                -> 4 tests
  - report it for a call that cannot work anyway -> 1 test

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeremymanning
jeremymanning merged commit a59924e into main Aug 3, 2026
9 checks passed
@jeremymanning
jeremymanning deleted the fix/deprecate-now-on-main branch August 3, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant