@intentius/chant-lexicon-temporal 0.7.0 → 0.8.1
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.
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +7 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +16 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/package-cli.d.ts +3 -0
- package/dist/codegen/package-cli.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +10 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/apply-op.d.ts +73 -0
- package/dist/composites/apply-op.d.ts.map +1 -0
- package/dist/composites/cloud-stack.d.ts +54 -0
- package/dist/composites/cloud-stack.d.ts.map +1 -0
- package/dist/composites/dev-stack.d.ts +63 -0
- package/dist/composites/dev-stack.d.ts.map +1 -0
- package/dist/composites/pipeline-audit-op.d.ts +55 -0
- package/dist/composites/pipeline-audit-op.d.ts.map +1 -0
- package/dist/composites/reconcile-op.d.ts +62 -0
- package/dist/composites/reconcile-op.d.ts.map +1 -0
- package/dist/composites/watch-op.d.ts +56 -0
- package/dist/composites/watch-op.d.ts.map +1 -0
- package/dist/composites/workflow-audit-op.d.ts +61 -0
- package/dist/composites/workflow-audit-op.d.ts.map +1 -0
- package/dist/config.d.ts +197 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/coverage.d.ts +15 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +26 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/integrity.json +5 -5
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/post-synth/tmp001-retention-too-short.d.ts +10 -0
- package/dist/lint/post-synth/tmp001-retention-too-short.d.ts.map +1 -0
- package/dist/lint/post-synth/tmp002-allowall-without-note.d.ts +11 -0
- package/dist/lint/post-synth/tmp002-allowall-without-note.d.ts.map +1 -0
- package/dist/lint/post-synth/tmp010-cron-syntax.d.ts +12 -0
- package/dist/lint/post-synth/tmp010-cron-syntax.d.ts.map +1 -0
- package/dist/lint/post-synth/tmp011-namespace-reference.d.ts +10 -0
- package/dist/lint/post-synth/tmp011-namespace-reference.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/apply.d.ts +70 -0
- package/dist/op/activities/apply.d.ts.map +1 -0
- package/dist/op/activities/argo.d.ts +56 -0
- package/dist/op/activities/argo.d.ts.map +1 -0
- package/dist/op/activities/build.d.ts +11 -0
- package/dist/op/activities/build.d.ts.map +1 -0
- package/dist/op/activities/gitlab.d.ts +15 -0
- package/dist/op/activities/gitlab.d.ts.map +1 -0
- package/dist/op/activities/heartbeat.d.ts +27 -0
- package/dist/op/activities/heartbeat.d.ts.map +1 -0
- package/dist/op/activities/helm.d.ts +18 -0
- package/dist/op/activities/helm.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +29 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/kubectl.d.ts +11 -0
- package/dist/op/activities/kubectl.d.ts.map +1 -0
- package/dist/op/activities/lifecycle.d.ts +42 -0
- package/dist/op/activities/lifecycle.d.ts.map +1 -0
- package/dist/op/activities/pipeline-audit.d.ts +84 -0
- package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
- package/dist/op/activities/policy.d.ts +17 -0
- package/dist/op/activities/policy.d.ts.map +1 -0
- package/dist/op/activities/reconcile.d.ts +69 -0
- package/dist/op/activities/reconcile.d.ts.map +1 -0
- package/dist/op/activities/shell.d.ts +13 -0
- package/dist/op/activities/shell.d.ts.map +1 -0
- package/dist/op/activities/teardown.d.ts +10 -0
- package/dist/op/activities/teardown.d.ts.map +1 -0
- package/dist/op/activities/util.d.ts +8 -0
- package/dist/op/activities/util.d.ts.map +1 -0
- package/dist/op/activities/wait.d.ts +16 -0
- package/dist/op/activities/wait.d.ts.map +1 -0
- package/dist/op/activities/workflow-audit.d.ts +92 -0
- package/dist/op/activities/workflow-audit.d.ts.map +1 -0
- package/dist/op/serializer.d.ts +20 -0
- package/dist/op/serializer.d.ts.map +1 -0
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/resources.d.ts +101 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/rules/{tmp001.ts → tmp001-retention-too-short.ts} +9 -11
- package/dist/rules/{tmp002.ts → tmp002-allowall-without-note.ts} +9 -11
- package/dist/rules/tmp011-namespace-reference.ts +2 -2
- package/dist/serializer.d.ts +15 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +18 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +27 -6
- package/src/lint/post-synth/index.ts +13 -0
- package/src/lint/post-synth/post-synth.test.ts +124 -1
- package/src/lint/{rules/tmp001.ts → post-synth/tmp001-retention-too-short.ts} +9 -11
- package/src/lint/{rules/tmp002.ts → post-synth/tmp002-allowall-without-note.ts} +9 -11
- package/src/lint/post-synth/tmp011-namespace-reference.ts +2 -2
- package/src/op/serializer.ts +2 -2
- package/src/plugin.test.ts +5 -6
- package/src/plugin.ts +2 -12
- package/src/serializer.ts +6 -6
- package/src/lint/rules/index.ts +0 -2
- 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 {
|
|
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:
|
|
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(
|
|
27
|
-
const diagnostics:
|
|
24
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
25
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
28
26
|
|
|
29
|
-
for (const [name, entity] of
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
10
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
11
|
|
|
12
|
-
export const tmp002:
|
|
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(
|
|
19
|
-
const diagnostics:
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
20
18
|
|
|
21
|
-
for (const [name, entity] of
|
|
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
|
-
|
|
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
|
-
|
|
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 @@
|
|
|
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.
|
|
3
|
+
"version": "0.8.1",
|
|
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
|
-
".":
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
57
|
+
"@intentius/chant": "^0.8.1"
|
|
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 {
|
|
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:
|
|
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(
|
|
27
|
-
const diagnostics:
|
|
24
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
25
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
28
26
|
|
|
29
|
-
for (const [name, entity] of
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
10
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
11
|
|
|
12
|
-
export const tmp002:
|
|
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(
|
|
19
|
-
const diagnostics:
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
20
18
|
|
|
21
|
-
for (const [name, entity] of
|
|
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
|
-
|
|
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
|
-
|
|
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 ?? {};
|
package/src/op/serializer.ts
CHANGED
|
@@ -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.");
|
package/src/plugin.test.ts
CHANGED
|
@@ -20,12 +20,11 @@ describe("temporal plugin", () => {
|
|
|
20
20
|
expect(temporalPlugin.serializer.name).toBe("temporal");
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it("
|
|
24
|
-
const
|
|
25
|
-
expect(Array.isArray(
|
|
26
|
-
|
|
27
|
-
|
|
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 {
|
|
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
|
|
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
|
}
|
package/src/lint/rules/index.ts
DELETED