@nimiplatform/nimi-coding 0.4.0 → 0.5.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/CHANGELOG.md +9 -0
- package/README.md +47 -35
- package/README.zh-CN.md +48 -36
- package/cli/commands/authority.mjs +80 -125
- package/cli/constants.mjs +1 -1
- package/cli/dev/check-authority-surface.mjs +17 -8
- package/cli/dev/check-product-conformance.mjs +4 -32
- package/cli/lib/authority/audit.mjs +262 -185
- package/cli/lib/authority/change-candidates.mjs +807 -0
- package/cli/lib/authority/closed-sets.mjs +7 -8
- package/cli/lib/authority/compile.mjs +6 -24
- package/cli/lib/authority/compiled-snapshot.mjs +143 -0
- package/cli/lib/authority/consumers.mjs +69 -19
- package/cli/lib/authority/diagnostics.mjs +7 -1
- package/cli/lib/authority/diff.mjs +77 -54
- package/cli/lib/authority/format.mjs +4 -32
- package/cli/lib/authority/git-snapshot.mjs +34 -379
- package/cli/lib/authority/graph.mjs +10 -10
- package/cli/lib/authority/impact.mjs +17 -144
- package/cli/lib/authority/review.mjs +33 -43
- package/cli/lib/authority/sarif.mjs +1 -8
- package/cli/lib/authority/source-yaml.mjs +0 -4
- package/cli/lib/authority/surface.mjs +19 -1
- package/cli/lib/authority/terms.mjs +10 -9
- package/cli/lib/authority/validate.mjs +6 -14
- package/cli/lib/entrypoints.mjs +2 -1
- package/contracts/authority-source.schema.yaml +0 -6
- package/contracts/authority-verifier-bindings.schema.yaml +21 -10
- package/methodology/authority-authoring.yaml +9 -26
- package/methodology/core.yaml +5 -3
- package/package.json +1 -1
- package/spec/authority-audit.yaml +56 -52
- package/spec/authority-authoring.yaml +3 -16
- package/spec/authority-change-candidates.yaml +146 -0
- package/spec/authority-consumers.yaml +7 -0
- package/spec/authority-discovery.yaml +2 -2
- package/spec/authority-graph.yaml +1 -1
- package/spec/authority-impact.yaml +16 -39
- package/spec/authority-review.yaml +29 -47
- package/spec/authority-terms.yaml +5 -1
- package/spec/product-scope.yaml +8 -7
- package/cli/lib/authority/evidence-snapshot.mjs +0 -1
- package/cli/lib/authority/evidence.mjs +0 -1432
- package/contracts/authority-evidence-bindings.schema.yaml +0 -53
- package/contracts/authority-evidence-probe-results.schema.yaml +0 -38
- package/contracts/authority-impact-dispositions.schema.yaml +0 -13
- package/spec/authority-evidence.yaml +0 -200
|
@@ -6,39 +6,36 @@ import {
|
|
|
6
6
|
refuseAnchorsOutputBudget,
|
|
7
7
|
} from "../lib/authority/anchors.mjs";
|
|
8
8
|
import { auditAuthorityPath } from "../lib/authority/audit.mjs";
|
|
9
|
+
import {
|
|
10
|
+
CHANGE_CANDIDATE_CHANNELS,
|
|
11
|
+
changeCandidatesAuthorityPaths,
|
|
12
|
+
} from "../lib/authority/change-candidates.mjs";
|
|
9
13
|
import { checkAuthorityPath, compileAuthorityPath } from "../lib/authority/compile.mjs";
|
|
10
14
|
import { closedSetsAuthorityRepository } from "../lib/authority/closed-sets.mjs";
|
|
11
15
|
import { consumersAuthorityRepository } from "../lib/authority/consumers.mjs";
|
|
12
16
|
import { diffAuthorityPaths } from "../lib/authority/diff.mjs";
|
|
13
17
|
import { discoverAuthorityPath } from "../lib/authority/discover.mjs";
|
|
14
|
-
import { evidenceAuthorityRepository } from "../lib/authority/evidence.mjs";
|
|
15
18
|
import { AuthorityInputError, formatAuthorityFile } from "../lib/authority/format.mjs";
|
|
16
19
|
import { pathAuthorityPath, refsAuthorityPath, subgraphAuthorityPath } from "../lib/authority/graph.mjs";
|
|
17
20
|
import { impactAuthorityPaths } from "../lib/authority/impact.mjs";
|
|
18
21
|
import { contextAuthorityPath, queryAuthorityPath } from "../lib/authority/query.mjs";
|
|
19
22
|
import { reviewAuthorityRepository } from "../lib/authority/review.mjs";
|
|
20
23
|
import { authorityAuditResultToSarif } from "../lib/authority/sarif.mjs";
|
|
21
|
-
import { loadAuthorityContract } from "../lib/authority/source-yaml.mjs";
|
|
22
24
|
import { AUTHORING_SURFACE, AUTHORITY_SURFACE, integerFlagAdmission, booleanFlagAdmission } from "../lib/authority/surface.mjs";
|
|
23
25
|
import { AUTHORITY_TERM_GRAMMAR_HELP, termsAuthorityRepository } from "../lib/authority/terms.mjs";
|
|
24
26
|
|
|
25
|
-
const AUTHORITY_SOURCE_CONTRACT = await loadAuthorityContract();
|
|
26
|
-
const ADMITTED_AUTHORITY_FORMATS = new Set([
|
|
27
|
-
AUTHORITY_SOURCE_CONTRACT.format,
|
|
28
|
-
AUTHORITY_SOURCE_CONTRACT.migration_inputs?.format,
|
|
29
|
-
].filter((format) => typeof format === "string"));
|
|
30
|
-
|
|
31
27
|
// The single source for the authority command list; the root help renders it
|
|
32
28
|
// rather than keeping a second copy that drifts.
|
|
33
29
|
export const AUTHORITY_COMMAND_USAGE = [
|
|
34
|
-
"nimicoding authority fmt <file> [--check] [--
|
|
35
|
-
"nimicoding authority check <path> [--scope-bindings <file>] [--
|
|
30
|
+
"nimicoding authority fmt <file> [--check] [--json]",
|
|
31
|
+
"nimicoding authority check <path> [--scope-bindings <file>] [--json]",
|
|
36
32
|
"nimicoding authority compile <path> [--json]",
|
|
37
33
|
"nimicoding authority anchors <repository-path> --spec <corpus-path> [--scope-bindings <file>] [--embedded-paths] --max-units <positive-safe-integer> --max-anchors <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
38
34
|
"nimicoding authority consumers <repository-path> --spec <corpus-path> [--external-root <scheme>=<path>]... --max-files <positive-safe-integer> --max-refs <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
39
35
|
"nimicoding authority closed-sets <repository-path> --spec <corpus-path> --max-units <positive-safe-integer> --max-sets <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
40
36
|
"nimicoding authority terms <repository-path> --spec <corpus-path> [--term <t>] [--code] --max-units <positive-safe-integer> --max-terms <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
41
37
|
"nimicoding authority discover <path> <query> [--kind <definition|rule>] [--owner <exact-owner>] [--scope <exact-scope>] [--lifecycle <active|removed>] --max-candidates <positive-safe-integer> --max-snippet-terms <positive-safe-integer> --max-bytes <positive-safe-integer> [--preview-direction <incoming|outgoing|both> --relations <comma-separated-relation-types> --max-edges <positive-safe-integer>] [--json]",
|
|
38
|
+
"nimicoding authority change-candidates <before-path> <after-path> --channels <comma-separated-closed-set> --max-candidates <positive-safe-integer> --max-reasons <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
42
39
|
"nimicoding authority query <path> <id> --max-bytes <positive-integer> [--json]",
|
|
43
40
|
"nimicoding authority context <path> <id> --max-units <positive-integer> --max-bytes <positive-integer> [--json]",
|
|
44
41
|
"nimicoding authority refs <path> <id> --direction <incoming|outgoing|both> --relations <comma-separated-relation-types> --max-units <positive-safe-integer> --max-edges <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
@@ -46,9 +43,8 @@ export const AUTHORITY_COMMAND_USAGE = [
|
|
|
46
43
|
"nimicoding authority subgraph <path> <id> --direction <incoming|outgoing|both> --relations <comma-separated-relation-types> --depth <positive-safe-integer> --max-units <positive-safe-integer> --max-edges <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
47
44
|
"nimicoding authority audit <path> --bindings <file> --max-units <positive-safe-integer> --max-edges <positive-safe-integer> --max-bytes <positive-safe-integer> [--json|--sarif]",
|
|
48
45
|
"nimicoding authority diff <before-path> <after-path> --max-bytes <positive-integer> [--json]",
|
|
49
|
-
"nimicoding authority impact <before-path> <after-path> --
|
|
50
|
-
"nimicoding authority review <repository-path> --base <git-ref> --bindings <file> --
|
|
51
|
-
"nimicoding authority evidence <repository-path> --bindings <tracked-.nimi/config-path> [--probe-results <.nimi/local-path>] --max-units <positive-safe-integer> --max-bindings <positive-safe-integer> --max-locators <positive-safe-integer> --max-edges <positive-safe-integer> --max-input-bytes <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
46
|
+
"nimicoding authority impact <before-path> <after-path> --max-bytes <positive-integer> [--json]",
|
|
47
|
+
"nimicoding authority review <repository-path> --base <git-ref> --bindings <file> --max-units <positive-safe-integer> --max-edges <positive-safe-integer> --max-bytes <positive-safe-integer> [--json]",
|
|
52
48
|
];
|
|
53
49
|
|
|
54
50
|
const USAGE = [
|
|
@@ -62,26 +58,27 @@ const USAGE = [
|
|
|
62
58
|
const AUTHORITY_IDENTIFIER = /^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z](?:[a-z0-9-]*[a-z0-9])?)+$/;
|
|
63
59
|
|
|
64
60
|
function parseOptions(subcommand, args) {
|
|
65
|
-
const options = { json: false, sarif: false, check: false,
|
|
61
|
+
const options = { json: false, sarif: false, check: false, embeddedPaths: false, code: false, path: null, repositoryPath: null, spec: null, term: null, base: null, id: null, fromId: null, toId: null, query: null, beforePath: null, afterPath: null, bindings: null, scopeBindings: null, kind: null, owner: null, scope: null, lifecycle: null, direction: null, previewDirection: null, traversal: null, relations: null, channels: null, depth: null, maxHops: null, maxCandidates: null, maxReasons: null, maxSnippetTerms: null, maxUnits: null, maxTerms: null, maxSets: null, maxAnchors: null, maxFiles: null, maxRefs: null, maxEdges: null, maxBytes: null, externalRoots: [] };
|
|
66
62
|
const graphCommands = ["refs", "path", "subgraph"];
|
|
67
63
|
const reportIntegerAdmission = integerFlagAdmission();
|
|
68
64
|
const reportIntegerCommands = (flag) => reportIntegerAdmission[flag] ?? [];
|
|
65
|
+
const surfaceValueCommands = (flag) => Object.entries(AUTHORITY_SURFACE)
|
|
66
|
+
.filter(([, entry]) => entry.valueFlags.includes(flag))
|
|
67
|
+
.map(([command]) => command);
|
|
69
68
|
const integerOptions = {
|
|
70
|
-
"--max-candidates": ["maxCandidates", ["discover"]],
|
|
69
|
+
"--max-candidates": ["maxCandidates", ["discover", "change-candidates"]],
|
|
70
|
+
"--max-reasons": ["maxReasons", ["change-candidates"]],
|
|
71
71
|
"--max-snippet-terms": ["maxSnippetTerms", ["discover"]],
|
|
72
72
|
// The report products' integer flags come from the enforced surface table,
|
|
73
73
|
// so the parser and the product contracts cannot drift apart silently.
|
|
74
|
-
"--max-units": ["maxUnits", ["context", "
|
|
74
|
+
"--max-units": ["maxUnits", ["context", "review", ...reportIntegerCommands("--max-units"), ...graphCommands]],
|
|
75
75
|
"--max-terms": ["maxTerms", reportIntegerCommands("--max-terms")],
|
|
76
76
|
"--max-sets": ["maxSets", reportIntegerCommands("--max-sets")],
|
|
77
77
|
"--max-anchors": ["maxAnchors", reportIntegerCommands("--max-anchors")],
|
|
78
78
|
"--max-files": ["maxFiles", reportIntegerCommands("--max-files")],
|
|
79
79
|
"--max-refs": ["maxRefs", reportIntegerCommands("--max-refs")],
|
|
80
|
-
"--max-
|
|
81
|
-
"--max-
|
|
82
|
-
"--max-edges": ["maxEdges", ["audit", "review", "evidence", "discover", ...graphCommands]],
|
|
83
|
-
"--max-input-bytes": ["maxInputBytes", ["evidence"]],
|
|
84
|
-
"--max-bytes": ["maxBytes", ["discover", "query", "context", "audit", "diff", "impact", "review", "evidence", "anchors", "consumers", "terms", "closed-sets", ...graphCommands]],
|
|
80
|
+
"--max-edges": ["maxEdges", ["review", "discover", ...reportIntegerCommands("--max-edges"), ...graphCommands]],
|
|
81
|
+
"--max-bytes": ["maxBytes", ["discover", "change-candidates", "query", "context", "diff", "impact", "review", ...reportIntegerCommands("--max-bytes"), ...graphCommands]],
|
|
85
82
|
"--max-hops": ["maxHops", ["path"]],
|
|
86
83
|
"--depth": ["depth", ["subgraph"]],
|
|
87
84
|
};
|
|
@@ -94,14 +91,13 @@ function parseOptions(subcommand, args) {
|
|
|
94
91
|
continue;
|
|
95
92
|
}
|
|
96
93
|
if (!terminated && arg.startsWith("--")) {
|
|
97
|
-
if (arg === "--json" && !options.json) {
|
|
94
|
+
if (arg === "--json" && !options.json && (AUTHORITY_SURFACE[subcommand]?.booleanFlags ?? ["json"]).includes("json")) {
|
|
98
95
|
if (options.sarif) return { ok: false, error: "authority audit requires --json and --sarif to be mutually exclusive" };
|
|
99
96
|
options.json = true;
|
|
100
|
-
} else if (arg === "--sarif" && subcommand
|
|
97
|
+
} else if (arg === "--sarif" && AUTHORITY_SURFACE[subcommand]?.booleanFlags.includes("sarif") && !options.sarif) {
|
|
101
98
|
if (options.json) return { ok: false, error: "authority audit requires --json and --sarif to be mutually exclusive" };
|
|
102
99
|
options.sarif = true;
|
|
103
100
|
} else if (arg === "--check" && AUTHORING_SURFACE[subcommand]?.booleanFlags.includes("check") && !options.check) options.check = true;
|
|
104
|
-
else if (arg === "--upgrade" && AUTHORING_SURFACE[subcommand]?.booleanFlags.includes("upgrade") && !options.upgrade) options.upgrade = true;
|
|
105
101
|
else if (arg === "--embedded-paths" && booleanFlagAdmission("embedded-paths").includes(subcommand) && !options.embeddedPaths) options.embeddedPaths = true;
|
|
106
102
|
else if (arg === "--code" && booleanFlagAdmission("code").includes(subcommand) && !options.code) options.code = true;
|
|
107
103
|
else if (arg === "--external-root" && (AUTHORITY_SURFACE[subcommand]?.repeatableFlags ?? []).includes("external-root")) {
|
|
@@ -156,6 +152,18 @@ function parseOptions(subcommand, args) {
|
|
|
156
152
|
if (!["directed", "incidence"].includes(value)) return { ok: false, error: "authority path requires --traversal directed or incidence" };
|
|
157
153
|
options.traversal = value;
|
|
158
154
|
index += 1;
|
|
155
|
+
} else if (arg === "--channels" && subcommand === "change-candidates" && options.channels === null) {
|
|
156
|
+
const value = args[index + 1];
|
|
157
|
+
const channels = value?.split(",") ?? [];
|
|
158
|
+
if (
|
|
159
|
+
channels.length === 0
|
|
160
|
+
|| channels.some((entry) => entry.length === 0 || !CHANGE_CANDIDATE_CHANNELS.includes(entry))
|
|
161
|
+
|| new Set(channels).size !== channels.length
|
|
162
|
+
) {
|
|
163
|
+
return { ok: false, error: `authority change-candidates requires a non-empty unique closed --channels set containing only ${CHANGE_CANDIDATE_CHANNELS.join(", ")}` };
|
|
164
|
+
}
|
|
165
|
+
options.channels = channels;
|
|
166
|
+
index += 1;
|
|
159
167
|
} else if (arg === "--relations" && [...graphCommands, "discover"].includes(subcommand) && options.relations === null) {
|
|
160
168
|
const value = args[index + 1];
|
|
161
169
|
const relations = value?.split(",") ?? [];
|
|
@@ -164,12 +172,7 @@ function parseOptions(subcommand, args) {
|
|
|
164
172
|
}
|
|
165
173
|
options.relations = relations.sort();
|
|
166
174
|
index += 1;
|
|
167
|
-
} else if (arg === "--
|
|
168
|
-
const value = args[index + 1];
|
|
169
|
-
if (!value || value.startsWith("--")) return { ok: false, error: `authority ${subcommand} requires --dispositions followed by one file` };
|
|
170
|
-
options.dispositions = value;
|
|
171
|
-
index += 1;
|
|
172
|
-
} else if (arg === "--bindings" && ["audit", "review", "evidence"].includes(subcommand) && options.bindings === null) {
|
|
175
|
+
} else if (arg === "--bindings" && ["review", ...surfaceValueCommands("bindings")].includes(subcommand) && options.bindings === null) {
|
|
173
176
|
const value = args[index + 1];
|
|
174
177
|
if (!value || value.startsWith("--")) return { ok: false, error: `authority ${subcommand} requires --bindings followed by one file` };
|
|
175
178
|
options.bindings = value;
|
|
@@ -179,13 +182,6 @@ function parseOptions(subcommand, args) {
|
|
|
179
182
|
if (!value || value.startsWith("--")) return { ok: false, error: `authority ${subcommand} requires --scope-bindings followed by one file` };
|
|
180
183
|
options.scopeBindings = value;
|
|
181
184
|
index += 1;
|
|
182
|
-
} else if (arg === "--require-format" && subcommand === "check" && options.requireFormat === null) {
|
|
183
|
-
const value = args[index + 1];
|
|
184
|
-
if (!ADMITTED_AUTHORITY_FORMATS.has(value)) {
|
|
185
|
-
return { ok: false, error: `authority check requires --require-format followed by an admitted source format: ${[...ADMITTED_AUTHORITY_FORMATS].join(" or ")}` };
|
|
186
|
-
}
|
|
187
|
-
options.requireFormat = value;
|
|
188
|
-
index += 1;
|
|
189
185
|
} else if (arg === "--spec" && ["anchors", "consumers", "terms", "closed-sets"].includes(subcommand) && options.spec === null) {
|
|
190
186
|
const value = args[index + 1];
|
|
191
187
|
if (!value || value.startsWith("--")) return { ok: false, error: `authority ${subcommand} requires --spec followed by one corpus path` };
|
|
@@ -196,11 +192,6 @@ function parseOptions(subcommand, args) {
|
|
|
196
192
|
if (!value || value.startsWith("--") || /[\0\r\n]/u.test(value)) return { ok: false, error: "authority terms requires --term followed by one non-empty single-line term" };
|
|
197
193
|
options.term = value;
|
|
198
194
|
index += 1;
|
|
199
|
-
} else if (arg === "--probe-results" && subcommand === "evidence" && options.probeResults === null) {
|
|
200
|
-
const value = args[index + 1];
|
|
201
|
-
if (!value || value.startsWith("--")) return { ok: false, error: "authority evidence requires --probe-results followed by one repository-relative file" };
|
|
202
|
-
options.probeResults = value;
|
|
203
|
-
index += 1;
|
|
204
195
|
} else if (arg === "--base" && subcommand === "review" && options.base === null) {
|
|
205
196
|
const value = args[index + 1];
|
|
206
197
|
if (!value || value.startsWith("-")) return { ok: false, error: "authority review requires --base followed by one non-option Git ref" };
|
|
@@ -211,17 +202,17 @@ function parseOptions(subcommand, args) {
|
|
|
211
202
|
}
|
|
212
203
|
positionals.push(arg);
|
|
213
204
|
}
|
|
214
|
-
const expected = subcommand === "path" ? 3 : ["discover", "query", "context", "refs", "subgraph", "diff", "impact"].includes(subcommand) ? 2 : 1;
|
|
205
|
+
const expected = subcommand === "path" ? 3 : ["discover", "change-candidates", "query", "context", "refs", "subgraph", "diff", "impact"].includes(subcommand) ? 2 : 1;
|
|
215
206
|
if (positionals.length !== expected) {
|
|
216
207
|
const requirement = subcommand === "discover"
|
|
217
208
|
? "one path and one query"
|
|
218
|
-
: ["diff", "impact"].includes(subcommand)
|
|
209
|
+
: ["change-candidates", "diff", "impact"].includes(subcommand)
|
|
219
210
|
? "one before path and one after path"
|
|
220
|
-
: subcommand === "path" ? "one path, one exact from ID, and one exact to ID" : ["review", "
|
|
211
|
+
: subcommand === "path" ? "one path, one exact from ID, and one exact to ID" : ["review", "anchors", "consumers", "terms", "closed-sets"].includes(subcommand) ? "one repository path" : expected === 1 ? "one path" : "one path and one exact ID";
|
|
221
212
|
return { ok: false, error: `authority ${subcommand} requires exactly ${requirement}` };
|
|
222
213
|
}
|
|
223
|
-
if (["diff", "impact"].includes(subcommand)) [options.beforePath, options.afterPath] = positionals;
|
|
224
|
-
else if (["review", "
|
|
214
|
+
if (["change-candidates", "diff", "impact"].includes(subcommand)) [options.beforePath, options.afterPath] = positionals;
|
|
215
|
+
else if (["review", "anchors", "consumers", "terms", "closed-sets"].includes(subcommand)) [options.repositoryPath] = positionals;
|
|
225
216
|
else if (subcommand === "path") [options.path, options.fromId, options.toId] = positionals;
|
|
226
217
|
else [options.path, options.id] = positionals;
|
|
227
218
|
if (subcommand === "discover") {
|
|
@@ -235,29 +226,24 @@ function parseOptions(subcommand, args) {
|
|
|
235
226
|
const provided = previewOptions.filter((value) => value !== null).length;
|
|
236
227
|
if (provided !== 0 && provided !== previewOptions.length) return { ok: false, error: "authority discover requires --preview-direction, --relations, and --max-edges together" };
|
|
237
228
|
}
|
|
229
|
+
if (subcommand === "change-candidates" && options.channels === null) return { ok: false, error: "authority change-candidates requires --channels followed by a non-empty closed comma-separated set" };
|
|
230
|
+
if (subcommand === "change-candidates" && options.maxCandidates === null) return { ok: false, error: "authority change-candidates requires --max-candidates followed by a positive integer" };
|
|
231
|
+
if (subcommand === "change-candidates" && options.maxReasons === null) return { ok: false, error: "authority change-candidates requires --max-reasons followed by a positive integer" };
|
|
238
232
|
if (["context", "anchors", "terms", "closed-sets", ...graphCommands].includes(subcommand) && options.maxUnits === null) return { ok: false, error: `authority ${subcommand} requires --max-units followed by a positive integer` };
|
|
239
233
|
if (graphCommands.includes(subcommand) && options.maxEdges === null) return { ok: false, error: `authority ${subcommand} requires --max-edges followed by a positive integer` };
|
|
240
|
-
if (["discover", "query", "context", "audit", "diff", "impact", "review", "
|
|
234
|
+
if (["discover", "change-candidates", "query", "context", "audit", "diff", "impact", "review", "anchors", "consumers", "terms", "closed-sets", ...graphCommands].includes(subcommand) && options.maxBytes === null) return { ok: false, error: `authority ${subcommand} requires --max-bytes followed by a positive integer` };
|
|
241
235
|
if (["refs", "subgraph"].includes(subcommand) && options.direction === null) return { ok: false, error: `authority ${subcommand} requires --direction` };
|
|
242
236
|
if (subcommand === "path" && options.traversal === null) return { ok: false, error: "authority path requires --traversal" };
|
|
243
237
|
if (graphCommands.includes(subcommand) && options.relations === null) return { ok: false, error: `authority ${subcommand} requires --relations` };
|
|
244
238
|
if (subcommand === "path" && options.maxHops === null) return { ok: false, error: "authority path requires --max-hops followed by a positive integer" };
|
|
245
239
|
if (subcommand === "subgraph" && options.depth === null) return { ok: false, error: "authority subgraph requires --depth followed by a positive integer" };
|
|
246
|
-
if (subcommand === "impact" && options.dispositions === null) return { ok: false, error: "authority impact requires --dispositions followed by one file" };
|
|
247
240
|
if (subcommand === "audit" && options.bindings === null) return { ok: false, error: "authority audit requires --bindings followed by one file" };
|
|
248
241
|
if (subcommand === "audit" && options.maxUnits === null) return { ok: false, error: "authority audit requires --max-units followed by a positive integer" };
|
|
249
242
|
if (subcommand === "audit" && options.maxEdges === null) return { ok: false, error: "authority audit requires --max-edges followed by a positive integer" };
|
|
250
243
|
if (subcommand === "review" && options.base === null) return { ok: false, error: "authority review requires --base followed by one Git ref" };
|
|
251
244
|
if (subcommand === "review" && options.bindings === null) return { ok: false, error: "authority review requires --bindings followed by one file" };
|
|
252
|
-
if (subcommand === "review" && options.dispositions === null) return { ok: false, error: "authority review requires --dispositions followed by one file" };
|
|
253
245
|
if (subcommand === "review" && options.maxUnits === null) return { ok: false, error: "authority review requires --max-units followed by a positive integer" };
|
|
254
246
|
if (subcommand === "review" && options.maxEdges === null) return { ok: false, error: "authority review requires --max-edges followed by a positive integer" };
|
|
255
|
-
if (subcommand === "evidence" && options.bindings === null) return { ok: false, error: "authority evidence requires --bindings followed by one repository-relative file" };
|
|
256
|
-
if (subcommand === "evidence" && options.maxUnits === null) return { ok: false, error: "authority evidence requires --max-units followed by a positive integer" };
|
|
257
|
-
if (subcommand === "evidence" && options.maxBindings === null) return { ok: false, error: "authority evidence requires --max-bindings followed by a positive integer" };
|
|
258
|
-
if (subcommand === "evidence" && options.maxLocators === null) return { ok: false, error: "authority evidence requires --max-locators followed by a positive integer" };
|
|
259
|
-
if (subcommand === "evidence" && options.maxEdges === null) return { ok: false, error: "authority evidence requires --max-edges followed by a positive integer" };
|
|
260
|
-
if (subcommand === "evidence" && options.maxInputBytes === null) return { ok: false, error: "authority evidence requires --max-input-bytes followed by a positive integer" };
|
|
261
247
|
if (subcommand === "anchors" && options.spec === null) return { ok: false, error: "authority anchors requires --spec followed by one corpus path" };
|
|
262
248
|
if (subcommand === "anchors" && options.maxAnchors === null) return { ok: false, error: "authority anchors requires --max-anchors followed by a positive integer" };
|
|
263
249
|
if (subcommand === "consumers" && options.spec === null) return { ok: false, error: "authority consumers requires --spec followed by one corpus path" };
|
|
@@ -284,19 +270,17 @@ function humanAuditLocation(location) {
|
|
|
284
270
|
function formatAuditHuman(report) {
|
|
285
271
|
const audit = report.audit;
|
|
286
272
|
const operationStatus = audit?.operationStatus ?? report.semantic_status;
|
|
287
|
-
const
|
|
288
|
-
const complete = audit?.complete ?? false;
|
|
273
|
+
const blockingStatus = audit?.blockingStatus ?? "not_evaluated";
|
|
289
274
|
const lines = [
|
|
290
|
-
`nimicoding authority audit: operation=${operationStatus};
|
|
275
|
+
`nimicoding authority audit: operation=${operationStatus}; blocking=${blockingStatus}`,
|
|
291
276
|
`files: ${report.summary.files}; units: ${report.summary.units}; diagnostics: ${report.summary.diagnostics}`,
|
|
292
277
|
`audit bytes: ${report.audit_bytes}`,
|
|
293
278
|
];
|
|
294
279
|
if (audit) {
|
|
295
|
-
lines.push(`bindings:
|
|
296
|
-
lines.push(`returned:
|
|
280
|
+
lines.push(`bindings: configured=${audit.counts.bindings.configured}; evaluated=${audit.counts.bindings.evaluated}`);
|
|
281
|
+
lines.push(`returned: findings=${audit.counts.returned.findings}`);
|
|
297
282
|
lines.push(`traversal: units=${audit.counts.traversal.units}; edges=${audit.counts.traversal.edges}`);
|
|
298
283
|
for (const finding of audit.findings) lines.push(`finding ${finding.code} ${finding.fingerprint} at ${humanAuditLocation(finding.primaryLocation)}: ${finding.message}`);
|
|
299
|
-
for (const gap of audit.gaps) lines.push(`gap ${gap.code} ${gap.fingerprint} at ${humanAuditLocation(gap.primaryLocation)}: ${gap.message}`);
|
|
300
284
|
} else lines.push("counts: unavailable (audit refused before evaluation)");
|
|
301
285
|
for (const diagnostic of report.diagnostics) lines.push(humanDiagnostic(diagnostic));
|
|
302
286
|
return `${lines.join("\n")}\n`;
|
|
@@ -310,6 +294,7 @@ function outputReport(report, json) {
|
|
|
310
294
|
if (report.changed !== undefined) process.stdout.write(`changed: ${report.changed}\n`);
|
|
311
295
|
if (report.packet_bytes !== undefined) process.stdout.write(`packet bytes: ${report.packet_bytes}\n`);
|
|
312
296
|
if (report.discovery_bytes !== undefined) process.stdout.write(`discovery bytes: ${report.discovery_bytes}\n`);
|
|
297
|
+
if (report.candidate_bytes !== undefined) process.stdout.write(`change-candidate bytes: ${report.candidate_bytes}\n`);
|
|
313
298
|
if (report.payload_bytes !== undefined) process.stdout.write(`semantic payload bytes: ${report.payload_bytes}\n`);
|
|
314
299
|
if (report.graph_bytes !== undefined) process.stdout.write(`graph bytes: ${report.graph_bytes}\n`);
|
|
315
300
|
if (report.packet) {
|
|
@@ -321,6 +306,12 @@ function outputReport(report, json) {
|
|
|
321
306
|
process.stdout.write(`candidates: ${report.discovery.candidates.map((candidate) => candidate.id).join(", ")}\n`);
|
|
322
307
|
if (report.discovery.relationPreview) process.stdout.write(`relation preview: units=${report.discovery.relationPreview.counts.units}; edges=${report.discovery.relationPreview.counts.edges}; complete=${report.discovery.relationPreview.complete}\n`);
|
|
323
308
|
}
|
|
309
|
+
if (report.change_candidates) {
|
|
310
|
+
const product = report.change_candidates;
|
|
311
|
+
process.stdout.write(`requested channels: ${product.requestedChannels.join(", ")}\n`);
|
|
312
|
+
process.stdout.write(`changed roots: ${product.counts.changedRoots}; eligible survivors: ${product.counts.eligibleAfterUnits}; recalled candidates: ${product.counts.candidates}; reasons: ${product.counts.reasons}\n`);
|
|
313
|
+
process.stdout.write(`candidates: ${product.candidates.map((candidate) => candidate.id).join(", ")}\n`);
|
|
314
|
+
}
|
|
324
315
|
if (report.diff) process.stdout.write(`semantic changes: ${report.diff.summary.changes}\n`);
|
|
325
316
|
if (report.impact) process.stdout.write(`impacted units: ${report.impact.impactedUnits.map((unit) => unit.id).join(", ")}\n`);
|
|
326
317
|
if (report.graph) {
|
|
@@ -348,19 +339,18 @@ function outputAuditReport(report, output) {
|
|
|
348
339
|
function formatReviewHuman(report) {
|
|
349
340
|
const review = report.review;
|
|
350
341
|
const lines = [
|
|
351
|
-
`nimicoding authority review: operation=${review?.operationStatus ?? "refused"};
|
|
342
|
+
`nimicoding authority review: operation=${review?.operationStatus ?? "refused"}; analysis-complete=${review?.analysisComplete ?? false}; configured-audit-blocking=${review?.audit?.blockingStatus ?? "not_evaluated"}`,
|
|
352
343
|
];
|
|
353
344
|
if (review) {
|
|
354
345
|
const base = review.snapshots.base;
|
|
355
346
|
const worktree = review.snapshots.worktree;
|
|
356
|
-
const unresolved = review.impact.obligations.filter((obligation) => obligation.disposition === null).length;
|
|
357
347
|
lines.push(`base: commit=${base.commitOid}; identity=${base.contentIdentity}; files=${base.counts.files}; units=${base.counts.units}`);
|
|
358
348
|
lines.push(`worktree: identity=${worktree.contentIdentity}; files=${worktree.counts.files}; units=${worktree.counts.units}`);
|
|
359
|
-
lines.push(`semantic changes: ${review.diff.summary.changes}; impacted units: ${review.impact.impactedUnits.length};
|
|
360
|
-
lines.push(`current audit:
|
|
349
|
+
lines.push(`semantic changes: ${review.diff.summary.changes}; impacted units: ${review.impact.impactedUnits.length}; declared obligations: ${review.impact.obligations.length}`);
|
|
350
|
+
lines.push(`current configured audit: blocking=${review.audit.blockingStatus}; findings=${review.audit.findings.length}`);
|
|
351
|
+
lines.push(`business semantics: ${review.businessSemanticStatus}; implementation conformance: ${review.implementationConformanceStatus}`);
|
|
361
352
|
lines.push(`review bytes: ${report.review_bytes}`);
|
|
362
353
|
for (const finding of review.audit.findings) lines.push(`finding ${finding.code} ${finding.fingerprint} at ${humanAuditLocation(finding.primaryLocation)}: ${finding.message}`);
|
|
363
|
-
for (const gap of review.audit.gaps) lines.push(`gap ${gap.code} ${gap.fingerprint} at ${humanAuditLocation(gap.primaryLocation)}: ${gap.message}`);
|
|
364
354
|
} else {
|
|
365
355
|
lines.push("review unavailable; partial=false");
|
|
366
356
|
}
|
|
@@ -373,32 +363,6 @@ function outputReviewReport(report, json) {
|
|
|
373
363
|
else process.stdout.write(formatReviewHuman(report));
|
|
374
364
|
}
|
|
375
365
|
|
|
376
|
-
function formatEvidenceHuman(report) {
|
|
377
|
-
const evidence = report.evidence;
|
|
378
|
-
const lines = [
|
|
379
|
-
`nimicoding authority evidence: operation=${evidence?.operationStatus ?? "refused"}; evidence=${evidence?.evidenceStatus ?? "indeterminate"}; complete=${evidence?.complete ?? false}; conformance=${evidence?.conformanceStatus ?? "not_evaluated"}`,
|
|
380
|
-
];
|
|
381
|
-
if (evidence) {
|
|
382
|
-
lines.push(`snapshot: head=${evidence.identities.headCommitOid}; authority=${evidence.identities.authorityContentIdentity}; repository-input=${evidence.identities.repositoryInputIdentity}`);
|
|
383
|
-
lines.push(`bindings: required=${evidence.counts.bindings.required}; configured=${evidence.counts.bindings.configured}; evaluated=${evidence.counts.bindings.evaluated}`);
|
|
384
|
-
lines.push(`returned: observations=${evidence.counts.returned.observations}; findings=${evidence.counts.returned.findings}; gaps=${evidence.counts.returned.gaps}`);
|
|
385
|
-
lines.push(`evidence bytes: ${report.evidence_bytes}`);
|
|
386
|
-
for (const binding of evidence.bindings) {
|
|
387
|
-
lines.push(`binding ${binding.id}: required=${binding.required}; reachability=${binding.packageProbe.reachability}; execution=${binding.packageProbe.execution}; conformance=${binding.packageProbe.conformance}`);
|
|
388
|
-
if (binding.externalProbe) lines.push(` external ${binding.externalProbe.id}@${binding.externalProbe.version}: ${binding.externalProbe.status}${binding.externalProbe.reportedOutcome ? `/${binding.externalProbe.reportedOutcome}` : ""}; package-attestation=false`);
|
|
389
|
-
}
|
|
390
|
-
for (const finding of evidence.findings) lines.push(`finding ${finding.code} ${finding.fingerprint} at ${humanAuditLocation(finding.primaryLocation)}: ${finding.message}`);
|
|
391
|
-
for (const gap of evidence.gaps) lines.push(`gap ${gap.code} ${gap.fingerprint} at ${humanAuditLocation(gap.primaryLocation)}: ${gap.message}`);
|
|
392
|
-
} else lines.push("evidence unavailable; partial=false");
|
|
393
|
-
for (const diagnostic of report.diagnostics) lines.push(humanDiagnostic(diagnostic));
|
|
394
|
-
return `${lines.join("\n")}\n`;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function outputEvidenceReport(report, json) {
|
|
398
|
-
if (json) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
399
|
-
else process.stdout.write(formatEvidenceHuman(report));
|
|
400
|
-
}
|
|
401
|
-
|
|
402
366
|
function formatAnchorsReport(report, json) {
|
|
403
367
|
if (json) return `${JSON.stringify(report, null, 2)}\n`;
|
|
404
368
|
const lines = [
|
|
@@ -544,7 +508,7 @@ export async function runAuthority(args) {
|
|
|
544
508
|
process.stdout.write(`${USAGE}\n`);
|
|
545
509
|
return 0;
|
|
546
510
|
}
|
|
547
|
-
if (!["fmt", "check", "compile", "anchors", "consumers", "terms", "closed-sets", "discover", "query", "context", "refs", "path", "subgraph", "audit", "diff", "impact", "review"
|
|
511
|
+
if (!["fmt", "check", "compile", "anchors", "consumers", "terms", "closed-sets", "discover", "change-candidates", "query", "context", "refs", "path", "subgraph", "audit", "diff", "impact", "review"].includes(subcommand)) {
|
|
548
512
|
process.stderr.write(`nimicoding authority refused unknown subcommand: ${subcommand}\n${USAGE}\n`);
|
|
549
513
|
return 2;
|
|
550
514
|
}
|
|
@@ -555,15 +519,14 @@ export async function runAuthority(args) {
|
|
|
555
519
|
}
|
|
556
520
|
try {
|
|
557
521
|
if (subcommand === "fmt") {
|
|
558
|
-
const result = await formatAuthorityFile(parsed.options.path, { check: parsed.options.check
|
|
559
|
-
const report = makeReport("fmt", result, "not_evaluated", { changed: result.changed, check: parsed.options.check
|
|
522
|
+
const result = await formatAuthorityFile(parsed.options.path, { check: parsed.options.check });
|
|
523
|
+
const report = makeReport("fmt", result, "not_evaluated", { changed: result.changed, check: parsed.options.check });
|
|
560
524
|
outputReport(report, parsed.options.json);
|
|
561
525
|
return result.ok ? 0 : 1;
|
|
562
526
|
}
|
|
563
527
|
if (subcommand === "check") {
|
|
564
528
|
const result = await checkAuthorityPath(parsed.options.path, {
|
|
565
529
|
scopeBindings: parsed.options.scopeBindings,
|
|
566
|
-
requireFormat: parsed.options.requireFormat,
|
|
567
530
|
});
|
|
568
531
|
const report = makeReport("check", result, result.ok ? "valid" : "invalid");
|
|
569
532
|
outputReport(report, parsed.options.json);
|
|
@@ -592,6 +555,21 @@ export async function runAuthority(args) {
|
|
|
592
555
|
outputReport(report, parsed.options.json);
|
|
593
556
|
return result.ok ? 0 : 1;
|
|
594
557
|
}
|
|
558
|
+
if (subcommand === "change-candidates") {
|
|
559
|
+
const result = await changeCandidatesAuthorityPaths(parsed.options.beforePath, parsed.options.afterPath, {
|
|
560
|
+
channels: parsed.options.channels,
|
|
561
|
+
maxCandidates: parsed.options.maxCandidates,
|
|
562
|
+
maxReasons: parsed.options.maxReasons,
|
|
563
|
+
maxBytes: parsed.options.maxBytes,
|
|
564
|
+
});
|
|
565
|
+
const report = makeReport("change-candidates", result, result.ok ? "completed" : "refused", {
|
|
566
|
+
candidate_bytes: result.candidateBytes,
|
|
567
|
+
change_candidates: result.changeCandidates,
|
|
568
|
+
partial: result.partial,
|
|
569
|
+
});
|
|
570
|
+
outputReport(report, parsed.options.json);
|
|
571
|
+
return result.ok ? 0 : 1;
|
|
572
|
+
}
|
|
595
573
|
if (subcommand === "query") {
|
|
596
574
|
const result = await queryAuthorityPath(parsed.options.path, parsed.options.id, { maxBytes: parsed.options.maxBytes });
|
|
597
575
|
const report = makeReport("query", result, result.ok ? "valid" : "invalid", { packet_bytes: result.packetBytes, packet: result.packet });
|
|
@@ -635,17 +613,17 @@ export async function runAuthority(args) {
|
|
|
635
613
|
});
|
|
636
614
|
outputAuditReport(report, parsed.options.json ? "json" : "human");
|
|
637
615
|
}
|
|
638
|
-
return result.audit?.operationStatus === "completed" && result.audit.
|
|
616
|
+
return result.audit?.operationStatus === "completed" && result.audit.blockingStatus === "clear" ? 0 : 1;
|
|
639
617
|
}
|
|
640
618
|
if (subcommand === "diff") {
|
|
641
619
|
const result = await diffAuthorityPaths(parsed.options.beforePath, parsed.options.afterPath, { maxBytes: parsed.options.maxBytes });
|
|
642
|
-
const report = makeReport("diff", result, result.ok ? "valid" : "invalid", { payload_bytes: result.payloadBytes, diff: result.diff });
|
|
620
|
+
const report = makeReport("diff", result, result.ok ? "valid" : "invalid", { payload_bytes: result.payloadBytes, diff: result.diff, partial: result.partial });
|
|
643
621
|
outputReport(report, parsed.options.json);
|
|
644
622
|
return result.ok ? 0 : 1;
|
|
645
623
|
}
|
|
646
624
|
if (subcommand === "impact") {
|
|
647
|
-
const result = await impactAuthorityPaths(parsed.options.beforePath, parsed.options.afterPath,
|
|
648
|
-
const report = makeReport("impact", result, result.ok ? "valid" : "invalid", { payload_bytes: result.payloadBytes, diff: result.diff, impact: result.impact });
|
|
625
|
+
const result = await impactAuthorityPaths(parsed.options.beforePath, parsed.options.afterPath, { maxBytes: parsed.options.maxBytes });
|
|
626
|
+
const report = makeReport("impact", result, result.ok ? "valid" : "invalid", { payload_bytes: result.payloadBytes, diff: result.diff, impact: result.impact, partial: result.partial });
|
|
649
627
|
outputReport(report, parsed.options.json);
|
|
650
628
|
return result.ok ? 0 : 1;
|
|
651
629
|
}
|
|
@@ -803,33 +781,10 @@ export async function runAuthority(args) {
|
|
|
803
781
|
outputClosedSetsReport(report, parsed.options.json);
|
|
804
782
|
return result.ok ? 0 : 1;
|
|
805
783
|
}
|
|
806
|
-
if (subcommand === "evidence") {
|
|
807
|
-
const result = await evidenceAuthorityRepository(
|
|
808
|
-
parsed.options.repositoryPath,
|
|
809
|
-
parsed.options.bindings,
|
|
810
|
-
parsed.options.probeResults,
|
|
811
|
-
{
|
|
812
|
-
maxUnits: parsed.options.maxUnits,
|
|
813
|
-
maxBindings: parsed.options.maxBindings,
|
|
814
|
-
maxLocators: parsed.options.maxLocators,
|
|
815
|
-
maxEdges: parsed.options.maxEdges,
|
|
816
|
-
maxInputBytes: parsed.options.maxInputBytes,
|
|
817
|
-
maxBytes: parsed.options.maxBytes,
|
|
818
|
-
},
|
|
819
|
-
);
|
|
820
|
-
const report = makeReport("evidence", result, result.evidence?.operationStatus ?? "refused", {
|
|
821
|
-
evidence_bytes: result.evidenceBytes,
|
|
822
|
-
evidence: result.evidence,
|
|
823
|
-
partial: result.partial,
|
|
824
|
-
});
|
|
825
|
-
outputEvidenceReport(report, parsed.options.json);
|
|
826
|
-
return result.evidence?.complete && result.evidence.evidenceStatus === "available" ? 0 : 1;
|
|
827
|
-
}
|
|
828
784
|
const result = await reviewAuthorityRepository(
|
|
829
785
|
parsed.options.repositoryPath,
|
|
830
786
|
parsed.options.base,
|
|
831
787
|
parsed.options.bindings,
|
|
832
|
-
parsed.options.dispositions,
|
|
833
788
|
{ maxUnits: parsed.options.maxUnits, maxEdges: parsed.options.maxEdges, maxBytes: parsed.options.maxBytes },
|
|
834
789
|
);
|
|
835
790
|
const report = makeReport("review", result, result.review?.operationStatus ?? "refused", {
|
|
@@ -838,7 +793,7 @@ export async function runAuthority(args) {
|
|
|
838
793
|
partial: result.partial,
|
|
839
794
|
});
|
|
840
795
|
outputReviewReport(report, parsed.options.json);
|
|
841
|
-
return result.review?.
|
|
796
|
+
return result.review?.analysisComplete === true && result.review.audit.blockingStatus === "clear" ? 0 : 1;
|
|
842
797
|
} catch (error) {
|
|
843
798
|
const message = error instanceof AuthorityInputError ? error.message : `authority ${subcommand} could not evaluate input: ${error.message}`;
|
|
844
799
|
process.stderr.write(`${message}\n`);
|
package/cli/constants.mjs
CHANGED
|
@@ -12,12 +12,18 @@ import { fileURLToPath } from "node:url";
|
|
|
12
12
|
|
|
13
13
|
import YAML from "yaml";
|
|
14
14
|
|
|
15
|
+
import { AUTHORITY_COMMAND_USAGE } from "../commands/authority.mjs";
|
|
15
16
|
import { PNPM_BUILTIN_SUBCOMMANDS } from "../lib/authority/anchors.mjs";
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
AUTHORING_SURFACE,
|
|
19
|
+
AUTHORITY_SURFACE,
|
|
20
|
+
ENFORCED_AUTHORITY_COMMANDS,
|
|
21
|
+
} from "../lib/authority/surface.mjs";
|
|
17
22
|
|
|
18
23
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
19
24
|
|
|
20
25
|
const CONTRACT_FILES = {
|
|
26
|
+
audit: "spec/authority-audit.yaml",
|
|
21
27
|
anchors: "spec/authority-anchors.yaml",
|
|
22
28
|
consumers: "spec/authority-consumers.yaml",
|
|
23
29
|
terms: "spec/authority-terms.yaml",
|
|
@@ -42,7 +48,7 @@ export function checkAuthoritySurface(root = packageRoot) {
|
|
|
42
48
|
const failures = [];
|
|
43
49
|
const contractIds = new Map();
|
|
44
50
|
|
|
45
|
-
for (const command of
|
|
51
|
+
for (const command of ENFORCED_AUTHORITY_COMMANDS) {
|
|
46
52
|
const relative = CONTRACT_FILES[command];
|
|
47
53
|
if (relative === undefined) {
|
|
48
54
|
failures.push(`command ${command} is in the enforced surface but has no product contract file`);
|
|
@@ -109,6 +115,13 @@ export function checkAuthoritySurface(root = packageRoot) {
|
|
|
109
115
|
`${command} command line flags ${JSON.stringify(commandFlags)} != declared surface ${JSON.stringify(surfaceFlags)}`,
|
|
110
116
|
);
|
|
111
117
|
}
|
|
118
|
+
const usagePrefix = `nimicoding authority ${command} `;
|
|
119
|
+
const helpUsages = AUTHORITY_COMMAND_USAGE.filter((usage) => usage.startsWith(usagePrefix));
|
|
120
|
+
if (helpUsages.length !== 1 || helpUsages[0] !== contract.command) {
|
|
121
|
+
failures.push(
|
|
122
|
+
`${command} help usage ${JSON.stringify(helpUsages)} != contract command ${JSON.stringify(contract.command)}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
// fmt and the source schema: the authoring contract has to describe the
|
|
@@ -135,10 +148,6 @@ export function checkAuthoritySurface(root = packageRoot) {
|
|
|
135
148
|
if (authoring.fields?.format !== source.format) {
|
|
136
149
|
failures.push(`authoring format ${authoring.fields?.format} != source schema format ${source.format}`);
|
|
137
150
|
}
|
|
138
|
-
const authoringMigration = authoring.fields?.migration_inputs?.format;
|
|
139
|
-
if (authoringMigration !== source.migration_inputs?.format) {
|
|
140
|
-
failures.push(`migration input format: authoring ${authoringMigration} != source schema ${source.migration_inputs?.format}`);
|
|
141
|
-
}
|
|
142
151
|
if (!sameSet(authoring.profiles?.yaml?.container_fields ?? [], source.yaml_document?.allowed_fields ?? [])) {
|
|
143
152
|
failures.push(
|
|
144
153
|
`container fields: authoring ${JSON.stringify(sorted(authoring.profiles?.yaml?.container_fields ?? []))} `
|
|
@@ -152,7 +161,7 @@ export function checkAuthoritySurface(root = packageRoot) {
|
|
|
152
161
|
|
|
153
162
|
// The other direction: a contract that no enforced command claims.
|
|
154
163
|
for (const [command, relative] of Object.entries(CONTRACT_FILES)) {
|
|
155
|
-
if (!
|
|
164
|
+
if (!ENFORCED_AUTHORITY_COMMANDS.includes(command)) {
|
|
156
165
|
failures.push(`${relative} declares command ${command}, which the enforced surface does not admit`);
|
|
157
166
|
}
|
|
158
167
|
}
|
|
@@ -168,6 +177,6 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
|
|
|
168
177
|
for (const failure of failures) process.stderr.write(`authority-surface: ${failure}\n`);
|
|
169
178
|
if (failures.length > 0) process.exit(1);
|
|
170
179
|
process.stdout.write(
|
|
171
|
-
`authority-surface: ${
|
|
180
|
+
`authority-surface: ${ENFORCED_AUTHORITY_COMMANDS.length} public command surfaces match their product contracts\n`,
|
|
172
181
|
);
|
|
173
182
|
}
|
|
@@ -335,18 +335,6 @@ function createClassification(contracts) {
|
|
|
335
335
|
return leaves;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
function addAuthoringClassification(leaves, authoring) {
|
|
339
|
-
const values = authoring?.commands?.check?.require_format?.admitted_values;
|
|
340
|
-
if (!Array.isArray(values) || values.length === 0) throw new Error("authoring contract declares no admitted check formats");
|
|
341
|
-
values.forEach((value, index) => leaves.set(`authoring:commands.check.require_format.admitted_values[${index}]`, {
|
|
342
|
-
contract: "authoring",
|
|
343
|
-
path: `commands.check.require_format.admitted_values[${index}]`,
|
|
344
|
-
value,
|
|
345
|
-
status: "unmapped",
|
|
346
|
-
details: [],
|
|
347
|
-
}));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
338
|
function classify(leaves, contract, leafPath, matches, detail) {
|
|
351
339
|
const leaf = leaves.get(`${contract}:${leafPath}`);
|
|
352
340
|
if (!leaf) throw new Error(`behavioural check names no classified leaf: ${contract}:${leafPath}`);
|
|
@@ -431,7 +419,7 @@ async function exerciseParserSurface(cliPath, fixture, contracts, leaves) {
|
|
|
431
419
|
return validReports;
|
|
432
420
|
}
|
|
433
421
|
|
|
434
|
-
async function exerciseRequiredCombinations(cliPath, fixture, contracts,
|
|
422
|
+
async function exerciseRequiredCombinations(cliPath, fixture, contracts, leaves) {
|
|
435
423
|
const codeWithoutTerm = await runCli(cliPath, fixture.root, [
|
|
436
424
|
...requiredArgs("terms", fixture).filter((entry) => entry !== "--json"),
|
|
437
425
|
"--code", "--json",
|
|
@@ -442,20 +430,6 @@ async function exerciseRequiredCombinations(cliPath, fixture, contracts, authori
|
|
|
442
430
|
classify(leaves, "terms", leafPath, refusedCode, `required --code/--term combination exited ${codeWithoutTerm.code}`);
|
|
443
431
|
}
|
|
444
432
|
|
|
445
|
-
const admitted = authoring.commands.check.require_format.admitted_values;
|
|
446
|
-
const invalidFormat = await runCli(cliPath, fixture.root, [
|
|
447
|
-
"authority", "check", fixture.spec, "--require-format", "nimicoding.authority/invalid", "--json",
|
|
448
|
-
]);
|
|
449
|
-
const formatRefusal = invalidFormat.code === 2
|
|
450
|
-
&& invalidFormat.stdout === ""
|
|
451
|
-
&& admitted.every((format) => invalidFormat.stderr.includes(format));
|
|
452
|
-
admitted.forEach((_, index) => classify(
|
|
453
|
-
leaves,
|
|
454
|
-
"authoring",
|
|
455
|
-
`commands.check.require_format.admitted_values[${index}]`,
|
|
456
|
-
formatRefusal,
|
|
457
|
-
`invalid --require-format exited ${invalidFormat.code} and named admitted formats`,
|
|
458
|
-
));
|
|
459
433
|
}
|
|
460
434
|
|
|
461
435
|
async function independentFixtureOracle(fixture) {
|
|
@@ -479,7 +453,7 @@ async function independentFixtureOracle(fixture) {
|
|
|
479
453
|
units: 3,
|
|
480
454
|
files: 6,
|
|
481
455
|
refs: 2,
|
|
482
|
-
terms:
|
|
456
|
+
terms: 5,
|
|
483
457
|
sets: 2,
|
|
484
458
|
consumersInputBytes: nonCorpusBytes,
|
|
485
459
|
termsInputBytes: authorityBytes + nonCorpusBytes,
|
|
@@ -682,7 +656,7 @@ async function exerciseCoreClosure(cliPath, fixture, contracts, leaves, validRep
|
|
|
682
656
|
consumers: validReports.consumers.report?.summary?.filesScanned === 6
|
|
683
657
|
&& validReports.consumers.report?.summary?.references === 2,
|
|
684
658
|
terms: validReports.terms.report?.summary?.units === 3
|
|
685
|
-
&& validReports.terms.report?.summary?.distinctTerms ===
|
|
659
|
+
&& validReports.terms.report?.summary?.distinctTerms === 5,
|
|
686
660
|
"closed-sets": validReports["closed-sets"].report?.summary?.sets?.total === 2,
|
|
687
661
|
};
|
|
688
662
|
for (const command of Object.keys(contracts)) {
|
|
@@ -841,13 +815,11 @@ async function runConformanceLane({ contractDirectory, cliPath, lane }) {
|
|
|
841
815
|
for (const [command, file] of Object.entries(PRODUCT_CONTRACTS)) {
|
|
842
816
|
contracts[command] = YAML.parse(await readFile(path.join(contractDirectory, file), "utf8"));
|
|
843
817
|
}
|
|
844
|
-
const authoring = YAML.parse(await readFile(path.join(contractDirectory, "authority-authoring.yaml"), "utf8"));
|
|
845
818
|
const leaves = createClassification(contracts);
|
|
846
|
-
addAuthoringClassification(leaves, authoring);
|
|
847
819
|
const fixture = await createRepositoryFixture();
|
|
848
820
|
try {
|
|
849
821
|
const validReports = await exerciseParserSurface(cliPath, fixture, contracts, leaves);
|
|
850
|
-
await exerciseRequiredCombinations(cliPath, fixture, contracts,
|
|
822
|
+
await exerciseRequiredCombinations(cliPath, fixture, contracts, leaves);
|
|
851
823
|
await exerciseCompleteAndBudgets(cliPath, fixture, contracts, leaves, validReports);
|
|
852
824
|
await exerciseGrammar(cliPath, fixture, leaves, validReports);
|
|
853
825
|
await addAnchorFinding(fixture);
|