@agent-inspect/mcp-server 6.31.9 → 6.31.11
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/dist/{chunk-U3JBHSNB.mjs → chunk-FWSGOK3F.mjs} +717 -681
- package/dist/chunk-FWSGOK3F.mjs.map +1 -0
- package/dist/cli.cjs +715 -679
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.cjs +715 -679
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -3
- package/dist/chunk-U3JBHSNB.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1692,7 +1692,7 @@ function stepMixLine(summary) {
|
|
|
1692
1692
|
if (summary.logicSteps > 0) parts.push(`${summary.logicSteps} logic`);
|
|
1693
1693
|
return parts.length > 0 ? parts.join(", ") : "none";
|
|
1694
1694
|
}
|
|
1695
|
-
function
|
|
1695
|
+
function executionLine(summary) {
|
|
1696
1696
|
if (summary.status === "success") {
|
|
1697
1697
|
return summary.errorSteps > 0 ? "Completed with step errors recorded." : "Completed successfully.";
|
|
1698
1698
|
}
|
|
@@ -1710,7 +1710,7 @@ function outcomeLine(summary) {
|
|
|
1710
1710
|
if (summary.status === "running") {
|
|
1711
1711
|
return "Run is still in progress (no run_completed).";
|
|
1712
1712
|
}
|
|
1713
|
-
return "
|
|
1713
|
+
return "Execution status unknown \u2014 inspect events may be incomplete.";
|
|
1714
1714
|
}
|
|
1715
1715
|
function buildRunWhatSummary(events) {
|
|
1716
1716
|
const base = buildRunSummary(events);
|
|
@@ -1801,7 +1801,7 @@ function renderRunWhat(summary, options = {}) {
|
|
|
1801
1801
|
lines.push(`Correlation: ${parts.join(", ")}`);
|
|
1802
1802
|
}
|
|
1803
1803
|
}
|
|
1804
|
-
lines.push(`
|
|
1804
|
+
lines.push(`Execution: ${executionLine(summary)}`);
|
|
1805
1805
|
if (summary.longestStep && summary.totalSteps > 0) {
|
|
1806
1806
|
lines.push(
|
|
1807
1807
|
`Slowest: ${summary.longestStep.name} (${formatDuration2(summary.longestStep.durationMs)}, ${summary.longestStep.type})`
|
|
@@ -1955,6 +1955,369 @@ function findFirstCausalFailure(events, options = {}) {
|
|
|
1955
1955
|
);
|
|
1956
1956
|
}
|
|
1957
1957
|
|
|
1958
|
+
// packages/core/src/diagnostics/programmatic.ts
|
|
1959
|
+
var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
|
|
1960
|
+
AI_TRACE_INPUT_INVALID: {
|
|
1961
|
+
code: "AI_TRACE_INPUT_INVALID",
|
|
1962
|
+
summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
|
|
1963
|
+
remediation: "For a file path, use openTraceFile(path).",
|
|
1964
|
+
relatedCodes: ["invalid_input"]
|
|
1965
|
+
},
|
|
1966
|
+
AI_TRACE_FORMAT_UNSUPPORTED: {
|
|
1967
|
+
code: "AI_TRACE_FORMAT_UNSUPPORTED",
|
|
1968
|
+
summary: "No trace reader could detect the input format.",
|
|
1969
|
+
remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
|
|
1970
|
+
relatedCodes: ["unsupported_format"]
|
|
1971
|
+
},
|
|
1972
|
+
AI_TRACE_FORMAT_AMBIGUOUS: {
|
|
1973
|
+
code: "AI_TRACE_FORMAT_AMBIGUOUS",
|
|
1974
|
+
summary: "Multiple trace readers matched the input with equal confidence.",
|
|
1975
|
+
remediation: "Set options.format explicitly to disambiguate the reader.",
|
|
1976
|
+
relatedCodes: ["ambiguous_format"]
|
|
1977
|
+
},
|
|
1978
|
+
AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
|
|
1979
|
+
code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
|
|
1980
|
+
summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
|
|
1981
|
+
remediation: "Use openTraceFile() to normalize a JSONL trace first."
|
|
1982
|
+
},
|
|
1983
|
+
AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
|
|
1984
|
+
code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
|
|
1985
|
+
summary: "Multiple runs are available; select a run before executing checks.",
|
|
1986
|
+
remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
|
|
1987
|
+
relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
|
|
1988
|
+
},
|
|
1989
|
+
AI_TRACE_RELATIONSHIP_SELF_PARENT: {
|
|
1990
|
+
code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
1991
|
+
summary: "A parentId equals its own event/step id.",
|
|
1992
|
+
remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
|
|
1993
|
+
relatedCodes: [
|
|
1994
|
+
"AI_LANGGRAPH_SELF_PARENT_REJECTED",
|
|
1995
|
+
"AI_LOGICAL_SELF_PARENT_REMOVED"
|
|
1996
|
+
]
|
|
1997
|
+
},
|
|
1998
|
+
AI_TRACE_RELATIONSHIP_CYCLE: {
|
|
1999
|
+
code: "AI_TRACE_RELATIONSHIP_CYCLE",
|
|
2000
|
+
summary: "Trace contains a parentId cycle.",
|
|
2001
|
+
remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
|
|
2002
|
+
relatedCodes: ["structure.cycle"]
|
|
2003
|
+
},
|
|
2004
|
+
AI_TRACE_CONTRACT_INPUT_INVALID: {
|
|
2005
|
+
code: "AI_TRACE_CONTRACT_INPUT_INVALID",
|
|
2006
|
+
summary: "evaluateTraceContract expects ({ read }, contract) or use evaluateTraceContractRead(read, contract).",
|
|
2007
|
+
remediation: "Pass TraceCheckInput as the first argument: evaluateTraceContract({ read }, contract).",
|
|
2008
|
+
relatedCodes: ["AI_CHECK_INVALID_ARGUMENTS"]
|
|
2009
|
+
},
|
|
2010
|
+
AI_INSPECT_RUN_UNSUPPORTED_OPTION: {
|
|
2011
|
+
code: "AI_INSPECT_RUN_UNSUPPORTED_OPTION",
|
|
2012
|
+
summary: 'inspectRun does not accept a "writer" option.',
|
|
2013
|
+
remediation: "Use createInspector({ writer }) then inspector.run(...). inspectRun persists via the global JSONL path (schema 0.1)."
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
function formatProgrammaticDiagnostic(code, detail) {
|
|
2017
|
+
const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
|
|
2018
|
+
const summary = detail?.trim() ? detail.trim() : spec.summary;
|
|
2019
|
+
return `${code}: ${summary} Remediation: ${spec.remediation}`;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// packages/core/src/checks/logical-events.ts
|
|
2023
|
+
function isRecord6(value) {
|
|
2024
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2025
|
+
}
|
|
2026
|
+
function legacyEvent(event) {
|
|
2027
|
+
const value = event.attributes?.legacyEvent;
|
|
2028
|
+
return typeof value === "string" ? value : void 0;
|
|
2029
|
+
}
|
|
2030
|
+
function stepIdOf(event) {
|
|
2031
|
+
const value = event.attributes?.stepId;
|
|
2032
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
2033
|
+
return void 0;
|
|
2034
|
+
}
|
|
2035
|
+
function cloneEvent(event) {
|
|
2036
|
+
return {
|
|
2037
|
+
...event,
|
|
2038
|
+
...event.attributes !== void 0 ? { attributes: { ...event.attributes } } : {},
|
|
2039
|
+
...event.error !== void 0 ? { error: { ...event.error } } : {},
|
|
2040
|
+
...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
|
|
2041
|
+
...event.source !== void 0 ? { source: { ...event.source } } : {}
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
function mergeAttributes(start, complete) {
|
|
2045
|
+
const merged = {
|
|
2046
|
+
...isRecord6(complete.attributes) ? complete.attributes : {},
|
|
2047
|
+
...isRecord6(start.attributes) ? start.attributes : {}
|
|
2048
|
+
};
|
|
2049
|
+
merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
|
|
2050
|
+
merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
|
|
2051
|
+
if (complete.attributes?.errorStack !== void 0) {
|
|
2052
|
+
merged.errorStack = complete.attributes.errorStack;
|
|
2053
|
+
}
|
|
2054
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
2055
|
+
}
|
|
2056
|
+
function pairStartComplete(start, complete) {
|
|
2057
|
+
const attributes = mergeAttributes(start, complete);
|
|
2058
|
+
const paired = {
|
|
2059
|
+
...cloneEvent(start),
|
|
2060
|
+
status: complete.status,
|
|
2061
|
+
timestamp: complete.timestamp ?? start.timestamp,
|
|
2062
|
+
...complete.endedAt !== void 0 ? { endedAt: complete.endedAt } : {},
|
|
2063
|
+
...complete.durationMs !== void 0 ? { durationMs: complete.durationMs } : {},
|
|
2064
|
+
...complete.error !== void 0 ? { error: { ...complete.error } } : {},
|
|
2065
|
+
...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
|
|
2066
|
+
...attributes !== void 0 ? { attributes } : {}
|
|
2067
|
+
};
|
|
2068
|
+
return {
|
|
2069
|
+
...paired,
|
|
2070
|
+
sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
|
|
2071
|
+
projection: {
|
|
2072
|
+
paired: true,
|
|
2073
|
+
absorbedEventIds: Object.freeze([complete.eventId]),
|
|
2074
|
+
parentNormalized: false,
|
|
2075
|
+
...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
function asLogical(event, extras) {
|
|
2080
|
+
return {
|
|
2081
|
+
...cloneEvent(event),
|
|
2082
|
+
sourceEventIds: Object.freeze([event.eventId]),
|
|
2083
|
+
projection: {
|
|
2084
|
+
paired: false,
|
|
2085
|
+
absorbedEventIds: Object.freeze([]),
|
|
2086
|
+
parentNormalized: extras?.parentNormalized === true,
|
|
2087
|
+
...{}
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
function projectLogicalEvents(events) {
|
|
2092
|
+
const diagnostics = [];
|
|
2093
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
2094
|
+
for (const event of events) {
|
|
2095
|
+
const list = byRun.get(event.runId) ?? [];
|
|
2096
|
+
list.push(event);
|
|
2097
|
+
byRun.set(event.runId, list);
|
|
2098
|
+
}
|
|
2099
|
+
const absorbedIds = /* @__PURE__ */ new Set();
|
|
2100
|
+
const logicalByRawId = /* @__PURE__ */ new Map();
|
|
2101
|
+
const stepIdToLogicalId = /* @__PURE__ */ new Map();
|
|
2102
|
+
const logical = [];
|
|
2103
|
+
const orderedRuns = [...byRun.keys()].sort((a, b) => a.localeCompare(b));
|
|
2104
|
+
for (const runId of orderedRuns) {
|
|
2105
|
+
const runEvents = byRun.get(runId) ?? [];
|
|
2106
|
+
const starts = [];
|
|
2107
|
+
const completes = [];
|
|
2108
|
+
const others = [];
|
|
2109
|
+
for (const event of runEvents) {
|
|
2110
|
+
const legacy = legacyEvent(event);
|
|
2111
|
+
if (legacy === "step_started" || legacy === "run_started" && event.status === "running") {
|
|
2112
|
+
starts.push(event);
|
|
2113
|
+
} else if (legacy === "step_completed" || legacy === "run_completed") {
|
|
2114
|
+
completes.push(event);
|
|
2115
|
+
} else {
|
|
2116
|
+
others.push(event);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
const usedCompletes = /* @__PURE__ */ new Set();
|
|
2120
|
+
for (const start of starts) {
|
|
2121
|
+
const stepId = stepIdOf(start);
|
|
2122
|
+
let match;
|
|
2123
|
+
if (legacyEvent(start) === "run_started") {
|
|
2124
|
+
const candidates = completes.filter(
|
|
2125
|
+
(c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "run_completed"
|
|
2126
|
+
);
|
|
2127
|
+
if (candidates.length > 1) {
|
|
2128
|
+
diagnostics.push({
|
|
2129
|
+
code: "AI_LOGICAL_PAIR_AMBIGUOUS",
|
|
2130
|
+
message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
|
|
2131
|
+
eventIds: candidates.map((c) => c.eventId)
|
|
2132
|
+
});
|
|
2133
|
+
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
2134
|
+
}
|
|
2135
|
+
match = candidates[0];
|
|
2136
|
+
} else if (stepId) {
|
|
2137
|
+
const candidates = completes.filter(
|
|
2138
|
+
(c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
|
|
2139
|
+
);
|
|
2140
|
+
if (candidates.length > 1) {
|
|
2141
|
+
diagnostics.push({
|
|
2142
|
+
code: "AI_LOGICAL_PAIR_AMBIGUOUS",
|
|
2143
|
+
message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
|
|
2144
|
+
eventIds: candidates.map((c) => c.eventId)
|
|
2145
|
+
});
|
|
2146
|
+
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
2147
|
+
}
|
|
2148
|
+
match = candidates[0];
|
|
2149
|
+
}
|
|
2150
|
+
if (match) {
|
|
2151
|
+
usedCompletes.add(match.eventId);
|
|
2152
|
+
absorbedIds.add(match.eventId);
|
|
2153
|
+
const paired = pairStartComplete(start, match);
|
|
2154
|
+
logical.push(paired);
|
|
2155
|
+
logicalByRawId.set(start.eventId, paired);
|
|
2156
|
+
logicalByRawId.set(match.eventId, paired);
|
|
2157
|
+
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
|
|
2158
|
+
} else {
|
|
2159
|
+
diagnostics.push({
|
|
2160
|
+
code: "AI_LOGICAL_PAIR_UNMATCHED_START",
|
|
2161
|
+
message: `No matching complete for start ${start.eventId}.`,
|
|
2162
|
+
eventIds: [start.eventId]
|
|
2163
|
+
});
|
|
2164
|
+
const alone = asLogical(start);
|
|
2165
|
+
logical.push(alone);
|
|
2166
|
+
logicalByRawId.set(start.eventId, alone);
|
|
2167
|
+
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
for (const complete of completes) {
|
|
2171
|
+
if (usedCompletes.has(complete.eventId)) continue;
|
|
2172
|
+
diagnostics.push({
|
|
2173
|
+
code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
|
|
2174
|
+
message: `No matching start for complete ${complete.eventId}.`,
|
|
2175
|
+
eventIds: [complete.eventId]
|
|
2176
|
+
});
|
|
2177
|
+
const alone = asLogical(complete);
|
|
2178
|
+
logical.push(alone);
|
|
2179
|
+
logicalByRawId.set(complete.eventId, alone);
|
|
2180
|
+
const stepId = stepIdOf(complete);
|
|
2181
|
+
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
2182
|
+
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
for (const event of others) {
|
|
2186
|
+
const alone = asLogical(event);
|
|
2187
|
+
logical.push(alone);
|
|
2188
|
+
logicalByRawId.set(event.eventId, alone);
|
|
2189
|
+
const stepId = stepIdOf(event);
|
|
2190
|
+
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
2191
|
+
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
const logicalById = new Map(logical.map((e) => [e.eventId, e]));
|
|
2196
|
+
const normalized = [];
|
|
2197
|
+
for (const event of logical) {
|
|
2198
|
+
const originalParentId = event.parentId;
|
|
2199
|
+
if (!originalParentId) {
|
|
2200
|
+
normalized.push(event);
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
let nextParent = originalParentId;
|
|
2204
|
+
let remapped = false;
|
|
2205
|
+
const viaAbsorbed = logicalByRawId.get(originalParentId);
|
|
2206
|
+
if (viaAbsorbed && viaAbsorbed.eventId !== originalParentId) {
|
|
2207
|
+
nextParent = viaAbsorbed.eventId;
|
|
2208
|
+
remapped = true;
|
|
2209
|
+
} else if (!logicalById.has(originalParentId)) {
|
|
2210
|
+
const viaStep = stepIdToLogicalId.get(`${event.runId}:${originalParentId}`);
|
|
2211
|
+
if (viaStep) {
|
|
2212
|
+
nextParent = viaStep;
|
|
2213
|
+
remapped = true;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
if (!remapped) {
|
|
2217
|
+
if (originalParentId === event.eventId) {
|
|
2218
|
+
diagnostics.push({
|
|
2219
|
+
code: "AI_LOGICAL_SELF_PARENT_REMOVED",
|
|
2220
|
+
message: formatProgrammaticDiagnostic(
|
|
2221
|
+
"AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
2222
|
+
`Removed self-parent edge on ${event.eventId}.`
|
|
2223
|
+
),
|
|
2224
|
+
eventIds: [event.eventId]
|
|
2225
|
+
});
|
|
2226
|
+
const { parentId: _drop, ...rest } = event;
|
|
2227
|
+
normalized.push({
|
|
2228
|
+
...rest,
|
|
2229
|
+
projection: {
|
|
2230
|
+
...event.projection,
|
|
2231
|
+
parentNormalized: true,
|
|
2232
|
+
originalParentId
|
|
2233
|
+
}
|
|
2234
|
+
});
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
if (!logicalById.has(originalParentId) && !logicalByRawId.has(originalParentId)) {
|
|
2238
|
+
const mapping = event.attributes?.parentMapping;
|
|
2239
|
+
const unresolved = mapping === "unresolved" || event.attributes?.parentUnresolved === true || event.attributes?.unresolvedParent === true || // LangGraph/framework scaffolding sentinel labels are not event ids.
|
|
2240
|
+
/^LangGraph$/i.test(originalParentId) || originalParentId.startsWith("unresolved:");
|
|
2241
|
+
if (!unresolved) {
|
|
2242
|
+
diagnostics.push({
|
|
2243
|
+
code: "AI_LOGICAL_PARENT_UNRESOLVED",
|
|
2244
|
+
message: `Parent ${originalParentId} unresolved for ${event.eventId}.`,
|
|
2245
|
+
eventIds: [event.eventId]
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
normalized.push(event);
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
if (nextParent === event.eventId) {
|
|
2253
|
+
diagnostics.push({
|
|
2254
|
+
code: "AI_LOGICAL_SELF_PARENT_REMOVED",
|
|
2255
|
+
message: formatProgrammaticDiagnostic(
|
|
2256
|
+
"AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
2257
|
+
`Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
|
|
2258
|
+
),
|
|
2259
|
+
eventIds: [event.eventId]
|
|
2260
|
+
});
|
|
2261
|
+
const { parentId: _drop, ...rest } = event;
|
|
2262
|
+
normalized.push({
|
|
2263
|
+
...rest,
|
|
2264
|
+
projection: {
|
|
2265
|
+
...event.projection,
|
|
2266
|
+
parentNormalized: true,
|
|
2267
|
+
originalParentId
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
continue;
|
|
2271
|
+
}
|
|
2272
|
+
diagnostics.push({
|
|
2273
|
+
code: "AI_LOGICAL_PARENT_REMAPPED",
|
|
2274
|
+
message: `Remapped parent ${originalParentId} \u2192 ${nextParent} for ${event.eventId}.`,
|
|
2275
|
+
eventIds: [event.eventId]
|
|
2276
|
+
});
|
|
2277
|
+
normalized.push({
|
|
2278
|
+
...event,
|
|
2279
|
+
parentId: nextParent,
|
|
2280
|
+
projection: {
|
|
2281
|
+
...event.projection,
|
|
2282
|
+
parentNormalized: true,
|
|
2283
|
+
originalParentId
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
const rawIndex = new Map(events.map((e, i) => [e.eventId, i]));
|
|
2288
|
+
normalized.sort((a, b) => {
|
|
2289
|
+
const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
|
|
2290
|
+
const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
|
|
2291
|
+
return ai - bi || a.eventId.localeCompare(b.eventId);
|
|
2292
|
+
});
|
|
2293
|
+
return {
|
|
2294
|
+
logicalEvents: Object.freeze(normalized),
|
|
2295
|
+
diagnostics: Object.freeze(diagnostics)
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
function resolveCanonicalToolName(event) {
|
|
2299
|
+
const attrs2 = event.attributes;
|
|
2300
|
+
const direct = pickString(attrs2, ["toolName", "tool"]);
|
|
2301
|
+
if (direct) return direct;
|
|
2302
|
+
const metadata = attrs2?.metadata;
|
|
2303
|
+
if (isRecord6(metadata)) {
|
|
2304
|
+
const nested = pickString(metadata, ["toolName", "tool"]);
|
|
2305
|
+
if (nested) return nested;
|
|
2306
|
+
}
|
|
2307
|
+
for (const prefix of ["tool:", "function:", "mcp-tools:"]) {
|
|
2308
|
+
if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
|
|
2309
|
+
}
|
|
2310
|
+
return event.name;
|
|
2311
|
+
}
|
|
2312
|
+
function pickString(record, keys) {
|
|
2313
|
+
if (!record) return void 0;
|
|
2314
|
+
for (const key of keys) {
|
|
2315
|
+
const value = record[key];
|
|
2316
|
+
if (typeof value === "string" && value.trim() !== "") return value.trim();
|
|
2317
|
+
}
|
|
2318
|
+
return void 0;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
1958
2321
|
// packages/core/src/search.ts
|
|
1959
2322
|
function parseDurationFilter(expr) {
|
|
1960
2323
|
const raw = expr.trim();
|
|
@@ -2054,708 +2417,357 @@ async function searchTraces(metas, options) {
|
|
|
2054
2417
|
statusFilter: options.status
|
|
2055
2418
|
});
|
|
2056
2419
|
results.push(...stepMatches);
|
|
2057
|
-
if (observationStatus) {
|
|
2058
|
-
const outcomes = extractOutcomesFromTraceEvents(events);
|
|
2059
|
-
const matched = outcomes.filter((outcome) => outcome.status === observationStatus);
|
|
2060
|
-
for (const outcome of matched) {
|
|
2061
|
-
results.push({
|
|
2062
|
-
runId: m.runId,
|
|
2063
|
-
runName: m.name,
|
|
2064
|
-
runStatus: m.status,
|
|
2065
|
-
stepName: outcome.name,
|
|
2066
|
-
timestamp: outcome.observedAt,
|
|
2067
|
-
matchReason: `observation status=${outcome.status}`,
|
|
2068
|
-
matchedFields: ["outcome.status", "outcome.name"],
|
|
2069
|
-
filePath: m.filePath
|
|
2070
|
-
});
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
results.sort((a, b) => {
|
|
2075
|
-
const ta = a.timestamp ?? 0;
|
|
2076
|
-
const tb = b.timestamp ?? 0;
|
|
2077
|
-
if (ta !== tb) return tb - ta;
|
|
2078
|
-
const runCmp = a.runId.localeCompare(b.runId);
|
|
2079
|
-
if (runCmp !== 0) return runCmp;
|
|
2080
|
-
return (a.stepName ?? "").localeCompare(b.stepName ?? "");
|
|
2081
|
-
});
|
|
2082
|
-
return results.slice(0, limit);
|
|
2083
|
-
}
|
|
2084
|
-
function matchRunLevel(m, opts) {
|
|
2085
|
-
if (opts.stepTypeFilter || opts.toolQuery) return [];
|
|
2086
|
-
if (opts.statusFilter && m.status !== opts.statusFilter) return [];
|
|
2087
|
-
if (opts.nameQuery && !nameMatches(m.name ?? m.runId, opts.nameQuery)) {
|
|
2088
|
-
return [];
|
|
2089
|
-
}
|
|
2090
|
-
if (opts.durationFilter && !durationMatches(m.durationMs, opts.durationFilter)) {
|
|
2091
|
-
return [];
|
|
2092
|
-
}
|
|
2093
|
-
const fields = [];
|
|
2094
|
-
if (opts.statusFilter) fields.push("run.status");
|
|
2095
|
-
if (opts.nameQuery) fields.push("run.name");
|
|
2096
|
-
if (opts.durationFilter) fields.push("run.durationMs");
|
|
2097
|
-
if (fields.length === 0) return [];
|
|
2098
|
-
return [
|
|
2099
|
-
{
|
|
2100
|
-
runId: m.runId,
|
|
2101
|
-
runName: m.name,
|
|
2102
|
-
runStatus: m.status,
|
|
2103
|
-
timestamp: m.startedAt,
|
|
2104
|
-
durationMs: m.durationMs,
|
|
2105
|
-
matchReason: `run match: ${fields.join(", ")}`,
|
|
2106
|
-
matchedFields: fields,
|
|
2107
|
-
filePath: m.filePath
|
|
2108
|
-
}
|
|
2109
|
-
];
|
|
2110
|
-
}
|
|
2111
|
-
function matchStepLevel(m, events, opts) {
|
|
2112
|
-
const out = [];
|
|
2113
|
-
const started = /* @__PURE__ */ new Map();
|
|
2114
|
-
for (const e of events) {
|
|
2115
|
-
if (e.event === "step_started") {
|
|
2116
|
-
started.set(e.stepId, e);
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
for (const e of events) {
|
|
2120
|
-
if (e.event !== "step_completed") continue;
|
|
2121
|
-
const c = e;
|
|
2122
|
-
const s = started.get(c.stepId);
|
|
2123
|
-
if (!s) continue;
|
|
2124
|
-
const fields = [];
|
|
2125
|
-
const stepType = s.type;
|
|
2126
|
-
if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
|
|
2127
|
-
continue;
|
|
2128
|
-
}
|
|
2129
|
-
const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
|
|
2130
|
-
if (!hasStepFilters) continue;
|
|
2131
|
-
if (opts.statusFilter === "error" && c.status === "error") {
|
|
2132
|
-
fields.push("step.status");
|
|
2133
|
-
} else if (opts.statusFilter === "success" && c.status === "success") {
|
|
2134
|
-
fields.push("step.status");
|
|
2135
|
-
} else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
|
|
2136
|
-
continue;
|
|
2137
|
-
}
|
|
2138
|
-
if (opts.nameQuery) {
|
|
2139
|
-
if (!nameMatches(s.name, opts.nameQuery)) continue;
|
|
2140
|
-
fields.push("step.name");
|
|
2141
|
-
}
|
|
2142
|
-
if (opts.toolQuery) {
|
|
2143
|
-
const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
|
|
2144
|
-
if (!nameMatches(toolName, opts.toolQuery)) continue;
|
|
2145
|
-
fields.push("step.tool");
|
|
2146
|
-
}
|
|
2147
|
-
if (opts.durationFilter) {
|
|
2148
|
-
if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
|
|
2149
|
-
fields.push("step.durationMs");
|
|
2150
|
-
}
|
|
2151
|
-
if (opts.stepTypeFilter) {
|
|
2152
|
-
fields.push("step.type");
|
|
2153
|
-
}
|
|
2154
|
-
if (fields.length === 0) continue;
|
|
2155
|
-
out.push({
|
|
2156
|
-
runId: m.runId,
|
|
2157
|
-
runName: m.name,
|
|
2158
|
-
runStatus: m.status,
|
|
2159
|
-
stepId: c.stepId,
|
|
2160
|
-
stepName: s.name,
|
|
2161
|
-
stepType,
|
|
2162
|
-
timestamp: s.startTime ?? s.timestamp,
|
|
2163
|
-
durationMs: c.durationMs,
|
|
2164
|
-
matchReason: `step match: ${fields.join(", ")}`,
|
|
2165
|
-
matchedFields: fields,
|
|
2166
|
-
filePath: m.filePath
|
|
2167
|
-
});
|
|
2168
|
-
}
|
|
2169
|
-
return out;
|
|
2170
|
-
}
|
|
2171
|
-
async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
2172
|
-
const metas = [];
|
|
2173
|
-
for (const fileName of fileNames) {
|
|
2174
|
-
try {
|
|
2175
|
-
const filePath = getPath(fileName);
|
|
2176
|
-
const meta = await extractMetadata(filePath);
|
|
2177
|
-
metas.push(meta);
|
|
2178
|
-
} catch {
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
return metas;
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
// packages/core/src/sessions/metadata.ts
|
|
2185
|
-
function isNonEmptyString3(value) {
|
|
2186
|
-
return typeof value === "string" && value.trim() !== "";
|
|
2187
|
-
}
|
|
2188
|
-
function finitePositiveInt(value) {
|
|
2189
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
2190
|
-
return void 0;
|
|
2191
|
-
}
|
|
2192
|
-
return Math.trunc(value);
|
|
2193
|
-
}
|
|
2194
|
-
function extractSessionWorkflowMetadata(record) {
|
|
2195
|
-
if (!record) return void 0;
|
|
2196
|
-
const out = {};
|
|
2197
|
-
let found = false;
|
|
2198
|
-
const assignString = (key, value) => {
|
|
2199
|
-
if (isNonEmptyString3(value)) {
|
|
2200
|
-
out[key] = value.trim();
|
|
2201
|
-
found = true;
|
|
2202
|
-
}
|
|
2203
|
-
};
|
|
2204
|
-
assignString("sessionId", record.sessionId);
|
|
2205
|
-
assignString("conversationId", record.conversationId);
|
|
2206
|
-
assignString("groupId", record.groupId);
|
|
2207
|
-
assignString("parentGroupId", record.parentGroupId);
|
|
2208
|
-
assignString("retryOf", record.retryOf);
|
|
2209
|
-
assignString("retryReason", record.retryReason);
|
|
2210
|
-
assignString("handoffFrom", record.handoffFrom);
|
|
2211
|
-
assignString("handoffTo", record.handoffTo);
|
|
2212
|
-
assignString("subAgentId", record.subAgentId);
|
|
2213
|
-
assignString("subAgentName", record.subAgentName);
|
|
2214
|
-
assignString("jobId", record.jobId);
|
|
2215
|
-
assignString("queueName", record.queueName);
|
|
2216
|
-
assignString("workflowName", record.workflowName);
|
|
2217
|
-
assignString("workflowStep", record.workflowStep);
|
|
2218
|
-
assignString("toolCallId", record.toolCallId);
|
|
2219
|
-
assignString("mcpToolCallId", record.mcpToolCallId);
|
|
2220
|
-
assignString("linkedStepId", record.linkedStepId);
|
|
2221
|
-
assignString("operationId", record.operationId);
|
|
2222
|
-
assignString("attemptId", record.attemptId);
|
|
2223
|
-
assignString("fallbackOf", record.fallbackOf);
|
|
2224
|
-
assignString("idempotencyKey", record.idempotencyKey);
|
|
2225
|
-
assignString("correlationId", record.correlationId);
|
|
2226
|
-
assignString("requestId", record.requestId);
|
|
2227
|
-
assignString("decisionId", record.decisionId);
|
|
2228
|
-
const attempt = finitePositiveInt(record.attempt);
|
|
2229
|
-
if (attempt !== void 0) {
|
|
2230
|
-
out.attempt = attempt;
|
|
2231
|
-
found = true;
|
|
2232
|
-
}
|
|
2233
|
-
const attemptNumber = finitePositiveInt(record.attemptNumber);
|
|
2234
|
-
if (attemptNumber !== void 0) {
|
|
2235
|
-
out.attemptNumber = attemptNumber;
|
|
2236
|
-
found = true;
|
|
2237
|
-
}
|
|
2238
|
-
return found ? out : void 0;
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
// packages/core/src/bundle/safety-status.ts
|
|
2242
|
-
function aggregateBundleSafeStatus(statuses) {
|
|
2243
|
-
if (statuses.length === 0) return "UNKNOWN";
|
|
2244
|
-
if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
|
|
2245
|
-
if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
|
|
2246
|
-
if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
|
|
2247
|
-
return "SAFE";
|
|
2248
|
-
}
|
|
2249
|
-
function toMetadataSafeStatus(status) {
|
|
2250
|
-
if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
|
|
2251
|
-
return status;
|
|
2252
|
-
}
|
|
2253
|
-
function bundleFailsOnSafety(status, allowUnsafe) {
|
|
2254
|
-
return status === "UNSAFE" || status === "UNKNOWN";
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
// packages/core/src/bundle/manifest.ts
|
|
2258
|
-
var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
|
|
2259
|
-
function buildBundleMetadata(parts) {
|
|
2260
|
-
const aggregate = aggregateBundleSafeStatus(
|
|
2261
|
-
parts.checks.runs.map((run) => run.status)
|
|
2262
|
-
);
|
|
2263
|
-
return {
|
|
2264
|
-
createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2265
|
-
agentInspectVersion: parts.agentInspectVersion,
|
|
2266
|
-
redactionProfile: parts.profile,
|
|
2267
|
-
sourceTraceCount: parts.resolve.runIds.length,
|
|
2268
|
-
runIds: [...parts.resolve.runIds],
|
|
2269
|
-
safeStatus: toMetadataSafeStatus(aggregate),
|
|
2270
|
-
files: [...parts.files].sort((a, b) => a.localeCompare(b)),
|
|
2271
|
-
note: BUNDLE_NOTE,
|
|
2272
|
-
...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
|
|
2273
|
-
...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
|
|
2274
|
-
};
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
// packages/core/src/evidence/types.ts
|
|
2278
|
-
var EVIDENCE_FORMAT_VERSION = "1.0";
|
|
2279
|
-
var EVIDENCE_ASSESSMENT_NOTE = "Best-effort local safety verification only; not a compliance certification.";
|
|
2280
|
-
var EVIDENCE_MANIFEST_FILENAME = "evidence.json";
|
|
2281
|
-
function sha256Hex(data) {
|
|
2282
|
-
const hash = crypto.createHash("sha256");
|
|
2283
|
-
if (typeof data === "string") {
|
|
2284
|
-
hash.update(data, "utf8");
|
|
2285
|
-
} else {
|
|
2286
|
-
hash.update(data);
|
|
2420
|
+
if (observationStatus) {
|
|
2421
|
+
const outcomes = extractOutcomesFromTraceEvents(events);
|
|
2422
|
+
const matched = outcomes.filter((outcome) => outcome.status === observationStatus);
|
|
2423
|
+
for (const outcome of matched) {
|
|
2424
|
+
results.push({
|
|
2425
|
+
runId: m.runId,
|
|
2426
|
+
runName: m.name,
|
|
2427
|
+
runStatus: m.status,
|
|
2428
|
+
stepName: outcome.name,
|
|
2429
|
+
timestamp: outcome.observedAt,
|
|
2430
|
+
matchReason: `observation status=${outcome.status}`,
|
|
2431
|
+
matchedFields: ["outcome.status", "outcome.name"],
|
|
2432
|
+
filePath: m.filePath
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2287
2436
|
}
|
|
2288
|
-
|
|
2437
|
+
results.sort((a, b) => {
|
|
2438
|
+
const ta = a.timestamp ?? 0;
|
|
2439
|
+
const tb = b.timestamp ?? 0;
|
|
2440
|
+
if (ta !== tb) return tb - ta;
|
|
2441
|
+
const runCmp = a.runId.localeCompare(b.runId);
|
|
2442
|
+
if (runCmp !== 0) return runCmp;
|
|
2443
|
+
return (a.stepName ?? "").localeCompare(b.stepName ?? "");
|
|
2444
|
+
});
|
|
2445
|
+
return results.slice(0, limit);
|
|
2289
2446
|
}
|
|
2290
|
-
function
|
|
2291
|
-
if (
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
if (path14__default.default.isAbsolute(trimmed) || trimmed.startsWith("/")) {
|
|
2296
|
-
throw new Error(`Evidence file path must be relative: ${relativePath}`);
|
|
2447
|
+
function matchRunLevel(m, opts) {
|
|
2448
|
+
if (opts.stepTypeFilter || opts.toolQuery) return [];
|
|
2449
|
+
if (opts.statusFilter && m.status !== opts.statusFilter) return [];
|
|
2450
|
+
if (opts.nameQuery && !nameMatches(m.name ?? m.runId, opts.nameQuery)) {
|
|
2451
|
+
return [];
|
|
2297
2452
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
throw new Error(`Evidence file path must be relative: ${relativePath}`);
|
|
2453
|
+
if (opts.durationFilter && !durationMatches(m.durationMs, opts.durationFilter)) {
|
|
2454
|
+
return [];
|
|
2301
2455
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2456
|
+
const fields = [];
|
|
2457
|
+
if (opts.statusFilter) fields.push("run.status");
|
|
2458
|
+
if (opts.nameQuery) fields.push("run.name");
|
|
2459
|
+
if (opts.durationFilter) fields.push("run.durationMs");
|
|
2460
|
+
if (fields.length === 0) return [];
|
|
2461
|
+
return [
|
|
2462
|
+
{
|
|
2463
|
+
runId: m.runId,
|
|
2464
|
+
runName: m.name,
|
|
2465
|
+
runStatus: m.status,
|
|
2466
|
+
timestamp: m.startedAt,
|
|
2467
|
+
durationMs: m.durationMs,
|
|
2468
|
+
matchReason: `run match: ${fields.join(", ")}`,
|
|
2469
|
+
matchedFields: fields,
|
|
2470
|
+
filePath: m.filePath
|
|
2305
2471
|
}
|
|
2306
|
-
|
|
2307
|
-
return parts.join("/");
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
// packages/core/src/evidence/manifest.ts
|
|
2311
|
-
function stable(value) {
|
|
2312
|
-
if (Array.isArray(value)) return value.map(stable);
|
|
2313
|
-
if (value === null || typeof value !== "object") return value;
|
|
2314
|
-
const record = value;
|
|
2315
|
-
return Object.fromEntries(
|
|
2316
|
-
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
|
|
2317
|
-
);
|
|
2318
|
-
}
|
|
2319
|
-
function serializeEvidenceManifest(manifest) {
|
|
2320
|
-
return `${JSON.stringify(stable(manifest), null, 2)}
|
|
2321
|
-
`;
|
|
2472
|
+
];
|
|
2322
2473
|
}
|
|
2323
|
-
function
|
|
2324
|
-
const
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
return "redacted-trace";
|
|
2331
|
-
}
|
|
2332
|
-
if (base === "check-results.json") {
|
|
2333
|
-
return "checks";
|
|
2334
|
-
}
|
|
2335
|
-
if (base === "contract.resolved.json") {
|
|
2336
|
-
return "contract";
|
|
2337
|
-
}
|
|
2338
|
-
if (base === "redaction-report.json") {
|
|
2339
|
-
return "redaction-report";
|
|
2340
|
-
}
|
|
2341
|
-
if (base === "summary.md") {
|
|
2342
|
-
return "summary";
|
|
2474
|
+
function matchStepLevel(m, events, opts) {
|
|
2475
|
+
const out = [];
|
|
2476
|
+
const started = /* @__PURE__ */ new Map();
|
|
2477
|
+
for (const e of events) {
|
|
2478
|
+
if (e.event === "step_started") {
|
|
2479
|
+
started.set(e.stepId, e);
|
|
2480
|
+
}
|
|
2343
2481
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
const
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
);
|
|
2482
|
+
for (const e of events) {
|
|
2483
|
+
if (e.event !== "step_completed") continue;
|
|
2484
|
+
const c = e;
|
|
2485
|
+
const s = started.get(c.stepId);
|
|
2486
|
+
if (!s) continue;
|
|
2487
|
+
const fields = [];
|
|
2488
|
+
const stepType = s.type;
|
|
2489
|
+
if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
|
|
2490
|
+
continue;
|
|
2354
2491
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2492
|
+
const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
|
|
2493
|
+
if (!hasStepFilters) continue;
|
|
2494
|
+
if (opts.statusFilter === "error" && c.status === "error") {
|
|
2495
|
+
fields.push("step.status");
|
|
2496
|
+
} else if (opts.statusFilter === "success" && c.status === "success") {
|
|
2497
|
+
fields.push("step.status");
|
|
2498
|
+
} else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
|
|
2499
|
+
continue;
|
|
2357
2500
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
role: file.role ?? inferEvidenceFileRole(relativePath)
|
|
2362
|
-
});
|
|
2363
|
-
}
|
|
2364
|
-
return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
2365
|
-
}
|
|
2366
|
-
function buildEvidenceManifest(parts) {
|
|
2367
|
-
const runIds = [...parts.runIds];
|
|
2368
|
-
if (runIds.length === 0) {
|
|
2369
|
-
throw new Error("Evidence manifest requires at least one run id.");
|
|
2370
|
-
}
|
|
2371
|
-
for (const item of parts.sourceHashes) {
|
|
2372
|
-
if (!runIds.includes(item.runId)) {
|
|
2373
|
-
throw new Error(`sourceHashes runId "${item.runId}" is not listed in source.runIds.`);
|
|
2501
|
+
if (opts.nameQuery) {
|
|
2502
|
+
if (!nameMatches(s.name, opts.nameQuery)) continue;
|
|
2503
|
+
fields.push("step.name");
|
|
2374
2504
|
}
|
|
2375
|
-
if (
|
|
2376
|
-
|
|
2505
|
+
if (opts.toolQuery) {
|
|
2506
|
+
const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
|
|
2507
|
+
if (!nameMatches(toolName, opts.toolQuery)) continue;
|
|
2508
|
+
fields.push("step.tool");
|
|
2377
2509
|
}
|
|
2510
|
+
if (opts.durationFilter) {
|
|
2511
|
+
if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
|
|
2512
|
+
fields.push("step.durationMs");
|
|
2513
|
+
}
|
|
2514
|
+
if (opts.stepTypeFilter) {
|
|
2515
|
+
fields.push("step.type");
|
|
2516
|
+
}
|
|
2517
|
+
if (fields.length === 0) continue;
|
|
2518
|
+
out.push({
|
|
2519
|
+
runId: m.runId,
|
|
2520
|
+
runName: m.name,
|
|
2521
|
+
runStatus: m.status,
|
|
2522
|
+
stepId: c.stepId,
|
|
2523
|
+
stepName: s.name,
|
|
2524
|
+
stepType,
|
|
2525
|
+
timestamp: s.startTime ?? s.timestamp,
|
|
2526
|
+
durationMs: c.durationMs,
|
|
2527
|
+
matchReason: `step match: ${fields.join(", ")}`,
|
|
2528
|
+
matchedFields: fields,
|
|
2529
|
+
filePath: m.filePath
|
|
2530
|
+
});
|
|
2378
2531
|
}
|
|
2379
|
-
|
|
2380
|
-
status: parts.assessmentStatus,
|
|
2381
|
-
note: parts.note ?? EVIDENCE_ASSESSMENT_NOTE
|
|
2382
|
-
};
|
|
2383
|
-
if (parts.sourceStatus !== void 0) {
|
|
2384
|
-
assessment.sourceStatus = parts.sourceStatus;
|
|
2385
|
-
}
|
|
2386
|
-
return {
|
|
2387
|
-
evidenceFormatVersion: EVIDENCE_FORMAT_VERSION,
|
|
2388
|
-
generator: {
|
|
2389
|
-
name: parts.generatorName ?? "agent-inspect",
|
|
2390
|
-
version: parts.generatorVersion
|
|
2391
|
-
},
|
|
2392
|
-
createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2393
|
-
source: {
|
|
2394
|
-
runIds,
|
|
2395
|
-
traceSchemaVersions: [...parts.traceSchemaVersions].sort((a, b) => a.localeCompare(b)),
|
|
2396
|
-
sourceHashes: [...parts.sourceHashes].sort((a, b) => a.runId.localeCompare(b.runId))
|
|
2397
|
-
},
|
|
2398
|
-
policy: {
|
|
2399
|
-
redactionProfile: parts.redactionProfile,
|
|
2400
|
-
verificationPolicy: parts.verificationPolicy ?? parts.redactionProfile
|
|
2401
|
-
},
|
|
2402
|
-
assessment,
|
|
2403
|
-
...parts.semantics !== void 0 ? { semantics: { ...parts.semantics } } : {},
|
|
2404
|
-
...parts.contract !== void 0 ? { contract: { ...parts.contract } } : {},
|
|
2405
|
-
files: buildEvidenceFileEntries(parts.files)
|
|
2406
|
-
};
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
// packages/core/src/diagnostics/programmatic.ts
|
|
2410
|
-
var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
|
|
2411
|
-
AI_TRACE_INPUT_INVALID: {
|
|
2412
|
-
code: "AI_TRACE_INPUT_INVALID",
|
|
2413
|
-
summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
|
|
2414
|
-
remediation: "For a file path, use openTraceFile(path).",
|
|
2415
|
-
relatedCodes: ["invalid_input"]
|
|
2416
|
-
},
|
|
2417
|
-
AI_TRACE_FORMAT_UNSUPPORTED: {
|
|
2418
|
-
code: "AI_TRACE_FORMAT_UNSUPPORTED",
|
|
2419
|
-
summary: "No trace reader could detect the input format.",
|
|
2420
|
-
remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
|
|
2421
|
-
relatedCodes: ["unsupported_format"]
|
|
2422
|
-
},
|
|
2423
|
-
AI_TRACE_FORMAT_AMBIGUOUS: {
|
|
2424
|
-
code: "AI_TRACE_FORMAT_AMBIGUOUS",
|
|
2425
|
-
summary: "Multiple trace readers matched the input with equal confidence.",
|
|
2426
|
-
remediation: "Set options.format explicitly to disambiguate the reader.",
|
|
2427
|
-
relatedCodes: ["ambiguous_format"]
|
|
2428
|
-
},
|
|
2429
|
-
AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
|
|
2430
|
-
code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
|
|
2431
|
-
summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
|
|
2432
|
-
remediation: "Use openTraceFile() to normalize a JSONL trace first."
|
|
2433
|
-
},
|
|
2434
|
-
AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
|
|
2435
|
-
code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
|
|
2436
|
-
summary: "Multiple runs are available; select a run before executing checks.",
|
|
2437
|
-
remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
|
|
2438
|
-
relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
|
|
2439
|
-
},
|
|
2440
|
-
AI_TRACE_RELATIONSHIP_SELF_PARENT: {
|
|
2441
|
-
code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
2442
|
-
summary: "A parentId equals its own event/step id.",
|
|
2443
|
-
remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
|
|
2444
|
-
relatedCodes: [
|
|
2445
|
-
"AI_LANGGRAPH_SELF_PARENT_REJECTED",
|
|
2446
|
-
"AI_LOGICAL_SELF_PARENT_REMOVED"
|
|
2447
|
-
]
|
|
2448
|
-
},
|
|
2449
|
-
AI_TRACE_RELATIONSHIP_CYCLE: {
|
|
2450
|
-
code: "AI_TRACE_RELATIONSHIP_CYCLE",
|
|
2451
|
-
summary: "Trace contains a parentId cycle.",
|
|
2452
|
-
remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
|
|
2453
|
-
relatedCodes: ["structure.cycle"]
|
|
2454
|
-
}
|
|
2455
|
-
});
|
|
2456
|
-
function formatProgrammaticDiagnostic(code, detail) {
|
|
2457
|
-
const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
|
|
2458
|
-
const summary = detail?.trim() ? detail.trim() : spec.summary;
|
|
2459
|
-
return `${code}: ${summary} Remediation: ${spec.remediation}`;
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
// packages/core/src/checks/logical-events.ts
|
|
2463
|
-
function isRecord6(value) {
|
|
2464
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2465
|
-
}
|
|
2466
|
-
function legacyEvent(event) {
|
|
2467
|
-
const value = event.attributes?.legacyEvent;
|
|
2468
|
-
return typeof value === "string" ? value : void 0;
|
|
2532
|
+
return out;
|
|
2469
2533
|
}
|
|
2470
|
-
function
|
|
2471
|
-
const
|
|
2472
|
-
|
|
2473
|
-
|
|
2534
|
+
async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
2535
|
+
const metas = [];
|
|
2536
|
+
for (const fileName of fileNames) {
|
|
2537
|
+
try {
|
|
2538
|
+
const filePath = getPath(fileName);
|
|
2539
|
+
const meta = await extractMetadata(filePath);
|
|
2540
|
+
metas.push(meta);
|
|
2541
|
+
} catch {
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
return metas;
|
|
2474
2545
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
...event.error !== void 0 ? { error: { ...event.error } } : {},
|
|
2480
|
-
...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
|
|
2481
|
-
...event.source !== void 0 ? { source: { ...event.source } } : {}
|
|
2482
|
-
};
|
|
2546
|
+
|
|
2547
|
+
// packages/core/src/sessions/metadata.ts
|
|
2548
|
+
function isNonEmptyString3(value) {
|
|
2549
|
+
return typeof value === "string" && value.trim() !== "";
|
|
2483
2550
|
}
|
|
2484
|
-
function
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
...isRecord6(start.attributes) ? start.attributes : {}
|
|
2488
|
-
};
|
|
2489
|
-
merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
|
|
2490
|
-
merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
|
|
2491
|
-
if (complete.attributes?.errorStack !== void 0) {
|
|
2492
|
-
merged.errorStack = complete.attributes.errorStack;
|
|
2551
|
+
function finitePositiveInt(value) {
|
|
2552
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
2553
|
+
return void 0;
|
|
2493
2554
|
}
|
|
2494
|
-
return
|
|
2555
|
+
return Math.trunc(value);
|
|
2495
2556
|
}
|
|
2496
|
-
function
|
|
2497
|
-
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
...complete.error !== void 0 ? { error: { ...complete.error } } : {},
|
|
2505
|
-
...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
|
|
2506
|
-
...attributes !== void 0 ? { attributes } : {}
|
|
2507
|
-
};
|
|
2508
|
-
return {
|
|
2509
|
-
...paired,
|
|
2510
|
-
sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
|
|
2511
|
-
projection: {
|
|
2512
|
-
paired: true,
|
|
2513
|
-
absorbedEventIds: Object.freeze([complete.eventId]),
|
|
2514
|
-
parentNormalized: false,
|
|
2515
|
-
...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
|
|
2557
|
+
function extractSessionWorkflowMetadata(record) {
|
|
2558
|
+
if (!record) return void 0;
|
|
2559
|
+
const out = {};
|
|
2560
|
+
let found = false;
|
|
2561
|
+
const assignString = (key, value) => {
|
|
2562
|
+
if (isNonEmptyString3(value)) {
|
|
2563
|
+
out[key] = value.trim();
|
|
2564
|
+
found = true;
|
|
2516
2565
|
}
|
|
2517
2566
|
};
|
|
2567
|
+
assignString("sessionId", record.sessionId);
|
|
2568
|
+
assignString("conversationId", record.conversationId);
|
|
2569
|
+
assignString("groupId", record.groupId);
|
|
2570
|
+
assignString("parentGroupId", record.parentGroupId);
|
|
2571
|
+
assignString("retryOf", record.retryOf);
|
|
2572
|
+
assignString("retryReason", record.retryReason);
|
|
2573
|
+
assignString("handoffFrom", record.handoffFrom);
|
|
2574
|
+
assignString("handoffTo", record.handoffTo);
|
|
2575
|
+
assignString("subAgentId", record.subAgentId);
|
|
2576
|
+
assignString("subAgentName", record.subAgentName);
|
|
2577
|
+
assignString("jobId", record.jobId);
|
|
2578
|
+
assignString("queueName", record.queueName);
|
|
2579
|
+
assignString("workflowName", record.workflowName);
|
|
2580
|
+
assignString("workflowStep", record.workflowStep);
|
|
2581
|
+
assignString("toolCallId", record.toolCallId);
|
|
2582
|
+
assignString("mcpToolCallId", record.mcpToolCallId);
|
|
2583
|
+
assignString("linkedStepId", record.linkedStepId);
|
|
2584
|
+
assignString("operationId", record.operationId);
|
|
2585
|
+
assignString("attemptId", record.attemptId);
|
|
2586
|
+
assignString("fallbackOf", record.fallbackOf);
|
|
2587
|
+
assignString("idempotencyKey", record.idempotencyKey);
|
|
2588
|
+
assignString("correlationId", record.correlationId);
|
|
2589
|
+
assignString("requestId", record.requestId);
|
|
2590
|
+
assignString("decisionId", record.decisionId);
|
|
2591
|
+
const attempt = finitePositiveInt(record.attempt);
|
|
2592
|
+
if (attempt !== void 0) {
|
|
2593
|
+
out.attempt = attempt;
|
|
2594
|
+
found = true;
|
|
2595
|
+
}
|
|
2596
|
+
const attemptNumber = finitePositiveInt(record.attemptNumber);
|
|
2597
|
+
if (attemptNumber !== void 0) {
|
|
2598
|
+
out.attemptNumber = attemptNumber;
|
|
2599
|
+
found = true;
|
|
2600
|
+
}
|
|
2601
|
+
return found ? out : void 0;
|
|
2518
2602
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
...{}
|
|
2528
|
-
}
|
|
2529
|
-
};
|
|
2603
|
+
|
|
2604
|
+
// packages/core/src/bundle/safety-status.ts
|
|
2605
|
+
function aggregateBundleSafeStatus(statuses) {
|
|
2606
|
+
if (statuses.length === 0) return "UNKNOWN";
|
|
2607
|
+
if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
|
|
2608
|
+
if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
|
|
2609
|
+
if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
|
|
2610
|
+
return "SAFE";
|
|
2530
2611
|
}
|
|
2531
|
-
function
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
const
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
code: "AI_LOGICAL_PAIR_AMBIGUOUS",
|
|
2570
|
-
message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
|
|
2571
|
-
eventIds: candidates.map((c) => c.eventId)
|
|
2572
|
-
});
|
|
2573
|
-
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
2574
|
-
}
|
|
2575
|
-
match = candidates[0];
|
|
2576
|
-
} else if (stepId) {
|
|
2577
|
-
const candidates = completes.filter(
|
|
2578
|
-
(c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
|
|
2579
|
-
);
|
|
2580
|
-
if (candidates.length > 1) {
|
|
2581
|
-
diagnostics.push({
|
|
2582
|
-
code: "AI_LOGICAL_PAIR_AMBIGUOUS",
|
|
2583
|
-
message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
|
|
2584
|
-
eventIds: candidates.map((c) => c.eventId)
|
|
2585
|
-
});
|
|
2586
|
-
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
2587
|
-
}
|
|
2588
|
-
match = candidates[0];
|
|
2589
|
-
}
|
|
2590
|
-
if (match) {
|
|
2591
|
-
usedCompletes.add(match.eventId);
|
|
2592
|
-
absorbedIds.add(match.eventId);
|
|
2593
|
-
const paired = pairStartComplete(start, match);
|
|
2594
|
-
logical.push(paired);
|
|
2595
|
-
logicalByRawId.set(start.eventId, paired);
|
|
2596
|
-
logicalByRawId.set(match.eventId, paired);
|
|
2597
|
-
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
|
|
2598
|
-
} else {
|
|
2599
|
-
diagnostics.push({
|
|
2600
|
-
code: "AI_LOGICAL_PAIR_UNMATCHED_START",
|
|
2601
|
-
message: `No matching complete for start ${start.eventId}.`,
|
|
2602
|
-
eventIds: [start.eventId]
|
|
2603
|
-
});
|
|
2604
|
-
const alone = asLogical(start);
|
|
2605
|
-
logical.push(alone);
|
|
2606
|
-
logicalByRawId.set(start.eventId, alone);
|
|
2607
|
-
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
for (const complete of completes) {
|
|
2611
|
-
if (usedCompletes.has(complete.eventId)) continue;
|
|
2612
|
-
diagnostics.push({
|
|
2613
|
-
code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
|
|
2614
|
-
message: `No matching start for complete ${complete.eventId}.`,
|
|
2615
|
-
eventIds: [complete.eventId]
|
|
2616
|
-
});
|
|
2617
|
-
const alone = asLogical(complete);
|
|
2618
|
-
logical.push(alone);
|
|
2619
|
-
logicalByRawId.set(complete.eventId, alone);
|
|
2620
|
-
const stepId = stepIdOf(complete);
|
|
2621
|
-
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
2622
|
-
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
for (const event of others) {
|
|
2626
|
-
const alone = asLogical(event);
|
|
2627
|
-
logical.push(alone);
|
|
2628
|
-
logicalByRawId.set(event.eventId, alone);
|
|
2629
|
-
const stepId = stepIdOf(event);
|
|
2630
|
-
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
2631
|
-
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2612
|
+
function toMetadataSafeStatus(status) {
|
|
2613
|
+
if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
|
|
2614
|
+
return status;
|
|
2615
|
+
}
|
|
2616
|
+
function bundleFailsOnSafety(status, allowUnsafe) {
|
|
2617
|
+
return status === "UNSAFE" || status === "UNKNOWN";
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
// packages/core/src/bundle/manifest.ts
|
|
2621
|
+
var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
|
|
2622
|
+
function buildBundleMetadata(parts) {
|
|
2623
|
+
const aggregate = aggregateBundleSafeStatus(
|
|
2624
|
+
parts.checks.runs.map((run) => run.status)
|
|
2625
|
+
);
|
|
2626
|
+
return {
|
|
2627
|
+
createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2628
|
+
agentInspectVersion: parts.agentInspectVersion,
|
|
2629
|
+
redactionProfile: parts.profile,
|
|
2630
|
+
sourceTraceCount: parts.resolve.runIds.length,
|
|
2631
|
+
runIds: [...parts.resolve.runIds],
|
|
2632
|
+
safeStatus: toMetadataSafeStatus(aggregate),
|
|
2633
|
+
files: [...parts.files].sort((a, b) => a.localeCompare(b)),
|
|
2634
|
+
note: BUNDLE_NOTE,
|
|
2635
|
+
...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
|
|
2636
|
+
...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// packages/core/src/evidence/types.ts
|
|
2641
|
+
var EVIDENCE_FORMAT_VERSION = "1.0";
|
|
2642
|
+
var EVIDENCE_ASSESSMENT_NOTE = "Best-effort local safety verification only; not a compliance certification.";
|
|
2643
|
+
var EVIDENCE_MANIFEST_FILENAME = "evidence.json";
|
|
2644
|
+
function sha256Hex(data) {
|
|
2645
|
+
const hash = crypto.createHash("sha256");
|
|
2646
|
+
if (typeof data === "string") {
|
|
2647
|
+
hash.update(data, "utf8");
|
|
2648
|
+
} else {
|
|
2649
|
+
hash.update(data);
|
|
2634
2650
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
nextParent = viaStep;
|
|
2653
|
-
remapped = true;
|
|
2654
|
-
}
|
|
2651
|
+
return hash.digest("hex");
|
|
2652
|
+
}
|
|
2653
|
+
function assertEvidenceRelativePath(relativePath) {
|
|
2654
|
+
if (typeof relativePath !== "string" || relativePath.trim() === "") {
|
|
2655
|
+
throw new Error("Evidence file path must be a non-empty relative path.");
|
|
2656
|
+
}
|
|
2657
|
+
const trimmed = relativePath.trim().replaceAll("\\", "/");
|
|
2658
|
+
if (path14__default.default.isAbsolute(trimmed) || trimmed.startsWith("/")) {
|
|
2659
|
+
throw new Error(`Evidence file path must be relative: ${relativePath}`);
|
|
2660
|
+
}
|
|
2661
|
+
const parts = trimmed.split("/").filter((part) => part !== "");
|
|
2662
|
+
if (parts.length === 0) {
|
|
2663
|
+
throw new Error(`Evidence file path must be relative: ${relativePath}`);
|
|
2664
|
+
}
|
|
2665
|
+
for (const part of parts) {
|
|
2666
|
+
if (part === "." || part === "..") {
|
|
2667
|
+
throw new Error(`Evidence file path must not contain "." or "..": ${relativePath}`);
|
|
2655
2668
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2669
|
+
}
|
|
2670
|
+
return parts.join("/");
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// packages/core/src/evidence/manifest.ts
|
|
2674
|
+
function stable(value) {
|
|
2675
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
2676
|
+
if (value === null || typeof value !== "object") return value;
|
|
2677
|
+
const record = value;
|
|
2678
|
+
return Object.fromEntries(
|
|
2679
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
function serializeEvidenceManifest(manifest) {
|
|
2683
|
+
return `${JSON.stringify(stable(manifest), null, 2)}
|
|
2684
|
+
`;
|
|
2685
|
+
}
|
|
2686
|
+
function inferEvidenceFileRole(relativePath) {
|
|
2687
|
+
const normalized = assertEvidenceRelativePath(relativePath);
|
|
2688
|
+
const base = normalized.includes("/") ? normalized.slice(normalized.lastIndexOf("/") + 1) : normalized;
|
|
2689
|
+
if (base === "evidence.html" || base === "trace.html" || base.endsWith(".html")) {
|
|
2690
|
+
return "report";
|
|
2691
|
+
}
|
|
2692
|
+
if (base === "trace.jsonl" || base.endsWith(".jsonl")) {
|
|
2693
|
+
return "redacted-trace";
|
|
2694
|
+
}
|
|
2695
|
+
if (base === "check-results.json") {
|
|
2696
|
+
return "checks";
|
|
2697
|
+
}
|
|
2698
|
+
if (base === "contract.resolved.json") {
|
|
2699
|
+
return "contract";
|
|
2700
|
+
}
|
|
2701
|
+
if (base === "redaction-report.json") {
|
|
2702
|
+
return "redaction-report";
|
|
2703
|
+
}
|
|
2704
|
+
if (base === "summary.md") {
|
|
2705
|
+
return "summary";
|
|
2706
|
+
}
|
|
2707
|
+
return "other";
|
|
2708
|
+
}
|
|
2709
|
+
function buildEvidenceFileEntries(files) {
|
|
2710
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
2711
|
+
for (const file of files) {
|
|
2712
|
+
const relativePath = assertEvidenceRelativePath(file.path);
|
|
2713
|
+
if (relativePath === EVIDENCE_MANIFEST_FILENAME) {
|
|
2714
|
+
throw new Error(
|
|
2715
|
+
`Do not include ${EVIDENCE_MANIFEST_FILENAME} in packaged file hashes (self-hash is undefined).`
|
|
2716
|
+
);
|
|
2691
2717
|
}
|
|
2692
|
-
if (
|
|
2693
|
-
|
|
2694
|
-
code: "AI_LOGICAL_SELF_PARENT_REMOVED",
|
|
2695
|
-
message: formatProgrammaticDiagnostic(
|
|
2696
|
-
"AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
2697
|
-
`Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
|
|
2698
|
-
),
|
|
2699
|
-
eventIds: [event.eventId]
|
|
2700
|
-
});
|
|
2701
|
-
const { parentId: _drop, ...rest } = event;
|
|
2702
|
-
normalized.push({
|
|
2703
|
-
...rest,
|
|
2704
|
-
projection: {
|
|
2705
|
-
...event.projection,
|
|
2706
|
-
parentNormalized: true,
|
|
2707
|
-
originalParentId
|
|
2708
|
-
}
|
|
2709
|
-
});
|
|
2710
|
-
continue;
|
|
2718
|
+
if (byPath.has(relativePath)) {
|
|
2719
|
+
throw new Error(`Duplicate evidence file path: ${relativePath}`);
|
|
2711
2720
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
});
|
|
2717
|
-
normalized.push({
|
|
2718
|
-
...event,
|
|
2719
|
-
parentId: nextParent,
|
|
2720
|
-
projection: {
|
|
2721
|
-
...event.projection,
|
|
2722
|
-
parentNormalized: true,
|
|
2723
|
-
originalParentId
|
|
2724
|
-
}
|
|
2721
|
+
byPath.set(relativePath, {
|
|
2722
|
+
path: relativePath,
|
|
2723
|
+
sha256: sha256Hex(file.content),
|
|
2724
|
+
role: file.role ?? inferEvidenceFileRole(relativePath)
|
|
2725
2725
|
});
|
|
2726
2726
|
}
|
|
2727
|
-
|
|
2728
|
-
normalized.sort((a, b) => {
|
|
2729
|
-
const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
|
|
2730
|
-
const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
|
|
2731
|
-
return ai - bi || a.eventId.localeCompare(b.eventId);
|
|
2732
|
-
});
|
|
2733
|
-
return {
|
|
2734
|
-
logicalEvents: Object.freeze(normalized),
|
|
2735
|
-
diagnostics: Object.freeze(diagnostics)
|
|
2736
|
-
};
|
|
2727
|
+
return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
2737
2728
|
}
|
|
2738
|
-
function
|
|
2739
|
-
const
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
const metadata = attrs2?.metadata;
|
|
2743
|
-
if (isRecord6(metadata)) {
|
|
2744
|
-
const nested = pickString(metadata, ["toolName", "tool"]);
|
|
2745
|
-
if (nested) return nested;
|
|
2729
|
+
function buildEvidenceManifest(parts) {
|
|
2730
|
+
const runIds = [...parts.runIds];
|
|
2731
|
+
if (runIds.length === 0) {
|
|
2732
|
+
throw new Error("Evidence manifest requires at least one run id.");
|
|
2746
2733
|
}
|
|
2747
|
-
for (const
|
|
2748
|
-
if (
|
|
2734
|
+
for (const item of parts.sourceHashes) {
|
|
2735
|
+
if (!runIds.includes(item.runId)) {
|
|
2736
|
+
throw new Error(`sourceHashes runId "${item.runId}" is not listed in source.runIds.`);
|
|
2737
|
+
}
|
|
2738
|
+
if (item.algorithm !== "sha256") {
|
|
2739
|
+
throw new Error(`Unsupported source hash algorithm: ${item.algorithm}`);
|
|
2740
|
+
}
|
|
2749
2741
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
if (typeof value === "string" && value.trim() !== "") return value.trim();
|
|
2742
|
+
const assessment = {
|
|
2743
|
+
status: parts.assessmentStatus,
|
|
2744
|
+
note: parts.note ?? EVIDENCE_ASSESSMENT_NOTE
|
|
2745
|
+
};
|
|
2746
|
+
if (parts.sourceStatus !== void 0) {
|
|
2747
|
+
assessment.sourceStatus = parts.sourceStatus;
|
|
2757
2748
|
}
|
|
2758
|
-
return
|
|
2749
|
+
return {
|
|
2750
|
+
evidenceFormatVersion: EVIDENCE_FORMAT_VERSION,
|
|
2751
|
+
generator: {
|
|
2752
|
+
name: parts.generatorName ?? "agent-inspect",
|
|
2753
|
+
version: parts.generatorVersion
|
|
2754
|
+
},
|
|
2755
|
+
createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2756
|
+
source: {
|
|
2757
|
+
runIds,
|
|
2758
|
+
traceSchemaVersions: [...parts.traceSchemaVersions].sort((a, b) => a.localeCompare(b)),
|
|
2759
|
+
sourceHashes: [...parts.sourceHashes].sort((a, b) => a.runId.localeCompare(b.runId))
|
|
2760
|
+
},
|
|
2761
|
+
policy: {
|
|
2762
|
+
redactionProfile: parts.redactionProfile,
|
|
2763
|
+
verificationPolicy: parts.verificationPolicy ?? parts.redactionProfile
|
|
2764
|
+
},
|
|
2765
|
+
assessment,
|
|
2766
|
+
...parts.semantics !== void 0 ? { semantics: { ...parts.semantics } } : {},
|
|
2767
|
+
...parts.contract !== void 0 ? { contract: { ...parts.contract } } : {},
|
|
2768
|
+
...parts.inputs !== void 0 ? { inputs: { ...parts.inputs } } : {},
|
|
2769
|
+
files: buildEvidenceFileEntries(parts.files)
|
|
2770
|
+
};
|
|
2759
2771
|
}
|
|
2760
2772
|
|
|
2761
2773
|
// packages/core/src/checks/derived-failure.ts
|
|
@@ -3429,7 +3441,11 @@ function emptySummary() {
|
|
|
3429
3441
|
failed: 0,
|
|
3430
3442
|
warnings: 0,
|
|
3431
3443
|
errors: 0,
|
|
3432
|
-
rulesEvaluated: 0
|
|
3444
|
+
rulesEvaluated: 0,
|
|
3445
|
+
rulesPassed: 0,
|
|
3446
|
+
rulesWarning: 0,
|
|
3447
|
+
rulesFailed: 0,
|
|
3448
|
+
rulesError: 0
|
|
3433
3449
|
};
|
|
3434
3450
|
}
|
|
3435
3451
|
function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
|
|
@@ -3441,7 +3457,11 @@ function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
|
|
|
3441
3457
|
summary: {
|
|
3442
3458
|
...emptySummary(),
|
|
3443
3459
|
errors: diagnostics.filter((item) => item.severity === "error").length,
|
|
3444
|
-
rulesEvaluated: ruleExecutions.length
|
|
3460
|
+
rulesEvaluated: ruleExecutions.length,
|
|
3461
|
+
rulesPassed: ruleExecutions.filter((item) => item.status === "pass").length,
|
|
3462
|
+
rulesWarning: ruleExecutions.filter((item) => item.status === "warning").length,
|
|
3463
|
+
rulesFailed: ruleExecutions.filter((item) => item.status === "fail").length,
|
|
3464
|
+
rulesError: ruleExecutions.filter((item) => item.status === "error").length
|
|
3445
3465
|
},
|
|
3446
3466
|
findings: [],
|
|
3447
3467
|
diagnostics: [...diagnostics],
|
|
@@ -3601,7 +3621,7 @@ function normalizeFinding(rule, finding) {
|
|
|
3601
3621
|
...finding.action !== void 0 ? { action: finding.action } : {}
|
|
3602
3622
|
};
|
|
3603
3623
|
}
|
|
3604
|
-
function summarize(findings, diagnostics,
|
|
3624
|
+
function summarize(findings, diagnostics, ruleExecutions) {
|
|
3605
3625
|
return {
|
|
3606
3626
|
passed: findings.filter((finding) => finding.status === "pass").length,
|
|
3607
3627
|
failed: findings.filter(
|
|
@@ -3611,7 +3631,11 @@ function summarize(findings, diagnostics, rulesEvaluated) {
|
|
|
3611
3631
|
(finding) => finding.status === "warning" || finding.severity === "warning"
|
|
3612
3632
|
).length,
|
|
3613
3633
|
errors: diagnostics.filter((item) => item.severity === "error").length,
|
|
3614
|
-
rulesEvaluated
|
|
3634
|
+
rulesEvaluated: ruleExecutions.length,
|
|
3635
|
+
rulesPassed: ruleExecutions.filter((item) => item.status === "pass").length,
|
|
3636
|
+
rulesWarning: ruleExecutions.filter((item) => item.status === "warning").length,
|
|
3637
|
+
rulesFailed: ruleExecutions.filter((item) => item.status === "fail").length,
|
|
3638
|
+
rulesError: ruleExecutions.filter((item) => item.status === "error").length
|
|
3615
3639
|
};
|
|
3616
3640
|
}
|
|
3617
3641
|
function classifyRuleExecution(findings, threw) {
|
|
@@ -4052,7 +4076,7 @@ function runTraceChecks(input, options = {}) {
|
|
|
4052
4076
|
}
|
|
4053
4077
|
const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
|
|
4054
4078
|
const sortedFindings = findings.sort(compareFindings(eventById));
|
|
4055
|
-
const summary = summarize(sortedFindings, diagnostics, ruleExecutions
|
|
4079
|
+
const summary = summarize(sortedFindings, diagnostics, ruleExecutions);
|
|
4056
4080
|
const status = summary.failed > 0 ? "fail" : "pass";
|
|
4057
4081
|
return {
|
|
4058
4082
|
ok: status === "pass",
|
|
@@ -8195,6 +8219,15 @@ function actionForRule(rule) {
|
|
|
8195
8219
|
if (rule.strategy === "full") return "replace";
|
|
8196
8220
|
return rule.strategy;
|
|
8197
8221
|
}
|
|
8222
|
+
function isCompleteRedactionMarker(value, replacement = "[REDACTED]") {
|
|
8223
|
+
if (typeof value !== "string") return false;
|
|
8224
|
+
const trimmed = value.trim();
|
|
8225
|
+
if (trimmed.length === 0) return false;
|
|
8226
|
+
if (trimmed === replacement) return true;
|
|
8227
|
+
if (/^\[HASH:[A-Za-z0-9_-]+\]$/.test(trimmed)) return true;
|
|
8228
|
+
if (/^\[REDACTED:[^\]]*\]$/.test(trimmed)) return true;
|
|
8229
|
+
return false;
|
|
8230
|
+
}
|
|
8198
8231
|
function applyRule(rule, value, replacement) {
|
|
8199
8232
|
if (rule.strategy === "full") return replacement;
|
|
8200
8233
|
const asString = stringifyScalar(value);
|
|
@@ -8309,6 +8342,9 @@ var Redactor2 = class {
|
|
|
8309
8342
|
if (key !== void 0) {
|
|
8310
8343
|
const rule = findCompiledKeyRule(key, this.#rules);
|
|
8311
8344
|
if (rule) {
|
|
8345
|
+
if (isCompleteRedactionMarker(value, this.#replacement)) {
|
|
8346
|
+
return value;
|
|
8347
|
+
}
|
|
8312
8348
|
this.#recordFinding(
|
|
8313
8349
|
state,
|
|
8314
8350
|
makeFinding(path16, `key.${rule.key}`, actionForRule(rule), "key", "warning")
|