From 3915170647436638ad7e83f601e2d5ba96e8acc6 Mon Sep 17 00:00:00 2001 From: lex00 <121451605+lex00@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:36:03 -0600 Subject: [PATCH 1/2] docs(lexicons): describe the current run mechanism in the fountain, k8s, cedar, github, gitlab, terraform, helm, k3d, k3s and aws docs and skills (#2194) Sub-issue 3 of #2191. Lexicon docs pages, skills and comments that still taught the deleted hosting-runtime model. No behaviour changed; the diff is prose, docstrings, one LSP completion table, two skills and the regenerated doc twins. fountain: the second-apply claim is scoped to Teammate, Schedule and Webhook, the three kinds fountainApply reconciles per route. The steward mesh table has seven rows, and composites.mdx says six kinds. k8s: the argo skill teaches Argo owning the sync, an Op owning ordering and gates, and CI running the Op, and attributes waitForArgoSync to this lexicon and the argoSync profile to core. The argo-composites heading is renamed and both inbound anchors follow it. cedar: the replay page and skill teach `schedule` on the Op (#2120). github and gitlab: WorkflowAuditOp and PipelineAuditOp ship from core; the op-pipeline generators describe OpSchedule. terraform: the taskQueue completion and both option-table rows are gone. Seven activity files no longer promise a heartbeat ActivityProfile cannot express. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0148a4dPTErCBpmNy7J7933f --- lexicons/aws/docs/pages/policy-validation.mdx | 2 +- lexicons/aws/docs/src/content/docs/index.mdx | 6 +-- .../aws/docs/src/content/docs/intrinsics.mdx | 6 +-- .../src/content/docs/policy-validation.mdx | 2 +- lexicons/aws/src/agentcore/trace-fetch.ts | 9 +++-- lexicons/aws/src/op/activities/floci.ts | 5 ++- lexicons/cedar/docs/pages/dogwood-replay.mdx | 21 +++++++--- .../docs/src/content/docs/dogwood-replay.mdx | 21 +++++++--- .../cedar/docs/src/content/docs/index.mdx | 2 +- lexicons/cedar/src/dogwood/replay-activity.ts | 5 +-- lexicons/cedar/src/index.ts | 4 +- lexicons/cedar/src/op/activities/index.ts | 4 +- .../cedar/src/skills/chant-cedar-dogwood.md | 7 +++- lexicons/fountain/docs/pages/composites.mdx | 2 +- lexicons/fountain/docs/pages/steward.mdx | 4 +- .../docs/src/content/docs/composites.mdx | 2 +- .../docs/src/content/docs/steward.mdx | 4 +- lexicons/fountain/src/deep-observe.ts | 4 +- .../fountain/src/skills/chant-fountain-ops.md | 6 ++- lexicons/github/docs/pages/lint-rules.mdx | 2 +- .../github/docs/src/content/docs/index.mdx | 4 +- .../docs/src/content/docs/lint-rules.mdx | 2 +- .../src/components/generate-op-pipeline.ts | 9 +++-- lexicons/gitlab/docs/pages/lint-rules.mdx | 2 +- .../gitlab/docs/src/content/docs/index.mdx | 2 +- .../docs/src/content/docs/lint-rules.mdx | 2 +- .../src/components/generate-op-pipeline.ts | 10 +++-- lexicons/gitlab/src/op/activities/gitlab.ts | 2 +- lexicons/gitlab/src/op/activities/index.ts | 9 +++-- lexicons/helm/src/op/activities/helm.ts | 2 +- lexicons/helm/src/op/activities/index.ts | 8 ++-- lexicons/k3d/src/op/activities/index.ts | 8 ++-- lexicons/k3d/src/op/activities/k3d.ts | 5 ++- lexicons/k3s/src/op/activities/index.ts | 9 +++-- lexicons/k3s/src/op/activities/k3s.ts | 6 +-- lexicons/k8s/docs/pages/api-client.mdx | 4 +- lexicons/k8s/docs/pages/argo-composites.mdx | 6 +-- lexicons/k8s/docs/pages/crd-classes.mdx | 2 +- lexicons/k8s/docs/pages/flux-composites.mdx | 2 +- .../k8s/docs/src/content/docs/api-client.mdx | 4 +- .../docs/src/content/docs/argo-composites.mdx | 6 +-- .../k8s/docs/src/content/docs/crd-classes.mdx | 21 +++++++++- .../docs/src/content/docs/flux-composites.mdx | 2 +- lexicons/k8s/docs/src/content/docs/index.mdx | 2 +- lexicons/k8s/src/api/sweep-types.ts | 3 +- lexicons/k8s/src/config.ts | 4 +- lexicons/k8s/src/deep-observe-hooks.ts | 8 ++-- lexicons/k8s/src/deep-observe.ts | 10 ++--- lexicons/k8s/src/export-resources.ts | 2 +- lexicons/k8s/src/op/activities/argo.test.ts | 6 +-- lexicons/k8s/src/op/activities/argo.ts | 8 ++-- lexicons/k8s/src/op/activities/index.ts | 16 ++++---- .../k8s/src/op/activities/kubectl.test.ts | 9 +++-- lexicons/k8s/src/op/activities/kubectl.ts | 10 ++--- .../k8s/src/op/activities/wait-for-ready.ts | 11 ++--- lexicons/k8s/src/plugin.ts | 2 +- lexicons/k8s/src/skills/chant-k8s-argo.md | 40 +++++++++++++------ lexicons/terraform/docs/pages/ops.mdx | 6 +-- .../terraform/docs/src/content/docs/ops.mdx | 6 +-- .../src/composites/terraform-adopt-op.ts | 2 +- .../src/composites/terraform-apply-op.ts | 2 +- .../terraform/src/lsp/completions.test.ts | 3 +- lexicons/terraform/src/lsp/option-keys.ts | 3 +- 63 files changed, 231 insertions(+), 167 deletions(-) diff --git a/lexicons/aws/docs/pages/policy-validation.mdx b/lexicons/aws/docs/pages/policy-validation.mdx index 1b21ddbe8..b0fa0a655 100644 --- a/lexicons/aws/docs/pages/policy-validation.mdx +++ b/lexicons/aws/docs/pages/policy-validation.mdx @@ -53,7 +53,7 @@ export default Op({ // (`chant build src --lexicon aws -o template.json`). phase("Build", [build(".")]), // Runs `cfn-guard validate -r rules.guard -d template.json`. - // A violation fails the workflow here, so nothing is applied. + // A violation fails the run here, so nothing is applied. phase("Policy", [guardValidate("rules.guard")]), phase("Apply", [awsApply("template.json", { stackName: "prod" })]), ], diff --git a/lexicons/aws/docs/src/content/docs/index.mdx b/lexicons/aws/docs/src/content/docs/index.mdx index 87ef536bc..bb86fadf2 100644 --- a/lexicons/aws/docs/src/content/docs/index.mdx +++ b/lexicons/aws/docs/src/content/docs/index.mdx @@ -23,13 +23,13 @@ npm install --save-dev @intentius/chant-lexicon-aws | Resources | 1663 | | Property types | 9098 | | Services | 286 | -| Intrinsic functions | 13 | +| Intrinsic functions | 9 | | Pseudo-parameters | 8 | | Lint rules | 64 | -**Lexicon version:** 0.56.0 +**Lexicon version:** 0.57.0 **Namespace:** `AWS` -- [Intrinsic Functions](./intrinsics) — 13 built-in functions +- [Intrinsic Functions](./intrinsics) — 9 built-in functions - [All Rules](./rules) — 64 rules - [Serialization](./serialization) — output format details diff --git a/lexicons/aws/docs/src/content/docs/intrinsics.mdx b/lexicons/aws/docs/src/content/docs/intrinsics.mdx index ec94f7f3e..d356fa87d 100644 --- a/lexicons/aws/docs/src/content/docs/intrinsics.mdx +++ b/lexicons/aws/docs/src/content/docs/intrinsics.mdx @@ -6,7 +6,7 @@ description: "Built-in intrinsic functions for the AWS CloudFormation lexicon" {/* GENERATED-BY-CHANT-DOCS by `npm run docs -w @intentius/chant-lexicon-aws` — DO NOT EDIT. Edit lexicons/aws/src/codegen/docs.ts instead; changes here are overwritten. */} -The AWS CloudFormation lexicon provides **13** intrinsic functions. +The AWS CloudFormation lexicon provides **9** intrinsic functions. **Tag?** shows how an intrinsic is authored: a genuine tagged template (`Sub\`...\``) or a plain function call (`Ref(...)`). **Folds?** shows whether [folding](/chant/concepts/typescript-as-data/#folded-vs-run) — the default `chant build` path — can reduce a use of this intrinsic today, without running the file — generated directly from this lexicon's registration, not restated by hand. A tagged template folds once it is registered. A plain call folds only where this lexicon has opted that intrinsic in one at a time (`foldsAsCall`), so a `No` in this column means this intrinsic has not been opted in, not that calls in general cannot fold. @@ -21,7 +21,3 @@ The AWS CloudFormation lexicon provides **13** intrinsic functions. | `Split` | Fn::Split — split string by delimiter | `Fn::Split` | No | Yes | | `Base64` | Fn::Base64 — encode to Base64 | `Fn::Base64` | No | Yes | | `GetAZs` | Fn::GetAZs — list Availability Zones | `Fn::GetAZs` | No | Yes | -| `Equals` | Fn::Equals — condition comparing two values | `Fn::Equals` | No | Yes | -| `And` | Fn::And — condition conjunction | `Fn::And` | No | Yes | -| `Or` | Fn::Or — condition disjunction | `Fn::Or` | No | Yes | -| `Not` | Fn::Not — condition negation | `Fn::Not` | No | Yes | diff --git a/lexicons/aws/docs/src/content/docs/policy-validation.mdx b/lexicons/aws/docs/src/content/docs/policy-validation.mdx index db6ad6c16..784277276 100644 --- a/lexicons/aws/docs/src/content/docs/policy-validation.mdx +++ b/lexicons/aws/docs/src/content/docs/policy-validation.mdx @@ -57,7 +57,7 @@ export default Op({ // (`chant build src --lexicon aws -o template.json`). phase("Build", [build(".")]), // Runs `cfn-guard validate -r rules.guard -d template.json`. - // A violation fails the workflow here, so nothing is applied. + // A violation fails the run here, so nothing is applied. phase("Policy", [guardValidate("rules.guard")]), phase("Apply", [awsApply("template.json", { stackName: "prod" })]), ], diff --git a/lexicons/aws/src/agentcore/trace-fetch.ts b/lexicons/aws/src/agentcore/trace-fetch.ts index fc6b19dd5..0a72d72dd 100644 --- a/lexicons/aws/src/agentcore/trace-fetch.ts +++ b/lexicons/aws/src/agentcore/trace-fetch.ts @@ -6,10 +6,11 @@ * lexicon contributes `dogwoodReplay`: a plain exported async function taking * one args object, re-exported from `src/op/activities/index.ts`, resolved **by * name** by core's activity registry when a project lists the `aws` lexicon. - * No Temporal import beneath it, so the local executor runs it unchanged and a - * Temporal worker registers the same function. Transport is injectable through - * the same `AwsReadHttp` seam `src/api/read-client.ts` already uses, so tests - * never touch the network and `endpoint` retargets the whole thing. + * It imports no runtime of its own, so the local executor + * (`packages/core/src/op/local-executor.ts`) calls it as-is. Transport is + * injectable through the same `AwsReadHttp` seam `src/api/read-client.ts` + * already uses, so tests never touch the network and `endpoint` retargets the + * whole thing. * * The output is text. The cedar lexicon's `PolicyReplayOp` reads a trace from * `tracePath`, so `outPath` here is the handoff — and it is the *only* handoff. diff --git a/lexicons/aws/src/op/activities/floci.ts b/lexicons/aws/src/op/activities/floci.ts index 4206b1fd4..f66130fc4 100644 --- a/lexicons/aws/src/op/activities/floci.ts +++ b/lexicons/aws/src/op/activities/floci.ts @@ -101,8 +101,9 @@ export function flociRunCommand(args: FlociUpArgs = {}): string { * Idempotent: reuses a running container of the same name. Waits for the health * endpoint to report `readyService`, then sets `AWS_ENDPOINT_URL` + test creds in * the process environment so a following `nativeApply`/`cfn-deploy` targets the - * emulator. Env injection assumes the in-process local executor; under a - * distributed Temporal worker, pass the endpoint explicitly instead. + * emulator. Env injection works because the local executor runs every step of a + * run in this process; a step that runs anywhere else needs the endpoint passed + * to it explicitly. */ export async function flociUp(args: FlociUpArgs = {}, signal?: AbortSignal): Promise<{ endpoint: string }> { const region = args.region ?? DEFAULT_REGION; diff --git a/lexicons/cedar/docs/pages/dogwood-replay.mdx b/lexicons/cedar/docs/pages/dogwood-replay.mdx index 0fbc4223b..c51ebdac8 100644 --- a/lexicons/cedar/docs/pages/dogwood-replay.mdx +++ b/lexicons/cedar/docs/pages/dogwood-replay.mdx @@ -67,11 +67,22 @@ markdown, and the other two hand back a title and body for whatever opens them division `workflowSupplyChainAudit` draws. `failOnDivergence` defaults to false: an observe-dial Op reports, and a red run is the caller's decision. -The composite ships from cedar, not from temporal, because it hands back an Op -and nothing else. It imports `@intentius/chant/op` and carries no dependency on -the temporal lexicon. A project that wants it scheduled pairs it with a -`TemporalSchedule` of its own — two lines, project-side, rather than a config -flag that would drag the dependency in for everyone. +The composite ships from cedar because it hands back an Op and nothing else. It +imports `@intentius/chant/op` and depends on no other lexicon. + +A project that wants it scheduled sets `schedule` on the composite (#2120). The +string lands on the Op as `schedule: { cron, overlap: "skip" }`, which is +runtime-neutral data each reader interprets: `chant operator` ticks it locally, +the github/gitlab/forgejo lexicons render it as a CI cron, and the one-shot +executor behind `chant run policy-replay` ignores it. + +```typescript +export const { op } = PolicyReplayOp({ + name: "policy-replay", + schedule: "0 6 * * *", + // … +}); +``` ## Typed traces diff --git a/lexicons/cedar/docs/src/content/docs/dogwood-replay.mdx b/lexicons/cedar/docs/src/content/docs/dogwood-replay.mdx index 018040693..8787ad31d 100644 --- a/lexicons/cedar/docs/src/content/docs/dogwood-replay.mdx +++ b/lexicons/cedar/docs/src/content/docs/dogwood-replay.mdx @@ -70,11 +70,22 @@ markdown, and the other two hand back a title and body for whatever opens them division `workflowSupplyChainAudit` draws. `failOnDivergence` defaults to false: an observe-dial Op reports, and a red run is the caller's decision. -The composite ships from cedar, not from temporal, because it hands back an Op -and nothing else. It imports `@intentius/chant/op` and carries no dependency on -the temporal lexicon. A project that wants it scheduled pairs it with a -`TemporalSchedule` of its own — two lines, project-side, rather than a config -flag that would drag the dependency in for everyone. +The composite ships from cedar because it hands back an Op and nothing else. It +imports `@intentius/chant/op` and depends on no other lexicon. + +A project that wants it scheduled sets `schedule` on the composite (#2120). The +string lands on the Op as `schedule: { cron, overlap: "skip" }`, which is +runtime-neutral data each reader interprets: `chant operator` ticks it locally, +the github/gitlab/forgejo lexicons render it as a CI cron, and the one-shot +executor behind `chant run policy-replay` ignores it. + +```typescript +export const { op } = PolicyReplayOp({ + name: "policy-replay", + schedule: "0 6 * * *", + // … +}); +``` ## Typed traces diff --git a/lexicons/cedar/docs/src/content/docs/index.mdx b/lexicons/cedar/docs/src/content/docs/index.mdx index aff20fe0b..7c4fd3d78 100644 --- a/lexicons/cedar/docs/src/content/docs/index.mdx +++ b/lexicons/cedar/docs/src/content/docs/index.mdx @@ -67,7 +67,7 @@ honest about upstream's governance before it shows you a builder. | Pseudo-parameters | 0 | | Lint rules | 18 | -**Lexicon version:** 0.49.0 +**Lexicon version:** 0.57.0 **Namespace:** `Cedar` - [All Rules](./rules) — 18 rules diff --git a/lexicons/cedar/src/dogwood/replay-activity.ts b/lexicons/cedar/src/dogwood/replay-activity.ts index 3a4a2ce5f..ee2b7d3f5 100644 --- a/lexicons/cedar/src/dogwood/replay-activity.ts +++ b/lexicons/cedar/src/dogwood/replay-activity.ts @@ -5,9 +5,8 @@ * Contributed the way the fly lexicon contributes `flyApply`: a plain exported * async function taking one args object, re-exported from * `src/op/activities/index.ts`, resolved **by name** by core's activity - * registry when a project lists the `cedar` lexicon. There is no Temporal - * import here and no Temporal dependency in the package — the local executor - * runs it as-is, and a Temporal worker registers the same function. + * registry when a project lists the `cedar` lexicon. It imports no runtime of + * its own, so the local executor calls it as-is. * * What it does: takes a policy bundle (inline text or paths), an event trace * (typed events, inline text, or a path) and a set of expectations, runs diff --git a/lexicons/cedar/src/index.ts b/lexicons/cedar/src/index.ts index 8dd3c2c6d..bcb0982e4 100644 --- a/lexicons/cedar/src/index.ts +++ b/lexicons/cedar/src/index.ts @@ -115,8 +115,8 @@ export { DOGWOOD_UPSTREAM } from "./dogwood/upstream"; // The replay Op composite and its typed step builders (#1661). Flat, like // fly's `flyDeploy`: an Op factory is what a project's `ops/*.op.ts` names, -// and it carries no dependency on the temporal lexicon — see -// ./dogwood/replay-op.ts for why the composite ships from cedar. +// and it depends on no lexicon but this one — see ./dogwood/replay-op.ts for +// why the composite ships from cedar. export { DEFAULT_REPLAY_REPORT_PATH, PolicyReplayOp, diff --git a/lexicons/cedar/src/op/activities/index.ts b/lexicons/cedar/src/op/activities/index.ts index ec2e3e0e7..903e5378a 100644 --- a/lexicons/cedar/src/op/activities/index.ts +++ b/lexicons/cedar/src/op/activities/index.ts @@ -10,8 +10,8 @@ * being registered as activities nobody would ever name in a step. * * Contributed the `flyApply` way: a plain async function taking one args - * object, with no Temporal import anywhere beneath it, so the local executor - * runs it unchanged and a Temporal worker registers the same function. + * object, depending on no runtime beyond node, so the local executor + * (`packages/core/src/op/local-executor.ts`) calls it directly. */ export { dogwoodReplay, dogwoodReplayReport } from "../../dogwood/replay-activity"; diff --git a/lexicons/cedar/src/skills/chant-cedar-dogwood.md b/lexicons/cedar/src/skills/chant-cedar-dogwood.md index 57f1bc388..995841d61 100644 --- a/lexicons/cedar/src/skills/chant-cedar-dogwood.md +++ b/lexicons/cedar/src/skills/chant-cedar-dogwood.md @@ -282,8 +282,11 @@ Three phases — Artifacts (`chantBuild`, skippable with `buildScript: false`), Replay (`dogwoodReplay`, writes `dist/dogwood-replay.json`), Report (`dogwoodReplayReport`, acts on `report | issue | pull-request`). `failOnDivergence` defaults to false: an observe-dial Op reports. The composite -ships from cedar and carries no dependency on the temporal lexicon; a scheduled -form is a project-side `TemporalSchedule` pairing. +ships from cedar; it imports `@intentius/chant/op` and nothing else. Cadence is +a `schedule` on the Op itself (#2120): pass `schedule: "0 6 * * *"` and it lands +as `schedule: { cron, overlap: "skip" }`, which `chant operator` ticks locally +and the github/gitlab/forgejo lexicons render as a CI cron. The one-shot +executor behind `chant run` ignores it. Build traces with `dogwood.traceEvent()` rather than by hand. Two traps it exists to close, and both are worth naming whenever a user assembles a fixture: diff --git a/lexicons/fountain/docs/pages/composites.mdx b/lexicons/fountain/docs/pages/composites.mdx index 9b562cabc..95c78dbdb 100644 --- a/lexicons/fountain/docs/pages/composites.mdx +++ b/lexicons/fountain/docs/pages/composites.mdx @@ -68,4 +68,4 @@ Three things are refused at construction rather than at apply: - An op whose `schedule.overlap` is anything but `skip`. A fountain schedule that fires while the teammate is busy is dropped with `teammate was busy`, so any other policy would be a promise the server does not keep. - A webhook url FTN022 would reject: plaintext http, or a loopback, link-local or RFC1918 target. Refusing here rather than at synth keeps the author from reading a lint error about a resource they never typed. -`chant build` emits all four to the manifest in dependency order — Environment, Vault, Agent, Teammate, Schedule, Webhook — and `fountainApply` reconciles them. The first three go through fountain's bulk `POST /api/apply`; the other three, which bulk apply does not carry yet ([fountain#1636](https://github.com/BinaryBourbon/fountain/issues/1636)), go through their own routes afterwards, matched by name and by url, so a second apply of an unchanged manifest makes no writes at all. +`chant build` emits all six to the manifest in dependency order: Environment, Vault, Agent, Teammate, Schedule, Webhook. `fountainApply` reconciles them. The first three go through fountain's bulk `POST /api/apply`, which reports each resource as `created` or `updated` and nothing else, so it cannot tell you an Environment was already right. The other three, which bulk apply does not carry yet ([fountain#1636](https://github.com/BinaryBourbon/fountain/issues/1636)), go through their own routes afterwards, matched by name and by url, reading live state and comparing before they write, so a second apply of an unchanged manifest makes no Teammate, Schedule or Webhook writes. diff --git a/lexicons/fountain/docs/pages/steward.mdx b/lexicons/fountain/docs/pages/steward.mdx index 551e6e04c..44b6df995 100644 --- a/lexicons/fountain/docs/pages/steward.mdx +++ b/lexicons/fountain/docs/pages/steward.mdx @@ -10,7 +10,7 @@ The teammate's thread is that environment's operational history. Each turn is on ## The mesh -chant needs six things from a place to run ops. fountain already has a noun for each: +chant needs seven things from a place to run ops. fountain already has a noun for each: | chant need | fountain noun | |---|---| @@ -100,6 +100,6 @@ The composite refuses three things at construction, so the failure arrives while ## What `chant build` emits -All six kinds, in dependency order: Environment, Vault, Agent, Teammate, Schedule, Webhook. `fountainApply` sends the first three through fountain's bulk `POST /api/apply` and the other three through their own routes afterwards, matched by name and by url, so a second apply of an unchanged manifest makes no writes. +All six kinds, in dependency order: Environment, Vault, Agent, Teammate, Schedule, Webhook. `fountainApply` sends the first three through fountain's bulk `POST /api/apply` and the other three through their own routes afterwards, matched by name and by url. Bulk apply reports each resource as `created` or `updated` and nothing else, so an Environment that changed in no way still comes back `updated`. The per-route reconcilers read what is live and compare before they write, so a second apply of an unchanged manifest makes no Teammate, Schedule or Webhook writes. See [Composites](/chant/lexicons/fountain/composites/) for the constructor's full option list, [Runtime](/chant/lexicons/fountain/runtime/) for running an op on the steward, and [ACP](/chant/lexicons/fountain/acp/) for what the sandbox is actually speaking. diff --git a/lexicons/fountain/docs/src/content/docs/composites.mdx b/lexicons/fountain/docs/src/content/docs/composites.mdx index e5dee21ad..53a21e587 100644 --- a/lexicons/fountain/docs/src/content/docs/composites.mdx +++ b/lexicons/fountain/docs/src/content/docs/composites.mdx @@ -72,4 +72,4 @@ Three things are refused at construction rather than at apply: - An op whose `schedule.overlap` is anything but `skip`. A fountain schedule that fires while the teammate is busy is dropped with `teammate was busy`, so any other policy would be a promise the server does not keep. - A webhook url FTN022 would reject: plaintext http, or a loopback, link-local or RFC1918 target. Refusing here rather than at synth keeps the author from reading a lint error about a resource they never typed. -`chant build` emits all four to the manifest in dependency order — Environment, Vault, Agent, Teammate, Schedule, Webhook — and `fountainApply` reconciles them. The first three go through fountain's bulk `POST /api/apply`; the other three, which bulk apply does not carry yet ([fountain#1636](https://github.com/BinaryBourbon/fountain/issues/1636)), go through their own routes afterwards, matched by name and by url, so a second apply of an unchanged manifest makes no writes at all. +`chant build` emits all six to the manifest in dependency order: Environment, Vault, Agent, Teammate, Schedule, Webhook. `fountainApply` reconciles them. The first three go through fountain's bulk `POST /api/apply`, which reports each resource as `created` or `updated` and nothing else, so it cannot tell you an Environment was already right. The other three, which bulk apply does not carry yet ([fountain#1636](https://github.com/BinaryBourbon/fountain/issues/1636)), go through their own routes afterwards, matched by name and by url, reading live state and comparing before they write, so a second apply of an unchanged manifest makes no Teammate, Schedule or Webhook writes. diff --git a/lexicons/fountain/docs/src/content/docs/steward.mdx b/lexicons/fountain/docs/src/content/docs/steward.mdx index 5d07a45c0..36a1b3876 100644 --- a/lexicons/fountain/docs/src/content/docs/steward.mdx +++ b/lexicons/fountain/docs/src/content/docs/steward.mdx @@ -14,7 +14,7 @@ The teammate's thread is that environment's operational history. Each turn is on ## The mesh -chant needs six things from a place to run ops. fountain already has a noun for each: +chant needs seven things from a place to run ops. fountain already has a noun for each: | chant need | fountain noun | |---|---| @@ -104,6 +104,6 @@ The composite refuses three things at construction, so the failure arrives while ## What `chant build` emits -All six kinds, in dependency order: Environment, Vault, Agent, Teammate, Schedule, Webhook. `fountainApply` sends the first three through fountain's bulk `POST /api/apply` and the other three through their own routes afterwards, matched by name and by url, so a second apply of an unchanged manifest makes no writes. +All six kinds, in dependency order: Environment, Vault, Agent, Teammate, Schedule, Webhook. `fountainApply` sends the first three through fountain's bulk `POST /api/apply` and the other three through their own routes afterwards, matched by name and by url. Bulk apply reports each resource as `created` or `updated` and nothing else, so an Environment that changed in no way still comes back `updated`. The per-route reconcilers read what is live and compare before they write, so a second apply of an unchanged manifest makes no Teammate, Schedule or Webhook writes. See [Composites](/chant/lexicons/fountain/composites/) for the constructor's full option list, [Runtime](/chant/lexicons/fountain/runtime/) for running an op on the steward, and [ACP](/chant/lexicons/fountain/acp/) for what the sandbox is actually speaking. diff --git a/lexicons/fountain/src/deep-observe.ts b/lexicons/fountain/src/deep-observe.ts index b2c7b3f6f..0f0b5bddb 100644 --- a/lexicons/fountain/src/deep-observe.ts +++ b/lexicons/fountain/src/deep-observe.ts @@ -26,8 +26,8 @@ * * fountain's JSON views name their fields the same way the request schema does * (`networking_type`, `env_vars`, `skills`), so the live tree and the declared - * tree already speak one vocabulary — the AWS situation, not temporal's. The - * payload is therefore forwarded as-is and the noise rules + * tree already speak one vocabulary and nothing has to be renamed on the way in. + * The payload is therefore forwarded as-is and the noise rules * (./deep-observe-hooks.ts) do the rest. A field fountain adds in a later * release surfaces as unclaimed until the table names it (#2160: source * never set it, so it is not drift and is never proposed for update), which is diff --git a/lexicons/fountain/src/skills/chant-fountain-ops.md b/lexicons/fountain/src/skills/chant-fountain-ops.md index b1a27e759..6421f6b7f 100644 --- a/lexicons/fountain/src/skills/chant-fountain-ops.md +++ b/lexicons/fountain/src/skills/chant-fountain-ops.md @@ -75,8 +75,10 @@ chant run # or call fountainApply directly `fountainApply` sends Environment, Vault and Agent through fountain's bulk `POST /api/apply`, then Teammate, Schedule and Webhook through their own routes, -matched by name and by url. A second apply of an unchanged manifest writes -nothing. +matched by name and by url. Bulk apply reports each resource as `created` or +`updated` and nothing else, so it never says a resource was already right. The +per-route reconcilers compare live state before they write, so a second apply of +an unchanged manifest makes no Teammate, Schedule or Webhook writes. Endpoint and token come from `fountain.profiles` in `chant.config.ts`, falling back to `FOUNTAIN_ENDPOINT` / `FOUNTAIN_TOKEN`. A profile's `token` is always diff --git a/lexicons/github/docs/pages/lint-rules.mdx b/lexicons/github/docs/pages/lint-rules.mdx index 479fa46a6..b3bf317ac 100644 --- a/lexicons/github/docs/pages/lint-rules.mdx +++ b/lexicons/github/docs/pages/lint-rules.mdx @@ -188,7 +188,7 @@ Flags a workflow file with no top-level `on:` key. Without a trigger the workflo GHA029 onward are a CI/CD supply-chain security pass: pin & vet external references, enforce least-privilege token scopes, guard trust boundaries against untrusted input, contain secrets, reject unsound expressions, and keep artifacts/caches honest. They run statically on the emitted YAML — everything answerable without leaving the build. -The checks that need a *moving external truth* — whether a pinned SHA still maps to a real upstream tag, whether a ref still exists, whether a new advisory now covers an action in use — can't be deterministic, so they live in the operational layer instead. Schedule the [`WorkflowAuditOp`](/chant/guide/ops/#audit-supply-chain-drift) (temporal lexicon) for that live, always-fresh half; it reads the same emitted workflow references and reports drift via `report | issue | pull-request`. +The checks that need a *moving external truth* — whether a pinned SHA still maps to a real upstream tag, whether a ref still exists, whether a new advisory now covers an action in use — can't be deterministic, so they live in the operational layer instead. Schedule the [`WorkflowAuditOp`](/chant/guide/ops/#audit-supply-chain-drift), which ships from core's `@intentius/chant/op`, for that live, always-fresh half; it reads the same emitted workflow references and reports drift via `report | issue | pull-request`. ### GHA029 — Action or reusable workflow not pinned to a commit SHA diff --git a/lexicons/github/docs/src/content/docs/index.mdx b/lexicons/github/docs/src/content/docs/index.mdx index 2a27c0111..7c8bfe9ad 100644 --- a/lexicons/github/docs/src/content/docs/index.mdx +++ b/lexicons/github/docs/src/content/docs/index.mdx @@ -53,11 +53,11 @@ The lexicon provides **3 resources** (Workflow, Job, Dependabot config), **15 co | Resources | 3 | | Property types | 19 | | Services | 1 | -| Intrinsic functions | 19 | +| Intrinsic functions | 1 | | Pseudo-parameters | 0 | | Lint rules | 68 | -**Lexicon version:** 0.51.0 +**Lexicon version:** 0.57.0 **Namespace:** `GitHub` - [All Rules](./rules) — 68 rules diff --git a/lexicons/github/docs/src/content/docs/lint-rules.mdx b/lexicons/github/docs/src/content/docs/lint-rules.mdx index f5ab4ea83..2b8350f80 100644 --- a/lexicons/github/docs/src/content/docs/lint-rules.mdx +++ b/lexicons/github/docs/src/content/docs/lint-rules.mdx @@ -217,7 +217,7 @@ Flags a workflow file with no top-level `on:` key. Without a trigger the workflo GHA029 onward are a CI/CD supply-chain security pass: pin & vet external references, enforce least-privilege token scopes, guard trust boundaries against untrusted input, contain secrets, reject unsound expressions, and keep artifacts/caches honest. They run statically on the emitted YAML — everything answerable without leaving the build. -The checks that need a *moving external truth* — whether a pinned SHA still maps to a real upstream tag, whether a ref still exists, whether a new advisory now covers an action in use — can't be deterministic, so they live in the operational layer instead. Schedule the [`WorkflowAuditOp`](/chant/guide/ops/#audit-supply-chain-drift) (temporal lexicon) for that live, always-fresh half; it reads the same emitted workflow references and reports drift via `report | issue | pull-request`. +The checks that need a *moving external truth* — whether a pinned SHA still maps to a real upstream tag, whether a ref still exists, whether a new advisory now covers an action in use — can't be deterministic, so they live in the operational layer instead. Schedule the [`WorkflowAuditOp`](/chant/guide/ops/#audit-supply-chain-drift), which ships from core's `@intentius/chant/op`, for that live, always-fresh half; it reads the same emitted workflow references and reports drift via `report | issue | pull-request`. ### GHA029 — Action or reusable workflow not pinned to a commit SHA diff --git a/lexicons/github/src/components/generate-op-pipeline.ts b/lexicons/github/src/components/generate-op-pipeline.ts index aef2af8bf..f0df5822c 100644 --- a/lexicons/github/src/components/generate-op-pipeline.ts +++ b/lexicons/github/src/components/generate-op-pipeline.ts @@ -4,10 +4,11 @@ * The Op counterpart to `./generate-pipeline.ts` (#891): that module * synthesizes a `workflow_dispatch`-triggered pipeline from a deploy-time * component graph, this one synthesizes a cron-triggered workflow per - * stateless Op — the CI-native alternative to a Temporal `TemporalSchedule` - * for downstream projects that don't run Temporal (`WorkflowAuditOp`, - * `PipelineAuditOp`, `ReconcileOp`, … all accept an optional `schedule` - * precisely for this). + * stateless Op. An Op's cadence is an `OpSchedule` on the Op itself + * (`packages/core/src/op/types.ts`), runtime-neutral data each reader + * interprets; this module is the reader that turns it into a cron a GitHub + * runner fires (`WorkflowAuditOp`, `PipelineAuditOp`, `ReconcileOp`, … all + * accept an optional `schedule` precisely for this). * * GitHub Actions' `on.schedule` is workflow-scoped, not job-scoped, so unlike * the component generator (one combined pipeline for the whole graph) this diff --git a/lexicons/gitlab/docs/pages/lint-rules.mdx b/lexicons/gitlab/docs/pages/lint-rules.mdx index 457a33542..23b28b780 100644 --- a/lexicons/gitlab/docs/pages/lint-rules.mdx +++ b/lexicons/gitlab/docs/pages/lint-rules.mdx @@ -166,7 +166,7 @@ Detects `needs:` entries already implied by stage ordering. Not incorrect, but r WGL029 onward are a CI/CD supply-chain security pass, the GitLab counterpart to the github lexicon's GHA029–058: pin & vet includes/components/images, scope `CI_JOB_TOKEN` and OIDC, guard trust boundaries against untrusted CI input, mask/protect/scope secrets, reject unsound `rules:` expressions, and keep artifacts/caches honest. They run statically on the emitted `.gitlab-ci.yml`. -The checks that need a *moving external truth* — whether a pinned component/include ref still resolves, whether an upstream was archived or moved, whether a new advisory covers a component in use — live in the operational layer instead. Schedule the [`PipelineAuditOp`](/chant/guide/ops/#audit-supply-chain-drift) (temporal lexicon) for that live half; it reads the emitted `include:` / `component:` / `image:` references and reports drift via `report | issue | merge-request`. +The checks that need a *moving external truth* — whether a pinned component/include ref still resolves, whether an upstream was archived or moved, whether a new advisory covers a component in use — live in the operational layer instead. Schedule the [`PipelineAuditOp`](/chant/guide/ops/#audit-supply-chain-drift), which ships from core's `@intentius/chant/op`, for that live half; it reads the emitted `include:` / `component:` / `image:` references and reports drift via `report | issue | merge-request`. ### WGL029 — Unpinned include:project / component diff --git a/lexicons/gitlab/docs/src/content/docs/index.mdx b/lexicons/gitlab/docs/src/content/docs/index.mdx index 7e2de67d4..a5b5ce6e3 100644 --- a/lexicons/gitlab/docs/src/content/docs/index.mdx +++ b/lexicons/gitlab/docs/src/content/docs/index.mdx @@ -47,7 +47,7 @@ The lexicon provides **3 resources** (Job, Workflow, Default), **16 property typ | Pseudo-parameters | 0 | | Lint rules | 45 | -**Lexicon version:** 0.49.0 +**Lexicon version:** 0.57.0 **Namespace:** `GitLab` - [Intrinsic Functions](./intrinsics) — 1 built-in functions diff --git a/lexicons/gitlab/docs/src/content/docs/lint-rules.mdx b/lexicons/gitlab/docs/src/content/docs/lint-rules.mdx index d125a5c26..a9eb4a815 100644 --- a/lexicons/gitlab/docs/src/content/docs/lint-rules.mdx +++ b/lexicons/gitlab/docs/src/content/docs/lint-rules.mdx @@ -246,7 +246,7 @@ Detects `needs:` entries already implied by stage ordering. Not incorrect, but r WGL029 onward are a CI/CD supply-chain security pass, the GitLab counterpart to the github lexicon's GHA029–058: pin & vet includes/components/images, scope `CI_JOB_TOKEN` and OIDC, guard trust boundaries against untrusted CI input, mask/protect/scope secrets, reject unsound `rules:` expressions, and keep artifacts/caches honest. They run statically on the emitted `.gitlab-ci.yml`. -The checks that need a *moving external truth* — whether a pinned component/include ref still resolves, whether an upstream was archived or moved, whether a new advisory covers a component in use — live in the operational layer instead. Schedule the [`PipelineAuditOp`](/chant/guide/ops/#audit-supply-chain-drift) (temporal lexicon) for that live half; it reads the emitted `include:` / `component:` / `image:` references and reports drift via `report | issue | merge-request`. +The checks that need a *moving external truth* — whether a pinned component/include ref still resolves, whether an upstream was archived or moved, whether a new advisory covers a component in use — live in the operational layer instead. Schedule the [`PipelineAuditOp`](/chant/guide/ops/#audit-supply-chain-drift), which ships from core's `@intentius/chant/op`, for that live half; it reads the emitted `include:` / `component:` / `image:` references and reports drift via `report | issue | merge-request`. ### WGL029 — Unpinned include:project / component diff --git a/lexicons/gitlab/src/components/generate-op-pipeline.ts b/lexicons/gitlab/src/components/generate-op-pipeline.ts index f6ad4684f..81c727a2c 100644 --- a/lexicons/gitlab/src/components/generate-op-pipeline.ts +++ b/lexicons/gitlab/src/components/generate-op-pipeline.ts @@ -3,10 +3,12 @@ * * The Op counterpart to `./generate-pipeline.ts` (#563): that module * synthesizes a deploy-time component graph as one `.gitlab-ci.yml`; this one - * synthesizes a cron-triggered job per stateless Op — the CI-native - * alternative to a Temporal `TemporalSchedule` for downstream projects that - * don't run Temporal (`WorkflowAuditOp`/`PipelineAuditOp`/`ReconcileOp` all - * accept an optional `schedule` precisely for this). + * synthesizes a cron-triggered job per stateless Op. An Op's cadence is an + * `OpSchedule` on the Op itself (`packages/core/src/op/types.ts`), + * runtime-neutral data each reader interprets; this module is the reader that + * turns it into a GitLab pipeline schedule (`WorkflowAuditOp`/ + * `PipelineAuditOp`/`ReconcileOp` all accept an optional `schedule` precisely + * for this). * * Unlike GitHub Actions' per-workflow `on.schedule`, GitLab has no in-file * cron at all — a schedule is a project-level object (Settings → CI/CD → diff --git a/lexicons/gitlab/src/op/activities/gitlab.ts b/lexicons/gitlab/src/op/activities/gitlab.ts index 0a68976a6..45e0ad535 100644 --- a/lexicons/gitlab/src/op/activities/gitlab.ts +++ b/lexicons/gitlab/src/op/activities/gitlab.ts @@ -16,7 +16,7 @@ export interface GitlabPipelineArgs { /** * Trigger a GitLab CI pipeline and wait for it to complete successfully. * Requires `glab` CLI authenticated in the environment. - * Uses longInfra profile — 20m timeout, heartbeat every poll. + * Uses the longInfra profile: 20m timeout, three attempts backing off from 30s. */ export async function gitlabPipeline(args: GitlabPipelineArgs, signal?: AbortSignal): Promise { const ref = args.ref ?? "HEAD"; diff --git a/lexicons/gitlab/src/op/activities/index.ts b/lexicons/gitlab/src/op/activities/index.ts index 85c4875f3..6081c9587 100644 --- a/lexicons/gitlab/src/op/activities/index.ts +++ b/lexicons/gitlab/src/op/activities/index.ts @@ -1,10 +1,11 @@ /** * gitlab Op activities — resolved by the core activity registry when a project's * `chant.config.ts` lists the `gitlab` lexicon. `gitlabPipeline` triggers a - * pipeline over the GitLab CLI with heartbeat/retry semantics; relocated from the - * temporal lexicon (#809) so gitlab's imperative activity lives with its product. - * The `gitlabPipeline` step builder stays in core, re-exported from the temporal - * Op-authoring barrel like the other core builders. + * pipeline over the GitLab CLI and polls it to completion under the step's + * profile; relocated from the hosting lexicon (#809) so gitlab's imperative + * activity lives with its product. The `gitlabPipeline` step builder stays in + * core and reaches authors through `@intentius/chant/op` like the other core + * builders. */ export { gitlabPipeline } from "./gitlab"; export type { GitlabPipelineArgs } from "./gitlab"; diff --git a/lexicons/helm/src/op/activities/helm.ts b/lexicons/helm/src/op/activities/helm.ts index 9d81c06e0..24963ba32 100644 --- a/lexicons/helm/src/op/activities/helm.ts +++ b/lexicons/helm/src/op/activities/helm.ts @@ -394,7 +394,7 @@ async function recordHelmRelease( * finished deploy into a failed activity — the record is observability, not * part of the deploy itself. * - * Uses longInfra profile — 20m timeout, heartbeat every 15s. + * Uses the longInfra profile: 20m timeout, three attempts backing off from 30s. */ export async function helmInstall( args: HelmInstallArgs, diff --git a/lexicons/helm/src/op/activities/index.ts b/lexicons/helm/src/op/activities/index.ts index dc1b7d3a8..00f5f672f 100644 --- a/lexicons/helm/src/op/activities/index.ts +++ b/lexicons/helm/src/op/activities/index.ts @@ -1,10 +1,10 @@ /** * helm Op activities — resolved by the core activity registry when a project's * `chant.config.ts` lists the `helm` lexicon. `helmInstall` shells out to the - * helm CLI with heartbeat/retry semantics; relocated from the temporal lexicon - * (#809) so helm's imperative activity lives with its product, not in temporal. - * The `helmInstall` step builder stays in core (@intentius/chant/op), re-exported - * from the temporal Op-authoring barrel like the other core builders. + * helm CLI, retried under the step's profile; relocated from the hosting + * lexicon (#809) so helm's imperative activity lives with its product. The + * `helmInstall` step builder stays in core and reaches authors through + * `@intentius/chant/op` like the other core builders. */ export { helmInstall, diff --git a/lexicons/k3d/src/op/activities/index.ts b/lexicons/k3d/src/op/activities/index.ts index 1a66e3605..930c49081 100644 --- a/lexicons/k3d/src/op/activities/index.ts +++ b/lexicons/k3d/src/op/activities/index.ts @@ -7,10 +7,12 @@ * context are left alone unless explicitly requested, and `k3dUp` returns * `{ context, kubeconfigPath? }` so later steps know what to talk to. * - * The step builders (k3dUp, k3dDown) stay in core, re-exported from the - * temporal Op-authoring barrel like the other core builders. The activities are + * The step builders (k3dUp, k3dDown) stay in core and reach authors through + * `@intentius/chant/op` like the other core builders. The activities are * dependency-light — they shell out to the k3d CLI and do not import the k3d - * declarable surface — so a Temporal worker loads them cheaply. + * declarable surface — so `loadActivities` + * (`packages/core/src/op/activity-registry.ts`), which imports this module at + * run time, pulls in nothing expensive. */ export { k3dUp, diff --git a/lexicons/k3d/src/op/activities/k3d.ts b/lexicons/k3d/src/op/activities/k3d.ts index 35d5644f4..f0c538176 100644 --- a/lexicons/k3d/src/op/activities/k3d.ts +++ b/lexicons/k3d/src/op/activities/k3d.ts @@ -137,8 +137,9 @@ async function resolveConnection(args: K3dUpArgs, signal?: AbortSignal): Promise /** * Create a local k3d cluster (vanilla Kubernetes in Docker). Idempotent: if a - * cluster of the same name already exists it is left as-is. Uses longInfra - * profile — 20m timeout, heartbeat every 15s (creation may pull the k3s image). + * cluster of the same name already exists it is left as-is. Uses the longInfra + * profile: a 20m timeout, wide enough that creation can pull the k3s image, and + * three attempts backing off from 30s. * * Unlike the upstream CLI, this does NOT touch the caller's default kubeconfig * or current context unless asked (see {@link K3dUpArgs.updateDefaultKubeconfig}). diff --git a/lexicons/k3s/src/op/activities/index.ts b/lexicons/k3s/src/op/activities/index.ts index 7a996d738..4d6b76ace 100644 --- a/lexicons/k3s/src/op/activities/index.ts +++ b/lexicons/k3s/src/op/activities/index.ts @@ -11,11 +11,12 @@ * as a value — only `tokenFile`, a path — see the token-boundary note on * {@link k3sInstall} in ./k3s. * - * The step builders (k3sInstall, k3sUninstall) live in core, re-exported from - * the temporal Op-authoring barrel like k3dUp/k3dDown. The activities here are + * The step builders (k3sInstall, k3sUninstall) live in core and reach authors + * through `@intentius/chant/op` like k3dUp/k3dDown. The activities here are * dependency-light — they shell out to the k3s installer/uninstall scripts and - * only pull in the lexicon's version pin, not its declarable surface — so a - * Temporal worker loads them cheaply. + * only pull in the lexicon's version pin, not its declarable surface — so + * `loadActivities` (`packages/core/src/op/activity-registry.ts`), which imports + * this module at run time, pulls in nothing expensive. */ export { k3sInstall, diff --git a/lexicons/k3s/src/op/activities/k3s.ts b/lexicons/k3s/src/op/activities/k3s.ts index 6d7155fdd..69266612b 100644 --- a/lexicons/k3s/src/op/activities/k3s.ts +++ b/lexicons/k3s/src/op/activities/k3s.ts @@ -111,9 +111,9 @@ export function k3sUninstallCommand(args: K3sUninstallArgs): string { /** * Run the pinned k3s installer against a reachable host. Idempotent on an * already-installed matching version: if `k3s --version` already reports the - * target version, the install is skipped. Uses longInfra profile — 20m - * timeout, heartbeat every 15s (the installer downloads and starts the - * k3s binary). + * target version, the install is skipped. Uses the longInfra profile: a 20m + * timeout, wide enough for the installer to download and start the k3s binary, + * and three attempts backing off from 30s. * * Bounded exactly as `k3dUp`/`k3dDown` were (chant#1410, epic #1598): this * drives the case where the host is reachable from where the Op runs. It diff --git a/lexicons/k8s/docs/pages/api-client.mdx b/lexicons/k8s/docs/pages/api-client.mdx index 686cbc72f..c112aedbd 100644 --- a/lexicons/k8s/docs/pages/api-client.mdx +++ b/lexicons/k8s/docs/pages/api-client.mdx @@ -18,7 +18,7 @@ Everything chant does against a live cluster — `chant lifecycle diff --live`, **Typed failures.** The API server sends a `Status` object with a numeric code and a `reason` enum. Chant reads those fields instead of matching English on stderr, so `403 Forbidden` becomes "not observed, no credentials" and `404 NotFound` becomes a genuine absence, by construction rather than by regex. -**No `kubectl` in the image.** A Temporal worker running `kubectlApply` or `waitForReady` needs no `kubectl` binary. (The activity is still called `kubectlApply`: workers register activities by name, and renaming it would break every registered workflow.) +**No `kubectl` in the image.** A process running `kubectlApply` or `waitForReady` needs no `kubectl` binary. (The activity is still called `kubectlApply`: core's activity registry keys the activity map by export name and an Op step resolves its `fn` string against it, so renaming the export would break every Op that names the step.) ## Installation @@ -79,7 +79,7 @@ The allowlist matches the command *name*, not its full path: `aws`, `/usr/local/ `kubectlApply` performs a **server-side apply**, rather than the client-side three-way merge `kubectl apply` does by default. Server-side apply is where Kubernetes itself has gone, it removes the `last-applied-configuration` annotation from the story, and it is what makes field ownership something the API server tracks rather than something chant has to infer. -`ApplyOp({ target: "kubectl" })` goes through the same path. The kubectl branch of `nativeApply` used to shell `kubectl apply -f`; it now dispatches into this lexicon, where the apply is server-side and the prune runs against the typed client. The dispatcher itself stays in the Temporal lexicon, because "which mechanism applies this target" is not Kubernetes knowledge — but applying to Kubernetes is. +`ApplyOp({ target: "kubectl" })` goes through the same path. The kubectl branch of `nativeApply` used to shell `kubectl apply -f`; it now dispatches into this lexicon, where the apply is server-side and the prune runs against the typed client. `nativeApply`, the dispatcher itself, stays in core, because "which mechanism applies this target" is not Kubernetes knowledge; applying to Kubernetes is. ### chant's field manager diff --git a/lexicons/k8s/docs/pages/argo-composites.mdx b/lexicons/k8s/docs/pages/argo-composites.mdx index 6f934842f..84b4c5a44 100644 --- a/lexicons/k8s/docs/pages/argo-composites.mdx +++ b/lexicons/k8s/docs/pages/argo-composites.mdx @@ -1,6 +1,6 @@ --- title: "Argo CD Composites" -description: "Argo CD support in the k8s lexicon — the argoproj.io CRDs, ArgoAppFor / ArgoAppSetForRegions composites, cluster registration, the ARGO00x rules, and the Argo-vs-Temporal split." +description: "Argo CD support in the k8s lexicon — the argoproj.io CRDs, ArgoAppFor / ArgoAppSetForRegions composites, cluster registration, the ARGO00x rules, and how a deploy splits between Argo, an Op and CI." diataxis: reference --- @@ -12,14 +12,14 @@ Argo CD's `Application`, `ApplicationSet`, and `AppProject` are Kubernetes CRDs The k8s lexicon stays **runtime-agnostic** — it only emits manifests. Argo is **opt-in** via the composites below; nothing here is implied unless you reach for it. -## The three-layer model: Argo vs Temporal vs CI +## The three-layer model: Argo, an Op and CI Chant authors typed infra into manifests. From there, who applies them? | Layer | Owns | Reach for it when | |---|---|---| | **Argo CD** | Continuously reconciling declarative manifests | the desired state lives in git and *converges* — Deployments, CRs, Helm releases | -| **Temporal** | Procedural steps with ordering, signals, human gates, one-shot RPCs | the step is a *procedure*, not a state — DNS delegation, cert generation, `db init` | +| **A chant Op** | Procedural steps with ordering, human gates, one-shot RPCs | the step is a *procedure*, not a state — DNS delegation, cert generation, `db init` | | **CI** | One-shot, fire-and-forget apply | a simple pipeline with no reconciliation or long-running orchestration need | Rule of thumb: **if it's declarative and converges, let Argo reconcile it; if it's a procedure with ordering or gates, run it as a chant Op.** Prefer Argo CD over Argo *Workflows* — the procedural layer stays an Op. See the [CockroachDB multi-region tutorial](/chant/tutorials/cockroachdb-multi-region/) for a deployment that uses both. diff --git a/lexicons/k8s/docs/pages/crd-classes.mdx b/lexicons/k8s/docs/pages/crd-classes.mdx index 0323f42ce..a68dd7a69 100644 --- a/lexicons/k8s/docs/pages/crd-classes.mdx +++ b/lexicons/k8s/docs/pages/crd-classes.mdx @@ -421,7 +421,7 @@ export const apps = new Kustomization({ ``` The `FluxGitSource` and `FluxAppFor` composites collapse the `GitRepository` + `Kustomization` pair above into two calls with estate-tested defaults, and the FLUX001–003 [lint rules](/chant/lexicons/k8s/lint-rules/#flux) validate the source pin, `sourceRef`, and `dependsOn` edges. diff --git a/lexicons/k8s/docs/pages/flux-composites.mdx b/lexicons/k8s/docs/pages/flux-composites.mdx index 8bf1a75bf..de01d438d 100644 --- a/lexicons/k8s/docs/pages/flux-composites.mdx +++ b/lexicons/k8s/docs/pages/flux-composites.mdx @@ -91,5 +91,5 @@ It applies the Flux CRs through the same server-side apply `kubectl-apply` uses ## See also - [flux-apps tutorial](/chant/tutorials/flux-apps/) — a self-hosted on-ramp: k3s, Traefik `IngressRoute`, cert-manager, three Kustomizations with `dependsOn`. -- [Argo CD Composites](../argo-composites/) — the Argo counterpart, and the three-layer Argo-vs-Temporal split. +- [Argo CD Composites](../argo-composites/) — the Argo counterpart, and the three-layer split between Argo, an Op and CI. - The `chant-k8s-flux` [skill](../skills/) — agent guidance for these patterns. diff --git a/lexicons/k8s/docs/src/content/docs/api-client.mdx b/lexicons/k8s/docs/src/content/docs/api-client.mdx index 94a1d7852..6998212fb 100644 --- a/lexicons/k8s/docs/src/content/docs/api-client.mdx +++ b/lexicons/k8s/docs/src/content/docs/api-client.mdx @@ -20,7 +20,7 @@ Everything chant does against a live cluster — `chant lifecycle diff --live`, **Typed failures.** The API server sends a `Status` object with a numeric code and a `reason` enum. Chant reads those fields instead of matching English on stderr, so `403 Forbidden` becomes "not observed, no credentials" and `404 NotFound` becomes a genuine absence, by construction rather than by regex. -**No `kubectl` in the image.** A Temporal worker running `kubectlApply` or `waitForReady` needs no `kubectl` binary. (The activity is still called `kubectlApply`: workers register activities by name, and renaming it would break every registered workflow.) +**No `kubectl` in the image.** A process running `kubectlApply` or `waitForReady` needs no `kubectl` binary. (The activity is still called `kubectlApply`: core's activity registry keys the activity map by export name and an Op step resolves its `fn` string against it, so renaming the export would break every Op that names the step.) ## Installation @@ -81,7 +81,7 @@ The allowlist matches the command *name*, not its full path: `aws`, `/usr/local/ `kubectlApply` performs a **server-side apply**, rather than the client-side three-way merge `kubectl apply` does by default. Server-side apply is where Kubernetes itself has gone, it removes the `last-applied-configuration` annotation from the story, and it is what makes field ownership something the API server tracks rather than something chant has to infer. -`ApplyOp({ target: "kubectl" })` goes through the same path. The kubectl branch of `nativeApply` used to shell `kubectl apply -f`; it now dispatches into this lexicon, where the apply is server-side and the prune runs against the typed client. The dispatcher itself stays in the Temporal lexicon, because "which mechanism applies this target" is not Kubernetes knowledge — but applying to Kubernetes is. +`ApplyOp({ target: "kubectl" })` goes through the same path. The kubectl branch of `nativeApply` used to shell `kubectl apply -f`; it now dispatches into this lexicon, where the apply is server-side and the prune runs against the typed client. `nativeApply`, the dispatcher itself, stays in core, because "which mechanism applies this target" is not Kubernetes knowledge; applying to Kubernetes is. ### chant's field manager diff --git a/lexicons/k8s/docs/src/content/docs/argo-composites.mdx b/lexicons/k8s/docs/src/content/docs/argo-composites.mdx index 482b7940d..1ad92ae11 100644 --- a/lexicons/k8s/docs/src/content/docs/argo-composites.mdx +++ b/lexicons/k8s/docs/src/content/docs/argo-composites.mdx @@ -1,6 +1,6 @@ --- title: "Argo CD Composites" -description: "Argo CD support in the k8s lexicon — the argoproj.io CRDs, ArgoAppFor / ArgoAppSetForRegions composites, cluster registration, the ARGO00x rules, and the Argo-vs-Temporal split." +description: "Argo CD support in the k8s lexicon — the argoproj.io CRDs, ArgoAppFor / ArgoAppSetForRegions composites, cluster registration, the ARGO00x rules, and how a deploy splits between Argo, an Op and CI." diataxis: reference --- @@ -16,14 +16,14 @@ Argo CD's `Application`, `ApplicationSet`, and `AppProject` are Kubernetes CRDs The k8s lexicon stays **runtime-agnostic** — it only emits manifests. Argo is **opt-in** via the composites below; nothing here is implied unless you reach for it. -## The three-layer model: Argo vs Temporal vs CI +## The three-layer model: Argo, an Op and CI Chant authors typed infra into manifests. From there, who applies them? | Layer | Owns | Reach for it when | |---|---|---| | **Argo CD** | Continuously reconciling declarative manifests | the desired state lives in git and *converges* — Deployments, CRs, Helm releases | -| **Temporal** | Procedural steps with ordering, signals, human gates, one-shot RPCs | the step is a *procedure*, not a state — DNS delegation, cert generation, `db init` | +| **A chant Op** | Procedural steps with ordering, human gates, one-shot RPCs | the step is a *procedure*, not a state — DNS delegation, cert generation, `db init` | | **CI** | One-shot, fire-and-forget apply | a simple pipeline with no reconciliation or long-running orchestration need | Rule of thumb: **if it's declarative and converges, let Argo reconcile it; if it's a procedure with ordering or gates, run it as a chant Op.** Prefer Argo CD over Argo *Workflows* — the procedural layer stays an Op. See the [CockroachDB multi-region tutorial](/chant/tutorials/cockroachdb-multi-region/) for a deployment that uses both. diff --git a/lexicons/k8s/docs/src/content/docs/crd-classes.mdx b/lexicons/k8s/docs/src/content/docs/crd-classes.mdx index f2cdec04d..764224330 100644 --- a/lexicons/k8s/docs/src/content/docs/crd-classes.mdx +++ b/lexicons/k8s/docs/src/content/docs/crd-classes.mdx @@ -425,7 +425,7 @@ export const apps = new Kustomization({ ``` The `FluxGitSource` and `FluxAppFor` composites collapse the `GitRepository` + `Kustomization` pair above into two calls with estate-tested defaults, and the FLUX001–003 [lint rules](/chant/lexicons/k8s/lint-rules/#flux) validate the source pin, `sourceRef`, and `dependsOn` edges. @@ -473,3 +473,22 @@ Guidelines: - **One URL per CRD**, or a single multi-doc bundle URL (the parser uses `loadAll`, as with cert-manager). - The group's first segment becomes the namespace. Add a `GROUP_NAMESPACE_OVERRIDES` entry in `crd/parser.ts` if the default mapping reads poorly. - Document the produced classes and the operator install command in a comment block next to the entry — match the existing entries. + +## A kind with no generated class: `k8sManifest` + +`k8sManifest` declares an object from its manifest, for a kind the lexicon ships no class for and you do not want to generate one for: + +```typescript +import { k8sManifest } from "@intentius/chant-lexicon-k8s"; + +export const widget = k8sManifest({ + apiVersion: "acme.io/v1", + kind: "Widget", + metadata: { name: "demo", namespace: "web" }, + spec: { size: 3 }, +}); +``` + +The props **are** the manifest: the serializer emits the document as written, plus the default-label and ownership merge every discovered resource gets. `apiVersion` and `kind` are required — they are what makes the object addressable — and they resolve the entity type through the same group rule a generated class uses, so `acme.io/v1 Widget` is `K8s::Acme::Widget` either way and `lifecycle diff --live` reads it through the same operation surface. + +What you give up is the whole point of a generated class: no typed constructor, no spec validation at lint time, no LSP hover. Prefer generating the CRD. Two callers use this deliberately — a [kustomize build root](/chant/lexicons/k8s/importing-yaml/), whose output is already finished documents, and `chant carve emit` adopting a Terraform `kubernetes_manifest`, whose kind is only known once the body is read. diff --git a/lexicons/k8s/docs/src/content/docs/flux-composites.mdx b/lexicons/k8s/docs/src/content/docs/flux-composites.mdx index f61214fb3..cf0e09bfe 100644 --- a/lexicons/k8s/docs/src/content/docs/flux-composites.mdx +++ b/lexicons/k8s/docs/src/content/docs/flux-composites.mdx @@ -95,5 +95,5 @@ It applies the Flux CRs through the same server-side apply `kubectl-apply` uses ## See also - [flux-apps tutorial](/chant/tutorials/flux-apps/) — a self-hosted on-ramp: k3s, Traefik `IngressRoute`, cert-manager, three Kustomizations with `dependsOn`. -- [Argo CD Composites](../argo-composites/) — the Argo counterpart, and the three-layer Argo-vs-Temporal split. +- [Argo CD Composites](../argo-composites/) — the Argo counterpart, and the three-layer split between Argo, an Op and CI. - The `chant-k8s-flux` [skill](../skills/) — agent guidance for these patterns. diff --git a/lexicons/k8s/docs/src/content/docs/index.mdx b/lexicons/k8s/docs/src/content/docs/index.mdx index d9008d465..e83bcaabd 100644 --- a/lexicons/k8s/docs/src/content/docs/index.mdx +++ b/lexicons/k8s/docs/src/content/docs/index.mdx @@ -68,7 +68,7 @@ The lexicon provides **147 resource types** (Deployment, Service, ConfigMap, Sta | Pseudo-parameters | 0 | | Lint rules | 46 | -**Lexicon version:** 0.49.0 +**Lexicon version:** 0.57.0 **Namespace:** `K8s` - [All Rules](./rules) — 46 rules diff --git a/lexicons/k8s/src/api/sweep-types.ts b/lexicons/k8s/src/api/sweep-types.ts index 57a9702aa..b5ec8cacf 100644 --- a/lexicons/k8s/src/api/sweep-types.ts +++ b/lexicons/k8s/src/api/sweep-types.ts @@ -10,8 +10,7 @@ * * It lives in its own module, with no imports of its own, because both * consumers reach it from different directions — `../export-resources.ts` - * pulls in the whole import parser, and a Temporal worker loading the apply - * activity should not. + * pulls in the whole import parser, and the apply activity should not. */ export const DEFAULT_IMPORT_TYPES: readonly string[] = [ "K8s::Apps::Deployment", diff --git a/lexicons/k8s/src/config.ts b/lexicons/k8s/src/config.ts index 4e0a53cc2..fc3fafe94 100644 --- a/lexicons/k8s/src/config.ts +++ b/lexicons/k8s/src/config.ts @@ -2,8 +2,8 @@ * K8s environment → cluster binding — chant #1100. * * Every cloud lexicon binds an environment to a scope: AWS resolves `` - * to a CloudFormation stack, Azure treats `` as the resource group, - * Temporal looks up `temporal.profiles.`. Before this, k8s bound + * to a CloudFormation stack, Azure treats `` as the resource group. + * Before this, k8s bound * nothing — `describeResources` shelled out to `kubectl get` with no * `--context`, so `chant lifecycle diff prod --live` read whichever cluster * `kubectl config current-context` happened to point at. diff --git a/lexicons/k8s/src/deep-observe-hooks.ts b/lexicons/k8s/src/deep-observe-hooks.ts index dc811e4c9..b913d2957 100644 --- a/lexicons/k8s/src/deep-observe-hooks.ts +++ b/lexicons/k8s/src/deep-observe-hooks.ts @@ -61,7 +61,8 @@ import { GENERATED_ONCE_LABEL_KEY } from "./secret-labels"; * Kubernetes-defaulted fields, per entity type, as index-erased property * paths. Subtracted only where source never declared the property * (`side === "live" && counterpart === "absent"`) — cdk-real-drift's default - * subtraction, same convention as AWS/Azure/Temporal's tables. + * subtraction, same convention as the AWS and Azure tables + * (`AWS_SERVICE_DEFAULTS`, `AZURE_SERVICE_DEFAULTS`). * * Sparse and evidence-based rather than derived from the generated schema: * the k8s OpenAPI spec this lexicon's codegen consumes @@ -70,9 +71,8 @@ import { GENERATED_ONCE_LABEL_KEY } from "./secret-labels"; * actually expressible today. Widening this table is additive and needs no * contract change. * - * `spec.strategy` is listed whole, not as `spec.strategy.type`, for the same - * reason Temporal's `TEMPORAL_SCHEDULE_DEFAULTS` lists `state` whole: pruning - * only the leaf would still recurse into the object, and a nested default the + * `spec.strategy` is listed whole rather than as `spec.strategy.type`, because + * pruning only the leaf would still recurse into the object, and a nested default the * table does not separately name (`rollingUpdate.maxSurge`/`maxUnavailable`, * both `"25%"` when `spec.strategy` is omitted entirely) would leave behind an * empty `strategy: {}` — a value distinct from no `strategy` key at all, and diff --git a/lexicons/k8s/src/deep-observe.ts b/lexicons/k8s/src/deep-observe.ts index 29a6c6fa3..e5c112705 100644 --- a/lexicons/k8s/src/deep-observe.ts +++ b/lexicons/k8s/src/deep-observe.ts @@ -9,10 +9,10 @@ * * ## What managedFields decides, and what it does not * - * AWS, Azure and Temporal's rows all prune by a **static, entityType-keyed** - * table: an ARN always looks like an ARN, `provisioningState` is always - * server-populated, a namespace's retention default is always the same - * value. None of that needs the specific live object in hand — it is exactly + * The AWS and Azure rows both prune by a **static, entityType-keyed** table + * (`AWS_SERVICE_DEFAULTS`, `AZURE_SERVICE_DEFAULTS`): an ARN always looks like + * an ARN, `provisioningState` is always server-populated. Neither needs the + * specific live object in hand — it is exactly * what `./deep-observe-hooks.ts`'s `k8sDeepNormalizationHooks` is, and it * covers Kubernetes' *equivalent* static noise (`status`, * `metadata.{uid,resourceVersion,generation,creationTimestamp}`, a handful of @@ -121,7 +121,7 @@ import { k8sDeepNormalizationHooks } from "./deep-observe-hooks"; // Re-exported so a dynamic importer of this module (plugin.ts's // `observeResourcesDeep`, a test) can get the reader and its hooks from one -// place, the same shape AWS/Azure/Temporal's single deep-observe.ts offers. +// place, the same shape the AWS and Azure deep-observe.ts modules offer. // `plugin.ts`'s own `deepNormalizationHooks` field imports the hooks // separately, directly from `./deep-observe-hooks` — that file has no // dependency on `@intentius/chant-k8s-client`, so it is safe to import diff --git a/lexicons/k8s/src/export-resources.ts b/lexicons/k8s/src/export-resources.ts index a9102a486..0f5093f0a 100644 --- a/lexicons/k8s/src/export-resources.ts +++ b/lexicons/k8s/src/export-resources.ts @@ -28,7 +28,7 @@ import { DEFAULT_IMPORT_TYPES } from "./api/sweep-types"; * reachable by naming it with `--selector type=...`. Defined in * `./api/sweep-types.ts` and re-exported here, its original home — chant * #1075's ownership-scoped prune needs the same list without pulling the - * import parser into a Temporal worker. + * import parser in behind the apply activity. */ export { DEFAULT_IMPORT_TYPES }; diff --git a/lexicons/k8s/src/op/activities/argo.test.ts b/lexicons/k8s/src/op/activities/argo.test.ts index d7e40889b..799cd7734 100644 --- a/lexicons/k8s/src/op/activities/argo.test.ts +++ b/lexicons/k8s/src/op/activities/argo.test.ts @@ -5,8 +5,8 @@ import { type ArgoAppStatus, type ArgoStatusFetcher, } from "./argo"; -// Activity profiles live centrally in the temporal lexicon (loadProfiles reads -// them there); argoSync marks ArgoSyncFailedError non-retryable for this activity. +// Activity profiles live centrally in core (loadProfiles serves this table); +// argoSync marks ArgoSyncFailedError non-retryable for this activity. import { ACTIVITY_PROFILES } from "@intentius/chant/op/activity-profiles"; /** A fetcher that returns a scripted sequence of statuses, repeating the last. */ @@ -66,7 +66,7 @@ describe("waitForArgoSync", () => { }); describe("argoSync profile", () => { - test("is exported with a long timeout and 60s heartbeat", () => { + test("is exported with a long timeout", () => { const p = ACTIVITY_PROFILES.argoSync; expect(p.timeout).toBe("30m"); }); diff --git a/lexicons/k8s/src/op/activities/argo.ts b/lexicons/k8s/src/op/activities/argo.ts index cad24e64e..ada70a34f 100644 --- a/lexicons/k8s/src/op/activities/argo.ts +++ b/lexicons/k8s/src/op/activities/argo.ts @@ -16,8 +16,8 @@ const execAsync = promisify(exec); * `ResourceFetcher` for `waitForReady`. * * It stays **dependency-light** — primitives-only signature (app name / - * namespace / server), no generated Argo CRD types — so a Temporal worker loads - * it cheaply. + * namespace / server), no generated Argo CRD types — so importing the lexicon's + * activity module at run time stays cheap. */ export interface WaitForArgoSyncArgs { @@ -36,7 +36,7 @@ export interface WaitForArgoSyncArgs { insecure?: boolean; /** kubectl context (used when `server` is not set). */ context?: string; - /** Poll interval in ms (default 15000). Heartbeats every poll. */ + /** Poll interval in ms (default 15000). */ intervalMs?: number; } @@ -113,7 +113,7 @@ export const defaultArgoStatusFetcher: ArgoStatusFetcher = (args, signal) => * `ArgoSyncFailedError` if it reaches a terminal unhealthy state (Degraded / * Missing). * - * Delegates the poll loop, heartbeat, and ready/terminal evaluation to the + * Delegates the poll loop and the ready/terminal evaluation to the * generic `waitForReady` using the shared `argoproj.io/Application` readiness * spec. The Argo `ArgoStatusFetcher` is adapted into a `ResourceFetcher` that * shapes `{health, sync}` into the `status.health.status` / `status.sync.status` diff --git a/lexicons/k8s/src/op/activities/index.ts b/lexicons/k8s/src/op/activities/index.ts index b95ab834c..a5be7e36e 100644 --- a/lexicons/k8s/src/op/activities/index.ts +++ b/lexicons/k8s/src/op/activities/index.ts @@ -1,21 +1,23 @@ /** * k8s Op activities — resolved by the core activity registry when a project's - * `chant.config.ts` lists the `k8s` lexicon. Relocated from the temporal lexicon + * `chant.config.ts` lists the `k8s` lexicon. Relocated from the hosting lexicon * (#809) so Kubernetes-facing imperative activities live with their product: * - kubectlApply — server-side apply a rendered manifest (and, since chant * #1075, prune chant-owned objects it no longer declares; `applyManifest` - * is the same work with a report of what it did, which is what the - * Temporal lexicon's `nativeApply` dispatcher calls for a kubectl target) + * is the same work with a report of what it did, which is what core's + * `nativeApply` dispatcher calls for a kubectl target) * - waitForArgoSync — block until an Argo CD Application is Healthy && Synced * * k3dUp / k3dDown moved again, to the k3d lexicon (chant #1410) — a lexicon * owns its own product's activities, and k3d is its own product now. Projects * using them list `k3d` in `lexicons`; loadActivities(["k3d"]) provides them. * - * The step builders (kubectlApply, k3dUp, k3dDown) stay in core, re-exported from - * the temporal Op-authoring barrel like the other core builders. Each activity is - * dependency-light — it shells out to a CLI and does not import the k8s declarable - * surface — so a Temporal worker loads it cheaply. + * The step builders (kubectlApply, k3dUp, k3dDown) stay in core and reach + * authors through `@intentius/chant/op` like the other core builders. Each + * activity is dependency-light — it shells out to a CLI and does not import the + * k8s declarable surface — so `loadActivities` + * (`packages/core/src/op/activity-registry.ts`), which imports this module at + * run time, pulls in nothing expensive. */ export { kubectlApply, applyManifest, readManifestDocuments } from "./kubectl"; export type { KubectlApplyArgs, ApplyManifestResult, AppliedRef, ApplyDeleteMode } from "./kubectl"; diff --git a/lexicons/k8s/src/op/activities/kubectl.test.ts b/lexicons/k8s/src/op/activities/kubectl.test.ts index 44957df8e..80c74a49a 100644 --- a/lexicons/k8s/src/op/activities/kubectl.test.ts +++ b/lexicons/k8s/src/op/activities/kubectl.test.ts @@ -1,10 +1,11 @@ /** * `kubectlApply` over the typed API client (chant #1074, #1075). * - * The activity contract is what Temporal workers register, so the shape of the - * arguments and the `Promise` return are asserted alongside the new - * behavior. Nothing here spawns a process or reads an ambient kubeconfig, - * which is the acceptance criterion: a worker image needs no `kubectl` binary. + * The activity contract is what an Op step names and core's registry resolves, + * so the shape of the arguments and the `Promise` return are asserted + * alongside the new behavior. Nothing here spawns a process or reads an ambient + * kubeconfig, which is the acceptance criterion: the image a run happens in + * needs no `kubectl` binary. * * chant #1075 adds two things to assert: that the field manager is derived * from the project's `ownership.stack` rather than hardcoded, and that the diff --git a/lexicons/k8s/src/op/activities/kubectl.ts b/lexicons/k8s/src/op/activities/kubectl.ts index f1268834c..720c469a2 100644 --- a/lexicons/k8s/src/op/activities/kubectl.ts +++ b/lexicons/k8s/src/op/activities/kubectl.ts @@ -3,9 +3,9 @@ * * chant #1074 moved this off `kubectl apply -f`. The activity contract is * unchanged (a manifest path, an optional context, `Promise`, the - * `longInfra` profile's 15s heartbeat) because Temporal workers register it by - * that signature; what changed is underneath. The name is kept for the same - * reason. + * `longInfra` profile) because an Op step names the activity by its export + * name and core's registry resolves it there; what changed is underneath. The + * name is kept for the same reason. * * chant #1075 finished the job on two axes: * @@ -58,7 +58,7 @@ export type ApplyDeleteMode = "never" | "owned-only" | "gated"; export interface KubectlApplyArgs { /** * Path to a manifest file, or a directory of them. With `documents` given, - * this becomes only the human-facing label the heartbeats and logs carry + * this becomes only the human-facing label the step's log lines carry * (e.g. `kustomize:`), and nothing is read from disk. */ manifest: string; @@ -377,7 +377,7 @@ export async function applyManifest( /** * Apply every document in `args.manifest`. - * Uses longInfra profile — 20m timeout, heartbeat every 15s. + * Uses the longInfra profile: 20m timeout, three attempts backing off from 30s. */ export async function kubectlApply( args: KubectlApplyArgs, diff --git a/lexicons/k8s/src/op/activities/wait-for-ready.ts b/lexicons/k8s/src/op/activities/wait-for-ready.ts index d860a8235..b4cc7aeb8 100644 --- a/lexicons/k8s/src/op/activities/wait-for-ready.ts +++ b/lexicons/k8s/src/op/activities/wait-for-ready.ts @@ -6,8 +6,8 @@ import { defaultK8sConnector, type K8sConnector } from "../../api/connect"; * ready, driven by a data-only **readiness spec** rather than per-CRD code. * * Like `waitForArgoSync`, this activity is intentionally **dependency-light**: - * its signature is primitives + a plain readiness spec, so a Temporal worker - * loads it without importing the generated CRD declarable surface. It reads the + * its signature is primitives + a plain readiness spec, so the activity module + * loads without pulling in the generated CRD declarable surface. It reads the * resource and evaluates the spec's predicates. It generalizes the bespoke * `waitForArgoSync` / `waitForStack` waits — see #365. * @@ -271,7 +271,7 @@ export interface WaitForReadyArgs { group?: string; /** Explicit readiness spec — wins over the registry/default. */ spec?: ReadinessSpec; - /** Poll interval in ms (default 15000). Heartbeats every poll. */ + /** Poll interval in ms (default 15000). */ intervalMs?: number; } @@ -335,8 +335,9 @@ export const defaultResourceFetcher: ResourceFetcher = (args, signal) => apiReso /** * Poll until the resource satisfies its readiness spec. Throws - * `ReadinessFailedError` on a terminal state. Heartbeats every poll so the - * `k8sWait` profile's 60s heartbeat timeout never trips. + * `ReadinessFailedError` on a terminal state, which the `k8sWait` profile lists + * as non-retryable, so a resource that will never become ready fails on the + * first attempt instead of burning the profile's 15m timeout three times. * * @param fetcher injectable reader (defaults to kubectl). Tests pass a fake to * drive not-ready → ready / terminal transitions. diff --git a/lexicons/k8s/src/plugin.ts b/lexicons/k8s/src/plugin.ts index 6d7aea2a6..6edffa9e9 100644 --- a/lexicons/k8s/src/plugin.ts +++ b/lexicons/k8s/src/plugin.ts @@ -632,7 +632,7 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService { file: "chant-k8s-argo.md", name: "chant-k8s-argo", - description: "Argo CD composites — ArgoAppFor, ArgoAppSetForRegions, AppProject scoping, cluster registration, and the Argo-vs-Temporal split", + description: "Argo CD composites — ArgoAppFor, ArgoAppSetForRegions, AppProject scoping, cluster registration, and how a deploy splits between Argo and a chant Op", triggers: [ { type: "context", value: "argo" }, { type: "context", value: "argo cd" }, diff --git a/lexicons/k8s/src/skills/chant-k8s-argo.md b/lexicons/k8s/src/skills/chant-k8s-argo.md index e368cfc3a..049843d66 100644 --- a/lexicons/k8s/src/skills/chant-k8s-argo.md +++ b/lexicons/k8s/src/skills/chant-k8s-argo.md @@ -1,6 +1,6 @@ --- skill: chant-k8s-argo -description: Argo CD composites for GitOps reconciliation — ArgoAppFor, ArgoAppSetForRegions, AppProject scoping, cluster registration, and the Argo-vs-Temporal split +description: Argo CD composites for GitOps reconciliation — ArgoAppFor, ArgoAppSetForRegions, AppProject scoping, cluster registration, and how a deploy splits between Argo and a chant Op user-invocable: true --- @@ -14,9 +14,9 @@ Chant authors typed infrastructure into manifests. Argo CD continuously reconcil |---|---|---| | **Chant** | Authoring typed infra → manifests | the lexicons | | **Argo CD** | Continuously reconciling declarative manifests (the apply layer) | `ArgoAppFor` / `ArgoAppSetForRegions` | -| **Temporal** | Procedural steps Argo can't express — ordering, signals, human gates, one-shot RPCs | the temporal lexicon + `waitForArgoSync` | +| **A chant Op** | Procedural steps Argo can't express: ordering, human gates, one-shot RPCs | an `Op` in the project, plus this lexicon's `waitForArgoSync` | -Rule of thumb: **if it's declarative and converges, let Argo reconcile it. If it's a procedure with ordering, gates, or out-of-band steps, orchestrate it in Temporal.** Prefer Argo CD over Argo Workflows — the procedural layer stays Temporal. +Rule of thumb: **if it's declarative and converges, let Argo reconcile it. If it's a procedure with ordering, gates, or out-of-band steps, write it as a chant Op and run it from CI or a steward.** Prefer Argo CD over Argo Workflows; the procedural layer stays an Op. ## Prerequisites @@ -138,9 +138,9 @@ Produces a `Secret` labelled `argocd.argoproj.io/secret-type: cluster`. After th --- -## The Argo-vs-Temporal split +## Splitting a deploy between Argo and an Op -When a deploy has both declarative and procedural parts, let each layer own what it's good at. Example — the multi-region CockroachDB deploy: +When a deploy has both declarative and procedural parts, let each layer own what it's good at. Example, the multi-region CockroachDB deploy: | Step | Owner | Why | |---|---|---| @@ -148,16 +148,30 @@ When a deploy has both declarative and procedural parts, let each layer own what | Install ESO / operators (Helm) | **Argo** | Declarative Helm source | | Apply per-cluster K8s manifests | **Argo** (`ApplicationSet`) | One App per workload cluster | | Wait for workloads Healthy | **Argo** (`Health=Healthy`) | Subsumed by Application health | -| Wait for DNS delegation | **Temporal** | Signal/update/auto-poll race — out of band | -| Generate + push TLS certs | **Temporal** | One-shot procedure, secrets not in git | -| `cockroach init`, configure regions | **Temporal** | Ordered one-shot RPCs | - -From a Temporal workflow, gate procedural steps on Argo finishing a declarative apply with the `waitForArgoSync` activity (temporal lexicon, `argoSync` profile): +| Wait for DNS delegation | **an Op** | Out of band, and a human confirms it | +| Generate + push TLS certs | **an Op** | One-shot procedure, secrets not in git | +| `cockroach init`, configure regions | **an Op** | Ordered one-shot RPCs | + +Argo owns the sync. The Op owns the ordering and the gates: its phases run in +sequence in one process (`packages/core/src/op/local-executor.ts`), and a `gate` +step reads the gate ledger, so a run that reaches a gate nobody has approved +records the pending fact, ends with status `gated` and exits 3. Someone runs +`chant approve `, the next run reads the resolution and walks +through. CI is what runs the Op, on whatever cadence the Op's `schedule` +names. + +To make a step wait on Argo, use this lexicon's `waitForArgoSync` activity. It is +exported from `lexicons/k8s/src/op/activities/index.ts`, and the core activity +registry resolves it by export name once `k8s` is in the project's `lexicons`. +Give the step core's `argoSync` profile +(`packages/core/src/op/activity-profiles.ts`): a 30m timeout, five attempts +backing off from 10s, and `ArgoSyncFailedError` marked non-retryable so a +terminally unhealthy Application fails fast instead of polling to the cap. ```typescript -// In a Temporal Op workflow: -await waitForArgoSync({ appName: "east-crdb", namespace: "argocd" }); -// ...now run the procedural steps that depend on the workloads being Healthy. +// In an Op phase: +activity("waitForArgoSync", { appName: "east-crdb", namespace: "argocd" }, "argoSync"), +// Later steps in the phase run once the workloads are Healthy. ``` `waitForArgoSync` is dependency-free — it polls the Application's status (`health=Healthy && sync=Synced`) and never imports the Argo CRD types. diff --git a/lexicons/terraform/docs/pages/ops.mdx b/lexicons/terraform/docs/pages/ops.mdx index 4e410da1e..42bacbc03 100644 --- a/lexicons/terraform/docs/pages/ops.mdx +++ b/lexicons/terraform/docs/pages/ops.mdx @@ -46,7 +46,7 @@ export const { op } = TerraformApplyOp({ | Option | Default | Notes | |---|---|---| -| `name` | — required | Op name (kebab-case). Also the default task queue and gate signal suffix. | +| `name` | — required | Op name (kebab-case). `signalName` defaults to `approve-`. | | `root` | — required | Key into `terraform.roots`. | | `planFile` | `chant.tfplan` | Written by Plan, consumed by Apply — relative to the root dir. | | `gate` | `"on-destroy"` | `"on-destroy"` and `"always"` build the identical four-phase shape (`GateStep` carries no condition to branch a plan's destroy count at build time — see below); `"never"` drops the Gate phase entirely. | @@ -56,7 +56,6 @@ export const { op } = TerraformApplyOp({ | `upgrade` | `false` | `-upgrade` on the Init step. | | `cwd` | the process's cwd | Directory each step starts its `chant.config.*` search from. | | `compensate` | unset | Saga-style rollback on a failed apply — see below. | -| `taskQueue` | `name` | Override the task queue. | ### The gate @@ -173,7 +172,7 @@ export const { op } = TerraformAdoptOp({ name: "estate-adopt", root: "estate" }) | Option | Default | Notes | |---|---|---| -| `name` | — required | Op name (kebab-case). Also the default task queue and gate signal suffix. | +| `name` | — required | Op name (kebab-case). `signalName` defaults to `approve-`. | | `root` | — required | Key into `terraform.roots`. Must be a live root. | | `estate` | auto-detected | The estate whose markers to look for; normally the root's own `live` block or sidecar answers this. | | `signalName` | `approve-` | Gate signal name. | @@ -181,7 +180,6 @@ export const { op } = TerraformAdoptOp({ name: "estate-adopt", root: "estate" }) | `gateDescription` | a generated description naming the ledger | Override text shown to the approver. | | `cwd` | the process's cwd | Directory each step starts its `chant.config.*` search from. | | `compensate` | unset | Refused without a command. See below. | -| `taskQueue` | `name` | Override the task queue. | ### The adopt mechanism diff --git a/lexicons/terraform/docs/src/content/docs/ops.mdx b/lexicons/terraform/docs/src/content/docs/ops.mdx index 6bec44eea..ae60f2830 100644 --- a/lexicons/terraform/docs/src/content/docs/ops.mdx +++ b/lexicons/terraform/docs/src/content/docs/ops.mdx @@ -50,7 +50,7 @@ export const { op } = TerraformApplyOp({ | Option | Default | Notes | |---|---|---| -| `name` | — required | Op name (kebab-case). Also the default task queue and gate signal suffix. | +| `name` | — required | Op name (kebab-case). `signalName` defaults to `approve-`. | | `root` | — required | Key into `terraform.roots`. | | `planFile` | `chant.tfplan` | Written by Plan, consumed by Apply — relative to the root dir. | | `gate` | `"on-destroy"` | `"on-destroy"` and `"always"` build the identical four-phase shape (`GateStep` carries no condition to branch a plan's destroy count at build time — see below); `"never"` drops the Gate phase entirely. | @@ -60,7 +60,6 @@ export const { op } = TerraformApplyOp({ | `upgrade` | `false` | `-upgrade` on the Init step. | | `cwd` | the process's cwd | Directory each step starts its `chant.config.*` search from. | | `compensate` | unset | Saga-style rollback on a failed apply — see below. | -| `taskQueue` | `name` | Override the task queue. | ### The gate @@ -177,7 +176,7 @@ export const { op } = TerraformAdoptOp({ name: "estate-adopt", root: "estate" }) | Option | Default | Notes | |---|---|---| -| `name` | — required | Op name (kebab-case). Also the default task queue and gate signal suffix. | +| `name` | — required | Op name (kebab-case). `signalName` defaults to `approve-`. | | `root` | — required | Key into `terraform.roots`. Must be a live root. | | `estate` | auto-detected | The estate whose markers to look for; normally the root's own `live` block or sidecar answers this. | | `signalName` | `approve-` | Gate signal name. | @@ -185,7 +184,6 @@ export const { op } = TerraformAdoptOp({ name: "estate-adopt", root: "estate" }) | `gateDescription` | a generated description naming the ledger | Override text shown to the approver. | | `cwd` | the process's cwd | Directory each step starts its `chant.config.*` search from. | | `compensate` | unset | Refused without a command. See below. | -| `taskQueue` | `name` | Override the task queue. | ### The adopt mechanism diff --git a/lexicons/terraform/src/composites/terraform-adopt-op.ts b/lexicons/terraform/src/composites/terraform-adopt-op.ts index 84fa8f939..08e9e4a15 100644 --- a/lexicons/terraform/src/composites/terraform-adopt-op.ts +++ b/lexicons/terraform/src/composites/terraform-adopt-op.ts @@ -89,7 +89,7 @@ import { } from "../op/builders"; export interface TerraformAdoptOpConfig { - /** Op name (kebab-case). Also the default task queue and gate signal suffix. */ + /** Op name (kebab-case). `signalName` defaults to `approve-`. */ name: string; /** Key into the project's `terraform.roots`. Must be a live root: choudoufu, with a declared estate. */ root: string; diff --git a/lexicons/terraform/src/composites/terraform-apply-op.ts b/lexicons/terraform/src/composites/terraform-apply-op.ts index fbd7e1aea..e18ba30f9 100644 --- a/lexicons/terraform/src/composites/terraform-apply-op.ts +++ b/lexicons/terraform/src/composites/terraform-apply-op.ts @@ -96,7 +96,7 @@ import { export type TerraformGateMode = "on-destroy" | "always" | "never"; export interface TerraformApplyOpConfig { - /** Op name (kebab-case). Also the default task queue and gate signal suffix. */ + /** Op name (kebab-case). `signalName` defaults to `approve-`. */ name: string; /** Key into the project's `terraform.roots`. The root carries dir, workspace, var files and backend config. */ root: string; diff --git a/lexicons/terraform/src/lsp/completions.test.ts b/lexicons/terraform/src/lsp/completions.test.ts index 73453938f..5677d518e 100644 --- a/lexicons/terraform/src/lsp/completions.test.ts +++ b/lexicons/terraform/src/lsp/completions.test.ts @@ -91,7 +91,8 @@ describe("LSP completions", () => { const labels = items.map((i) => i.label); expect(labels).toContain("gate"); expect(labels).toContain("compensate"); - expect(labels).toContain("taskQueue"); + expect(labels).toContain("gateTimeout"); + expect(labels).not.toContain("taskQueue"); }); it("completes a builder's own opts keys inside its second argument", () => { diff --git a/lexicons/terraform/src/lsp/option-keys.ts b/lexicons/terraform/src/lsp/option-keys.ts index d6c868ede..d74a7498a 100644 --- a/lexicons/terraform/src/lsp/option-keys.ts +++ b/lexicons/terraform/src/lsp/option-keys.ts @@ -30,7 +30,7 @@ export const ROOT_ENTRY_KEYS: OptionKey[] = [ /** `TerraformApplyOpConfig`'s keys (`TerraformApplyOp({ })`). */ export const APPLY_OP_KEYS: OptionKey[] = [ - { key: "name", detail: "Op name (kebab-case). Also the default task queue and gate signal suffix." }, + { key: "name", detail: "Op name (kebab-case). `signalName` defaults to `approve-`." }, { key: "root", detail: "Key into the project's `terraform.roots`." }, { key: "planFile", detail: "Plan file written by Plan and consumed by Apply. Default: `chant.tfplan`." }, { key: "gate", detail: '"on-destroy" | "always" | "never" — when to emit the approval gate. Default: "on-destroy".' }, @@ -40,7 +40,6 @@ export const APPLY_OP_KEYS: OptionKey[] = [ { key: "upgrade", detail: "`-upgrade` on the Init step: re-resolve provider and module versions." }, { key: "cwd", detail: "Directory each step starts the `chant.config.*` search from." }, { key: "compensate", detail: "Saga-style rollback on a failed apply: `true` with no command throws; supply `{ command }`." }, - { key: "taskQueue", detail: "Override the task queue. Defaults to `name`." }, ]; /** `compensate`'s own shape when it is an object: `compensate: { }`. */ From 6fc2a1149e9f6820bcf49440d5cf0f6d11816c24 Mon Sep 17 00:00:00 2001 From: lex00 <121451605+lex00@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:42:11 -0600 Subject: [PATCH 2/2] docs(lexicons): keep the twin regeneration to the pages this PR edits The six twins the previous commit touched without editing their sources (aws index.mdx and intrinsics.mdx, cedar/github/gitlab/k8s index.mdx) are restored to main. Their regeneration was not faithful: the generator reads each lexicon's local `dist/manifest.json`, and those manifests are stale relative to source in this checkout. `lexicons/aws/src/intrinsics.ts` defines Fn::Equals, Fn::And, Fn::Or and Fn::Not (#2068), so thirteen intrinsics, while `lexicons/aws/dist/manifest.json` is version 0.56.0 and lists nine; `lexicons/github/dist/manifest.json` is dated Aug 25 at version 0.46.0 and lists one intrinsic where the committed twin says nineteen. CI runs prepack fresh, so what those pages say after a real regeneration cannot be settled from here. Every remaining twin change now pairs with a `docs/pages/` change of the same basename, and every edited source has its twin regenerated: eleven pages each way, no page on one side without the other. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0148a4dPTErCBpmNy7J7933f --- lexicons/aws/docs/src/content/docs/index.mdx | 6 +++--- lexicons/aws/docs/src/content/docs/intrinsics.mdx | 6 +++++- lexicons/cedar/docs/src/content/docs/index.mdx | 2 +- lexicons/github/docs/src/content/docs/index.mdx | 4 ++-- lexicons/gitlab/docs/src/content/docs/index.mdx | 2 +- lexicons/k8s/docs/src/content/docs/index.mdx | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lexicons/aws/docs/src/content/docs/index.mdx b/lexicons/aws/docs/src/content/docs/index.mdx index bb86fadf2..87ef536bc 100644 --- a/lexicons/aws/docs/src/content/docs/index.mdx +++ b/lexicons/aws/docs/src/content/docs/index.mdx @@ -23,13 +23,13 @@ npm install --save-dev @intentius/chant-lexicon-aws | Resources | 1663 | | Property types | 9098 | | Services | 286 | -| Intrinsic functions | 9 | +| Intrinsic functions | 13 | | Pseudo-parameters | 8 | | Lint rules | 64 | -**Lexicon version:** 0.57.0 +**Lexicon version:** 0.56.0 **Namespace:** `AWS` -- [Intrinsic Functions](./intrinsics) — 9 built-in functions +- [Intrinsic Functions](./intrinsics) — 13 built-in functions - [All Rules](./rules) — 64 rules - [Serialization](./serialization) — output format details diff --git a/lexicons/aws/docs/src/content/docs/intrinsics.mdx b/lexicons/aws/docs/src/content/docs/intrinsics.mdx index d356fa87d..ec94f7f3e 100644 --- a/lexicons/aws/docs/src/content/docs/intrinsics.mdx +++ b/lexicons/aws/docs/src/content/docs/intrinsics.mdx @@ -6,7 +6,7 @@ description: "Built-in intrinsic functions for the AWS CloudFormation lexicon" {/* GENERATED-BY-CHANT-DOCS by `npm run docs -w @intentius/chant-lexicon-aws` — DO NOT EDIT. Edit lexicons/aws/src/codegen/docs.ts instead; changes here are overwritten. */} -The AWS CloudFormation lexicon provides **9** intrinsic functions. +The AWS CloudFormation lexicon provides **13** intrinsic functions. **Tag?** shows how an intrinsic is authored: a genuine tagged template (`Sub\`...\``) or a plain function call (`Ref(...)`). **Folds?** shows whether [folding](/chant/concepts/typescript-as-data/#folded-vs-run) — the default `chant build` path — can reduce a use of this intrinsic today, without running the file — generated directly from this lexicon's registration, not restated by hand. A tagged template folds once it is registered. A plain call folds only where this lexicon has opted that intrinsic in one at a time (`foldsAsCall`), so a `No` in this column means this intrinsic has not been opted in, not that calls in general cannot fold. @@ -21,3 +21,7 @@ The AWS CloudFormation lexicon provides **9** intrinsic functions. | `Split` | Fn::Split — split string by delimiter | `Fn::Split` | No | Yes | | `Base64` | Fn::Base64 — encode to Base64 | `Fn::Base64` | No | Yes | | `GetAZs` | Fn::GetAZs — list Availability Zones | `Fn::GetAZs` | No | Yes | +| `Equals` | Fn::Equals — condition comparing two values | `Fn::Equals` | No | Yes | +| `And` | Fn::And — condition conjunction | `Fn::And` | No | Yes | +| `Or` | Fn::Or — condition disjunction | `Fn::Or` | No | Yes | +| `Not` | Fn::Not — condition negation | `Fn::Not` | No | Yes | diff --git a/lexicons/cedar/docs/src/content/docs/index.mdx b/lexicons/cedar/docs/src/content/docs/index.mdx index 7c4fd3d78..aff20fe0b 100644 --- a/lexicons/cedar/docs/src/content/docs/index.mdx +++ b/lexicons/cedar/docs/src/content/docs/index.mdx @@ -67,7 +67,7 @@ honest about upstream's governance before it shows you a builder. | Pseudo-parameters | 0 | | Lint rules | 18 | -**Lexicon version:** 0.57.0 +**Lexicon version:** 0.49.0 **Namespace:** `Cedar` - [All Rules](./rules) — 18 rules diff --git a/lexicons/github/docs/src/content/docs/index.mdx b/lexicons/github/docs/src/content/docs/index.mdx index 7c8bfe9ad..2a27c0111 100644 --- a/lexicons/github/docs/src/content/docs/index.mdx +++ b/lexicons/github/docs/src/content/docs/index.mdx @@ -53,11 +53,11 @@ The lexicon provides **3 resources** (Workflow, Job, Dependabot config), **15 co | Resources | 3 | | Property types | 19 | | Services | 1 | -| Intrinsic functions | 1 | +| Intrinsic functions | 19 | | Pseudo-parameters | 0 | | Lint rules | 68 | -**Lexicon version:** 0.57.0 +**Lexicon version:** 0.51.0 **Namespace:** `GitHub` - [All Rules](./rules) — 68 rules diff --git a/lexicons/gitlab/docs/src/content/docs/index.mdx b/lexicons/gitlab/docs/src/content/docs/index.mdx index a5b5ce6e3..7e2de67d4 100644 --- a/lexicons/gitlab/docs/src/content/docs/index.mdx +++ b/lexicons/gitlab/docs/src/content/docs/index.mdx @@ -47,7 +47,7 @@ The lexicon provides **3 resources** (Job, Workflow, Default), **16 property typ | Pseudo-parameters | 0 | | Lint rules | 45 | -**Lexicon version:** 0.57.0 +**Lexicon version:** 0.49.0 **Namespace:** `GitLab` - [Intrinsic Functions](./intrinsics) — 1 built-in functions diff --git a/lexicons/k8s/docs/src/content/docs/index.mdx b/lexicons/k8s/docs/src/content/docs/index.mdx index e83bcaabd..d9008d465 100644 --- a/lexicons/k8s/docs/src/content/docs/index.mdx +++ b/lexicons/k8s/docs/src/content/docs/index.mdx @@ -68,7 +68,7 @@ The lexicon provides **147 resource types** (Deployment, Service, ConfigMap, Sta | Pseudo-parameters | 0 | | Lint rules | 46 | -**Lexicon version:** 0.57.0 +**Lexicon version:** 0.49.0 **Namespace:** `K8s` - [All Rules](./rules) — 46 rules