@intentius/chant-lexicon-temporal 0.7.0 → 0.8.0

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 (107) hide show
  1. package/dist/codegen/docs-cli.d.ts +3 -0
  2. package/dist/codegen/docs-cli.d.ts.map +1 -0
  3. package/dist/codegen/docs.d.ts +7 -0
  4. package/dist/codegen/docs.d.ts.map +1 -0
  5. package/dist/codegen/generate-cli.d.ts +3 -0
  6. package/dist/codegen/generate-cli.d.ts.map +1 -0
  7. package/dist/codegen/generate.d.ts +16 -0
  8. package/dist/codegen/generate.d.ts.map +1 -0
  9. package/dist/codegen/package-cli.d.ts +3 -0
  10. package/dist/codegen/package-cli.d.ts.map +1 -0
  11. package/dist/codegen/package.d.ts +10 -0
  12. package/dist/codegen/package.d.ts.map +1 -0
  13. package/dist/composites/apply-op.d.ts +73 -0
  14. package/dist/composites/apply-op.d.ts.map +1 -0
  15. package/dist/composites/cloud-stack.d.ts +54 -0
  16. package/dist/composites/cloud-stack.d.ts.map +1 -0
  17. package/dist/composites/dev-stack.d.ts +63 -0
  18. package/dist/composites/dev-stack.d.ts.map +1 -0
  19. package/dist/composites/pipeline-audit-op.d.ts +55 -0
  20. package/dist/composites/pipeline-audit-op.d.ts.map +1 -0
  21. package/dist/composites/reconcile-op.d.ts +62 -0
  22. package/dist/composites/reconcile-op.d.ts.map +1 -0
  23. package/dist/composites/watch-op.d.ts +56 -0
  24. package/dist/composites/watch-op.d.ts.map +1 -0
  25. package/dist/composites/workflow-audit-op.d.ts +61 -0
  26. package/dist/composites/workflow-audit-op.d.ts.map +1 -0
  27. package/dist/config.d.ts +197 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/coverage.d.ts +15 -0
  30. package/dist/coverage.d.ts.map +1 -0
  31. package/dist/describe-resources.d.ts +26 -0
  32. package/dist/describe-resources.d.ts.map +1 -0
  33. package/dist/index.d.ts +23 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/integrity.json +5 -5
  36. package/dist/lint/post-synth/index.d.ts +3 -0
  37. package/dist/lint/post-synth/index.d.ts.map +1 -0
  38. package/dist/lint/post-synth/tmp001-retention-too-short.d.ts +10 -0
  39. package/dist/lint/post-synth/tmp001-retention-too-short.d.ts.map +1 -0
  40. package/dist/lint/post-synth/tmp002-allowall-without-note.d.ts +11 -0
  41. package/dist/lint/post-synth/tmp002-allowall-without-note.d.ts.map +1 -0
  42. package/dist/lint/post-synth/tmp010-cron-syntax.d.ts +12 -0
  43. package/dist/lint/post-synth/tmp010-cron-syntax.d.ts.map +1 -0
  44. package/dist/lint/post-synth/tmp011-namespace-reference.d.ts +10 -0
  45. package/dist/lint/post-synth/tmp011-namespace-reference.d.ts.map +1 -0
  46. package/dist/manifest.json +1 -1
  47. package/dist/op/activities/apply.d.ts +70 -0
  48. package/dist/op/activities/apply.d.ts.map +1 -0
  49. package/dist/op/activities/argo.d.ts +56 -0
  50. package/dist/op/activities/argo.d.ts.map +1 -0
  51. package/dist/op/activities/build.d.ts +11 -0
  52. package/dist/op/activities/build.d.ts.map +1 -0
  53. package/dist/op/activities/gitlab.d.ts +15 -0
  54. package/dist/op/activities/gitlab.d.ts.map +1 -0
  55. package/dist/op/activities/heartbeat.d.ts +27 -0
  56. package/dist/op/activities/heartbeat.d.ts.map +1 -0
  57. package/dist/op/activities/helm.d.ts +18 -0
  58. package/dist/op/activities/helm.d.ts.map +1 -0
  59. package/dist/op/activities/index.d.ts +29 -0
  60. package/dist/op/activities/index.d.ts.map +1 -0
  61. package/dist/op/activities/kubectl.d.ts +11 -0
  62. package/dist/op/activities/kubectl.d.ts.map +1 -0
  63. package/dist/op/activities/lifecycle.d.ts +42 -0
  64. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  65. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  66. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  67. package/dist/op/activities/policy.d.ts +17 -0
  68. package/dist/op/activities/policy.d.ts.map +1 -0
  69. package/dist/op/activities/reconcile.d.ts +69 -0
  70. package/dist/op/activities/reconcile.d.ts.map +1 -0
  71. package/dist/op/activities/shell.d.ts +13 -0
  72. package/dist/op/activities/shell.d.ts.map +1 -0
  73. package/dist/op/activities/teardown.d.ts +10 -0
  74. package/dist/op/activities/teardown.d.ts.map +1 -0
  75. package/dist/op/activities/util.d.ts +8 -0
  76. package/dist/op/activities/util.d.ts.map +1 -0
  77. package/dist/op/activities/wait.d.ts +16 -0
  78. package/dist/op/activities/wait.d.ts.map +1 -0
  79. package/dist/op/activities/workflow-audit.d.ts +92 -0
  80. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  81. package/dist/op/serializer.d.ts +20 -0
  82. package/dist/op/serializer.d.ts.map +1 -0
  83. package/dist/plugin.d.ts +10 -0
  84. package/dist/plugin.d.ts.map +1 -0
  85. package/dist/resources.d.ts +101 -0
  86. package/dist/resources.d.ts.map +1 -0
  87. package/dist/rules/{tmp001.ts → tmp001-retention-too-short.ts} +9 -11
  88. package/dist/rules/{tmp002.ts → tmp002-allowall-without-note.ts} +9 -11
  89. package/dist/rules/tmp011-namespace-reference.ts +2 -2
  90. package/dist/serializer.d.ts +15 -0
  91. package/dist/serializer.d.ts.map +1 -0
  92. package/dist/validate-cli.d.ts +3 -0
  93. package/dist/validate-cli.d.ts.map +1 -0
  94. package/dist/validate.d.ts +18 -0
  95. package/dist/validate.d.ts.map +1 -0
  96. package/package.json +27 -6
  97. package/src/lint/post-synth/index.ts +13 -0
  98. package/src/lint/post-synth/post-synth.test.ts +124 -1
  99. package/src/lint/{rules/tmp001.ts → post-synth/tmp001-retention-too-short.ts} +9 -11
  100. package/src/lint/{rules/tmp002.ts → post-synth/tmp002-allowall-without-note.ts} +9 -11
  101. package/src/lint/post-synth/tmp011-namespace-reference.ts +2 -2
  102. package/src/op/serializer.ts +2 -2
  103. package/src/plugin.test.ts +5 -6
  104. package/src/plugin.ts +2 -12
  105. package/src/serializer.ts +6 -6
  106. package/src/lint/rules/index.ts +0 -2
  107. package/src/lint/rules/lint-rules.test.ts +0 -150
@@ -6,7 +6,7 @@
6
6
  * failures or running ad-hoc queries against closed workflow executions.
7
7
  */
8
8
 
9
- import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
10
 
11
11
  /** Parse a retention string like "1d", "12h", "3d" → total hours. Returns NaN on unrecognised format. */
12
12
  function retentionHours(retention: string): number {
@@ -17,17 +17,15 @@ function retentionHours(retention: string): number {
17
17
  return NaN;
18
18
  }
19
19
 
20
- export const tmp001: LintRule = {
20
+ export const tmp001: PostSynthCheck = {
21
21
  id: "TMP001",
22
- severity: "error",
23
- category: "correctness",
24
22
  description: "TemporalNamespace retention should be at least 3 days to preserve workflow history for debugging",
25
23
 
26
- check(context: LintContext): LintDiagnostic[] {
27
- const diagnostics: LintDiagnostic[] = [];
24
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
25
+ const diagnostics: PostSynthDiagnostic[] = [];
28
26
 
29
- for (const [name, entity] of context.entities) {
30
- const et = (entity as Record<string, unknown>).entityType as string;
27
+ for (const [name, entity] of ctx.entities) {
28
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
31
29
  if (et !== "Temporal::Namespace") continue;
32
30
 
33
31
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
@@ -39,11 +37,11 @@ export const tmp001: LintRule = {
39
37
 
40
38
  if (hours < 72) {
41
39
  diagnostics.push({
42
- ruleId: "TMP001",
40
+ checkId: "TMP001",
43
41
  severity: "error",
44
- message: `Namespace "${name}" has retention "${retention}" — minimum recommended is 3d (72h) to preserve workflow history`,
42
+ message: `Namespace "${name}" has retention "${retention}" — minimum recommended is 3d (72h) to preserve workflow history. Set retention to at least "3d" — e.g. retention: "7d"`,
45
43
  entity: name,
46
- fix: 'Set retention to at least "3d" — e.g. retention: "7d"',
44
+ lexicon: "temporal",
47
45
  });
48
46
  }
49
47
  }
@@ -7,19 +7,17 @@
7
7
  * the author to document the intent.
8
8
  */
9
9
 
10
- import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
10
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
11
 
12
- export const tmp002: LintRule = {
12
+ export const tmp002: PostSynthCheck = {
13
13
  id: "TMP002",
14
- severity: "warning",
15
- category: "best-practices",
16
14
  description: "TemporalSchedule with overlap AllowAll should include state.note explaining the intent",
17
15
 
18
- check(context: LintContext): LintDiagnostic[] {
19
- const diagnostics: LintDiagnostic[] = [];
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ const diagnostics: PostSynthDiagnostic[] = [];
20
18
 
21
- for (const [name, entity] of context.entities) {
22
- const et = (entity as Record<string, unknown>).entityType as string;
19
+ for (const [name, entity] of ctx.entities) {
20
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
23
21
  if (et !== "Temporal::Schedule") continue;
24
22
 
25
23
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
@@ -31,11 +29,11 @@ export const tmp002: LintRule = {
31
29
 
32
30
  if (!note || note.trim() === "") {
33
31
  diagnostics.push({
34
- ruleId: "TMP002",
32
+ checkId: "TMP002",
35
33
  severity: "warning",
36
- message: `Schedule "${name}" uses overlap "AllowAll" — add state.note explaining why concurrent runs are safe`,
34
+ message: `Schedule "${name}" uses overlap "AllowAll" — add state.note explaining why concurrent runs are safe. Add state: { note: "Workflow is idempotent — concurrent runs are safe" }`,
37
35
  entity: name,
38
- fix: 'Add state: { note: "Workflow is idempotent — concurrent runs are safe" }',
36
+ lexicon: "temporal",
39
37
  });
40
38
  }
41
39
  }
@@ -18,7 +18,7 @@ export const tmp011: PostSynthCheck = {
18
18
  // Collect declared namespace names
19
19
  const declaredNamespaces = new Set<string>();
20
20
  for (const [, entity] of ctx.entities) {
21
- const et = (entity as Record<string, unknown>).entityType as string;
21
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
22
22
  if (et !== "Temporal::Namespace") continue;
23
23
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
24
24
  const name = props.name as string | undefined;
@@ -27,7 +27,7 @@ export const tmp011: PostSynthCheck = {
27
27
 
28
28
  // Check each SearchAttribute that specifies a namespace
29
29
  for (const [entityKey, entity] of ctx.entities) {
30
- const et = (entity as Record<string, unknown>).entityType as string;
30
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
31
31
  if (et !== "Temporal::SearchAttribute") continue;
32
32
 
33
33
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Temporal serializer.
3
+ *
4
+ * Routes entities by entityType:
5
+ * Temporal::Server → docker-compose.yml (primary) + temporal-helm-values.yaml (file)
6
+ * Temporal::Namespace → temporal-setup.sh (file)
7
+ * Temporal::SearchAttribute → temporal-setup.sh (file, appended after namespaces)
8
+ * Temporal::Schedule → schedules/<scheduleId>.ts (file per schedule)
9
+ *
10
+ * Returns a plain string when only TemporalServer entities are present.
11
+ * Returns SerializerResult for any other combination.
12
+ */
13
+ import type { Serializer } from "@intentius/chant/serializer";
14
+ export declare const temporalSerializer: Serializer;
15
+ //# sourceMappingURL=serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,6BAA6B,CAAC;AAwQhF,eAAO,MAAM,kBAAkB,EAAE,UAyDhC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
3
+ //# sourceMappingURL=validate-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-cli.d.ts","sourceRoot":"","sources":["../src/validate-cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
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
+ export interface ValidateResult {
10
+ passed: number;
11
+ failed: number;
12
+ errors: string[];
13
+ }
14
+ export declare function validate(opts?: {
15
+ verbose?: boolean;
16
+ basePath?: string;
17
+ }): Promise<ValidateResult>;
18
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AASD,wBAAsB,QAAQ,CAAC,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAiEvG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-temporal",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Temporal lexicon for chant — server deployment, namespaces, search attributes, and schedules",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -17,10 +17,30 @@
17
17
  "src/",
18
18
  "dist/"
19
19
  ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
20
23
  "exports": {
21
- ".": "./src/index.ts",
22
- "./*": "./src/*.ts",
23
- "./op/activities": "./src/op/activities/index.ts",
24
+ ".": {
25
+ "development": "./src/index.ts",
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./src/index.ts"
28
+ },
29
+ "./*": {
30
+ "development": "./src/*.ts",
31
+ "types": "./dist/*.d.ts",
32
+ "default": "./src/*.ts"
33
+ },
34
+ "./lint/post-synth": {
35
+ "development": "./src/lint/post-synth/index.ts",
36
+ "types": "./dist/lint/post-synth/index.d.ts",
37
+ "default": "./src/lint/post-synth/index.ts"
38
+ },
39
+ "./op/activities": {
40
+ "development": "./src/op/activities/index.ts",
41
+ "types": "./dist/op/activities/index.d.ts",
42
+ "default": "./src/op/activities/index.ts"
43
+ },
24
44
  "./manifest": "./dist/manifest.json",
25
45
  "./meta": "./dist/meta.json",
26
46
  "./types": "./dist/types/index.d.ts"
@@ -30,10 +50,11 @@
30
50
  "bundle": "tsx src/codegen/package-cli.ts",
31
51
  "validate": "tsx src/validate-cli.ts",
32
52
  "docs": "tsx src/codegen/docs-cli.ts",
33
- "prepack": "npm run generate && npm run bundle && npm run validate"
53
+ "prepack": "npm run generate && npm run bundle && npm run validate && npm run build",
54
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && find dist -type f \\( -name \"*.js\" -o -name \"*.js.map\" \\) -delete"
34
55
  },
35
56
  "peerDependencies": {
36
- "@intentius/chant": "^0.1.0"
57
+ "@intentius/chant": "^0.8.0"
37
58
  },
38
59
  "devDependencies": {
39
60
  "@intentius/chant": "*",
@@ -0,0 +1,13 @@
1
+ // Code generated by chant generate. DO NOT EDIT.
2
+ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
3
+ import { tmp001 } from "./tmp001-retention-too-short";
4
+ import { tmp002 } from "./tmp002-allowall-without-note";
5
+ import { tmp010 } from "./tmp010-cron-syntax";
6
+ import { tmp011 } from "./tmp011-namespace-reference";
7
+
8
+ export const postSynthChecks: PostSynthCheck[] = [
9
+ tmp001,
10
+ tmp002,
11
+ tmp010,
12
+ tmp011,
13
+ ];
@@ -1,10 +1,12 @@
1
1
  /**
2
- * Post-synth check tests — TMP010, TMP011.
2
+ * Post-synth check tests — TMP001, TMP002, TMP010, TMP011.
3
3
  */
4
4
 
5
5
  import { describe, test, expect } from "vitest";
6
6
  import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
7
7
  import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
8
+ import { tmp001 } from "./tmp001-retention-too-short";
9
+ import { tmp002 } from "./tmp002-allowall-without-note";
8
10
  import { tmp010 } from "./tmp010-cron-syntax";
9
11
  import { tmp011 } from "./tmp011-namespace-reference";
10
12
 
@@ -49,6 +51,127 @@ function makeCtxFromEntities(entities: Map<string, unknown>): PostSynthContext {
49
51
  };
50
52
  }
51
53
 
54
+ // ── TMP001: retention-too-short ──────────────────────────────────────
55
+
56
+ describe("TMP001: retention-too-short", () => {
57
+ test("flags namespace with 1d retention", () => {
58
+ const ctx = makeCtxFromEntities(new Map([
59
+ ["ns", makeEntity("Temporal::Namespace", { name: "default", retention: "1d" })],
60
+ ]));
61
+ const diags = tmp001.check(ctx);
62
+ expect(diags).toHaveLength(1);
63
+ expect(diags[0].checkId).toBe("TMP001");
64
+ expect(diags[0].severity).toBe("error");
65
+ expect(diags[0].message).toContain("1d");
66
+ });
67
+
68
+ test("flags namespace with 48h retention", () => {
69
+ const ctx = makeCtxFromEntities(new Map([
70
+ ["ns", makeEntity("Temporal::Namespace", { name: "default", retention: "48h" })],
71
+ ]));
72
+ const diags = tmp001.check(ctx);
73
+ expect(diags).toHaveLength(1);
74
+ expect(diags[0].checkId).toBe("TMP001");
75
+ });
76
+
77
+ test("passes with 3d retention (exactly at threshold)", () => {
78
+ const ctx = makeCtxFromEntities(new Map([
79
+ ["ns", makeEntity("Temporal::Namespace", { name: "default", retention: "3d" })],
80
+ ]));
81
+ expect(tmp001.check(ctx)).toHaveLength(0);
82
+ });
83
+
84
+ test("passes with 7d retention", () => {
85
+ const ctx = makeCtxFromEntities(new Map([
86
+ ["ns", makeEntity("Temporal::Namespace", { name: "default", retention: "7d" })],
87
+ ]));
88
+ expect(tmp001.check(ctx)).toHaveLength(0);
89
+ });
90
+
91
+ test("passes when retention is unset (defaults to 7d)", () => {
92
+ const ctx = makeCtxFromEntities(new Map([
93
+ ["ns", makeEntity("Temporal::Namespace", { name: "default" })],
94
+ ]));
95
+ expect(tmp001.check(ctx)).toHaveLength(0);
96
+ });
97
+
98
+ test("skips non-namespace entities", () => {
99
+ const ctx = makeCtxFromEntities(new Map([
100
+ ["s", makeEntity("Temporal::Server", { mode: "dev" })],
101
+ ]));
102
+ expect(tmp001.check(ctx)).toHaveLength(0);
103
+ });
104
+
105
+ test("skips unrecognised retention format", () => {
106
+ const ctx = makeCtxFromEntities(new Map([
107
+ ["ns", makeEntity("Temporal::Namespace", { name: "default", retention: "1week" })],
108
+ ]));
109
+ expect(tmp001.check(ctx)).toHaveLength(0);
110
+ });
111
+ });
112
+
113
+ // ── TMP002: allowall-without-note ────────────────────────────────────
114
+
115
+ describe("TMP002: allowall-without-note", () => {
116
+ test("warns for AllowAll overlap without state.note", () => {
117
+ const ctx = makeCtxFromEntities(new Map([
118
+ ["sched", makeEntity("Temporal::Schedule", {
119
+ scheduleId: "heavy-job",
120
+ spec: { cronExpressions: ["0 * * * *"] },
121
+ action: { workflowType: "heavyWorkflow", taskQueue: "heavy" },
122
+ policies: { overlap: "AllowAll" },
123
+ })],
124
+ ]));
125
+ const diags = tmp002.check(ctx);
126
+ expect(diags).toHaveLength(1);
127
+ expect(diags[0].checkId).toBe("TMP002");
128
+ expect(diags[0].severity).toBe("warning");
129
+ });
130
+
131
+ test("passes when AllowAll has a note", () => {
132
+ const ctx = makeCtxFromEntities(new Map([
133
+ ["sched", makeEntity("Temporal::Schedule", {
134
+ scheduleId: "heavy-job",
135
+ spec: { cronExpressions: ["0 * * * *"] },
136
+ action: { workflowType: "heavyWorkflow", taskQueue: "heavy" },
137
+ policies: { overlap: "AllowAll" },
138
+ state: { note: "Workflow is idempotent — concurrent runs are safe" },
139
+ })],
140
+ ]));
141
+ expect(tmp002.check(ctx)).toHaveLength(0);
142
+ });
143
+
144
+ test("passes for Skip overlap (no note needed)", () => {
145
+ const ctx = makeCtxFromEntities(new Map([
146
+ ["sched", makeEntity("Temporal::Schedule", {
147
+ scheduleId: "daily",
148
+ spec: { cronExpressions: ["0 3 * * *"] },
149
+ action: { workflowType: "dailyWorkflow", taskQueue: "daily" },
150
+ policies: { overlap: "Skip" },
151
+ })],
152
+ ]));
153
+ expect(tmp002.check(ctx)).toHaveLength(0);
154
+ });
155
+
156
+ test("passes when no policies set", () => {
157
+ const ctx = makeCtxFromEntities(new Map([
158
+ ["sched", makeEntity("Temporal::Schedule", {
159
+ scheduleId: "daily",
160
+ spec: { cronExpressions: ["0 3 * * *"] },
161
+ action: { workflowType: "dailyWorkflow", taskQueue: "daily" },
162
+ })],
163
+ ]));
164
+ expect(tmp002.check(ctx)).toHaveLength(0);
165
+ });
166
+
167
+ test("skips non-schedule entities", () => {
168
+ const ctx = makeCtxFromEntities(new Map([
169
+ ["ns", makeEntity("Temporal::Namespace", { name: "default" })],
170
+ ]));
171
+ expect(tmp002.check(ctx)).toHaveLength(0);
172
+ });
173
+ });
174
+
52
175
  // ── TMP010: cron-syntax ──────────────────────────────────────────────
53
176
 
54
177
  describe("TMP010: cron-syntax", () => {
@@ -6,7 +6,7 @@
6
6
  * failures or running ad-hoc queries against closed workflow executions.
7
7
  */
8
8
 
9
- import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
10
 
11
11
  /** Parse a retention string like "1d", "12h", "3d" → total hours. Returns NaN on unrecognised format. */
12
12
  function retentionHours(retention: string): number {
@@ -17,17 +17,15 @@ function retentionHours(retention: string): number {
17
17
  return NaN;
18
18
  }
19
19
 
20
- export const tmp001: LintRule = {
20
+ export const tmp001: PostSynthCheck = {
21
21
  id: "TMP001",
22
- severity: "error",
23
- category: "correctness",
24
22
  description: "TemporalNamespace retention should be at least 3 days to preserve workflow history for debugging",
25
23
 
26
- check(context: LintContext): LintDiagnostic[] {
27
- const diagnostics: LintDiagnostic[] = [];
24
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
25
+ const diagnostics: PostSynthDiagnostic[] = [];
28
26
 
29
- for (const [name, entity] of context.entities) {
30
- const et = (entity as Record<string, unknown>).entityType as string;
27
+ for (const [name, entity] of ctx.entities) {
28
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
31
29
  if (et !== "Temporal::Namespace") continue;
32
30
 
33
31
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
@@ -39,11 +37,11 @@ export const tmp001: LintRule = {
39
37
 
40
38
  if (hours < 72) {
41
39
  diagnostics.push({
42
- ruleId: "TMP001",
40
+ checkId: "TMP001",
43
41
  severity: "error",
44
- message: `Namespace "${name}" has retention "${retention}" — minimum recommended is 3d (72h) to preserve workflow history`,
42
+ message: `Namespace "${name}" has retention "${retention}" — minimum recommended is 3d (72h) to preserve workflow history. Set retention to at least "3d" — e.g. retention: "7d"`,
45
43
  entity: name,
46
- fix: 'Set retention to at least "3d" — e.g. retention: "7d"',
44
+ lexicon: "temporal",
47
45
  });
48
46
  }
49
47
  }
@@ -7,19 +7,17 @@
7
7
  * the author to document the intent.
8
8
  */
9
9
 
10
- import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
10
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
11
 
12
- export const tmp002: LintRule = {
12
+ export const tmp002: PostSynthCheck = {
13
13
  id: "TMP002",
14
- severity: "warning",
15
- category: "best-practices",
16
14
  description: "TemporalSchedule with overlap AllowAll should include state.note explaining the intent",
17
15
 
18
- check(context: LintContext): LintDiagnostic[] {
19
- const diagnostics: LintDiagnostic[] = [];
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ const diagnostics: PostSynthDiagnostic[] = [];
20
18
 
21
- for (const [name, entity] of context.entities) {
22
- const et = (entity as Record<string, unknown>).entityType as string;
19
+ for (const [name, entity] of ctx.entities) {
20
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
23
21
  if (et !== "Temporal::Schedule") continue;
24
22
 
25
23
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
@@ -31,11 +29,11 @@ export const tmp002: LintRule = {
31
29
 
32
30
  if (!note || note.trim() === "") {
33
31
  diagnostics.push({
34
- ruleId: "TMP002",
32
+ checkId: "TMP002",
35
33
  severity: "warning",
36
- message: `Schedule "${name}" uses overlap "AllowAll" — add state.note explaining why concurrent runs are safe`,
34
+ message: `Schedule "${name}" uses overlap "AllowAll" — add state.note explaining why concurrent runs are safe. Add state: { note: "Workflow is idempotent — concurrent runs are safe" }`,
37
35
  entity: name,
38
- fix: 'Add state: { note: "Workflow is idempotent — concurrent runs are safe" }',
36
+ lexicon: "temporal",
39
37
  });
40
38
  }
41
39
  }
@@ -18,7 +18,7 @@ export const tmp011: PostSynthCheck = {
18
18
  // Collect declared namespace names
19
19
  const declaredNamespaces = new Set<string>();
20
20
  for (const [, entity] of ctx.entities) {
21
- const et = (entity as Record<string, unknown>).entityType as string;
21
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
22
22
  if (et !== "Temporal::Namespace") continue;
23
23
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
24
24
  const name = props.name as string | undefined;
@@ -27,7 +27,7 @@ export const tmp011: PostSynthCheck = {
27
27
 
28
28
  // Check each SearchAttribute that specifies a namespace
29
29
  for (const [entityKey, entity] of ctx.entities) {
30
- const et = (entity as Record<string, unknown>).entityType as string;
30
+ const et = (entity as unknown as Record<string, unknown>).entityType as string;
31
31
  if (et !== "Temporal::SearchAttribute") continue;
32
32
 
33
33
  const props = (entity as { props?: Record<string, unknown> }).props ?? {};
@@ -332,14 +332,14 @@ export function serializeOps(ops: Map<string, Declarable>): Record<string, strin
332
332
 
333
333
  // First pass: collect all names
334
334
  for (const [, entity] of ops) {
335
- const props = getProps(entity) as OpConfig;
335
+ const props = getProps(entity) as unknown as OpConfig;
336
336
  if (props.name) knownNames.add(props.name);
337
337
  }
338
338
 
339
339
  const files: Record<string, string> = {};
340
340
 
341
341
  for (const [, entity] of ops) {
342
- const config = getProps(entity) as OpConfig;
342
+ const config = getProps(entity) as unknown as OpConfig;
343
343
 
344
344
  if (!config.name) {
345
345
  throw new Error("Op entity missing required `name` field.");
@@ -20,12 +20,11 @@ describe("temporal plugin", () => {
20
20
  expect(temporalPlugin.serializer.name).toBe("temporal");
21
21
  });
22
22
 
23
- it("lintRules() returns 2 rules (TMP001, TMP002)", () => {
24
- const rules = temporalPlugin.lintRules?.();
25
- expect(Array.isArray(rules)).toBe(true);
26
- expect(rules?.length).toBe(2);
27
- const ids = rules?.map((r) => r.id).sort();
28
- expect(ids).toEqual(["TMP001", "TMP002"]);
23
+ it("postSynthChecks() returns 4 checks (TMP001, TMP002, TMP010, TMP011)", () => {
24
+ const checks = temporalPlugin.postSynthChecks?.();
25
+ expect(Array.isArray(checks)).toBe(true);
26
+ const ids = checks?.map((c) => c.id).sort();
27
+ expect(ids).toEqual(["TMP001", "TMP002", "TMP010", "TMP011"]);
29
28
  });
30
29
 
31
30
  it("mcpTools() returns 1 diff tool", () => {
package/src/plugin.ts CHANGED
@@ -6,17 +6,11 @@
6
6
  * and copies skill markdown files.
7
7
  */
8
8
 
9
- import { join, dirname } from "path";
10
- import { fileURLToPath } from "url";
11
9
  import type { LexiconPlugin } from "@intentius/chant/lexicon";
12
- import { discoverLintRules, discoverPostSynthChecks } from "@intentius/chant/lint/discover";
10
+ import { postSynthChecks as postSynthCheckList } from "./lint/post-synth";
13
11
  import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
14
12
  import { temporalSerializer } from "./serializer";
15
13
 
16
- const srcDir = dirname(fileURLToPath(import.meta.url));
17
- const rulesDir = join(srcDir, "lint/rules");
18
- const postSynthDir = join(srcDir, "lint/post-synth");
19
-
20
14
  export const temporalPlugin: LexiconPlugin = {
21
15
  name: "temporal",
22
16
  serializer: temporalSerializer,
@@ -61,12 +55,8 @@ export const temporalPlugin: LexiconPlugin = {
61
55
 
62
56
  // ── Optional extensions ─────────────────────────────────────────────
63
57
 
64
- lintRules() {
65
- return discoverLintRules(rulesDir, import.meta.url);
66
- },
67
-
68
58
  postSynthChecks() {
69
- return discoverPostSynthChecks(postSynthDir, import.meta.url);
59
+ return postSynthCheckList;
70
60
  },
71
61
 
72
62
  skills() {
package/src/serializer.ts CHANGED
@@ -26,7 +26,7 @@ function getProps(entity: Declarable): Record<string, unknown> {
26
26
  }
27
27
 
28
28
  function entityType(entity: Declarable): string {
29
- return (entity as Record<string, unknown>).entityType as string;
29
+ return (entity as unknown as Record<string, unknown>).entityType as string;
30
30
  }
31
31
 
32
32
  // ── Docker Compose ───────────────────────────────────────────────────
@@ -43,7 +43,7 @@ function serializeDockerCompose(servers: Map<string, Declarable>): string {
43
43
 
44
44
  // Use the first server entity (a project should have exactly one)
45
45
  const [, serverEntity] = [...servers.entries()][0];
46
- const props = getProps(serverEntity) as TemporalServerProps;
46
+ const props = getProps(serverEntity) as unknown as TemporalServerProps;
47
47
  const version = props.version ?? "1.26.2";
48
48
  const mode = props.mode ?? "dev";
49
49
  const port = props.port ?? 7233;
@@ -100,7 +100,7 @@ function serializeHelmValues(servers: Map<string, Declarable>): string {
100
100
  if (servers.size === 0) return "";
101
101
 
102
102
  const [, serverEntity] = [...servers.entries()][0];
103
- const props = getProps(serverEntity) as TemporalServerProps;
103
+ const props = getProps(serverEntity) as unknown as TemporalServerProps;
104
104
  const version = props.version ?? "1.26.2";
105
105
  const port = props.port ?? 7233;
106
106
  const chartVersion = props.helmChartVersion;
@@ -152,7 +152,7 @@ function serializeSetupScript(
152
152
  lines.push("# ── Namespaces ────────────────────────────────────────────────────────");
153
153
  lines.push("");
154
154
  for (const [, entity] of namespaces) {
155
- const props = getProps(entity) as TemporalNamespaceProps;
155
+ const props = getProps(entity) as unknown as TemporalNamespaceProps;
156
156
  const parts: string[] = [
157
157
  `temporal operator namespace create \\`,
158
158
  ` --address "\${TEMPORAL_ADDRESS}" \\`,
@@ -177,7 +177,7 @@ function serializeSetupScript(
177
177
  lines.push("# ── Search Attributes ─────────────────────────────────────────────────");
178
178
  lines.push("");
179
179
  for (const [, entity] of searchAttrs) {
180
- const props = getProps(entity) as SearchAttributeProps;
180
+ const props = getProps(entity) as unknown as SearchAttributeProps;
181
181
  const parts: string[] = [
182
182
  `temporal operator search-attribute create \\`,
183
183
  ` --address "\${TEMPORAL_ADDRESS}" \\`,
@@ -322,7 +322,7 @@ export const temporalSerializer: Serializer = {
322
322
  }
323
323
 
324
324
  for (const [name, entity] of schedules) {
325
- const props = getProps(entity) as TemporalScheduleProps;
325
+ const props = getProps(entity) as unknown as TemporalScheduleProps;
326
326
  const scheduleId = props.scheduleId ?? name;
327
327
  files[`schedules/${scheduleId}.ts`] = serializeSchedule(scheduleId, props);
328
328
  }
@@ -1,2 +0,0 @@
1
- export { tmp001 } from "./tmp001";
2
- export { tmp002 } from "./tmp002";