@nimiplatform/nimi-coding 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/CHANGELOG.md +8 -92
  2. package/CONTRIBUTING.md +5 -34
  3. package/README.md +47 -350
  4. package/README.zh-CN.md +33 -287
  5. package/cli/commands/start.mjs +48 -688
  6. package/cli/commands/validate-spec-audit.mjs +1 -5
  7. package/cli/constants.mjs +9 -417
  8. package/cli/help.mjs +28 -145
  9. package/cli/index.mjs +5 -33
  10. package/cli/lib/blueprint-audit.mjs +5 -12
  11. package/cli/lib/bootstrap.mjs +0 -9
  12. package/cli/lib/contracts.mjs +0 -163
  13. package/cli/lib/doctor.mjs +155 -10
  14. package/cli/lib/entrypoints.mjs +63 -167
  15. package/cli/lib/internal/contracts-loaders.mjs +0 -85
  16. package/cli/lib/internal/contracts-parse.mjs +84 -520
  17. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +13 -8
  18. package/cli/lib/internal/surface-taxonomy-validators.mjs +316 -66
  19. package/cli/lib/internal/validators-shared.mjs +0 -16
  20. package/cli/lib/internal/validators-spec-helpers.mjs +7 -140
  21. package/cli/lib/internal/validators-spec.mjs +103 -342
  22. package/cli/lib/shared.mjs +1 -42
  23. package/cli/lib/validators.mjs +0 -48
  24. package/cli/seeds/seed-policy.yaml +0 -3
  25. package/config/bootstrap.yaml +4 -4
  26. package/config/spec-generation-inputs.yaml +6 -12
  27. package/contracts/domain-admission.schema.yaml +3 -3
  28. package/contracts/migration-inventory.schema.yaml +12 -75
  29. package/contracts/negative-fixtures.yaml +19 -99
  30. package/contracts/placement-contract.schema.yaml +49 -78
  31. package/contracts/projection-edge.schema.yaml +37 -121
  32. package/contracts/shared-enums.yaml +4 -8
  33. package/contracts/spec-generation-inputs.schema.yaml +15 -112
  34. package/contracts/spec-layout.schema.yaml +32 -0
  35. package/contracts/surface-taxonomy.schema.yaml +43 -94
  36. package/contracts/table-family.schema.yaml +1 -1
  37. package/contracts/tracked-output-admission.schema.yaml +13 -66
  38. package/methodology/core.yaml +33 -25
  39. package/methodology/four-closure-policy.yaml +18 -26
  40. package/methodology/role-separation-policy.yaml +21 -27
  41. package/methodology/spec-reconstruction.yaml +17 -34
  42. package/package.json +2 -4
  43. package/spec/_meta/spec-tree-model.yaml +5 -101
  44. package/spec/product-scope.yaml +25 -57
  45. package/adapters/README.md +0 -25
  46. package/adapters/claude/README.md +0 -89
  47. package/adapters/claude/profile.yaml +0 -70
  48. package/adapters/codex/README.md +0 -53
  49. package/adapters/codex/profile.yaml +0 -78
  50. package/adapters/oh-my-codex/README.md +0 -184
  51. package/adapters/oh-my-codex/profile.yaml +0 -46
  52. package/cli/commands/admit-high-risk-decision.mjs +0 -108
  53. package/cli/commands/audit-sweep.mjs +0 -364
  54. package/cli/commands/closeout.mjs +0 -186
  55. package/cli/commands/decide-high-risk-execution.mjs +0 -124
  56. package/cli/commands/handoff.mjs +0 -123
  57. package/cli/commands/ingest-high-risk-execution.mjs +0 -95
  58. package/cli/commands/review-high-risk-execution.mjs +0 -95
  59. package/cli/commands/sweep-design.mjs +0 -295
  60. package/cli/commands/sweep.mjs +0 -22
  61. package/cli/commands/topic-formatters.mjs +0 -382
  62. package/cli/commands/topic-goal.mjs +0 -33
  63. package/cli/commands/topic-options-shared.mjs +0 -27
  64. package/cli/commands/topic-options-workflow.mjs +0 -767
  65. package/cli/commands/topic-options.mjs +0 -626
  66. package/cli/commands/topic-runner.mjs +0 -169
  67. package/cli/commands/topic.mjs +0 -795
  68. package/cli/commands/validate-acceptance.mjs +0 -5
  69. package/cli/commands/validate-execution-packet.mjs +0 -5
  70. package/cli/commands/validate-orchestration-state.mjs +0 -5
  71. package/cli/commands/validate-prompt.mjs +0 -5
  72. package/cli/commands/validate-worker-output.mjs +0 -5
  73. package/cli/lib/adapter-profiles.mjs +0 -403
  74. package/cli/lib/audit-execution.mjs +0 -52
  75. package/cli/lib/audit-sweep-runtime/admissions.mjs +0 -508
  76. package/cli/lib/audit-sweep-runtime/audit-validity.mjs +0 -356
  77. package/cli/lib/audit-sweep-runtime/chunks.mjs +0 -697
  78. package/cli/lib/audit-sweep-runtime/claude-auditor.mjs +0 -658
  79. package/cli/lib/audit-sweep-runtime/closeout.mjs +0 -144
  80. package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +0 -654
  81. package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +0 -527
  82. package/cli/lib/audit-sweep-runtime/common.mjs +0 -341
  83. package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +0 -172
  84. package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +0 -155
  85. package/cli/lib/audit-sweep-runtime/format.mjs +0 -57
  86. package/cli/lib/audit-sweep-runtime/ingest.mjs +0 -486
  87. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +0 -425
  88. package/cli/lib/audit-sweep-runtime/inventory.mjs +0 -798
  89. package/cli/lib/audit-sweep-runtime/ledger.mjs +0 -315
  90. package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +0 -101
  91. package/cli/lib/audit-sweep-runtime/remediation.mjs +0 -349
  92. package/cli/lib/audit-sweep-runtime/rerun.mjs +0 -129
  93. package/cli/lib/audit-sweep-runtime/risk-budget.mjs +0 -300
  94. package/cli/lib/audit-sweep-runtime/status.mjs +0 -62
  95. package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +0 -215
  96. package/cli/lib/audit-sweep-runtime/validators.mjs +0 -797
  97. package/cli/lib/audit-sweep.mjs +0 -19
  98. package/cli/lib/authority-convergence.mjs +0 -720
  99. package/cli/lib/closeout.mjs +0 -732
  100. package/cli/lib/codex-sdk-runner.mjs +0 -76
  101. package/cli/lib/external-execution.mjs +0 -101
  102. package/cli/lib/handoff.mjs +0 -812
  103. package/cli/lib/high-risk-admission.mjs +0 -512
  104. package/cli/lib/high-risk-decision.mjs +0 -353
  105. package/cli/lib/high-risk-ingest.mjs +0 -321
  106. package/cli/lib/high-risk-review.mjs +0 -267
  107. package/cli/lib/internal/contracts-parse-high-risk.mjs +0 -131
  108. package/cli/lib/internal/contracts-validators.mjs +0 -399
  109. package/cli/lib/internal/doctor-bootstrap-surface.mjs +0 -406
  110. package/cli/lib/internal/doctor-delegated-surface.mjs +0 -256
  111. package/cli/lib/internal/doctor-finalize.mjs +0 -383
  112. package/cli/lib/internal/doctor-format.mjs +0 -286
  113. package/cli/lib/internal/doctor-inspectors.mjs +0 -327
  114. package/cli/lib/internal/doctor-state.mjs +0 -205
  115. package/cli/lib/internal/validators-artifacts.mjs +0 -515
  116. package/cli/lib/sweep-design-runtime/common.mjs +0 -246
  117. package/cli/lib/sweep-design-runtime/engine.mjs +0 -733
  118. package/cli/lib/sweep-design-runtime/fix-topic.mjs +0 -414
  119. package/cli/lib/sweep-design-runtime/lifecycle.mjs +0 -54
  120. package/cli/lib/sweep-design-runtime/results.mjs +0 -324
  121. package/cli/lib/sweep-design.mjs +0 -8
  122. package/cli/lib/topic-artifacts.mjs +0 -186
  123. package/cli/lib/topic-authority-coverage.mjs +0 -73
  124. package/cli/lib/topic-closeout.mjs +0 -560
  125. package/cli/lib/topic-common.mjs +0 -404
  126. package/cli/lib/topic-decisions.mjs +0 -332
  127. package/cli/lib/topic-draft-packets.mjs +0 -167
  128. package/cli/lib/topic-execution.mjs +0 -533
  129. package/cli/lib/topic-goal.mjs +0 -440
  130. package/cli/lib/topic-ledger.mjs +0 -281
  131. package/cli/lib/topic-lifecycle-artifacts.mjs +0 -173
  132. package/cli/lib/topic-root-validation.mjs +0 -288
  133. package/cli/lib/topic-runner-commands.mjs +0 -174
  134. package/cli/lib/topic-runner-deferral.mjs +0 -532
  135. package/cli/lib/topic-runner-stale-gates.mjs +0 -114
  136. package/cli/lib/topic-runner-validation.mjs +0 -138
  137. package/cli/lib/topic-runner.mjs +0 -727
  138. package/cli/lib/topic-scaffold.mjs +0 -252
  139. package/cli/lib/topic-waves.mjs +0 -403
  140. package/cli/lib/topic.mjs +0 -81
  141. package/config/audit-execution-artifacts.yaml +0 -20
  142. package/config/external-execution-artifacts.yaml +0 -16
  143. package/config/host-adapter.yaml +0 -30
  144. package/config/host-profile.yaml +0 -29
  145. package/config/installer-evidence.yaml +0 -31
  146. package/config/skill-installer.yaml +0 -23
  147. package/config/skill-manifest.yaml +0 -48
  148. package/config/skills.yaml +0 -30
  149. package/contracts/acceptance.schema.yaml +0 -16
  150. package/contracts/admission-checklist.schema.yaml +0 -15
  151. package/contracts/audit-chunk.schema.yaml +0 -123
  152. package/contracts/audit-closeout.schema.yaml +0 -51
  153. package/contracts/audit-finding.schema.yaml +0 -61
  154. package/contracts/audit-ledger.schema.yaml +0 -138
  155. package/contracts/audit-plan.schema.yaml +0 -138
  156. package/contracts/audit-remediation-map.schema.yaml +0 -52
  157. package/contracts/audit-rerun.schema.yaml +0 -31
  158. package/contracts/audit-sweep-result.yaml +0 -53
  159. package/contracts/authority-convergence-audit.schema.yaml +0 -19
  160. package/contracts/closeout.schema.yaml +0 -25
  161. package/contracts/decision-review.schema.yaml +0 -16
  162. package/contracts/doc-spec-audit-result.yaml +0 -19
  163. package/contracts/execution-packet.schema.yaml +0 -49
  164. package/contracts/external-host-compatibility.yaml +0 -22
  165. package/contracts/forbidden-shortcuts.catalog.yaml +0 -23
  166. package/contracts/high-risk-admission.schema.yaml +0 -23
  167. package/contracts/high-risk-execution-result.yaml +0 -20
  168. package/contracts/orchestration-state.schema.yaml +0 -41
  169. package/contracts/overflow-continuation.schema.yaml +0 -12
  170. package/contracts/packet.schema.yaml +0 -30
  171. package/contracts/pending-note.schema.yaml +0 -17
  172. package/contracts/prompt.schema.yaml +0 -12
  173. package/contracts/remediation.schema.yaml +0 -16
  174. package/contracts/result.schema.yaml +0 -24
  175. package/contracts/spec-reconstruction-result.yaml +0 -41
  176. package/contracts/sweep-design-result.yaml +0 -349
  177. package/contracts/topic-goal.schema.yaml +0 -87
  178. package/contracts/topic-run-ledger.schema.yaml +0 -72
  179. package/contracts/topic-step-decision.schema.yaml +0 -45
  180. package/contracts/topic.schema.yaml +0 -65
  181. package/contracts/true-close.schema.yaml +0 -15
  182. package/contracts/wave.schema.yaml +0 -29
  183. package/contracts/worker-output.schema.yaml +0 -15
  184. package/contracts/workflow-consumer.schema.yaml +0 -110
  185. package/methodology/audit-sweep-p0p1-recall.yaml +0 -45
  186. package/methodology/authority-convergence-policy.yaml +0 -42
  187. package/methodology/overflow-continuation-policy.yaml +0 -14
  188. package/methodology/skill-exchange-projection.yaml +0 -114
  189. package/methodology/skill-handoff.yaml +0 -34
  190. package/methodology/skill-installer-result.yaml +0 -27
  191. package/methodology/skill-installer-summary-projection.yaml +0 -181
  192. package/methodology/skill-runtime.yaml +0 -23
  193. package/methodology/spec-target-truth-profile.yaml +0 -46
  194. package/methodology/topic-lifecycle-report.yaml +0 -144
  195. package/methodology/topic-lifecycle.yaml +0 -37
  196. package/methodology/topic-naming-ontology.yaml +0 -21
  197. package/methodology/topic-ontology.yaml +0 -38
  198. package/methodology/topic-validation-policy.yaml +0 -9
  199. package/methodology/wave-dag-policy.yaml +0 -14
  200. package/spec/_meta/command-gating-matrix.yaml +0 -143
  201. package/spec/_meta/generate-drift-migration-checklist.yaml +0 -137
  202. package/spec/_meta/governance-routing-cutover-checklist.yaml +0 -35
  203. package/spec/_meta/phase2-impacted-surface-matrix.yaml +0 -44
  204. package/spec/_meta/spec-authority-cutover-readiness.yaml +0 -102
  205. package/spec/bootstrap-state.yaml +0 -99
@@ -28,13 +28,18 @@ const GENERIC_PNPM_COMMANDS = new Set([
28
28
  "run",
29
29
  ]);
30
30
 
31
- function readJson(filePath) {
32
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
33
- }
34
-
35
- function collectKnownPnpmScripts(projectRoot) {
36
- const rootPkg = readJson(path.join(projectRoot, "package.json"));
37
- return new Set(Object.keys(rootPkg.scripts || {}));
31
+ function collectKnownPnpmScripts(projectRoot, failures) {
32
+ const packagePath = path.join(projectRoot, "package.json");
33
+ if (!fs.existsSync(packagePath)) {
34
+ return new Set();
35
+ }
36
+ try {
37
+ const rootPkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
38
+ return new Set(Object.keys(rootPkg?.scripts || {}));
39
+ } catch (error) {
40
+ failures.push(`package.json: invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
41
+ return new Set();
42
+ }
38
43
  }
39
44
 
40
45
  function validatePnpmCommand(command, knownScripts, failures, relPath) {
@@ -91,7 +96,7 @@ export function evaluateAgentsFreshnessCheck(options = {}) {
91
96
  : DEFAULT_STALE_TOKENS;
92
97
 
93
98
  const failures = [];
94
- const knownScripts = collectKnownPnpmScripts(projectRoot);
99
+ const knownScripts = collectKnownPnpmScripts(projectRoot, failures);
95
100
 
96
101
  for (const target of targets) {
97
102
  const abs = path.join(projectRoot, target.rel);
@@ -6,7 +6,7 @@ import { pathExists } from "../fs-helpers.mjs";
6
6
  import { parseYamlText } from "../yaml-helpers.mjs";
7
7
  import { isPlainObject } from "../value-helpers.mjs";
8
8
 
9
- const SURFACE_RESULT_CONTRACT = "nimicoding.surface-validator-result.v1";
9
+ const SURFACE_RESULT_CONTRACT = "nimicoding.surface-validator-result.v2";
10
10
 
11
11
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
12
12
 
@@ -18,12 +18,12 @@ const CONTRACT_REFS = {
18
18
  tableFamily: "contracts/table-family.schema.yaml",
19
19
  domainAdmission: "contracts/domain-admission.schema.yaml",
20
20
  trackedOutputAdmission: "contracts/tracked-output-admission.schema.yaml",
21
- highRiskAdmission: "contracts/high-risk-admission.schema.yaml",
21
+ specLayout: "contracts/spec-layout.schema.yaml",
22
22
  negativeFixtures: "contracts/negative-fixtures.yaml",
23
23
  };
24
24
 
25
25
  const TEXT_RULE_BODY_PATTERN = /\bMUST(?:\s+NOT)?\b|\bmust\s+not\b|必须|不得|fail(?:s|ed)?\s+closed/i;
26
- const PRODUCT_RULE_ID_PATTERN = /\b[A-Z][A-Z0-9]*-[A-Z0-9]+-[A-Z0-9-]+\b/;
26
+ const PRODUCT_RULE_DEFINITION_PATTERN = /^\s*(?:[-*]\s*)?(?:rule_id|rule id)\s*[:|]/im;
27
27
  const GENERATED_REF_PATTERN = /\.nimi\/spec\/[^)\s]+\/kernel\/generated\/|\.nimi\/spec\/generated\/|kernel\/generated\//;
28
28
 
29
29
  function toPosix(value) {
@@ -65,9 +65,227 @@ async function loadSurfaceContracts(projectRoot) {
65
65
  if (hostDomainAdmission) {
66
66
  contracts.domainAdmission = hostDomainAdmission;
67
67
  }
68
+ const hostSpecLayout = await readHostYamlAt(projectRoot, ".nimi/config/spec-layout.yaml");
69
+ contracts.hostSpecLayout = parseHostSpecLayout(
70
+ hostSpecLayout,
71
+ contracts.specLayout,
72
+ contracts.tableFamily,
73
+ );
68
74
  return contracts;
69
75
  }
70
76
 
77
+ function isPortableRef(ref) {
78
+ return typeof ref === "string"
79
+ && ref.length > 0
80
+ && !path.posix.isAbsolute(ref)
81
+ && !ref.startsWith("./")
82
+ && !ref.split("/").includes("..")
83
+ && path.posix.normalize(ref) === ref;
84
+ }
85
+
86
+ function isRefWithin(ref, root) {
87
+ return ref === root || ref.startsWith(`${root}/`);
88
+ }
89
+
90
+ function hasExactFields(value, requiredFields) {
91
+ if (!isPlainObject(value)) {
92
+ return false;
93
+ }
94
+ const actual = Object.keys(value).sort();
95
+ const expected = [...requiredFields].sort();
96
+ return actual.length === expected.length
97
+ && actual.every((field, index) => field === expected[index]);
98
+ }
99
+
100
+ function isLiteralPortableRef(ref) {
101
+ return isPortableRef(ref) && !/[\*{}]/.test(ref);
102
+ }
103
+
104
+ function isGeneratedProjectionRoot(ref, canonicalRoot) {
105
+ return ref === `${canonicalRoot}/generated`
106
+ || ref.startsWith(`${canonicalRoot}/generated/`)
107
+ || /^\.nimi\/spec\/[^/]+\/kernel\/generated(?:\/.*)?$/.test(ref);
108
+ }
109
+
110
+ function parseHostSpecLayout(hostConfig, schemaContract, tableFamilyContract) {
111
+ if (!hostConfig) {
112
+ return {
113
+ ok: true,
114
+ present: false,
115
+ errors: [],
116
+ canonicalRoot: ".nimi/spec",
117
+ hostInstructionPaths: [],
118
+ trackedDerivedProjections: [],
119
+ tableFamilyExtensions: [],
120
+ };
121
+ }
122
+
123
+ const errors = [];
124
+ const parsed = hostConfig.data;
125
+ const layout = parsed?.spec_layout;
126
+ const requiredDocumentFields = Array.isArray(schemaContract.data?.required_top_level_fields)
127
+ ? schemaContract.data.required_top_level_fields.map(String)
128
+ : [];
129
+ const requiredLayoutFields = Array.isArray(schemaContract.data?.layout_required_fields)
130
+ ? schemaContract.data.layout_required_fields.map(String)
131
+ : [];
132
+ const requiredProjectionFields = Array.isArray(schemaContract.data?.tracked_derived_projection_required_fields)
133
+ ? schemaContract.data.tracked_derived_projection_required_fields.map(String)
134
+ : [];
135
+ const requiredFamilyFields = Array.isArray(schemaContract.data?.table_family_extension_required_fields)
136
+ ? schemaContract.data.table_family_extension_required_fields.map(String)
137
+ : [];
138
+ const packageFamilyIds = new Set(
139
+ (tableFamilyContract.data?.table_families ?? []).map((entry) => String(entry.table_family)),
140
+ );
141
+
142
+ if (parsed?.version !== 1 || parsed?.contract_ref !== ".nimi/contracts/spec-layout.schema.yaml" || !isPlainObject(layout)) {
143
+ errors.push("invalid_spec_layout_config: .nimi/config/spec-layout.yaml");
144
+ }
145
+ if (!hasExactFields(parsed, requiredDocumentFields)) {
146
+ errors.push("invalid_spec_layout_document_fields: .nimi/config/spec-layout.yaml");
147
+ }
148
+ if (!hasExactFields(layout, requiredLayoutFields)) {
149
+ errors.push("invalid_spec_layout_fields: .nimi/config/spec-layout.yaml");
150
+ }
151
+ for (const field of requiredLayoutFields) {
152
+ if (!(field in (layout ?? {}))) {
153
+ errors.push(`missing_spec_layout_field: .nimi/config/spec-layout.yaml: ${field}`);
154
+ }
155
+ }
156
+
157
+ const canonicalRoot = typeof layout?.canonical_root === "string" ? layout.canonical_root : ".nimi/spec";
158
+ if (!isPortableRef(canonicalRoot) || canonicalRoot !== ".nimi/spec") {
159
+ errors.push(`invalid_spec_layout_canonical_root: ${canonicalRoot}`);
160
+ }
161
+
162
+ if (!Array.isArray(layout?.host_instruction_paths)) {
163
+ errors.push("invalid_host_instruction_paths: .nimi/config/spec-layout.yaml");
164
+ }
165
+ const hostInstructionPaths = Array.isArray(layout?.host_instruction_paths)
166
+ ? layout.host_instruction_paths.map(String)
167
+ : [];
168
+ const seenInstructionPaths = new Set();
169
+ for (const ref of hostInstructionPaths) {
170
+ if (!isPortableRef(ref)
171
+ || !ref.startsWith(`${canonicalRoot}/`)
172
+ || ref === `${canonicalRoot}/INDEX.md`
173
+ || ref.includes("/kernel/")
174
+ || !ref.endsWith(".md")
175
+ || /[\*{}]/.test(path.posix.basename(ref))) {
176
+ errors.push(`invalid_host_instruction_path: ${ref}`);
177
+ }
178
+ if (seenInstructionPaths.has(ref)) {
179
+ errors.push(`duplicate_host_instruction_path: ${ref}`);
180
+ }
181
+ seenInstructionPaths.add(ref);
182
+ }
183
+
184
+ if (!Array.isArray(layout?.tracked_derived_projections)) {
185
+ errors.push("invalid_tracked_derived_projections: .nimi/config/spec-layout.yaml");
186
+ }
187
+ const trackedDerivedProjections = Array.isArray(layout?.tracked_derived_projections)
188
+ ? layout.tracked_derived_projections
189
+ : [];
190
+ const seenProjectionIds = new Set();
191
+ const seenProjectionRoots = new Set();
192
+ for (const projection of trackedDerivedProjections) {
193
+ if (!isPlainObject(projection)) {
194
+ errors.push("invalid_tracked_derived_projection: non-object entry");
195
+ continue;
196
+ }
197
+ if (!hasExactFields(projection, requiredProjectionFields)) {
198
+ errors.push(`invalid_tracked_derived_projection_fields: ${String(projection.projection_id ?? "unknown")}`);
199
+ }
200
+ for (const field of requiredProjectionFields) {
201
+ if (!(field in projection)) {
202
+ errors.push(`missing_tracked_derived_projection_field: ${String(projection.projection_id ?? "unknown")}: ${field}`);
203
+ }
204
+ }
205
+ const projectionId = String(projection.projection_id ?? "");
206
+ const root = String(projection.root ?? "");
207
+ const sourceRoots = Array.isArray(projection.source_roots) ? projection.source_roots.map(String) : [];
208
+ if (!projectionId || seenProjectionIds.has(projectionId)) {
209
+ errors.push(`invalid_or_duplicate_projection_id: ${projectionId || "empty"}`);
210
+ }
211
+ const overlapsExistingRoot = [...seenProjectionRoots].some((existingRoot) => (
212
+ isRefWithin(root, existingRoot) || isRefWithin(existingRoot, root)
213
+ ));
214
+ if (!isLiteralPortableRef(root)
215
+ || !isGeneratedProjectionRoot(root, canonicalRoot)
216
+ || overlapsExistingRoot) {
217
+ errors.push(`invalid_or_duplicate_projection_root: ${root || "empty"}`);
218
+ }
219
+ if (sourceRoots.length === 0 || sourceRoots.some((ref) => (
220
+ !isLiteralPortableRef(ref)
221
+ || !ref.startsWith(`${canonicalRoot}/`)
222
+ || isRefWithin(ref, root)
223
+ ))) {
224
+ errors.push(`invalid_projection_source_roots: ${projectionId || "unknown"}`);
225
+ }
226
+ for (const field of ["generate_command", "drift_check_command", "required_marker"]) {
227
+ if (typeof projection[field] !== "string" || projection[field].trim().length === 0) {
228
+ errors.push(`invalid_tracked_derived_projection_field: ${projectionId || "unknown"}: ${field}`);
229
+ }
230
+ }
231
+ seenProjectionIds.add(projectionId);
232
+ seenProjectionRoots.add(root);
233
+ }
234
+
235
+ if (!Array.isArray(layout?.table_family_extensions)) {
236
+ errors.push("invalid_table_family_extensions: .nimi/config/spec-layout.yaml");
237
+ }
238
+ const tableFamilyExtensions = Array.isArray(layout?.table_family_extensions)
239
+ ? layout.table_family_extensions
240
+ : [];
241
+ const seenFamilies = new Set();
242
+ for (const family of tableFamilyExtensions) {
243
+ if (!isPlainObject(family)) {
244
+ errors.push("invalid_table_family_extension: non-object entry");
245
+ continue;
246
+ }
247
+ if (!hasExactFields(family, requiredFamilyFields)) {
248
+ errors.push(`invalid_table_family_extension_fields: ${String(family.table_family ?? "unknown")}`);
249
+ }
250
+ for (const field of requiredFamilyFields) {
251
+ if (!(field in family)) {
252
+ errors.push(`missing_table_family_extension_field: ${String(family.table_family ?? "unknown")}: ${field}`);
253
+ }
254
+ }
255
+ const familyId = String(family.table_family ?? "");
256
+ if (!/^[a-z][a-z0-9_]*$/.test(familyId) || seenFamilies.has(familyId)) {
257
+ errors.push(`invalid_or_duplicate_table_family_extension: ${familyId || "empty"}`);
258
+ }
259
+ if (packageFamilyIds.has(familyId)) {
260
+ errors.push(`table_family_extension_shadows_package_family: ${familyId}`);
261
+ }
262
+ if (family.authority_class !== "product_authority_table"
263
+ || !Array.isArray(family.required_fields)
264
+ || family.required_fields.length === 0
265
+ || family.required_fields.some((field) => typeof field !== "string" || field.length === 0)
266
+ || !family.required_fields.includes("table_family")
267
+ || !family.required_fields.includes("owner")
268
+ || new Set(family.required_fields).size !== family.required_fields.length
269
+ || !Array.isArray(family.forbidden_fields)
270
+ || family.forbidden_fields.some((field) => typeof field !== "string" || field.length === 0)
271
+ || new Set(family.forbidden_fields).size !== family.forbidden_fields.length
272
+ || family.required_fields.some((field) => family.forbidden_fields.includes(field))) {
273
+ errors.push(`invalid_table_family_extension_shape: ${familyId || "unknown"}`);
274
+ }
275
+ seenFamilies.add(familyId);
276
+ }
277
+
278
+ return {
279
+ ok: errors.length === 0,
280
+ present: true,
281
+ errors,
282
+ canonicalRoot,
283
+ hostInstructionPaths,
284
+ trackedDerivedProjections,
285
+ tableFamilyExtensions,
286
+ };
287
+ }
288
+
71
289
  async function collectFilesUnder(rootPath) {
72
290
  const exists = await pathExists(rootPath);
73
291
  if (!exists || !exists.isDirectory()) {
@@ -100,7 +318,6 @@ async function collectCandidateFiles(projectRoot, rootRef = ".nimi/spec") {
100
318
  ".nimi/derived",
101
319
  ".nimi/state",
102
320
  ".nimi/audit",
103
- ".nimi/roadmap",
104
321
  ];
105
322
  const uniqueRoots = [...new Set(roots)];
106
323
  const files = [];
@@ -191,7 +408,10 @@ function tableFamilyContractMap(contracts) {
191
408
  const families = Array.isArray(contracts.tableFamily.data?.table_families)
192
409
  ? contracts.tableFamily.data.table_families
193
410
  : [];
194
- return new Map(families.map((entry) => [String(entry.table_family), entry]));
411
+ const extensions = contracts.hostSpecLayout?.ok
412
+ ? contracts.hostSpecLayout.tableFamilyExtensions
413
+ : [];
414
+ return new Map([...families, ...extensions].map((entry) => [String(entry.table_family), entry]));
195
415
  }
196
416
 
197
417
  function domainAdmissionMap(contracts) {
@@ -211,17 +431,34 @@ function trackedAdmissionRoots(contracts) {
211
431
  function isTrackedNonProductRef(ref) {
212
432
  return ref.startsWith(".nimi/derived/")
213
433
  || ref.startsWith(".nimi/state/")
214
- || ref.startsWith(".nimi/audit/")
215
- || ref.startsWith(".nimi/roadmap/");
434
+ || ref.startsWith(".nimi/audit/");
216
435
  }
217
436
 
218
437
  function isAdmittedTrackedOutput(ref, admittedRoots) {
219
438
  return admittedRoots.some((root) => pathMatchesGlob(ref, root));
220
439
  }
221
440
 
222
- function classifyRef(ref, text, parsedYaml) {
441
+ function projectionForRef(ref, contracts) {
442
+ if (!contracts.hostSpecLayout?.ok) {
443
+ return null;
444
+ }
445
+ return contracts.hostSpecLayout.trackedDerivedProjections.find((entry) => isRefWithin(ref, String(entry.root))) ?? null;
446
+ }
447
+
448
+ function isHostInstructionRef(ref, contracts) {
449
+ return contracts.hostSpecLayout?.ok
450
+ && contracts.hostSpecLayout.hostInstructionPaths.some((pattern) => pathMatchesGlob(ref, pattern));
451
+ }
452
+
453
+ function classifyRef(ref, text, parsedYaml, contracts) {
223
454
  if (ref.startsWith(".nimi/spec/future/")) {
224
- return "candidate_roadmap";
455
+ return "unclassified";
456
+ }
457
+ if (isHostInstructionRef(ref, contracts)) {
458
+ return "host_instruction";
459
+ }
460
+ if (projectionForRef(ref, contracts)) {
461
+ return "tracked_derived_projection";
225
462
  }
226
463
  if (ref.startsWith(".nimi/spec/generated/") || ref.includes("/kernel/generated/")) {
227
464
  return "derived_view";
@@ -233,14 +470,8 @@ function classifyRef(ref, text, parsedYaml) {
233
470
  if (ref.endsWith("-admission-anchor.yaml")) {
234
471
  return "host_projection_anchor";
235
472
  }
236
- if (/cutover|checklist|matrix|readiness|migration/i.test(ref)) {
237
- return "lifecycle_progress_state";
238
- }
239
473
  return "methodology_authority";
240
474
  }
241
- if (ref === ".nimi/spec/bootstrap-state.yaml") {
242
- return "lifecycle_progress_state";
243
- }
244
475
  if (ref === ".nimi/spec/product-scope.yaml") {
245
476
  return "methodology_authority";
246
477
  }
@@ -278,9 +509,6 @@ function classifyRef(ref, text, parsedYaml) {
278
509
  if (ref.startsWith(".nimi/local/") || ref.startsWith(".local/")) {
279
510
  return "operational_local_artifact";
280
511
  }
281
- if (ref.startsWith(".nimi/topics/")) {
282
- return "lifecycle_progress_state";
283
- }
284
512
  if (ref.startsWith(".nimi/contracts/") || ref.startsWith(".nimi/methodology/")) {
285
513
  return "nimicoding_managed_projection";
286
514
  }
@@ -294,7 +522,6 @@ function classifyRef(ref, text, parsedYaml) {
294
522
  if (ref.startsWith(".nimi/derived/")) return "derived_view";
295
523
  if (ref.startsWith(".nimi/state/")) return "spec_generation_state";
296
524
  if (ref.startsWith(".nimi/audit/")) return "audit_evidence_state";
297
- return "candidate_roadmap";
298
525
  }
299
526
  return "unclassified";
300
527
  }
@@ -312,7 +539,10 @@ function dispositionFor(ref, surfaceClass, errors) {
312
539
  if (surfaceClass === "derived_view") {
313
540
  return "delete";
314
541
  }
315
- if (["spec_generation_state", "audit_evidence_state", "candidate_roadmap", "lifecycle_progress_state", "operational_local_artifact"].includes(surfaceClass)) {
542
+ if (surfaceClass === "host_instruction" || surfaceClass === "tracked_derived_projection") {
543
+ return errors.length > 0 ? "block" : "keep";
544
+ }
545
+ if (["spec_generation_state", "audit_evidence_state", "operational_local_artifact"].includes(surfaceClass)) {
316
546
  return "move_local";
317
547
  }
318
548
  if (errors.length > 0) {
@@ -331,15 +561,15 @@ function targetRootFor(surfaceClass, ref) {
331
561
  if (surfaceClass === "derived_view") {
332
562
  return "stdout_view";
333
563
  }
334
- if (surfaceClass === "spec_generation_state" || surfaceClass === "lifecycle_progress_state" || surfaceClass === "operational_local_artifact") {
564
+ if (surfaceClass === "host_instruction" || surfaceClass === "tracked_derived_projection") {
565
+ return ".nimi/spec";
566
+ }
567
+ if (surfaceClass === "spec_generation_state" || surfaceClass === "operational_local_artifact") {
335
568
  return ".nimi/local/state";
336
569
  }
337
570
  if (surfaceClass === "audit_evidence_state") {
338
571
  return ".nimi/local/audit";
339
572
  }
340
- if (surfaceClass === "candidate_roadmap") {
341
- return ".nimi/topics";
342
- }
343
573
  if (surfaceClass === "unclassified") {
344
574
  return null;
345
575
  }
@@ -356,17 +586,20 @@ function ownerFor(surfaceClass, ref) {
356
586
  if (surfaceClass === "derived_view") {
357
587
  return "generator";
358
588
  }
589
+ if (surfaceClass === "tracked_derived_projection") {
590
+ return "generator";
591
+ }
592
+ if (surfaceClass === "host_instruction") {
593
+ return "product_host";
594
+ }
359
595
  if (surfaceClass === "spec_generation_state") {
360
596
  return "spec_generator";
361
597
  }
362
598
  if (surfaceClass === "audit_evidence_state") {
363
- return "audit_workflow";
599
+ return "verification_process";
364
600
  }
365
- if (surfaceClass === "candidate_roadmap") {
366
- return "planning_owner";
367
- }
368
- if (surfaceClass === "lifecycle_progress_state") {
369
- return "topic_or_execution_workflow";
601
+ if (surfaceClass === "operational_local_artifact") {
602
+ return "external_tooling";
370
603
  }
371
604
  return domainForSpecRef(ref) ?? "product_domain";
372
605
  }
@@ -404,9 +637,10 @@ function validationCommandsFor(entryClass) {
404
637
  product_authority_table: ["pnpm exec nimicoding validate-table-family --profile nimi --root .nimi/spec"],
405
638
  support_registry: ["pnpm exec nimicoding validate-table-family --profile nimi --root .nimi/spec"],
406
639
  thin_guidance: ["pnpm exec nimicoding validate-guidance-bodies --profile nimi --root .nimi/spec"],
640
+ host_instruction: ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"],
641
+ tracked_derived_projection: ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"],
407
642
  host_projection_anchor: ["pnpm exec nimicoding validate-projection-edges --profile nimi --root .nimi/spec"],
408
643
  nimicoding_managed_projection: ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"],
409
- candidate_roadmap: ["pnpm exec nimicoding validate-domain-admission --profile nimi --root .nimi/spec"],
410
644
  };
411
645
  return commands[entryClass] ?? ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"];
412
646
  }
@@ -424,6 +658,17 @@ function validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts) {
424
658
  if (surfaceClass === "derived_view" && ref.startsWith(".nimi/spec/")) {
425
659
  addError(errors, "derived_view_under_product_authority_root", ref);
426
660
  }
661
+ if (surfaceClass === "tracked_derived_projection") {
662
+ const projection = projectionForRef(ref, contracts);
663
+ if (!projection) {
664
+ addError(errors, "tracked_derived_projection_without_layout_admission", ref);
665
+ } else if (!text.includes(String(projection.required_marker))) {
666
+ addError(errors, "tracked_derived_projection_missing_generated_marker", ref, String(projection.required_marker));
667
+ }
668
+ }
669
+ if (surfaceClass === "host_instruction" && PRODUCT_RULE_DEFINITION_PATTERN.test(text)) {
670
+ addError(errors, "host_instruction_defines_product_rule", ref);
671
+ }
427
672
  if (surfaceClass === "derived_view" && ref.startsWith(".nimi/local/derived/")) {
428
673
  addError(errors, "derived_view_written_to_local_derived", ref);
429
674
  }
@@ -433,15 +678,9 @@ function validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts) {
433
678
  if (surfaceClass === "audit_evidence_state" && ref.startsWith(".nimi/spec/")) {
434
679
  addError(errors, "audit_evidence_state_under_spec", ref);
435
680
  }
436
- if (surfaceClass === "lifecycle_progress_state" && ref.startsWith(".nimi/spec/")) {
437
- addError(errors, "lifecycle_progress_state_under_spec", ref);
438
- }
439
681
  if (surfaceClass === "methodology_authority" && ref.startsWith(".nimi/spec/")) {
440
682
  addError(errors, "package_methodology_under_host_spec", ref);
441
683
  }
442
- if (surfaceClass === "candidate_roadmap" && ref.startsWith(".nimi/spec/")) {
443
- addError(errors, "candidate_roadmap_under_spec", ref);
444
- }
445
684
  if (isTrackedNonProductRef(ref) && !isAdmittedTrackedOutput(ref, trackedAdmissionRoots(contracts))) {
446
685
  addError(errors, "tracked_non_product_without_admission", ref);
447
686
  }
@@ -449,8 +688,8 @@ function validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts) {
449
688
  if (TEXT_RULE_BODY_PATTERN.test(text)) {
450
689
  addError(errors, "guidance_defines_rule_body", ref);
451
690
  }
452
- if (PRODUCT_RULE_ID_PATTERN.test(text) && !ref.endsWith("INDEX.md")) {
453
- addError(errors, "guidance_defines_or_restates_rule_id", ref);
691
+ if (PRODUCT_RULE_DEFINITION_PATTERN.test(text)) {
692
+ addError(errors, "guidance_defines_product_rule", ref);
454
693
  }
455
694
  }
456
695
  if (surfaceClass === "product_authority" && isMarkdownRef(ref) && GENERATED_REF_PATTERN.test(text)) {
@@ -483,6 +722,9 @@ function validateTableRef(ref, parsedYaml, contracts) {
483
722
  addError(errors, "missing_table_family_required_field", ref, String(field));
484
723
  }
485
724
  }
725
+ if (containsAnyKey(parsedYaml, familyContract.forbidden_fields ?? [])) {
726
+ addError(errors, "table_contains_family_forbidden_field", ref, family);
727
+ }
486
728
  }
487
729
 
488
730
  const forbiddenFields = contracts.tableFamily.data?.forbidden_fields_by_authority_class?.product_authority_table ?? [];
@@ -519,18 +761,20 @@ async function buildInventory(projectRoot, options = {}) {
519
761
  const rootRef = options.rootRef ?? ".nimi/spec";
520
762
  const absoluteFiles = await collectCandidateFiles(projectRoot, rootRef);
521
763
  const entries = [];
522
- const errors = [];
764
+ const layoutErrors = [...(contracts.hostSpecLayout?.errors ?? [])];
765
+ const errors = [...layoutErrors];
523
766
 
524
767
  for (const absolutePath of absoluteFiles) {
525
768
  const ref = relativeRef(projectRoot, absolutePath);
526
769
  const text = await readFile(absolutePath, "utf8");
527
770
  const parsedYaml = isYamlRef(ref) ? yamlForText(text) : null;
528
- const surfaceClass = classifyRef(ref, text, parsedYaml);
529
- const entryErrors = [
530
- ...validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts),
531
- ...validateTableRef(ref, parsedYaml, contracts),
532
- ...validateDomainRef(ref, contracts),
533
- ];
771
+ const surfaceClass = classifyRef(ref, text, parsedYaml, contracts);
772
+ const validationErrors = {
773
+ placement: validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts),
774
+ table_family: validateTableRef(ref, parsedYaml, contracts),
775
+ domain_admission: validateDomainRef(ref, contracts),
776
+ };
777
+ const entryErrors = Object.values(validationErrors).flat();
534
778
  errors.push(...entryErrors);
535
779
  const requiredConfirmation = confirmationFor(entryErrors);
536
780
  entries.push({
@@ -544,6 +788,7 @@ async function buildInventory(projectRoot, options = {}) {
544
788
  ambiguity: ambiguityFor(requiredConfirmation, surfaceClass),
545
789
  evidence: entryErrors.length > 0 ? entryErrors : [`classified_as:${surfaceClass}`],
546
790
  validation_commands: validationCommandsFor(surfaceClass),
791
+ validation_errors: validationErrors,
547
792
  errors: entryErrors,
548
793
  });
549
794
  }
@@ -551,6 +796,7 @@ async function buildInventory(projectRoot, options = {}) {
551
796
  return {
552
797
  contracts,
553
798
  entries,
799
+ layoutErrors,
554
800
  errors,
555
801
  };
556
802
  }
@@ -602,39 +848,39 @@ function groupInventoryByDisposition(entries) {
602
848
  return Object.fromEntries(Object.entries(groups).map(([key, value]) => [key, value.sort()]));
603
849
  }
604
850
 
605
- function buildMigrationExecutionPackets(entries) {
606
- const packetKinds = [
851
+ function buildMigrationGroups(entries) {
852
+ const groupKinds = [
607
853
  {
608
- packet_id: "move-package-methodology-copied-under-spec",
854
+ group_id: "move-package-methodology-copied-under-spec",
609
855
  dispositions: ["move_package"],
610
856
  requires_confirmation: false,
611
857
  },
612
858
  {
613
- packet_id: "move-local-derived-audit-state-and-lifecycle",
859
+ group_id: "move-local-derived-and-audit-state",
614
860
  dispositions: ["move_local"],
615
861
  requires_confirmation: false,
616
862
  },
617
863
  {
618
- packet_id: "rewrite-product-authority-tables-and-guidance",
864
+ group_id: "rewrite-product-authority-tables-and-guidance",
619
865
  dispositions: ["rewrite"],
620
866
  requires_confirmation: true,
621
867
  },
622
868
  {
623
- packet_id: "resolve-blocked-semantic-forks",
869
+ group_id: "resolve-blocked-semantic-forks",
624
870
  dispositions: ["block"],
625
871
  requires_confirmation: true,
626
872
  },
627
873
  ];
628
- return packetKinds
629
- .map((packet) => {
630
- const matching = entries.filter((entry) => packet.dispositions.includes(entry.disposition));
874
+ return groupKinds
875
+ .map((group) => {
876
+ const matching = entries.filter((entry) => group.dispositions.includes(entry.disposition));
631
877
  return {
632
- ...packet,
878
+ ...group,
633
879
  entry_count: matching.length,
634
880
  source_paths: matching.map((entry) => entry.source_path).sort(),
635
881
  };
636
882
  })
637
- .filter((packet) => packet.entry_count > 0);
883
+ .filter((group) => group.entry_count > 0);
638
884
  }
639
885
 
640
886
  function enumValidation(entries, inventory) {
@@ -655,7 +901,7 @@ function migrationPlanForInventory(rootRef, inventory) {
655
901
  const enumStatus = enumValidation(entries, inventory);
656
902
  const requiredConfirmationEntries = entries.filter((entry) => entry.required_confirmation !== "none");
657
903
  return {
658
- contract: "nimicoding.spec-migration-plan.v1",
904
+ contract: "nimicoding.spec-migration-plan.v2",
659
905
  ok: enumStatus.unknown_target_classes.length === 0 && enumStatus.unknown_dispositions.length === 0,
660
906
  version: inventory.version,
661
907
  root: rootRef,
@@ -687,7 +933,7 @@ function migrationPlanForInventory(rootRef, inventory) {
687
933
  : "resolve_owner_or_package_boundary_before_migration",
688
934
  })),
689
935
  groups: groupInventoryByDisposition(entries),
690
- execution_packets: buildMigrationExecutionPackets(entries),
936
+ migration_groups: buildMigrationGroups(entries),
691
937
  mutation_policy: {
692
938
  mutates_source_tree: false,
693
939
  allowed_output_roots: [".nimi/local/state/spec-surface"],
@@ -700,13 +946,15 @@ export async function classifySpecSurface(projectRoot, options = {}) {
700
946
  const { entries, errors } = await buildInventory(projectRoot, options);
701
947
  return reportFor("classify-spec-tree", errors.length === 0, errors, [], entries, {
702
948
  inventory: {
703
- version: 1,
949
+ version: 2,
704
950
  inventory: entries,
705
951
  disposition_enum: ["keep", "move_package", "move_local", "split", "rewrite", "delete", "block"],
706
952
  target_class_enum: [
707
953
  "product_authority",
708
954
  "product_authority_table",
709
955
  "thin_guidance",
956
+ "host_instruction",
957
+ "tracked_derived_projection",
710
958
  "derived_view",
711
959
  "spec_generation_state",
712
960
  "audit_evidence_state",
@@ -714,13 +962,11 @@ export async function classifySpecSurface(projectRoot, options = {}) {
714
962
  "methodology_authority",
715
963
  "nimicoding_managed_projection",
716
964
  "host_projection_anchor",
717
- "candidate_roadmap",
718
965
  "support_registry",
719
- "lifecycle_progress_state",
720
966
  ],
721
967
  semantic_constraints: [
722
968
  "inventory_must_not_modify_files",
723
- "future_under_spec_must_not_have_keep_disposition",
969
+ "future_under_spec_must_fail_classification",
724
970
  "nimicoding_managed_projection_must_not_be_promoted_to_product_authority",
725
971
  ],
726
972
  },
@@ -753,7 +999,11 @@ export function isProductAuthoritySurfaceClass(surfaceClass) {
753
999
  }
754
1000
 
755
1001
  export async function validatePlacement(projectRoot, options = {}) {
756
- const { entries, errors } = await buildInventory(projectRoot, options);
1002
+ const { entries, layoutErrors } = await buildInventory(projectRoot, options);
1003
+ const errors = [
1004
+ ...layoutErrors,
1005
+ ...entries.flatMap((entry) => entry.validation_errors?.placement ?? []),
1006
+ ];
757
1007
  return reportFor("validate-placement", errors.length === 0, errors, [], entries);
758
1008
  }
759
1009
 
@@ -804,8 +1054,8 @@ export async function validateGuidanceBodies(projectRoot, options = {}) {
804
1054
  if (TEXT_RULE_BODY_PATTERN.test(text)) {
805
1055
  addError(errors, "guidance_defines_rule_body", entry.source_path);
806
1056
  }
807
- if (PRODUCT_RULE_ID_PATTERN.test(text) && !entry.source_path.endsWith("INDEX.md")) {
808
- addError(errors, "guidance_defines_or_restates_rule_id", entry.source_path);
1057
+ if (PRODUCT_RULE_DEFINITION_PATTERN.test(text)) {
1058
+ addError(errors, "guidance_defines_product_rule", entry.source_path);
809
1059
  }
810
1060
  }
811
1061
  return reportFor("validate-guidance-bodies", errors.length === 0, errors, [], entries);