pluribus-context 0.3.22 → 0.3.26
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/CHANGELOG.md +25 -3
- package/README.md +2 -2
- package/docs/community-review-packet.md +2 -1
- package/docs/context-budget-receipts.md +150 -0
- package/docs/context-input-evidence.md +397 -0
- package/docs/context-receipts-for-agent-observability.md +177 -0
- package/docs/orchestration-search-receipts.md +102 -0
- package/docs/portability-fidelity-report.md +4 -2
- package/examples/context-input-evidence/AGENTS.md +12 -0
- package/examples/context-input-evidence/agent-overlay-log.jsonl +4 -0
- package/examples/context-input-evidence/agent-overlay-otel-trace.json +548 -0
- package/examples/context-input-evidence/agent-overlay-receipt.ndjson +3 -0
- package/examples/context-input-evidence/agentgateway-progressive-disclosure-otel-trace.json +393 -0
- package/examples/context-input-evidence/agentgateway-progressive-disclosure-receipt.ndjson +4 -0
- package/examples/context-input-evidence/brain-remediation-otel-trace.json +645 -0
- package/examples/context-input-evidence/brain-remediation-receipt.ndjson +7 -0
- package/examples/context-input-evidence/claudekit-mcp-manager-otel-trace.json +417 -0
- package/examples/context-input-evidence/claudekit-mcp-manager-receipt.ndjson +5 -0
- package/examples/context-input-evidence/cli-progressive-disclosure-otel-trace.json +399 -0
- package/examples/context-input-evidence/cli-progressive-disclosure-receipt.ndjson +4 -0
- package/examples/context-input-evidence/compaction-otel-trace.json +711 -0
- package/examples/context-input-evidence/compaction-receipt.ndjson +6 -0
- package/examples/context-input-evidence/context-selection-otel-trace.json +627 -0
- package/examples/context-input-evidence/context-selection-receipt.ndjson +7 -0
- package/examples/context-input-evidence/convert-agent-overlay-log.mjs +156 -0
- package/examples/context-input-evidence/convert-agentgateway-progressive-disclosure-log.mjs +251 -0
- package/examples/context-input-evidence/convert-brain-remediation-log.mjs +241 -0
- package/examples/context-input-evidence/convert-claudekit-mcp-manager-log.mjs +253 -0
- package/examples/context-input-evidence/convert-cli-progressive-disclosure-log.mjs +251 -0
- package/examples/context-input-evidence/convert-compaction-log.mjs +224 -0
- package/examples/context-input-evidence/convert-context-selection-log.mjs +247 -0
- package/examples/context-input-evidence/convert-mcp-tool-search-log.mjs +242 -0
- package/examples/context-input-evidence/convert-memory-consolidation-log.mjs +240 -0
- package/examples/context-input-evidence/convert-memory-governance-delete-log.mjs +223 -0
- package/examples/context-input-evidence/convert-memory-log.mjs +226 -0
- package/examples/context-input-evidence/convert-memory-provenance-log.mjs +263 -0
- package/examples/context-input-evidence/convert-secret-scanning-log.mjs +233 -0
- package/examples/context-input-evidence/convert-session-log.mjs +186 -0
- package/examples/context-input-evidence/convert-skill-log.mjs +161 -0
- package/examples/context-input-evidence/convert-skill-registry-log.mjs +246 -0
- package/examples/context-input-evidence/convert-skill-routing-log.mjs +253 -0
- package/examples/context-input-evidence/convert-subagent-context-budget-log.mjs +267 -0
- package/examples/context-input-evidence/convert-subagent-delegation-log.mjs +264 -0
- package/examples/context-input-evidence/export-otel-trace.mjs +128 -0
- package/examples/context-input-evidence/generate-receipt.mjs +188 -0
- package/examples/context-input-evidence/mcp-tool-search-otel-trace.json +477 -0
- package/examples/context-input-evidence/mcp-tool-search-receipt.ndjson +5 -0
- package/examples/context-input-evidence/memory-consolidation-otel-trace.json +492 -0
- package/examples/context-input-evidence/memory-consolidation-receipt.ndjson +4 -0
- package/examples/context-input-evidence/memory-governance-delete-otel-trace.json +614 -0
- package/examples/context-input-evidence/memory-governance-delete-receipt.ndjson +5 -0
- package/examples/context-input-evidence/memory-otel-trace.json +645 -0
- package/examples/context-input-evidence/memory-provenance-otel-trace.json +711 -0
- package/examples/context-input-evidence/memory-provenance-receipt.ndjson +5 -0
- package/examples/context-input-evidence/memory-receipt.ndjson +4 -0
- package/examples/context-input-evidence/otel-trace.json +1119 -0
- package/examples/context-input-evidence/receipt.ndjson +6 -0
- package/examples/context-input-evidence/sample-agentgateway-progressive-disclosure-log.jsonl +5 -0
- package/examples/context-input-evidence/sample-brain-remediation-log.jsonl +9 -0
- package/examples/context-input-evidence/sample-claudekit-mcp-manager-log.jsonl +6 -0
- package/examples/context-input-evidence/sample-cli-progressive-disclosure-log.jsonl +5 -0
- package/examples/context-input-evidence/sample-compaction-log.jsonl +7 -0
- package/examples/context-input-evidence/sample-context-selection-log.jsonl +7 -0
- package/examples/context-input-evidence/sample-mcp-tool-search-log.jsonl +6 -0
- package/examples/context-input-evidence/sample-memory-consolidation-log.jsonl +5 -0
- package/examples/context-input-evidence/sample-memory-governance-delete-log.jsonl +6 -0
- package/examples/context-input-evidence/sample-memory-provenance-log.jsonl +6 -0
- package/examples/context-input-evidence/sample-memory-retrieval-log.jsonl +6 -0
- package/examples/context-input-evidence/sample-secret-scanning-log.jsonl +7 -0
- package/examples/context-input-evidence/sample-session-log.jsonl +6 -0
- package/examples/context-input-evidence/sample-skill-registry-log.jsonl +5 -0
- package/examples/context-input-evidence/sample-skill-routing-log.jsonl +7 -0
- package/examples/context-input-evidence/sample-subagent-context-budget-log.jsonl +6 -0
- package/examples/context-input-evidence/sample-subagent-delegation-log.jsonl +5 -0
- package/examples/context-input-evidence/secret-scanning-otel-trace.json +794 -0
- package/examples/context-input-evidence/secret-scanning-receipt.ndjson +6 -0
- package/examples/context-input-evidence/session-otel-trace.json +411 -0
- package/examples/context-input-evidence/session-receipt.ndjson +2 -0
- package/examples/context-input-evidence/skill-invocation-log.jsonl +4 -0
- package/examples/context-input-evidence/skill-otel-trace.json +548 -0
- package/examples/context-input-evidence/skill-receipt.ndjson +3 -0
- package/examples/context-input-evidence/skill-registry-otel-trace.json +471 -0
- package/examples/context-input-evidence/skill-registry-receipt.ndjson +5 -0
- package/examples/context-input-evidence/skill-routing-otel-trace.json +567 -0
- package/examples/context-input-evidence/skill-routing-receipt.ndjson +6 -0
- package/examples/context-input-evidence/subagent-context-budget-otel-trace.json +507 -0
- package/examples/context-input-evidence/subagent-context-budget-receipt.ndjson +5 -0
- package/examples/context-input-evidence/subagent-delegation-otel-trace.json +388 -0
- package/examples/context-input-evidence/subagent-delegation-receipt.ndjson +4 -0
- package/package.json +6 -2
- package/schemas/audit-result.schema.json +409 -71
- package/src/commands/audit.js +64 -3
- package/src/utils/version.js +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const inputPath = process.argv[2] ? resolve(process.argv[2]) : join(here, 'sample-compaction-log.jsonl');
|
|
9
|
+
const receiptPath = process.argv[3] ? resolve(process.argv[3]) : join(here, 'compaction-receipt.ndjson');
|
|
10
|
+
const tracePath = process.argv[4] ? resolve(process.argv[4]) : join(here, 'compaction-otel-trace.json');
|
|
11
|
+
|
|
12
|
+
function sha256(value) {
|
|
13
|
+
return `sha256:${createHash('sha256').update(value ?? '').digest('hex')}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function hashRef(value) {
|
|
17
|
+
return sha256(value ?? '').slice(0, 19);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readJsonl(path) {
|
|
21
|
+
return readFileSync(path, 'utf8')
|
|
22
|
+
.trim()
|
|
23
|
+
.split('\n')
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((line, index) => {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(line);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Invalid JSONL at ${path}:${index + 1}: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function unixNano(isoTimestamp) {
|
|
35
|
+
return `${BigInt(Date.parse(isoTimestamp)) * 1_000_000n}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function otelValue(value) {
|
|
39
|
+
if (typeof value === 'boolean') return { boolValue: value };
|
|
40
|
+
if (typeof value === 'number' && Number.isInteger(value)) return { intValue: String(value) };
|
|
41
|
+
if (typeof value === 'number') return { doubleValue: value };
|
|
42
|
+
if (value == null) return { stringValue: '' };
|
|
43
|
+
return { stringValue: String(value) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function attributesToOtel(attributes) {
|
|
47
|
+
return Object.entries(attributes).map(([key, value]) => ({ key, value: otelValue(value) }));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function tokenBucket(value) {
|
|
51
|
+
if (value < 1_000) return 'under_1k';
|
|
52
|
+
if (value < 10_000) return 'under_10k';
|
|
53
|
+
if (value < 50_000) return 'under_50k';
|
|
54
|
+
return 'over_50k';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const records = readJsonl(inputPath);
|
|
58
|
+
const session = records.find((record) => record.type === 'session.start');
|
|
59
|
+
const start = records.find((record) => record.type === 'context.compaction.start');
|
|
60
|
+
const items = records.filter((record) => record.type === 'context.item.evaluated');
|
|
61
|
+
const completed = records.find((record) => record.type === 'context.compaction.completed');
|
|
62
|
+
|
|
63
|
+
if (!session || !start || !completed || items.length === 0) {
|
|
64
|
+
throw new Error(`Expected session.start, context.compaction.start, context.item.evaluated, and context.compaction.completed records in ${inputPath}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const traceSeed = `${session.session_id}:${start.time}:context-compaction`;
|
|
68
|
+
const traceId = sha256(traceSeed).replace('sha256:', '').slice(0, 32);
|
|
69
|
+
const spanId = sha256(`${traceSeed}:span`).replace('sha256:', '').slice(0, 16);
|
|
70
|
+
const compactionId = hashRef(`${session.session_id}:${start.time}:${completed.time}`);
|
|
71
|
+
|
|
72
|
+
const startEvent = {
|
|
73
|
+
trace_id: traceId,
|
|
74
|
+
span_id: spanId,
|
|
75
|
+
name: 'context.compaction.started',
|
|
76
|
+
time: start.time,
|
|
77
|
+
attributes: {
|
|
78
|
+
'session.id': session.session_id,
|
|
79
|
+
'gen_ai.conversation.id': session.conversation_id,
|
|
80
|
+
'agent.name': session.agent,
|
|
81
|
+
'context.compaction.id_hash': compactionId,
|
|
82
|
+
'context.compaction.reason': start.reason,
|
|
83
|
+
'context.compaction.trigger': start.trigger,
|
|
84
|
+
'context.compaction.token_count.before_bucket': tokenBucket(start.token_count_before),
|
|
85
|
+
'context.compaction.token_threshold_bucket': tokenBucket(start.token_threshold),
|
|
86
|
+
'context.compaction.window_bucket': start.window_bucket,
|
|
87
|
+
'context.compaction.objective.before_hash': sha256(start.raw_recent_task),
|
|
88
|
+
'privacy.raw_prompt_recorded': false,
|
|
89
|
+
'privacy.raw_context_recorded': false,
|
|
90
|
+
'privacy.raw_tool_output_recorded': false
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const itemEvents = items.map((item) => ({
|
|
95
|
+
trace_id: traceId,
|
|
96
|
+
span_id: spanId,
|
|
97
|
+
name: 'context.compaction.item.evaluated',
|
|
98
|
+
time: item.time,
|
|
99
|
+
attributes: {
|
|
100
|
+
'session.id': session.session_id,
|
|
101
|
+
'gen_ai.conversation.id': session.conversation_id,
|
|
102
|
+
'context.compaction.id_hash': compactionId,
|
|
103
|
+
'context.item.id_hash': hashRef(item.item_id),
|
|
104
|
+
'context.item.kind': item.kind,
|
|
105
|
+
'context.item.source': item.source,
|
|
106
|
+
'context.item.source.hash': sha256(item.source),
|
|
107
|
+
'context.item.semantic_role': item.semantic_role,
|
|
108
|
+
'context.item.action': item.action,
|
|
109
|
+
'context.item.token_count_bucket': tokenBucket(item.token_count),
|
|
110
|
+
'context.item.summary_token_count_bucket': item.summary_token_count ? tokenBucket(item.summary_token_count) : '',
|
|
111
|
+
'context.item.drop_reason': item.drop_reason ?? '',
|
|
112
|
+
'context.item.raw_text_hash': sha256(item.raw_text),
|
|
113
|
+
'context.item.raw_text_recorded': false,
|
|
114
|
+
'context.item.reconstructable_from_hash': item.action === 'dropped' || item.action === 'preserved_hash_only',
|
|
115
|
+
'privacy.raw_context_recorded': false,
|
|
116
|
+
'privacy.raw_tool_output_recorded': false
|
|
117
|
+
}
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const completedEvent = {
|
|
121
|
+
trace_id: traceId,
|
|
122
|
+
span_id: spanId,
|
|
123
|
+
name: 'context.compaction.completed',
|
|
124
|
+
time: completed.time,
|
|
125
|
+
attributes: {
|
|
126
|
+
'session.id': session.session_id,
|
|
127
|
+
'gen_ai.conversation.id': session.conversation_id,
|
|
128
|
+
'context.compaction.id_hash': compactionId,
|
|
129
|
+
'context.compaction.token_count.after_bucket': tokenBucket(completed.token_count_after),
|
|
130
|
+
'context.compaction.summary.hash': sha256(completed.summary_hash_basis),
|
|
131
|
+
'context.compaction.objective.after_hash': sha256(completed.objective_hash_basis),
|
|
132
|
+
'context.compaction.item.count': items.length,
|
|
133
|
+
'context.compaction.items.dropped': completed.dropped_count,
|
|
134
|
+
'context.compaction.items.summarized': completed.summarized_count,
|
|
135
|
+
'context.compaction.items.preserved': completed.preserved_count,
|
|
136
|
+
'context.compaction.audit_gap': 'cannot_prove_semantic_equivalence_without_eval',
|
|
137
|
+
'privacy.raw_summary_recorded': false,
|
|
138
|
+
'privacy.raw_prompt_recorded': false,
|
|
139
|
+
'privacy.raw_context_recorded': false,
|
|
140
|
+
'privacy.raw_tool_output_recorded': false
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const events = [startEvent, ...itemEvents, completedEvent]
|
|
145
|
+
.sort((left, right) => Date.parse(left.time) - Date.parse(right.time));
|
|
146
|
+
|
|
147
|
+
writeFileSync(receiptPath, `${events.map((event) => JSON.stringify(event)).join('\n')}\n`);
|
|
148
|
+
|
|
149
|
+
const trace = {
|
|
150
|
+
resourceSpans: [
|
|
151
|
+
{
|
|
152
|
+
resource: {
|
|
153
|
+
attributes: attributesToOtel({
|
|
154
|
+
'service.name': 'pluribus-context-compaction-receipt-demo',
|
|
155
|
+
'service.version': '0.0.0-fixture',
|
|
156
|
+
'deployment.environment.name': 'local-fixture'
|
|
157
|
+
})
|
|
158
|
+
},
|
|
159
|
+
scopeSpans: [
|
|
160
|
+
{
|
|
161
|
+
scope: {
|
|
162
|
+
name: 'pluribus.context_input_evidence.compaction_demo',
|
|
163
|
+
version: '0.0.0-fixture'
|
|
164
|
+
},
|
|
165
|
+
spans: [
|
|
166
|
+
{
|
|
167
|
+
traceId,
|
|
168
|
+
spanId,
|
|
169
|
+
parentSpanId: '',
|
|
170
|
+
name: 'agent.session.context.compaction',
|
|
171
|
+
kind: 1,
|
|
172
|
+
startTimeUnixNano: unixNano(start.time),
|
|
173
|
+
endTimeUnixNano: unixNano(completed.time),
|
|
174
|
+
attributes: attributesToOtel({
|
|
175
|
+
'session.id': session.session_id,
|
|
176
|
+
'gen_ai.conversation.id': session.conversation_id,
|
|
177
|
+
'agent.name': session.agent,
|
|
178
|
+
'workspace.name': session.workspace,
|
|
179
|
+
'gen_ai.request.model': session.model,
|
|
180
|
+
'context.compaction.id_hash': compactionId,
|
|
181
|
+
'context.compaction.reason': start.reason,
|
|
182
|
+
'context.compaction.trigger': start.trigger
|
|
183
|
+
}),
|
|
184
|
+
events: events.map((event) => ({
|
|
185
|
+
name: event.name,
|
|
186
|
+
timeUnixNano: unixNano(event.time),
|
|
187
|
+
attributes: attributesToOtel(event.attributes)
|
|
188
|
+
}))
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
writeFileSync(tracePath, `${JSON.stringify(trace, null, 2)}\n`);
|
|
198
|
+
|
|
199
|
+
const forbiddenRawStrings = [
|
|
200
|
+
'private customer checkout failures',
|
|
201
|
+
'never log customer payment tokens',
|
|
202
|
+
'vendor URLs, internal commands',
|
|
203
|
+
'customer-like fixture data',
|
|
204
|
+
'root cause previously suspected',
|
|
205
|
+
'avoid symptom patch'
|
|
206
|
+
];
|
|
207
|
+
const exportedText = `${events.map((event) => JSON.stringify(event)).join('\n')}\n${JSON.stringify(trace)}`;
|
|
208
|
+
const rawTextCopiedToReceipt = forbiddenRawStrings.some((value) => exportedText.includes(value));
|
|
209
|
+
const actionCounts = Object.fromEntries(items.map((item) => [item.action, items.filter((candidate) => candidate.action === item.action).length]));
|
|
210
|
+
|
|
211
|
+
const summary = {
|
|
212
|
+
schema: 'pluribus.contextCompactionReceipt.demo.v0',
|
|
213
|
+
eventCount: events.length,
|
|
214
|
+
itemEvents: itemEvents.length,
|
|
215
|
+
actionCounts,
|
|
216
|
+
includesObjectiveHashes: Boolean(startEvent.attributes['context.compaction.objective.before_hash'] && completedEvent.attributes['context.compaction.objective.after_hash']),
|
|
217
|
+
includesAuditGap: completedEvent.attributes['context.compaction.audit_gap'],
|
|
218
|
+
rawTextCopiedToReceipt,
|
|
219
|
+
receiptPath: 'examples/context-input-evidence/compaction-receipt.ndjson',
|
|
220
|
+
tracePath: 'examples/context-input-evidence/compaction-otel-trace.json',
|
|
221
|
+
lesson: 'Context compaction needs receipts for trigger, item-level preserve/summarize/drop decisions, objective continuity, and audit gaps; green tests alone do not prove the original task survived compaction.'
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const inputPath = process.argv[2] ? resolve(process.argv[2]) : join(here, 'sample-context-selection-log.jsonl');
|
|
9
|
+
const receiptPath = process.argv[3] ? resolve(process.argv[3]) : join(here, 'context-selection-receipt.ndjson');
|
|
10
|
+
const tracePath = process.argv[4] ? resolve(process.argv[4]) : join(here, 'context-selection-otel-trace.json');
|
|
11
|
+
|
|
12
|
+
function sha256(value) {
|
|
13
|
+
return `sha256:${createHash('sha256').update(String(value)).digest('hex')}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readJsonl(path) {
|
|
17
|
+
return readFileSync(path, 'utf8')
|
|
18
|
+
.trim()
|
|
19
|
+
.split('\n')
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.map((line, index) => {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(line);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`Invalid JSONL at ${path}:${index + 1}: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function unixNano(isoTimestamp) {
|
|
31
|
+
return `${BigInt(Date.parse(isoTimestamp)) * 1_000_000n}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function otelValue(value) {
|
|
35
|
+
if (Array.isArray(value)) return { arrayValue: { values: value.map((item) => otelValue(item)) } };
|
|
36
|
+
if (typeof value === 'boolean') return { boolValue: value };
|
|
37
|
+
if (typeof value === 'number' && Number.isInteger(value)) return { intValue: String(value) };
|
|
38
|
+
if (typeof value === 'number') return { doubleValue: value };
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
if (value === 'true' || value === 'false') return { boolValue: value === 'true' };
|
|
41
|
+
if (/^-?\d+$/.test(value)) return { intValue: value };
|
|
42
|
+
return { stringValue: value };
|
|
43
|
+
}
|
|
44
|
+
if (value == null) return { stringValue: '' };
|
|
45
|
+
return { stringValue: JSON.stringify(value) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function attributesToOtel(attributes) {
|
|
49
|
+
return Object.entries(attributes).map(([key, value]) => ({ key, value: otelValue(value) }));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const records = readJsonl(inputPath);
|
|
53
|
+
const selection = records.find((record) => record.type === 'context.selection');
|
|
54
|
+
if (!selection) {
|
|
55
|
+
throw new Error(`No context.selection record found in ${inputPath}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const inputs = records.filter((record) => record.type === 'context.input');
|
|
59
|
+
if (inputs.length === 0) {
|
|
60
|
+
throw new Error(`No context.input records found in ${inputPath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const relevance = records.find((record) => record.type === 'context.decision.relevance');
|
|
64
|
+
const sessionId = selection.session_id ?? 'demo-session-context-selection';
|
|
65
|
+
const conversationId = selection.conversation_id ?? sessionId;
|
|
66
|
+
const traceId = sha256(`${sessionId}:trace`).replace('sha256:', '').slice(0, 32);
|
|
67
|
+
const spanId = sha256(`${sessionId}:span`).replace('sha256:', '').slice(0, 16);
|
|
68
|
+
|
|
69
|
+
const inputEvents = inputs.map((record) => {
|
|
70
|
+
const sourceIdentity = record.source_id ?? 'unknown-source';
|
|
71
|
+
const sourceHash = sha256(sourceIdentity);
|
|
72
|
+
const deliveredHash = sha256(`${sessionId}:${sourceIdentity}:${record.selection_rank ?? 'unknown'}:${record.token_bucket ?? 'unknown'}`);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
trace_id: traceId,
|
|
76
|
+
span_id: spanId,
|
|
77
|
+
name: 'context.input.loaded',
|
|
78
|
+
time: record.time,
|
|
79
|
+
attributes: {
|
|
80
|
+
'session.id': sessionId,
|
|
81
|
+
'gen_ai.conversation.id': conversationId,
|
|
82
|
+
'context.input.kind': record.kind ?? 'unknown',
|
|
83
|
+
'context.input.source.id_hash': sourceHash,
|
|
84
|
+
'context.input.source.role': record.source_role ?? 'unknown',
|
|
85
|
+
'context.input.selection.rank': record.selection_rank ?? 0,
|
|
86
|
+
'context.input.selection.status': record.selection_status ?? 'unknown',
|
|
87
|
+
'context.input.delivery.status': record.delivery_status ?? 'unknown',
|
|
88
|
+
'context.input.delivered.hash': deliveredHash,
|
|
89
|
+
'context.input.token_bucket': record.token_bucket ?? 'unknown',
|
|
90
|
+
'context.input.audit_gap': 'hashes and counts prove selected/delivered identity, not semantic usefulness'
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const selectionEvent = {
|
|
96
|
+
trace_id: traceId,
|
|
97
|
+
span_id: spanId,
|
|
98
|
+
name: 'context.input.selection.evaluated',
|
|
99
|
+
time: selection.time,
|
|
100
|
+
attributes: {
|
|
101
|
+
'session.id': sessionId,
|
|
102
|
+
'gen_ai.conversation.id': conversationId,
|
|
103
|
+
'context.selection.strategy': selection.selection_strategy ?? 'unknown',
|
|
104
|
+
'context.selection.policy': selection.selection_policy ?? 'unknown',
|
|
105
|
+
'context.input.candidate_count': selection.candidate_count ?? inputs.length,
|
|
106
|
+
'context.input.selected_count': selection.selected_count ?? inputs.length,
|
|
107
|
+
'context.input.suppressed_count': selection.suppressed_count ?? 0,
|
|
108
|
+
'context.input.delivered_hash_count': selection.delivered_hash_count ?? inputEvents.length,
|
|
109
|
+
'context.input.selected_token_bucket': selection.selected_token_bucket ?? 'unknown',
|
|
110
|
+
'context.input.suppressed_token_bucket': selection.suppressed_token_bucket ?? 'unknown',
|
|
111
|
+
'context.selection.operator_question': selection.operator_question ?? 'did_we_load_too_much_or_the_wrong_context',
|
|
112
|
+
'context.decision.relevance_evaluator': selection.decision_relevance_evaluator ?? 'not_available_yet',
|
|
113
|
+
'context.selection.audit_gap': selection.audit_gap ?? 'selection receipt proves delivery pressure, not semantic relevance'
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const events = [selectionEvent, ...inputEvents];
|
|
118
|
+
|
|
119
|
+
if (relevance) {
|
|
120
|
+
const selectedCount = relevance.selected_count ?? selection.selected_count ?? inputs.length;
|
|
121
|
+
const decisiveRanks = relevance.decisive_selection_ranks ?? [];
|
|
122
|
+
const supportingRanks = relevance.supporting_selection_ranks ?? relevance.relevant_selection_ranks ?? [];
|
|
123
|
+
const unusedRanks = relevance.unused_selection_ranks ?? [];
|
|
124
|
+
const unknownRanks = relevance.unknown_selection_ranks ?? [];
|
|
125
|
+
const decisiveCount = decisiveRanks.length;
|
|
126
|
+
const supportingCount = supportingRanks.length;
|
|
127
|
+
const unusedCount = unusedRanks.length;
|
|
128
|
+
const unknownCount = unknownRanks.length;
|
|
129
|
+
const accountedCount = decisiveCount + supportingCount + unusedCount + unknownCount;
|
|
130
|
+
|
|
131
|
+
if (decisiveCount + supportingCount > selectedCount) {
|
|
132
|
+
throw new Error(`Invalid relevance receipt: decisive_count + supporting_count (${decisiveCount + supportingCount}) exceeds selected_count (${selectedCount})`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (accountedCount !== selectedCount) {
|
|
136
|
+
throw new Error(`Invalid relevance receipt: selected_count (${selectedCount}) must equal decisive + supporting + unused + unknown (${accountedCount}) so over-selection does not disappear into a generic bucket`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const decisionInputRanks = [...decisiveRanks, ...supportingRanks];
|
|
140
|
+
const decisionInputHashes = inputs
|
|
141
|
+
.filter((input) => decisionInputRanks.includes(input.selection_rank))
|
|
142
|
+
.map((input) => sha256(`${sessionId}:${input.source_id}:${input.selection_rank}:${input.token_bucket}`));
|
|
143
|
+
|
|
144
|
+
events.push({
|
|
145
|
+
trace_id: traceId,
|
|
146
|
+
span_id: spanId,
|
|
147
|
+
name: 'context.decision.relevance.evaluated',
|
|
148
|
+
time: relevance.time,
|
|
149
|
+
attributes: {
|
|
150
|
+
'session.id': sessionId,
|
|
151
|
+
'gen_ai.conversation.id': conversationId,
|
|
152
|
+
'decision.id_hash': sha256(relevance.decision_id ?? 'unknown-decision'),
|
|
153
|
+
'context.input.selected_count': selectedCount,
|
|
154
|
+
'context.input.suppressed_count': relevance.suppressed_count ?? selection.suppressed_count ?? 0,
|
|
155
|
+
'context.input.delivered_hash_count': relevance.delivered_hash_count ?? selection.delivered_hash_count ?? inputEvents.length,
|
|
156
|
+
'context.decision.input_hashes': decisionInputHashes,
|
|
157
|
+
'context.decision.relevance.decisive_count': decisiveCount,
|
|
158
|
+
'context.decision.relevance.supporting_count': supportingCount,
|
|
159
|
+
'context.decision.relevance.unused_count': unusedCount,
|
|
160
|
+
'context.decision.relevance.unknown_count': unknownCount,
|
|
161
|
+
'context.decision.relevance.accounted_count': accountedCount,
|
|
162
|
+
'context.decision.relevance.invariant': 'selected_count == decisive_count + supporting_count + unused_count + unknown_count; decisive_count + supporting_count <= selected_count',
|
|
163
|
+
'context.decision.relevance.outcome': relevance.relevance_outcome ?? 'unknown',
|
|
164
|
+
'context.decision.evaluator': relevance.decision_relevance_evaluator ?? 'unknown',
|
|
165
|
+
'context.decision.audit_gap': relevance.audit_gap ?? 'relevance is evaluator-derived; loaded receipts only prove delivery'
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
writeFileSync(receiptPath, `${events.map((event) => JSON.stringify(event)).join('\n')}\n`);
|
|
171
|
+
|
|
172
|
+
const eventTimes = events.map((event) => Date.parse(event.time)).filter(Number.isFinite);
|
|
173
|
+
const startTimeMs = Math.min(...eventTimes);
|
|
174
|
+
const endTimeMs = Math.max(...eventTimes) + 1;
|
|
175
|
+
|
|
176
|
+
const otlpTrace = {
|
|
177
|
+
resourceSpans: [
|
|
178
|
+
{
|
|
179
|
+
resource: {
|
|
180
|
+
attributes: attributesToOtel({
|
|
181
|
+
'service.name': 'pluribus-context-selection-demo',
|
|
182
|
+
'service.version': '0.0.0-fixture',
|
|
183
|
+
'deployment.environment.name': 'local-fixture'
|
|
184
|
+
})
|
|
185
|
+
},
|
|
186
|
+
scopeSpans: [
|
|
187
|
+
{
|
|
188
|
+
scope: { name: 'pluribus.context_selection.demo', version: '0.0.0-fixture' },
|
|
189
|
+
spans: [
|
|
190
|
+
{
|
|
191
|
+
traceId,
|
|
192
|
+
spanId,
|
|
193
|
+
parentSpanId: '',
|
|
194
|
+
name: 'agent.session',
|
|
195
|
+
kind: 1,
|
|
196
|
+
startTimeUnixNano: `${BigInt(startTimeMs) * 1_000_000n}`,
|
|
197
|
+
endTimeUnixNano: `${BigInt(endTimeMs) * 1_000_000n}`,
|
|
198
|
+
attributes: attributesToOtel({
|
|
199
|
+
'session.id': sessionId,
|
|
200
|
+
'gen_ai.conversation.id': conversationId,
|
|
201
|
+
'gen_ai.agent.name': selection.agent ?? 'unknown',
|
|
202
|
+
'gen_ai.operation.name': 'agent_session'
|
|
203
|
+
}),
|
|
204
|
+
events: events.map((event) => ({
|
|
205
|
+
name: event.name,
|
|
206
|
+
timeUnixNano: unixNano(event.time),
|
|
207
|
+
attributes: attributesToOtel(event.attributes)
|
|
208
|
+
}))
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
writeFileSync(tracePath, `${JSON.stringify(otlpTrace, null, 2)}\n`);
|
|
218
|
+
|
|
219
|
+
const rawLeakNeedles = [
|
|
220
|
+
'Acme-Co',
|
|
221
|
+
'Stripe prod incident',
|
|
222
|
+
'/private/work/acme',
|
|
223
|
+
'sk_live_private_demo',
|
|
224
|
+
'private-demo-token',
|
|
225
|
+
'customer request payload'
|
|
226
|
+
];
|
|
227
|
+
const receiptText = readFileSync(receiptPath, 'utf8');
|
|
228
|
+
const traceText = readFileSync(tracePath, 'utf8');
|
|
229
|
+
const leakedNeedles = rawLeakNeedles.filter((needle) => receiptText.includes(needle) || traceText.includes(needle));
|
|
230
|
+
if (leakedNeedles.length > 0) {
|
|
231
|
+
throw new Error(`Raw private fixture strings leaked into receipt/trace: ${leakedNeedles.join(', ')}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log(JSON.stringify({
|
|
235
|
+
schema: 'pluribus.contextSelectionOverSelectionDemo.v0',
|
|
236
|
+
inputPath,
|
|
237
|
+
receiptPath,
|
|
238
|
+
tracePath,
|
|
239
|
+
sessionId,
|
|
240
|
+
eventCount: events.length,
|
|
241
|
+
selectedCount: selection.selected_count,
|
|
242
|
+
suppressedCount: selection.suppressed_count,
|
|
243
|
+
deliveredHashCount: selection.delivered_hash_count,
|
|
244
|
+
hasDecisionRelevanceEvent: Boolean(relevance),
|
|
245
|
+
privacyDefault: 'outputs hashes, buckets, counts, ranks, categorical fields, and audit gaps; does not copy raw prompts, customer names, private paths, secrets, tool output, or memory bodies',
|
|
246
|
+
lesson: 'The cheap first signal is over-selection: selected_count and delivered_hash_count can show too much context crossed the boundary before any relevance evaluator exists. When relevance exists, decisive/supporting/unused/unknown counts must account for selected_count so over-selection stays explicit.'
|
|
247
|
+
}, null, 2));
|