@nimiplatform/nimi-coding 0.3.0 → 0.4.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 +27 -0
- package/CONTRIBUTING.md +3 -3
- package/README.md +285 -41
- package/README.zh-CN.md +294 -31
- package/bin/nimicoding.mjs +1 -1
- package/cli/commands/authority.mjs +849 -0
- package/cli/commands/clear.mjs +27 -7
- package/cli/commands/doctor.mjs +10 -1
- package/cli/commands/start.mjs +23 -27
- package/cli/commands/sync.mjs +21 -101
- package/cli/constants.mjs +7 -31
- package/cli/dev/check-authority-surface.mjs +173 -0
- package/cli/dev/check-product-conformance.mjs +917 -0
- package/cli/help.mjs +10 -17
- package/cli/index.mjs +6 -47
- package/cli/lib/authority/anchors.mjs +491 -0
- package/cli/lib/authority/audit.mjs +490 -0
- package/cli/lib/authority/closed-sets.mjs +646 -0
- package/cli/lib/authority/compile.mjs +110 -0
- package/cli/lib/authority/consumers.mjs +470 -0
- package/cli/lib/authority/diagnostics.mjs +87 -0
- package/cli/lib/authority/diff.mjs +287 -0
- package/cli/lib/authority/discover.mjs +447 -0
- package/cli/lib/authority/evidence-snapshot.mjs +1 -0
- package/cli/lib/authority/evidence.mjs +1432 -0
- package/cli/lib/authority/format.mjs +197 -0
- package/cli/lib/authority/git-snapshot.mjs +1065 -0
- package/cli/lib/authority/graph.mjs +351 -0
- package/cli/lib/authority/impact.mjs +252 -0
- package/cli/lib/authority/lexicon.mjs +55 -0
- package/cli/lib/authority/query.mjs +162 -0
- package/cli/lib/authority/repository-corpus.mjs +37 -0
- package/cli/lib/authority/repository-inventory.mjs +34 -0
- package/cli/lib/authority/review.mjs +222 -0
- package/cli/lib/authority/sarif.mjs +181 -0
- package/cli/lib/authority/scope-bindings.mjs +225 -0
- package/cli/lib/authority/source-map.mjs +105 -0
- package/cli/lib/authority/source-markdown.mjs +167 -0
- package/cli/lib/authority/source-yaml.mjs +312 -0
- package/cli/lib/authority/surface.mjs +79 -0
- package/cli/lib/authority/terms.mjs +421 -0
- package/cli/lib/authority/tracked-text.mjs +70 -0
- package/cli/lib/authority/validate.mjs +308 -0
- package/cli/lib/bootstrap.mjs +55 -71
- package/cli/lib/doctor.mjs +40 -123
- package/cli/lib/entrypoints.mjs +103 -105
- package/cli/lib/fs-helpers.mjs +139 -15
- package/cli/lib/internal/governance/config.mjs +0 -36
- package/cli/lib/shared.mjs +8 -21
- package/cli/lib/sync.mjs +81 -92
- package/cli/lib/value-helpers.mjs +0 -26
- package/cli/lib/yaml-helpers.mjs +1 -124
- package/cli/seeds/bootstrap.mjs +31 -87
- package/cli/seeds/seed-policy.yaml +9 -63
- package/contracts/authority-evidence-bindings.schema.yaml +53 -0
- package/contracts/authority-evidence-probe-results.schema.yaml +38 -0
- package/contracts/authority-impact-dispositions.schema.yaml +13 -0
- package/contracts/authority-scope-bindings.schema.yaml +15 -0
- package/contracts/authority-source.schema.yaml +64 -0
- package/contracts/authority-verifier-bindings.schema.yaml +20 -0
- package/methodology/authority-authoring.yaml +144 -0
- package/methodology/core.yaml +31 -26
- package/package.json +10 -6
- package/spec/authority-anchors.yaml +99 -0
- package/spec/authority-audit.yaml +85 -0
- package/spec/authority-authoring.yaml +150 -0
- package/spec/authority-closed-sets.yaml +65 -0
- package/spec/authority-consumers.yaml +70 -0
- package/spec/authority-context.yaml +50 -0
- package/spec/authority-discovery.yaml +119 -0
- package/spec/authority-evidence.yaml +200 -0
- package/spec/authority-graph.yaml +59 -0
- package/spec/authority-impact.yaml +84 -0
- package/spec/authority-review.yaml +89 -0
- package/spec/authority-terms.yaml +59 -0
- package/spec/product-scope.yaml +36 -23
- package/cli/commands/blueprint-audit.mjs +0 -91
- package/cli/commands/classify-spec-tree.mjs +0 -5
- package/cli/commands/generate-spec-derived-docs.mjs +0 -151
- package/cli/commands/generate-spec-migration-plan.mjs +0 -30
- package/cli/commands/surface-validator-command.mjs +0 -49
- package/cli/commands/validate-domain-admission.mjs +0 -5
- package/cli/commands/validate-guidance-bodies.mjs +0 -5
- package/cli/commands/validate-placement.mjs +0 -5
- package/cli/commands/validate-projection-edges.mjs +0 -5
- package/cli/commands/validate-spec-audit.mjs +0 -27
- package/cli/commands/validate-spec-governance.mjs +0 -124
- package/cli/commands/validate-spec-tree.mjs +0 -27
- package/cli/commands/validate-table-family.mjs +0 -5
- package/cli/commands/validate-tracked-output-admission.mjs +0 -5
- package/cli/lib/blueprint-audit.mjs +0 -363
- package/cli/lib/contracts.mjs +0 -21
- package/cli/lib/internal/contracts-loaders.mjs +0 -47
- package/cli/lib/internal/contracts-parse.mjs +0 -192
- package/cli/lib/internal/governance/runner.mjs +0 -35
- package/cli/lib/internal/surface-taxonomy-validators.mjs +0 -1147
- package/cli/lib/internal/validators-shared.mjs +0 -12
- package/cli/lib/internal/validators-spec-helpers.mjs +0 -52
- package/cli/lib/internal/validators-spec.mjs +0 -380
- package/cli/lib/validators.mjs +0 -30
- package/config/bootstrap.yaml +0 -6
- package/config/spec-generation-inputs.yaml +0 -41
- package/contracts/domain-admission.schema.yaml +0 -56
- package/contracts/migration-inventory.schema.yaml +0 -15
- package/contracts/negative-fixtures.yaml +0 -26
- package/contracts/placement-contract.schema.yaml +0 -127
- package/contracts/projection-edge.schema.yaml +0 -46
- package/contracts/shared-enums.yaml +0 -63
- package/contracts/spec-generation-audit.schema.yaml +0 -46
- package/contracts/spec-generation-inputs.schema.yaml +0 -43
- package/contracts/spec-layout.schema.yaml +0 -32
- package/contracts/surface-taxonomy.schema.yaml +0 -139
- package/contracts/table-family.schema.yaml +0 -133
- package/contracts/tracked-output-admission.schema.yaml +0 -17
- package/methodology/four-closure-policy.yaml +0 -20
- package/methodology/role-separation-policy.yaml +0 -22
- package/methodology/spec-reconstruction.yaml +0 -69
- package/spec/_meta/spec-tree-model.yaml +0 -44
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import YAML, { isAlias, isMap, isScalar, isSeq, visit } from "yaml";
|
|
5
|
+
|
|
6
|
+
import { compareText, createLocator, makeDiagnostic, portablePath, REPAIRS } from "./diagnostics.mjs";
|
|
7
|
+
|
|
8
|
+
const CONTRACT_PATH = fileURLToPath(new URL("../../../contracts/authority-source.schema.yaml", import.meta.url));
|
|
9
|
+
let contractPromise = null;
|
|
10
|
+
|
|
11
|
+
export async function loadAuthorityContract() {
|
|
12
|
+
if (!contractPromise) {
|
|
13
|
+
contractPromise = readFile(CONTRACT_PATH, "utf8").then((text) => {
|
|
14
|
+
const value = YAML.parse(text);
|
|
15
|
+
// v2 is the current authoring contract; v1 remains readable as a
|
|
16
|
+
// migration input, which the contract itself records rather than the
|
|
17
|
+
// loader inferring it.
|
|
18
|
+
if (value?.version !== 1 || value?.contract?.id !== "nimicoding.authority-source.v2") {
|
|
19
|
+
throw new Error("installed authority source contract is invalid");
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return contractPromise;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pointerToken(value) {
|
|
28
|
+
return String(value).replaceAll("~", "~0").replaceAll("/", "~1");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function nodeRange(node, locator, offset) {
|
|
32
|
+
const range = node?.range;
|
|
33
|
+
if (!Array.isArray(range)) return locator.range(offset, offset);
|
|
34
|
+
return locator.range(offset + range[0], offset + (range[1] ?? range[0]));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function collectLocations(node, locator, offset, pointer, locations, keyLocations) {
|
|
38
|
+
if (!node) return;
|
|
39
|
+
locations.set(pointer, nodeRange(node, locator, offset));
|
|
40
|
+
if (isMap(node)) {
|
|
41
|
+
for (const pair of node.items) {
|
|
42
|
+
const key = isScalar(pair.key) ? String(pair.key.value) : "";
|
|
43
|
+
const childPointer = `${pointer}/${pointerToken(key)}`;
|
|
44
|
+
keyLocations.set(childPointer, nodeRange(pair.key, locator, offset));
|
|
45
|
+
locations.set(childPointer, nodeRange(pair.value ?? pair.key, locator, offset));
|
|
46
|
+
collectLocations(pair.value, locator, offset, childPointer, locations, keyLocations);
|
|
47
|
+
}
|
|
48
|
+
} else if (isSeq(node)) {
|
|
49
|
+
node.items.forEach((item, index) => collectLocations(item, locator, offset, `${pointer}/${index}`, locations, keyLocations));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function zeroAt(position) {
|
|
54
|
+
return { start: position, end: position };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildInsertionPoints(data, locations, keyLocations, keyOrder) {
|
|
58
|
+
const insertionPoints = new Map();
|
|
59
|
+
const rootEnd = locations.get("")?.end ?? { line: 1, column: 1 };
|
|
60
|
+
for (let index = 0; index < keyOrder.length; index += 1) {
|
|
61
|
+
const field = keyOrder[index];
|
|
62
|
+
if (Object.hasOwn(data, field)) continue;
|
|
63
|
+
const nextField = keyOrder.slice(index + 1).find((candidate) => keyLocations.has(`/${candidate}`));
|
|
64
|
+
insertionPoints.set(`/${field}`, nextField ? zeroAt(keyLocations.get(`/${nextField}`).start) : zeroAt(rootEnd));
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(data.relations)) {
|
|
67
|
+
data.relations.forEach((relation, index) => {
|
|
68
|
+
if (!relation || typeof relation !== "object" || Array.isArray(relation)) return;
|
|
69
|
+
const pointer = `/relations/${index}`;
|
|
70
|
+
const relationEnd = locations.get(pointer)?.end ?? rootEnd;
|
|
71
|
+
if (!Object.hasOwn(relation, "type")) {
|
|
72
|
+
insertionPoints.set(`${pointer}/type`, keyLocations.has(`${pointer}/target`)
|
|
73
|
+
? zeroAt(keyLocations.get(`${pointer}/target`).start)
|
|
74
|
+
: zeroAt(relationEnd));
|
|
75
|
+
}
|
|
76
|
+
if (!Object.hasOwn(relation, "target")) insertionPoints.set(`${pointer}/target`, zeroAt(relationEnd));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return insertionPoints;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function yamlErrorDiagnostic(error, file, locator, offset) {
|
|
83
|
+
const positions = Array.isArray(error.pos) ? error.pos : [0, 0];
|
|
84
|
+
const duplicate = /keys must be unique|duplicate key/i.test(error.message);
|
|
85
|
+
return makeDiagnostic({
|
|
86
|
+
code: duplicate ? "AUTH_DUPLICATE_KEY" : "AUTH_SYNTAX_UNSUPPORTED",
|
|
87
|
+
file,
|
|
88
|
+
range: locator.range(offset + positions[0], offset + (positions[1] ?? positions[0])),
|
|
89
|
+
reason: duplicate ? "YAML mapping keys must be unique" : `unsupported YAML syntax: ${error.message.split(" at line")[0]}`,
|
|
90
|
+
repair: duplicate ? REPAIRS.duplicate : REPAIRS.structural,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function parseRestrictedYaml(text, absolutePath, options = {}) {
|
|
95
|
+
const offset = options.offset ?? 0;
|
|
96
|
+
const fullText = options.fullText ?? text;
|
|
97
|
+
const file = portablePath(absolutePath, options.cwd);
|
|
98
|
+
const locator = createLocator(fullText);
|
|
99
|
+
const diagnostics = [];
|
|
100
|
+
const documents = YAML.parseAllDocuments(text, {
|
|
101
|
+
keepSourceTokens: true,
|
|
102
|
+
prettyErrors: false,
|
|
103
|
+
strict: true,
|
|
104
|
+
uniqueKeys: true,
|
|
105
|
+
version: "1.2",
|
|
106
|
+
});
|
|
107
|
+
if (documents.length !== 1) {
|
|
108
|
+
diagnostics.push(makeDiagnostic({
|
|
109
|
+
code: "AUTH_SYNTAX_UNSUPPORTED",
|
|
110
|
+
file,
|
|
111
|
+
range: locator.range(offset, offset + text.length),
|
|
112
|
+
reason: "authority source must contain exactly one YAML document",
|
|
113
|
+
repair: REPAIRS.structural,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
const document = documents[0];
|
|
117
|
+
for (const error of document?.errors ?? []) diagnostics.push(yamlErrorDiagnostic(error, file, locator, offset));
|
|
118
|
+
if (diagnostics.length > 0 || !document) {
|
|
119
|
+
return { ok: false, file, locator, diagnostics, locations: new Map(), keyLocations: new Map(), insertionPoints: new Map(), data: null };
|
|
120
|
+
}
|
|
121
|
+
if (!isMap(document.contents)) {
|
|
122
|
+
diagnostics.push(makeDiagnostic({
|
|
123
|
+
code: "AUTH_SYNTAX_UNSUPPORTED",
|
|
124
|
+
file,
|
|
125
|
+
range: nodeRange(document.contents, locator, offset),
|
|
126
|
+
reason: "authority YAML must be one top-level mapping",
|
|
127
|
+
repair: REPAIRS.structural,
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
if (/^%/.test(text)) {
|
|
131
|
+
diagnostics.push(makeDiagnostic({
|
|
132
|
+
code: "AUTH_SYNTAX_UNSUPPORTED",
|
|
133
|
+
file,
|
|
134
|
+
range: locator.range(offset, offset + Math.min(text.length, text.indexOf("\n") + 1 || text.length)),
|
|
135
|
+
reason: "YAML directives are not admitted",
|
|
136
|
+
repair: REPAIRS.structural,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
if (document.comment || document.commentBefore) {
|
|
140
|
+
const searchStart = document.contents?.range?.[2] ?? document.contents?.range?.[1] ?? 0;
|
|
141
|
+
const commentStart = text.indexOf("#", searchStart);
|
|
142
|
+
diagnostics.push(makeDiagnostic({
|
|
143
|
+
code: "AUTH_SYNTAX_UNSUPPORTED",
|
|
144
|
+
file,
|
|
145
|
+
range: locator.range(offset + Math.max(0, commentStart), offset + (commentStart < 0 ? text.length : commentStart + 1)),
|
|
146
|
+
reason: "YAML comments are not admitted in canonical authority",
|
|
147
|
+
repair: REPAIRS.structural,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
if (document.contents) {
|
|
151
|
+
visit(document, (_key, node) => {
|
|
152
|
+
if (!node || typeof node !== "object") return;
|
|
153
|
+
let reason = null;
|
|
154
|
+
if (node.comment || node.commentBefore) reason = "YAML comments are not admitted in canonical authority";
|
|
155
|
+
else if (isAlias(node)) reason = "YAML aliases are not admitted";
|
|
156
|
+
else if (node.anchor) reason = "YAML anchors are not admitted";
|
|
157
|
+
else if (node.tag) reason = "explicit YAML tags are not admitted";
|
|
158
|
+
if (reason) diagnostics.push(makeDiagnostic({
|
|
159
|
+
code: "AUTH_SYNTAX_UNSUPPORTED",
|
|
160
|
+
file,
|
|
161
|
+
range: nodeRange(node, locator, offset),
|
|
162
|
+
reason,
|
|
163
|
+
repair: REPAIRS.structural,
|
|
164
|
+
}));
|
|
165
|
+
if (node.constructor?.name === "Pair" && !isScalar(node.key)) {
|
|
166
|
+
diagnostics.push(makeDiagnostic({
|
|
167
|
+
code: "AUTH_SYNTAX_UNSUPPORTED",
|
|
168
|
+
file,
|
|
169
|
+
range: nodeRange(node.key, locator, offset),
|
|
170
|
+
reason: "complex YAML mapping keys are not admitted",
|
|
171
|
+
repair: REPAIRS.structural,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (diagnostics.length > 0) return { ok: false, file, locator, diagnostics, locations: new Map(), keyLocations: new Map(), insertionPoints: new Map(), data: null };
|
|
177
|
+
const locations = new Map();
|
|
178
|
+
const keyLocations = new Map();
|
|
179
|
+
collectLocations(document.contents, locator, offset, "", locations, keyLocations);
|
|
180
|
+
const data = document.toJS({ mapAsMap: false });
|
|
181
|
+
const insertionPoints = buildInsertionPoints(data, locations, keyLocations, options.keyOrder ?? []);
|
|
182
|
+
return {
|
|
183
|
+
ok: true,
|
|
184
|
+
file,
|
|
185
|
+
locator,
|
|
186
|
+
diagnostics,
|
|
187
|
+
locations,
|
|
188
|
+
keyLocations,
|
|
189
|
+
insertionPoints,
|
|
190
|
+
data,
|
|
191
|
+
yamlDocument: document,
|
|
192
|
+
sourceText: fullText,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const AUTHORITY_FORMAT_V1 = "nimicoding.authority/v1";
|
|
197
|
+
export const AUTHORITY_FORMAT_V2 = "nimicoding.authority/v2";
|
|
198
|
+
export const YAML_CONTAINER_KEY_ORDER = ["format", "owner", "scope", "units"];
|
|
199
|
+
export const YAML_UNIT_KEY_ORDER = [
|
|
200
|
+
"id", "kind", "owner", "lifecycle", "title", "meaning", "modality", "scope",
|
|
201
|
+
"statement", "condition", "failure", "reason", "relations",
|
|
202
|
+
];
|
|
203
|
+
export const FRONTMATTER_KEY_ORDER = ["format", "id", "kind", "owner", "lifecycle", "modality", "scope", "relations"];
|
|
204
|
+
|
|
205
|
+
function orderedObject(data, order) {
|
|
206
|
+
return Object.fromEntries(order.filter((key) => Object.hasOwn(data, key)).map((key) => [key, data[key]]));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function normalizedUnit(data, order = YAML_UNIT_KEY_ORDER, defaults = null) {
|
|
210
|
+
const next = orderedObject(data, order);
|
|
211
|
+
if (defaults) {
|
|
212
|
+
if (next.owner === defaults.owner) delete next.owner;
|
|
213
|
+
if (next.lifecycle === "active") delete next.lifecycle;
|
|
214
|
+
if (Array.isArray(next.relations) && next.relations.length === 0) delete next.relations;
|
|
215
|
+
if (next.kind === "rule" && next.lifecycle !== "removed" && Array.isArray(next.scope)
|
|
216
|
+
&& next.scope.length === 1 && next.scope[0] === defaults.scope) delete next.scope;
|
|
217
|
+
}
|
|
218
|
+
if (Array.isArray(next.scope)) next.scope = [...next.scope].sort(compareText);
|
|
219
|
+
if (Array.isArray(next.relations)) {
|
|
220
|
+
next.relations = next.relations.map((entry) => ({ type: entry.type, target: entry.target })).sort((left, right) => (
|
|
221
|
+
compareText(left.type, right.type) || compareText(left.target, right.target)
|
|
222
|
+
));
|
|
223
|
+
}
|
|
224
|
+
return next;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function normalizedData(data, { frontmatter = false, container = false } = {}) {
|
|
228
|
+
if (frontmatter === container) throw new TypeError("canonical YAML serialization requires exactly one explicit profile: container or frontmatter");
|
|
229
|
+
if (container) {
|
|
230
|
+
const v2 = data?.format === AUTHORITY_FORMAT_V2;
|
|
231
|
+
const defaults = v2 ? { owner: data.owner, scope: data.scope } : null;
|
|
232
|
+
return {
|
|
233
|
+
...(Object.hasOwn(data, "format") ? { format: data.format } : {}),
|
|
234
|
+
...(v2 && Object.hasOwn(data, "owner") ? { owner: data.owner } : {}),
|
|
235
|
+
...(v2 && Object.hasOwn(data, "scope") ? { scope: data.scope } : {}),
|
|
236
|
+
...(Object.hasOwn(data, "units") ? { units: Array.isArray(data.units) ? data.units.map((unit) => normalizedUnit(unit, YAML_UNIT_KEY_ORDER, defaults)) : data.units } : {}),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return normalizedUnit(data, FRONTMATTER_KEY_ORDER);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function stringifyCanonicalYaml(data, options = {}) {
|
|
243
|
+
return YAML.stringify(normalizedData(data, options), {
|
|
244
|
+
indent: 2,
|
|
245
|
+
lineWidth: 0,
|
|
246
|
+
minContentWidth: 0,
|
|
247
|
+
defaultStringType: "PLAIN",
|
|
248
|
+
defaultKeyType: "PLAIN",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function localMap(map, prefix) {
|
|
253
|
+
return new Map([...map.entries()]
|
|
254
|
+
.filter(([pointer]) => pointer === prefix || pointer.startsWith(`${prefix}/`))
|
|
255
|
+
.map(([pointer, range]) => [pointer.slice(prefix.length), range]));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function yamlUnitSource(parsed, index) {
|
|
259
|
+
const prefix = `/units/${index}`;
|
|
260
|
+
const locations = localMap(parsed.locations, prefix);
|
|
261
|
+
const keyLocations = localMap(parsed.keyLocations, prefix);
|
|
262
|
+
const authoredData = parsed.data.units[index];
|
|
263
|
+
let data = authoredData;
|
|
264
|
+
if (parsed.data.format === AUTHORITY_FORMAT_V2 && authoredData && typeof authoredData === "object" && !Array.isArray(authoredData)) {
|
|
265
|
+
data = { ...authoredData };
|
|
266
|
+
const unitRange = locations.get("") ?? parsed.locations.get(prefix);
|
|
267
|
+
if (!Object.hasOwn(data, "owner") && typeof parsed.data.owner === "string") {
|
|
268
|
+
data.owner = parsed.data.owner;
|
|
269
|
+
locations.set("/owner", parsed.locations.get("/owner") ?? unitRange);
|
|
270
|
+
}
|
|
271
|
+
if (!Object.hasOwn(data, "lifecycle")) {
|
|
272
|
+
data.lifecycle = "active";
|
|
273
|
+
locations.set("/lifecycle", unitRange);
|
|
274
|
+
}
|
|
275
|
+
if (!Object.hasOwn(data, "relations")) {
|
|
276
|
+
data.relations = [];
|
|
277
|
+
locations.set("/relations", unitRange);
|
|
278
|
+
}
|
|
279
|
+
if (data.kind === "rule" && data.lifecycle === "active" && !Object.hasOwn(data, "scope") && typeof parsed.data.scope === "string") {
|
|
280
|
+
data.scope = [parsed.data.scope];
|
|
281
|
+
locations.set("/scope", parsed.locations.get("/scope") ?? unitRange);
|
|
282
|
+
locations.set("/scope/0", parsed.locations.get("/scope") ?? unitRange);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
ok: true,
|
|
287
|
+
file: parsed.file,
|
|
288
|
+
locator: parsed.locator,
|
|
289
|
+
diagnostics: [],
|
|
290
|
+
locations,
|
|
291
|
+
keyLocations,
|
|
292
|
+
insertionPoints: authoredData && typeof authoredData === "object" && !Array.isArray(authoredData)
|
|
293
|
+
? buildInsertionPoints(authoredData, locations, keyLocations, YAML_UNIT_KEY_ORDER)
|
|
294
|
+
: new Map(),
|
|
295
|
+
data,
|
|
296
|
+
authoredData,
|
|
297
|
+
documentFormat: parsed.data.format,
|
|
298
|
+
profile: "yaml",
|
|
299
|
+
sourcePrefix: prefix,
|
|
300
|
+
sourceIndex: index,
|
|
301
|
+
sourceText: parsed.sourceText,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function parseYamlAuthority(text, absolutePath, options = {}) {
|
|
306
|
+
const parsed = parseRestrictedYaml(text, absolutePath, { ...options, keyOrder: YAML_CONTAINER_KEY_ORDER });
|
|
307
|
+
if (!parsed.ok) return { ...parsed, profile: "yaml", isDocument: true, units: [] };
|
|
308
|
+
const units = Array.isArray(parsed.data?.units)
|
|
309
|
+
? parsed.data.units.map((_unit, index) => yamlUnitSource(parsed, index))
|
|
310
|
+
: [];
|
|
311
|
+
return { ...parsed, profile: "yaml", isDocument: true, units };
|
|
312
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The command surface the parser actually enforces.
|
|
2
|
+
//
|
|
3
|
+
// This is not documentation. parseOptions derives its flag admission from this
|
|
4
|
+
// table, so a flag that is not here is rejected at parse time, and a flag added
|
|
5
|
+
// here without a product contract fails the surface gate. Both directions have
|
|
6
|
+
// to move together or something breaks.
|
|
7
|
+
|
|
8
|
+
// anchors validates and exits non-zero on findings; the other three report and
|
|
9
|
+
// do not. Each contract states which it is rather than the name implying it.
|
|
10
|
+
export const REPOSITORY_BOUND_COMMANDS = ["anchors", "consumers", "terms", "closed-sets"];
|
|
11
|
+
|
|
12
|
+
// fmt is not repository-bound, but its flags and the formats it admits are
|
|
13
|
+
// exactly what the authoring contract promises, so it is compared too.
|
|
14
|
+
export const AUTHORING_SURFACE = {
|
|
15
|
+
fmt: {
|
|
16
|
+
contract: "nimicoding.authority-authoring.v2",
|
|
17
|
+
booleanFlags: ["json", "check", "upgrade"],
|
|
18
|
+
valueFlags: [],
|
|
19
|
+
integerFlags: [],
|
|
20
|
+
repeatableFlags: [],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const AUTHORITY_SURFACE = {
|
|
25
|
+
anchors: {
|
|
26
|
+
contract: "nimicoding.authority-anchors.v1",
|
|
27
|
+
reportFormat: "nimicoding.authority-anchors/v1",
|
|
28
|
+
corpus: "repository",
|
|
29
|
+
booleanFlags: ["json", "embedded-paths"],
|
|
30
|
+
valueFlags: ["spec", "scope-bindings"],
|
|
31
|
+
integerFlags: ["max-units", "max-anchors", "max-bytes"],
|
|
32
|
+
repeatableFlags: [],
|
|
33
|
+
},
|
|
34
|
+
consumers: {
|
|
35
|
+
contract: "nimicoding.authority-consumers.v1",
|
|
36
|
+
reportFormat: "nimicoding.authority-consumers/v1",
|
|
37
|
+
corpus: "repository",
|
|
38
|
+
booleanFlags: ["json"],
|
|
39
|
+
valueFlags: ["spec"],
|
|
40
|
+
integerFlags: ["max-files", "max-refs", "max-bytes"],
|
|
41
|
+
repeatableFlags: ["external-root"],
|
|
42
|
+
},
|
|
43
|
+
terms: {
|
|
44
|
+
contract: "nimicoding.authority-terms.v1",
|
|
45
|
+
reportFormat: "nimicoding.authority-terms/v1",
|
|
46
|
+
corpus: "repository",
|
|
47
|
+
booleanFlags: ["json", "code"],
|
|
48
|
+
valueFlags: ["spec", "term"],
|
|
49
|
+
integerFlags: ["max-units", "max-terms", "max-bytes"],
|
|
50
|
+
repeatableFlags: [],
|
|
51
|
+
},
|
|
52
|
+
"closed-sets": {
|
|
53
|
+
contract: "nimicoding.authority-closed-sets.v1",
|
|
54
|
+
reportFormat: "nimicoding.authority-closed-sets/v1",
|
|
55
|
+
corpus: "repository",
|
|
56
|
+
booleanFlags: ["json"],
|
|
57
|
+
valueFlags: ["spec"],
|
|
58
|
+
integerFlags: ["max-units", "max-sets", "max-bytes"],
|
|
59
|
+
repeatableFlags: [],
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function integerFlagAdmission() {
|
|
64
|
+
const admission = {};
|
|
65
|
+
for (const [command, entry] of Object.entries(AUTHORITY_SURFACE)) {
|
|
66
|
+
for (const flag of entry.integerFlags) {
|
|
67
|
+
const option = `--${flag}`;
|
|
68
|
+
admission[option] = admission[option] ?? [];
|
|
69
|
+
admission[option].push(command);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return admission;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function booleanFlagAdmission(flag) {
|
|
76
|
+
return Object.entries(AUTHORITY_SURFACE)
|
|
77
|
+
.filter(([, entry]) => entry.booleanFlags.includes(flag))
|
|
78
|
+
.map(([command]) => command);
|
|
79
|
+
}
|