@nimiplatform/nimi-coding 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -20
- package/adapters/oh-my-codex/README.md +8 -9
- package/cli/commands/audit-sweep.mjs +10 -10
- package/cli/commands/classify-spec-tree.mjs +5 -0
- package/cli/commands/closeout.mjs +3 -0
- package/cli/commands/generate-spec-derived-docs.mjs +20 -0
- package/cli/commands/generate-spec-migration-plan.mjs +30 -0
- package/cli/commands/start.mjs +5 -1
- package/cli/commands/surface-validator-command.mjs +49 -0
- package/cli/commands/sweep-design.mjs +295 -0
- package/cli/commands/sweep.mjs +22 -0
- package/cli/commands/sync.mjs +132 -0
- package/cli/commands/topic-formatters.mjs +8 -8
- package/cli/commands/validate-ai-governance.mjs +167 -46
- package/cli/commands/validate-domain-admission.mjs +5 -0
- package/cli/commands/validate-guidance-bodies.mjs +5 -0
- package/cli/commands/validate-placement.mjs +5 -0
- package/cli/commands/validate-projection-edges.mjs +5 -0
- package/cli/commands/validate-spec-audit.mjs +5 -1
- package/cli/commands/validate-table-family.mjs +5 -0
- package/cli/commands/validate-tracked-output-admission.mjs +5 -0
- package/cli/constants.mjs +5 -49
- package/cli/help.mjs +33 -11
- package/cli/index.mjs +20 -2
- package/cli/lib/audit-sweep-runtime/admissions.mjs +38 -29
- package/cli/lib/audit-sweep-runtime/audit-validity.mjs +8 -0
- package/cli/lib/audit-sweep-runtime/chunks.mjs +11 -11
- package/cli/lib/audit-sweep-runtime/closeout.mjs +8 -8
- package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +3 -3
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +10 -10
- package/cli/lib/audit-sweep-runtime/common.mjs +7 -7
- package/cli/lib/audit-sweep-runtime/format.mjs +3 -3
- package/cli/lib/audit-sweep-runtime/ingest.mjs +8 -8
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +24 -27
- package/cli/lib/audit-sweep-runtime/inventory.mjs +58 -18
- package/cli/lib/audit-sweep-runtime/ledger.mjs +1 -1
- package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +2 -2
- package/cli/lib/audit-sweep-runtime/remediation.mjs +6 -6
- package/cli/lib/audit-sweep-runtime/rerun.mjs +6 -6
- package/cli/lib/audit-sweep-runtime/status.mjs +1 -1
- package/cli/lib/audit-sweep-runtime/validators.mjs +2 -2
- package/cli/lib/authority-convergence.mjs +397 -2
- package/cli/lib/blueprint-audit.mjs +5 -5
- package/cli/lib/closeout.mjs +126 -3
- package/cli/lib/contracts.mjs +21 -17
- package/cli/lib/handoff.mjs +29 -11
- package/cli/lib/high-risk-admission.mjs +60 -11
- package/cli/lib/high-risk-decision.mjs +31 -2
- package/cli/lib/high-risk-ingest.mjs +5 -1
- package/cli/lib/high-risk-review.mjs +5 -1
- package/cli/lib/internal/contracts-parse.mjs +195 -24
- package/cli/lib/internal/contracts-validators.mjs +3 -2
- package/cli/lib/internal/doctor-bootstrap-surface.mjs +82 -35
- package/cli/lib/internal/doctor-delegated-surface.mjs +1 -1
- package/cli/lib/internal/doctor-finalize.mjs +12 -8
- package/cli/lib/internal/doctor-inspectors.mjs +34 -1
- package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +74 -12
- package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +24 -6
- package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +18 -23
- package/cli/lib/internal/surface-taxonomy-validators.mjs +931 -0
- package/cli/lib/internal/validators-spec.mjs +229 -20
- package/cli/lib/sweep-design-runtime/common.mjs +246 -0
- package/cli/lib/sweep-design-runtime/engine.mjs +733 -0
- package/cli/lib/sweep-design-runtime/fix-topic.mjs +414 -0
- package/cli/lib/sweep-design-runtime/lifecycle.mjs +54 -0
- package/cli/lib/sweep-design-runtime/results.mjs +324 -0
- package/cli/lib/sweep-design.mjs +8 -0
- package/cli/lib/sync.mjs +143 -0
- package/cli/lib/topic-artifacts.mjs +186 -0
- package/cli/lib/topic-authority-coverage.mjs +73 -0
- package/cli/lib/topic-closeout.mjs +560 -0
- package/cli/lib/topic-common.mjs +404 -0
- package/cli/lib/topic-decisions.mjs +332 -0
- package/cli/lib/topic-draft-packets.mjs +126 -7
- package/cli/lib/topic-execution.mjs +515 -0
- package/cli/lib/topic-goal.mjs +112 -33
- package/cli/lib/topic-ledger.mjs +281 -0
- package/cli/lib/topic-lifecycle-artifacts.mjs +173 -0
- package/cli/lib/topic-root-validation.mjs +288 -0
- package/cli/lib/topic-runner-commands.mjs +174 -0
- package/cli/lib/topic-runner-deferral.mjs +532 -0
- package/cli/lib/topic-runner-stale-gates.mjs +114 -0
- package/cli/lib/topic-runner-validation.mjs +138 -0
- package/cli/lib/topic-runner.mjs +109 -154
- package/cli/lib/topic-scaffold.mjs +252 -0
- package/cli/lib/topic-waves.mjs +403 -0
- package/cli/lib/topic.mjs +81 -93
- package/cli/lib/value-helpers.mjs +6 -1
- package/cli/seeds/bootstrap.mjs +96 -20
- package/cli/seeds/seed-policy.yaml +67 -0
- package/config/bootstrap.yaml +1 -1
- package/config/skill-manifest.yaml +4 -2
- package/config/spec-generation-inputs.yaml +41 -19
- package/contracts/audit-remediation-map.schema.yaml +1 -0
- package/contracts/audit-sweep-result.yaml +4 -0
- package/contracts/domain-admission.schema.yaml +56 -0
- package/contracts/migration-inventory.schema.yaml +80 -0
- package/contracts/negative-fixtures.yaml +91 -0
- package/contracts/placement-contract.schema.yaml +163 -0
- package/contracts/projection-edge.schema.yaml +130 -0
- package/contracts/shared-enums.yaml +68 -0
- package/contracts/spec-generation-audit.schema.yaml +19 -4
- package/contracts/spec-generation-inputs.schema.yaml +130 -29
- package/contracts/spec-reconstruction-result.yaml +9 -5
- package/contracts/surface-taxonomy.schema.yaml +201 -0
- package/contracts/sweep-design-result.yaml +349 -0
- package/contracts/table-family.schema.yaml +114 -0
- package/contracts/topic-goal.schema.yaml +10 -1
- package/contracts/tracked-output-admission.schema.yaml +70 -0
- package/contracts/workflow-consumer.schema.yaml +112 -0
- package/methodology/audit-sweep-p0p1-recall.yaml +1 -1
- package/methodology/spec-reconstruction.yaml +53 -30
- package/package.json +5 -4
- package/spec/_meta/command-gating-matrix.yaml +33 -0
- package/spec/_meta/generate-drift-migration-checklist.yaml +44 -62
- package/spec/_meta/governance-routing-cutover-checklist.yaml +3 -3
- package/spec/_meta/phase2-impacted-surface-matrix.yaml +14 -14
- package/spec/_meta/spec-authority-cutover-readiness.yaml +3 -5
- package/spec/_meta/spec-tree-model.yaml +104 -36
- package/spec/bootstrap-state.yaml +36 -36
- package/spec/product-scope.yaml +13 -10
|
@@ -97,7 +97,7 @@ export function resolveInsideProject(projectRoot, inputPath, label) {
|
|
|
97
97
|
if (!isPathInside(projectRoot, absolutePath)) {
|
|
98
98
|
return {
|
|
99
99
|
ok: false,
|
|
100
|
-
error: `nimicoding
|
|
100
|
+
error: `nimicoding sweep audit refused: ${label} must stay inside the project root.\n`,
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -168,7 +168,7 @@ export async function withAuditSweepMutationLock(projectRoot, sweepId, label, fn
|
|
|
168
168
|
handle = await open(lockPath, "wx");
|
|
169
169
|
} catch (error) {
|
|
170
170
|
if (error?.code === "EEXIST") {
|
|
171
|
-
return inputError(`nimicoding
|
|
171
|
+
return inputError(`nimicoding sweep audit refused: ${label} mutation already in progress for ${sweepId}; retry after the current command finishes.\n`);
|
|
172
172
|
}
|
|
173
173
|
throw error;
|
|
174
174
|
}
|
|
@@ -264,7 +264,7 @@ export async function loadPlan(projectRoot, sweepId) {
|
|
|
264
264
|
const ref = planRef(sweepId);
|
|
265
265
|
const plan = await loadYamlRef(projectRoot, ref);
|
|
266
266
|
if (!isPlainObject(plan) || plan.kind !== "audit-plan" || plan.sweep_id !== sweepId) {
|
|
267
|
-
return { ok: false, error: `nimicoding
|
|
267
|
+
return { ok: false, error: `nimicoding sweep audit refused: plan not found for ${sweepId}.\n` };
|
|
268
268
|
}
|
|
269
269
|
return { ok: true, plan, planRef: ref };
|
|
270
270
|
}
|
|
@@ -273,7 +273,7 @@ export async function loadChunk(projectRoot, sweepId, chunkId) {
|
|
|
273
273
|
const ref = chunkRef(sweepId, chunkId);
|
|
274
274
|
const chunk = await loadYamlRef(projectRoot, ref);
|
|
275
275
|
if (!isPlainObject(chunk) || chunk.kind !== "audit-chunk" || chunk.sweep_id !== sweepId || chunk.chunk_id !== chunkId) {
|
|
276
|
-
return { ok: false, error: `nimicoding
|
|
276
|
+
return { ok: false, error: `nimicoding sweep audit refused: chunk not found for ${sweepId}/${chunkId}.\n` };
|
|
277
277
|
}
|
|
278
278
|
return { ok: true, chunk, chunkRef: ref };
|
|
279
279
|
}
|
|
@@ -306,12 +306,12 @@ export async function loadLatestLedger(projectRoot, sweepId) {
|
|
|
306
306
|
const latestRef = artifactRef("ledger_ref", sweepId, "latest.yaml");
|
|
307
307
|
const pointer = await loadYamlRef(projectRoot, latestRef);
|
|
308
308
|
if (!isPlainObject(pointer) || typeof pointer.ledger_ref !== "string") {
|
|
309
|
-
return { ok: false, error: `nimicoding
|
|
309
|
+
return { ok: false, error: `nimicoding sweep audit refused: latest ledger not found for ${sweepId}.\n` };
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
const ledger = await loadYamlRef(projectRoot, pointer.ledger_ref);
|
|
313
313
|
if (!isPlainObject(ledger) || ledger.kind !== "audit-ledger" || ledger.sweep_id !== sweepId) {
|
|
314
|
-
return { ok: false, error: `nimicoding
|
|
314
|
+
return { ok: false, error: `nimicoding sweep audit refused: latest ledger is malformed for ${sweepId}.\n` };
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
return { ok: true, ledger, ledgerRef: pointer.ledger_ref, pointerRef: latestRef };
|
|
@@ -323,7 +323,7 @@ export function inputError(error) {
|
|
|
323
323
|
|
|
324
324
|
export function ensureIsoTimestamp(value, label = "--verified-at") {
|
|
325
325
|
if (!isIsoUtcTimestamp(value)) {
|
|
326
|
-
return inputError(`nimicoding
|
|
326
|
+
return inputError(`nimicoding sweep audit refused: ${label} must be an ISO-8601 UTC timestamp.\n`);
|
|
327
327
|
}
|
|
328
328
|
return null;
|
|
329
329
|
}
|
|
@@ -5,12 +5,12 @@ export function formatAuditSweepPayload(payload) {
|
|
|
5
5
|
}
|
|
6
6
|
if (payload.checks) {
|
|
7
7
|
const failed = payload.checks.filter((entry) => !entry.ok);
|
|
8
|
-
return `
|
|
8
|
+
return `sweep audit ${payload.sweepId ?? "result"} validation failed\nchecks: ${payload.checks.length - failed.length}/${payload.checks.length}\nfailed: ${failed.map((entry) => entry.reason).join("; ")}\n`;
|
|
9
9
|
}
|
|
10
|
-
return "
|
|
10
|
+
return "sweep audit failed\n";
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const lines = [`
|
|
13
|
+
const lines = [`sweep audit ${payload.sweepId ?? payload.auditCloseout?.sweep_id ?? "result"}`];
|
|
14
14
|
for (const [label, value] of [
|
|
15
15
|
["plan", payload.planRef],
|
|
16
16
|
["chunk", payload.chunkRef],
|
|
@@ -297,7 +297,7 @@ function recordClusteredSymptom(store, cluster, finding, fingerprint, classifica
|
|
|
297
297
|
export async function ingestAuditSweepChunk(projectRoot, options) {
|
|
298
298
|
const sweepId = safeSweepId(options.sweepId);
|
|
299
299
|
if (!sweepId || typeof options.chunkId !== "string") {
|
|
300
|
-
return inputError("nimicoding
|
|
300
|
+
return inputError("nimicoding sweep audit refused: --sweep-id and --chunk-id are required.\n");
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
const timestampError = ensureIsoTimestamp(options.verifiedAt);
|
|
@@ -311,7 +311,7 @@ export async function ingestAuditSweepChunk(projectRoot, options) {
|
|
|
311
311
|
}
|
|
312
312
|
const sourceInfo = await pathExists(source.absolutePath);
|
|
313
313
|
if (!sourceInfo || !sourceInfo.isFile()) {
|
|
314
|
-
return inputError("nimicoding
|
|
314
|
+
return inputError("nimicoding sweep audit refused: --from must point to an existing JSON file.\n");
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
return withAuditSweepMutationLock(projectRoot, sweepId, "chunk ingest", async () => {
|
|
@@ -324,20 +324,20 @@ export async function ingestAuditSweepChunk(projectRoot, options) {
|
|
|
324
324
|
return inputError(chunkResult.error);
|
|
325
325
|
}
|
|
326
326
|
if (chunkResult.chunk.state !== "dispatched") {
|
|
327
|
-
return inputError("nimicoding
|
|
327
|
+
return inputError("nimicoding sweep audit refused: chunk ingest requires dispatched state.\n");
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
const evidenceJson = await loadJsonFile(source.absolutePath);
|
|
331
331
|
if (!evidenceJson.ok) {
|
|
332
|
-
return inputError("nimicoding
|
|
332
|
+
return inputError("nimicoding sweep audit refused: --from must contain valid JSON.\n");
|
|
333
333
|
}
|
|
334
334
|
const envelope = validateEvidenceEnvelope(evidenceJson.value, chunkResult.chunk);
|
|
335
335
|
if (!envelope.ok) {
|
|
336
|
-
return inputError(`nimicoding
|
|
336
|
+
return inputError(`nimicoding sweep audit refused: ${envelope.error}.\n`);
|
|
337
337
|
}
|
|
338
338
|
const auditValidity = buildAuditValidityForEvidence(chunkResult.chunk, evidenceJson.value);
|
|
339
339
|
if (auditValidity.posture === "invalid") {
|
|
340
|
-
return inputError(`nimicoding
|
|
340
|
+
return inputError(`nimicoding sweep audit refused: audit evidence is invalid no-finding evidence (${auditValidity.blockers.map((blocker) => blocker.id).join(", ")}).\n`);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
const evidenceRef = artifactRef("evidence_refs", sweepId, `${options.chunkId}.audit-evidence.json`);
|
|
@@ -355,11 +355,11 @@ export async function ingestAuditSweepChunk(projectRoot, options) {
|
|
|
355
355
|
for (const [index, rawFinding] of evidenceJson.value.findings.entries()) {
|
|
356
356
|
const normalized = normalizeFinding(rawFinding, index, chunkResult.chunk, sweepId, evidenceRef, options.verifiedAt);
|
|
357
357
|
if (!normalized.ok) {
|
|
358
|
-
return inputError(`nimicoding
|
|
358
|
+
return inputError(`nimicoding sweep audit refused: ${normalized.error}.\n`);
|
|
359
359
|
}
|
|
360
360
|
const clusterResult = deriveFindingCluster(rawFinding, normalized.finding, chunkResult.chunk, planResult.plan);
|
|
361
361
|
if (!clusterResult.ok) {
|
|
362
|
-
return inputError(`nimicoding
|
|
362
|
+
return inputError(`nimicoding sweep audit refused: finding ${index + 1} ${clusterResult.error}.\n`);
|
|
363
363
|
}
|
|
364
364
|
if (seen.has(normalized.fingerprint)) {
|
|
365
365
|
duplicateCount += 1;
|
|
@@ -6,33 +6,25 @@ function evidenceRootsForSpecOwner(ownerDomain, targetRootRef) {
|
|
|
6
6
|
if (targetRootRef !== ".") {
|
|
7
7
|
return [targetRootRef];
|
|
8
8
|
}
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
const owner = String(ownerDomain ?? "").trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
10
|
+
const repoWideEvidenceRoots = [".", ".github", "config", "scripts", "src", "lib", "packages", "apps", "tools", "services"];
|
|
11
|
+
if (!owner || owner === "spec-meta" || owner === "spec-root") {
|
|
12
|
+
return repoWideEvidenceRoots;
|
|
13
|
+
}
|
|
14
|
+
if (owner === "project") {
|
|
15
|
+
return ["src", "lib", "packages", "apps", "tools", "services", "scripts", "config"];
|
|
16
|
+
}
|
|
17
|
+
return [
|
|
18
|
+
owner,
|
|
19
|
+
`src/${owner}`,
|
|
20
|
+
`lib/${owner}`,
|
|
21
|
+
`packages/${owner}`,
|
|
22
|
+
`apps/${owner}`,
|
|
23
|
+
`tools/${owner}`,
|
|
24
|
+
`services/${owner}`,
|
|
18
25
|
"scripts",
|
|
19
|
-
"
|
|
20
|
-
".nimi/spec",
|
|
21
|
-
".nimi/contracts",
|
|
22
|
-
".nimi/methodology",
|
|
26
|
+
"config",
|
|
23
27
|
];
|
|
24
|
-
const roots = {
|
|
25
|
-
"spec-meta": repoWideEvidenceRoots,
|
|
26
|
-
"spec-root": repoWideEvidenceRoots,
|
|
27
|
-
cognition: ["nimi-cognition", ".nimi/spec/cognition"],
|
|
28
|
-
desktop: ["apps/desktop", "kit", ".nimi/spec/desktop"],
|
|
29
|
-
future: [".nimi/spec/future", ".nimi/topics"],
|
|
30
|
-
platform: ["kit", "scripts", ".nimi/spec/platform"],
|
|
31
|
-
realm: ["sdk/src/realm", "runtime/internal/protocol", ".nimi/spec/realm"],
|
|
32
|
-
runtime: ["runtime", "proto/runtime/v1", "scripts", "config", ".nimi/spec/runtime"],
|
|
33
|
-
sdk: ["sdk/src", "sdk/test", "scripts", ".nimi/spec/sdk"],
|
|
34
|
-
};
|
|
35
|
-
return roots[ownerDomain] ?? [ownerDomain, `.nimi/spec/${ownerDomain}`];
|
|
36
28
|
}
|
|
37
29
|
|
|
38
30
|
function slugPart(value) {
|
|
@@ -74,7 +66,13 @@ function candidateEvidenceRefsForModuleMapPath(modulePath, evidenceRoots) {
|
|
|
74
66
|
if (!normalized || normalized.startsWith("http:") || normalized.startsWith("https:")) {
|
|
75
67
|
return [];
|
|
76
68
|
}
|
|
77
|
-
const
|
|
69
|
+
const firstSegment = normalized.split("/")[0];
|
|
70
|
+
const genericDirectRoots = new Set([".github", ".nimi", "apps", "config", "lib", "packages", "scripts", "services", "src", "tools"]);
|
|
71
|
+
const directRoot = genericDirectRoots.has(firstSegment)
|
|
72
|
+
|| (evidenceRoots ?? []).some((rootRef) => {
|
|
73
|
+
const root = String(rootRef ?? "").replace(/\\/g, "/").replace(/\/$/, "");
|
|
74
|
+
return root !== "." && (normalized === root || normalized.startsWith(`${root}/`));
|
|
75
|
+
});
|
|
78
76
|
const candidates = [];
|
|
79
77
|
if (directRoot) {
|
|
80
78
|
candidates.push(normalized);
|
|
@@ -195,4 +193,3 @@ export function buildSpecChunks(includedInventory, options) {
|
|
|
195
193
|
}
|
|
196
194
|
return chunks;
|
|
197
195
|
}
|
|
198
|
-
|
|
@@ -38,6 +38,10 @@ import { assignEvidenceInventory } from "./evidence-assignment.mjs";
|
|
|
38
38
|
import { buildSpecChunks } from "./inventory-spec-chunks.mjs";
|
|
39
39
|
import { buildRiskBudgetPolicy } from "./risk-budget.mjs";
|
|
40
40
|
import { pathExists } from "../fs-helpers.mjs";
|
|
41
|
+
import {
|
|
42
|
+
buildSpecSurfaceInventory,
|
|
43
|
+
isProductAuthoritySurfaceClass,
|
|
44
|
+
} from "../internal/surface-taxonomy-validators.mjs";
|
|
41
45
|
|
|
42
46
|
const execFile = promisify(execFileCallback);
|
|
43
47
|
async function listGitFiles(projectRoot, targetRootRef) {
|
|
@@ -140,7 +144,7 @@ async function hasSpecAuthorityRoot(projectRoot) {
|
|
|
140
144
|
function resolveChunkBasis(targetRootRef, requested, specRootPresent) {
|
|
141
145
|
const normalized = requested ? String(requested).trim() : "auto";
|
|
142
146
|
if (!["auto", "files", "spec"].includes(normalized)) {
|
|
143
|
-
return { ok: false, error: "nimicoding
|
|
147
|
+
return { ok: false, error: "nimicoding sweep audit refused: --chunk-basis must be auto, files, or spec.\n" };
|
|
144
148
|
}
|
|
145
149
|
if (normalized === "files") {
|
|
146
150
|
return { ok: true, basis: "files" };
|
|
@@ -148,7 +152,7 @@ function resolveChunkBasis(targetRootRef, requested, specRootPresent) {
|
|
|
148
152
|
if (normalized === "spec") {
|
|
149
153
|
return specRootPresent
|
|
150
154
|
? { ok: true, basis: "spec" }
|
|
151
|
-
: { ok: false, error: "nimicoding
|
|
155
|
+
: { ok: false, error: "nimicoding sweep audit refused: --chunk-basis spec requires .nimi/spec.\n" };
|
|
152
156
|
}
|
|
153
157
|
return { ok: true, basis: (targetRootRef === "." || isSpecAuthorityRoot(targetRootRef)) && specRootPresent ? "spec" : "files" };
|
|
154
158
|
}
|
|
@@ -169,6 +173,7 @@ async function buildInventoryEntry(projectRoot, fileRef, targetRootRef, excludeP
|
|
|
169
173
|
extension: extension || "none",
|
|
170
174
|
owner_domain: options.ownerDomain ?? ownerDomainForFile(fileRef, targetRootRef),
|
|
171
175
|
classification: classifyFile(fileRef),
|
|
176
|
+
surface_class: options.surfaceClass ?? null,
|
|
172
177
|
included,
|
|
173
178
|
exclusion_reason: included
|
|
174
179
|
? null
|
|
@@ -176,6 +181,25 @@ async function buildInventoryEntry(projectRoot, fileRef, targetRootRef, excludeP
|
|
|
176
181
|
};
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
function applySpecSurfaceAuthorityFilter(inventory, surfaceEntriesByRef) {
|
|
185
|
+
return inventory.map((entry) => {
|
|
186
|
+
const surfaceEntry = surfaceEntriesByRef.get(entry.file_ref);
|
|
187
|
+
if (!surfaceEntry) {
|
|
188
|
+
return entry;
|
|
189
|
+
}
|
|
190
|
+
const surfaceClass = surfaceEntry.current_inferred_class;
|
|
191
|
+
if (isProductAuthoritySurfaceClass(surfaceClass)) {
|
|
192
|
+
return { ...entry, surface_class: surfaceClass };
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
...entry,
|
|
196
|
+
surface_class: surfaceClass,
|
|
197
|
+
included: false,
|
|
198
|
+
exclusion_reason: `non_product_surface:${surfaceClass}`,
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
179
203
|
function buildFileChunks(includedInventory, options) {
|
|
180
204
|
const byOwner = new Map();
|
|
181
205
|
for (const entry of includedInventory) {
|
|
@@ -222,7 +246,7 @@ function buildAuditIgnorePolicy(projectConfig, options) {
|
|
|
222
246
|
if (!reason) {
|
|
223
247
|
return {
|
|
224
248
|
ok: false,
|
|
225
|
-
error: "nimicoding
|
|
249
|
+
error: "nimicoding sweep audit refused: --ignore or --ignore-owner requires --ignore-reason, or .nimi/config/audit-sweep.yaml audit_sweep.ignore_reason.\n",
|
|
226
250
|
};
|
|
227
251
|
}
|
|
228
252
|
return {
|
|
@@ -297,7 +321,7 @@ async function listAdmittedPackageAuthorityEntries(projectRoot, packageAuthority
|
|
|
297
321
|
if (!rootInfo?.isDirectory()) {
|
|
298
322
|
return {
|
|
299
323
|
ok: false,
|
|
300
|
-
error: `nimicoding
|
|
324
|
+
error: `nimicoding sweep audit refused: package authority admission ${admission.id} authority_root is missing: ${admission.authority_root}.\n`,
|
|
301
325
|
};
|
|
302
326
|
}
|
|
303
327
|
const gitFiles = await listGitFiles(projectRoot, admission.authority_root);
|
|
@@ -326,7 +350,7 @@ async function listAdmittedAppAuthorityEntries(projectRoot, appSliceAdmissions,
|
|
|
326
350
|
if (!rootInfo?.isDirectory()) {
|
|
327
351
|
return {
|
|
328
352
|
ok: false,
|
|
329
|
-
error: `nimicoding
|
|
353
|
+
error: `nimicoding sweep audit refused: app-slice admission ${admission.app_id} authority_root is missing: ${admission.authority_root}.\n`,
|
|
330
354
|
};
|
|
331
355
|
}
|
|
332
356
|
const gitFiles = await listGitFiles(projectRoot, admission.authority_root);
|
|
@@ -381,12 +405,12 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
381
405
|
|
|
382
406
|
const targetInfo = await pathExists(targetRoot.absolutePath);
|
|
383
407
|
if (!targetInfo || !targetInfo.isDirectory()) {
|
|
384
|
-
return inputError("nimicoding
|
|
408
|
+
return inputError("nimicoding sweep audit refused: --root must point to an existing directory.\n");
|
|
385
409
|
}
|
|
386
410
|
|
|
387
411
|
const sweepId = options.sweepId ? safeSweepId(options.sweepId) : deriveSweepId(targetRootRef);
|
|
388
412
|
if (!sweepId) {
|
|
389
|
-
return inputError("nimicoding
|
|
413
|
+
return inputError("nimicoding sweep audit refused: --sweep-id must be a safe id.\n");
|
|
390
414
|
}
|
|
391
415
|
|
|
392
416
|
const specRootPresent = await hasSpecAuthorityRoot(projectRoot);
|
|
@@ -446,15 +470,22 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
446
470
|
for (const fileRef of allFileRefs) {
|
|
447
471
|
inventory.push(await buildInventoryEntry(projectRoot, fileRef, inventoryRootRef, excludePatterns, { forceAuthority: true }));
|
|
448
472
|
}
|
|
473
|
+
let specSurfaceReport = null;
|
|
474
|
+
let authorityInventory = inventory;
|
|
475
|
+
if (chunkBasis.basis === "spec") {
|
|
476
|
+
specSurfaceReport = await buildSpecSurfaceInventory(projectRoot, { rootRef: inventoryRootRef });
|
|
477
|
+
const surfaceEntriesByRef = new Map(specSurfaceReport.entries.map((entry) => [entry.source_path, entry]));
|
|
478
|
+
authorityInventory = applySpecSurfaceAuthorityFilter(inventory, surfaceEntriesByRef);
|
|
479
|
+
}
|
|
449
480
|
if (chunkBasis.basis === "spec" && appSliceAdmissions.length > 0) {
|
|
450
481
|
const appAuthorityEntries = await listAdmittedAppAuthorityEntries(projectRoot, appSliceAdmissions, excludePatterns);
|
|
451
482
|
if (!appAuthorityEntries.ok) {
|
|
452
483
|
return inputError(appAuthorityEntries.error);
|
|
453
484
|
}
|
|
454
|
-
const seenAuthorityRefs = new Set(
|
|
485
|
+
const seenAuthorityRefs = new Set(authorityInventory.map((entry) => entry.file_ref));
|
|
455
486
|
for (const entry of appAuthorityEntries.entries) {
|
|
456
487
|
if (!seenAuthorityRefs.has(entry.file_ref)) {
|
|
457
|
-
|
|
488
|
+
authorityInventory.push(entry);
|
|
458
489
|
seenAuthorityRefs.add(entry.file_ref);
|
|
459
490
|
}
|
|
460
491
|
}
|
|
@@ -464,16 +495,16 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
464
495
|
if (!packageAuthorityEntries.ok) {
|
|
465
496
|
return inputError(packageAuthorityEntries.error);
|
|
466
497
|
}
|
|
467
|
-
const seenAuthorityRefs = new Set(
|
|
498
|
+
const seenAuthorityRefs = new Set(authorityInventory.map((entry) => entry.file_ref));
|
|
468
499
|
for (const entry of packageAuthorityEntries.entries) {
|
|
469
500
|
if (!seenAuthorityRefs.has(entry.file_ref)) {
|
|
470
|
-
|
|
501
|
+
authorityInventory.push(entry);
|
|
471
502
|
seenAuthorityRefs.add(entry.file_ref);
|
|
472
503
|
}
|
|
473
504
|
}
|
|
474
505
|
}
|
|
475
506
|
|
|
476
|
-
const includedInventory =
|
|
507
|
+
const includedInventory = authorityInventory.filter((entry) => entry.included);
|
|
477
508
|
const authorityFileRefs = new Set(includedInventory.map((entry) => entry.file_ref));
|
|
478
509
|
const authorityTextByRef = new Map();
|
|
479
510
|
if (chunkBasis.basis === "spec") {
|
|
@@ -516,7 +547,7 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
516
547
|
const createdAt = options.createdAt ?? new Date().toISOString();
|
|
517
548
|
const ignoreResult = applyAuditIgnorePolicy(chunks, auditIgnorePolicy, createdAt);
|
|
518
549
|
chunks = ignoreResult.chunks;
|
|
519
|
-
const inventoryHash = sha256Object(
|
|
550
|
+
const inventoryHash = sha256Object(authorityInventory.map((entry) => ({
|
|
520
551
|
file_ref: entry.file_ref,
|
|
521
552
|
sha256: entry.sha256,
|
|
522
553
|
included: entry.included,
|
|
@@ -574,8 +605,15 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
574
605
|
} : {}),
|
|
575
606
|
exclude_patterns: excludePatterns,
|
|
576
607
|
inventory_hash: inventoryHash,
|
|
608
|
+
...(specSurfaceReport ? {
|
|
609
|
+
surface_classification: {
|
|
610
|
+
contract: specSurfaceReport.contract,
|
|
611
|
+
summary: specSurfaceReport.summary,
|
|
612
|
+
errors: specSurfaceReport.errors,
|
|
613
|
+
},
|
|
614
|
+
} : {}),
|
|
577
615
|
...(evidenceInventoryHash ? { evidence_inventory_hash: evidenceInventoryHash } : {}),
|
|
578
|
-
inventory,
|
|
616
|
+
inventory: authorityInventory,
|
|
579
617
|
...(chunkBasis.basis === "spec" ? {
|
|
580
618
|
evidence_inventory: evidenceInventory.map((entry) => ({
|
|
581
619
|
file_ref: entry.file_ref,
|
|
@@ -591,9 +629,9 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
591
629
|
} : {}),
|
|
592
630
|
chunks,
|
|
593
631
|
coverage: {
|
|
594
|
-
total_files:
|
|
632
|
+
total_files: authorityInventory.length,
|
|
595
633
|
included_files: includedInventory.length,
|
|
596
|
-
excluded_files:
|
|
634
|
+
excluded_files: authorityInventory.length - includedInventory.length,
|
|
597
635
|
...(chunkBasis.basis === "spec" ? {
|
|
598
636
|
authority_files: includedInventory.length,
|
|
599
637
|
evidence_files: evidenceInventory.length,
|
|
@@ -694,11 +732,12 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
694
732
|
sweepId,
|
|
695
733
|
planRef: planRef(sweepId),
|
|
696
734
|
chunkRefs: chunks.map((chunk) => chunkRef(sweepId, chunk.chunk_id)),
|
|
735
|
+
chunkIds: chunks.map((chunk) => chunk.chunk_id),
|
|
697
736
|
runLedgerRef: runRef,
|
|
698
737
|
chunkCount: chunks.length,
|
|
699
|
-
totalFiles:
|
|
738
|
+
totalFiles: authorityInventory.length,
|
|
700
739
|
includedFiles: includedInventory.length,
|
|
701
|
-
excludedFiles:
|
|
740
|
+
excludedFiles: authorityInventory.length - includedInventory.length,
|
|
702
741
|
...(chunkBasis.basis === "spec" ? {
|
|
703
742
|
evidenceFiles: evidenceInventory.length,
|
|
704
743
|
unmappedEvidenceFiles: unmappedEvidenceFiles.length,
|
|
@@ -724,5 +763,6 @@ export async function getPlannedChunkRefs(projectRoot, sweepId) {
|
|
|
724
763
|
return {
|
|
725
764
|
ok: true,
|
|
726
765
|
chunkRefs: loaded.plan.chunks.map((chunk) => chunkRef(sweepId, chunk.chunk_id)),
|
|
766
|
+
chunkIds: loaded.plan.chunks.map((chunk) => chunk.chunk_id),
|
|
727
767
|
};
|
|
728
768
|
}
|
|
@@ -206,7 +206,7 @@ function formatReport({ sweepId, ledger, findings }) {
|
|
|
206
206
|
export async function buildAuditSweepLedger(projectRoot, options) {
|
|
207
207
|
const sweepId = safeSweepId(options.sweepId);
|
|
208
208
|
if (!sweepId) {
|
|
209
|
-
return inputError("nimicoding
|
|
209
|
+
return inputError("nimicoding sweep audit refused: --sweep-id is required.\n");
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
const timestampError = options.verifiedAt ? ensureIsoTimestamp(options.verifiedAt) : null;
|
|
@@ -135,7 +135,7 @@ function groupOpenFindings(findings, clusters, maxFindingsPerWave) {
|
|
|
135
135
|
export async function buildAuditSweepRemediationMap(projectRoot, options) {
|
|
136
136
|
const sweepId = safeSweepId(options.sweepId);
|
|
137
137
|
if (!sweepId) {
|
|
138
|
-
return inputError("nimicoding
|
|
138
|
+
return inputError("nimicoding sweep audit refused: --sweep-id is required.\n");
|
|
139
139
|
}
|
|
140
140
|
const timestampError = options.verifiedAt ? ensureIsoTimestamp(options.verifiedAt) : null;
|
|
141
141
|
if (timestampError) {
|
|
@@ -243,7 +243,7 @@ function topicWaveFromRemediationWave(wave, ledgerRef, remediationMapRefValue) {
|
|
|
243
243
|
export async function admitAuditSweepRemediationMap(projectRoot, options) {
|
|
244
244
|
const sweepId = safeSweepId(options.sweepId);
|
|
245
245
|
if (!sweepId || typeof options.topicId !== "string" || !options.topicId.trim()) {
|
|
246
|
-
return inputError("nimicoding
|
|
246
|
+
return inputError("nimicoding sweep audit refused: --sweep-id and --topic-id are required.\n");
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
const ledgerResult = await loadLatestLedger(projectRoot, sweepId);
|
|
@@ -253,7 +253,7 @@ export async function admitAuditSweepRemediationMap(projectRoot, options) {
|
|
|
253
253
|
const mapRef = remediationMapRef(sweepId, ledgerResult.ledger.snapshot_id);
|
|
254
254
|
const remediationMap = await loadYamlRef(projectRoot, mapRef);
|
|
255
255
|
if (!remediationMap || remediationMap.kind !== "audit-remediation-map" || remediationMap.source_ledger_ref !== ledgerResult.ledgerRef || !Array.isArray(remediationMap.waves)) {
|
|
256
|
-
return inputError("nimicoding
|
|
256
|
+
return inputError("nimicoding sweep audit refused: latest remediation map is missing or malformed.\n");
|
|
257
257
|
}
|
|
258
258
|
const planResult = await loadPlan(projectRoot, sweepId);
|
|
259
259
|
if (!planResult.ok) {
|
|
@@ -273,7 +273,7 @@ export async function admitAuditSweepRemediationMap(projectRoot, options) {
|
|
|
273
273
|
ok: false,
|
|
274
274
|
inputError: true,
|
|
275
275
|
exitCode: 1,
|
|
276
|
-
error: `nimicoding
|
|
276
|
+
error: `nimicoding sweep audit refused: remediation wave admission failed: ${addResult.error}\n`,
|
|
277
277
|
};
|
|
278
278
|
}
|
|
279
279
|
materialized.push(topicWave.wave_id);
|
|
@@ -289,7 +289,7 @@ export async function admitAuditSweepRemediationMap(projectRoot, options) {
|
|
|
289
289
|
ok: false,
|
|
290
290
|
inputError: true,
|
|
291
291
|
exitCode: 1,
|
|
292
|
-
error: `nimicoding
|
|
292
|
+
error: `nimicoding sweep audit refused: remediation wave selection failed: ${selectResult.error}\n`,
|
|
293
293
|
};
|
|
294
294
|
}
|
|
295
295
|
const admitResult = await admitWaveInTopic(projectRoot, options.topicId, topicWave.wave_id);
|
|
@@ -298,7 +298,7 @@ export async function admitAuditSweepRemediationMap(projectRoot, options) {
|
|
|
298
298
|
ok: false,
|
|
299
299
|
inputError: true,
|
|
300
300
|
exitCode: 1,
|
|
301
|
-
error: `nimicoding
|
|
301
|
+
error: `nimicoding sweep audit refused: remediation wave admission failed: ${admitResult.error}\n`,
|
|
302
302
|
};
|
|
303
303
|
}
|
|
304
304
|
admitted.push(topicWave.wave_id);
|
|
@@ -59,10 +59,10 @@ function validateRerunEvidence(evidence, finding, disposition) {
|
|
|
59
59
|
export async function resolveAuditSweepFinding(projectRoot, options) {
|
|
60
60
|
const sweepId = safeSweepId(options.sweepId);
|
|
61
61
|
if (!sweepId || typeof options.findingId !== "string") {
|
|
62
|
-
return inputError("nimicoding
|
|
62
|
+
return inputError("nimicoding sweep audit refused: --sweep-id and --finding-id are required.\n");
|
|
63
63
|
}
|
|
64
64
|
if (!FINDING_DISPOSITION.has(options.disposition) || options.disposition === "open") {
|
|
65
|
-
return inputError("nimicoding
|
|
65
|
+
return inputError("nimicoding sweep audit refused: --disposition must be one of remediated, accepted-risk, false-positive, deferred-backlog.\n");
|
|
66
66
|
}
|
|
67
67
|
const timestampError = ensureIsoTimestamp(options.verifiedAt);
|
|
68
68
|
if (timestampError) {
|
|
@@ -74,22 +74,22 @@ export async function resolveAuditSweepFinding(projectRoot, options) {
|
|
|
74
74
|
}
|
|
75
75
|
const sourceInfo = await pathExists(source.absolutePath);
|
|
76
76
|
if (!sourceInfo || !sourceInfo.isFile()) {
|
|
77
|
-
return inputError("nimicoding
|
|
77
|
+
return inputError("nimicoding sweep audit refused: --from must point to an existing JSON evidence file.\n");
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const { findingsRef: aggregateFindingsRef, store } = await loadFindings(projectRoot, sweepId);
|
|
81
81
|
const findingIndex = store.findings.findIndex((finding) => finding.id === options.findingId);
|
|
82
82
|
if (findingIndex === -1) {
|
|
83
|
-
return inputError(`nimicoding
|
|
83
|
+
return inputError(`nimicoding sweep audit refused: finding not found for ${options.findingId}.\n`);
|
|
84
84
|
}
|
|
85
85
|
const finding = store.findings[findingIndex];
|
|
86
86
|
const evidenceJson = await loadJsonFile(source.absolutePath);
|
|
87
87
|
if (!evidenceJson.ok) {
|
|
88
|
-
return inputError("nimicoding
|
|
88
|
+
return inputError("nimicoding sweep audit refused: --from must contain valid JSON.\n");
|
|
89
89
|
}
|
|
90
90
|
const validation = validateRerunEvidence(evidenceJson.value, finding, options.disposition);
|
|
91
91
|
if (!validation.ok) {
|
|
92
|
-
return inputError(`nimicoding
|
|
92
|
+
return inputError(`nimicoding sweep audit refused: ${validation.error}.\n`);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const evidenceRef = artifactRef("evidence_refs", sweepId, `resolution-${options.findingId}.json`);
|
|
@@ -12,7 +12,7 @@ import { ensureClusterStore } from "./risk-budget.mjs";
|
|
|
12
12
|
export async function getAuditSweepStatus(projectRoot, options) {
|
|
13
13
|
const sweepId = safeSweepId(options.sweepId);
|
|
14
14
|
if (!sweepId) {
|
|
15
|
-
return inputError("nimicoding
|
|
15
|
+
return inputError("nimicoding sweep audit refused: --sweep-id is required.\n");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const planResult = await loadPlan(projectRoot, sweepId);
|
|
@@ -688,11 +688,11 @@ async function validateCloseoutArtifact(projectRoot, sweepId, ledgerInfo, remedi
|
|
|
688
688
|
export async function validateAuditSweepArtifacts(projectRoot, options) {
|
|
689
689
|
const sweepId = safeSweepId(options.sweepId);
|
|
690
690
|
if (!sweepId) {
|
|
691
|
-
return inputError("nimicoding
|
|
691
|
+
return inputError("nimicoding sweep audit refused: --sweep-id is required.\n");
|
|
692
692
|
}
|
|
693
693
|
const scope = options.scope ?? "all";
|
|
694
694
|
if (!VALIDATION_SCOPES.has(scope)) {
|
|
695
|
-
return inputError("nimicoding
|
|
695
|
+
return inputError("nimicoding sweep audit refused: --scope must be one of all, plan, chunks, findings, ledger, remediation, rerun, closeout.\n");
|
|
696
696
|
}
|
|
697
697
|
|
|
698
698
|
const checks = [];
|