@nimiplatform/nimi-coding 0.2.5 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/adapters/oh-my-codex/README.md +1 -1
  5. package/cli/commands/admit-high-risk-decision.mjs +4 -4
  6. package/cli/constants.mjs +1 -1
  7. package/cli/help.mjs +1 -1
  8. package/cli/lib/audit-sweep-runtime/admissions.mjs +118 -0
  9. package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +3 -0
  10. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +134 -2
  11. package/cli/lib/audit-sweep-runtime/inventory.mjs +33 -1
  12. package/cli/lib/audit-sweep-runtime/validators.mjs +34 -0
  13. package/cli/lib/closeout.mjs +0 -14
  14. package/cli/lib/entrypoints.mjs +1 -1
  15. package/cli/lib/high-risk-admission.mjs +68 -47
  16. package/cli/lib/internal/contracts-parse-high-risk.mjs +1 -1
  17. package/cli/lib/internal/doctor-finalize.mjs +1 -28
  18. package/cli/lib/internal/doctor-format.mjs +0 -1
  19. package/cli/lib/internal/surface-taxonomy-validators.mjs +0 -34
  20. package/cli/lib/internal/validators-spec-helpers.mjs +0 -1
  21. package/config/bootstrap.yaml +1 -1
  22. package/config/spec-generation-inputs.yaml +1 -1
  23. package/contracts/audit-chunk.schema.yaml +13 -0
  24. package/contracts/audit-plan.schema.yaml +15 -0
  25. package/contracts/high-risk-admission.schema.yaml +4 -4
  26. package/contracts/migration-inventory.schema.yaml +0 -2
  27. package/contracts/placement-contract.schema.yaml +1 -8
  28. package/contracts/shared-enums.yaml +0 -1
  29. package/contracts/spec-generation-inputs.schema.yaml +2 -2
  30. package/contracts/surface-taxonomy.schema.yaml +0 -11
  31. package/contracts/workflow-consumer.schema.yaml +0 -2
  32. package/methodology/spec-target-truth-profile.yaml +0 -7
  33. package/package.json +1 -1
  34. package/spec/_meta/command-gating-matrix.yaml +2 -2
  35. package/spec/product-scope.yaml +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ 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
+
20
+ ## 0.2.6
21
+
22
+ - Hard-cut high-risk admission records out of active `.nimi/spec/**`
23
+ authority. Admission records are now local-only evidence under
24
+ `.nimi/local/high-risk-admissions.yaml`; product authority must live in
25
+ domain spec files.
26
+ - Removed the `product_admission_registry` surface class and changed
27
+ `admit-high-risk-decision` to write local evidence through `--write-local`
28
+ instead of writing canonical spec truth.
29
+
7
30
  ## 0.2.5
8
31
 
9
32
  - Fixed the `cli_version` field in `config/bootstrap.yaml` drifting away from
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 (341 tests at 0.2.5)
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.5 共 341 用例)
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
  ```
@@ -69,7 +69,7 @@ named host-profile recognition, and explicit manager-owned admission, but
69
69
  final high-risk completion should still stay bounded by `nimicoding`
70
70
  validators, explicit local closeout/decision/ingest/review surfaces, and
71
71
  manual manager-side admission rather than automatic semantic promotion. The
72
- canonical admission target is now also shape-validated by a package-owned
72
+ local admission evidence target is now also shape-validated by a package-owned
73
73
  admission schema contract before `nimicoding` accepts it as semantic truth.
74
74
 
75
75
  ## Recommended First-User Flow
@@ -10,7 +10,7 @@ function parseAdmitHighRiskDecisionOptions(args) {
10
10
  fromPath: null,
11
11
  admittedAt: null,
12
12
  json: false,
13
- writeSpec: false,
13
+ writeLocal: false,
14
14
  };
15
15
 
16
16
  for (let index = 0; index < args.length; index += 1) {
@@ -21,8 +21,8 @@ function parseAdmitHighRiskDecisionOptions(args) {
21
21
  continue;
22
22
  }
23
23
 
24
- if (arg === "--write-spec") {
25
- options.writeSpec = true;
24
+ if (arg === "--write-local") {
25
+ options.writeLocal = true;
26
26
  continue;
27
27
  }
28
28
 
@@ -92,7 +92,7 @@ export async function runAdmitHighRiskDecision(args) {
92
92
  return payload.exitCode;
93
93
  }
94
94
 
95
- if (payload.ok && parsed.options.writeSpec) {
95
+ if (payload.ok && parsed.options.writeLocal) {
96
96
  await writeHighRiskAdmission(process.cwd(), payload);
97
97
  }
98
98
 
package/cli/constants.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION = "0.2.5";
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;
package/cli/help.mjs CHANGED
@@ -71,7 +71,7 @@ export function helpText() {
71
71
  ` ${styleCommand("nimicoding sweep design finalize --run-id <id> [--json]")}`,
72
72
  ` ${styleCommand("nimicoding sweep design wave-plan --run-id <id> --topic-id <id> [--json]")}`,
73
73
  ` ${styleCommand("nimicoding sweep design fix-topic --run-id <id> [--slug <slug>] [--title <title>] [--admit-first-wave|--admit-wave-id <id>] [--json]")}`,
74
- ` ${styleCommand("nimicoding admit-high-risk-decision --from <json> --admitted-at <iso8601> [--json] [--write-spec]")}`,
74
+ ` ${styleCommand("nimicoding admit-high-risk-decision --from <json> --admitted-at <iso8601> [--json] [--write-local]")}`,
75
75
  ` ${styleCommand("nimicoding closeout --skill <skill-id> --outcome <completed|blocked|failed> --verified-at <iso8601>")}`,
76
76
  ` ${styleCommand("nimicoding closeout --skill <skill-id> --outcome <completed|blocked|failed> --verified-at <iso8601> --json")}`,
77
77
  ` ${styleCommand("nimicoding closeout --skill <skill-id> --outcome <completed|blocked|failed> --verified-at <iso8601> --write-local")}`,
@@ -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) => (
@@ -44,7 +44,6 @@ function translateCloseoutReason(reason) {
44
44
  ["Completed closeout requires declared canonical tree files to be valid", "完成 closeout 需要声明的 canonical tree 文件有效"],
45
45
  ["Completed closeout requires a valid `.nimi/local/state/spec-generation/spec-generation-audit.yaml` artifact", "完成 closeout 需要一个有效的 `.nimi/local/state/spec-generation/spec-generation-audit.yaml` 产物"],
46
46
  ["Completed doc_spec_audit closeout must compare against `.nimi/spec`", "完成 doc_spec_audit closeout 时必须对 `.nimi/spec` 进行比较"],
47
- ["Completed high_risk_execution closeout requires canonical admissions truth to remain `.nimi/spec/high-risk-admissions.yaml`", "完成 high_risk_execution closeout 需要 canonical admissions truth 继续落在 `.nimi/spec/high-risk-admissions.yaml`"],
48
47
  ["Completed closeout is consistent with the current canonical tree state", "completed closeout 与当前 canonical tree 状态一致"],
49
48
  ["Imported spec_reconstruction summary must match active spec-generation audit coverage", "导入的 spec_reconstruction 摘要必须与当前 spec-generation audit 覆盖情况一致"],
50
49
  ]);
@@ -395,19 +394,6 @@ function evaluateCloseoutReadiness(skillId, outcome, doctorResult, summary) {
395
394
  }
396
395
  }
397
396
 
398
- if (rule.completedRequires.high_risk_admissions_truth_ref) {
399
- const admissionsTruthPath = rule.completedRequires.high_risk_admissions_truth_ref;
400
- const expectedAdmissionsPath = doctorResult.commandGating.entries.find(
401
- (entry) => entry.command === "admit-high-risk-decision" && entry.requires?.canonical_admissions_truth,
402
- )?.requires?.canonical_admissions_truth ?? ".nimi/spec/high-risk-admissions.yaml";
403
- if (admissionsTruthPath !== expectedAdmissionsPath) {
404
- return {
405
- ok: false,
406
- reason: "Completed high_risk_execution closeout requires canonical admissions truth to remain `.nimi/spec/high-risk-admissions.yaml`",
407
- };
408
- }
409
- }
410
-
411
397
  if (!doctorResult.ok || !doctorResult.handoffReadiness.ok) {
412
398
  return {
413
399
  ok: false,
@@ -18,7 +18,7 @@ function managedAgentsBlock() {
18
18
  - Treat \`/.nimi/spec/**\` as the current repo-wide product authority for this project, and use Git history for retired pre-cutover authority evidence.
19
19
  - If .nimi/spec remains bootstrap-only, use .nimi/methodology/spec-reconstruction.yaml and .nimi/config/skills.yaml to drive AI-side truth reconstruction.
20
20
  - Treat .nimi/methodology/spec-target-truth-profile.yaml as repo-local support guidance for future governance slices, not as the canonical reconstruction completion target or a guaranteed fresh-bootstrap seed.
21
- - Treat .nimi/contracts/spec-reconstruction-result.yaml, .nimi/contracts/doc-spec-audit-result.yaml, .nimi/contracts/high-risk-execution-result.yaml, and .nimi/contracts/high-risk-admission.schema.yaml as machine contracts for reconstruction, audit, local-only high-risk closeout summaries, and canonical high-risk admission truth.
21
+ - Treat .nimi/contracts/spec-reconstruction-result.yaml, .nimi/contracts/doc-spec-audit-result.yaml, .nimi/contracts/high-risk-execution-result.yaml, and .nimi/contracts/high-risk-admission.schema.yaml as machine contracts for reconstruction, audit, local-only high-risk closeout summaries, and local-only high-risk admission evidence.
22
22
  - Treat .nimi/config/skill-manifest.yaml, .nimi/config/host-profile.yaml, .nimi/config/host-adapter.yaml, .nimi/config/external-execution-artifacts.yaml, .nimi/config/skill-installer.yaml, .nimi/methodology/skill-runtime.yaml, .nimi/methodology/skill-installer-result.yaml, .nimi/methodology/skill-handoff.yaml, and admitted package-owned adapter profiles under adapters/**/profile.yaml as the canonical bridge to any external AI/skill execution.
23
23
  - Treat standalone nimicoding as boundary-complete for bootstrap, handoff, validation, projection, and explicit admission only; do not assume packaged run-kernel, provider, scheduler, notification, or automation ownership.
24
24
  - Treat .nimi/config/installer-evidence.yaml and .nimi/methodology/skill-installer-summary-projection.yaml as the operational-to-semantic installer projection boundary; do not promote concrete evidence artifacts into semantic truth.