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.
Files changed (93) hide show
  1. package/CHANGELOG.md +25 -3
  2. package/README.md +2 -2
  3. package/docs/community-review-packet.md +2 -1
  4. package/docs/context-budget-receipts.md +150 -0
  5. package/docs/context-input-evidence.md +397 -0
  6. package/docs/context-receipts-for-agent-observability.md +177 -0
  7. package/docs/orchestration-search-receipts.md +102 -0
  8. package/docs/portability-fidelity-report.md +4 -2
  9. package/examples/context-input-evidence/AGENTS.md +12 -0
  10. package/examples/context-input-evidence/agent-overlay-log.jsonl +4 -0
  11. package/examples/context-input-evidence/agent-overlay-otel-trace.json +548 -0
  12. package/examples/context-input-evidence/agent-overlay-receipt.ndjson +3 -0
  13. package/examples/context-input-evidence/agentgateway-progressive-disclosure-otel-trace.json +393 -0
  14. package/examples/context-input-evidence/agentgateway-progressive-disclosure-receipt.ndjson +4 -0
  15. package/examples/context-input-evidence/brain-remediation-otel-trace.json +645 -0
  16. package/examples/context-input-evidence/brain-remediation-receipt.ndjson +7 -0
  17. package/examples/context-input-evidence/claudekit-mcp-manager-otel-trace.json +417 -0
  18. package/examples/context-input-evidence/claudekit-mcp-manager-receipt.ndjson +5 -0
  19. package/examples/context-input-evidence/cli-progressive-disclosure-otel-trace.json +399 -0
  20. package/examples/context-input-evidence/cli-progressive-disclosure-receipt.ndjson +4 -0
  21. package/examples/context-input-evidence/compaction-otel-trace.json +711 -0
  22. package/examples/context-input-evidence/compaction-receipt.ndjson +6 -0
  23. package/examples/context-input-evidence/context-selection-otel-trace.json +627 -0
  24. package/examples/context-input-evidence/context-selection-receipt.ndjson +7 -0
  25. package/examples/context-input-evidence/convert-agent-overlay-log.mjs +156 -0
  26. package/examples/context-input-evidence/convert-agentgateway-progressive-disclosure-log.mjs +251 -0
  27. package/examples/context-input-evidence/convert-brain-remediation-log.mjs +241 -0
  28. package/examples/context-input-evidence/convert-claudekit-mcp-manager-log.mjs +253 -0
  29. package/examples/context-input-evidence/convert-cli-progressive-disclosure-log.mjs +251 -0
  30. package/examples/context-input-evidence/convert-compaction-log.mjs +224 -0
  31. package/examples/context-input-evidence/convert-context-selection-log.mjs +247 -0
  32. package/examples/context-input-evidence/convert-mcp-tool-search-log.mjs +242 -0
  33. package/examples/context-input-evidence/convert-memory-consolidation-log.mjs +240 -0
  34. package/examples/context-input-evidence/convert-memory-governance-delete-log.mjs +223 -0
  35. package/examples/context-input-evidence/convert-memory-log.mjs +226 -0
  36. package/examples/context-input-evidence/convert-memory-provenance-log.mjs +263 -0
  37. package/examples/context-input-evidence/convert-secret-scanning-log.mjs +233 -0
  38. package/examples/context-input-evidence/convert-session-log.mjs +186 -0
  39. package/examples/context-input-evidence/convert-skill-log.mjs +161 -0
  40. package/examples/context-input-evidence/convert-skill-registry-log.mjs +246 -0
  41. package/examples/context-input-evidence/convert-skill-routing-log.mjs +253 -0
  42. package/examples/context-input-evidence/convert-subagent-context-budget-log.mjs +267 -0
  43. package/examples/context-input-evidence/convert-subagent-delegation-log.mjs +264 -0
  44. package/examples/context-input-evidence/export-otel-trace.mjs +128 -0
  45. package/examples/context-input-evidence/generate-receipt.mjs +188 -0
  46. package/examples/context-input-evidence/mcp-tool-search-otel-trace.json +477 -0
  47. package/examples/context-input-evidence/mcp-tool-search-receipt.ndjson +5 -0
  48. package/examples/context-input-evidence/memory-consolidation-otel-trace.json +492 -0
  49. package/examples/context-input-evidence/memory-consolidation-receipt.ndjson +4 -0
  50. package/examples/context-input-evidence/memory-governance-delete-otel-trace.json +614 -0
  51. package/examples/context-input-evidence/memory-governance-delete-receipt.ndjson +5 -0
  52. package/examples/context-input-evidence/memory-otel-trace.json +645 -0
  53. package/examples/context-input-evidence/memory-provenance-otel-trace.json +711 -0
  54. package/examples/context-input-evidence/memory-provenance-receipt.ndjson +5 -0
  55. package/examples/context-input-evidence/memory-receipt.ndjson +4 -0
  56. package/examples/context-input-evidence/otel-trace.json +1119 -0
  57. package/examples/context-input-evidence/receipt.ndjson +6 -0
  58. package/examples/context-input-evidence/sample-agentgateway-progressive-disclosure-log.jsonl +5 -0
  59. package/examples/context-input-evidence/sample-brain-remediation-log.jsonl +9 -0
  60. package/examples/context-input-evidence/sample-claudekit-mcp-manager-log.jsonl +6 -0
  61. package/examples/context-input-evidence/sample-cli-progressive-disclosure-log.jsonl +5 -0
  62. package/examples/context-input-evidence/sample-compaction-log.jsonl +7 -0
  63. package/examples/context-input-evidence/sample-context-selection-log.jsonl +7 -0
  64. package/examples/context-input-evidence/sample-mcp-tool-search-log.jsonl +6 -0
  65. package/examples/context-input-evidence/sample-memory-consolidation-log.jsonl +5 -0
  66. package/examples/context-input-evidence/sample-memory-governance-delete-log.jsonl +6 -0
  67. package/examples/context-input-evidence/sample-memory-provenance-log.jsonl +6 -0
  68. package/examples/context-input-evidence/sample-memory-retrieval-log.jsonl +6 -0
  69. package/examples/context-input-evidence/sample-secret-scanning-log.jsonl +7 -0
  70. package/examples/context-input-evidence/sample-session-log.jsonl +6 -0
  71. package/examples/context-input-evidence/sample-skill-registry-log.jsonl +5 -0
  72. package/examples/context-input-evidence/sample-skill-routing-log.jsonl +7 -0
  73. package/examples/context-input-evidence/sample-subagent-context-budget-log.jsonl +6 -0
  74. package/examples/context-input-evidence/sample-subagent-delegation-log.jsonl +5 -0
  75. package/examples/context-input-evidence/secret-scanning-otel-trace.json +794 -0
  76. package/examples/context-input-evidence/secret-scanning-receipt.ndjson +6 -0
  77. package/examples/context-input-evidence/session-otel-trace.json +411 -0
  78. package/examples/context-input-evidence/session-receipt.ndjson +2 -0
  79. package/examples/context-input-evidence/skill-invocation-log.jsonl +4 -0
  80. package/examples/context-input-evidence/skill-otel-trace.json +548 -0
  81. package/examples/context-input-evidence/skill-receipt.ndjson +3 -0
  82. package/examples/context-input-evidence/skill-registry-otel-trace.json +471 -0
  83. package/examples/context-input-evidence/skill-registry-receipt.ndjson +5 -0
  84. package/examples/context-input-evidence/skill-routing-otel-trace.json +567 -0
  85. package/examples/context-input-evidence/skill-routing-receipt.ndjson +6 -0
  86. package/examples/context-input-evidence/subagent-context-budget-otel-trace.json +507 -0
  87. package/examples/context-input-evidence/subagent-context-budget-receipt.ndjson +5 -0
  88. package/examples/context-input-evidence/subagent-delegation-otel-trace.json +388 -0
  89. package/examples/context-input-evidence/subagent-delegation-receipt.ndjson +4 -0
  90. package/package.json +6 -2
  91. package/schemas/audit-result.schema.json +409 -71
  92. package/src/commands/audit.js +64 -3
  93. package/src/utils/version.js +1 -1
@@ -0,0 +1,223 @@
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-memory-governance-delete-log.jsonl');
9
+ const receiptPath = process.argv[3] ? resolve(process.argv[3]) : join(here, 'memory-governance-delete-receipt.ndjson');
10
+ const tracePath = process.argv[4] ? resolve(process.argv[4]) : join(here, 'memory-governance-delete-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 hashList(values = []) {
21
+ return sha256(values.join('\n'));
22
+ }
23
+
24
+ function readJsonl(path) {
25
+ return readFileSync(path, 'utf8')
26
+ .trim()
27
+ .split('\n')
28
+ .filter(Boolean)
29
+ .map((line, index) => {
30
+ try {
31
+ return JSON.parse(line);
32
+ } catch (error) {
33
+ throw new Error(`Invalid JSONL at ${path}:${index + 1}: ${error.message}`);
34
+ }
35
+ });
36
+ }
37
+
38
+ function unixNano(isoTimestamp) {
39
+ return `${BigInt(Date.parse(isoTimestamp)) * 1_000_000n}`;
40
+ }
41
+
42
+ function otelValue(value) {
43
+ if (typeof value === 'boolean') return { boolValue: value };
44
+ if (typeof value === 'number' && Number.isInteger(value)) return { intValue: String(value) };
45
+ if (typeof value === 'number') return { doubleValue: value };
46
+ if (value == null) return { stringValue: '' };
47
+ return { stringValue: String(value) };
48
+ }
49
+
50
+ function attributesToOtel(attributes) {
51
+ return Object.entries(attributes).map(([key, value]) => ({ key, value: otelValue(value) }));
52
+ }
53
+
54
+ function countBucket(value) {
55
+ if (value === 0) return 'zero';
56
+ if (value <= 2) return 'under_2';
57
+ if (value <= 5) return 'under_5';
58
+ return 'over_5';
59
+ }
60
+
61
+ function durationBucket(ms) {
62
+ if (ms < 1_000) return 'under_1s';
63
+ if (ms < 10_000) return 'under_10s';
64
+ return 'over_10s';
65
+ }
66
+
67
+ const records = readJsonl(inputPath);
68
+ const session = records.find((record) => record.type === 'session.start');
69
+ const requested = records.find((record) => record.type === 'memory.governance.delete.requested');
70
+ const candidates = records.find((record) => record.type === 'memory.governance.delete.candidates.presented');
71
+ const confirmation = records.find((record) => record.type === 'memory.governance.delete.confirmation.recorded');
72
+ const completed = records.find((record) => record.type === 'memory.governance.delete.completed');
73
+ const audit = records.find((record) => record.type === 'memory.governance.audit.completed');
74
+
75
+ if (!session || !requested || !candidates || !confirmation || !completed || !audit) {
76
+ throw new Error(`Expected session.start and all memory.governance.delete/audit records in ${inputPath}`);
77
+ }
78
+
79
+ const traceSeed = `${session.session_id}:${session.conversation_id}:memory-governance-delete`;
80
+ const traceId = sha256(traceSeed).replace('sha256:', '').slice(0, 32);
81
+ const spanId = sha256(`${traceSeed}:span`).replace('sha256:', '').slice(0, 16);
82
+
83
+ const baseAttrs = {
84
+ 'session.id': session.session_id,
85
+ 'gen_ai.conversation.id': session.conversation_id,
86
+ 'agent.name': session.agent,
87
+ 'memory.provider': session.provider,
88
+ 'memory.client': session.client,
89
+ 'memory.project.hash': hashRef(session.project)
90
+ };
91
+
92
+ const events = [
93
+ {
94
+ name: 'memory.governance.delete.requested',
95
+ time: requested.time,
96
+ attributes: {
97
+ ...baseAttrs,
98
+ 'memory.governance.request.hash': hashRef(requested.request_id),
99
+ 'memory.governance.trigger': requested.trigger,
100
+ 'memory.governance.requested_by_hash': hashRef(requested.requested_by),
101
+ 'memory.governance.reason_hash': sha256(requested.reason),
102
+ 'memory.governance.query_hash': sha256(requested.query),
103
+ 'memory.governance.scope': requested.scope,
104
+ 'memory.governance.delete.policy': requested.policy,
105
+ 'memory.governance.sensitive_class_count': requested.sensitive_classes.length,
106
+ 'memory.governance.sensitive_classes_hash': hashList(requested.sensitive_classes),
107
+ 'memory.governance.project_path_hash': sha256(requested.project_path),
108
+ 'privacy.raw_query_recorded': false,
109
+ 'privacy.raw_reason_recorded': false,
110
+ 'privacy.raw_project_path_recorded': false
111
+ }
112
+ },
113
+ {
114
+ name: 'memory.governance.delete.candidates.presented',
115
+ time: candidates.time,
116
+ attributes: {
117
+ ...baseAttrs,
118
+ 'memory.governance.request.hash': hashRef(candidates.request_id),
119
+ 'memory.governance.candidate_count': candidates.candidate_count,
120
+ 'memory.governance.candidate_count_bucket': countBucket(candidates.candidate_count),
121
+ 'memory.governance.candidate_ids_hash': hashList(candidates.candidate_ids),
122
+ 'memory.governance.preview_policy': candidates.preview_policy,
123
+ 'memory.governance.preview_hash': sha256(candidates.previewed_candidate_text.join('\n')),
124
+ 'memory.governance.requires_confirmation': candidates.requires_confirmation,
125
+ 'privacy.raw_candidate_text_recorded': false,
126
+ 'privacy.raw_preview_recorded': false
127
+ }
128
+ },
129
+ {
130
+ name: 'memory.governance.delete.confirmation.recorded',
131
+ time: confirmation.time,
132
+ attributes: {
133
+ ...baseAttrs,
134
+ 'memory.governance.request.hash': hashRef(confirmation.request_id),
135
+ 'memory.governance.confirmation.hash': hashRef(confirmation.confirmation_id),
136
+ 'memory.governance.confirmed': confirmation.confirmed,
137
+ 'memory.governance.confirmation_channel': confirmation.confirmation_channel,
138
+ 'memory.governance.confirmed_candidate_count': confirmation.confirmed_candidate_ids.length,
139
+ 'memory.governance.confirmed_candidate_ids_hash': hashList(confirmation.confirmed_candidate_ids),
140
+ 'memory.governance.rejected_candidate_count': confirmation.rejected_candidate_ids.length,
141
+ 'memory.governance.rejected_candidate_ids_hash': hashList(confirmation.rejected_candidate_ids),
142
+ 'memory.governance.operator_note_hash': sha256(confirmation.operator_note),
143
+ 'privacy.raw_operator_note_recorded': false
144
+ }
145
+ },
146
+ {
147
+ name: 'memory.governance.delete.completed',
148
+ time: completed.time,
149
+ attributes: {
150
+ ...baseAttrs,
151
+ 'memory.governance.request.hash': hashRef(completed.request_id),
152
+ 'memory.governance.status': completed.status,
153
+ 'memory.governance.deleted_count': completed.deleted_ids.length,
154
+ 'memory.governance.deleted_ids_hash': hashList(completed.deleted_ids),
155
+ 'memory.governance.retained_count': completed.retained_ids.length,
156
+ 'memory.governance.retained_ids_hash': hashList(completed.retained_ids),
157
+ 'memory.governance.tombstone_count': completed.tombstone_ids.length,
158
+ 'memory.governance.tombstone_ids_hash': hashList(completed.tombstone_ids),
159
+ 'memory.governance.audit_entry_hash': hashRef(completed.audit_entry_id),
160
+ 'memory.governance.store_snapshot_before_hash': sha256(completed.store_snapshot_before),
161
+ 'memory.governance.store_snapshot_after_hash': sha256(completed.store_snapshot_after),
162
+ 'memory.governance.latency_bucket': durationBucket(completed.latency_ms),
163
+ 'privacy.raw_deleted_memory_recorded': false
164
+ }
165
+ },
166
+ {
167
+ name: 'memory.governance.audit.completed',
168
+ time: audit.time,
169
+ attributes: {
170
+ ...baseAttrs,
171
+ 'memory.governance.request.hash': hashRef(audit.request_id),
172
+ 'memory.governance.audit_entry_hash': hashRef(audit.audit_entry_id),
173
+ 'memory.governance.query_replay_result_count': audit.query_replay_result_count,
174
+ 'memory.governance.tombstone_count': audit.tombstone_count,
175
+ 'memory.governance.retained_count': audit.retained_count,
176
+ 'memory.governance.retention_policy': audit.retention_policy,
177
+ 'memory.governance.audit_gap': audit.audit_gap,
178
+ 'privacy.raw_memory_body_recorded': false,
179
+ 'privacy.raw_delete_query_recorded': false
180
+ }
181
+ }
182
+ ].map((event) => ({ trace_id: traceId, span_id: spanId, ...event }));
183
+
184
+ const receipt = events.map((event) => JSON.stringify(event)).join('\n') + '\n';
185
+ writeFileSync(receiptPath, receipt);
186
+
187
+ const trace = {
188
+ resourceSpans: [
189
+ {
190
+ resource: {
191
+ attributes: attributesToOtel({
192
+ 'service.name': 'pluribus-context-input-evidence-demo',
193
+ 'service.version': '0.3.23'
194
+ })
195
+ },
196
+ scopeSpans: [
197
+ {
198
+ scope: { name: 'pluribus.context_input_evidence.memory_governance_delete', version: '0.1.0' },
199
+ spans: [
200
+ {
201
+ traceId,
202
+ spanId,
203
+ name: 'agent.session',
204
+ kind: 1,
205
+ startTimeUnixNano: unixNano(session.time),
206
+ endTimeUnixNano: unixNano(audit.time),
207
+ attributes: attributesToOtel(baseAttrs),
208
+ events: events.map((event) => ({
209
+ name: event.name,
210
+ timeUnixNano: unixNano(event.time),
211
+ attributes: attributesToOtel(event.attributes)
212
+ }))
213
+ }
214
+ ]
215
+ }
216
+ ]
217
+ }
218
+ ]
219
+ };
220
+
221
+ writeFileSync(tracePath, `${JSON.stringify(trace, null, 2)}\n`);
222
+ console.log(`wrote ${events.length} memory governance delete receipt events to ${receiptPath}`);
223
+ console.log(`wrote OpenTelemetry-style trace to ${tracePath}`);
@@ -0,0 +1,226 @@
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-memory-retrieval-log.jsonl');
9
+ const receiptPath = process.argv[3] ? resolve(process.argv[3]) : join(here, 'memory-receipt.ndjson');
10
+ const tracePath = process.argv[4] ? resolve(process.argv[4]) : join(here, 'memory-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 canonicalize(text) {
21
+ return text.normalize('NFC').replace(/\r\n/g, '\n');
22
+ }
23
+
24
+ function scoreBucket(score) {
25
+ if (score >= 0.9) return 'very_high';
26
+ if (score >= 0.75) return 'high';
27
+ if (score >= 0.5) return 'medium';
28
+ if (score > 0) return 'low';
29
+ return 'unknown';
30
+ }
31
+
32
+ function readJsonl(path) {
33
+ return readFileSync(path, 'utf8')
34
+ .trim()
35
+ .split('\n')
36
+ .filter(Boolean)
37
+ .map((line, index) => {
38
+ try {
39
+ return JSON.parse(line);
40
+ } catch (error) {
41
+ throw new Error(`Invalid JSONL at ${path}:${index + 1}: ${error.message}`);
42
+ }
43
+ });
44
+ }
45
+
46
+ function unixNano(isoTimestamp) {
47
+ return `${BigInt(Date.parse(isoTimestamp)) * 1_000_000n}`;
48
+ }
49
+
50
+ function otelValue(value) {
51
+ if (typeof value === 'boolean') return { boolValue: value };
52
+ if (typeof value === 'number' && Number.isInteger(value)) return { intValue: String(value) };
53
+ if (typeof value === 'number') return { doubleValue: value };
54
+ if (typeof value === 'string') {
55
+ if (value === 'true' || value === 'false') return { boolValue: value === 'true' };
56
+ if (/^-?\d+$/.test(value)) return { intValue: value };
57
+ return { stringValue: value };
58
+ }
59
+ if (value == null) return { stringValue: '' };
60
+ return { stringValue: JSON.stringify(value) };
61
+ }
62
+
63
+ function attributesToOtel(attributes) {
64
+ return Object.entries(attributes).map(([key, value]) => ({ key, value: otelValue(value) }));
65
+ }
66
+
67
+ const records = readJsonl(inputPath);
68
+ const sessionStart = records.find((record) => record.type === 'session.start') ?? {};
69
+ const sessionEnd = [...records].reverse().find((record) => record.type === 'session.end') ?? {};
70
+ const retrievalRecords = records.filter((record) => record.type === 'memory.search');
71
+ const loadedRecords = records.filter((record) => record.type === 'context.input');
72
+
73
+ if (retrievalRecords.length === 0) {
74
+ throw new Error(`No memory.search records found in ${inputPath}`);
75
+ }
76
+
77
+ const sessionId = sessionStart.session_id ?? 'unknown-session';
78
+ const conversationId = sessionStart.conversation_id ?? sessionId;
79
+ const traceId = sha256(`${sessionId}:memory-trace`).replace('sha256:', '').slice(0, 32);
80
+ const spanId = sha256(`${sessionId}:memory-span`).replace('sha256:', '').slice(0, 16);
81
+
82
+ const searchEvents = retrievalRecords.map((record) => {
83
+ const resultHashes = (record.results ?? []).map((result) => sha256(result.text ?? ''));
84
+ const resultIdHashes = (record.results ?? []).map((result) => hashRef(result.id ?? result.uri ?? ''));
85
+ const topResult = (record.results ?? [])[0] ?? {};
86
+
87
+ return {
88
+ trace_id: traceId,
89
+ span_id: spanId,
90
+ name: 'memory.search.returned',
91
+ time: record.time,
92
+ attributes: {
93
+ 'memory.provider': record.provider ?? 'unknown',
94
+ 'memory.client': record.client ?? 'unknown',
95
+ 'memory.project.hash': hashRef(record.project ?? ''),
96
+ 'memory.retrieval.id_hash': hashRef(record.retrieval_id ?? ''),
97
+ 'memory.query.hash': sha256(record.query_text ?? ''),
98
+ 'memory.snapshot.id_hash': hashRef(record.snapshot_id ?? ''),
99
+ 'memory.result.count': (record.results ?? []).length,
100
+ 'memory.result.ids_hash': sha256(resultIdHashes.join('\n')),
101
+ 'memory.result.payloads_hash': sha256(resultHashes.join('\n')),
102
+ 'memory.result.top.id_hash': hashRef(topResult.id ?? topResult.uri ?? ''),
103
+ 'memory.result.top.score_bucket': scoreBucket(topResult.score ?? 0),
104
+ 'memory.latency_ms': record.latency_ms ?? 0,
105
+ 'memory.privacy.raw_query_recorded': 'false',
106
+ 'memory.privacy.raw_result_recorded': 'false',
107
+ 'session.id': sessionId,
108
+ 'gen_ai.conversation.id': conversationId
109
+ }
110
+ };
111
+ });
112
+
113
+ const loadEvents = loadedRecords.map((record) => {
114
+ const deliveredText = record.delivered_text ?? '';
115
+ const deliveredCanonicalText = canonicalize(deliveredText);
116
+ const deliveredHash = sha256(deliveredText);
117
+ const duplicateRole = record.duplicate_role ?? 'selected';
118
+
119
+ return {
120
+ trace_id: traceId,
121
+ span_id: spanId,
122
+ name: 'context.input.loaded',
123
+ time: record.time,
124
+ attributes: {
125
+ 'context.input.kind': record.kind ?? 'mcp_memory',
126
+ 'context.input.source.uri_hash': hashRef(record.source_uri ?? ''),
127
+ 'context.input.source.memory_id_hash': hashRef(record.memory_id ?? record.source_uri ?? ''),
128
+ 'context.input.source.bytes_hash': sha256(deliveredText),
129
+ 'context.input.source.canonical.form': 'otel.context.source.nfc_lf.v1_candidate',
130
+ 'context.input.source.canonical.hash': sha256(deliveredCanonicalText),
131
+ 'context.input.source.canonicalization': 'utf8,unicode_nfc,crlf_to_lf',
132
+ 'context.input.delivered.hash': deliveredHash,
133
+ 'context.input.delivered.full_render.hash': deliveredHash,
134
+ 'context.input.delivered.full_render.status': 'available',
135
+ 'context.input.delivered.transform': 'as-returned-by-memory-client',
136
+ 'context.input.delivered.nondeterministic': 'false',
137
+ 'context.input.delivered.truncated': 'false',
138
+ 'context.input.loaded_by': record.loaded_by ?? 'unknown',
139
+ 'context.input.activation': record.activation ?? 'unknown',
140
+ 'context.input.scope': record.scope ?? 'unknown',
141
+ 'context.input.applies_to': record.applies_to ?? sessionStart.agent ?? 'unknown',
142
+ 'context.input.why_loaded': record.why_loaded ?? 'unknown',
143
+ 'context.input.expected_benefit': record.expected_benefit ?? 'unknown',
144
+ 'context.input.retrieval.id_hash': hashRef(record.retrieval_id ?? ''),
145
+ 'context.input.memory.id_hash': hashRef(record.memory_id ?? record.source_uri ?? ''),
146
+ 'context.input.duplicate.dedupe_key': `${conversationId}:${deliveredHash}`,
147
+ 'context.input.duplicate.dedupe_scope': 'conversation',
148
+ 'context.input.duplicate.suppression_policy': duplicateRole === 'candidate_duplicate'
149
+ ? 'keep_distinct_cross_client_load_until_loaded_receipt_proves_suppression'
150
+ : 'suppress_equal_dedupe_key_within_scope',
151
+ 'context.input.duplicate.role': duplicateRole,
152
+ 'context.input.duplicate.risk': duplicateRole === 'candidate_duplicate' ? 'cross_client_duplicate_possible' : 'unknown',
153
+ 'session.id': sessionId,
154
+ 'gen_ai.conversation.id': conversationId
155
+ }
156
+ };
157
+ });
158
+
159
+ const events = [...searchEvents, ...loadEvents].sort((left, right) => Date.parse(left.time) - Date.parse(right.time));
160
+ writeFileSync(receiptPath, `${events.map((event) => JSON.stringify(event)).join('\n')}\n`);
161
+
162
+ const eventTimes = records.map((record) => Date.parse(record.time)).filter(Number.isFinite);
163
+ const startTimeMs = Number.isFinite(Date.parse(sessionStart.time)) ? Date.parse(sessionStart.time) : Math.min(...eventTimes);
164
+ const endTimeMs = Number.isFinite(Date.parse(sessionEnd.time)) ? Date.parse(sessionEnd.time) : Math.max(...eventTimes) + 1;
165
+
166
+ const otlpTrace = {
167
+ resourceSpans: [
168
+ {
169
+ resource: {
170
+ attributes: attributesToOtel({
171
+ 'service.name': 'pluribus-shared-memory-receipt-demo',
172
+ 'service.version': '0.0.0-fixture',
173
+ 'deployment.environment.name': 'local-fixture'
174
+ })
175
+ },
176
+ scopeSpans: [
177
+ {
178
+ scope: {
179
+ name: 'pluribus.context_input_evidence.memory_receipt_demo',
180
+ version: '0.0.0-fixture'
181
+ },
182
+ spans: [
183
+ {
184
+ traceId,
185
+ spanId,
186
+ parentSpanId: '',
187
+ name: 'agent.session',
188
+ kind: 1,
189
+ startTimeUnixNano: `${BigInt(startTimeMs) * 1_000_000n}`,
190
+ endTimeUnixNano: `${BigInt(endTimeMs) * 1_000_000n}`,
191
+ attributes: attributesToOtel({
192
+ 'session.id': sessionId,
193
+ 'gen_ai.conversation.id': conversationId,
194
+ 'gen_ai.agent.name': sessionStart.agent ?? 'unknown',
195
+ 'gen_ai.operation.name': 'agent_session',
196
+ 'code.repository.name': sessionStart.repo ?? '',
197
+ 'pluribus.memory_search.count': retrievalRecords.length,
198
+ 'pluribus.memory_load.count': loadedRecords.length
199
+ }),
200
+ events: events.map((event) => ({
201
+ name: event.name,
202
+ timeUnixNano: unixNano(event.time),
203
+ attributes: attributesToOtel(event.attributes)
204
+ }))
205
+ }
206
+ ]
207
+ }
208
+ ]
209
+ }
210
+ ]
211
+ };
212
+
213
+ writeFileSync(tracePath, `${JSON.stringify(otlpTrace, null, 2)}\n`);
214
+
215
+ console.log(JSON.stringify({
216
+ schema: 'pluribus.contextInputEvidence.memoryReceiptDemo.v0',
217
+ inputPath,
218
+ receiptPath,
219
+ tracePath,
220
+ sessionId,
221
+ conversationId,
222
+ memorySearchEventCount: searchEvents.length,
223
+ contextInputEventCount: loadEvents.length,
224
+ privacyDefault: 'outputs hashes, counts, buckets, categorical fields, and session identifiers; does not copy raw query text, memory contents, prompts, tool arguments, secrets, or transcript bodies',
225
+ lesson: 'Shared-memory clients need two receipts: what memory search returned and what the harness actually loaded into context.'
226
+ }, null, 2));