@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 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 (340 tests at 0.2.6)
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 套件(0.2.6 共 340 用例)
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
@@ -1,4 +1,4 @@
1
- export const VERSION = "0.2.6";
1
+ export const VERSION = "0.2.7";
2
2
  export const PACKAGE_NAME = "@nimiplatform/nimi-coding";
3
3
  export const BOOTSTRAP_CONTRACT_ID = "nimicoding.bootstrap";
4
4
  export const BOOTSTRAP_CONTRACT_VERSION = 1;
@@ -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 normalized = String(value ?? "")
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) => (
@@ -1,6 +1,6 @@
1
1
  version: 1
2
2
  initialized_by: "@nimiplatform/nimi-coding"
3
- cli_version: "0.2.6"
3
+ cli_version: "0.2.7"
4
4
  bootstrap_contract: "nimicoding.bootstrap"
5
5
  bootstrap_contract_version: 1
6
6
  profile: default
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimiplatform/nimi-coding",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "private": false,
5
5
  "description": "AI-native coding governance toolkit for bootstrapping .nimi/** into arbitrary projects.",
6
6
  "license": "MIT",