@intentius/chant 0.107.1 → 0.108.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.
- package/dist/cli/handlers/generate.d.ts +17 -0
- package/dist/cli/handlers/generate.d.ts.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +6 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/json-schema-to-ts.d.ts +61 -0
- package/dist/codegen/json-schema-to-ts.d.ts.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon.d.ts +13 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/project-codegen.d.ts +145 -0
- package/dist/project-codegen.d.ts.map +1 -0
- package/dist/workspace/ci-green-cli.d.ts +16 -0
- package/dist/workspace/ci-green-cli.d.ts.map +1 -0
- package/dist/workspace/ci-green-forge.d.ts +54 -0
- package/dist/workspace/ci-green-forge.d.ts.map +1 -0
- package/dist/workspace/ci-green-workflow.d.ts +82 -0
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -0
- package/dist/workspace/ci-green.d.ts +185 -0
- package/dist/workspace/ci-green.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +31 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +102 -0
- package/dist/workspace/ls.d.ts +15 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +2 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/handlers/generate.ts +90 -0
- package/src/cli/main.ts +39 -0
- package/src/cli/plugins.ts +16 -2
- package/src/cli/registry.ts +6 -0
- package/src/codegen/json-schema-to-ts.test.ts +256 -0
- package/src/codegen/json-schema-to-ts.ts +373 -0
- package/src/config.ts +16 -0
- package/src/lexicon.ts +20 -0
- package/src/project-codegen.test.ts +208 -0
- package/src/project-codegen.ts +296 -0
- package/src/workspace/ci-green-cli.ts +101 -0
- package/src/workspace/ci-green-forge.test.ts +66 -0
- package/src/workspace/ci-green-forge.ts +111 -0
- package/src/workspace/ci-green-workflow.test.ts +198 -0
- package/src/workspace/ci-green-workflow.ts +215 -0
- package/src/workspace/ci-green.test.ts +375 -0
- package/src/workspace/ci-green.ts +408 -0
- package/src/workspace/ci-last-green.schema.json +158 -0
- package/src/workspace/declaration.schema.json +102 -0
- package/src/workspace/declaration.ts +64 -0
- package/src/workspace/ls.schema.json +34 -0
- package/src/workspace/ls.ts +22 -0
- package/src/workspace/read-contract.test.ts +2 -1
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +3 -0
|
@@ -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"],
|
package/src/workspace/ls.ts
CHANGED
|
@@ -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.",
|