@nimiplatform/nimi-coding 0.2.6 → 0.2.7
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 +13 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/cli/constants.mjs +1 -1
- package/cli/lib/audit-sweep-runtime/admissions.mjs +118 -0
- package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +3 -0
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +134 -2
- package/cli/lib/audit-sweep-runtime/inventory.mjs +33 -1
- package/cli/lib/audit-sweep-runtime/validators.mjs +34 -0
- package/config/bootstrap.yaml +1 -1
- package/contracts/audit-chunk.schema.yaml +13 -0
- package/contracts/audit-plan.schema.yaml +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to `@nimiplatform/nimi-coding` are tracked here.
|
|
|
4
4
|
|
|
5
5
|
This project follows semantic versioning for published npm releases.
|
|
6
6
|
|
|
7
|
+
## 0.2.7
|
|
8
|
+
|
|
9
|
+
- Added delegated projection admissions for spec-authority sweeps so a host
|
|
10
|
+
`.nimi/spec/**` subtree projected from a parent or external source authority
|
|
11
|
+
can audit host-local projection evidence while delegating source-owned
|
|
12
|
+
implementation refs through an explicit boundary.
|
|
13
|
+
- Kept delegated projections as audit modeling only: the CLI records and
|
|
14
|
+
validates source-authority boundaries, but does not read, sync, rewrite, or
|
|
15
|
+
mutate parent/external source repositories.
|
|
16
|
+
- Ignored npm package/import specifiers and explicit `./` relative refs when
|
|
17
|
+
deriving declared evidence targets, so package subpaths and YAML fragment refs
|
|
18
|
+
do not get promoted into project-local evidence paths.
|
|
19
|
+
|
|
7
20
|
## 0.2.6
|
|
8
21
|
|
|
9
22
|
- Hard-cut high-risk admission records out of active `.nimi/spec/**`
|
package/README.md
CHANGED
|
@@ -314,7 +314,7 @@ repository itself keeps the package-owned source directly under
|
|
|
314
314
|
|
|
315
315
|
```bash
|
|
316
316
|
pnpm install
|
|
317
|
-
pnpm test # runs the node:test suite
|
|
317
|
+
pnpm test # runs the node:test suite
|
|
318
318
|
pnpm check:pack # npm pack --dry-run
|
|
319
319
|
pnpm check:ci # test + pack + CLI help/version smoke
|
|
320
320
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -256,7 +256,7 @@ Nimi Coding 坐在你已经用的 AI host *底下*。它是让 AI 做完的工
|
|
|
256
256
|
|
|
257
257
|
```bash
|
|
258
258
|
pnpm install
|
|
259
|
-
pnpm test # 跑 node:test
|
|
259
|
+
pnpm test # 跑 node:test 套件
|
|
260
260
|
pnpm check:pack # npm pack --dry-run
|
|
261
261
|
pnpm check:ci # test + pack + CLI help/version smoke
|
|
262
262
|
```
|
package/cli/constants.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { pathExists } from "../fs-helpers.mjs";
|
|
|
9
9
|
export const AUDIT_SWEEP_PROJECT_CONFIG_REF = ".nimi/config/audit-sweep.yaml";
|
|
10
10
|
export const APP_SLICE_ADMISSION_REF = ".nimi/spec/platform/kernel/tables/app-slice-admissions.yaml";
|
|
11
11
|
const AUDIT_EVIDENCE_ROOT_TABLE_BASENAME = "audit-evidence-roots.yaml";
|
|
12
|
+
const DELEGATED_PROJECTION_ADMISSION_BASENAME = "delegated-projection-admissions.yaml";
|
|
12
13
|
const PACKAGE_AUTHORITY_ADMISSION_BASENAME = "package-authority-admissions.yaml";
|
|
13
14
|
|
|
14
15
|
export function refInsideRoot(fileRef, rootRef) {
|
|
@@ -23,6 +24,32 @@ function safeProjectRef(value) {
|
|
|
23
24
|
&& !value.split("/").includes("..");
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function normalizeProjectRef(value) {
|
|
28
|
+
const normalized = String(value ?? "").trim().replace(/\\/g, "/");
|
|
29
|
+
if (/^[a-z][a-z0-9+.-]*:\/\/$/iu.test(normalized)) {
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
return normalized.replace(/\/$/, "");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function activePosture(row) {
|
|
36
|
+
return String(row?.status ?? row?.admission_posture ?? "").trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function safeProjectionSourceRef(value) {
|
|
40
|
+
const normalized = normalizeProjectRef(value);
|
|
41
|
+
return normalized.startsWith("parent://")
|
|
42
|
+
|| normalized.startsWith("package://")
|
|
43
|
+
|| safeProjectRef(normalized);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safeRelativePath(value) {
|
|
47
|
+
const normalized = normalizeProjectRef(value);
|
|
48
|
+
return normalized.length > 0
|
|
49
|
+
&& !path.isAbsolute(normalized)
|
|
50
|
+
&& !normalized.split("/").includes("..");
|
|
51
|
+
}
|
|
52
|
+
|
|
26
53
|
export async function loadAuditSweepProjectConfig(projectRoot) {
|
|
27
54
|
const configPath = artifactPath(projectRoot, AUDIT_SWEEP_PROJECT_CONFIG_REF);
|
|
28
55
|
const info = await pathExists(configPath);
|
|
@@ -241,6 +268,97 @@ export async function loadAuditEvidenceRootAdmissions(projectRoot, listGitFiles,
|
|
|
241
268
|
return { ok: true, tableRefs, admissions };
|
|
242
269
|
}
|
|
243
270
|
|
|
271
|
+
export async function loadDelegatedProjectionAdmissions(projectRoot, listGitFiles, listFallbackFiles) {
|
|
272
|
+
const tableRefs = await listSpecTableRefs(projectRoot, DELEGATED_PROJECTION_ADMISSION_BASENAME, listGitFiles, listFallbackFiles);
|
|
273
|
+
if (!tableRefs) {
|
|
274
|
+
return { ok: true, tableRefs: [], admissions: [] };
|
|
275
|
+
}
|
|
276
|
+
const admissions = [];
|
|
277
|
+
const seenIds = new Set();
|
|
278
|
+
|
|
279
|
+
for (const tableRef of tableRefs) {
|
|
280
|
+
let parsed;
|
|
281
|
+
try {
|
|
282
|
+
parsed = YAML.parse(await readFile(artifactPath(projectRoot, tableRef), "utf8"));
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
error: `nimicoding sweep audit refused: ${tableRef} must contain valid YAML (${error.message}).\n`,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const rows = Array.isArray(parsed?.admissions) ? parsed.admissions : null;
|
|
290
|
+
if (!rows) {
|
|
291
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} must declare admissions as an array.\n` };
|
|
292
|
+
}
|
|
293
|
+
for (const row of rows) {
|
|
294
|
+
const id = String(row?.id ?? "").trim();
|
|
295
|
+
const posture = activePosture(row);
|
|
296
|
+
const ownerDomain = String(row?.owner_domain ?? "").trim();
|
|
297
|
+
const authorityRoot = normalizeProjectRef(row?.authority_root);
|
|
298
|
+
const sourceAuthorityRoot = normalizeProjectRef(row?.source_authority_root);
|
|
299
|
+
const localProjectionEvidenceRoots = Array.isArray(row?.local_projection_evidence_roots)
|
|
300
|
+
? row.local_projection_evidence_roots.map(normalizeProjectRef).filter(Boolean)
|
|
301
|
+
: null;
|
|
302
|
+
const delegatedEvidenceRoots = Array.isArray(row?.delegated_evidence_roots)
|
|
303
|
+
? row.delegated_evidence_roots.map(normalizeProjectRef).filter(Boolean)
|
|
304
|
+
: null;
|
|
305
|
+
const delegatedDeclaredEvidencePrefixes = Array.isArray(row?.delegated_declared_evidence_prefixes)
|
|
306
|
+
? row.delegated_declared_evidence_prefixes.map(normalizeProjectRef).filter(Boolean)
|
|
307
|
+
: [];
|
|
308
|
+
const hostOwnedRelativePaths = Array.isArray(row?.host_owned_relative_paths)
|
|
309
|
+
? row.host_owned_relative_paths.map(normalizeProjectRef).filter(Boolean)
|
|
310
|
+
: [];
|
|
311
|
+
const requiredVerificationCommands = Array.isArray(row?.required_verification_commands)
|
|
312
|
+
? row.required_verification_commands.map((entry) => String(entry ?? "").trim()).filter(Boolean)
|
|
313
|
+
: [];
|
|
314
|
+
const admissionKey = `${tableRef}#${id}`;
|
|
315
|
+
if (!id || seenIds.has(admissionKey)) {
|
|
316
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} has missing or duplicate delegated projection id.\n` };
|
|
317
|
+
}
|
|
318
|
+
seenIds.add(admissionKey);
|
|
319
|
+
if (posture !== "active") {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (!ownerDomain || !safeProjectRef(authorityRoot) || !authorityRoot.startsWith(".nimi/spec/")) {
|
|
323
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} ${id} has invalid owner_domain or authority_root.\n` };
|
|
324
|
+
}
|
|
325
|
+
if (!safeProjectionSourceRef(sourceAuthorityRoot)) {
|
|
326
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} ${id} has invalid source_authority_root.\n` };
|
|
327
|
+
}
|
|
328
|
+
if (!localProjectionEvidenceRoots?.length || !localProjectionEvidenceRoots.every((rootRef) => safeProjectRef(rootRef) && !rootRef.startsWith(".nimi/spec/"))) {
|
|
329
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} ${id} has invalid local_projection_evidence_roots.\n` };
|
|
330
|
+
}
|
|
331
|
+
if (!delegatedEvidenceRoots?.length || !delegatedEvidenceRoots.every(safeProjectionSourceRef)) {
|
|
332
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} ${id} has invalid delegated_evidence_roots.\n` };
|
|
333
|
+
}
|
|
334
|
+
if (delegatedDeclaredEvidencePrefixes.length === 0 || !delegatedDeclaredEvidencePrefixes.every(safeRelativePath)) {
|
|
335
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} ${id} must declare delegated_declared_evidence_prefixes.\n` };
|
|
336
|
+
}
|
|
337
|
+
if (!hostOwnedRelativePaths.every(safeRelativePath)) {
|
|
338
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} ${id} has invalid host_owned_relative_paths.\n` };
|
|
339
|
+
}
|
|
340
|
+
if (!requiredVerificationCommands.every((command) => command.length > 0)) {
|
|
341
|
+
return { ok: false, error: `nimicoding sweep audit refused: ${tableRef} ${id} has invalid required_verification_commands.\n` };
|
|
342
|
+
}
|
|
343
|
+
admissions.push({
|
|
344
|
+
id,
|
|
345
|
+
owner_domain: ownerDomain,
|
|
346
|
+
status: posture,
|
|
347
|
+
authority_root: authorityRoot,
|
|
348
|
+
source_authority_root: sourceAuthorityRoot,
|
|
349
|
+
local_projection_evidence_roots: localProjectionEvidenceRoots,
|
|
350
|
+
delegated_evidence_roots: delegatedEvidenceRoots,
|
|
351
|
+
delegated_declared_evidence_prefixes: delegatedDeclaredEvidencePrefixes,
|
|
352
|
+
host_owned_relative_paths: hostOwnedRelativePaths,
|
|
353
|
+
required_verification_commands: requiredVerificationCommands,
|
|
354
|
+
admission_ref: admissionKey,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { ok: true, tableRefs, admissions };
|
|
360
|
+
}
|
|
361
|
+
|
|
244
362
|
export async function loadPackageAuthorityAdmissions(projectRoot, listGitFiles, listFallbackFiles) {
|
|
245
363
|
const tableRefs = await listSpecTableRefs(projectRoot, PACKAGE_AUTHORITY_ADMISSION_BASENAME, listGitFiles, listFallbackFiles);
|
|
246
364
|
if (!tableRefs) {
|
|
@@ -67,6 +67,9 @@ function chunkAcceptsEvidenceFile(chunk, fileRef) {
|
|
|
67
67
|
if (surface === "spec-generation-audit" || surface === "high-risk-admissions" || surface === "package-meta" || surface === "package-root") {
|
|
68
68
|
return true;
|
|
69
69
|
}
|
|
70
|
+
if (surface === "delegated-projection") {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
70
73
|
return false;
|
|
71
74
|
}
|
|
72
75
|
|
|
@@ -46,11 +46,17 @@ function looksLikeSpecAuthorityRelativeRef(normalized) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function normalizeDeclaredEvidenceRef(value) {
|
|
49
|
-
const
|
|
49
|
+
const raw = String(value ?? "")
|
|
50
50
|
.trim()
|
|
51
51
|
.replace(/\\/g, "/")
|
|
52
|
-
.replace(/^\.\//, "")
|
|
53
52
|
.replace(/[),.;:]+$/u, "");
|
|
53
|
+
if (!raw || raw.startsWith("../") || raw.includes("/../") || raw.startsWith("http:") || raw.startsWith("https:")) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (raw.startsWith("@") || raw.startsWith("./")) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const normalized = raw.replace(/^\.\//, "");
|
|
54
60
|
if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("http:") || normalized.startsWith("https:")) {
|
|
55
61
|
return null;
|
|
56
62
|
}
|
|
@@ -102,6 +108,37 @@ function candidateEvidenceRefsForDeclaredRef(declaredRef, evidenceRoots) {
|
|
|
102
108
|
return [...new Set(candidates)].sort();
|
|
103
109
|
}
|
|
104
110
|
|
|
111
|
+
function normalizedRef(value) {
|
|
112
|
+
return String(value ?? "").replace(/\\/g, "/").replace(/\/$/, "");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function refInsideRoot(fileRef, rootRef) {
|
|
116
|
+
const root = normalizedRef(rootRef);
|
|
117
|
+
return Boolean(root) && (fileRef === root || fileRef.startsWith(`${root}/`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function delegatedProjectionAdmissionForFile(fileRef, admissions = []) {
|
|
121
|
+
return admissions.find((admission) => {
|
|
122
|
+
if (!refInsideRoot(fileRef, admission.authority_root)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const relative = fileRef === admission.authority_root
|
|
126
|
+
? ""
|
|
127
|
+
: fileRef.slice(admission.authority_root.length + 1);
|
|
128
|
+
return !(admission.host_owned_relative_paths ?? []).some((hostOwnedPath) => (
|
|
129
|
+
relative === hostOwnedPath || relative.startsWith(`${hostOwnedPath}/`)
|
|
130
|
+
));
|
|
131
|
+
}) ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function declaredRefDelegated(ref, prefixes = []) {
|
|
135
|
+
const normalized = normalizedRef(ref);
|
|
136
|
+
return prefixes.some((prefix) => {
|
|
137
|
+
const normalizedPrefix = normalizedRef(prefix);
|
|
138
|
+
return normalizedPrefix && (normalized === normalizedPrefix || normalized.startsWith(`${normalizedPrefix}/`));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
105
142
|
function extractDeclaredEvidenceRefs(text) {
|
|
106
143
|
const refs = [];
|
|
107
144
|
for (const match of String(text ?? "").matchAll(DECLARED_EVIDENCE_REF_PATTERN)) {
|
|
@@ -147,6 +184,74 @@ function extractModuleMapRefs(markdownText) {
|
|
|
147
184
|
return [...new Set(refs)].sort();
|
|
148
185
|
}
|
|
149
186
|
|
|
187
|
+
function buildDelegatedProjectionChunk({
|
|
188
|
+
admission,
|
|
189
|
+
entries,
|
|
190
|
+
chunkIndex,
|
|
191
|
+
criteria,
|
|
192
|
+
authorityTextByRef,
|
|
193
|
+
}) {
|
|
194
|
+
const authorityRefs = entries.map((entry) => entry.file_ref).sort();
|
|
195
|
+
const authorityText = authorityRefs
|
|
196
|
+
.map((authorityRef) => authorityTextByRef?.get(authorityRef) ?? "")
|
|
197
|
+
.join("\n");
|
|
198
|
+
const declaredEvidenceRefs = extractDeclaredEvidenceRefs(authorityText);
|
|
199
|
+
const delegatedDeclaredEvidenceRefs = declaredEvidenceRefs
|
|
200
|
+
.filter((ref) => declaredRefDelegated(ref, admission.delegated_declared_evidence_prefixes))
|
|
201
|
+
.sort();
|
|
202
|
+
const localDeclaredEvidenceRefs = declaredEvidenceRefs
|
|
203
|
+
.filter((ref) => !declaredRefDelegated(ref, admission.delegated_declared_evidence_prefixes))
|
|
204
|
+
.sort();
|
|
205
|
+
const evidenceRoots = [...new Set(admission.local_projection_evidence_roots ?? [])].sort();
|
|
206
|
+
const declaredEvidenceTargets = localDeclaredEvidenceRefs
|
|
207
|
+
.map((evidenceRef) => ({
|
|
208
|
+
source_path: evidenceRef,
|
|
209
|
+
candidates: candidateEvidenceRefsForDeclaredRef(evidenceRef, evidenceRoots),
|
|
210
|
+
}))
|
|
211
|
+
.filter((target) => target.candidates.length > 0);
|
|
212
|
+
return {
|
|
213
|
+
chunk_id: [
|
|
214
|
+
`chunk-${String(chunkIndex).padStart(3, "0")}`,
|
|
215
|
+
slugPart(admission.owner_domain),
|
|
216
|
+
"delegated-projection",
|
|
217
|
+
slugPart(admission.id),
|
|
218
|
+
].join("-"),
|
|
219
|
+
state: "planned",
|
|
220
|
+
owner_domain: admission.owner_domain,
|
|
221
|
+
planning_basis: "spec_authority",
|
|
222
|
+
spec_surface: "delegated-projection",
|
|
223
|
+
criteria,
|
|
224
|
+
files: authorityRefs,
|
|
225
|
+
authority_refs: authorityRefs,
|
|
226
|
+
authority_kind: "delegated_projection",
|
|
227
|
+
delegated_projection_id: admission.id,
|
|
228
|
+
admission_ref: admission.admission_ref,
|
|
229
|
+
authority_root: admission.authority_root,
|
|
230
|
+
source_authority_root: admission.source_authority_root,
|
|
231
|
+
admitted_evidence_roots: evidenceRoots,
|
|
232
|
+
delegated_evidence_roots: admission.delegated_evidence_roots,
|
|
233
|
+
delegated_declared_evidence_prefixes: admission.delegated_declared_evidence_prefixes,
|
|
234
|
+
...(delegatedDeclaredEvidenceRefs.length > 0 ? {
|
|
235
|
+
delegated_declared_evidence_refs: delegatedDeclaredEvidenceRefs,
|
|
236
|
+
declared_evidence_delegation: {
|
|
237
|
+
status: "delegated_to_source_authority",
|
|
238
|
+
delegated_ref_count: delegatedDeclaredEvidenceRefs.length,
|
|
239
|
+
source_authority_root: admission.source_authority_root,
|
|
240
|
+
delegated_evidence_roots: admission.delegated_evidence_roots,
|
|
241
|
+
},
|
|
242
|
+
} : {}),
|
|
243
|
+
...(admission.required_verification_commands?.length > 0 ? {
|
|
244
|
+
required_verification_commands: admission.required_verification_commands,
|
|
245
|
+
} : {}),
|
|
246
|
+
...(declaredEvidenceTargets.length > 0 ? {
|
|
247
|
+
declared_evidence_targets: declaredEvidenceTargets,
|
|
248
|
+
} : {}),
|
|
249
|
+
evidence_roots: evidenceRoots,
|
|
250
|
+
file_count: authorityRefs.length,
|
|
251
|
+
finding_count: 0,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
150
255
|
function candidateEvidenceRefsForModuleMapPath(modulePath, evidenceRoots) {
|
|
151
256
|
const normalized = String(modulePath ?? "").replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
152
257
|
if (!normalized || normalized.startsWith("http:") || normalized.startsWith("https:")) {
|
|
@@ -181,6 +286,16 @@ function candidateEvidenceRefsForModuleMapPath(modulePath, evidenceRoots) {
|
|
|
181
286
|
export function buildSpecChunks(includedInventory, options) {
|
|
182
287
|
const sortedEntries = [...includedInventory].sort((left, right) => left.file_ref.localeCompare(right.file_ref));
|
|
183
288
|
const includedRefs = new Set(sortedEntries.map((entry) => entry.file_ref));
|
|
289
|
+
const delegatedEntriesByAdmissionRef = new Map();
|
|
290
|
+
for (const entry of sortedEntries) {
|
|
291
|
+
const admission = delegatedProjectionAdmissionForFile(entry.file_ref, options.delegatedProjectionAdmissions);
|
|
292
|
+
if (!admission) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const entries = delegatedEntriesByAdmissionRef.get(admission.admission_ref) ?? [];
|
|
296
|
+
entries.push(entry);
|
|
297
|
+
delegatedEntriesByAdmissionRef.set(admission.admission_ref, entries);
|
|
298
|
+
}
|
|
184
299
|
const hostProjectionByHostRef = new Map();
|
|
185
300
|
const hostProjectionsByPackageRef = new Map();
|
|
186
301
|
for (const admission of options.packageAuthorityAdmissions ?? []) {
|
|
@@ -202,7 +317,24 @@ export function buildSpecChunks(includedInventory, options) {
|
|
|
202
317
|
}
|
|
203
318
|
let chunkIndex = 0;
|
|
204
319
|
const chunks = [];
|
|
320
|
+
const emittedDelegatedAdmissions = new Set();
|
|
205
321
|
for (const entry of sortedEntries) {
|
|
322
|
+
const delegatedAdmission = delegatedProjectionAdmissionForFile(entry.file_ref, options.delegatedProjectionAdmissions);
|
|
323
|
+
if (delegatedAdmission) {
|
|
324
|
+
if (emittedDelegatedAdmissions.has(delegatedAdmission.admission_ref)) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
emittedDelegatedAdmissions.add(delegatedAdmission.admission_ref);
|
|
328
|
+
chunkIndex += 1;
|
|
329
|
+
chunks.push(buildDelegatedProjectionChunk({
|
|
330
|
+
admission: delegatedAdmission,
|
|
331
|
+
entries: delegatedEntriesByAdmissionRef.get(delegatedAdmission.admission_ref) ?? [entry],
|
|
332
|
+
chunkIndex,
|
|
333
|
+
criteria: options.criteria,
|
|
334
|
+
authorityTextByRef: options.authorityTextByRef,
|
|
335
|
+
}));
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
206
338
|
if (hostProjectionByHostRef.has(entry.file_ref)) {
|
|
207
339
|
continue;
|
|
208
340
|
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
loadAppSliceAdmissions,
|
|
32
32
|
loadAuditEvidenceRootAdmissions,
|
|
33
33
|
loadAuditSweepProjectConfig,
|
|
34
|
+
loadDelegatedProjectionAdmissions,
|
|
34
35
|
loadPackageAuthorityAdmissions,
|
|
35
36
|
} from "./admissions.mjs";
|
|
36
37
|
import { buildCoverageQuality } from "./coverage-quality.mjs";
|
|
@@ -441,6 +442,8 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
441
442
|
let appSliceAdmissions = [];
|
|
442
443
|
let auditEvidenceRootAdmissions = [];
|
|
443
444
|
let auditEvidenceRootAdmissionRefs = [];
|
|
445
|
+
let delegatedProjectionAdmissions = [];
|
|
446
|
+
let delegatedProjectionAdmissionRefs = [];
|
|
444
447
|
let packageAuthorityAdmissions = [];
|
|
445
448
|
let packageAuthorityAdmissionRefs = [];
|
|
446
449
|
if (chunkBasis.basis === "spec") {
|
|
@@ -455,6 +458,12 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
455
458
|
}
|
|
456
459
|
auditEvidenceRootAdmissions = loadedEvidenceRootAdmissions.admissions;
|
|
457
460
|
auditEvidenceRootAdmissionRefs = loadedEvidenceRootAdmissions.tableRefs;
|
|
461
|
+
const loadedDelegatedProjectionAdmissions = await loadDelegatedProjectionAdmissions(projectRoot, listGitFiles, listFallbackFiles);
|
|
462
|
+
if (!loadedDelegatedProjectionAdmissions.ok) {
|
|
463
|
+
return inputError(loadedDelegatedProjectionAdmissions.error);
|
|
464
|
+
}
|
|
465
|
+
delegatedProjectionAdmissions = loadedDelegatedProjectionAdmissions.admissions;
|
|
466
|
+
delegatedProjectionAdmissionRefs = loadedDelegatedProjectionAdmissions.tableRefs;
|
|
458
467
|
const loadedPackageAuthorityAdmissions = await loadPackageAuthorityAdmissions(projectRoot, listGitFiles, listFallbackFiles);
|
|
459
468
|
if (!loadedPackageAuthorityAdmissions.ok) {
|
|
460
469
|
return inputError(loadedPackageAuthorityAdmissions.error);
|
|
@@ -513,7 +522,7 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
513
522
|
}
|
|
514
523
|
}
|
|
515
524
|
let chunks = chunkBasis.basis === "spec"
|
|
516
|
-
? buildSpecChunks(includedInventory, { criteria, targetRootRef, appSliceAdmissions, auditEvidenceRootAdmissions, packageAuthorityAdmissions, authorityTextByRef })
|
|
525
|
+
? buildSpecChunks(includedInventory, { criteria, targetRootRef, appSliceAdmissions, auditEvidenceRootAdmissions, delegatedProjectionAdmissions, packageAuthorityAdmissions, authorityTextByRef })
|
|
517
526
|
: buildFileChunks(includedInventory, { criteria, maxFilesPerChunk });
|
|
518
527
|
let evidenceInventory = [];
|
|
519
528
|
let unmappedEvidenceFiles = [];
|
|
@@ -589,6 +598,22 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
589
598
|
...(chunkBasis.basis === "spec" && auditEvidenceRootAdmissionRefs.length > 0 ? {
|
|
590
599
|
audit_evidence_root_refs: auditEvidenceRootAdmissionRefs,
|
|
591
600
|
} : {}),
|
|
601
|
+
...(chunkBasis.basis === "spec" && delegatedProjectionAdmissions.length > 0 ? {
|
|
602
|
+
delegated_projection_admission_refs: delegatedProjectionAdmissionRefs,
|
|
603
|
+
delegated_projection_admissions: delegatedProjectionAdmissions.map((admission) => ({
|
|
604
|
+
id: admission.id,
|
|
605
|
+
owner_domain: admission.owner_domain,
|
|
606
|
+
status: admission.status,
|
|
607
|
+
authority_root: admission.authority_root,
|
|
608
|
+
source_authority_root: admission.source_authority_root,
|
|
609
|
+
local_projection_evidence_roots: admission.local_projection_evidence_roots,
|
|
610
|
+
delegated_evidence_roots: admission.delegated_evidence_roots,
|
|
611
|
+
delegated_declared_evidence_prefixes: admission.delegated_declared_evidence_prefixes,
|
|
612
|
+
host_owned_relative_paths: admission.host_owned_relative_paths,
|
|
613
|
+
required_verification_commands: admission.required_verification_commands,
|
|
614
|
+
admission_ref: admission.admission_ref,
|
|
615
|
+
})),
|
|
616
|
+
} : {}),
|
|
592
617
|
...(chunkBasis.basis === "spec" && packageAuthorityAdmissions.length > 0 ? {
|
|
593
618
|
package_authority_admission_refs: packageAuthorityAdmissionRefs,
|
|
594
619
|
package_authority_admissions: packageAuthorityAdmissions.map((admission) => ({
|
|
@@ -669,11 +694,18 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
669
694
|
...(chunk.authority_kind ? { authority_kind: chunk.authority_kind } : {}),
|
|
670
695
|
...(chunk.app_id ? { app_id: chunk.app_id } : {}),
|
|
671
696
|
...(chunk.package_authority_id ? { package_authority_id: chunk.package_authority_id } : {}),
|
|
697
|
+
...(chunk.delegated_projection_id ? { delegated_projection_id: chunk.delegated_projection_id } : {}),
|
|
672
698
|
...(chunk.admission_ref ? { admission_ref: chunk.admission_ref } : {}),
|
|
673
699
|
...(chunk.authority_root ? { authority_root: chunk.authority_root } : {}),
|
|
700
|
+
...(chunk.source_authority_root ? { source_authority_root: chunk.source_authority_root } : {}),
|
|
674
701
|
...(chunk.evidence_root_admission_refs ? { evidence_root_admission_refs: chunk.evidence_root_admission_refs } : {}),
|
|
675
702
|
...(chunk.admitted_evidence_roots ? { admitted_evidence_roots: chunk.admitted_evidence_roots } : {}),
|
|
676
703
|
...(chunk.host_authority_projection_refs ? { host_authority_projection_refs: chunk.host_authority_projection_refs } : {}),
|
|
704
|
+
...(chunk.delegated_evidence_roots ? { delegated_evidence_roots: chunk.delegated_evidence_roots } : {}),
|
|
705
|
+
...(chunk.delegated_declared_evidence_prefixes ? { delegated_declared_evidence_prefixes: chunk.delegated_declared_evidence_prefixes } : {}),
|
|
706
|
+
...(chunk.delegated_declared_evidence_refs ? { delegated_declared_evidence_refs: chunk.delegated_declared_evidence_refs } : {}),
|
|
707
|
+
...(chunk.declared_evidence_delegation ? { declared_evidence_delegation: chunk.declared_evidence_delegation } : {}),
|
|
708
|
+
...(chunk.required_verification_commands ? { required_verification_commands: chunk.required_verification_commands } : {}),
|
|
677
709
|
...(chunk.declared_evidence_targets ? { declared_evidence_targets: chunk.declared_evidence_targets } : {}),
|
|
678
710
|
...(chunk.evidence_roots ? { evidence_roots: chunk.evidence_roots } : {}),
|
|
679
711
|
...(chunk.declared_evidence_unresolved ? { declared_evidence_unresolved: chunk.declared_evidence_unresolved } : {}),
|
|
@@ -259,6 +259,40 @@ function validatePlanShape(plan, sweepId, checks) {
|
|
|
259
259
|
));
|
|
260
260
|
});
|
|
261
261
|
check(checks, "plan_spec_package_authority_admitted", packageAuthorityChunksOk, "package-local authority chunks are admitted through .nimi/spec package authority admissions");
|
|
262
|
+
const delegatedProjectionAdmissions = Array.isArray(plan.delegated_projection_admissions) ? plan.delegated_projection_admissions : [];
|
|
263
|
+
const delegatedAdmissionByRef = new Map(delegatedProjectionAdmissions
|
|
264
|
+
.filter((entry) => isPlainObject(entry) && nonEmptyString(entry.admission_ref))
|
|
265
|
+
.map((entry) => [entry.admission_ref, entry]));
|
|
266
|
+
const delegatedProjectionChunks = plan.chunks.filter((chunk) => chunk.authority_kind === "delegated_projection");
|
|
267
|
+
const delegatedProjectionChunksOk = delegatedProjectionChunks.every((chunk) => {
|
|
268
|
+
const admission = delegatedAdmissionByRef.get(chunk.admission_ref);
|
|
269
|
+
const prefixes = Array.isArray(admission?.delegated_declared_evidence_prefixes)
|
|
270
|
+
? admission.delegated_declared_evidence_prefixes
|
|
271
|
+
: [];
|
|
272
|
+
const delegatedRefs = Array.isArray(chunk.delegated_declared_evidence_refs)
|
|
273
|
+
? chunk.delegated_declared_evidence_refs
|
|
274
|
+
: [];
|
|
275
|
+
const delegatedRefsOk = delegatedRefs.every((ref) => prefixes.some((prefix) => (
|
|
276
|
+
String(ref) === String(prefix).replace(/\/$/u, "")
|
|
277
|
+
|| String(ref).startsWith(`${String(prefix).replace(/\/$/u, "")}/`)
|
|
278
|
+
)));
|
|
279
|
+
return nonEmptyString(chunk.delegated_projection_id)
|
|
280
|
+
&& nonEmptyString(chunk.admission_ref)
|
|
281
|
+
&& nonEmptyString(chunk.authority_root)
|
|
282
|
+
&& nonEmptyString(chunk.source_authority_root)
|
|
283
|
+
&& admission
|
|
284
|
+
&& admission.id === chunk.delegated_projection_id
|
|
285
|
+
&& admission.authority_root === chunk.authority_root
|
|
286
|
+
&& admission.source_authority_root === chunk.source_authority_root
|
|
287
|
+
&& Array.isArray(admission.local_projection_evidence_roots)
|
|
288
|
+
&& sortedArrayEquals(chunk.evidence_roots, admission.local_projection_evidence_roots)
|
|
289
|
+
&& sortedArrayEquals(chunk.admitted_evidence_roots, admission.local_projection_evidence_roots)
|
|
290
|
+
&& sortedArrayEquals(chunk.delegated_evidence_roots, admission.delegated_evidence_roots)
|
|
291
|
+
&& sortedArrayEquals(chunk.delegated_declared_evidence_prefixes, admission.delegated_declared_evidence_prefixes)
|
|
292
|
+
&& delegatedRefsOk
|
|
293
|
+
&& chunk.authority_refs.every((fileRef) => String(fileRef).startsWith(`${chunk.authority_root}/`));
|
|
294
|
+
});
|
|
295
|
+
check(checks, "plan_spec_delegated_projection_authority_admitted", delegatedProjectionChunksOk, "delegated projection chunks are admitted through .nimi/spec delegated projection admissions");
|
|
262
296
|
const evidenceRootAdmissionsOk = plan.chunks.every((chunk) => (
|
|
263
297
|
!Array.isArray(chunk.evidence_root_admission_refs)
|
|
264
298
|
|| chunk.evidence_root_admission_refs.every((ref) => (
|
package/config/bootstrap.yaml
CHANGED
|
@@ -22,11 +22,20 @@ optional_top_level_fields:
|
|
|
22
22
|
- authority_kind
|
|
23
23
|
- app_id
|
|
24
24
|
- package_authority_id
|
|
25
|
+
- delegated_projection_id
|
|
25
26
|
- admission_ref
|
|
26
27
|
- authority_root
|
|
28
|
+
- source_authority_root
|
|
27
29
|
- evidence_root_admission_refs
|
|
28
30
|
- admitted_evidence_roots
|
|
29
31
|
- host_authority_projection_refs
|
|
32
|
+
- delegated_evidence_roots
|
|
33
|
+
- delegated_declared_evidence_prefixes
|
|
34
|
+
- delegated_declared_evidence_refs
|
|
35
|
+
- declared_evidence_delegation
|
|
36
|
+
- required_verification_commands
|
|
37
|
+
- declared_evidence_targets
|
|
38
|
+
- declared_evidence_unresolved
|
|
30
39
|
- evidence_roots
|
|
31
40
|
- evidence_inventory
|
|
32
41
|
- evidence_inventory_status
|
|
@@ -60,6 +69,10 @@ hard_constraints:
|
|
|
60
69
|
- spec_authority_chunks_must_declare_coverage_contract
|
|
61
70
|
- spec_authority_chunk_files_remain_authority_refs_only
|
|
62
71
|
- admitted_package_authority_projection_refs_must_be_explicitly_mapped
|
|
72
|
+
- delegated_projection_authority_must_be_explicitly_admitted
|
|
73
|
+
- delegated_declared_evidence_must_name_source_authority_boundary
|
|
74
|
+
- delegated_projection_is_audit_modeling_only_not_source_repo_execution
|
|
75
|
+
- delegated_projection_must_not_resolve_or_mutate_source_authority_refs
|
|
63
76
|
- spec_authority_ingest_requires_one_authority_outcome_per_authority_ref
|
|
64
77
|
- spec_authority_ingest_requires_evidence_files_exactly_matching_evidence_inventory
|
|
65
78
|
- spec_authority_authority_outcome_evidence_refs_must_belong_to_authority_refs_or_evidence_inventory
|
|
@@ -22,6 +22,8 @@ optional_top_level_fields:
|
|
|
22
22
|
- app_slice_admission_ref
|
|
23
23
|
- app_slice_admissions
|
|
24
24
|
- audit_evidence_root_refs
|
|
25
|
+
- delegated_projection_admission_refs
|
|
26
|
+
- delegated_projection_admissions
|
|
25
27
|
- package_authority_admission_refs
|
|
26
28
|
- package_authority_admissions
|
|
27
29
|
- evidence_inventory_hash
|
|
@@ -52,11 +54,20 @@ chunk_optional_fields:
|
|
|
52
54
|
- authority_kind
|
|
53
55
|
- app_id
|
|
54
56
|
- package_authority_id
|
|
57
|
+
- delegated_projection_id
|
|
55
58
|
- admission_ref
|
|
56
59
|
- authority_root
|
|
60
|
+
- source_authority_root
|
|
57
61
|
- evidence_root_admission_refs
|
|
58
62
|
- admitted_evidence_roots
|
|
59
63
|
- host_authority_projection_refs
|
|
64
|
+
- delegated_evidence_roots
|
|
65
|
+
- delegated_declared_evidence_prefixes
|
|
66
|
+
- delegated_declared_evidence_refs
|
|
67
|
+
- declared_evidence_delegation
|
|
68
|
+
- required_verification_commands
|
|
69
|
+
- declared_evidence_targets
|
|
70
|
+
- declared_evidence_unresolved
|
|
60
71
|
- evidence_roots
|
|
61
72
|
- evidence_inventory
|
|
62
73
|
- coverage_contract
|
|
@@ -112,6 +123,10 @@ hard_constraints:
|
|
|
112
123
|
- risk_budget_pause_must_not_mark_unresolved_findings_as_closed
|
|
113
124
|
- risk_budget_pause_blocks_further_dispatch_until_remediation_bundle_or_manager_action
|
|
114
125
|
- admitted_package_authority_projection_refs_must_be_explicitly_mapped
|
|
126
|
+
- delegated_projection_authority_must_be_explicitly_admitted
|
|
127
|
+
- delegated_declared_evidence_must_name_source_authority_boundary
|
|
128
|
+
- delegated_projection_is_audit_modeling_only_not_source_repo_execution
|
|
129
|
+
- delegated_projection_must_not_resolve_or_mutate_source_authority_refs
|
|
115
130
|
- spec_authority_plans_must_materialize_full_evidence_inventory
|
|
116
131
|
- spec_authority_chunks_must_assign_evidence_inventory_to_every_matching_authority_chunk
|
|
117
132
|
- spec_authority_evidence_inventory_files_may_be_reused_across_chunks_without_creating_parallel_truth
|