pluribus-context 0.3.34 → 0.3.36
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 +23 -0
- package/README.md +2 -1
- package/bin/pluribus.js +12 -0
- package/docs/agent-firewall-denial-audit.md +95 -0
- package/docs/ai-pr-review-receipts.md +173 -0
- package/docs/canonical-output-receipts.md +107 -0
- package/docs/compaction-resume-receipts.md +43 -0
- package/docs/controlled-learning-queue.md +48 -0
- package/docs/dynamic-workflow-run-receipts.md +158 -0
- package/docs/install-plan-receipts.md +79 -0
- package/docs/loaded-resource-boundary.md +97 -0
- package/docs/mcp-tool-visibility-receipts.md +67 -0
- package/docs/memory-write-policy-receipts.md +41 -0
- package/docs/parallel-session-review-ledger.md +103 -0
- package/docs/phase-boundary-contracts.md +87 -0
- package/docs/review-primitive-gate.md +109 -0
- package/docs/skill-install-receipts.md +102 -0
- package/docs/skill-policy-receipts.md +87 -0
- package/docs/skill-use-rate-receipts.md +104 -0
- package/docs/subagent-role-receipts.md +95 -0
- package/docs/temporal-context-receipts.md +123 -0
- package/examples/agent-firewall-denial-audit/README.md +14 -0
- package/examples/agent-firewall-denial-audit/check-denial-audit.mjs +116 -0
- package/examples/agent-firewall-denial-audit/denial-envelope.json +9 -0
- package/examples/agent-firewall-denial-audit/operator-audit-record.json +20 -0
- package/examples/agent-skills/skill-policy-receipts/README.md +22 -0
- package/examples/agent-skills/skill-policy-receipts/SKILL.md +77 -0
- package/examples/ai-pr-review-receipts/.github/pull_request_template.md +31 -0
- package/examples/ai-pr-review-receipts/.github/workflows/ai-pr-review-receipt.yml +25 -0
- package/examples/ai-pr-review-receipts/README.md +55 -0
- package/examples/ai-pr-review-receipts/incomplete-review-primitive-receipt.json +43 -0
- package/examples/ai-pr-review-receipts/review-primitive-receipt.json +60 -0
- package/examples/canonical-output-receipts/canonical-output-receipt.json +55 -0
- package/examples/claude-code-review-hook/README.md +74 -0
- package/examples/claude-code-review-hook/check-review-receipt-hook.mjs +80 -0
- package/examples/claude-code-review-hook/sample-task-completed-event.json +6 -0
- package/examples/compaction-resume-receipts/README.md +12 -0
- package/examples/compaction-resume-receipts/check-resume-receipt.mjs +116 -0
- package/examples/compaction-resume-receipts/safe-resume-receipt.json +52 -0
- package/examples/compaction-resume-receipts/unsafe-resume-receipt.json +41 -0
- package/examples/controlled-learning-queue/README.md +26 -0
- package/examples/controlled-learning-queue/check-learning-queue.mjs +44 -0
- package/examples/controlled-learning-queue/leads/acme-job-card.md +12 -0
- package/examples/controlled-learning-queue/learning_queue.md +27 -0
- package/examples/controlled-learning-queue/memory/durable.md +10 -0
- package/examples/controlled-learning-queue/memory/working-notes.md +5 -0
- package/examples/controlled-learning-queue/role/job-contract.md +18 -0
- package/examples/controlled-learning-queue/skills/qualify-lead.md +17 -0
- package/examples/dynamic-workflow-run-receipts/README.md +18 -0
- package/examples/dynamic-workflow-run-receipts/workflow-run-receipt.json +112 -0
- package/examples/install-plan-receipts/README.md +34 -0
- package/examples/install-plan-receipts/agent-install-plan-receipt.json +56 -0
- package/examples/loaded-resource-boundary/README.md +22 -0
- package/examples/loaded-resource-boundary/check-loaded-resource-boundary.mjs +65 -0
- package/examples/loaded-resource-boundary/loaded-resource-boundary.json +69 -0
- package/examples/memory-write-policy/README.md +28 -0
- package/examples/memory-write-policy/approved-memory-update.json +48 -0
- package/examples/memory-write-policy/check-memory-update.mjs +120 -0
- package/examples/memory-write-policy/quarantined-memory-update.json +43 -0
- package/examples/parallel-session-review-ledger/README.md +13 -0
- package/examples/parallel-session-review-ledger/check-parallel-session-review-ledger.mjs +69 -0
- package/examples/parallel-session-review-ledger/parallel-session-review-ledger.json +72 -0
- package/examples/phase-boundary-contract/README.md +23 -0
- package/examples/phase-boundary-contract/check-phase-boundary.mjs +73 -0
- package/examples/phase-boundary-contract/phase-boundary-contract.json +68 -0
- package/examples/review-primitive-gate/README.md +19 -0
- package/examples/review-primitive-gate/check-review-receipt.mjs +100 -0
- package/examples/review-primitive-gate/fail-review-receipt.json +42 -0
- package/examples/review-primitive-gate/pass-review-receipt.json +54 -0
- package/examples/skill-install-receipts/README.md +31 -0
- package/examples/skill-install-receipts/check-skill-install-receipt.mjs +75 -0
- package/examples/skill-install-receipts/skill-install-receipt.json +79 -0
- package/examples/skill-use-rate-receipts/README.md +16 -0
- package/examples/skill-use-rate-receipts/check-skill-use-rate.mjs +89 -0
- package/examples/skill-use-rate-receipts/skill-use-rate-receipt.json +79 -0
- package/examples/subagent-role-receipts/README.md +15 -0
- package/examples/subagent-role-receipts/agents.toml +36 -0
- package/examples/temporal-context-receipts/CURRENT_STATE.md +13 -0
- package/examples/temporal-context-receipts/specs/2025-checkout-rewrite.md +10 -0
- package/examples/temporal-context-receipts/specs/2026-checkout-risk-notes.md +10 -0
- package/examples/temporal-context-receipts/temporal-authority-receipt.json +27 -0
- package/package.json +1 -1
- package/src/commands/demo.js +155 -0
- package/src/index.js +1 -0
- package/src/utils/version.js +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const file = process.argv[2] || 'phase-boundary-contract.json';
|
|
5
|
+
const contract = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
6
|
+
const errors = [];
|
|
7
|
+
|
|
8
|
+
const phases = ['explore', 'propose', 'spec', 'design', 'tasks', 'apply', 'verify'];
|
|
9
|
+
const hashRe = /^sha256:[a-f0-9]{64}$/;
|
|
10
|
+
const unsafeRefRe = /(^\/|\.\.|[A-Za-z]:\\|secret|token|password|private_key)/i;
|
|
11
|
+
const requiredGateKeys = ['changed_files', 'tests_run', 'open_risks', 'stop_conditions'];
|
|
12
|
+
|
|
13
|
+
function expect(condition, message) {
|
|
14
|
+
if (!condition) errors.push(message);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
expect(contract.schema === 'pluribus.phase-boundary-contract.v1', 'schema must be pluribus.phase-boundary-contract.v1');
|
|
18
|
+
expect(typeof contract.workflowId === 'string' && contract.workflowId.length >= 6, 'workflowId is required');
|
|
19
|
+
expect(phases.includes(contract.currentPhase), 'currentPhase must be a known phase');
|
|
20
|
+
expect(phases.includes(contract.nextPhase), 'nextPhase must be a known phase');
|
|
21
|
+
expect(contract.currentPhase !== contract.nextPhase, 'currentPhase and nextPhase must differ');
|
|
22
|
+
|
|
23
|
+
expect(Array.isArray(contract.allowedInput) && contract.allowedInput.length > 0, 'allowedInput must list at least one source');
|
|
24
|
+
for (const [index, input] of (contract.allowedInput || []).entries()) {
|
|
25
|
+
expect(typeof input.kind === 'string' && input.kind.length > 0, `allowedInput[${index}].kind is required`);
|
|
26
|
+
expect(typeof input.ref === 'string' && !unsafeRefRe.test(input.ref), `allowedInput[${index}].ref must be a non-secret relative/logical ref`);
|
|
27
|
+
expect(hashRe.test(input.contentHash || ''), `allowedInput[${index}].contentHash must be sha256:<64 hex>`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
expect(contract.outputArtifact && typeof contract.outputArtifact.kind === 'string', 'outputArtifact.kind is required');
|
|
31
|
+
expect(contract.outputArtifact && typeof contract.outputArtifact.ref === 'string' && !unsafeRefRe.test(contract.outputArtifact.ref), 'outputArtifact.ref must be a non-secret logical ref');
|
|
32
|
+
expect(contract.outputArtifact && hashRe.test(contract.outputArtifact.contentHash || ''), 'outputArtifact.contentHash must be sha256:<64 hex>');
|
|
33
|
+
|
|
34
|
+
const gate = contract.evidenceGate || {};
|
|
35
|
+
expect(gate.status === 'pass' || gate.status === 'needs_review' || gate.status === 'fail', 'evidenceGate.status must be pass, needs_review, or fail');
|
|
36
|
+
expect(Array.isArray(gate.requiredBeforeNextPhase), 'evidenceGate.requiredBeforeNextPhase must be an array');
|
|
37
|
+
for (const key of requiredGateKeys) {
|
|
38
|
+
expect((gate.requiredBeforeNextPhase || []).includes(key), `evidenceGate.requiredBeforeNextPhase must include ${key}`);
|
|
39
|
+
}
|
|
40
|
+
expect(gate.changedFiles && Number.isInteger(gate.changedFiles.count) && gate.changedFiles.count >= 0, 'evidenceGate.changedFiles.count is required');
|
|
41
|
+
expect(gate.changedFiles && hashRe.test(gate.changedFiles.fileSetHash || ''), 'evidenceGate.changedFiles.fileSetHash must be sha256:<64 hex>');
|
|
42
|
+
expect(Array.isArray(gate.testsRun), 'evidenceGate.testsRun must be an array');
|
|
43
|
+
for (const [index, test] of (gate.testsRun || []).entries()) {
|
|
44
|
+
expect(typeof test.name === 'string' && test.name.length > 0, `testsRun[${index}].name is required`);
|
|
45
|
+
expect(hashRe.test(test.commandHash || ''), `testsRun[${index}].commandHash must be sha256:<64 hex>`);
|
|
46
|
+
expect(['pass', 'fail', 'skipped'].includes(test.status), `testsRun[${index}].status must be pass/fail/skipped`);
|
|
47
|
+
}
|
|
48
|
+
expect(Array.isArray(gate.openRisks), 'evidenceGate.openRisks must be an array');
|
|
49
|
+
for (const [index, risk] of (gate.openRisks || []).entries()) {
|
|
50
|
+
expect(typeof risk.riskClass === 'string' && risk.riskClass.length > 0, `openRisks[${index}].riskClass is required`);
|
|
51
|
+
expect(['low', 'medium', 'high'].includes(risk.severity), `openRisks[${index}].severity must be low/medium/high`);
|
|
52
|
+
expect(typeof risk.safeToContinue === 'boolean', `openRisks[${index}].safeToContinue must be boolean`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
expect(Array.isArray(contract.droppedContext), 'droppedContext must be an array');
|
|
56
|
+
for (const [index, dropped] of (contract.droppedContext || []).entries()) {
|
|
57
|
+
expect(typeof dropped.kind === 'string' && dropped.kind.length > 0, `droppedContext[${index}].kind is required`);
|
|
58
|
+
expect(typeof dropped.reason === 'string' && dropped.reason.length > 0, `droppedContext[${index}].reason is required`);
|
|
59
|
+
}
|
|
60
|
+
expect(Array.isArray(contract.stopConditions), 'stopConditions must be an array');
|
|
61
|
+
|
|
62
|
+
if (gate.status === 'pass') {
|
|
63
|
+
expect((contract.stopConditions || []).length === 0, 'pass contracts must not have active stopConditions');
|
|
64
|
+
expect((gate.openRisks || []).every((risk) => risk.safeToContinue), 'pass contracts require all open risks to be safeToContinue');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (errors.length) {
|
|
68
|
+
console.error(`phase-boundary contract failed (${errors.length}):`);
|
|
69
|
+
for (const error of errors) console.error(`- ${error}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(`phase-boundary contract ok: ${contract.workflowId} ${contract.currentPhase}->${contract.nextPhase}`);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "pluribus.phase-boundary-contract.v1",
|
|
3
|
+
"workflowId": "checkout-refactor-2026-06-03",
|
|
4
|
+
"currentPhase": "apply",
|
|
5
|
+
"nextPhase": "verify",
|
|
6
|
+
"allowedInput": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "approved_plan",
|
|
9
|
+
"ref": "plans/checkout-refactor.md",
|
|
10
|
+
"contentHash": "sha256:0b70d75d6c8f0e54c74e1d7ea90e7c7b3d83e15dd3a3f7b4b9e9e73339d2b22e",
|
|
11
|
+
"required": true
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "task_list",
|
|
15
|
+
"ref": "tasks/checkout-refactor.md#apply",
|
|
16
|
+
"contentHash": "sha256:63ccf4555f0adbe64bbff5a8f7c4b24708fdb2b5e4d0e417a5cb057b9ed39a76",
|
|
17
|
+
"required": true
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"outputArtifact": {
|
|
21
|
+
"kind": "patch",
|
|
22
|
+
"ref": "git:working-tree",
|
|
23
|
+
"contentHash": "sha256:3f9d6216dc7dcb53e6f29ad23433de97f0c172be6a6f46d574aa67e0edfd0789"
|
|
24
|
+
},
|
|
25
|
+
"evidenceGate": {
|
|
26
|
+
"requiredBeforeNextPhase": [
|
|
27
|
+
"changed_files",
|
|
28
|
+
"tests_run",
|
|
29
|
+
"open_risks",
|
|
30
|
+
"stop_conditions"
|
|
31
|
+
],
|
|
32
|
+
"status": "pass",
|
|
33
|
+
"changedFiles": {
|
|
34
|
+
"count": 4,
|
|
35
|
+
"fileSetHash": "sha256:5f6c8b62349d527cd2e24c6913b8c5f0af8775be1a836bf942853c39efc11f91"
|
|
36
|
+
},
|
|
37
|
+
"testsRun": [
|
|
38
|
+
{
|
|
39
|
+
"name": "unit-tests",
|
|
40
|
+
"commandHash": "sha256:1f2cc9d3f4ce8cb118198c7da4d6951de2f21485af126eb1a0fe5f1d7e0139de",
|
|
41
|
+
"status": "pass"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "typecheck",
|
|
45
|
+
"commandHash": "sha256:54d29f16fd215f74b4c03da61e37e71918f2e5c8db454a76f5f582b76471cb13",
|
|
46
|
+
"status": "pass"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"openRisks": [
|
|
50
|
+
{
|
|
51
|
+
"riskClass": "manual_browser_flow_not_verified",
|
|
52
|
+
"severity": "medium",
|
|
53
|
+
"safeToContinue": true
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"droppedContext": [
|
|
58
|
+
{
|
|
59
|
+
"kind": "exploration_transcript",
|
|
60
|
+
"reason": "not authoritative after approved plan"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"kind": "rejected_design_option",
|
|
64
|
+
"reason": "kept as citation only; not input to verify phase"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"stopConditions": []
|
|
68
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Review primitive gate example
|
|
2
|
+
|
|
3
|
+
This example validates a privacy-safe agent handoff receipt as a reviewer/CI primitive.
|
|
4
|
+
|
|
5
|
+
Run the passing fixture:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node examples/review-primitive-gate/check-review-receipt.mjs \
|
|
9
|
+
examples/review-primitive-gate/pass-review-receipt.json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Run the failing fixture:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
node examples/review-primitive-gate/check-review-receipt.mjs \
|
|
16
|
+
examples/review-primitive-gate/fail-review-receipt.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The script exits non-zero if the run is partial/unsafe, if a required check failed or was skipped, or if the agent changed scope/access without approval.
|
|
@@ -0,0 +1,100 @@
|
|
|
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-review-receipt.mjs <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.review_primitive_receipt.v1') {
|
|
23
|
+
errors.push('type must be agent.review_primitive_receipt.v1')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const key of ['assignment_id', 'run_id']) {
|
|
27
|
+
if (!receipt[key] || typeof receipt[key] !== 'string') {
|
|
28
|
+
errors.push(`${key} is required`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const boundaries = receipt.approved_boundaries || {}
|
|
33
|
+
if (!Array.isArray(boundaries.read) || boundaries.read.length === 0) {
|
|
34
|
+
errors.push('approved_boundaries.read must name at least one coarse read boundary')
|
|
35
|
+
}
|
|
36
|
+
if (!Array.isArray(boundaries.write)) {
|
|
37
|
+
errors.push('approved_boundaries.write must be an array, even for read-only runs')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const scopeChanges = receipt.scope_access_changes || []
|
|
41
|
+
if (!Array.isArray(scopeChanges)) {
|
|
42
|
+
errors.push('scope_access_changes must be an array')
|
|
43
|
+
} else {
|
|
44
|
+
for (const [index, change] of scopeChanges.entries()) {
|
|
45
|
+
if (change?.approved !== true) {
|
|
46
|
+
errors.push(`scope_access_changes[${index}] is not explicitly approved`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const checks = receipt.commands_and_checks || []
|
|
52
|
+
if (!Array.isArray(checks) || checks.length === 0) {
|
|
53
|
+
errors.push('commands_and_checks must include at least one required check/test')
|
|
54
|
+
} else {
|
|
55
|
+
for (const [index, check] of checks.entries()) {
|
|
56
|
+
if (!String(check?.kind || '').startsWith('required_')) continue
|
|
57
|
+
if (check.status !== 'passed') {
|
|
58
|
+
errors.push(`commands_and_checks[${index}] required check did not pass: ${check.status || 'missing status'}`)
|
|
59
|
+
}
|
|
60
|
+
if (!check.evidence || check.evidence === 'not-run') {
|
|
61
|
+
errors.push(`commands_and_checks[${index}] required check is missing evidence`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const allowedResumeStates = new Set(['complete', 'partial', 'unsafe-to-resume'])
|
|
67
|
+
if (!allowedResumeStates.has(receipt.resume_state)) {
|
|
68
|
+
errors.push('resume_state must be complete, partial, or unsafe-to-resume')
|
|
69
|
+
}
|
|
70
|
+
if (receipt.resume_state !== 'complete') {
|
|
71
|
+
errors.push(`resume_state is ${receipt.resume_state}; reviewer must inspect before merge/continuation`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const handoff = receipt.handoff || {}
|
|
75
|
+
if (!handoff.next_safe_action || typeof handoff.next_safe_action !== 'string') {
|
|
76
|
+
errors.push('handoff.next_safe_action is required')
|
|
77
|
+
}
|
|
78
|
+
if (!handoff.evidence_path || typeof handoff.evidence_path !== 'string') {
|
|
79
|
+
warnings.push('handoff.evidence_path is recommended for review traceability')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const privacy = receipt.privacy || {}
|
|
83
|
+
for (const key of ['raw_prompts_logged', 'raw_tool_output_logged', 'source_code_logged', 'secrets_logged']) {
|
|
84
|
+
if (privacy[key] !== false) {
|
|
85
|
+
errors.push(`privacy.${key} must be false for this gate`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = {
|
|
90
|
+
ok: errors.length === 0,
|
|
91
|
+
file,
|
|
92
|
+
assignment_id: receipt.assignment_id,
|
|
93
|
+
run_id: receipt.run_id,
|
|
94
|
+
resume_state: receipt.resume_state,
|
|
95
|
+
errors,
|
|
96
|
+
warnings
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(JSON.stringify(result, null, 2))
|
|
100
|
+
process.exit(result.ok ? 0 : 1)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "agent.review_primitive_receipt.v1",
|
|
3
|
+
"assignment_id": "agent-auth-audit-43",
|
|
4
|
+
"run_id": "run-2026-05-31T17-05Z",
|
|
5
|
+
"agent": {
|
|
6
|
+
"tool": "claude-code",
|
|
7
|
+
"role": "auth-reviewer"
|
|
8
|
+
},
|
|
9
|
+
"approved_boundaries": {
|
|
10
|
+
"read": ["src/auth/**", "tests/auth/**"],
|
|
11
|
+
"write": ["tests/auth/**"],
|
|
12
|
+
"network": false
|
|
13
|
+
},
|
|
14
|
+
"scope_access_changes": [
|
|
15
|
+
{
|
|
16
|
+
"change": "write src/auth/session.ts",
|
|
17
|
+
"reason": "agent decided implementation change was easier than fixture-only test",
|
|
18
|
+
"approved": false
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"commands_and_checks": [
|
|
22
|
+
{
|
|
23
|
+
"name": "npm test -- tests/auth",
|
|
24
|
+
"kind": "required_test",
|
|
25
|
+
"status": "skipped",
|
|
26
|
+
"evidence": "not-run"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"refused_operations": [],
|
|
30
|
+
"handoff": {
|
|
31
|
+
"changed_files_bucket": "under_5",
|
|
32
|
+
"evidence_path": "artifacts/agent-auth-audit-43.json",
|
|
33
|
+
"next_safe_action": "human must review scope change before any continuation"
|
|
34
|
+
},
|
|
35
|
+
"resume_state": "unsafe-to-resume",
|
|
36
|
+
"privacy": {
|
|
37
|
+
"raw_prompts_logged": false,
|
|
38
|
+
"raw_tool_output_logged": false,
|
|
39
|
+
"source_code_logged": false,
|
|
40
|
+
"secrets_logged": false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "agent.review_primitive_receipt.v1",
|
|
3
|
+
"assignment_id": "agent-auth-audit-42",
|
|
4
|
+
"run_id": "run-2026-05-31T17-00Z",
|
|
5
|
+
"agent": {
|
|
6
|
+
"tool": "claude-code",
|
|
7
|
+
"role": "auth-reviewer"
|
|
8
|
+
},
|
|
9
|
+
"approved_boundaries": {
|
|
10
|
+
"read": ["src/auth/**", "tests/auth/**"],
|
|
11
|
+
"write": ["tests/auth/**"],
|
|
12
|
+
"network": false
|
|
13
|
+
},
|
|
14
|
+
"scope_access_changes": [
|
|
15
|
+
{
|
|
16
|
+
"change": "read docs/security/**",
|
|
17
|
+
"reason": "needed policy wording for test fixture",
|
|
18
|
+
"approved": true,
|
|
19
|
+
"approved_by": "human-reviewer"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"commands_and_checks": [
|
|
23
|
+
{
|
|
24
|
+
"name": "npm test -- tests/auth",
|
|
25
|
+
"kind": "required_test",
|
|
26
|
+
"status": "passed",
|
|
27
|
+
"evidence": "ci://job/123#auth-tests"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "npm run lint",
|
|
31
|
+
"kind": "required_check",
|
|
32
|
+
"status": "passed",
|
|
33
|
+
"evidence": "ci://job/123#lint"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"refused_operations": [
|
|
37
|
+
{
|
|
38
|
+
"operation": "write src/auth/session.ts",
|
|
39
|
+
"reason": "outside approved write boundary"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"handoff": {
|
|
43
|
+
"changed_files_bucket": "under_5",
|
|
44
|
+
"evidence_path": "artifacts/agent-auth-audit-42.json",
|
|
45
|
+
"next_safe_action": "review tests/auth/session.test.ts before merge"
|
|
46
|
+
},
|
|
47
|
+
"resume_state": "complete",
|
|
48
|
+
"privacy": {
|
|
49
|
+
"raw_prompts_logged": false,
|
|
50
|
+
"raw_tool_output_logged": false,
|
|
51
|
+
"source_code_logged": false,
|
|
52
|
+
"secrets_logged": false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Skill install/load receipt example
|
|
2
|
+
|
|
3
|
+
This example is for setup tools that install Skills across multiple agents and then need to prove whether each target can discover/load the installed resource before the first real session.
|
|
4
|
+
|
|
5
|
+
It complements:
|
|
6
|
+
|
|
7
|
+
- `examples/install-plan-receipts/` — pre-write plan proof (`writes_started=false`);
|
|
8
|
+
- `examples/loaded-resource-boundary/` — runtime proof that an existing resource crossed discovery/attachment/injection/readability stages.
|
|
9
|
+
|
|
10
|
+
## Smoke test
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
node examples/skill-install-receipts/check-skill-install-receipt.mjs \
|
|
14
|
+
examples/skill-install-receipts/skill-install-receipt.json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Expected output:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
skill install receipt ok: 3 targets checked
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Review checklist
|
|
24
|
+
|
|
25
|
+
A useful receipt should answer:
|
|
26
|
+
|
|
27
|
+
- What installer/source ref was used, without credentials?
|
|
28
|
+
- Which agent targets and scopes were touched?
|
|
29
|
+
- Which targets were installed, skipped, discovered, deferred, injected, or readable?
|
|
30
|
+
- Was any required target unsafe before the first session?
|
|
31
|
+
- Did the receipt avoid raw skill bodies, prompts, transcripts, env dumps, secrets, and private paths?
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const file = process.argv[2] || new URL('./skill-install-receipt.json', import.meta.url);
|
|
5
|
+
const receipt = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
6
|
+
|
|
7
|
+
const allowedInstall = new Set(['installed', 'skipped', 'failed']);
|
|
8
|
+
const allowedDiscovery = new Set(['discovered', 'not_discovered', 'not_tested', 'failed']);
|
|
9
|
+
const allowedLoad = new Set(['injected', 'readable', 'activation_required', 'deferred', 'not_tested', 'failed']);
|
|
10
|
+
const allowedCost = new Set(['0-1k', '1k-5k', '5k-20k', 'over_budget', 'unknown']);
|
|
11
|
+
const requiredPrivacy = [
|
|
12
|
+
'raw_skill_body',
|
|
13
|
+
'raw_prompt',
|
|
14
|
+
'transcript',
|
|
15
|
+
'secrets',
|
|
16
|
+
'env_dump',
|
|
17
|
+
'private_absolute_path'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function assert(condition, message) {
|
|
21
|
+
if (!condition) {
|
|
22
|
+
throw new Error(message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
assert(receipt.receipt_type === 'agent.skill_install_receipt.v1', 'unexpected receipt_type');
|
|
27
|
+
assert(receipt.run_id, 'missing run_id');
|
|
28
|
+
assert(receipt.installer?.name, 'missing installer.name');
|
|
29
|
+
assert(receipt.installer?.source?.kind, 'missing installer.source.kind');
|
|
30
|
+
assert(receipt.installer?.source?.ref, 'missing installer.source.ref');
|
|
31
|
+
assert(receipt.installer.source.credentials_in_ref === false, 'source ref must not carry credentials');
|
|
32
|
+
assert(Array.isArray(receipt.targets) && receipt.targets.length > 0, 'targets must be a non-empty array');
|
|
33
|
+
assert(Array.isArray(receipt.privacy_exclusions), 'missing privacy_exclusions');
|
|
34
|
+
|
|
35
|
+
for (const item of requiredPrivacy) {
|
|
36
|
+
assert(receipt.privacy_exclusions.includes(item), `privacy_exclusions must include ${item}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let computedOverallSafe = true;
|
|
40
|
+
for (const [index, target] of receipt.targets.entries()) {
|
|
41
|
+
assert(target.agent, `targets[${index}].agent missing`);
|
|
42
|
+
assert(['project', 'global', 'workspace', 'unknown'].includes(target.scope), `targets[${index}].scope invalid`);
|
|
43
|
+
assert(typeof target.required === 'boolean', `targets[${index}].required must be boolean`);
|
|
44
|
+
assert(allowedInstall.has(target.install_status), `targets[${index}].install_status invalid`);
|
|
45
|
+
assert(allowedDiscovery.has(target.discovery_status), `targets[${index}].discovery_status invalid`);
|
|
46
|
+
assert(allowedLoad.has(target.load_status), `targets[${index}].load_status invalid`);
|
|
47
|
+
assert(allowedCost.has(target.context_cost_bucket), `targets[${index}].context_cost_bucket invalid`);
|
|
48
|
+
assert(typeof target.safe_to_start_session === 'boolean', `targets[${index}].safe_to_start_session must be boolean`);
|
|
49
|
+
|
|
50
|
+
if (target.evidence) {
|
|
51
|
+
assert(target.evidence.raw_body_logged === false, `targets[${index}] must not log raw skill body`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const requiredTargetUnsafe = target.required && (
|
|
55
|
+
target.install_status !== 'installed' ||
|
|
56
|
+
target.discovery_status !== 'discovered' ||
|
|
57
|
+
target.load_status === 'failed' ||
|
|
58
|
+
target.load_status === 'not_tested' ||
|
|
59
|
+
target.context_cost_bucket === 'over_budget' ||
|
|
60
|
+
target.safe_to_start_session !== true
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (requiredTargetUnsafe) {
|
|
64
|
+
computedOverallSafe = false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
assert(receipt.overall_safe_to_start_session === computedOverallSafe, 'overall_safe_to_start_session does not match required target safety');
|
|
69
|
+
|
|
70
|
+
const serialized = JSON.stringify(receipt).toLowerCase();
|
|
71
|
+
for (const forbidden of ['private_key', 'api_key=', 'ghp_', 'github_pat_', 'npm_', 'bearer ']) {
|
|
72
|
+
assert(!serialized.includes(forbidden), `receipt appears to include secret marker: ${forbidden}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`skill install receipt ok: ${receipt.targets.length} targets checked`);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"receipt_type": "agent.skill_install_receipt.v1",
|
|
3
|
+
"run_id": "skill-install-demo-2026-06-04T13:00Z",
|
|
4
|
+
"installer": {
|
|
5
|
+
"name": "skills-cli",
|
|
6
|
+
"version_bucket": "0.x",
|
|
7
|
+
"command_class": "skill_package_install",
|
|
8
|
+
"source": {
|
|
9
|
+
"kind": "git_ref",
|
|
10
|
+
"package": "vercel-labs/skills/context-budget-preflight",
|
|
11
|
+
"ref": "sha256:demo-source-package-hash",
|
|
12
|
+
"credentials_in_ref": false
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"mode_requested": "install_then_check",
|
|
16
|
+
"mode_effective": "post_install_check",
|
|
17
|
+
"writes_completed": true,
|
|
18
|
+
"backup_created": true,
|
|
19
|
+
"targets": [
|
|
20
|
+
{
|
|
21
|
+
"agent": "claude-code",
|
|
22
|
+
"scope": "project",
|
|
23
|
+
"required": true,
|
|
24
|
+
"install_status": "installed",
|
|
25
|
+
"discovery_status": "discovered",
|
|
26
|
+
"load_status": "activation_required",
|
|
27
|
+
"activation": "on_demand_skill_description",
|
|
28
|
+
"context_cost_bucket": "0-1k",
|
|
29
|
+
"safe_to_start_session": true,
|
|
30
|
+
"evidence": {
|
|
31
|
+
"manifest_hash": "sha256:demo-claude-manifest-hash",
|
|
32
|
+
"skill_body_hash": "sha256:demo-claude-skill-body-hash",
|
|
33
|
+
"raw_body_logged": false
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"agent": "codex",
|
|
38
|
+
"scope": "project",
|
|
39
|
+
"required": true,
|
|
40
|
+
"install_status": "installed",
|
|
41
|
+
"discovery_status": "discovered",
|
|
42
|
+
"load_status": "deferred",
|
|
43
|
+
"activation": "manual_or_triggered",
|
|
44
|
+
"context_cost_bucket": "0-1k",
|
|
45
|
+
"safe_to_start_session": true,
|
|
46
|
+
"evidence": {
|
|
47
|
+
"manifest_hash": "sha256:demo-codex-manifest-hash",
|
|
48
|
+
"skill_body_hash": "sha256:demo-codex-skill-body-hash",
|
|
49
|
+
"raw_body_logged": false
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"agent": "zed-acp",
|
|
54
|
+
"scope": "project",
|
|
55
|
+
"required": false,
|
|
56
|
+
"install_status": "skipped",
|
|
57
|
+
"discovery_status": "not_tested",
|
|
58
|
+
"load_status": "not_tested",
|
|
59
|
+
"activation": "unsupported_target_in_this_installer",
|
|
60
|
+
"context_cost_bucket": "unknown",
|
|
61
|
+
"safe_to_start_session": true,
|
|
62
|
+
"skipped_reason": "target_not_selected"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"overall_safe_to_start_session": true,
|
|
66
|
+
"next_safe_command": "start a disposable agent session and ask it to cite the installed Skill sentinel line",
|
|
67
|
+
"privacy_exclusions": [
|
|
68
|
+
"raw_skill_body",
|
|
69
|
+
"raw_source",
|
|
70
|
+
"raw_prompt",
|
|
71
|
+
"transcript",
|
|
72
|
+
"raw_tool_output",
|
|
73
|
+
"customer_data",
|
|
74
|
+
"secrets",
|
|
75
|
+
"env_dump",
|
|
76
|
+
"private_absolute_path"
|
|
77
|
+
],
|
|
78
|
+
"audit_gap": "proves installer/discovery/load boundary, not skill semantic quality or future activation"
|
|
79
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Skill use-rate receipts
|
|
2
|
+
|
|
3
|
+
This example shows a privacy-safe receipt for the gap between installing an Agent Skill and actually using it.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
node check-skill-use-rate.mjs skill-use-rate-receipt.json
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Expected output:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
skill use-rate receipt ok: 3 skills checked, 1 unused install warning
|
|
13
|
+
- rust-lsp-helper is installed/attached but has 0 invocations in this window
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The warning is intentional: installation is not adoption. A skill can be installed, attached, and discoverable while still adding context surface area with no observed invocation.
|
|
@@ -0,0 +1,89 @@
|
|
|
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, 'skill-use-rate-receipt.json')
|
|
6
|
+
const receipt = JSON.parse(fs.readFileSync(receiptPath, 'utf8'))
|
|
7
|
+
const errors = []
|
|
8
|
+
const warnings = []
|
|
9
|
+
|
|
10
|
+
function requireString(value, field) {
|
|
11
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
12
|
+
errors.push(`${field} must be a non-empty string`)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function requireBoolean(value, field) {
|
|
17
|
+
if (typeof value !== 'boolean') {
|
|
18
|
+
errors.push(`${field} must be boolean`)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function requireNonNegativeInteger(value, field) {
|
|
23
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
24
|
+
errors.push(`${field} must be a non-negative integer`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (receipt.schema !== 'pluribus.skill_use_rate_receipt.v1') {
|
|
29
|
+
errors.push('schema must be pluribus.skill_use_rate_receipt.v1')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
requireString(receipt.run_id, 'run_id')
|
|
33
|
+
requireString(receipt.generated_at, 'generated_at')
|
|
34
|
+
requireString(receipt.installer?.name, 'installer.name')
|
|
35
|
+
requireString(receipt.window?.started_at, 'window.started_at')
|
|
36
|
+
requireString(receipt.window?.ended_at, 'window.ended_at')
|
|
37
|
+
|
|
38
|
+
if (!Array.isArray(receipt.skills) || receipt.skills.length === 0) {
|
|
39
|
+
errors.push('skills must be a non-empty array')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [index, skill] of (receipt.skills || []).entries()) {
|
|
43
|
+
const prefix = `skills[${index}]`
|
|
44
|
+
requireString(skill.skill_id, `${prefix}.skill_id`)
|
|
45
|
+
requireString(skill.source_ref, `${prefix}.source_ref`)
|
|
46
|
+
requireString(skill.target_agent, `${prefix}.target_agent`)
|
|
47
|
+
requireString(skill.scope, `${prefix}.scope`)
|
|
48
|
+
requireString(skill.install_method, `${prefix}.install_method`)
|
|
49
|
+
requireBoolean(skill.discovered, `${prefix}.discovered`)
|
|
50
|
+
requireBoolean(skill.installed, `${prefix}.installed`)
|
|
51
|
+
requireBoolean(skill.attached, `${prefix}.attached`)
|
|
52
|
+
requireBoolean(skill.unused_since_install, `${prefix}.unused_since_install`)
|
|
53
|
+
requireNonNegativeInteger(skill.invoked_count, `${prefix}.invoked_count`)
|
|
54
|
+
requireNonNegativeInteger(skill.acted_on_count, `${prefix}.acted_on_count`)
|
|
55
|
+
|
|
56
|
+
if (Number.isInteger(skill.acted_on_count) && Number.isInteger(skill.invoked_count) && skill.acted_on_count > skill.invoked_count) {
|
|
57
|
+
errors.push(`${prefix}.acted_on_count cannot exceed invoked_count`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (skill.installed && skill.attached && skill.invoked_count === 0) {
|
|
61
|
+
if (skill.unused_since_install !== true) {
|
|
62
|
+
errors.push(`${prefix}.unused_since_install must be true when installed/attached but never invoked`)
|
|
63
|
+
}
|
|
64
|
+
warnings.push(`${skill.skill_id || prefix} is installed/attached but has 0 invocations in this window`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (skill.invoked_count > 0) {
|
|
68
|
+
if (skill.unused_since_install !== false) {
|
|
69
|
+
errors.push(`${prefix}.unused_since_install must be false when invoked_count > 0`)
|
|
70
|
+
}
|
|
71
|
+
if (typeof skill.last_invoked_at !== 'string' || skill.last_invoked_at.trim() === '') {
|
|
72
|
+
errors.push(`${prefix}.last_invoked_at must be set when invoked_count > 0`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!Array.isArray(skill.evidence) || skill.evidence.length === 0) {
|
|
77
|
+
errors.push(`${prefix}.evidence must include at least one privacy-safe evidence ref`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (errors.length > 0) {
|
|
82
|
+
console.error('skill use-rate receipt invalid:')
|
|
83
|
+
for (const error of errors) console.error(`- ${error}`)
|
|
84
|
+
process.exit(1)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const warningLabel = warnings.length === 1 ? 'warning' : 'warnings'
|
|
88
|
+
console.log(`skill use-rate receipt ok: ${receipt.skills.length} skills checked, ${warnings.length} unused install ${warningLabel}`)
|
|
89
|
+
for (const warning of warnings) console.log(`- ${warning}`)
|