@compaction/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +663 -0
- package/README.md +319 -0
- package/dist/cli/commands/adapter.d.ts +2 -0
- package/dist/cli/commands/adapter.js +49 -0
- package/dist/cli/commands/aggregate.d.ts +2 -0
- package/dist/cli/commands/aggregate.js +60 -0
- package/dist/cli/commands/analyze.d.ts +2 -0
- package/dist/cli/commands/analyze.js +55 -0
- package/dist/cli/commands/apply-context.d.ts +12 -0
- package/dist/cli/commands/apply-context.js +80 -0
- package/dist/cli/commands/apply.d.ts +2 -0
- package/dist/cli/commands/apply.js +65 -0
- package/dist/cli/commands/approve.d.ts +2 -0
- package/dist/cli/commands/approve.js +55 -0
- package/dist/cli/commands/audit.d.ts +2 -0
- package/dist/cli/commands/audit.js +63 -0
- package/dist/cli/commands/billing-delta.d.ts +2 -0
- package/dist/cli/commands/billing-delta.js +246 -0
- package/dist/cli/commands/capture-claude-code.d.ts +19 -0
- package/dist/cli/commands/capture-claude-code.js +198 -0
- package/dist/cli/commands/capture-provider-usage.d.ts +35 -0
- package/dist/cli/commands/capture-provider-usage.js +113 -0
- package/dist/cli/commands/capture.d.ts +2 -0
- package/dist/cli/commands/capture.js +76 -0
- package/dist/cli/commands/compact.d.ts +2 -0
- package/dist/cli/commands/compact.js +206 -0
- package/dist/cli/commands/eval.d.ts +2 -0
- package/dist/cli/commands/eval.js +66 -0
- package/dist/cli/commands/export-session-seed.d.ts +11 -0
- package/dist/cli/commands/export-session-seed.js +42 -0
- package/dist/cli/commands/feedback.d.ts +2 -0
- package/dist/cli/commands/feedback.js +233 -0
- package/dist/cli/commands/import.d.ts +2 -0
- package/dist/cli/commands/import.js +60 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +216 -0
- package/dist/cli/commands/integrations.d.ts +2 -0
- package/dist/cli/commands/integrations.js +29 -0
- package/dist/cli/commands/launch-treatment-session.d.ts +14 -0
- package/dist/cli/commands/launch-treatment-session.js +50 -0
- package/dist/cli/commands/optimize.d.ts +2 -0
- package/dist/cli/commands/optimize.js +33 -0
- package/dist/cli/commands/recommend.d.ts +4 -0
- package/dist/cli/commands/recommend.js +66 -0
- package/dist/cli/commands/review.d.ts +2 -0
- package/dist/cli/commands/review.js +60 -0
- package/dist/cli/commands/run.d.ts +2 -0
- package/dist/cli/commands/run.js +49 -0
- package/dist/cli/commands/spend.d.ts +2 -0
- package/dist/cli/commands/spend.js +24 -0
- package/dist/cli/commands/summary.d.ts +2 -0
- package/dist/cli/commands/summary.js +19 -0
- package/dist/cli/engine-degrade.d.ts +40 -0
- package/dist/cli/engine-degrade.js +59 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +59 -0
- package/dist/core/adapter-validation.d.ts +32 -0
- package/dist/core/adapter-validation.js +185 -0
- package/dist/core/adapters/claude-code-adapter.d.ts +51 -0
- package/dist/core/adapters/claude-code-adapter.js +539 -0
- package/dist/core/adapters/claude-code-discovery.d.ts +40 -0
- package/dist/core/adapters/claude-code-discovery.js +100 -0
- package/dist/core/aggregate-format.d.ts +62 -0
- package/dist/core/aggregate-format.js +349 -0
- package/dist/core/api-client/client.d.ts +43 -0
- package/dist/core/api-client/client.js +143 -0
- package/dist/core/api-client/config.d.ts +36 -0
- package/dist/core/api-client/config.js +44 -0
- package/dist/core/api-client/index.d.ts +13 -0
- package/dist/core/api-client/index.js +14 -0
- package/dist/core/api-client/payload.d.ts +93 -0
- package/dist/core/api-client/payload.js +180 -0
- package/dist/core/api-client/types.d.ts +95 -0
- package/dist/core/api-client/types.js +21 -0
- package/dist/core/artifact-writer.d.ts +2 -0
- package/dist/core/artifact-writer.js +14 -0
- package/dist/core/audit-report.d.ts +91 -0
- package/dist/core/audit-report.js +422 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +290 -0
- package/dist/core/billing-delta/billing-delta-record.js +394 -0
- package/dist/core/capture-adapter.d.ts +74 -0
- package/dist/core/capture-adapter.js +2 -0
- package/dist/core/command-runner.d.ts +29 -0
- package/dist/core/command-runner.js +96 -0
- package/dist/core/content-hash.d.ts +1 -0
- package/dist/core/content-hash.js +5 -0
- package/dist/core/cost-calculator.d.ts +6 -0
- package/dist/core/cost-calculator.js +20 -0
- package/dist/core/feedback-bundle.d.ts +159 -0
- package/dist/core/feedback-bundle.js +406 -0
- package/dist/core/openai-agents-capture.d.ts +66 -0
- package/dist/core/openai-agents-capture.js +475 -0
- package/dist/core/policy-types.d.ts +85 -0
- package/dist/core/policy-types.js +29 -0
- package/dist/core/pricing.d.ts +23 -0
- package/dist/core/pricing.js +55 -0
- package/dist/core/provider-usage/credential-redaction.d.ts +23 -0
- package/dist/core/provider-usage/credential-redaction.js +60 -0
- package/dist/core/provider-usage/provider-usage-adapter.d.ts +65 -0
- package/dist/core/provider-usage/provider-usage-adapter.js +147 -0
- package/dist/core/provider-usage/provider-usage-client.d.ts +89 -0
- package/dist/core/provider-usage/provider-usage-client.js +96 -0
- package/dist/core/report-generator.d.ts +7 -0
- package/dist/core/report-generator.js +275 -0
- package/dist/core/run-aggregator.d.ts +50 -0
- package/dist/core/run-aggregator.js +251 -0
- package/dist/core/run-labels.d.ts +52 -0
- package/dist/core/run-labels.js +80 -0
- package/dist/core/run-trace-converter.d.ts +3 -0
- package/dist/core/run-trace-converter.js +94 -0
- package/dist/core/safety-report.d.ts +52 -0
- package/dist/core/safety-report.js +440 -0
- package/dist/core/savings.d.ts +22 -0
- package/dist/core/savings.js +36 -0
- package/dist/core/session-aggregate.d.ts +211 -0
- package/dist/core/session-aggregate.js +490 -0
- package/dist/core/skill-injection-detector.d.ts +51 -0
- package/dist/core/skill-injection-detector.js +146 -0
- package/dist/core/spend-attribution.d.ts +169 -0
- package/dist/core/spend-attribution.js +611 -0
- package/dist/core/standalone-capsule-provenance.d.ts +4 -0
- package/dist/core/standalone-capsule-provenance.js +62 -0
- package/dist/core/state-capsule.d.ts +3 -0
- package/dist/core/state-capsule.js +147 -0
- package/dist/core/token-accounting.d.ts +62 -0
- package/dist/core/token-accounting.js +81 -0
- package/dist/core/token-estimator.d.ts +3 -0
- package/dist/core/token-estimator.js +18 -0
- package/dist/core/trace-adapters.d.ts +53 -0
- package/dist/core/trace-adapters.js +714 -0
- package/dist/core/trace-fingerprint.d.ts +79 -0
- package/dist/core/trace-fingerprint.js +102 -0
- package/dist/core/trace-intake.d.ts +41 -0
- package/dist/core/trace-intake.js +164 -0
- package/dist/core/trace-parser.d.ts +203 -0
- package/dist/core/trace-parser.js +143 -0
- package/dist/core/types.d.ts +238 -0
- package/dist/core/types.js +2 -0
- package/dist/core/usage-metadata.d.ts +51 -0
- package/dist/core/usage-metadata.js +122 -0
- package/dist/core/waste-detector.d.ts +8 -0
- package/dist/core/waste-detector.js +58 -0
- package/package.json +90 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { contentHash } from "./content-hash.js";
|
|
2
|
+
import { SKILL_INJECTION_POLICY_NAME, SUPPORTED_COMPACTION_POLICY_NAMES, isSupportedCompactionPolicyName } from "./policy-types.js";
|
|
3
|
+
function readCompactionMetadata(message) {
|
|
4
|
+
const metadata = message.metadata?.compaction;
|
|
5
|
+
if (!metadata) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(metadata.replacements) && metadata.replacements.length > 0) {
|
|
9
|
+
return metadata.replacements;
|
|
10
|
+
}
|
|
11
|
+
return [metadata];
|
|
12
|
+
}
|
|
13
|
+
function isSourcePointer(value) {
|
|
14
|
+
return (typeof value?.traceId === "string" &&
|
|
15
|
+
typeof value.messageId === "string" &&
|
|
16
|
+
typeof value.messageIndex === "number" &&
|
|
17
|
+
typeof value.contentSha256 === "string" &&
|
|
18
|
+
value.contentSha256.length > 0);
|
|
19
|
+
}
|
|
20
|
+
function findOriginalMessage(trace, pointer) {
|
|
21
|
+
const messageAtIndex = trace.messages[pointer.messageIndex];
|
|
22
|
+
if (messageAtIndex?.id === pointer.messageId) {
|
|
23
|
+
return messageAtIndex;
|
|
24
|
+
}
|
|
25
|
+
return trace.messages.find((message) => message.id === pointer.messageId);
|
|
26
|
+
}
|
|
27
|
+
function collectMetadataForCompactedMessages(compactedMessages, compactedMessageIds) {
|
|
28
|
+
const compactedIds = new Set(compactedMessageIds);
|
|
29
|
+
return compactedMessages.flatMap((message) => readCompactionMetadata(message).filter((metadata) => compactedIds.has(metadata.compactedMessageId ?? message.id)));
|
|
30
|
+
}
|
|
31
|
+
function addCheck(checks, id, label, status, required, details) {
|
|
32
|
+
checks.push({ id, label, status, required, details });
|
|
33
|
+
}
|
|
34
|
+
function statusFromChecks(checks) {
|
|
35
|
+
if (checks.some((check) => check.required && check.status === "fail")) {
|
|
36
|
+
return "fail";
|
|
37
|
+
}
|
|
38
|
+
if (checks.some((check) => check.status === "warn" || check.status === "fail")) {
|
|
39
|
+
return "warn";
|
|
40
|
+
}
|
|
41
|
+
return "pass";
|
|
42
|
+
}
|
|
43
|
+
function summarizeIssues(checks, status) {
|
|
44
|
+
return checks.filter((check) => check.status === status).map((check) => `${check.label}: ${check.details}`);
|
|
45
|
+
}
|
|
46
|
+
function riskLevelForStatus(status) {
|
|
47
|
+
if (status === "pass") {
|
|
48
|
+
return "low";
|
|
49
|
+
}
|
|
50
|
+
if (status === "warn") {
|
|
51
|
+
return "medium";
|
|
52
|
+
}
|
|
53
|
+
return "high";
|
|
54
|
+
}
|
|
55
|
+
function recommendationForStatus(status, approvalReadiness) {
|
|
56
|
+
if (status === "pass") {
|
|
57
|
+
if (approvalReadiness === "conditional") {
|
|
58
|
+
return "Safety checks passed. This compaction is suitable for local inspection with the recorded source pointers and state capsules. Evidence scope is limited (demo or fixture traces, no independent reviewer) — requires real captured traces and independent review before broader approval.";
|
|
59
|
+
}
|
|
60
|
+
if (approvalReadiness === "ready") {
|
|
61
|
+
return "Safety checks passed with real captured traces, an independent reviewer, and a complete review summary. This compaction is ready for approval.";
|
|
62
|
+
}
|
|
63
|
+
// approvalReadiness === "not_ready" should not occur when status === "pass", but handle defensively
|
|
64
|
+
return "Safety checks passed. Review approval readiness status before applying this compaction.";
|
|
65
|
+
}
|
|
66
|
+
if (status === "warn") {
|
|
67
|
+
return "Review missing non-critical metadata before recommending this compaction beyond local inspection.";
|
|
68
|
+
}
|
|
69
|
+
return "Do not recommend this compaction until the failed safety checks are fixed.";
|
|
70
|
+
}
|
|
71
|
+
export function evidenceSourceTypeFromTrace(source) {
|
|
72
|
+
if (source === "demo") {
|
|
73
|
+
return "demo";
|
|
74
|
+
}
|
|
75
|
+
if (source === "manual") {
|
|
76
|
+
return "fixture";
|
|
77
|
+
}
|
|
78
|
+
// `codex_import` is a real, user-supplied local `codex exec --json` JSONL export
|
|
79
|
+
// brought in through the import/adapter path — genuine local input, stronger than a
|
|
80
|
+
// hand-authored `manual` fixture, but NOT captured by this tool under controlled
|
|
81
|
+
// conditions. It therefore maps to `imported_local`, which is strictly BELOW
|
|
82
|
+
// `real_captured` and can never unlock the "ready" approval rung (only
|
|
83
|
+
// "real_captured" does), so a passing report on a codex_import trace caps at
|
|
84
|
+
// "conditional". This mirrors the provider_usage classification below
|
|
85
|
+
// (docs/design/provider-api-tracesource-classification.md, Option 2).
|
|
86
|
+
if (source === "cli_wrapper" || source === "local_command" || source === "codex_import") {
|
|
87
|
+
return "imported_local";
|
|
88
|
+
}
|
|
89
|
+
if (source === "real_captured") {
|
|
90
|
+
return "real_captured";
|
|
91
|
+
}
|
|
92
|
+
// A read-only provider usage/cost read is cost/spend-attribution evidence, NOT
|
|
93
|
+
// compaction-safety evidence. It maps to a dedicated evidence type that, by
|
|
94
|
+
// construction, can NEVER unlock the "ready" approval-readiness rung (only
|
|
95
|
+
// "real_captured" does), so a passing safety report on such a record caps at
|
|
96
|
+
// "conditional". See docs/design/provider-api-tracesource-classification.md (Option 2).
|
|
97
|
+
if (source === "provider_usage") {
|
|
98
|
+
return "provider_reported_usage";
|
|
99
|
+
}
|
|
100
|
+
return "unknown";
|
|
101
|
+
}
|
|
102
|
+
export function approvalReadinessStatusFromReport(status, evidenceSourceType, reviewerType, reviewSummaryPresent) {
|
|
103
|
+
if (status === "fail" || status === "warn") {
|
|
104
|
+
return "not_ready";
|
|
105
|
+
}
|
|
106
|
+
// status is "pass" — check evidence quality
|
|
107
|
+
const hasRealCapture = evidenceSourceType === "real_captured";
|
|
108
|
+
const hasIndependentReviewer = reviewerType === "independent" || reviewerType === "human";
|
|
109
|
+
const hasReviewSummary = reviewSummaryPresent;
|
|
110
|
+
if (hasRealCapture && hasIndependentReviewer && hasReviewSummary) {
|
|
111
|
+
return "ready";
|
|
112
|
+
}
|
|
113
|
+
return "conditional";
|
|
114
|
+
}
|
|
115
|
+
export function approvalReadinessReasonFromReport(status, approvalStatus, evidenceSourceType, reviewerType, reviewSummaryPresent) {
|
|
116
|
+
if (approvalStatus === "not_ready") {
|
|
117
|
+
if (status === "fail") {
|
|
118
|
+
return "Safety checks failed — approval cannot proceed until all required checks pass.";
|
|
119
|
+
}
|
|
120
|
+
return "Safety checks produced warnings — review warnings before considering approval.";
|
|
121
|
+
}
|
|
122
|
+
if (approvalStatus === "ready") {
|
|
123
|
+
return "Safety checks passed with real captured traces, an independent reviewer, and a complete review summary.";
|
|
124
|
+
}
|
|
125
|
+
// conditional
|
|
126
|
+
const reasons = [];
|
|
127
|
+
if (evidenceSourceType !== "real_captured") {
|
|
128
|
+
const label = evidenceSourceType === "demo"
|
|
129
|
+
? "demo"
|
|
130
|
+
: evidenceSourceType === "fixture"
|
|
131
|
+
? "fixture"
|
|
132
|
+
: evidenceSourceType === "imported_local"
|
|
133
|
+
? "imported local"
|
|
134
|
+
: evidenceSourceType === "provider_reported_usage"
|
|
135
|
+
? "provider-reported usage/cost (cost/spend evidence, not compaction-safety evidence)"
|
|
136
|
+
: "unknown";
|
|
137
|
+
const captureHint = evidenceSourceType === "demo" || evidenceSourceType === "fixture"
|
|
138
|
+
? ` — use \`compaction capture claude-code --session <path>\` to capture a real session`
|
|
139
|
+
: "";
|
|
140
|
+
reasons.push(`evidence is ${label} only — requires real captured traces before approval${captureHint}`);
|
|
141
|
+
}
|
|
142
|
+
if (reviewerType !== "independent" && reviewerType !== "human") {
|
|
143
|
+
reasons.push("no independent reviewer assigned");
|
|
144
|
+
}
|
|
145
|
+
if (!reviewSummaryPresent) {
|
|
146
|
+
reasons.push("no review summary generated");
|
|
147
|
+
}
|
|
148
|
+
const reasonText = reasons.length > 0 ? reasons.join("; ") : "evidence quality is insufficient for full approval";
|
|
149
|
+
return `Safety checks passed but ${reasonText}.`;
|
|
150
|
+
}
|
|
151
|
+
/** Trailing-whitespace-only normalization — MUST match skill-injection-policy.ts / detector. */
|
|
152
|
+
function normalizeExactContent(content) {
|
|
153
|
+
return content.replace(/\s+$/, "");
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Compute the 4 NEW skill-injection-specific safety checks (design §Q5). The checks are
|
|
157
|
+
* "applied" (and `required`) whenever the skill-injection policy compacted ≥1 message — detected
|
|
158
|
+
* EITHER from a `repeated_skill_injection_to_state_capsule` state capsule OR from compacted
|
|
159
|
+
* metadata carrying that policy name. The capsule gate is the robust one: it means an adversary
|
|
160
|
+
* cannot silence the checks by stripping the per-message provenance metadata — doing so instead
|
|
161
|
+
* trips the provenance-present check (a missing-provenance compacted message is a failure, not N/A).
|
|
162
|
+
*/
|
|
163
|
+
function computeSkillInjectionChecks(originalTrace, compactedMetadata, stateCapsules) {
|
|
164
|
+
const skillCapsules = stateCapsules.filter((capsule) => capsule.policyName === SKILL_INJECTION_POLICY_NAME);
|
|
165
|
+
const skillMetadata = compactedMetadata.filter((metadata) => metadata.policyName === SKILL_INJECTION_POLICY_NAME);
|
|
166
|
+
if (skillCapsules.length === 0 && skillMetadata.length === 0) {
|
|
167
|
+
const na = { status: "pass", details: "No skill-injection compaction in this run; check not applicable." };
|
|
168
|
+
return {
|
|
169
|
+
applied: false,
|
|
170
|
+
byteIdenticalEquality: na,
|
|
171
|
+
firstOfGroupRetained: na,
|
|
172
|
+
noCrossSkillGroup: na,
|
|
173
|
+
provenancePresent: na
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// The set of compacted skill-injection message ids — from capsules (authoritative) unioned with
|
|
177
|
+
// metadata-declared ids — so a stripped-metadata message is still checked for provenance.
|
|
178
|
+
const compactedSkillMessageIds = new Set([
|
|
179
|
+
...skillCapsules.flatMap((capsule) => capsule.compactedMessageIds),
|
|
180
|
+
...skillMetadata.map((metadata) => metadata.compactedMessageId).filter((id) => typeof id === "string")
|
|
181
|
+
]);
|
|
182
|
+
const provenanceByCompactedId = new Map(skillMetadata
|
|
183
|
+
.filter((metadata) => metadata.skillInjectionProvenance && metadata.compactedMessageId)
|
|
184
|
+
.map((metadata) => [metadata.compactedMessageId, metadata.skillInjectionProvenance]));
|
|
185
|
+
const messageById = new Map(originalTrace.messages.map((message) => [message.id, message]));
|
|
186
|
+
// First-copy pointer per compacted id, from the authoritative capsule set (used when the
|
|
187
|
+
// per-message metadata provenance has been stripped).
|
|
188
|
+
const firstCopyIdByCompactedId = new Map();
|
|
189
|
+
for (const capsule of skillCapsules) {
|
|
190
|
+
for (const compactedId of capsule.compactedMessageIds) {
|
|
191
|
+
firstCopyIdByCompactedId.set(compactedId, capsule.sourcePointer.messageId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const equalityFailures = []; // Check 1
|
|
195
|
+
const provenanceFailures = []; // Check 4
|
|
196
|
+
const firstCopyCompacted = []; // Check 2
|
|
197
|
+
const groupSkillNames = new Map(); // Check 3
|
|
198
|
+
for (const compactedId of compactedSkillMessageIds) {
|
|
199
|
+
const provenance = provenanceByCompactedId.get(compactedId);
|
|
200
|
+
// Check 4: provenance present (owning-agent id + replaced position) for every compacted injection.
|
|
201
|
+
const provenanceComplete = provenance !== undefined &&
|
|
202
|
+
typeof provenance.owningAgentId === "string" &&
|
|
203
|
+
provenance.owningAgentId.length > 0 &&
|
|
204
|
+
typeof provenance.replacedMessagePosition === "number" &&
|
|
205
|
+
provenance.replacedMessagePosition >= 0;
|
|
206
|
+
if (!provenanceComplete) {
|
|
207
|
+
provenanceFailures.push(compactedId);
|
|
208
|
+
}
|
|
209
|
+
// Resolve the first-copy id from metadata provenance, else from the capsule pointer.
|
|
210
|
+
const firstCopyId = provenance?.firstCopyMessageId ?? firstCopyIdByCompactedId.get(compactedId);
|
|
211
|
+
// Check 1: byte-identical (normalized sha256) equality between the compacted later copy and
|
|
212
|
+
// its first-copy source — the machine guarantee of the hard safety boundary.
|
|
213
|
+
const firstCopy = firstCopyId ? messageById.get(firstCopyId) : undefined;
|
|
214
|
+
const replacedCopy = messageById.get(compactedId);
|
|
215
|
+
if (!firstCopy || !replacedCopy) {
|
|
216
|
+
equalityFailures.push(`${compactedId} (missing source in original trace)`);
|
|
217
|
+
}
|
|
218
|
+
else if (contentHash(normalizeExactContent(firstCopy.content)) !== contentHash(normalizeExactContent(replacedCopy.content))) {
|
|
219
|
+
equalityFailures.push(`${compactedId} (normalized content not byte-identical to first copy ${firstCopyId})`);
|
|
220
|
+
}
|
|
221
|
+
// Check 2: the first occurrence of each group is retained — a first-copy id must never appear
|
|
222
|
+
// among the compacted ids.
|
|
223
|
+
if (firstCopyId && compactedSkillMessageIds.has(firstCopyId)) {
|
|
224
|
+
firstCopyCompacted.push(firstCopyId);
|
|
225
|
+
}
|
|
226
|
+
// Check 3: no compacted group spans more than one skill name. Group by first-copy id.
|
|
227
|
+
if (firstCopyId && provenance?.skillName) {
|
|
228
|
+
const set = groupSkillNames.get(firstCopyId) ?? new Set();
|
|
229
|
+
set.add(provenance.skillName);
|
|
230
|
+
groupSkillNames.set(firstCopyId, set);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const crossSkillGroups = [...groupSkillNames.entries()].filter(([, names]) => names.size > 1);
|
|
234
|
+
return {
|
|
235
|
+
applied: true,
|
|
236
|
+
byteIdenticalEquality: {
|
|
237
|
+
status: equalityFailures.length === 0 ? "pass" : "fail",
|
|
238
|
+
details: equalityFailures.length === 0
|
|
239
|
+
? `All ${compactedSkillMessageIds.size} compacted skill injection(s) are byte-identical (sha256) to their first-copy source.`
|
|
240
|
+
: `Non-byte-identical compacted skill injection(s): ${equalityFailures.join(", ")}.`
|
|
241
|
+
},
|
|
242
|
+
firstOfGroupRetained: {
|
|
243
|
+
status: firstCopyCompacted.length === 0 ? "pass" : "fail",
|
|
244
|
+
details: firstCopyCompacted.length === 0
|
|
245
|
+
? "The first occurrence of every (skill, exact-content) group is retained (never compacted)."
|
|
246
|
+
: `First-of-group copy was compacted: ${[...new Set(firstCopyCompacted)].join(", ")}.`
|
|
247
|
+
},
|
|
248
|
+
noCrossSkillGroup: {
|
|
249
|
+
status: crossSkillGroups.length === 0 ? "pass" : "fail",
|
|
250
|
+
details: crossSkillGroups.length === 0
|
|
251
|
+
? "No compacted group spans more than one skill name."
|
|
252
|
+
: `Cross-skill group(s) detected for first copy: ${crossSkillGroups.map(([id]) => id).join(", ")}.`
|
|
253
|
+
},
|
|
254
|
+
provenancePresent: {
|
|
255
|
+
status: provenanceFailures.length === 0 ? "pass" : "fail",
|
|
256
|
+
details: provenanceFailures.length === 0
|
|
257
|
+
? "Owning-agent-id + replaced-position provenance is present for every compacted skill injection."
|
|
258
|
+
: `Missing provenance for compacted skill injection(s): ${provenanceFailures.join(", ")}.`
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
export function createSafetyReport(input) {
|
|
263
|
+
if (input.compactedMessageIds.length === 0 && input.stateCapsules.length === 0) {
|
|
264
|
+
const noCompactionEvidenceSource = evidenceSourceTypeFromTrace(input.originalTrace.source);
|
|
265
|
+
const noCompactionReviewerType = input.reviewerType ?? "none";
|
|
266
|
+
const noCompactionReviewSummaryPresent = input.reviewSummaryPresent ?? false;
|
|
267
|
+
const noCompactionApprovalStatus = approvalReadinessStatusFromReport("pass", noCompactionEvidenceSource, noCompactionReviewerType, noCompactionReviewSummaryPresent);
|
|
268
|
+
return {
|
|
269
|
+
run_id: input.runId,
|
|
270
|
+
generated_at: input.generatedAt ?? new Date().toISOString(),
|
|
271
|
+
policy_name: input.policyName,
|
|
272
|
+
status: "pass",
|
|
273
|
+
checks: [
|
|
274
|
+
{
|
|
275
|
+
id: "no_compaction_applied",
|
|
276
|
+
label: "No compaction was applied",
|
|
277
|
+
status: "pass",
|
|
278
|
+
required: true,
|
|
279
|
+
details: "No duplicate tool output met the policy thresholds. No context was removed and no safety evidence is required."
|
|
280
|
+
}
|
|
281
|
+
],
|
|
282
|
+
warnings: [],
|
|
283
|
+
failures: [],
|
|
284
|
+
source_pointer_count: 0,
|
|
285
|
+
compacted_message_count: 0,
|
|
286
|
+
tokens_saved: 0,
|
|
287
|
+
savings_scope: "policy_level",
|
|
288
|
+
source_recoverability: "unknown",
|
|
289
|
+
risk_level: "low",
|
|
290
|
+
recommendation: "No compaction was applied. The original trace is unchanged.",
|
|
291
|
+
evidence_source_type: noCompactionEvidenceSource,
|
|
292
|
+
reviewer_type: noCompactionReviewerType,
|
|
293
|
+
review_summary_present: noCompactionReviewSummaryPresent,
|
|
294
|
+
approval_readiness_status: noCompactionApprovalStatus,
|
|
295
|
+
approval_readiness_reason: approvalReadinessReasonFromReport("pass", noCompactionApprovalStatus, noCompactionEvidenceSource, noCompactionReviewerType, noCompactionReviewSummaryPresent)
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const checks = [];
|
|
299
|
+
const compactedMetadata = collectMetadataForCompactedMessages(input.compactedMessages, input.compactedMessageIds);
|
|
300
|
+
const sourcePointers = compactedMetadata.flatMap((metadata) => isSourcePointer(metadata.rawSourcePointer) ? [metadata.rawSourcePointer] : []);
|
|
301
|
+
const sourcePointerKeys = new Set(sourcePointers.map((pointer) => `${pointer.traceId}:${pointer.messageId}:${pointer.messageIndex}:${pointer.contentSha256}`));
|
|
302
|
+
const capsuleByCompactedMessageId = new Map(input.stateCapsules.flatMap((capsule) => capsule.compactedMessageIds.map((messageId) => [messageId, capsule])));
|
|
303
|
+
const missingCapsuleIds = input.compactedMessageIds.filter((messageId) => !capsuleByCompactedMessageId.has(messageId));
|
|
304
|
+
const messagesWithMetadata = new Set(compactedMetadata.map((metadata) => metadata.compactedMessageId).filter(Boolean));
|
|
305
|
+
const allMetadataPresent = input.compactedMessageIds.every((messageId) => messagesWithMetadata.has(messageId));
|
|
306
|
+
const allMetadataPolicyNamesPresent = compactedMetadata.every((metadata) => typeof metadata.policyName === "string");
|
|
307
|
+
const allMetadataReplacementModesPresent = compactedMetadata.every((metadata) => metadata.replacementMode === "whole_message" || metadata.replacementMode === "embedded_payload");
|
|
308
|
+
const allMetadataRawPointersPresent = input.compactedMessageIds.length > 0 && compactedMetadata.every((metadata) => isSourcePointer(metadata.rawSourcePointer));
|
|
309
|
+
const allMetadataContentHashesPresent = compactedMetadata.every((metadata) => (isSourcePointer(metadata.rawSourcePointer) && metadata.rawSourcePointer.contentSha256.length > 0) ||
|
|
310
|
+
(typeof metadata.replacedContentSha256 === "string" && metadata.replacedContentSha256.length > 0));
|
|
311
|
+
const unavailablePointers = sourcePointers.filter((pointer) => {
|
|
312
|
+
if (pointer.traceId !== input.originalTrace.id) {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
const originalMessage = findOriginalMessage(input.originalTrace, pointer);
|
|
316
|
+
return !originalMessage;
|
|
317
|
+
});
|
|
318
|
+
const pointersWithPayloadMismatch = sourcePointers.filter((pointer) => {
|
|
319
|
+
const originalMessage = findOriginalMessage(input.originalTrace, pointer);
|
|
320
|
+
return originalMessage ? contentHash(originalMessage.content) !== pointer.contentSha256 : false;
|
|
321
|
+
});
|
|
322
|
+
const capsulesReferenceOriginalSource = input.stateCapsules.every((capsule) => {
|
|
323
|
+
const pointer = capsule.sourcePointer;
|
|
324
|
+
return pointer.traceId === input.originalTrace.id && Boolean(findOriginalMessage(input.originalTrace, pointer));
|
|
325
|
+
});
|
|
326
|
+
// Supported-policy SET (design §Q5): both the shipped tool-output policy and the
|
|
327
|
+
// approval-required skill-injection policy are supported. Any OTHER policy name is unsupported.
|
|
328
|
+
const unsupportedMetadataPolicies = compactedMetadata.filter((metadata) => metadata.policyName && !isSupportedCompactionPolicyName(metadata.policyName));
|
|
329
|
+
const unsupportedCapsulePolicies = input.stateCapsules.filter((capsule) => !isSupportedCompactionPolicyName(capsule.policyName));
|
|
330
|
+
const unsupportedPolicyApplied = !isSupportedCompactionPolicyName(input.policyName) ||
|
|
331
|
+
unsupportedMetadataPolicies.length > 0 ||
|
|
332
|
+
unsupportedCapsulePolicies.length > 0;
|
|
333
|
+
addCheck(checks, "raw_payload_preserved_by_source_pointer", "Raw payload preserved by source pointer", sourcePointers.length > 0 && unavailablePointers.length === 0 && pointersWithPayloadMismatch.length === 0 ? "pass" : "fail", true, sourcePointers.length === 0
|
|
334
|
+
? "No raw source pointer was recorded."
|
|
335
|
+
: unavailablePointers.length > 0
|
|
336
|
+
? `Source pointer(s) do not resolve to original trace message(s): ${unavailablePointers.map((pointer) => pointer.messageId).join(", ")}.`
|
|
337
|
+
: pointersWithPayloadMismatch.length > 0
|
|
338
|
+
? `Source pointer content hash mismatch for message(s): ${pointersWithPayloadMismatch.map((pointer) => pointer.messageId).join(", ")}.`
|
|
339
|
+
: `${sourcePointers.length} source pointer(s) resolve to original trace payloads with matching hashes.`);
|
|
340
|
+
addCheck(checks, "state_capsule_exists", "State capsule exists", missingCapsuleIds.length === 0 && input.compactedMessageIds.length > 0 ? "pass" : "fail", true, input.compactedMessageIds.length === 0
|
|
341
|
+
? "No compacted messages were recorded, so no state capsule can cover compacted output."
|
|
342
|
+
: missingCapsuleIds.length === 0
|
|
343
|
+
? `${input.stateCapsules.length} state capsule(s) cover compacted messages.`
|
|
344
|
+
: `Missing state capsule for compacted message(s): ${missingCapsuleIds.join(", ")}.`);
|
|
345
|
+
addCheck(checks, "state_capsule_references_original_source", "State capsule references original source message", input.stateCapsules.length > 0 && capsulesReferenceOriginalSource ? "pass" : "fail", true, input.stateCapsules.length === 0
|
|
346
|
+
? "No state capsules were recorded for compacted messages."
|
|
347
|
+
: capsulesReferenceOriginalSource
|
|
348
|
+
? "Every state capsule source pointer resolves to the original trace."
|
|
349
|
+
: "At least one state capsule source pointer does not resolve to the original trace.");
|
|
350
|
+
addCheck(checks, "metadata_policy_name", "Compacted message metadata includes policyName", allMetadataPresent && allMetadataPolicyNamesPresent ? "pass" : "warn", false, allMetadataPolicyNamesPresent ? "Every compaction metadata entry records policyName." : "At least one metadata entry is missing policyName.");
|
|
351
|
+
addCheck(checks, "metadata_replacement_mode", "Compacted message metadata includes replacementMode", allMetadataPresent && allMetadataReplacementModesPresent ? "pass" : "warn", false, allMetadataReplacementModesPresent
|
|
352
|
+
? "Every compaction metadata entry records replacementMode."
|
|
353
|
+
: "At least one metadata entry is missing replacementMode.");
|
|
354
|
+
addCheck(checks, "metadata_raw_source_pointer", "Compacted message metadata includes rawSourcePointer", allMetadataRawPointersPresent ? "pass" : "fail", true, input.compactedMessageIds.length === 0
|
|
355
|
+
? "No compacted messages were recorded, so no rawSourcePointer metadata can be validated."
|
|
356
|
+
: allMetadataRawPointersPresent
|
|
357
|
+
? "Every compaction metadata entry records rawSourcePointer."
|
|
358
|
+
: "At least one compacted message is missing rawSourcePointer metadata.");
|
|
359
|
+
addCheck(checks, "metadata_content_hash", "Compacted message metadata includes content hash", allMetadataPresent && allMetadataContentHashesPresent ? "pass" : "warn", false, allMetadataContentHashesPresent
|
|
360
|
+
? "Every compaction metadata entry includes a content hash."
|
|
361
|
+
: "At least one metadata entry is missing a content hash.");
|
|
362
|
+
addCheck(checks, "source_message_available", "Source message remains available in original trace", unavailablePointers.length === 0 && sourcePointers.length > 0 ? "pass" : "fail", true, sourcePointers.length === 0
|
|
363
|
+
? "No source pointers were recorded for compacted messages."
|
|
364
|
+
: unavailablePointers.length === 0
|
|
365
|
+
? "All source messages remain available in the original trace."
|
|
366
|
+
: `Unavailable source pointer(s): ${unavailablePointers.map((pointer) => pointer.messageId).join(", ")}.`);
|
|
367
|
+
addCheck(checks, "positive_token_savings", "Token savings are positive", input.tokensSaved > 0 ? "pass" : "fail", true, `${input.tokensSaved} input token(s) saved.`);
|
|
368
|
+
addCheck(checks, "policy_name_expected", "Policy name is one of the supported compaction policies", isSupportedCompactionPolicyName(input.policyName) ? "pass" : "fail", true, `Policy name: ${input.policyName}. Supported: ${SUPPORTED_COMPACTION_POLICY_NAMES.join(", ")}.`);
|
|
369
|
+
addCheck(checks, "no_unsupported_policy", "No unsupported policy was applied", unsupportedPolicyApplied ? "fail" : "pass", true, unsupportedPolicyApplied ? "Unsupported policy metadata was found." : "Only supported policies were applied.");
|
|
370
|
+
// NEW skill-injection-specific checks (design §Q5). Only relevant — and only `required` — when
|
|
371
|
+
// the approval-required skill-injection policy compacted ≥1 message. For the tool-output-only
|
|
372
|
+
// path these are informational passes and do not change existing behavior.
|
|
373
|
+
const skillChecks = computeSkillInjectionChecks(input.originalTrace, compactedMetadata, input.stateCapsules);
|
|
374
|
+
addCheck(checks, "skill_injection_byte_identical_equality", "Every compacted skill injection is byte-identical to its first-copy source", skillChecks.byteIdenticalEquality.status, skillChecks.applied, skillChecks.byteIdenticalEquality.details);
|
|
375
|
+
addCheck(checks, "skill_injection_first_of_group_retained", "First occurrence of each (skill, exact-content) group is retained", skillChecks.firstOfGroupRetained.status, skillChecks.applied, skillChecks.firstOfGroupRetained.details);
|
|
376
|
+
addCheck(checks, "skill_injection_no_cross_skill_group", "No compacted skill-injection group spans more than one skill name", skillChecks.noCrossSkillGroup.status, skillChecks.applied, skillChecks.noCrossSkillGroup.details);
|
|
377
|
+
addCheck(checks, "skill_injection_provenance_present", "Owning-agent-id + replaced-position provenance present for every compacted skill injection", skillChecks.provenancePresent.status, skillChecks.applied, skillChecks.provenancePresent.details);
|
|
378
|
+
const preliminaryStatus = statusFromChecks(checks);
|
|
379
|
+
addCheck(checks, "valid_safety_status", "Safety status is one of pass, warn, fail", ["pass", "warn", "fail"].includes(preliminaryStatus) ? "pass" : "fail", true, `Safety status: ${preliminaryStatus}.`);
|
|
380
|
+
const status = statusFromChecks(checks);
|
|
381
|
+
const evidenceSourceType = evidenceSourceTypeFromTrace(input.originalTrace.source);
|
|
382
|
+
const reviewerType = input.reviewerType ?? "none";
|
|
383
|
+
const reviewSummaryPresent = input.reviewSummaryPresent ?? false;
|
|
384
|
+
const approvalStatus = approvalReadinessStatusFromReport(status, evidenceSourceType, reviewerType, reviewSummaryPresent);
|
|
385
|
+
return {
|
|
386
|
+
run_id: input.runId,
|
|
387
|
+
generated_at: input.generatedAt ?? new Date().toISOString(),
|
|
388
|
+
policy_name: input.policyName,
|
|
389
|
+
status,
|
|
390
|
+
checks,
|
|
391
|
+
warnings: summarizeIssues(checks, "warn"),
|
|
392
|
+
failures: summarizeIssues(checks, "fail"),
|
|
393
|
+
source_pointer_count: sourcePointerKeys.size,
|
|
394
|
+
compacted_message_count: input.compactedMessageIds.length,
|
|
395
|
+
tokens_saved: input.tokensSaved,
|
|
396
|
+
savings_scope: "policy_level",
|
|
397
|
+
source_recoverability: sourcePointers.length === 0 ? "unknown" : unavailablePointers.length === 0,
|
|
398
|
+
risk_level: riskLevelForStatus(status),
|
|
399
|
+
recommendation: recommendationForStatus(status, approvalStatus),
|
|
400
|
+
evidence_source_type: evidenceSourceType,
|
|
401
|
+
reviewer_type: reviewerType,
|
|
402
|
+
review_summary_present: reviewSummaryPresent,
|
|
403
|
+
approval_readiness_status: approvalStatus,
|
|
404
|
+
approval_readiness_reason: approvalReadinessReasonFromReport(status, approvalStatus, evidenceSourceType, reviewerType, reviewSummaryPresent)
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
export function formatSafetyMarkdownReport(report) {
|
|
408
|
+
const passedChecks = report.checks.filter((check) => check.status === "pass");
|
|
409
|
+
const warnings = report.warnings.length > 0 ? report.warnings : ["None."];
|
|
410
|
+
const failures = report.failures.length > 0 ? report.failures : ["None."];
|
|
411
|
+
return [
|
|
412
|
+
`# Safety Report: ${report.run_id}`,
|
|
413
|
+
"",
|
|
414
|
+
`**Status:** ${report.status.toUpperCase()}`,
|
|
415
|
+
`**Risk level:** ${report.risk_level}`,
|
|
416
|
+
`**Policy:** ${report.policy_name}`,
|
|
417
|
+
`${report.savings_scope === "policy_level" ? "**Policy-level tokens saved:**" : "**Tokens saved:**"} ${report.tokens_saved}`,
|
|
418
|
+
`**Compacted messages:** ${report.compacted_message_count}`,
|
|
419
|
+
`**Source pointers:** ${report.source_pointer_count}`,
|
|
420
|
+
`**Source recoverability:** ${report.source_recoverability}`,
|
|
421
|
+
"",
|
|
422
|
+
"## Checks passed",
|
|
423
|
+
...passedChecks.map((check) => `- ${check.label}: ${check.details}`),
|
|
424
|
+
"",
|
|
425
|
+
"## Warnings",
|
|
426
|
+
...warnings.map((warning) => `- ${warning}`),
|
|
427
|
+
"",
|
|
428
|
+
"## Failures",
|
|
429
|
+
...failures.map((failure) => `- ${failure}`),
|
|
430
|
+
"",
|
|
431
|
+
"## Recommendation",
|
|
432
|
+
report.recommendation,
|
|
433
|
+
"",
|
|
434
|
+
"## Known limitations",
|
|
435
|
+
"- Safety Report v0 is deterministic local safety evidence, not semantic replay.",
|
|
436
|
+
"- It validates source pointers, state capsules, supported policy metadata, and positive token savings only.",
|
|
437
|
+
"- It does not call model providers, perform fuzzy matching, or prove task-level semantic equivalence."
|
|
438
|
+
].join("\n");
|
|
439
|
+
}
|
|
440
|
+
//# sourceMappingURL=safety-report.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CostEstimate, TokenEstimate } from "./types.js";
|
|
2
|
+
export type SavingsScope = "trace_level" | "policy_level" | "repeated_segment" | "embedded_context" | "run_level";
|
|
3
|
+
export interface SavingsCalculation {
|
|
4
|
+
savings_scope: SavingsScope;
|
|
5
|
+
input_tokens_before: number;
|
|
6
|
+
input_tokens_after: number;
|
|
7
|
+
tokens_saved: number;
|
|
8
|
+
percent_reduction: number;
|
|
9
|
+
estimated_cost_before: number;
|
|
10
|
+
estimated_cost_after: number;
|
|
11
|
+
estimated_saving_per_run: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function roundCurrency(value: number): number;
|
|
14
|
+
export declare function roundPercent(value: number): number;
|
|
15
|
+
export declare function calculateSavings(input: {
|
|
16
|
+
scope: SavingsScope;
|
|
17
|
+
beforeTokens: Pick<TokenEstimate, "inputTokens">;
|
|
18
|
+
afterTokens: Pick<TokenEstimate, "inputTokens">;
|
|
19
|
+
beforeCost: Pick<CostEstimate, "totalCostUsd">;
|
|
20
|
+
afterCost: Pick<CostEstimate, "totalCostUsd">;
|
|
21
|
+
}): SavingsCalculation;
|
|
22
|
+
export declare function savingsScopeLabel(scope: SavingsScope): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function roundCurrency(value) {
|
|
2
|
+
return Number(value.toFixed(6));
|
|
3
|
+
}
|
|
4
|
+
export function roundPercent(value) {
|
|
5
|
+
return Number(value.toFixed(2));
|
|
6
|
+
}
|
|
7
|
+
export function calculateSavings(input) {
|
|
8
|
+
const inputTokensBefore = input.beforeTokens.inputTokens;
|
|
9
|
+
const inputTokensAfter = input.afterTokens.inputTokens;
|
|
10
|
+
const tokensSaved = Math.max(0, inputTokensBefore - inputTokensAfter);
|
|
11
|
+
return {
|
|
12
|
+
savings_scope: input.scope,
|
|
13
|
+
input_tokens_before: inputTokensBefore,
|
|
14
|
+
input_tokens_after: inputTokensAfter,
|
|
15
|
+
tokens_saved: tokensSaved,
|
|
16
|
+
percent_reduction: inputTokensBefore === 0 ? 0 : roundPercent((tokensSaved / inputTokensBefore) * 100),
|
|
17
|
+
estimated_cost_before: roundCurrency(input.beforeCost.totalCostUsd),
|
|
18
|
+
estimated_cost_after: roundCurrency(input.afterCost.totalCostUsd),
|
|
19
|
+
estimated_saving_per_run: roundCurrency(Math.max(0, input.beforeCost.totalCostUsd - input.afterCost.totalCostUsd))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function savingsScopeLabel(scope) {
|
|
23
|
+
switch (scope) {
|
|
24
|
+
case "trace_level":
|
|
25
|
+
return "Trace-level";
|
|
26
|
+
case "policy_level":
|
|
27
|
+
return "Policy-level";
|
|
28
|
+
case "repeated_segment":
|
|
29
|
+
return "Repeated-segment";
|
|
30
|
+
case "embedded_context":
|
|
31
|
+
return "Embedded-context";
|
|
32
|
+
case "run_level":
|
|
33
|
+
return "Run-level";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=savings.js.map
|