pluribus-context 0.3.35 → 0.3.37

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 (70) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -2
  3. package/bin/pluribus.js +12 -0
  4. package/docs/agent-firewall-denial-audit.md +95 -0
  5. package/docs/ai-pr-review-receipts.md +20 -0
  6. package/docs/community-review-packet.md +19 -0
  7. package/docs/compaction-resume-receipts.md +43 -0
  8. package/docs/context-budget-receipts.md +1 -1
  9. package/docs/controlled-learning-queue.md +48 -0
  10. package/docs/install-plan-receipts.md +2 -0
  11. package/docs/loaded-resource-boundary.md +97 -0
  12. package/docs/mcp-runtime-config-receipts.md +91 -0
  13. package/docs/memory-write-policy-receipts.md +41 -0
  14. package/docs/parallel-session-review-ledger.md +103 -0
  15. package/docs/phase-boundary-contracts.md +87 -0
  16. package/docs/review-primitive-gate.md +2 -0
  17. package/docs/skill-install-receipts.md +102 -0
  18. package/docs/skill-policy-receipts.md +1 -1
  19. package/docs/skill-use-rate-receipts.md +104 -0
  20. package/examples/agent-firewall-denial-audit/README.md +14 -0
  21. package/examples/agent-firewall-denial-audit/check-denial-audit.mjs +116 -0
  22. package/examples/agent-firewall-denial-audit/denial-envelope.json +9 -0
  23. package/examples/agent-firewall-denial-audit/operator-audit-record.json +20 -0
  24. package/examples/agent-skills/README.md +10 -0
  25. package/examples/ai-pr-review-receipts/.github/workflows/ai-pr-review-receipt.yml +25 -0
  26. package/examples/ai-pr-review-receipts/README.md +51 -1
  27. package/examples/ai-pr-review-receipts/incomplete-review-primitive-receipt.json +43 -0
  28. package/examples/ai-pr-review-receipts/review-primitive-receipt.json +60 -0
  29. package/examples/compaction-resume-receipts/README.md +12 -0
  30. package/examples/compaction-resume-receipts/check-resume-receipt.mjs +116 -0
  31. package/examples/compaction-resume-receipts/safe-resume-receipt.json +52 -0
  32. package/examples/compaction-resume-receipts/unsafe-resume-receipt.json +41 -0
  33. package/examples/controlled-learning-queue/README.md +26 -0
  34. package/examples/controlled-learning-queue/check-learning-queue.mjs +44 -0
  35. package/examples/controlled-learning-queue/leads/acme-job-card.md +12 -0
  36. package/examples/controlled-learning-queue/learning_queue.md +27 -0
  37. package/examples/controlled-learning-queue/memory/durable.md +10 -0
  38. package/examples/controlled-learning-queue/memory/working-notes.md +5 -0
  39. package/examples/controlled-learning-queue/role/job-contract.md +18 -0
  40. package/examples/controlled-learning-queue/skills/qualify-lead.md +17 -0
  41. package/examples/loaded-resource-boundary/README.md +22 -0
  42. package/examples/loaded-resource-boundary/check-loaded-resource-boundary.mjs +65 -0
  43. package/examples/loaded-resource-boundary/loaded-resource-boundary.json +69 -0
  44. package/examples/mcp-runtime-config-receipts/README.md +15 -0
  45. package/examples/mcp-runtime-config-receipts/check-mcp-runtime-config-receipt.mjs +127 -0
  46. package/examples/mcp-runtime-config-receipts/mcp-runtime-config-receipt.json +82 -0
  47. package/examples/memory-write-policy/README.md +28 -0
  48. package/examples/memory-write-policy/approved-memory-update.json +48 -0
  49. package/examples/memory-write-policy/check-memory-update.mjs +120 -0
  50. package/examples/memory-write-policy/quarantined-memory-update.json +43 -0
  51. package/examples/parallel-session-review-ledger/README.md +13 -0
  52. package/examples/parallel-session-review-ledger/check-parallel-session-review-ledger.mjs +69 -0
  53. package/examples/parallel-session-review-ledger/parallel-session-review-ledger.json +72 -0
  54. package/examples/phase-boundary-contract/README.md +23 -0
  55. package/examples/phase-boundary-contract/check-phase-boundary.mjs +73 -0
  56. package/examples/phase-boundary-contract/phase-boundary-contract.json +68 -0
  57. package/examples/skill-install-receipts/README.md +31 -0
  58. package/examples/skill-install-receipts/check-skill-install-receipt.mjs +75 -0
  59. package/examples/skill-install-receipts/skill-install-receipt.json +79 -0
  60. package/examples/skill-use-rate-receipts/README.md +16 -0
  61. package/examples/skill-use-rate-receipts/check-skill-use-rate.mjs +89 -0
  62. package/examples/skill-use-rate-receipts/skill-use-rate-receipt.json +79 -0
  63. package/package.json +2 -1
  64. package/{examples/agent-skills → skills}/context-receipts/README.md +4 -4
  65. package/skills/context-receipts/SKILL.md +206 -0
  66. package/{examples/agent-skills → skills}/skill-policy-receipts/README.md +1 -1
  67. package/skills/skill-policy-receipts/SKILL.md +77 -0
  68. package/src/commands/demo.js +155 -0
  69. package/src/index.js +1 -0
  70. package/src/utils/version.js +1 -1
@@ -0,0 +1,69 @@
1
+ {
2
+ "receipt_type": "pluribus.loaded_resource_boundary.v1",
3
+ "scenario": "custom-agent skill parity across chat and ACP/Zed",
4
+ "expected_resources": [
5
+ {
6
+ "id": "skill:pr-review",
7
+ "kind": "skill",
8
+ "scope": "project",
9
+ "source_ref": ".kiro/skills/pr-review/SKILL.md",
10
+ "source_hash": "sha256:7fb8c53b1b1f9b0e0f5a6fdab315b748c64c8b926fdff3d1d6fe6b3f5c8c6a01",
11
+ "required": true
12
+ },
13
+ {
14
+ "id": "skill:release-notes",
15
+ "kind": "skill",
16
+ "scope": "project",
17
+ "source_ref": ".kiro/skills/release-notes/SKILL.md",
18
+ "source_hash": "sha256:290d6bbf8d43b5b3f9134718ce9f7b6d08cc25f1a0e9255f5b8ceadcab4e6c18",
19
+ "required": false
20
+ }
21
+ ],
22
+ "sessions": [
23
+ {
24
+ "session_id": "chat-reviewer-2026-06-03",
25
+ "runtime": "chat",
26
+ "client": "kiro-desktop",
27
+ "client_version": "2.5.1",
28
+ "agent": "reviewer",
29
+ "task_hash": "sha256:9ed0fd91ef88f64a7de10e7fbab4e979ec6b13701d8a7569e3d4ad4ed9932a9a",
30
+ "discovered_resources": ["skill:pr-review", "skill:release-notes"],
31
+ "attached_resources": ["skill:pr-review", "skill:release-notes"],
32
+ "injected_resources": ["skill:pr-review"],
33
+ "readable_resources": ["skill:pr-review"],
34
+ "skipped_resources": [
35
+ {
36
+ "id": "skill:release-notes",
37
+ "stage": "injected",
38
+ "reason": "trigger_not_matched"
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "session_id": "acp-zed-reviewer-2026-06-03",
44
+ "runtime": "acp",
45
+ "client": "zed",
46
+ "client_version": "2.5.1",
47
+ "agent": "reviewer",
48
+ "task_hash": "sha256:9ed0fd91ef88f64a7de10e7fbab4e979ec6b13701d8a7569e3d4ad4ed9932a9a",
49
+ "discovered_resources": ["skill:pr-review", "skill:release-notes"],
50
+ "attached_resources": ["skill:pr-review", "skill:release-notes"],
51
+ "injected_resources": [],
52
+ "readable_resources": [],
53
+ "skipped_resources": [
54
+ {
55
+ "id": "skill:pr-review",
56
+ "stage": "injected",
57
+ "reason": "runtime_does_not_inject_resources"
58
+ },
59
+ {
60
+ "id": "skill:release-notes",
61
+ "stage": "injected",
62
+ "reason": "trigger_not_matched"
63
+ }
64
+ ]
65
+ }
66
+ ],
67
+ "safe_to_continue": false,
68
+ "next_action": "file a host/runtime bug with stage-level evidence; do not fix this with stronger prompt wording alone"
69
+ }
@@ -0,0 +1,15 @@
1
+ # MCP runtime config receipts
2
+
3
+ This example validates the live-vs-template boundary for MCP config review.
4
+
5
+ ```bash
6
+ node check-mcp-runtime-config-receipt.mjs mcp-runtime-config-receipt.json
7
+ ```
8
+
9
+ Expected output:
10
+
11
+ ```text
12
+ mcp runtime config receipt ok: 3 configs checked, 1 runtime alert, 0 review-noise warnings
13
+ ```
14
+
15
+ The alert is intentional: the live `.mcp.json` changes what Claude Code can load. The template and disabled config are quiet by default because they are not runtime-active.
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ const receiptPath = process.argv[2] || path.join(import.meta.dirname, 'mcp-runtime-config-receipt.json')
6
+ const receipt = JSON.parse(fs.readFileSync(receiptPath, 'utf8'))
7
+ const errors = []
8
+ const warnings = []
9
+ let runtimeAlerts = 0
10
+
11
+ function fieldName(prefix, field) {
12
+ return prefix ? `${prefix}.${field}` : field
13
+ }
14
+
15
+ function requireString(value, field) {
16
+ if (typeof value !== 'string' || value.trim() === '') {
17
+ errors.push(`${field} must be a non-empty string`)
18
+ }
19
+ }
20
+
21
+ function requireBoolean(value, field) {
22
+ if (typeof value !== 'boolean') {
23
+ errors.push(`${field} must be boolean`)
24
+ }
25
+ }
26
+
27
+ function requireArray(value, field) {
28
+ if (!Array.isArray(value)) {
29
+ errors.push(`${field} must be an array`)
30
+ }
31
+ }
32
+
33
+ function requireStringArray(value, field) {
34
+ requireArray(value, field)
35
+ for (const [index, item] of (value || []).entries()) {
36
+ if (typeof item !== 'string' || item.trim() === '') {
37
+ errors.push(`${field}[${index}] must be a non-empty string`)
38
+ }
39
+ }
40
+ }
41
+
42
+ const runtimeKinds = new Set(['runtime_config'])
43
+ const inactiveKinds = new Set(['sample_config', 'disabled_config', 'catalog_example'])
44
+ const allowedKinds = new Set([...runtimeKinds, ...inactiveKinds])
45
+ const allowedChanges = new Set(['server_added', 'server_removed', 'command_changed', 'env_changed', 'tools_changed', 'unchanged'])
46
+
47
+ if (receipt.schema !== 'pluribus.mcp_runtime_config_receipt.v1') {
48
+ errors.push('schema must be pluribus.mcp_runtime_config_receipt.v1')
49
+ }
50
+
51
+ requireString(receipt.run_id, 'run_id')
52
+ requireString(receipt.generated_at, 'generated_at')
53
+ requireString(receipt.repository_ref, 'repository_ref')
54
+
55
+ if (!Array.isArray(receipt.configs) || receipt.configs.length === 0) {
56
+ errors.push('configs must be a non-empty array')
57
+ }
58
+
59
+ for (const [index, config] of (receipt.configs || []).entries()) {
60
+ const prefix = `configs[${index}]`
61
+ requireString(config.path, fieldName(prefix, 'path'))
62
+ requireString(config.client, fieldName(prefix, 'client'))
63
+ requireString(config.source_kind, fieldName(prefix, 'source_kind'))
64
+ requireString(config.change_kind, fieldName(prefix, 'change_kind'))
65
+ requireBoolean(config.runtime_active, fieldName(prefix, 'runtime_active'))
66
+ requireBoolean(config.permission_surface_changed, fieldName(prefix, 'permission_surface_changed'))
67
+ requireBoolean(config.sample_config_review, fieldName(prefix, 'sample_config_review'))
68
+ requireBoolean(config.should_alert, fieldName(prefix, 'should_alert'))
69
+ requireStringArray(config.loaded_by, fieldName(prefix, 'loaded_by'))
70
+
71
+ if (!allowedKinds.has(config.source_kind)) {
72
+ errors.push(`${prefix}.source_kind must be one of ${[...allowedKinds].join(', ')}`)
73
+ }
74
+
75
+ if (!allowedChanges.has(config.change_kind)) {
76
+ errors.push(`${prefix}.change_kind must be one of ${[...allowedChanges].join(', ')}`)
77
+ }
78
+
79
+ if (runtimeKinds.has(config.source_kind) && config.runtime_active !== true) {
80
+ errors.push(`${prefix}.runtime_active must be true for runtime_config`)
81
+ }
82
+
83
+ if (inactiveKinds.has(config.source_kind) && config.runtime_active !== false) {
84
+ errors.push(`${prefix}.runtime_active must be false for ${config.source_kind}`)
85
+ }
86
+
87
+ if (config.runtime_active && (!Array.isArray(config.loaded_by) || config.loaded_by.length === 0)) {
88
+ errors.push(`${prefix}.loaded_by must name at least one client when runtime_active is true`)
89
+ }
90
+
91
+ if (config.runtime_active && config.permission_surface_changed && config.change_kind !== 'unchanged') {
92
+ runtimeAlerts += 1
93
+ if (config.should_alert !== true) {
94
+ errors.push(`${prefix}.should_alert must be true when an active runtime permission surface changed`)
95
+ }
96
+ }
97
+
98
+ if (!config.runtime_active && config.should_alert && !config.sample_config_review) {
99
+ warnings.push(`${config.path || prefix} alerts even though it is not runtime-active; use sample_config_review or suppress as template noise`)
100
+ }
101
+
102
+ if (!Array.isArray(config.evidence) || config.evidence.length === 0) {
103
+ errors.push(`${prefix}.evidence must include at least one privacy-safe evidence ref`)
104
+ }
105
+
106
+ for (const [evidenceIndex, evidence] of (config.evidence || []).entries()) {
107
+ const evidencePrefix = `${prefix}.evidence[${evidenceIndex}]`
108
+ requireString(evidence.kind, fieldName(evidencePrefix, 'kind'))
109
+ requireString(evidence.ref, fieldName(evidencePrefix, 'ref'))
110
+ }
111
+
112
+ const envKeys = config.redacted_env_keys || {}
113
+ requireStringArray(envKeys.required, fieldName(prefix, 'redacted_env_keys.required'))
114
+ requireStringArray(envKeys.present, fieldName(prefix, 'redacted_env_keys.present'))
115
+ requireStringArray(envKeys.missing, fieldName(prefix, 'redacted_env_keys.missing'))
116
+ }
117
+
118
+ if (errors.length > 0) {
119
+ console.error('mcp runtime config receipt invalid:')
120
+ for (const error of errors) console.error(`- ${error}`)
121
+ process.exit(1)
122
+ }
123
+
124
+ const warningLabel = warnings.length === 1 ? 'warning' : 'warnings'
125
+ const alertLabel = runtimeAlerts === 1 ? 'alert' : 'alerts'
126
+ console.log(`mcp runtime config receipt ok: ${receipt.configs.length} configs checked, ${runtimeAlerts} runtime ${alertLabel}, ${warnings.length} review-noise ${warningLabel}`)
127
+ for (const warning of warnings) console.log(`- ${warning}`)
@@ -0,0 +1,82 @@
1
+ {
2
+ "schema": "pluribus.mcp_runtime_config_receipt.v1",
3
+ "run_id": "mcp-config-review-2026-06-05T23:00Z",
4
+ "generated_at": "2026-06-05T23:00:00Z",
5
+ "repository_ref": "github:example/app@pull/123",
6
+ "configs": [
7
+ {
8
+ "path": ".mcp.json",
9
+ "client": "claude-code",
10
+ "source_kind": "runtime_config",
11
+ "runtime_active": true,
12
+ "loaded_by": ["claude-code"],
13
+ "change_kind": "server_added",
14
+ "permission_surface_changed": true,
15
+ "sample_config_review": false,
16
+ "should_alert": true,
17
+ "evidence": [
18
+ {
19
+ "kind": "config_digest",
20
+ "ref": "sha256:9a1c4b7d4f1a"
21
+ },
22
+ {
23
+ "kind": "client_discovery_rule",
24
+ "ref": "claude-code:.mcp.json"
25
+ }
26
+ ],
27
+ "redacted_env_keys": {
28
+ "required": ["GITHUB_TOKEN"],
29
+ "present": [],
30
+ "missing": ["GITHUB_TOKEN"]
31
+ }
32
+ },
33
+ {
34
+ "path": ".mcp.json.template",
35
+ "client": "claude-code",
36
+ "source_kind": "sample_config",
37
+ "runtime_active": false,
38
+ "loaded_by": [],
39
+ "change_kind": "server_added",
40
+ "permission_surface_changed": true,
41
+ "sample_config_review": false,
42
+ "should_alert": false,
43
+ "evidence": [
44
+ {
45
+ "kind": "config_digest",
46
+ "ref": "sha256:2e76a9c103ab"
47
+ }
48
+ ],
49
+ "redacted_env_keys": {
50
+ "required": ["EXAMPLE_API_KEY"],
51
+ "present": [],
52
+ "missing": ["EXAMPLE_API_KEY"]
53
+ }
54
+ },
55
+ {
56
+ "path": ".cursor/mcp.disabled.json",
57
+ "client": "cursor",
58
+ "source_kind": "disabled_config",
59
+ "runtime_active": false,
60
+ "loaded_by": [],
61
+ "change_kind": "command_changed",
62
+ "permission_surface_changed": true,
63
+ "sample_config_review": false,
64
+ "should_alert": false,
65
+ "evidence": [
66
+ {
67
+ "kind": "config_digest",
68
+ "ref": "sha256:66f3ec00a12b"
69
+ },
70
+ {
71
+ "kind": "disabled_profile_marker",
72
+ "ref": "filename:mcp.disabled.json"
73
+ }
74
+ ],
75
+ "redacted_env_keys": {
76
+ "required": [],
77
+ "present": [],
78
+ "missing": []
79
+ }
80
+ }
81
+ ]
82
+ }
@@ -0,0 +1,28 @@
1
+ # Memory write policy receipt gate
2
+
3
+ Shared memory systems are useful when many agents can read the same durable facts. They become risky when every run can also write to that memory without review.
4
+
5
+ This example treats a memory write like a code change:
6
+
7
+ 1. the agent proposes a memory diff;
8
+ 2. the diff is scoped to a repo/project/org/user boundary;
9
+ 3. the source is hashed instead of copied;
10
+ 4. stale facts get an expiry or review date;
11
+ 5. future sessions can see what memory was injected;
12
+ 6. private/sensitive writes are quarantined until a human or external policy approves them.
13
+
14
+ Run the passing fixture:
15
+
16
+ ```bash
17
+ node examples/memory-write-policy/check-memory-update.mjs \
18
+ examples/memory-write-policy/approved-memory-update.json
19
+ ```
20
+
21
+ Run the failing fixture:
22
+
23
+ ```bash
24
+ node examples/memory-write-policy/check-memory-update.mjs \
25
+ examples/memory-write-policy/quarantined-memory-update.json
26
+ ```
27
+
28
+ Use this shape when evaluating cross-agent memory MCPs, knowledge graphs, or shared `CLAUDE.md`/`AGENTS.md` update flows. The point is not to store the memory body in the receipt. The point is to prove that a durable memory update had source, scope, lifecycle, visibility, approval, and privacy checks before it could teach every harness the same fact.
@@ -0,0 +1,48 @@
1
+ {
2
+ "type": "agent.memory_update_receipt.v1",
3
+ "update_id": "memupd_2026_06_01_001",
4
+ "run_id": "agent_run_8742",
5
+ "source": {
6
+ "kind": "claude-code-session",
7
+ "ref": "repo:acme/shop#issue-4812",
8
+ "content_hash": "sha256:4df7b1b9d6f4a2c51ad3e1ce761a8f0f918c9a0f4d26c4c8fd9f1e21ad1a19f4"
9
+ },
10
+ "scope": {
11
+ "kind": "repo",
12
+ "id": "acme/shop",
13
+ "path_prefix": "apps/checkout"
14
+ },
15
+ "proposed_diff": {
16
+ "adds": [
17
+ {
18
+ "memory_ref": "memory:checkout:idempotency-key-policy",
19
+ "summary_hash": "sha256:dad59f7764d0a6dd8db8f2d6f23d9dd30b3e6b1e1197d5478c05f9d093bc5fed",
20
+ "reason": "captured repo-local invariant after failing duplicate-charge test"
21
+ }
22
+ ],
23
+ "updates": [],
24
+ "supersedes": [],
25
+ "expires": []
26
+ },
27
+ "write_policy": {
28
+ "status": "approved",
29
+ "policy_ref": "repo-memory-policy:v2",
30
+ "approved_by": "maintainer:checkout-platform",
31
+ "approval_channel": "pull-request-review",
32
+ "private_or_sensitive_detected": false
33
+ },
34
+ "lifecycle": {
35
+ "review_after": "2026-07-01T00:00:00Z",
36
+ "supersedes_required": false
37
+ },
38
+ "injection_visibility": {
39
+ "next_session_visible": true,
40
+ "preview_path": ".pluribus/memory-previews/checkout-idempotency-key-policy.md"
41
+ },
42
+ "privacy": {
43
+ "raw_memory_text_logged": false,
44
+ "raw_prompts_logged": false,
45
+ "raw_tool_output_logged": false,
46
+ "secrets_logged": false
47
+ }
48
+ }
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs'
3
+
4
+ const [file] = process.argv.slice(2)
5
+
6
+ if (!file) {
7
+ console.error('Usage: node check-memory-update.mjs <memory-update-receipt.json>')
8
+ process.exit(2)
9
+ }
10
+
11
+ let receipt
12
+ try {
13
+ receipt = JSON.parse(readFileSync(file, 'utf8'))
14
+ } catch (error) {
15
+ console.error(JSON.stringify({ ok: false, file, errors: [`invalid JSON: ${error.message}`] }, null, 2))
16
+ process.exit(2)
17
+ }
18
+
19
+ const errors = []
20
+ const warnings = []
21
+
22
+ if (receipt.type !== 'agent.memory_update_receipt.v1') {
23
+ errors.push('type must be agent.memory_update_receipt.v1')
24
+ }
25
+
26
+ for (const key of ['update_id', 'run_id']) {
27
+ if (!receipt[key] || typeof receipt[key] !== 'string') {
28
+ errors.push(`${key} is required`)
29
+ }
30
+ }
31
+
32
+ const source = receipt.source || {}
33
+ if (!source.kind || typeof source.kind !== 'string') {
34
+ errors.push('source.kind is required')
35
+ }
36
+ if (!source.ref || typeof source.ref !== 'string') {
37
+ errors.push('source.ref is required')
38
+ }
39
+ if (!source.content_hash || typeof source.content_hash !== 'string') {
40
+ errors.push('source.content_hash is required; do not rely on raw memory text')
41
+ }
42
+
43
+ const scope = receipt.scope || {}
44
+ if (!scope.kind || !['repo', 'project', 'org', 'user'].includes(scope.kind)) {
45
+ errors.push('scope.kind must be repo, project, org, or user')
46
+ }
47
+ if (!scope.id || typeof scope.id !== 'string') {
48
+ errors.push('scope.id is required so a memory write cannot silently become global')
49
+ }
50
+ if (scope.kind === 'user') {
51
+ warnings.push('user-scoped durable memory is broad; prefer repo/project scope when possible')
52
+ }
53
+
54
+ const diff = receipt.proposed_diff || {}
55
+ const changed = ['adds', 'updates', 'supersedes', 'expires'].flatMap((key) => Array.isArray(diff[key]) ? diff[key] : [])
56
+ if (changed.length === 0) {
57
+ errors.push('proposed_diff must include at least one add, update, supersede, or expire entry')
58
+ }
59
+ for (const [index, item] of changed.entries()) {
60
+ if (!item.memory_ref || typeof item.memory_ref !== 'string') {
61
+ errors.push(`proposed_diff item ${index} is missing memory_ref`)
62
+ }
63
+ if (!item.summary_hash || typeof item.summary_hash !== 'string') {
64
+ errors.push(`proposed_diff item ${index} is missing summary_hash; log hashes, not raw memory bodies`)
65
+ }
66
+ if (item.raw_text) {
67
+ errors.push(`proposed_diff item ${index} must not include raw_text`)
68
+ }
69
+ }
70
+
71
+ const policy = receipt.write_policy || {}
72
+ if (policy.status !== 'approved') {
73
+ errors.push(`write_policy.status is ${policy.status || 'missing'}; shared memory write must remain proposed/quarantined until approved`)
74
+ }
75
+ if (!policy.policy_ref || typeof policy.policy_ref !== 'string') {
76
+ errors.push('write_policy.policy_ref is required')
77
+ }
78
+ if (!policy.approved_by || typeof policy.approved_by !== 'string') {
79
+ errors.push('write_policy.approved_by is required for durable writes')
80
+ }
81
+ if (policy.private_or_sensitive_detected !== false) {
82
+ errors.push('write_policy.private_or_sensitive_detected must be false before merge')
83
+ }
84
+
85
+ const lifecycle = receipt.lifecycle || {}
86
+ if (!lifecycle.expires_at && !lifecycle.review_after) {
87
+ errors.push('lifecycle.expires_at or lifecycle.review_after is required to avoid immortal stale facts')
88
+ }
89
+ if (lifecycle.supersedes_required === true && (!Array.isArray(diff.supersedes) || diff.supersedes.length === 0)) {
90
+ errors.push('lifecycle.supersedes_required is true but proposed_diff.supersedes is empty')
91
+ }
92
+
93
+ const visibility = receipt.injection_visibility || {}
94
+ if (visibility.next_session_visible !== true) {
95
+ errors.push('injection_visibility.next_session_visible must be true so future agents can see what memory was injected')
96
+ }
97
+ if (!visibility.preview_path || typeof visibility.preview_path !== 'string') {
98
+ warnings.push('injection_visibility.preview_path is recommended for human review')
99
+ }
100
+
101
+ const privacy = receipt.privacy || {}
102
+ for (const key of ['raw_memory_text_logged', 'raw_prompts_logged', 'raw_tool_output_logged', 'secrets_logged']) {
103
+ if (privacy[key] !== false) {
104
+ errors.push(`privacy.${key} must be false for this gate`)
105
+ }
106
+ }
107
+
108
+ const result = {
109
+ ok: errors.length === 0,
110
+ file,
111
+ update_id: receipt.update_id,
112
+ run_id: receipt.run_id,
113
+ scope: scope.kind && scope.id ? `${scope.kind}:${scope.id}` : undefined,
114
+ write_status: policy.status,
115
+ errors,
116
+ warnings
117
+ }
118
+
119
+ console.log(JSON.stringify(result, null, 2))
120
+ process.exit(result.ok ? 0 : 1)
@@ -0,0 +1,43 @@
1
+ {
2
+ "type": "agent.memory_update_receipt.v1",
3
+ "update_id": "memupd_2026_06_01_002",
4
+ "run_id": "agent_run_8743",
5
+ "source": {
6
+ "kind": "claude-code-session",
7
+ "ref": "repo:acme/shop#debug-chat",
8
+ "content_hash": "sha256:6d4fa2a2114f83fd8bd9d6eb3c632ff6f20252f978bc9deec0b9d85694e02d4b"
9
+ },
10
+ "scope": {
11
+ "kind": "user",
12
+ "id": "developer-laptop"
13
+ },
14
+ "proposed_diff": {
15
+ "adds": [
16
+ {
17
+ "memory_ref": "memory:global:production-debug-shortcut",
18
+ "summary_hash": "sha256:5ee95af43fd2fb8b90908ef8cc4a5e6f050a8f52c97c7c93fd7f37bd1dcf3c2a",
19
+ "raw_text": "do not store raw memory bodies here"
20
+ }
21
+ ],
22
+ "updates": [],
23
+ "supersedes": [],
24
+ "expires": []
25
+ },
26
+ "write_policy": {
27
+ "status": "quarantined",
28
+ "policy_ref": "repo-memory-policy:v2",
29
+ "private_or_sensitive_detected": true
30
+ },
31
+ "lifecycle": {
32
+ "supersedes_required": false
33
+ },
34
+ "injection_visibility": {
35
+ "next_session_visible": false
36
+ },
37
+ "privacy": {
38
+ "raw_memory_text_logged": true,
39
+ "raw_prompts_logged": false,
40
+ "raw_tool_output_logged": false,
41
+ "secrets_logged": false
42
+ }
43
+ }
@@ -0,0 +1,13 @@
1
+ # Parallel session review ledger example
2
+
3
+ This example is a copyable receipt for teams running multiple agent sessions at once. It is designed for the review bottleneck: deciding whether each session can be trusted, continued, or rejected without reading an entire transcript.
4
+
5
+ ```bash
6
+ node examples/parallel-session-review-ledger/check-parallel-session-review-ledger.mjs examples/parallel-session-review-ledger/parallel-session-review-ledger.json
7
+ ```
8
+
9
+ Expected output:
10
+
11
+ ```text
12
+ parallel session review ledger ok: 3 sessions checked
13
+ ```
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const file = process.argv[2] || path.join('examples', 'parallel-session-review-ledger', 'parallel-session-review-ledger.json');
6
+ const receipt = JSON.parse(fs.readFileSync(file, 'utf8'));
7
+ const errors = [];
8
+
9
+ const allowedStates = new Set(['complete', 'partial', 'blocked', 'unsafe_to_resume']);
10
+ const allowedNextActions = new Set([
11
+ 'review_diff',
12
+ 'run_missing_check',
13
+ 'continue_same_scope',
14
+ 'ask_human',
15
+ 'stop_manual_review'
16
+ ]);
17
+
18
+ function add(condition, message) {
19
+ if (!condition) errors.push(message);
20
+ }
21
+
22
+ function globPrefix(pattern) {
23
+ return pattern.replace(/\*\*.*$/, '').replace(/\*.*$/, '');
24
+ }
25
+
26
+ add(receipt.schema === 'pluribus.parallel_session_review_ledger.v1', 'schema must be pluribus.parallel_session_review_ledger.v1');
27
+ add(Array.isArray(receipt.sessions) && receipt.sessions.length > 0, 'sessions must be a non-empty array');
28
+
29
+ for (const session of receipt.sessions || []) {
30
+ const label = session.id || '<missing-id>';
31
+ add(Boolean(session.id), 'session id is required');
32
+ add(Boolean(session.assignment), `${label}: assignment is required`);
33
+ add(Boolean(session.branch), `${label}: branch is required`);
34
+ add(Boolean(session.allowed_scope), `${label}: allowed_scope is required`);
35
+ add(Array.isArray(session.allowed_scope?.files) && session.allowed_scope.files.length > 0, `${label}: allowed_scope.files must be non-empty`);
36
+ add(allowedStates.has(session.state), `${label}: invalid state ${session.state}`);
37
+ add(allowedNextActions.has(session.safe_next_action), `${label}: invalid safe_next_action ${session.safe_next_action}`);
38
+
39
+ const evidence = session.evidence || [];
40
+ const missingChecks = session.missing_checks || [];
41
+ const privacyFlags = session.privacy_flags || [];
42
+
43
+ if (session.state === 'complete') {
44
+ add(evidence.length > 0, `${label}: complete sessions need evidence`);
45
+ add(missingChecks.length === 0, `${label}: complete sessions cannot have missing_checks`);
46
+ add(privacyFlags.length === 0, `${label}: complete sessions cannot have privacy_flags`);
47
+ }
48
+
49
+ if (session.state === 'partial') {
50
+ add(missingChecks.length > 0, `${label}: partial sessions must name missing_checks`);
51
+ }
52
+
53
+ if (session.state === 'unsafe_to_resume') {
54
+ add(session.safe_next_action === 'stop_manual_review', `${label}: unsafe_to_resume must use stop_manual_review`);
55
+ }
56
+
57
+ const allowedPrefixes = (session.allowed_scope?.files || []).map(globPrefix);
58
+ for (const touched of session.touched_files || []) {
59
+ add(allowedPrefixes.some((prefix) => touched.startsWith(prefix)), `${label}: touched file outside allowed scope: ${touched}`);
60
+ }
61
+ }
62
+
63
+ if (errors.length > 0) {
64
+ console.error('parallel session review ledger invalid:');
65
+ for (const error of errors) console.error(`- ${error}`);
66
+ process.exit(1);
67
+ }
68
+
69
+ console.log(`parallel session review ledger ok: ${receipt.sessions.length} sessions checked`);