chore(op,lexicons): the search-attribute and workflow vocabulary, safeHeartbeat and its forty callers - #2203
Merged
Merged
Conversation
…ism, not the engine that is gone Sub-issue 4 of #2191, the vocabulary half. Every line here named a mechanism chant no longer has, so each one is rewritten against what the local executor actually does. "Search attribute" becomes the run outcome or the outcome attribute: an `outcomeAttribute` on a step is captured onto that step's `StepRecord` and folded onto the run ledger record, which is what a reader filters runs by. "Workflow", where it meant the Op or the run, becomes the Op or the run. `OpConfig.name` names the Op's output directory; the workflow function name it also claimed to name was deleted in 257ca0c. "The worker's cwd" becomes the chant process's cwd, and the one "replay-safe" argument becomes the reason it was standing in for. TMP014 becomes OPS014, which is the rule that ships; TMP010 as a live peer becomes the shared validator in `op/cron.ts`. The six lexicon step-builder headers stop contrasting themselves with a temporal barrel and say why the builders live in the lexicon: core cannot import a lexicon's `*Args` types. The vitest and no-egress timeout comments drop the webpack-bundling claim. `forgejo-preview-e2e` says the two packages it actually packs. The COMP004 docs heading becomes the rule's own description, with a note that the id and the file name deliberately stay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148a4dPTErCBpmNy7J7933f
…rts core's cron validator Sub-issue 4 of #2191, the dead-code half. `safeHeartbeat` was a no-op kept alive so the lexicons calling it would keep compiling. Forty-four call sites across core, aws, azure, fly, gcp, gitlab, helm, k3d, k3s, k8s, render and terraform are gone, and so is the export. Five of them ran a live 15 second `setInterval` for the length of a shell-out (helm's two install paths, k3s install, k3d cluster create, and terraform's `withHeartbeat` wrapper around seven calls); the timers and their `clearInterval` plumbing go with them, since nothing was listening and nothing acted on a missed tick. FTN020 now imports core's `isValidCronExpression` rather than carrying a divergent copy of it, and refuses the `@daily`-style nicknames the copy used to accept. chant validates and matches one cron notion: `cronMatches` cannot evaluate a nickname, so a cadence written that way is one chant itself would never fire. The rule doc, its audit-catalog guidance, the fountain resources page and the test record the decision. The four CI lexicon pipeline tests stop teaching `--temporal` as a sample `runCommand` (it exits 2) and use `--json` or `--verbose`, and `run.test.ts`'s `makeOp` drops the `taskQueue?` field `OpConfig` has not had since #2118. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148a4dPTErCBpmNy7J7933f
lex00
added a commit
that referenced
this pull request
Sep 7, 2026
…2214) Peer session dropped findings in #2198, #2203, #2206; this session dropped findings in #2208, #2210, #2213. Each PR deferred the ratchet so the drops would not conflict on the same baseline file, per PR #2174's precedent that the baseline is ratcheted down rather than left slack. Claude-Session: https://claude.ai/code/session_01FoXyD9UvKQ5ZdhiR9JT1yB Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2195. Sub-issue 4 of #2191, the last one.
Two commits. The first rewrites every comment, docstring, test name and fixture the audit listed, against what the local executor actually does. The second deletes
safeHeartbeatand everything that only existed to call it, points FTN020 at core's cron validator, and takes--temporalout of the four CI pipeline test fixtures.The vocabulary half is mostly one substitution repeated: what these comments call a "search attribute" is an
outcomeAttributeon a step, which the executor captures onto that step'sStepRecordand the runtime folds onto the run ledger record (packages/core/src/op/local-executor.ts:278-284,packages/core/src/op/runtime.ts:90-98). It is a run outcome, not a queryable index on a live workflow. "Workflow", where it meant the Op or the run, is the Op or the run.OpConfig.namenamesdist/ops/<name>/; the workflow function name it also claimed to name went away with 257ca0c.signalNameis left alone here and gets its own issue, #2202.The dead-code half removes a real cost, not only a dead symbol: five of the
safeHeartbeatcall sites sat inside a 15 secondsetIntervalthat ran for the whole length of a shell-out and did nothing on each tick.Three items on the list were already fixed by the sibling PRs and are not touched: the thrown message in
lexicons/k8s/src/composites/operator-stack.ts(#2197 rewrote it to cite OPS014 and the unattended-approval reason), and the heartbeat and "worker loads it" comments in the activity files (#2198). Their doc comments still said TMP014, so those are rewritten here.Acceptance
git show ca00f1a7 --statcovers it.safeHeartbeatdoes not exist and nosetIntervalheartbeat loop remains in any activity.rg -n --text "safeHeartbeat" packages lexiconsis empty (exit 1).rg -n "heartbeat" -i packages lexicons -g '!**/generated/**'leaves only fountain's SSE keep-alives, theheartbeatTimeoutremoval notes inactivity-profiles.ts, and the lease doc.@dailydecision.lexicons/fountain/src/lint/post-synth/ftn020-schedule-cron-syntax.ts:2importsisValidCronExpressionfrom@intentius/chant/op; lines 19-26 record the decision, as do the audit-catalog guidance andlexicons/fountain/docs/pages/resources.mdx:59.--temporalas a run command.rg -n -- "--temporal" lexiconsis empty. The four remaining hits repo-wide are the bridge inpackages/core/src/cli/main.ts, its test, and the two docs pages that document it.signalName. rename signalName on gate() and GateStep to a name that does not imply a signal #2202.The
@dailydecisionFTN020 used to accept the
@nicknameshorthands; core's validator never has. Rejecting them, per the issue's recommendation: chant has one idea of what a cron string is, andcronMatches/cronDueBetweencannot evaluate a nickname, so a cadence written that way is onechant operatorwould never fire even though fountain upstream would store it. Recorded in the rule's module doc, itsdescription, the audit-catalog guidance (and so the generatedaudit-rules.mdxrow), and the fountain resources page. The test that asserted@dailypasses now asserts it and four siblings are refused.Every rewritten line
"Search attribute" for what is now a run outcome
packages/core/src/op/local-executor.ts:50,55packages/core/src/op/activities/lifecycle.ts:64,70Driftoutcome"packages/core/src/op/composites/watch-op.ts:5-6../local-executor.ts) runs the phases in order and files each step's record under the phase it belongs to"packages/core/src/op/composites/watch-op.ts:80,89Driftoutcome on the run ledger, so a reader of the ledger can pick out the runs that detected drift" / "a StaleReceipts run outcome"packages/core/src/op/composites/workflow-audit-op.ts:78Findingsoutcome on the run ledger, so a reader can pick out the audits that found something"packages/core/src/op/composites/lexicon-upgrade-op.ts:124-125HasUpgradeoutcome on the run ledger, so a reader can pick out the lexicons with an upgrade ready"packages/core/src/op/composites/composites.test.ts:71,133,354and:75,128,190packages/core/src/op/composites/lexicon-upgrade-op.test.ts:45packages/core/src/lint/rules/op/ops014-converge-rule-refusals.test.ts:244lexicons/cedar/src/dogwood/replay-op.ts:212Divergencesoutcome on the run ledger, so the replays that found something are readable from the ledger"lexicons/cedar/src/dogwood/replay-activity.ts:97lexicons/cedar/src/dogwood/replay-activity.test.ts:413Adjacent, same error, found by the grep the issue asks the PR to run — the terraform composites, which #2198 did not reach:
terraform-watch-op.ts:10,43,129,terraform-watch-op.test.ts:48,terraform-apply-op.ts:92,216,terraform-apply-op.test.ts:103,terraform-apply-op.acceptance.test.ts:133,terraform-adopt-op.ts:186,terraform/src/op/activities/terraform.ts:378,terraform/src/op/adoption.ts:22,terraform/src/skills/chant-terraform.md:68. All become run outcome / outcome attribute. Two of them are gate description strings a human reads, so the wording matters there."Workflow" for the Op or the run
packages/core/src/op/activities/converge.ts:2,60ConvergeOp-generated workflow's Converge phase" / "bakes into the workflow'sconvergeTickstep args"ConvergeOp-generated Op's Converge phase" / "bakes into the Op'sconvergeTickstep args"packages/core/src/op/activities/env-teardown.ts:49,53packages/core/src/op/activities/env-teardown.ts:152-153onFailurecompensation runs for."packages/core/src/op/activities/apply.ts:93packages/core/src/op/builders.ts:366packages/core/src/op/builders.ts:868policyGate"blocking the workflow on any violation"packages/core/src/op/step-output-ref.ts:14-16resolveStepOutputRefsoverresultsById)"packages/core/src/components/cli-support.ts:435chant graph --componentsreports"chant graph --componentsreports"packages/core/src/op/types.ts:12dist/ops/<name>/), and is the namechant run <name>and another Op'sdependsrefer to"watch-op.ts:32,converge-op.ts:100,reconcile-op.ts:36,pipeline-audit-op.ts:33,workflow-audit-op.ts:37,lexicon-upgrade-op.ts:63chant runtakes"packages/core/src/cli/commands/build.test.ts:952-983ops/durable-hello/{workflow,worker,activities}.tsops/nightly-report/{steps.json,notes.md,nested/extra.txt}— the routing is on theops/prefix alone, so the test now proves that.build.ts:766and its two messages follow.Adjacent, same class, all in
packages/core/src:types.ts:169("carried into the generated workflow as a comment" → "into the Op's build output"),op-ir.ts:61("resolved by the serializer into a local variable in the generatedworkflow.ts" → "which the executor substitutes for at run time from the producing step's record"),apply.ts:451,receipt-store.ts:111,activities/reconcile.ts:25,apply-op.ts:74("not by a signal to a running workflow" → "which the next run reads; nothing waits in the meantime"),component.schema.json'sGate.signalNamedescription ("Signal name the generated workflow waits for" → "The gate's name — whatchant approve <component> <gate>resolves"), and the six/** Op resource — generates the X workflow */lines on the composites.Other
packages/core/src/components/auto-release.ts:16-17packages/core/src/op/composites/converge-op.ts:74,75,83lexicons/k8s/src/composites/operator-stack.ts:40,44,49,107,201:49also drops "(the local dispatch executor can't honor its required gate)" for OPS014's own unattended-approval reason, matching the message #2197 wrote;:33drops "independently of the temporal lexicon" for "from the Op configs alone"packages/core/src/op/activities/converge.ts:73,76,205,216,232,254,375,converge.test.ts:195,op-verb-class.ts:118,lexicons/k8s/src/composites/composites.test.ts:3871,3883packages/core/src/op/composites/converge-op.ts:176,178packages/core/src/op/cron.ts:37,types.ts:47,cron.test.ts:14,35,op.test.ts:56,converge-op.test.ts:86,composites.test.ts:53./cron.ts/cronSyntaxMessage's wording / "a lexicon's own post-synth cron check"lexicons/{aws,azure,gcp,k3d,k3s}/src/op/builders.ts:4-6optsIS the activity's own*Argsinterface can only sit in the lexicon that declares that interface"lexicons/k8s/src/op/builders.ts:25op. That is exactly the 'core stays product-agnostic' property…"lexicons/azure/src/deep-observe.ts:342lexicons/aws/src/codegen/docs.ts:117lexicons/fly/src/components/run-agent-conformance.test.ts:22-26,174-182run()androllback()execute as separate steps"inputfrom JSON for every call". This is the caseCapability.rollback's own doc already describes, and the sprite fixture name follows.vitest.config.ts:111-114test/no-egress.test.ts:274-277test/forgejo-preview-e2e.sh:13,test/forgejo-preview-e2e/README.md:21-wflags at lines 98-100packages/core/src/components/pilots/pilots-lint-clean.test.ts:62-63// chant-disable-next-line COMP004 -- <reason>"// chant-disable COMP004 -- <reason>… a COMP* diagnostic is reported for the whole component and carries no line, so-line/-next-linedo not suppress one".../__fixtures__/comp/comp004/pass/orders-table.component.ts:4docs/src/content/docs/lint-rules/composition.mdx:140description, with a paragraph saying the id and the file name deliberately stay (renaming either churns everychant-disablecomment and every import). The same note is in the rule file's module doc.Dead code
safeHeartbeatand its export are gone (packages/core/src/op/activity-runtime.ts,op/index.ts). 44 call sites removed across core'semulator-lifecycle.tsand aws, azure, fly, gcp, gitlab, helm, k3d, k3s, k8s, render and terraform. The issue did not list render;rgskipsrender-apply.tsas binary (it contains a NUL byte), so--textwas needed to find its four.setIntervalloops, not four: helm's two install paths (helm.ts:434,565),k3s.ts:142,k3d.ts:160, andkubectl.ts:312(whose interval body was the singlesafeHeartbeatcall). Plus terraform'swithHeartbeatwrapper (terraform.ts:736), which ran the same 15s timer around seven calls; its seven call sites are unwrapped and the helper,HEARTBEAT_MSand theclearIntervalplumbing deleted. helm's pinned path keeps itstry/finallybecause thefinallyalso removes the temp wrapper chart.lexicons/fountain/src/lint/post-synth/ftn020-schedule-cron-syntax.tsimportsisValidCronExpressionfrom@intentius/chant/opinstead of inlining a copy; the@dailydecision is above.--temporalas a samplerunCommandinlexicons/{github,gitlab}/src/components/generate-{op-,}pipeline.test.tsbecomes--json(the Op pipelines) and--verbose(the component pipelines). Both are realchant runflags, so the fixture teaches something that works.packages/core/src/cli/handlers/run.test.ts:82makeOpdropstaskQueue?: string.Grep survivors
rg -n --text "safeHeartbeat" packages lexicons— empty.rg -n --text "search attribute|searchAttribute" packages/core/src lexicons/*/src— 8 lines, all deliberate:lifecycle/run-ledger.ts:8,op/types.ts:28,op/runtime.ts:94,op/op-ir.ts:104OpConfig.searchAttributesfield by its own name to say it is gone and what replaced it ("used to carry", "are gone"). Renaming the field in its own obituary would make the sentence false.op/op-ir.test.ts:65,73,79searchAttributesis absent from the emitted JSON. The string is the assertion.op/step-output-ref.ts:3rg -n --text "TMP01" packages lexicons -g '!**/generated/**'— 15 lines:lint/rules/op/{index.ts:3,support.ts:6,ops012*.ts:4,ops013*.ts:6,ops014*.ts:3}and their three test headerscli/commands/build.ts:508,cli/commands/lint.ts:429lint/post-synth.test.ts:137op/cron.test.ts:2lexicons/k8s/src/composites/composites.test.ts:3990.not.toThrow(/TMP014/)is #2197's own regression assertion that the message no longer cites the retired rule. Changing it would delete the guard.One more that a
@dailygrep turns up and stays:lexicons/fountain/src/generated/index.d.ts:60says the nicknames work. That file is generated from fountain's OpenAPI spec and describes the server, which does accept them; FTN020 is chant being stricter, which is what the rule doc now says.Gates
Run from the worktree root at
71fe1914.npx vitest run packages/core lexicons/{fountain,k8s,helm,k3d,k3s,github,gitlab,aws,azure,gcp,fly,terraform,cedar,render}npx vitest run packages lexicons(alone)packages/core/src/cli/commands/init-lexicon.test.tsandpackages/core/src/cli/security-boundary.test.ts, every one a 20sTest timed out, no assertion failures. Re-run alone:2 passed (2) / 31 passed (31)in 13.5s. The known CLI end-to-end flake under pool load.npx vitest run packages/core(after the last edits)npx tsc --noEmit -p packages/core/tsconfig.jsonnpx tsc --noEmitnpx tsx scripts/typecheck.tsnpx tsx scripts/check-lexicons.tsnode scripts/depcheck.mjsnpx eslint packages/no-explicit-anywarnings inmeta/publish-metadata.test.tsnode scripts/lint-docs-sentences.mjsnode scripts/check-docs-diataxis.mjsdocs/src/content/docs/lint-rules/audit-rules.mdxwas regenerated withnpx tsx scripts/generate-audit-rules-doc.ts; the diff is the two FTN020 lines and nothing else. The fountainrules.mdxandresources.mdxtwins were edited by hand to match their sources, rather than regenerated, since the local manifests are stale (#2199).Follow-up
#2202 — rename
signalNameongate()andGateStep, with a deprecation, listing the authored surfaces that carry it.🤖 Generated with Claude Code
https://claude.ai/code/session_0148a4dPTErCBpmNy7J7933f