sentinelayer-cli 0.8.0 → 0.8.2
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 +23 -2
- package/package.json +4 -4
- package/src/agents/ai-governance/index.js +12 -0
- package/src/agents/ai-governance/tools/base.js +171 -0
- package/src/agents/ai-governance/tools/eval-regression.js +47 -0
- package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
- package/src/agents/ai-governance/tools/index.js +52 -0
- package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
- package/src/agents/ai-governance/tools/provenance-check.js +69 -0
- package/src/agents/backend/index.js +12 -0
- package/src/agents/backend/tools/base.js +189 -0
- package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
- package/src/agents/backend/tools/idempotency-audit.js +105 -0
- package/src/agents/backend/tools/index.js +87 -0
- package/src/agents/backend/tools/retry-audit.js +132 -0
- package/src/agents/backend/tools/timeout-audit.js +144 -0
- package/src/agents/code-quality/index.js +12 -0
- package/src/agents/code-quality/tools/base.js +159 -0
- package/src/agents/code-quality/tools/complexity-measure.js +197 -0
- package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
- package/src/agents/code-quality/tools/cycle-detect.js +49 -0
- package/src/agents/code-quality/tools/dep-graph.js +196 -0
- package/src/agents/code-quality/tools/index.js +89 -0
- package/src/agents/data-layer/index.js +12 -0
- package/src/agents/data-layer/tools/base.js +181 -0
- package/src/agents/data-layer/tools/index-audit.js +165 -0
- package/src/agents/data-layer/tools/index.js +83 -0
- package/src/agents/data-layer/tools/migration-scan.js +135 -0
- package/src/agents/data-layer/tools/query-explain.js +120 -0
- package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
- package/src/agents/documentation/index.js +12 -0
- package/src/agents/documentation/tools/api-diff.js +91 -0
- package/src/agents/documentation/tools/base.js +151 -0
- package/src/agents/documentation/tools/dead-link-check.js +58 -0
- package/src/agents/documentation/tools/docstring-coverage.js +78 -0
- package/src/agents/documentation/tools/index.js +52 -0
- package/src/agents/documentation/tools/readme-freshness.js +61 -0
- package/src/agents/envelope/fix-cycle.js +45 -0
- package/src/agents/envelope/index.js +31 -0
- package/src/agents/envelope/loop.js +150 -0
- package/src/agents/envelope/pulse.js +18 -0
- package/src/agents/envelope/stream.js +40 -0
- package/src/agents/infrastructure/index.js +12 -0
- package/src/agents/infrastructure/tools/base.js +171 -0
- package/src/agents/infrastructure/tools/checkov-run.js +32 -0
- package/src/agents/infrastructure/tools/drift-detect.js +59 -0
- package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
- package/src/agents/infrastructure/tools/index.js +52 -0
- package/src/agents/infrastructure/tools/tflint-run.js +31 -0
- package/src/agents/jules/loop.js +7 -4
- package/src/agents/jules/swarm/sub-agent.js +5 -1
- package/src/agents/jules/tools/auth-audit.js +10 -1
- package/src/agents/mode.js +113 -0
- package/src/agents/observability/index.js +12 -0
- package/src/agents/observability/tools/alert-audit.js +39 -0
- package/src/agents/observability/tools/base.js +181 -0
- package/src/agents/observability/tools/dashboard-gap.js +42 -0
- package/src/agents/observability/tools/index.js +54 -0
- package/src/agents/observability/tools/log-schema-check.js +74 -0
- package/src/agents/observability/tools/span-coverage.js +74 -0
- package/src/agents/persona-visuals.js +38 -0
- package/src/agents/release/index.js +12 -0
- package/src/agents/release/tools/base.js +181 -0
- package/src/agents/release/tools/changelog-diff.js +86 -0
- package/src/agents/release/tools/feature-flag-audit.js +126 -0
- package/src/agents/release/tools/index.js +61 -0
- package/src/agents/release/tools/rollback-verify.js +129 -0
- package/src/agents/release/tools/semver-check.js +109 -0
- package/src/agents/reliability/index.js +12 -0
- package/src/agents/reliability/tools/backpressure-check.js +129 -0
- package/src/agents/reliability/tools/base.js +181 -0
- package/src/agents/reliability/tools/chaos-probe.js +109 -0
- package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
- package/src/agents/reliability/tools/health-check-audit.js +111 -0
- package/src/agents/reliability/tools/index.js +87 -0
- package/src/agents/run-persona.js +109 -0
- package/src/agents/security/index.js +12 -0
- package/src/agents/security/tools/authz-audit.js +134 -0
- package/src/agents/security/tools/base.js +190 -0
- package/src/agents/security/tools/crypto-review.js +175 -0
- package/src/agents/security/tools/index.js +97 -0
- package/src/agents/security/tools/sast-scan.js +175 -0
- package/src/agents/security/tools/secrets-scan.js +216 -0
- package/src/agents/supply-chain/index.js +12 -0
- package/src/agents/supply-chain/tools/attestation-check.js +42 -0
- package/src/agents/supply-chain/tools/base.js +151 -0
- package/src/agents/supply-chain/tools/index.js +52 -0
- package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
- package/src/agents/supply-chain/tools/package-verify.js +56 -0
- package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
- package/src/agents/testing/index.js +12 -0
- package/src/agents/testing/tools/base.js +202 -0
- package/src/agents/testing/tools/coverage-gap.js +144 -0
- package/src/agents/testing/tools/flake-detect.js +125 -0
- package/src/agents/testing/tools/index.js +85 -0
- package/src/agents/testing/tools/mutation-test.js +143 -0
- package/src/agents/testing/tools/snapshot-diff.js +103 -0
- package/src/auth/gate.js +65 -37
- package/src/cli.js +1 -1
- package/src/commands/chat.js +3 -10
- package/src/commands/legacy-args.js +10 -0
- package/src/commands/omargate.js +36 -2
- package/src/commands/persona.js +46 -1
- package/src/commands/scan.js +3 -10
- package/src/commands/session.js +654 -6
- package/src/commands/spec.js +3 -10
- package/src/coord/events-log.js +141 -0
- package/src/coord/handshake.js +719 -0
- package/src/coord/index.js +35 -0
- package/src/coord/paths.js +84 -0
- package/src/coord/priority.js +62 -0
- package/src/coord/tarjan.js +157 -0
- package/src/cost/tokenizer.js +160 -0
- package/src/cost/tracker.js +61 -0
- package/src/daemon/artifact-lineage.js +362 -0
- package/src/daemon/assignment-ledger.js +117 -0
- package/src/daemon/ast-drift.js +496 -0
- package/src/daemon/ingest-refresh.js +69 -2
- package/src/ingest/engine.js +15 -0
- package/src/ingest/ownership.js +380 -0
- package/src/legacy-cli.js +68 -1
- package/src/orchestrator/kai-chen.js +126 -0
- package/src/review/ai-review.js +3 -10
- package/src/review/compliance-pack.js +389 -0
- package/src/review/investor-dd-config.js +54 -0
- package/src/review/investor-dd-file-loop.js +303 -0
- package/src/review/investor-dd-file-router.js +406 -0
- package/src/review/investor-dd-html-report.js +233 -0
- package/src/review/investor-dd-notification.js +120 -0
- package/src/review/investor-dd-orchestrator.js +405 -0
- package/src/review/investor-dd-persona-runner.js +275 -0
- package/src/review/live-validator.js +253 -0
- package/src/review/omargate-orchestrator.js +90 -2
- package/src/review/persona-prompts.js +244 -56
- package/src/review/reconciliation-rules.js +329 -0
- package/src/review/reproducibility-chain.js +136 -0
- package/src/review/scan-modes.js +102 -3
- package/src/session/agent-registry.js +7 -0
- package/src/session/analytics.js +479 -0
- package/src/session/daemon.js +609 -14
- package/src/session/file-locks.js +666 -0
- package/src/session/paths.js +4 -0
- package/src/session/recap.js +567 -0
- package/src/session/redact.js +82 -0
- package/src/session/runtime-bridge.js +24 -1
- package/src/session/scoring.js +406 -0
- package/src/session/setup-guides.js +304 -0
- package/src/session/store.js +318 -2
- package/src/session/stream.js +9 -1
- package/src/session/sync.js +753 -0
- package/src/session/tasks.js +1054 -0
- package/src/session/templates.js +188 -0
- package/src/swarm/runtime.js +1 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import fsp from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
@@ -6,6 +7,7 @@ import { getBudgetHealthColor, resolveOperatorControlStorage } from "./operator-
|
|
|
6
7
|
import { listBudgetStates, resolveBudgetGovernorStorage } from "./budget-governor.js";
|
|
7
8
|
import { listErrorQueue, resolveErrorDaemonStorage, WORK_ITEM_STATUSES } from "./error-worker.js";
|
|
8
9
|
import { listJiraIssues, resolveJiraLifecycleStorage } from "./jira-lifecycle.js";
|
|
10
|
+
import { resolveSessionPaths } from "../session/paths.js";
|
|
9
11
|
|
|
10
12
|
const LINEAGE_SCHEMA_VERSION = "1.0.0";
|
|
11
13
|
const WORK_ITEM_STATUS_SET = new Set(WORK_ITEM_STATUSES);
|
|
@@ -46,6 +48,348 @@ function toRelativePosix(baseDir, absolutePath) {
|
|
|
46
48
|
return toPosixPath(relative);
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
function normalizeDateKey(value = "", fallbackIso = new Date().toISOString()) {
|
|
52
|
+
const normalized = normalizeString(value);
|
|
53
|
+
if (normalized && /^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
|
|
54
|
+
return normalized;
|
|
55
|
+
}
|
|
56
|
+
return normalizeIsoTimestamp(normalized, fallbackIso).slice(0, 10);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildDayKey(nowIso = new Date().toISOString()) {
|
|
60
|
+
return normalizeDateKey(nowIso, nowIso);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeStringArray(values = []) {
|
|
64
|
+
if (!Array.isArray(values)) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
return Array.from(
|
|
68
|
+
new Set(
|
|
69
|
+
values
|
|
70
|
+
.map((value) => normalizeString(value))
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function readFileBufferOptional(filePath) {
|
|
77
|
+
try {
|
|
78
|
+
return await fsp.readFile(filePath);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error && typeof error === "object" && error.code === "ENOENT") {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function hashFileSha256(filePath) {
|
|
88
|
+
const payload = await fsp.readFile(filePath);
|
|
89
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveWorkItemArtifactDir(storage, workItemId, date) {
|
|
93
|
+
return path.join(
|
|
94
|
+
storage.observabilityRoot,
|
|
95
|
+
normalizeDateKey(date, new Date().toISOString()),
|
|
96
|
+
normalizeString(workItemId)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function canonicalizeArtifactRecords(artifactFiles = []) {
|
|
101
|
+
return artifactFiles
|
|
102
|
+
.map((artifact) => ({
|
|
103
|
+
name: normalizeString(artifact.name),
|
|
104
|
+
path: toPosixPath(normalizeString(artifact.path)),
|
|
105
|
+
sha256: normalizeString(artifact.sha256).toLowerCase(),
|
|
106
|
+
sizeBytes: Number(artifact.sizeBytes || 0),
|
|
107
|
+
}))
|
|
108
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildCloseoutAnchorPayload({
|
|
112
|
+
workItemId,
|
|
113
|
+
sessionId,
|
|
114
|
+
date,
|
|
115
|
+
artifacts = [],
|
|
116
|
+
sessionStream = null,
|
|
117
|
+
cosignAttestationRef = "",
|
|
118
|
+
sbomRef = "",
|
|
119
|
+
evidenceLinks = [],
|
|
120
|
+
} = {}) {
|
|
121
|
+
return {
|
|
122
|
+
workItemId: normalizeString(workItemId),
|
|
123
|
+
sessionId: normalizeString(sessionId) || null,
|
|
124
|
+
date: normalizeDateKey(date, new Date().toISOString()),
|
|
125
|
+
artifacts: canonicalizeArtifactRecords(artifacts),
|
|
126
|
+
sessionStream: sessionStream
|
|
127
|
+
? {
|
|
128
|
+
path: toPosixPath(normalizeString(sessionStream.path)),
|
|
129
|
+
sha256: normalizeString(sessionStream.sha256).toLowerCase(),
|
|
130
|
+
sizeBytes: Number(sessionStream.sizeBytes || 0),
|
|
131
|
+
}
|
|
132
|
+
: null,
|
|
133
|
+
cosignAttestationRef: normalizeString(cosignAttestationRef) || null,
|
|
134
|
+
sbomRef: normalizeString(sbomRef) || null,
|
|
135
|
+
evidenceLinks: normalizeStringArray(evidenceLinks).sort((left, right) =>
|
|
136
|
+
left.localeCompare(right)
|
|
137
|
+
),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function computeAnchorSha256(payload = {}) {
|
|
142
|
+
return createHash("sha256")
|
|
143
|
+
.update(JSON.stringify(payload))
|
|
144
|
+
.digest("hex");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function resolveArtifactAbsolutePath(storage, artifactPath = "") {
|
|
148
|
+
const normalized = normalizeString(artifactPath);
|
|
149
|
+
if (!normalized) {
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
if (path.isAbsolute(normalized)) {
|
|
153
|
+
return normalized;
|
|
154
|
+
}
|
|
155
|
+
return path.join(storage.outputRoot, normalized);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function resolveSessionStreamDigest(sessionId, { targetPath = "." } = {}) {
|
|
159
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
160
|
+
if (!normalizedSessionId) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const sessionPaths = resolveSessionPaths(normalizedSessionId, { targetPath });
|
|
165
|
+
const [rotatedBuffer, streamBuffer] = await Promise.all([
|
|
166
|
+
readFileBufferOptional(sessionPaths.rotatedStreamPath),
|
|
167
|
+
readFileBufferOptional(sessionPaths.streamPath),
|
|
168
|
+
]);
|
|
169
|
+
if (!rotatedBuffer && !streamBuffer) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const hash = createHash("sha256");
|
|
174
|
+
let totalBytes = 0;
|
|
175
|
+
if (rotatedBuffer) {
|
|
176
|
+
hash.update(rotatedBuffer);
|
|
177
|
+
totalBytes += rotatedBuffer.length;
|
|
178
|
+
}
|
|
179
|
+
if (streamBuffer) {
|
|
180
|
+
hash.update(streamBuffer);
|
|
181
|
+
totalBytes += streamBuffer.length;
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
path: toPosixPath(path.relative(path.resolve(String(targetPath || ".")), sessionPaths.streamPath)),
|
|
185
|
+
sha256: hash.digest("hex"),
|
|
186
|
+
sizeBytes: totalBytes,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function writeCloseoutArtifact({
|
|
191
|
+
workItemId,
|
|
192
|
+
sessionId = "",
|
|
193
|
+
date = "",
|
|
194
|
+
targetPath = ".",
|
|
195
|
+
outputDir = "",
|
|
196
|
+
env,
|
|
197
|
+
homeDir,
|
|
198
|
+
nowIso = new Date().toISOString(),
|
|
199
|
+
cosignAttestationRef = "",
|
|
200
|
+
sbomRef = "",
|
|
201
|
+
evidenceLinks = [],
|
|
202
|
+
chainVerified = true,
|
|
203
|
+
} = {}) {
|
|
204
|
+
const normalizedWorkItemId = normalizeString(workItemId);
|
|
205
|
+
if (!normalizedWorkItemId) {
|
|
206
|
+
throw new Error("workItemId is required.");
|
|
207
|
+
}
|
|
208
|
+
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
209
|
+
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
210
|
+
const storage = await resolveArtifactLineageStorage({
|
|
211
|
+
targetPath: normalizedTargetPath,
|
|
212
|
+
outputDir,
|
|
213
|
+
env,
|
|
214
|
+
homeDir,
|
|
215
|
+
});
|
|
216
|
+
const dateKey = normalizeDateKey(date, normalizedNow);
|
|
217
|
+
const artifactDir = resolveWorkItemArtifactDir(storage, normalizedWorkItemId, dateKey);
|
|
218
|
+
await fsp.mkdir(artifactDir, { recursive: true });
|
|
219
|
+
const closeoutPath = path.join(artifactDir, "closeout.json");
|
|
220
|
+
|
|
221
|
+
const entries = await fsp.readdir(artifactDir, { withFileTypes: true }).catch((error) => {
|
|
222
|
+
if (error && typeof error === "object" && error.code === "ENOENT") {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
throw error;
|
|
226
|
+
});
|
|
227
|
+
const artifactFiles = [];
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
if (!entry.isFile()) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const lower = entry.name.toLowerCase();
|
|
233
|
+
if (lower === "closeout.json") {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const absolutePath = path.join(artifactDir, entry.name);
|
|
237
|
+
const stat = await fsp.stat(absolutePath);
|
|
238
|
+
artifactFiles.push({
|
|
239
|
+
name: entry.name,
|
|
240
|
+
path: toRelativePosix(storage.outputRoot, absolutePath),
|
|
241
|
+
sha256: await hashFileSha256(absolutePath),
|
|
242
|
+
sizeBytes: Number(stat.size || 0),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const sessionStream = await resolveSessionStreamDigest(normalizeString(sessionId), {
|
|
246
|
+
targetPath: normalizedTargetPath,
|
|
247
|
+
});
|
|
248
|
+
const anchorPayload = buildCloseoutAnchorPayload({
|
|
249
|
+
workItemId: normalizedWorkItemId,
|
|
250
|
+
sessionId,
|
|
251
|
+
date: dateKey,
|
|
252
|
+
artifacts: artifactFiles,
|
|
253
|
+
sessionStream,
|
|
254
|
+
cosignAttestationRef,
|
|
255
|
+
sbomRef,
|
|
256
|
+
evidenceLinks,
|
|
257
|
+
});
|
|
258
|
+
const anchorSha256 = computeAnchorSha256(anchorPayload);
|
|
259
|
+
const payload = {
|
|
260
|
+
schemaVersion: "1.0.0",
|
|
261
|
+
generatedAt: normalizedNow,
|
|
262
|
+
chainVerified: Boolean(chainVerified),
|
|
263
|
+
workItemId: normalizedWorkItemId,
|
|
264
|
+
sessionId: normalizeString(sessionId) || null,
|
|
265
|
+
date: dateKey,
|
|
266
|
+
artifactDir: toRelativePosix(storage.outputRoot, artifactDir),
|
|
267
|
+
artifacts: canonicalizeArtifactRecords(artifactFiles),
|
|
268
|
+
sessionStream,
|
|
269
|
+
cosignAttestationRef: normalizeString(cosignAttestationRef) || null,
|
|
270
|
+
sbomRef: normalizeString(sbomRef) || null,
|
|
271
|
+
evidenceLinks: normalizeStringArray(evidenceLinks).sort((left, right) =>
|
|
272
|
+
left.localeCompare(right)
|
|
273
|
+
),
|
|
274
|
+
anchorSha256,
|
|
275
|
+
};
|
|
276
|
+
await writeJsonFile(closeoutPath, payload);
|
|
277
|
+
return {
|
|
278
|
+
closeoutPath,
|
|
279
|
+
payload,
|
|
280
|
+
anchorSha256,
|
|
281
|
+
artifactCount: payload.artifacts.length,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export async function verifyArtifactChain({
|
|
286
|
+
workItemId,
|
|
287
|
+
date = "",
|
|
288
|
+
targetPath = ".",
|
|
289
|
+
outputDir = "",
|
|
290
|
+
env,
|
|
291
|
+
homeDir,
|
|
292
|
+
} = {}) {
|
|
293
|
+
const normalizedWorkItemId = normalizeString(workItemId);
|
|
294
|
+
if (!normalizedWorkItemId) {
|
|
295
|
+
throw new Error("workItemId is required.");
|
|
296
|
+
}
|
|
297
|
+
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
298
|
+
const storage = await resolveArtifactLineageStorage({
|
|
299
|
+
targetPath: normalizedTargetPath,
|
|
300
|
+
outputDir,
|
|
301
|
+
env,
|
|
302
|
+
homeDir,
|
|
303
|
+
});
|
|
304
|
+
const dateKey = normalizeDateKey(date, new Date().toISOString());
|
|
305
|
+
const artifactDir = resolveWorkItemArtifactDir(storage, normalizedWorkItemId, dateKey);
|
|
306
|
+
const closeoutPath = path.join(artifactDir, "closeout.json");
|
|
307
|
+
const closeout = await readJsonFile(closeoutPath, null);
|
|
308
|
+
if (!closeout || typeof closeout !== "object") {
|
|
309
|
+
throw new Error(`closeout.json was not found for work item '${normalizedWorkItemId}' on '${dateKey}'.`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const mismatches = [];
|
|
313
|
+
const artifacts = Array.isArray(closeout.artifacts) ? closeout.artifacts : [];
|
|
314
|
+
for (const artifact of artifacts) {
|
|
315
|
+
const expectedPath = normalizeString(artifact.path);
|
|
316
|
+
const absolutePath = resolveArtifactAbsolutePath(storage, expectedPath);
|
|
317
|
+
if (!absolutePath) {
|
|
318
|
+
mismatches.push({
|
|
319
|
+
type: "artifact_path_missing",
|
|
320
|
+
path: expectedPath,
|
|
321
|
+
});
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const buffer = await readFileBufferOptional(absolutePath);
|
|
325
|
+
if (!buffer) {
|
|
326
|
+
mismatches.push({
|
|
327
|
+
type: "artifact_missing",
|
|
328
|
+
path: expectedPath,
|
|
329
|
+
});
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const actualSha256 = createHash("sha256").update(buffer).digest("hex");
|
|
333
|
+
if (normalizeString(artifact.sha256).toLowerCase() !== actualSha256) {
|
|
334
|
+
mismatches.push({
|
|
335
|
+
type: "artifact_sha_mismatch",
|
|
336
|
+
path: expectedPath,
|
|
337
|
+
expected: normalizeString(artifact.sha256).toLowerCase(),
|
|
338
|
+
actual: actualSha256,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let sessionStream = null;
|
|
344
|
+
const sessionId = normalizeString(closeout.sessionId);
|
|
345
|
+
if (sessionId) {
|
|
346
|
+
sessionStream = await resolveSessionStreamDigest(sessionId, {
|
|
347
|
+
targetPath: normalizedTargetPath,
|
|
348
|
+
});
|
|
349
|
+
if (closeout.sessionStream && sessionStream) {
|
|
350
|
+
const expectedStreamSha = normalizeString(closeout.sessionStream.sha256).toLowerCase();
|
|
351
|
+
if (expectedStreamSha !== normalizeString(sessionStream.sha256).toLowerCase()) {
|
|
352
|
+
mismatches.push({
|
|
353
|
+
type: "session_stream_sha_mismatch",
|
|
354
|
+
path: normalizeString(closeout.sessionStream.path),
|
|
355
|
+
expected: expectedStreamSha,
|
|
356
|
+
actual: normalizeString(sessionStream.sha256).toLowerCase(),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const recomputedAnchorPayload = buildCloseoutAnchorPayload({
|
|
363
|
+
workItemId: normalizedWorkItemId,
|
|
364
|
+
sessionId,
|
|
365
|
+
date: closeout.date,
|
|
366
|
+
artifacts,
|
|
367
|
+
sessionStream: sessionStream || closeout.sessionStream || null,
|
|
368
|
+
cosignAttestationRef: closeout.cosignAttestationRef,
|
|
369
|
+
sbomRef: closeout.sbomRef,
|
|
370
|
+
evidenceLinks: closeout.evidenceLinks,
|
|
371
|
+
});
|
|
372
|
+
const recomputedAnchorSha256 = computeAnchorSha256(recomputedAnchorPayload);
|
|
373
|
+
if (normalizeString(closeout.anchorSha256).toLowerCase() !== recomputedAnchorSha256) {
|
|
374
|
+
mismatches.push({
|
|
375
|
+
type: "anchor_sha_mismatch",
|
|
376
|
+
expected: normalizeString(closeout.anchorSha256).toLowerCase(),
|
|
377
|
+
actual: recomputedAnchorSha256,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
valid: mismatches.length === 0,
|
|
383
|
+
closeoutPath,
|
|
384
|
+
workItemId: normalizedWorkItemId,
|
|
385
|
+
date: normalizeDateKey(closeout.date, dateKey),
|
|
386
|
+
mismatches,
|
|
387
|
+
artifactCount: artifacts.length,
|
|
388
|
+
anchorSha256: normalizeString(closeout.anchorSha256).toLowerCase(),
|
|
389
|
+
recomputedAnchorSha256,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
49
393
|
async function writeJsonFile(filePath, payload = {}) {
|
|
50
394
|
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
51
395
|
await fsp.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
@@ -345,6 +689,7 @@ export async function buildArtifactLineageIndex({
|
|
|
345
689
|
firstSeenAt: queueItem.firstSeenAt,
|
|
346
690
|
lastSeenAt: queueItem.lastSeenAt,
|
|
347
691
|
links: {
|
|
692
|
+
sessionId: assignment?.sessionId || null,
|
|
348
693
|
agentIdentity: assignment?.assignedAgentIdentity || null,
|
|
349
694
|
assignmentStatus: assignment?.status || null,
|
|
350
695
|
assignmentStage: assignment?.stage || null,
|
|
@@ -370,6 +715,23 @@ export async function buildArtifactLineageIndex({
|
|
|
370
715
|
};
|
|
371
716
|
});
|
|
372
717
|
|
|
718
|
+
await Promise.all(
|
|
719
|
+
workItems.map(async (workItem) => {
|
|
720
|
+
const closeout = await writeCloseoutArtifact({
|
|
721
|
+
workItemId: workItem.workItemId,
|
|
722
|
+
sessionId: normalizeString(workItem.links.sessionId),
|
|
723
|
+
date: buildDayKey(normalizedNow),
|
|
724
|
+
targetPath,
|
|
725
|
+
outputDir,
|
|
726
|
+
env,
|
|
727
|
+
homeDir,
|
|
728
|
+
nowIso: normalizedNow,
|
|
729
|
+
});
|
|
730
|
+
workItem.artifacts.closeoutPath = toRelativePosix(storage.outputRoot, closeout.closeoutPath);
|
|
731
|
+
workItem.artifacts.closeoutAnchorSha256 = closeout.anchorSha256;
|
|
732
|
+
})
|
|
733
|
+
);
|
|
734
|
+
|
|
373
735
|
const lineageRunId = createLineageRunId(normalizedNow);
|
|
374
736
|
const statusCounts = summarizeStatusCounts(workItems);
|
|
375
737
|
const linkedAgentIdentities = new Set(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import fsp from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
@@ -55,6 +56,14 @@ function normalizeMetadata(value) {
|
|
|
55
56
|
return { ...value };
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
function normalizeSeverity(value, fallbackValue = "P2") {
|
|
60
|
+
const normalized = normalizeString(value).toUpperCase();
|
|
61
|
+
if (normalized === "P0" || normalized === "P1" || normalized === "P2" || normalized === "P3") {
|
|
62
|
+
return normalized;
|
|
63
|
+
}
|
|
64
|
+
return fallbackValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
function normalizeAssignmentStatus(value, fallbackValue = "QUEUED") {
|
|
59
68
|
const normalized = normalizeString(value).toUpperCase();
|
|
60
69
|
if (ASSIGNMENT_STATUS_SET.has(normalized)) {
|
|
@@ -964,3 +973,111 @@ export async function reassignLease({
|
|
|
964
973
|
nowIso,
|
|
965
974
|
});
|
|
966
975
|
}
|
|
976
|
+
|
|
977
|
+
function buildQueueFingerprint(seed = "") {
|
|
978
|
+
return createHash("sha256").update(normalizeString(seed) || "session-task").digest("hex");
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
export async function ensureWorkItemQueued({
|
|
982
|
+
targetPath = ".",
|
|
983
|
+
outputDir = "",
|
|
984
|
+
workItemId,
|
|
985
|
+
sessionId = "",
|
|
986
|
+
status = "QUEUED",
|
|
987
|
+
severity = "P2",
|
|
988
|
+
service = "session",
|
|
989
|
+
endpoint = "",
|
|
990
|
+
errorCode = "SESSION_TASK_ASSIGNMENT",
|
|
991
|
+
source = "session_task",
|
|
992
|
+
message = "",
|
|
993
|
+
dedupKey = "",
|
|
994
|
+
metadata = {},
|
|
995
|
+
env,
|
|
996
|
+
homeDir,
|
|
997
|
+
nowIso = new Date().toISOString(),
|
|
998
|
+
} = {}) {
|
|
999
|
+
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
1000
|
+
const normalizedWorkItemId = normalizeString(workItemId);
|
|
1001
|
+
if (!normalizedWorkItemId) {
|
|
1002
|
+
throw new Error("workItemId is required.");
|
|
1003
|
+
}
|
|
1004
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
1005
|
+
const normalizedStatus = normalizeWorkItemStatus(status, "QUEUED");
|
|
1006
|
+
const normalizedService = normalizeString(service) || "session";
|
|
1007
|
+
const normalizedEndpoint =
|
|
1008
|
+
normalizeString(endpoint) ||
|
|
1009
|
+
(normalizedSessionId
|
|
1010
|
+
? `/sessions/${normalizedSessionId}/tasks`
|
|
1011
|
+
: "/sessions/global/tasks");
|
|
1012
|
+
const normalizedErrorCode = normalizeString(errorCode) || "SESSION_TASK_ASSIGNMENT";
|
|
1013
|
+
const normalizedSource = normalizeString(source) || "session_task";
|
|
1014
|
+
const normalizedMessage = normalizeString(message) || "Session task assignment";
|
|
1015
|
+
const normalizedDedupKey =
|
|
1016
|
+
normalizeString(dedupKey) ||
|
|
1017
|
+
`session|${normalizedSessionId || "none"}|${normalizedWorkItemId}`;
|
|
1018
|
+
const normalizedMetadata = {
|
|
1019
|
+
...normalizeMetadata(metadata),
|
|
1020
|
+
sessionId: normalizedSessionId,
|
|
1021
|
+
source: normalizedSource,
|
|
1022
|
+
workItemType: "session_task",
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const storage = await resolveAssignmentLedgerStorage({
|
|
1026
|
+
targetPath,
|
|
1027
|
+
outputDir,
|
|
1028
|
+
env,
|
|
1029
|
+
homeDir,
|
|
1030
|
+
});
|
|
1031
|
+
const queue = await loadQueue(storage.queuePath, normalizedNow);
|
|
1032
|
+
const existingIndex = queue.items.findIndex(
|
|
1033
|
+
(item) => normalizeString(item.workItemId) === normalizedWorkItemId
|
|
1034
|
+
);
|
|
1035
|
+
const existing = existingIndex >= 0 ? queue.items[existingIndex] : null;
|
|
1036
|
+
const fingerprintSeed = `${normalizedDedupKey}|${normalizedService}|${normalizedEndpoint}|${normalizedErrorCode}`;
|
|
1037
|
+
const normalizedItem = normalizeQueueItem(
|
|
1038
|
+
{
|
|
1039
|
+
...existing,
|
|
1040
|
+
workItemId: normalizedWorkItemId,
|
|
1041
|
+
fingerprint:
|
|
1042
|
+
normalizeString(existing?.fingerprint) || buildQueueFingerprint(fingerprintSeed),
|
|
1043
|
+
source: normalizedSource,
|
|
1044
|
+
service: normalizedService,
|
|
1045
|
+
endpoint: normalizedEndpoint,
|
|
1046
|
+
errorCode: normalizedErrorCode,
|
|
1047
|
+
severity: normalizeSeverity(existing?.severity || severity, "P2"),
|
|
1048
|
+
status: normalizedStatus,
|
|
1049
|
+
message: normalizedMessage,
|
|
1050
|
+
stackFingerprint:
|
|
1051
|
+
normalizeString(existing?.stackFingerprint) ||
|
|
1052
|
+
buildQueueFingerprint(`${normalizedWorkItemId}|stack`).slice(0, 64),
|
|
1053
|
+
commitSha: existing?.commitSha || null,
|
|
1054
|
+
dedupKey: normalizedDedupKey,
|
|
1055
|
+
firstSeenAt: normalizeIsoTimestamp(existing?.firstSeenAt, normalizedNow),
|
|
1056
|
+
lastSeenAt: normalizedNow,
|
|
1057
|
+
latestEventId: normalizeString(existing?.latestEventId) || null,
|
|
1058
|
+
occurrenceCount: Math.max(1, Number(existing?.occurrenceCount || 1)),
|
|
1059
|
+
requestIds: Array.isArray(existing?.requestIds) ? [...existing.requestIds] : [],
|
|
1060
|
+
createdAt: normalizeIsoTimestamp(existing?.createdAt, normalizedNow),
|
|
1061
|
+
updatedAt: normalizedNow,
|
|
1062
|
+
metadata: {
|
|
1063
|
+
...normalizeMetadata(existing?.metadata),
|
|
1064
|
+
...normalizedMetadata,
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
normalizedNow
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
if (existingIndex >= 0) {
|
|
1071
|
+
queue.items[existingIndex] = normalizedItem;
|
|
1072
|
+
} else {
|
|
1073
|
+
queue.items.push(normalizedItem);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const savedQueue = await writeQueue(storage.queuePath, queue, normalizedNow);
|
|
1077
|
+
const queueItem = savedQueue.items.find((item) => item.workItemId === normalizedWorkItemId) || normalizedItem;
|
|
1078
|
+
return {
|
|
1079
|
+
...storage,
|
|
1080
|
+
queue: savedQueue,
|
|
1081
|
+
queueItem,
|
|
1082
|
+
};
|
|
1083
|
+
}
|