@intentius/chant-lexicon-temporal 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/integrity.json +15 -0
  2. package/dist/manifest.json +8 -0
  3. package/dist/meta.json +26 -0
  4. package/dist/rules/tmp001.ts +53 -0
  5. package/dist/rules/tmp002.ts +45 -0
  6. package/dist/rules/tmp010-cron-syntax.ts +64 -0
  7. package/dist/rules/tmp011-namespace-reference.ts +49 -0
  8. package/dist/skills/chant-temporal-ops.md +184 -0
  9. package/dist/skills/chant-temporal.md +201 -0
  10. package/dist/types/index.d.ts +3 -0
  11. package/package.json +32 -0
  12. package/src/codegen/docs-cli.ts +7 -0
  13. package/src/codegen/docs.ts +23 -0
  14. package/src/codegen/generate-cli.ts +17 -0
  15. package/src/codegen/generate.ts +82 -0
  16. package/src/codegen/package-cli.ts +14 -0
  17. package/src/codegen/package.ts +55 -0
  18. package/src/composites/cloud-stack.ts +74 -0
  19. package/src/composites/composites.test.ts +131 -0
  20. package/src/composites/dev-stack.ts +81 -0
  21. package/src/config.ts +150 -0
  22. package/src/coverage.test.ts +9 -0
  23. package/src/coverage.ts +37 -0
  24. package/src/example.test.ts +59 -0
  25. package/src/generated/lexicon-temporal.json +26 -0
  26. package/src/index.ts +29 -0
  27. package/src/lint/post-synth/post-synth.test.ts +152 -0
  28. package/src/lint/post-synth/tmp010-cron-syntax.ts +64 -0
  29. package/src/lint/post-synth/tmp011-namespace-reference.ts +49 -0
  30. package/src/lint/rules/index.ts +2 -0
  31. package/src/lint/rules/lint-rules.test.ts +150 -0
  32. package/src/lint/rules/tmp001.ts +53 -0
  33. package/src/lint/rules/tmp002.ts +45 -0
  34. package/src/plugin.test.ts +97 -0
  35. package/src/plugin.ts +286 -0
  36. package/src/resources.ts +121 -0
  37. package/src/serializer.test.ts +292 -0
  38. package/src/serializer.ts +310 -0
  39. package/src/skills/chant-temporal-ops.md +184 -0
  40. package/src/skills/chant-temporal.md +201 -0
  41. package/src/validate-cli.ts +7 -0
  42. package/src/validate.test.ts +9 -0
  43. package/src/validate.ts +92 -0
@@ -0,0 +1,184 @@
1
+ ---
2
+ skill: chant-temporal-ops
3
+ description: Signal workflows, diagnose stuck activities, reset checkpoints, and cancel runs via the Temporal CLI and chant run
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Temporal Operations Playbook
8
+
9
+ ## Signal a gate (unblock a paused workflow)
10
+
11
+ Workflows that use `setHandler` on a signal pause at gate activities waiting for a named signal. The `chant run signal` command (available in issue #8) forwards signals to the running workflow.
12
+
13
+ ```bash
14
+ # Via chant CLI (requires chant run — issue #8)
15
+ chant run signal <op-name> dnsConfigured
16
+
17
+ # Directly via temporal CLI
18
+ temporal workflow signal \
19
+ --workflow-id <workflow-id> \
20
+ --name dnsConfigured \
21
+ --namespace <namespace>
22
+ ```
23
+
24
+ List pending signals by querying the workflow:
25
+
26
+ ```bash
27
+ temporal workflow query \
28
+ --workflow-id <workflow-id> \
29
+ --type currentPhase \
30
+ --namespace <namespace>
31
+ ```
32
+
33
+ ## Check run status
34
+
35
+ ```bash
36
+ # Summary view
37
+ temporal workflow describe --workflow-id <id> --namespace <ns>
38
+
39
+ # Full event history
40
+ temporal workflow show --workflow-id <id> --namespace <ns>
41
+
42
+ # Filter by search attribute (requires registered custom attributes)
43
+ temporal workflow list \
44
+ --namespace <ns> \
45
+ --query 'GcpProject = "my-project"'
46
+ ```
47
+
48
+ ## Diagnose a stuck activity
49
+
50
+ Activities can be stuck for three distinct reasons:
51
+
52
+ | Symptom | Cause | Fix |
53
+ |---|---|---|
54
+ | Activity never started | `scheduleToStartTimeout` exceeded — no available workers | Start the worker: `chant run <op>` |
55
+ | Activity started but no heartbeats | `heartbeatTimeout` exceeded — worker crashed mid-activity | Bounce the worker; Temporal auto-retries |
56
+ | Activity running but slow | Normal — long-running activities with heartbeats | Wait, or check heartbeat details |
57
+
58
+ ### View activity timeout details
59
+
60
+ ```bash
61
+ temporal workflow show --workflow-id <id> --namespace <ns> | grep -A5 "ActivityTaskScheduled"
62
+ ```
63
+
64
+ ### Check worker connectivity
65
+
66
+ ```bash
67
+ # See which task queues have pollers
68
+ temporal task-queue describe --task-queue <queue> --namespace <ns>
69
+ ```
70
+
71
+ A task queue with `pollerCount: 0` means no workers are running.
72
+
73
+ ## Reset a workflow to a previous checkpoint
74
+
75
+ Use `workflow reset` to replay a workflow from a specific event, skipping failed activities:
76
+
77
+ ```bash
78
+ # Reset to just before the most recent failure
79
+ temporal workflow reset \
80
+ --workflow-id <id> \
81
+ --namespace <ns> \
82
+ --event-id <N> \
83
+ --reason "Retrying after infra fix"
84
+ ```
85
+
86
+ Find the event ID to reset to:
87
+
88
+ ```bash
89
+ # List events — find the last successful ActivityTaskCompleted before the failure
90
+ temporal workflow show --workflow-id <id> --namespace <ns> | grep -n "ActivityTask"
91
+ ```
92
+
93
+ Reset to the beginning of a named phase (requires workflow to record phase transitions as signals or markers):
94
+
95
+ ```bash
96
+ temporal workflow reset \
97
+ --workflow-id <id> \
98
+ --namespace <ns> \
99
+ --reapply-type None \
100
+ --type LastWorkflowTask
101
+ ```
102
+
103
+ ## Cancel a stuck or unwanted run
104
+
105
+ ```bash
106
+ # Graceful cancel — workflow receives CancellationError and can clean up
107
+ temporal workflow cancel \
108
+ --workflow-id <id> \
109
+ --namespace <ns>
110
+
111
+ # Forceful terminate — immediate stop, no cleanup
112
+ temporal workflow terminate \
113
+ --workflow-id <id> \
114
+ --namespace <ns> \
115
+ --reason "Terminated by operator"
116
+ ```
117
+
118
+ ## Pause and resume a schedule
119
+
120
+ ```bash
121
+ # Pause
122
+ temporal schedule pause \
123
+ --schedule-id <id> \
124
+ --namespace <ns> \
125
+ --note "Paused for maintenance"
126
+
127
+ # Resume
128
+ temporal schedule unpause \
129
+ --schedule-id <id> \
130
+ --namespace <ns>
131
+
132
+ # Trigger immediately (ignores spec)
133
+ temporal schedule trigger \
134
+ --schedule-id <id> \
135
+ --namespace <ns>
136
+ ```
137
+
138
+ ## Inspect workflow history for debugging
139
+
140
+ ```bash
141
+ # Show all events in JSON (machine-readable)
142
+ temporal workflow show \
143
+ --workflow-id <id> \
144
+ --namespace <ns> \
145
+ --output json
146
+
147
+ # Filter to activity failures only
148
+ temporal workflow show \
149
+ --workflow-id <id> \
150
+ --namespace <ns> \
151
+ --output json | jq '.[] | select(.eventType == "ActivityTaskFailed")'
152
+ ```
153
+
154
+ ## Common failure patterns
155
+
156
+ ### "no workers polling" after `chant run`
157
+
158
+ The worker started but cannot connect. Check:
159
+ 1. `TEMPORAL_ADDRESS` matches the server address
160
+ 2. `TEMPORAL_NAMESPACE` matches the namespace the workflow was started in
161
+ 3. TLS and API key config match (`tls: true` + `apiKey` for Temporal Cloud)
162
+
163
+ ### Activity retrying indefinitely
164
+
165
+ Default retry policy has `maximumAttempts: 0` (unlimited). If an activity is retrying unexpectedly:
166
+
167
+ ```bash
168
+ # Check the current attempt count and last failure
169
+ temporal workflow show --workflow-id <id> --namespace <ns> | grep "attempt\|failure"
170
+ ```
171
+
172
+ Add `maximumAttempts` to the activity's retry policy in the workflow code, or cancel the run.
173
+
174
+ ### "workflow execution already started" on re-run
175
+
176
+ `chant run` uses deterministic workflow IDs (e.g. `crdb-deploy-{project}`). If a previous run is still open:
177
+
178
+ ```bash
179
+ # Check if it's still running
180
+ temporal workflow describe --workflow-id <id> --namespace <ns> | grep "status"
181
+
182
+ # If stuck, terminate it first
183
+ temporal workflow terminate --workflow-id <id> --namespace <ns> --reason "Restarting"
184
+ ```
@@ -0,0 +1,201 @@
1
+ ---
2
+ skill: chant-temporal
3
+ description: Build and manage Temporal server deployment, namespace provisioning, and schedule registration from a chant project
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Temporal Operational Playbook
8
+
9
+ ## How chant and Temporal relate
10
+
11
+ chant is a **synthesis compiler** — it compiles TypeScript resource declarations into deployment artifacts. `chant build` does not start Temporal or register anything; synthesis is pure and deterministic. Your job as an agent is to bridge synthesis and deployment:
12
+
13
+ - Use **chant** for: build, lint, diff (local config comparison)
14
+ - Use **docker compose / kubectl / temporal CLI** for: starting the server, applying configs, and all runtime operations
15
+
16
+ The source of truth for Temporal configuration is the TypeScript in `src/`. The generated artifacts in `dist/` are intermediate outputs.
17
+
18
+ ## Resources and their outputs
19
+
20
+ | Resource | Emits |
21
+ |---|---|
22
+ | `TemporalServer` | `docker-compose.yml` (primary) + `temporal-helm-values.yaml` |
23
+ | `TemporalNamespace` | `temporal-setup.sh` (namespace create commands) |
24
+ | `SearchAttribute` | `temporal-setup.sh` (search-attribute create commands) |
25
+ | `TemporalSchedule` | `schedules/<id>.ts` (SDK schedule creation script) |
26
+
27
+ ## Build and validate
28
+
29
+ ### Build the project
30
+
31
+ ```bash
32
+ chant build src/ --output dist/
33
+ ```
34
+
35
+ Options:
36
+ - `--watch` — rebuild on source changes
37
+ - `--format json` — not applicable (Temporal outputs are YAML/shell/TypeScript)
38
+
39
+ ### Lint the source
40
+
41
+ ```bash
42
+ chant lint src/
43
+ ```
44
+
45
+ ## Start the Temporal server
46
+
47
+ ### Local dev (generated docker-compose.yml)
48
+
49
+ ```bash
50
+ # Start the dev server
51
+ docker compose up -d
52
+
53
+ # Verify it's running
54
+ temporal operator cluster health
55
+ ```
56
+
57
+ The dev server runs as a single container (`temporal server start-dev`). The Web UI is available at `http://localhost:8080`.
58
+
59
+ ### Temporal Cloud
60
+
61
+ No server to start. Configure your worker profile in `chant.config.ts`:
62
+
63
+ ```ts
64
+ import type { TemporalChantConfig } from "@intentius/chant-lexicon-temporal";
65
+
66
+ export default {
67
+ lexicons: ["temporal"],
68
+ temporal: {
69
+ profiles: {
70
+ cloud: {
71
+ address: "myns.a2dd6.tmprl.cloud:7233",
72
+ namespace: "myns.a2dd6",
73
+ taskQueue: "my-deploy",
74
+ tls: true,
75
+ apiKey: { env: "TEMPORAL_API_KEY" },
76
+ },
77
+ },
78
+ defaultProfile: "cloud",
79
+ } satisfies TemporalChantConfig,
80
+ };
81
+ ```
82
+
83
+ ## Provision namespaces and search attributes
84
+
85
+ After the server is ready, run the generated setup script:
86
+
87
+ ```bash
88
+ bash dist/temporal-setup.sh
89
+ ```
90
+
91
+ This creates namespaces and registers search attributes using the `temporal` CLI. The `TEMPORAL_ADDRESS` env var overrides the default `localhost:7233`:
92
+
93
+ ```bash
94
+ TEMPORAL_ADDRESS=myns.a2dd6.tmprl.cloud:7233 bash dist/temporal-setup.sh
95
+ ```
96
+
97
+ Verify:
98
+
99
+ ```bash
100
+ temporal operator namespace describe --namespace default
101
+ temporal operator search-attribute list --namespace default
102
+ ```
103
+
104
+ ## Deploy with Helm
105
+
106
+ ```bash
107
+ helm repo add temporal https://go.temporal.io/server/helm-charts
108
+ helm repo update
109
+ helm install temporal temporal/temporal -f dist/temporal-helm-values.yaml
110
+ ```
111
+
112
+ Wait for all pods:
113
+
114
+ ```bash
115
+ kubectl get pods -l app.kubernetes.io/name=temporal -w
116
+ ```
117
+
118
+ ## Register schedules
119
+
120
+ Each `TemporalSchedule` resource generates a standalone TypeScript runner:
121
+
122
+ ```bash
123
+ # Set connection env vars
124
+ export TEMPORAL_ADDRESS=localhost:7233
125
+ export TEMPORAL_NAMESPACE=default
126
+
127
+ # Run the generated schedule creation script
128
+ npx tsx dist/schedules/daily-backup.ts
129
+ ```
130
+
131
+ Verify the schedule was created:
132
+
133
+ ```bash
134
+ temporal schedule list --namespace default
135
+ temporal schedule describe --schedule-id daily-backup --namespace default
136
+ ```
137
+
138
+ ## Key resource types
139
+
140
+ | Resource | Purpose |
141
+ |---|---|
142
+ | `TemporalServer` | Server deployment config (dev vs full mode) |
143
+ | `TemporalNamespace` | Namespace with retention policy |
144
+ | `SearchAttribute` | Custom workflow search field |
145
+ | `TemporalSchedule` | Recurring workflow trigger |
146
+
147
+ ## Common patterns
148
+
149
+ ### Minimal local dev stack
150
+
151
+ ```ts
152
+ import { TemporalServer, TemporalNamespace } from "@intentius/chant-lexicon-temporal";
153
+
154
+ export const server = new TemporalServer({ mode: "dev" });
155
+ export const ns = new TemporalNamespace({ name: "default", retention: "7d" });
156
+ ```
157
+
158
+ ### Production namespace with search attributes
159
+
160
+ ```ts
161
+ import { TemporalNamespace, SearchAttribute } from "@intentius/chant-lexicon-temporal";
162
+
163
+ export const ns = new TemporalNamespace({
164
+ name: "prod-deploy",
165
+ retention: "30d",
166
+ description: "Production deployment workflows",
167
+ });
168
+
169
+ export const gcpProject = new SearchAttribute({
170
+ name: "GcpProject",
171
+ type: "Text",
172
+ namespace: "prod-deploy",
173
+ });
174
+
175
+ export const environment = new SearchAttribute({
176
+ name: "Environment",
177
+ type: "Keyword",
178
+ namespace: "prod-deploy",
179
+ });
180
+ ```
181
+
182
+ ### Recurring backup schedule
183
+
184
+ ```ts
185
+ import { TemporalSchedule } from "@intentius/chant-lexicon-temporal";
186
+
187
+ export const backupSchedule = new TemporalSchedule({
188
+ scheduleId: "daily-backup",
189
+ spec: {
190
+ cronExpressions: ["0 3 * * *"],
191
+ },
192
+ action: {
193
+ workflowType: "backupWorkflow",
194
+ taskQueue: "backup-queue",
195
+ },
196
+ policies: {
197
+ overlap: "Skip",
198
+ pauseOnFailure: true,
199
+ },
200
+ });
201
+ ```
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env tsx
2
+ import { validate } from "./validate";
3
+
4
+ const result = await validate({ verbose: true });
5
+ if (result.failed > 0) {
6
+ process.exit(1);
7
+ }
@@ -0,0 +1,9 @@
1
+ import { test, expect } from "vitest";
2
+ import { validate } from "./validate";
3
+
4
+ test("validate passes — all 4 resources have correct entityType strings", async () => {
5
+ const result = await validate({ verbose: false });
6
+ expect(result.valid ?? result.failed === 0).toBe(true);
7
+ expect(result.failed).toBe(0);
8
+ expect(result.errors).toHaveLength(0);
9
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Validate the Temporal lexicon dist/ artifacts.
3
+ *
4
+ * Since all resources are hand-written, validation checks that
5
+ * the packaging step produced correct dist/ artifacts: manifest.json,
6
+ * meta.json (with the 4 expected resource types), types/index.d.ts,
7
+ * and integrity.json.
8
+ */
9
+
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { join, dirname } from "path";
12
+ import { fileURLToPath } from "url";
13
+
14
+ export interface ValidateResult {
15
+ passed: number;
16
+ failed: number;
17
+ errors: string[];
18
+ }
19
+
20
+ const EXPECTED_RESOURCE_TYPES = [
21
+ "TemporalServer",
22
+ "TemporalNamespace",
23
+ "SearchAttribute",
24
+ "TemporalSchedule",
25
+ ] as const;
26
+
27
+ export async function validate(opts?: { verbose?: boolean; basePath?: string }): Promise<ValidateResult> {
28
+ const pkgDir = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
29
+ const distDir = join(pkgDir, "dist");
30
+ const errors: string[] = [];
31
+
32
+ // manifest.json
33
+ const manifestPath = join(distDir, "manifest.json");
34
+ if (!existsSync(manifestPath)) {
35
+ errors.push("dist/manifest.json not found — run npm run bundle");
36
+ } else {
37
+ try {
38
+ const m = JSON.parse(readFileSync(manifestPath, "utf-8")) as Record<string, unknown>;
39
+ if (m["name"] !== "temporal") errors.push(`manifest.json: expected name "temporal", got ${JSON.stringify(m["name"])}`);
40
+ if (m["namespace"] !== "Temporal") errors.push(`manifest.json: expected namespace "Temporal", got ${JSON.stringify(m["namespace"])}`);
41
+ } catch (err) {
42
+ errors.push(`manifest.json: parse error — ${err instanceof Error ? err.message : String(err)}`);
43
+ }
44
+ }
45
+
46
+ // meta.json (lexicon catalog)
47
+ const metaPath = join(distDir, "meta.json");
48
+ if (!existsSync(metaPath)) {
49
+ errors.push("dist/meta.json not found — run npm run bundle");
50
+ } else {
51
+ try {
52
+ const catalog = JSON.parse(readFileSync(metaPath, "utf-8")) as Record<string, unknown>;
53
+ for (const name of EXPECTED_RESOURCE_TYPES) {
54
+ if (!(name in catalog)) {
55
+ errors.push(`meta.json: missing resource "${name}"`);
56
+ }
57
+ }
58
+ } catch (err) {
59
+ errors.push(`meta.json: parse error — ${err instanceof Error ? err.message : String(err)}`);
60
+ }
61
+ }
62
+
63
+ // types/index.d.ts
64
+ const dtsPath = join(distDir, "types", "index.d.ts");
65
+ if (!existsSync(dtsPath)) {
66
+ errors.push("dist/types/index.d.ts not found — run npm run bundle");
67
+ }
68
+
69
+ // integrity.json
70
+ const integrityPath = join(distDir, "integrity.json");
71
+ if (!existsSync(integrityPath)) {
72
+ errors.push("dist/integrity.json not found — run npm run bundle");
73
+ }
74
+
75
+ const result: ValidateResult = {
76
+ passed: 4 - errors.length < 0 ? 0 : 4 - errors.length,
77
+ failed: errors.length,
78
+ errors,
79
+ };
80
+
81
+ if (opts?.verbose) {
82
+ if (errors.length === 0) {
83
+ console.error(`Validation passed: all dist/ artifacts present and valid`);
84
+ } else {
85
+ for (const err of errors) {
86
+ console.error(` ✗ ${err}`);
87
+ }
88
+ }
89
+ }
90
+
91
+ return result;
92
+ }