@intentius/chant 0.107.1 → 0.108.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.
Files changed (55) hide show
  1. package/dist/cli/handlers/generate.d.ts +17 -0
  2. package/dist/cli/handlers/generate.d.ts.map +1 -0
  3. package/dist/cli/main.d.ts.map +1 -1
  4. package/dist/cli/plugins.d.ts.map +1 -1
  5. package/dist/cli/registry.d.ts +12 -0
  6. package/dist/cli/registry.d.ts.map +1 -1
  7. package/dist/codegen/json-schema-to-ts.d.ts +61 -0
  8. package/dist/codegen/json-schema-to-ts.d.ts.map +1 -0
  9. package/dist/config.d.ts +15 -0
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/lexicon.d.ts +13 -0
  12. package/dist/lexicon.d.ts.map +1 -1
  13. package/dist/project-codegen.d.ts +145 -0
  14. package/dist/project-codegen.d.ts.map +1 -0
  15. package/dist/workspace/ci-green-cli.d.ts +17 -0
  16. package/dist/workspace/ci-green-cli.d.ts.map +1 -0
  17. package/dist/workspace/ci-green-forge.d.ts +54 -0
  18. package/dist/workspace/ci-green-forge.d.ts.map +1 -0
  19. package/dist/workspace/ci-green-workflow.d.ts +107 -0
  20. package/dist/workspace/ci-green-workflow.d.ts.map +1 -0
  21. package/dist/workspace/ci-green.d.ts +191 -0
  22. package/dist/workspace/ci-green.d.ts.map +1 -0
  23. package/dist/workspace/declaration.d.ts +31 -0
  24. package/dist/workspace/declaration.d.ts.map +1 -1
  25. package/dist/workspace/declaration.schema.json +102 -0
  26. package/dist/workspace/ls.d.ts +15 -0
  27. package/dist/workspace/ls.d.ts.map +1 -1
  28. package/dist/workspace/reason-codes.d.ts +2 -0
  29. package/dist/workspace/reason-codes.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/cli/handlers/generate.ts +90 -0
  32. package/src/cli/main.ts +53 -0
  33. package/src/cli/plugins.ts +16 -2
  34. package/src/cli/registry.ts +12 -0
  35. package/src/codegen/json-schema-to-ts.test.ts +256 -0
  36. package/src/codegen/json-schema-to-ts.ts +373 -0
  37. package/src/config.ts +16 -0
  38. package/src/lexicon.ts +20 -0
  39. package/src/project-codegen.test.ts +208 -0
  40. package/src/project-codegen.ts +296 -0
  41. package/src/workspace/ci-green-cli.ts +118 -0
  42. package/src/workspace/ci-green-forge.test.ts +66 -0
  43. package/src/workspace/ci-green-forge.ts +111 -0
  44. package/src/workspace/ci-green-workflow.test.ts +288 -0
  45. package/src/workspace/ci-green-workflow.ts +252 -0
  46. package/src/workspace/ci-green.test.ts +401 -0
  47. package/src/workspace/ci-green.ts +424 -0
  48. package/src/workspace/ci-last-green.schema.json +158 -0
  49. package/src/workspace/declaration.schema.json +102 -0
  50. package/src/workspace/declaration.ts +64 -0
  51. package/src/workspace/ls.schema.json +34 -0
  52. package/src/workspace/ls.ts +22 -0
  53. package/src/workspace/read-contract.test.ts +2 -1
  54. package/src/workspace/reason-codes.test.ts +2 -0
  55. package/src/workspace/reason-codes.ts +3 -0
@@ -166,6 +166,19 @@
166
166
  }
167
167
  }
168
168
  }
169
+ },
170
+ "ci": {
171
+ "type": "object",
172
+ "additionalProperties": false,
173
+ "patternProperties": {
174
+ "^x-": true
175
+ },
176
+ "description": "What the workspace's CI decides about its commits (#3573, ws-103). Added in schema 1 by chant 0.108.0, so a declaration that uses it sets minReader to 0.108.0 or newer.",
177
+ "properties": {
178
+ "green": {
179
+ "$ref": "#/$defs/ciGreen"
180
+ }
181
+ }
169
182
  }
170
183
  },
171
184
  "patternProperties": {
@@ -1397,6 +1410,95 @@
1397
1410
  "^x-": true
1398
1411
  },
1399
1412
  "additionalProperties": false
1413
+ },
1414
+ "ciGreen": {
1415
+ "type": "object",
1416
+ "description": "Which commits passed CI (#3573, ws-103). chant ci tick tags each first-parent commit of branch, within window, whose required phases all passed with ci/green/<sha>, and a green commit that later fails a required phase with ci/revoked/<sha>. Both are annotated tags made once and never moved or deleted by chant. chant ci last-green prints the newest commit with a green tag and no revoked tag.",
1417
+ "required": [
1418
+ "branch",
1419
+ "phases",
1420
+ "require"
1421
+ ],
1422
+ "properties": {
1423
+ "branch": {
1424
+ "type": "string",
1425
+ "minLength": 1,
1426
+ "pattern": "^[^\\s~^:?*\\[\\\\]+$",
1427
+ "description": "The branch whose commits are tagged, such as main. Only its first-parent commits count."
1428
+ },
1429
+ "window": {
1430
+ "type": "string",
1431
+ "pattern": "^[1-9][0-9]*(m|h|d)$",
1432
+ "description": "How far back a tick looks, by committer time, such as 24h, 90m or 7d. 24h when absent."
1433
+ },
1434
+ "phases": {
1435
+ "type": "object",
1436
+ "minProperties": 1,
1437
+ "description": "The phases, by name. Each is a list of check-run name patterns, or { runs, skipped }. A pattern matches a check run's whole name, with * standing for any run of characters. A phase passes when every check run its patterns match finished with success, judged by each run's latest attempt.",
1438
+ "propertyNames": {
1439
+ "$ref": "#/$defs/ciPhaseName"
1440
+ },
1441
+ "additionalProperties": {
1442
+ "$ref": "#/$defs/ciPhase"
1443
+ }
1444
+ },
1445
+ "require": {
1446
+ "type": "array",
1447
+ "minItems": 1,
1448
+ "uniqueItems": true,
1449
+ "description": "The phases a commit must pass to be tagged green, by name. Each must be a declared phase.",
1450
+ "items": {
1451
+ "$ref": "#/$defs/ciPhaseName"
1452
+ }
1453
+ }
1454
+ },
1455
+ "patternProperties": {
1456
+ "^x-": true
1457
+ },
1458
+ "additionalProperties": false
1459
+ },
1460
+ "ciPhaseName": {
1461
+ "type": "string",
1462
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$"
1463
+ },
1464
+ "ciPhase": {
1465
+ "oneOf": [
1466
+ {
1467
+ "$ref": "#/$defs/ciRunPatterns"
1468
+ },
1469
+ {
1470
+ "type": "object",
1471
+ "required": [
1472
+ "runs"
1473
+ ],
1474
+ "properties": {
1475
+ "runs": {
1476
+ "$ref": "#/$defs/ciRunPatterns"
1477
+ },
1478
+ "skipped": {
1479
+ "enum": [
1480
+ "pass",
1481
+ "fail"
1482
+ ],
1483
+ "description": "What a check run that finished as skipped counts as. fail (the default): it never passes. pass: it passes, for a job a path filter runs only on some commits."
1484
+ }
1485
+ },
1486
+ "patternProperties": {
1487
+ "^x-": true
1488
+ },
1489
+ "additionalProperties": false
1490
+ }
1491
+ ]
1492
+ },
1493
+ "ciRunPatterns": {
1494
+ "type": "array",
1495
+ "minItems": 1,
1496
+ "uniqueItems": true,
1497
+ "items": {
1498
+ "type": "string",
1499
+ "minLength": 1,
1500
+ "description": "A check run's name, such as lint, or a pattern with *, such as e2e (*)."
1501
+ }
1400
1502
  }
1401
1503
  }
1402
1504
  }
@@ -180,6 +180,40 @@ export interface AgentDeclaration {
180
180
  pointer: string;
181
181
  }
182
182
 
183
+ /** What a check run that finished as skipped counts as in a CI phase (#3573). */
184
+ export type CiSkipped = "pass" | "fail";
185
+
186
+ /** One phase of `ci.green`: the check runs that make it up (#3573, ws-103). */
187
+ export interface CiPhase {
188
+ name: string;
189
+ /** Check-run name patterns, `*` standing for any run of characters. */
190
+ runs: string[];
191
+ /** `fail` (the default): a skipped run never passes. `pass`: it passes, for a job a path filter runs only on some commits. */
192
+ skipped: CiSkipped;
193
+ pointer: string;
194
+ }
195
+
196
+ /** The default `ci.green.window`. */
197
+ export const DEFAULT_CI_WINDOW = "24h";
198
+
199
+ /** The declaration's `ci.green` block (#3573, ws-103), with its defaults. */
200
+ export interface CiGreen {
201
+ /** The branch whose first-parent commits are tagged. */
202
+ branch: string;
203
+ /** How far back a tick looks, such as `24h`. */
204
+ window: string;
205
+ /** The phases, in file order. */
206
+ phases: CiPhase[];
207
+ /** The phases a commit must pass, by name, in file order. */
208
+ require: string[];
209
+ pointer: string;
210
+ }
211
+
212
+ /** The declaration's `ci` block (#3573). */
213
+ export interface CiDeclaration {
214
+ green: CiGreen | null;
215
+ }
216
+
183
217
  export interface MemberRole {
184
218
  name: string;
185
219
  /** Relative to the member's directory, or null for the whole member. */
@@ -546,6 +580,8 @@ export interface Declaration {
546
580
  agents: AgentDeclaration[];
547
581
  /** Who a person-attributed write may name, and which gates need a signed approval (#3163), or null when the declaration has no `identity` block. */
548
582
  identity: IdentityPolicy | null;
583
+ /** What the workspace's CI decides about its commits (#3573), or null when the declaration has no `ci` block. */
584
+ ci: CiDeclaration | null;
549
585
  /** The file, relative to the workspace root's tree (`chant.workspace.json` or `.jsonc`). */
550
586
  file: string;
551
587
  }
@@ -900,6 +936,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
900
936
  const agents = agentsOf(obj.agents, members, at);
901
937
  const tierSession = factoryTierSessionProblem(members, agents);
902
938
  if (tierSession) throw new WorkspaceReadError("declaration-invalid", tierSession.message, at(tierSession.pointer));
939
+ const ci = ciOf(obj.ci, at);
903
940
 
904
941
  const pins = ((obj.pins as Record<string, string>[] | undefined) ?? []).map((p) => ({
905
942
  package: p.package ?? null,
@@ -925,6 +962,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
925
962
  writeScope,
926
963
  agents,
927
964
  identity: identityOf(obj.identity),
965
+ ci,
928
966
  file,
929
967
  };
930
968
  }
@@ -973,6 +1011,32 @@ function identityOf(raw: unknown): IdentityPolicy | null {
973
1011
  return { attribution: block.attribution ?? "any", gates };
974
1012
  }
975
1013
 
1014
+ /**
1015
+ * The `ci` block, already validated by the schema, with its defaults and the
1016
+ * rule the schema can't say (#3573): every phase `require` names is declared.
1017
+ */
1018
+ function ciOf(raw: unknown, at: (pointer: string, key?: boolean) => ErrorLocation): CiDeclaration | null {
1019
+ if (raw === undefined) return null;
1020
+ const block = raw as { green?: { branch: string; window?: string; phases: Record<string, string[] | { runs: string[]; skipped?: CiSkipped }>; require: string[] } };
1021
+ if (block.green === undefined) return { green: null };
1022
+ const g = block.green;
1023
+ const phases: CiPhase[] = Object.entries(g.phases).map(([name, p]) => ({
1024
+ name,
1025
+ runs: [...(Array.isArray(p) ? p : p.runs)],
1026
+ skipped: Array.isArray(p) ? "fail" : (p.skipped ?? "fail"),
1027
+ pointer: `/ci/green/phases/${pointerToken(name)}`,
1028
+ }));
1029
+ for (const [i, name] of g.require.entries()) {
1030
+ if (phases.some((p) => p.name === name)) continue;
1031
+ throw new WorkspaceReadError(
1032
+ "declaration-invalid",
1033
+ `ci.green.require names the phase ${JSON.stringify(name)}, which ci.green.phases does not declare; declared phases: ${phases.map((p) => p.name).join(", ")}`,
1034
+ at(`/ci/green/require/${i}`),
1035
+ );
1036
+ }
1037
+ return { green: { branch: g.branch, window: g.window ?? DEFAULT_CI_WINDOW, phases, require: [...g.require], pointer: "/ci/green" } };
1038
+ }
1039
+
976
1040
  function agentsOf(raw: unknown, members: Member[], at: (pointer: string, key?: boolean) => ErrorLocation): AgentDeclaration[] {
977
1041
  const agents = ((raw as { name: string; member?: string; members?: string[]; principals?: string[] }[] | undefined) ?? []).map((a, i) => {
978
1042
  // The schema requires exactly one of member and members (ws-101).
@@ -5,6 +5,29 @@
5
5
  "description": "What `chant workspace ls --json` prints, version 1 of the read contract for listing a workspace (#2524 D15, #2534). Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists: a new code is a new contract version. Contract version 1 is written by chant 0.81.0 and newer; a reader that needs it refuses output whose `contract` it doesn't know. Every code is in the one closed list of `reason-codes.ts`.",
6
6
  "oneOf": [{ "$ref": "#/$defs/result" }, { "$ref": "#/$defs/failure" }],
7
7
  "$defs": {
8
+ "ciGreen": {
9
+ "description": "Which commits chant ci tick tags green: the first-parent commits of branch within window whose required phases all passed.",
10
+ "type": "object",
11
+ "required": ["branch", "window", "phases", "require"],
12
+ "properties": {
13
+ "branch": { "type": "string" },
14
+ "window": { "type": "string", "description": "Such as 24h, the default." },
15
+ "phases": {
16
+ "description": "In file order.",
17
+ "type": "array",
18
+ "items": {
19
+ "type": "object",
20
+ "required": ["name", "runs", "skipped"],
21
+ "properties": {
22
+ "name": { "type": "string" },
23
+ "runs": { "type": "array", "items": { "type": "string" }, "description": "Check-run name patterns, * standing for any run of characters." },
24
+ "skipped": { "enum": ["pass", "fail"], "description": "What a run that finished as skipped counts as." }
25
+ }
26
+ }
27
+ },
28
+ "require": { "type": "array", "items": { "type": "string" } }
29
+ }
30
+ },
8
31
  "result": {
9
32
  "description": "The declaration was read. Exit code 0, even when some members can't be read.",
10
33
  "type": "object",
@@ -66,6 +89,17 @@
66
89
  "type": "array",
67
90
  "items": { "$ref": "#/$defs/diagram" }
68
91
  },
92
+ "ci": {
93
+ "description": "Added in contract 1 by #3573 (ws-103), so absent from an older chant's output. The declaration's ci block with its defaults filled in, or null when it has none; green is null when the block declares no green.",
94
+ "oneOf": [
95
+ { "type": "null" },
96
+ {
97
+ "type": "object",
98
+ "required": ["green"],
99
+ "properties": { "green": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/ciGreen" }] } }
100
+ }
101
+ ]
102
+ },
69
103
  "summary": {
70
104
  "type": "object",
71
105
  "required": ["members", "unreadable", "groups", "matches"],
@@ -134,6 +134,14 @@ export interface LsDiagram {
134
134
  member: string | null;
135
135
  }
136
136
 
137
+ /** The declaration's `ci.green` (#3573, ws-103), with its defaults filled in. */
138
+ export interface LsCiGreen {
139
+ branch: string;
140
+ window: string;
141
+ phases: { name: string; runs: string[]; skipped: "pass" | "fail" }[];
142
+ require: string[];
143
+ }
144
+
137
145
  export interface LsGroup {
138
146
  name: string;
139
147
  kind: "examples";
@@ -164,6 +172,8 @@ export type LsDocument =
164
172
  groups: LsGroup[];
165
173
  /** Every declared diagram, the workspace's own first, then each member's, in file order (#2764). */
166
174
  diagrams: LsDiagram[];
175
+ /** What the workspace's CI decides about its commits (#3573), or null with no `ci` block. */
176
+ ci: { green: LsCiGreen | null } | null;
167
177
  summary: { members: number; unreadable: number; groups: number; matches: number };
168
178
  }
169
179
  | {
@@ -286,6 +296,18 @@ function readListing(query: LsQuery): { doc: LsDocument; declaration?: Declarati
286
296
  members,
287
297
  groups: lsGroups,
288
298
  diagrams,
299
+ ci: declaration.ci
300
+ ? {
301
+ green: declaration.ci.green
302
+ ? {
303
+ branch: declaration.ci.green.branch,
304
+ window: declaration.ci.green.window,
305
+ phases: declaration.ci.green.phases.map((p) => ({ name: p.name, runs: p.runs, skipped: p.skipped })),
306
+ require: declaration.ci.green.require,
307
+ }
308
+ : null,
309
+ }
310
+ : null,
289
311
  summary: {
290
312
  members: members.length,
291
313
  unreadable: members.filter((m) => !m.readable).length,
@@ -21,6 +21,7 @@ import { pathToFileURL } from "node:url";
21
21
  import { describe, expect, test } from "vitest";
22
22
  import { contract, git, REPO, validSchema } from "./__fixtures__/contract-repo";
23
23
  import checkSchema from "./check.schema.json";
24
+ import ciLastGreenSchema from "./ci-last-green.schema.json";
24
25
  import { workspaceComposites } from "./composites";
25
26
  import compositesSchema from "./composites.schema.json";
26
27
  import { workspaceGraph } from "./graph-cli";
@@ -48,7 +49,7 @@ import statusSchema from "./status.schema.json";
48
49
  const FIXTURE = join(REPO, "reference-workspace");
49
50
  const TIMEOUT = 240_000;
50
51
 
51
- const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema, "records-since": recordsSinceSchema, intent: intentSchema, "intent-record": intentRecordSchema, composites: compositesSchema, points: pointsSchema, patch: patchSchema };
52
+ const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema, "records-since": recordsSinceSchema, intent: intentSchema, "intent-record": intentRecordSchema, composites: compositesSchema, points: pointsSchema, patch: patchSchema, "ci-last-green": ciLastGreenSchema };
52
53
 
53
54
  /** This checkout's chant, started the way the CLI starts it, for members with no toolchain of their own. */
54
55
  const reader: Toolchain = {
@@ -44,6 +44,7 @@ import { BOX_LISTING_ERROR_CODES } from "./box-listing";
44
44
  import { BOX_PUBLISH_ERROR_CODES } from "./box-publish";
45
45
  import { WRITE_LOCK_ERROR_CODES } from "./write-lock-cli";
46
46
  import { WIP_ERROR_CODES, WIP_WRITE_ERROR_CODES } from "./wip";
47
+ import { CI_GREEN_ERROR_CODES } from "./ci-green";
47
48
  import { ROTATION_REFUSAL_CODES } from "./trust/rotation";
48
49
  import { SIGNERS_ERROR_CODES } from "./trust/signers-cli";
49
50
  import { EVIDENCE_ERROR_CODES } from "./trust/evidence";
@@ -111,6 +112,7 @@ const PER_COMMAND: Record<string, readonly string[]> = {
111
112
  WRITE_LOCK_ERROR_CODES,
112
113
  WIP_ERROR_CODES,
113
114
  WIP_WRITE_ERROR_CODES,
115
+ CI_GREEN_ERROR_CODES,
114
116
  ROTATION_REFUSAL_CODES,
115
117
  SIGNERS_ERROR_CODES,
116
118
  EVIDENCE_ERROR_CODES,
@@ -275,6 +275,9 @@ export const REASONS = {
275
275
  "wip-race": "Another writer moved refs/chant/wip/<branch> between this command's read and its write.",
276
276
  "wip-policy-none": "wip push or wip fetch was run, and no box block declares replicate, so there is no remote.",
277
277
  "wip-remote-unknown": "The replicate policy names a git remote the checkout does not have; the host adds it, with its credential, before chant pushes.",
278
+ // Which commits passed CI (ci last-green and ci tick, #3573): why nothing was read.
279
+ "ci-green-undeclared": "The declaration has no ci.green block, so chant does not know which check runs make a commit green.",
280
+ "ci-branch-unknown": "The checkout has no ref for the branch ci.green names: neither the remote-tracking branch nor a local one.",
278
281
  // A box whose isolation fails (check, #2727). Each is also a WSP finding.
279
282
  "box-isolation-collision": "Two boxes on one host resolve to the same port, state path or cookie name, or two ports in one box share an offset.",
280
283
  "box-isolation-literal": "A host's stateRoot or a box's state entry is a literal machine path instead of one derived from an environment reference and the box's name.",