@nimiplatform/nimi-coding 0.3.1 → 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 +32 -0
- package/CONTRIBUTING.md +3 -3
- package/README.md +297 -41
- package/README.zh-CN.md +306 -31
- package/bin/nimicoding.mjs +1 -1
- package/cli/commands/authority.mjs +804 -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 +182 -0
- package/cli/dev/check-product-conformance.mjs +889 -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 +567 -0
- package/cli/lib/authority/change-candidates.mjs +807 -0
- package/cli/lib/authority/closed-sets.mjs +645 -0
- package/cli/lib/authority/compile.mjs +92 -0
- package/cli/lib/authority/compiled-snapshot.mjs +143 -0
- package/cli/lib/authority/consumers.mjs +520 -0
- package/cli/lib/authority/diagnostics.mjs +93 -0
- package/cli/lib/authority/diff.mjs +310 -0
- package/cli/lib/authority/discover.mjs +447 -0
- package/cli/lib/authority/format.mjs +169 -0
- package/cli/lib/authority/git-snapshot.mjs +720 -0
- package/cli/lib/authority/graph.mjs +351 -0
- package/cli/lib/authority/impact.mjs +125 -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 +212 -0
- package/cli/lib/authority/sarif.mjs +174 -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 +308 -0
- package/cli/lib/authority/surface.mjs +97 -0
- package/cli/lib/authority/terms.mjs +422 -0
- package/cli/lib/authority/tracked-text.mjs +70 -0
- package/cli/lib/authority/validate.mjs +300 -0
- package/cli/lib/bootstrap.mjs +55 -71
- package/cli/lib/doctor.mjs +40 -123
- package/cli/lib/entrypoints.mjs +104 -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-scope-bindings.schema.yaml +15 -0
- package/contracts/authority-source.schema.yaml +58 -0
- package/contracts/authority-verifier-bindings.schema.yaml +31 -0
- package/methodology/authority-authoring.yaml +127 -0
- package/methodology/core.yaml +34 -27
- package/package.json +10 -6
- package/spec/authority-anchors.yaml +99 -0
- package/spec/authority-audit.yaml +89 -0
- package/spec/authority-authoring.yaml +137 -0
- package/spec/authority-change-candidates.yaml +146 -0
- package/spec/authority-closed-sets.yaml +65 -0
- package/spec/authority-consumers.yaml +77 -0
- package/spec/authority-context.yaml +50 -0
- package/spec/authority-discovery.yaml +119 -0
- package/spec/authority-graph.yaml +59 -0
- package/spec/authority-impact.yaml +61 -0
- package/spec/authority-review.yaml +71 -0
- package/spec/authority-terms.yaml +63 -0
- package/spec/product-scope.yaml +37 -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 -144
- 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,310 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { compileAuthorityPath } from "./compile.mjs";
|
|
6
|
+
import { createCompiledAuthoritySnapshot } from "./compiled-snapshot.mjs";
|
|
7
|
+
import { compareText, makeDiagnostic, portablePath, sortDiagnostics } from "./diagnostics.mjs";
|
|
8
|
+
|
|
9
|
+
const DIFF_FORMAT = "nimicoding.authority-diff/v1";
|
|
10
|
+
|
|
11
|
+
async function sourceRoot(inputPath) {
|
|
12
|
+
const absolute = path.resolve(inputPath);
|
|
13
|
+
try {
|
|
14
|
+
const info = await stat(absolute);
|
|
15
|
+
return info.isDirectory() ? absolute : path.dirname(absolute);
|
|
16
|
+
} catch {
|
|
17
|
+
return path.dirname(absolute);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sidePath(file, basePath, side) {
|
|
22
|
+
const relative = portablePath(file, basePath);
|
|
23
|
+
const safe = path.isAbsolute(relative) ? path.basename(relative) : relative;
|
|
24
|
+
return path.posix.join(side, safe || ".");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function compareRelated(left, right) {
|
|
28
|
+
return compareText(left.path, right.path)
|
|
29
|
+
|| left.range.start.line - right.range.start.line
|
|
30
|
+
|| left.range.start.column - right.range.start.column
|
|
31
|
+
|| compareText(left.pointer, right.pointer)
|
|
32
|
+
|| compareText(left.role, right.role);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sideDiagnostics(diagnostics, basePath, side) {
|
|
36
|
+
return diagnostics.map((diagnostic) => ({
|
|
37
|
+
...diagnostic,
|
|
38
|
+
path: sidePath(diagnostic.path, basePath, side),
|
|
39
|
+
related: diagnostic.related
|
|
40
|
+
.map((related) => ({ ...related, path: sidePath(related.path, basePath, side) }))
|
|
41
|
+
.sort(compareRelated),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function portableMapping(mapping, basePath) {
|
|
46
|
+
if (!mapping) return null;
|
|
47
|
+
return { ...mapping, file: portablePath(mapping.file, basePath) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mappedAt(snapshot, unitIndex, pointer) {
|
|
51
|
+
const exact = snapshot.compiled.sourceMap.fields[`/units/${unitIndex}${pointer}`];
|
|
52
|
+
if (exact) return portableMapping(exact, snapshot.inputRoot);
|
|
53
|
+
const prefix = `/units/${unitIndex}${pointer}/`;
|
|
54
|
+
const nested = Object.entries(snapshot.compiled.sourceMap.fields)
|
|
55
|
+
.filter(([candidate]) => candidate.startsWith(prefix))
|
|
56
|
+
.sort(([left], [right]) => compareText(left, right))[0]?.[1];
|
|
57
|
+
return portableMapping(nested, snapshot.inputRoot);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function addChange(changes, { unitId, operation = "modified", pointer, before, after, beforeSource = null, afterSource = null }) {
|
|
61
|
+
changes.push({
|
|
62
|
+
unitId,
|
|
63
|
+
operation,
|
|
64
|
+
pointer,
|
|
65
|
+
before,
|
|
66
|
+
after,
|
|
67
|
+
...(beforeSource ? { beforeSource } : {}),
|
|
68
|
+
...(afterSource ? { afterSource } : {}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function compareScalar(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry, pointer) {
|
|
73
|
+
const before = beforeEntry.unit;
|
|
74
|
+
const after = afterEntry.unit;
|
|
75
|
+
const read = (value, parts) => parts.reduce((current, part) => current?.[part], value);
|
|
76
|
+
const parts = pointer.slice(1).split("/");
|
|
77
|
+
const left = read(before, parts) ?? null;
|
|
78
|
+
const right = read(after, parts) ?? null;
|
|
79
|
+
if (JSON.stringify(left) === JSON.stringify(right)) return;
|
|
80
|
+
addChange(changes, {
|
|
81
|
+
unitId: after.id,
|
|
82
|
+
pointer,
|
|
83
|
+
before: left,
|
|
84
|
+
after: right,
|
|
85
|
+
beforeSource: mappedAt(beforeSnapshot, beforeEntry.unitIndex, pointer),
|
|
86
|
+
afterSource: mappedAt(afterSnapshot, afterEntry.unitIndex, pointer),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function compareValueSet(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry, pointer) {
|
|
91
|
+
const parts = pointer.slice(1).split("/");
|
|
92
|
+
const read = (unit) => parts.reduce((current, part) => current?.[part], unit) ?? [];
|
|
93
|
+
const beforeValues = read(beforeEntry.unit);
|
|
94
|
+
const afterValues = read(afterEntry.unit);
|
|
95
|
+
const beforeSet = new Set(beforeValues.map((value) => JSON.stringify(value)));
|
|
96
|
+
const afterSet = new Set(afterValues.map((value) => JSON.stringify(value)));
|
|
97
|
+
for (const [operation, values, otherSet, snapshot, entry, sourceSide] of [
|
|
98
|
+
["removed", beforeValues, afterSet, beforeSnapshot, beforeEntry, "beforeSource"],
|
|
99
|
+
["added", afterValues, beforeSet, afterSnapshot, afterEntry, "afterSource"],
|
|
100
|
+
]) {
|
|
101
|
+
values.forEach((value, index) => {
|
|
102
|
+
if (otherSet.has(JSON.stringify(value))) return;
|
|
103
|
+
addChange(changes, {
|
|
104
|
+
unitId: afterEntry.unit.id,
|
|
105
|
+
operation,
|
|
106
|
+
pointer,
|
|
107
|
+
before: operation === "removed" ? value : null,
|
|
108
|
+
after: operation === "added" ? value : null,
|
|
109
|
+
[sourceSide]: mappedAt(snapshot, entry.unitIndex, `${pointer}/${index}`),
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function compareRelations(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry) {
|
|
116
|
+
const beforeRelations = beforeEntry.unit.relations;
|
|
117
|
+
const afterRelations = afterEntry.unit.relations;
|
|
118
|
+
const beforeSet = new Set(beforeRelations.map((edge) => `${edge.type}\0${edge.target}`));
|
|
119
|
+
const afterSet = new Set(afterRelations.map((edge) => `${edge.type}\0${edge.target}`));
|
|
120
|
+
for (const [operation, edges, otherSet, snapshot, entry, sourceSide] of [
|
|
121
|
+
["removed", beforeRelations, afterSet, beforeSnapshot, beforeEntry, "beforeSource"],
|
|
122
|
+
["added", afterRelations, beforeSet, afterSnapshot, afterEntry, "afterSource"],
|
|
123
|
+
]) {
|
|
124
|
+
edges.forEach((edge, index) => {
|
|
125
|
+
if (otherSet.has(`${edge.type}\0${edge.target}`)) return;
|
|
126
|
+
addChange(changes, {
|
|
127
|
+
unitId: afterEntry.unit.id,
|
|
128
|
+
operation,
|
|
129
|
+
pointer: "/relations",
|
|
130
|
+
before: operation === "removed" ? edge : null,
|
|
131
|
+
after: operation === "added" ? edge : null,
|
|
132
|
+
[sourceSide]: mappedAt(snapshot, entry.unitIndex, `/relations/${index}`),
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sideMappedAt(snapshot, unitIndex, pointer, side) {
|
|
139
|
+
const mapped = mappedAt(snapshot, unitIndex, pointer);
|
|
140
|
+
return { ...mapped, file: path.posix.join(side, mapped.file) };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function transitionDiagnostics(before, after) {
|
|
144
|
+
const diagnostics = [];
|
|
145
|
+
for (const entry of before.byId.values()) {
|
|
146
|
+
const { unit, unitIndex } = entry;
|
|
147
|
+
if (after.byId.has(unit.id)) continue;
|
|
148
|
+
const mapped = sideMappedAt(before, unitIndex, "/id", "before");
|
|
149
|
+
diagnostics.push(makeDiagnostic({
|
|
150
|
+
code: "AUTH_DIFF_TRANSITION_INVALID",
|
|
151
|
+
file: mapped.file,
|
|
152
|
+
range: mapped.range,
|
|
153
|
+
pointer: mapped.sourcePointer,
|
|
154
|
+
reason: `authority identity cannot physically disappear: ${unit.id}`,
|
|
155
|
+
repair: unit.lifecycle === "removed"
|
|
156
|
+
? "retain the removed tombstone; permanent reserved-ID storage is not admitted"
|
|
157
|
+
: "replace the active unit with a valid removed tombstone carrying an explicit reason",
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
for (const entry of after.byId.values()) {
|
|
161
|
+
const { unit, unitIndex } = entry;
|
|
162
|
+
const previous = before.byId.get(unit.id)?.unit;
|
|
163
|
+
if (!previous) continue;
|
|
164
|
+
const mapped = sideMappedAt(after, unitIndex, "/id", "after");
|
|
165
|
+
if (previous.lifecycle === "removed" && unit.lifecycle !== "removed") diagnostics.push(makeDiagnostic({
|
|
166
|
+
code: "AUTH_DIFF_TRANSITION_INVALID",
|
|
167
|
+
file: mapped.file,
|
|
168
|
+
range: mapped.range,
|
|
169
|
+
pointer: mapped.sourcePointer,
|
|
170
|
+
reason: `removed identity cannot return to ${unit.lifecycle}: ${unit.id}`,
|
|
171
|
+
repair: "retain the removed identity and declare a new product-authorized ID",
|
|
172
|
+
}));
|
|
173
|
+
if (previous.kind !== unit.kind) diagnostics.push(makeDiagnostic({
|
|
174
|
+
code: "AUTH_DIFF_TRANSITION_INVALID",
|
|
175
|
+
file: mapped.file,
|
|
176
|
+
range: mapped.range,
|
|
177
|
+
pointer: mapped.sourcePointer,
|
|
178
|
+
reason: `authority identity cannot change kind from ${previous.kind} to ${unit.kind}: ${unit.id}`,
|
|
179
|
+
repair: "retain the original identity and declare a distinct ID for the new kind",
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
return sortDiagnostics(diagnostics);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function compareUnits(before, after) {
|
|
186
|
+
const changes = [];
|
|
187
|
+
const ids = [...new Set([...before.byId.keys(), ...after.byId.keys()])].sort(compareText);
|
|
188
|
+
for (const id of ids) {
|
|
189
|
+
const left = before.byId.get(id);
|
|
190
|
+
const right = after.byId.get(id);
|
|
191
|
+
if (!left) {
|
|
192
|
+
addChange(changes, {
|
|
193
|
+
unitId: id,
|
|
194
|
+
operation: "unit_added",
|
|
195
|
+
pointer: "",
|
|
196
|
+
before: null,
|
|
197
|
+
after: right.unit,
|
|
198
|
+
afterSource: mappedAt(after, right.unitIndex, "/id"),
|
|
199
|
+
});
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (!right) continue;
|
|
203
|
+
for (const pointer of ["/kind", "/owner", "/lifecycle", "/metadata/title", "/metadata/reason", "/semantic/meaning", "/semantic/modality", "/semantic/statement", "/semantic/condition", "/semantic/failure"]) {
|
|
204
|
+
compareScalar(changes, before, after, left, right, pointer);
|
|
205
|
+
}
|
|
206
|
+
compareValueSet(changes, before, after, left, right, "/semantic/scope");
|
|
207
|
+
compareRelations(changes, before, after, left, right);
|
|
208
|
+
}
|
|
209
|
+
return changes.sort((left, right) => (
|
|
210
|
+
compareText(left.unitId, right.unitId)
|
|
211
|
+
|| compareText(left.pointer, right.pointer)
|
|
212
|
+
|| compareText(left.operation, right.operation)
|
|
213
|
+
|| compareText(JSON.stringify(left.before), JSON.stringify(right.before))
|
|
214
|
+
|| compareText(JSON.stringify(left.after), JSON.stringify(right.after))
|
|
215
|
+
));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function canonicalDiffBytes(diff) {
|
|
219
|
+
return Buffer.byteLength(JSON.stringify(diff), "utf8");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function budgetDiagnostic(requiredBytes, maxBytes) {
|
|
223
|
+
return makeDiagnostic({
|
|
224
|
+
code: "AUTH_DIFF_BUDGET",
|
|
225
|
+
file: ".",
|
|
226
|
+
range: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
|
|
227
|
+
pointer: "",
|
|
228
|
+
reason: `complete semantic diff requires ${requiredBytes} UTF-8 bytes but max-bytes is ${maxBytes}`,
|
|
229
|
+
repair: "increase the explicit byte budget; partial semantic diff is forbidden",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function diffAuthorityPathsWithSnapshots(beforePath, afterPath, { maxBytes = null } = {}) {
|
|
234
|
+
const [before, after, beforeBase, afterBase] = await Promise.all([
|
|
235
|
+
compileAuthorityPath(beforePath),
|
|
236
|
+
compileAuthorityPath(afterPath),
|
|
237
|
+
sourceRoot(beforePath),
|
|
238
|
+
sourceRoot(afterPath),
|
|
239
|
+
]);
|
|
240
|
+
if (!before.ok || !after.ok) return {
|
|
241
|
+
beforeSnapshot: null,
|
|
242
|
+
afterSnapshot: null,
|
|
243
|
+
result: {
|
|
244
|
+
ok: false,
|
|
245
|
+
diagnostics: sortDiagnostics([
|
|
246
|
+
...sideDiagnostics(before.diagnostics, beforeBase, "before"),
|
|
247
|
+
...sideDiagnostics(after.diagnostics, afterBase, "after"),
|
|
248
|
+
]),
|
|
249
|
+
fileCount: before.fileCount + after.fileCount,
|
|
250
|
+
unitCount: 0,
|
|
251
|
+
diff: null,
|
|
252
|
+
partial: false,
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
const beforeSnapshot = createCompiledAuthoritySnapshot(before, beforeBase);
|
|
256
|
+
const afterSnapshot = createCompiledAuthoritySnapshot(after, afterBase);
|
|
257
|
+
return {
|
|
258
|
+
beforeSnapshot,
|
|
259
|
+
afterSnapshot,
|
|
260
|
+
result: diffAuthoritySnapshots(beforeSnapshot, afterSnapshot, { maxBytes }),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function diffAuthorityPaths(beforePath, afterPath, options = {}) {
|
|
265
|
+
return (await diffAuthorityPathsWithSnapshots(beforePath, afterPath, options)).result;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function diffAuthoritySnapshots(beforeSnapshot, afterSnapshot, { maxBytes = null } = {}) {
|
|
269
|
+
const before = beforeSnapshot.compiled;
|
|
270
|
+
const after = afterSnapshot.compiled;
|
|
271
|
+
const invalidTransitions = transitionDiagnostics(beforeSnapshot, afterSnapshot);
|
|
272
|
+
if (invalidTransitions.length > 0) return {
|
|
273
|
+
ok: false,
|
|
274
|
+
diagnostics: invalidTransitions,
|
|
275
|
+
fileCount: before.fileCount + after.fileCount,
|
|
276
|
+
unitCount: 0,
|
|
277
|
+
diff: null,
|
|
278
|
+
partial: false,
|
|
279
|
+
};
|
|
280
|
+
const changes = compareUnits(beforeSnapshot, afterSnapshot);
|
|
281
|
+
const diff = {
|
|
282
|
+
format: DIFF_FORMAT,
|
|
283
|
+
changes,
|
|
284
|
+
summary: {
|
|
285
|
+
units: new Set(changes.map((change) => change.unitId)).size,
|
|
286
|
+
changes: changes.length,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
const payloadBytes = canonicalDiffBytes(diff);
|
|
290
|
+
if (maxBytes !== null && payloadBytes > maxBytes) return {
|
|
291
|
+
ok: false,
|
|
292
|
+
diagnostics: [budgetDiagnostic(payloadBytes, maxBytes)],
|
|
293
|
+
fileCount: before.fileCount + after.fileCount,
|
|
294
|
+
unitCount: 0,
|
|
295
|
+
payloadBytes,
|
|
296
|
+
diff: null,
|
|
297
|
+
partial: false,
|
|
298
|
+
};
|
|
299
|
+
return {
|
|
300
|
+
ok: true,
|
|
301
|
+
diagnostics: [],
|
|
302
|
+
fileCount: before.fileCount + after.fileCount,
|
|
303
|
+
unitCount: new Set([...beforeSnapshot.byId.keys(), ...afterSnapshot.byId.keys()]).size,
|
|
304
|
+
payloadBytes,
|
|
305
|
+
diff,
|
|
306
|
+
before,
|
|
307
|
+
after,
|
|
308
|
+
partial: false,
|
|
309
|
+
};
|
|
310
|
+
}
|