provenance-protocol 0.1.3 → 0.2.1
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/README.md +103 -22
- package/SPEC.md +483 -0
- package/package.json +23 -4
- package/schema/provenance-0.1.json +229 -0
- package/src/cli.js +196 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +54 -6
- package/src/verify.d.ts +85 -0
- package/src/verify.js +277 -0
- package/test-vectors/README.md +52 -0
- package/test-vectors/signatures-0.1.json +80 -0
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "provenance-protocol",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "The Provenance Protocol \u2014 open standard for AI agent identity \u2014 and its reference SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./src/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"provenance": "./src/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"types": "./src/index.d.ts",
|
|
@@ -13,6 +16,10 @@
|
|
|
13
16
|
"./keygen": {
|
|
14
17
|
"types": "./src/keygen.d.ts",
|
|
15
18
|
"import": "./src/keygen.js"
|
|
19
|
+
},
|
|
20
|
+
"./verify": {
|
|
21
|
+
"types": "./src/verify.d.ts",
|
|
22
|
+
"import": "./src/verify.js"
|
|
16
23
|
}
|
|
17
24
|
},
|
|
18
25
|
"files": [
|
|
@@ -20,7 +27,13 @@
|
|
|
20
27
|
"src/index.d.ts",
|
|
21
28
|
"src/keygen.js",
|
|
22
29
|
"src/keygen.d.ts",
|
|
23
|
-
"
|
|
30
|
+
"src/verify.d.ts",
|
|
31
|
+
"src/verify.js",
|
|
32
|
+
"src/cli.js",
|
|
33
|
+
"README.md",
|
|
34
|
+
"SPEC.md",
|
|
35
|
+
"schema/provenance-0.1.json",
|
|
36
|
+
"test-vectors/"
|
|
24
37
|
],
|
|
25
38
|
"keywords": [
|
|
26
39
|
"ai-agent",
|
|
@@ -28,7 +41,10 @@
|
|
|
28
41
|
"provenance",
|
|
29
42
|
"trust",
|
|
30
43
|
"llm",
|
|
31
|
-
"verification"
|
|
44
|
+
"verification",
|
|
45
|
+
"ed25519",
|
|
46
|
+
"offline-verification",
|
|
47
|
+
"standard"
|
|
32
48
|
],
|
|
33
49
|
"license": "MIT",
|
|
34
50
|
"repository": {
|
|
@@ -38,5 +54,8 @@
|
|
|
38
54
|
"homepage": "https://getprovenance.dev",
|
|
39
55
|
"engines": {
|
|
40
56
|
"node": ">=14.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"test": "node test/verify.test.mjs"
|
|
41
60
|
}
|
|
42
61
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://getprovenance.dev/schema/0.1.json",
|
|
4
|
+
"title": "PROVENANCE.yml",
|
|
5
|
+
"description": "Provenance Protocol v0.1 schema for AI agent identity files",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"provenance",
|
|
9
|
+
"name",
|
|
10
|
+
"description"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"provenance": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Spec version",
|
|
16
|
+
"enum": [
|
|
17
|
+
"0.1"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"name": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Human-readable name for this agent",
|
|
23
|
+
"minLength": 1,
|
|
24
|
+
"maxLength": 100
|
|
25
|
+
},
|
|
26
|
+
"description": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "What this agent does, in plain language",
|
|
29
|
+
"minLength": 1,
|
|
30
|
+
"maxLength": 1000
|
|
31
|
+
},
|
|
32
|
+
"version": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Semantic version of the agent",
|
|
35
|
+
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+.*$"
|
|
36
|
+
},
|
|
37
|
+
"model": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"description": "The LLM powering this agent",
|
|
40
|
+
"properties": {
|
|
41
|
+
"provider": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"enum": [
|
|
44
|
+
"anthropic",
|
|
45
|
+
"openai",
|
|
46
|
+
"google",
|
|
47
|
+
"mistral",
|
|
48
|
+
"meta",
|
|
49
|
+
"local",
|
|
50
|
+
"other"
|
|
51
|
+
],
|
|
52
|
+
"description": "LLM provider"
|
|
53
|
+
},
|
|
54
|
+
"model_id": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "Specific model ID e.g. claude-sonnet-4-5"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"additionalProperties": false
|
|
60
|
+
},
|
|
61
|
+
"capabilities": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"description": "What this agent can do \u2014 standard vocabulary or domain:custom",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"pattern": "^[a-z][a-z0-9_-]*(:[a-z0-9_:-]+)?$"
|
|
67
|
+
},
|
|
68
|
+
"uniqueItems": true
|
|
69
|
+
},
|
|
70
|
+
"constraints": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"description": "Public commitments \u2014 what this agent will NEVER do",
|
|
73
|
+
"items": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"pattern": "^no:[a-z0-9_:-]+$"
|
|
76
|
+
},
|
|
77
|
+
"uniqueItems": true
|
|
78
|
+
},
|
|
79
|
+
"delegates": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"description": "Sub-agents this orchestrator spawns \u2014 required if delegate:agents capability declared",
|
|
82
|
+
"items": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"required": [
|
|
85
|
+
"provenance_id"
|
|
86
|
+
],
|
|
87
|
+
"properties": {
|
|
88
|
+
"provenance_id": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"pattern": "^provenance:(github|npm|pypi|huggingface|clawmarket):.+$"
|
|
91
|
+
},
|
|
92
|
+
"purpose": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"maxLength": 200
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"skills": {
|
|
101
|
+
"type": "array",
|
|
102
|
+
"description": "External skills this agent depends on",
|
|
103
|
+
"items": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"required": [
|
|
106
|
+
"id",
|
|
107
|
+
"source"
|
|
108
|
+
],
|
|
109
|
+
"properties": {
|
|
110
|
+
"id": {
|
|
111
|
+
"type": "string"
|
|
112
|
+
},
|
|
113
|
+
"source": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"enum": [
|
|
116
|
+
"skillsmp",
|
|
117
|
+
"github",
|
|
118
|
+
"npm",
|
|
119
|
+
"custom"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
"url": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"format": "uri"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"additionalProperties": false
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"runtime": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"description": "How this agent runs",
|
|
133
|
+
"properties": {
|
|
134
|
+
"type": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"enum": [
|
|
137
|
+
"task",
|
|
138
|
+
"persistent",
|
|
139
|
+
"scheduled"
|
|
140
|
+
],
|
|
141
|
+
"description": "task: runs to completion | persistent: always on | scheduled: cron-like"
|
|
142
|
+
},
|
|
143
|
+
"trigger": {
|
|
144
|
+
"type": "string",
|
|
145
|
+
"enum": [
|
|
146
|
+
"api",
|
|
147
|
+
"webhook",
|
|
148
|
+
"schedule",
|
|
149
|
+
"event"
|
|
150
|
+
],
|
|
151
|
+
"description": "What starts this agent"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"additionalProperties": false
|
|
155
|
+
},
|
|
156
|
+
"contact": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"description": "Who is responsible for this agent",
|
|
159
|
+
"properties": {
|
|
160
|
+
"name": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"minLength": 1
|
|
163
|
+
},
|
|
164
|
+
"url": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"format": "uri"
|
|
167
|
+
},
|
|
168
|
+
"email": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"format": "email"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"additionalProperties": false
|
|
174
|
+
},
|
|
175
|
+
"provenance_id": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"description": "Your Provenance identifier \u2014 links this file to your index entry",
|
|
178
|
+
"pattern": "^provenance:(github|npm|pypi|huggingface|clawmarket):.+$"
|
|
179
|
+
},
|
|
180
|
+
"identity": {
|
|
181
|
+
"type": "object",
|
|
182
|
+
"description": "Cryptographic identity. public_key alone advertises the key an agent will use to prove control live. Adding signature makes the declaration itself tamper-evident.",
|
|
183
|
+
"properties": {
|
|
184
|
+
"public_key": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"description": "Base64-encoded Ed25519 SPKI DER public key. Generate with: generateProvenanceKeyPair() from provenance-protocol/keygen"
|
|
187
|
+
},
|
|
188
|
+
"signature": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"description": "Base64 Ed25519 signature of '<provenance_id>:<public_key>' \u2014 proves you control the private key and that the declaration is unaltered. Optional: omit it when the key is published only for live challenge-response. Requires provenance_id."
|
|
191
|
+
},
|
|
192
|
+
"algorithm": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"enum": [
|
|
195
|
+
"ed25519"
|
|
196
|
+
],
|
|
197
|
+
"description": "Signing algorithm. Always ed25519."
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"required": [
|
|
201
|
+
"public_key"
|
|
202
|
+
],
|
|
203
|
+
"additionalProperties": false
|
|
204
|
+
},
|
|
205
|
+
"ajp": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"description": "Agent Job Protocol endpoint \u2014 enables agent-to-agent job delegation",
|
|
208
|
+
"properties": {
|
|
209
|
+
"endpoint": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"format": "uri",
|
|
212
|
+
"description": "Base URL for your AJP implementation. Must accept POST /jobs, GET /jobs/:id, POST /jobs/:id/ack"
|
|
213
|
+
},
|
|
214
|
+
"version": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"enum": [
|
|
217
|
+
"0.1"
|
|
218
|
+
],
|
|
219
|
+
"description": "AJP spec version"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"required": [
|
|
223
|
+
"endpoint"
|
|
224
|
+
],
|
|
225
|
+
"additionalProperties": false
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"additionalProperties": false
|
|
229
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* provenance CLI
|
|
4
|
+
*
|
|
5
|
+
* npx provenance keygen
|
|
6
|
+
* npx provenance register --id provenance:github:org/agent --url https://...
|
|
7
|
+
* npx provenance status provenance:github:org/agent
|
|
8
|
+
* npx provenance revoke --id provenance:github:org/agent
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import { generateProvenanceKeyPair, signChallenge, signForProvenance, signRevocation } from './keygen.js';
|
|
14
|
+
|
|
15
|
+
const API = 'https://getprovenance.dev/api/agents';
|
|
16
|
+
const KEY_FILE = '.provenance-key';
|
|
17
|
+
|
|
18
|
+
function readPrivateKey() {
|
|
19
|
+
if (process.env.PROVENANCE_PRIVATE_KEY) return process.env.PROVENANCE_PRIVATE_KEY.trim();
|
|
20
|
+
if (existsSync(KEY_FILE)) return readFileSync(KEY_FILE, 'utf8').trim();
|
|
21
|
+
console.error(`No private key found. Set PROVENANCE_PRIVATE_KEY or run: npx provenance keygen`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseArgs(argv) {
|
|
26
|
+
const args = {};
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
if (argv[i].startsWith('--')) {
|
|
29
|
+
args[argv[i].slice(2)] = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[++i] : true;
|
|
30
|
+
} else {
|
|
31
|
+
args._ = args._ || [];
|
|
32
|
+
args._.push(argv[i]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [,, command, ...rest] = process.argv;
|
|
39
|
+
const args = parseArgs(rest);
|
|
40
|
+
|
|
41
|
+
// ── keygen ──────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
if (command === 'keygen') {
|
|
44
|
+
const { publicKey, privateKey } = generateProvenanceKeyPair();
|
|
45
|
+
|
|
46
|
+
writeFileSync(KEY_FILE, privateKey, { mode: 0o600 });
|
|
47
|
+
try { execSync(`grep -qxF "${KEY_FILE}" .gitignore 2>/dev/null || echo "${KEY_FILE}" >> .gitignore`); } catch {}
|
|
48
|
+
|
|
49
|
+
console.log(`\nKeypair generated.\n`);
|
|
50
|
+
console.log(`Private key → ${KEY_FILE} (chmod 600, added to .gitignore)`);
|
|
51
|
+
console.log(`\nPublic key (add to PROVENANCE.yml):\n`);
|
|
52
|
+
console.log(` identity:`);
|
|
53
|
+
console.log(` public_key: "${publicKey}"`);
|
|
54
|
+
console.log(` algorithm: ed25519`);
|
|
55
|
+
console.log(`\nNext: npx provenance register --id provenance:<platform>:<org>/<name>\n`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── register ─────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
if (command === 'register') {
|
|
62
|
+
const id = args.id || args._?.[0];
|
|
63
|
+
if (!id) { console.error('Usage: npx provenance register --id provenance:<platform>:<org>/<name> [options]'); process.exit(1); }
|
|
64
|
+
|
|
65
|
+
const privateKey = readPrivateKey();
|
|
66
|
+
const { publicKey } = (() => {
|
|
67
|
+
// derive public key from private key for display — we just use the stored one
|
|
68
|
+
// We can't derive public from private easily here, so read from PROVENANCE.yml or require --public-key
|
|
69
|
+
return { publicKey: args['public-key'] || null };
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
// If no public key arg, re-generate won't work — need the stored public key
|
|
73
|
+
// Best path: require keygen was run, read public key from PROVENANCE.yml if present
|
|
74
|
+
let pubKey = args['public-key'];
|
|
75
|
+
if (!pubKey) {
|
|
76
|
+
if (existsSync('PROVENANCE.yml')) {
|
|
77
|
+
const yml = readFileSync('PROVENANCE.yml', 'utf8');
|
|
78
|
+
const match = yml.match(/public_key:\s*["']?([A-Za-z0-9+/=]+)["']?/);
|
|
79
|
+
if (match) pubKey = match[1];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!pubKey) {
|
|
83
|
+
console.error('Public key required. Pass --public-key or add identity.public_key to PROVENANCE.yml first.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const signed_challenge = signChallenge(privateKey, id, 'REGISTER');
|
|
88
|
+
|
|
89
|
+
const body = {
|
|
90
|
+
provenance_id: id,
|
|
91
|
+
public_key: pubKey,
|
|
92
|
+
signed_challenge,
|
|
93
|
+
...(args.url ? { url: args.url } : {}),
|
|
94
|
+
...(args.name ? { name: args.name } : {}),
|
|
95
|
+
...(args.description ? { description: args.description } : {}),
|
|
96
|
+
...(args.capabilities ? { capabilities: args.capabilities.split(',').map(s => s.trim()) } : {}),
|
|
97
|
+
...(args.constraints ? { constraints: args.constraints.split(',').map(s => s.trim()) } : {}),
|
|
98
|
+
...(args.model ? { model_provider: args.model } : {}),
|
|
99
|
+
...(args['model-id'] ? { model_id: args['model-id'] } : {}),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(`${API}/register`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
body: JSON.stringify(body),
|
|
107
|
+
});
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
console.error(`\nRegistration failed: ${data.error}`);
|
|
111
|
+
if (data.hint) console.error(`Hint: ${data.hint}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log(`\n${data.created ? 'Registered' : 'Updated'}: ${id}`);
|
|
115
|
+
console.log(`identity: ${data.agent?.identity}`);
|
|
116
|
+
console.log(`profile: https://getprovenance.dev/agent/${id.replace('provenance:', '').replace(':', '/')}\n`);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error('Network error:', e.message);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── status ───────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
if (command === 'status') {
|
|
127
|
+
const id = args._?.[0] || args.id;
|
|
128
|
+
if (!id) { console.error('Usage: npx provenance status <provenance_id>'); process.exit(1); }
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(`${API}/register?provenance_id=${encodeURIComponent(id)}`);
|
|
132
|
+
const data = await res.json();
|
|
133
|
+
if (!data.registered) { console.log(`\nNot registered: ${id}\n`); process.exit(0); }
|
|
134
|
+
const a = data.agent;
|
|
135
|
+
console.log(`\n${id}`);
|
|
136
|
+
console.log(`name: ${a.name || '—'}`);
|
|
137
|
+
console.log(`identity: ${a.identity}`);
|
|
138
|
+
console.log(`status: ${a.status}`);
|
|
139
|
+
console.log(`profile: https://getprovenance.dev/agent/${id.replace('provenance:', '').replace(':', '/')}\n`);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error('Network error:', e.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── revoke ───────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
if (command === 'revoke') {
|
|
150
|
+
const id = args.id || args._?.[0];
|
|
151
|
+
if (!id) { console.error('Usage: npx provenance revoke --id <provenance_id>'); process.exit(1); }
|
|
152
|
+
|
|
153
|
+
const privateKey = readPrivateKey();
|
|
154
|
+
const signed_challenge = signRevocation(privateKey, id);
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetch(`${API}/revoke`, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
body: JSON.stringify({ provenance_id: id, signed_challenge }),
|
|
161
|
+
});
|
|
162
|
+
const data = await res.json();
|
|
163
|
+
if (!res.ok) { console.error(`Revocation failed: ${data.error}`); process.exit(1); }
|
|
164
|
+
console.log(`\nKey revoked for ${id}.`);
|
|
165
|
+
console.log(`Run npx provenance keygen && npx provenance register --id ${id} to re-register with a new key.\n`);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.error('Network error:', e.message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── help ─────────────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
console.log(`
|
|
176
|
+
provenance <command>
|
|
177
|
+
|
|
178
|
+
keygen Generate an Ed25519 keypair
|
|
179
|
+
register --id <id> [options] Register or update your agent
|
|
180
|
+
status <id> Check registration status
|
|
181
|
+
revoke --id <id> Revoke your registered key
|
|
182
|
+
|
|
183
|
+
register options:
|
|
184
|
+
--id provenance:<platform>:<org>/<name> (required)
|
|
185
|
+
--url URL to your PROVENANCE.yml (for independent verification)
|
|
186
|
+
--name Agent display name
|
|
187
|
+
--description One-sentence description
|
|
188
|
+
--capabilities read:web,write:summaries
|
|
189
|
+
--constraints no:pii,no:financial:transact
|
|
190
|
+
--model anthropic / openai / etc.
|
|
191
|
+
--model-id claude-sonnet-4-6 / gpt-4o / etc.
|
|
192
|
+
--public-key Base64 public key (auto-read from PROVENANCE.yml if present)
|
|
193
|
+
|
|
194
|
+
Full docs: https://getprovenance.dev/docs
|
|
195
|
+
`);
|
|
196
|
+
process.exit(0);
|
package/src/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface TrustProfile {
|
|
|
8
8
|
platform?: string;
|
|
9
9
|
name?: string;
|
|
10
10
|
declared?: boolean;
|
|
11
|
+
identity_verified?: boolean;
|
|
11
12
|
confidence?: number;
|
|
12
13
|
age_days?: number | null;
|
|
13
14
|
capabilities?: string[];
|
|
@@ -45,6 +46,8 @@ export interface VerifyResult {
|
|
|
45
46
|
|
|
46
47
|
export interface GateOptions {
|
|
47
48
|
requireDeclared?: boolean;
|
|
49
|
+
/** Require identity_verified: true — agent must have cryptographic proof of key ownership. */
|
|
50
|
+
requireVerified?: boolean;
|
|
48
51
|
requireConstraints?: string[];
|
|
49
52
|
requireCapabilities?: string[];
|
|
50
53
|
requireClean?: boolean;
|
package/src/index.js
CHANGED
|
@@ -45,6 +45,35 @@ async function _verifyEd25519(publicKeyBase64, signatureBase64, message) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const DEFAULT_API = 'https://getprovenance.dev';
|
|
48
|
+
|
|
49
|
+
const SEVERITY_ORDER = ['low', 'medium', 'high', 'critical'];
|
|
50
|
+
|
|
51
|
+
// Returns a failure reason string if clean check fails, null if passes.
|
|
52
|
+
// requireClean: true | false | { minSeverity: 'low'|'medium'|'high'|'critical' }
|
|
53
|
+
function _evaluateClean(requireClean, trust) {
|
|
54
|
+
if (!requireClean) return null;
|
|
55
|
+
if (trust.incidents === 0) return null;
|
|
56
|
+
|
|
57
|
+
// true = block on any open incident
|
|
58
|
+
if (requireClean === true) {
|
|
59
|
+
return `Agent has ${trust.incidents} open incident(s)`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// { minSeverity } = block only if any open incident meets or exceeds that severity
|
|
63
|
+
if (requireClean.minSeverity) {
|
|
64
|
+
const threshold = SEVERITY_ORDER.indexOf(requireClean.minSeverity);
|
|
65
|
+
const incidents = trust.incidents_detail || [];
|
|
66
|
+
const blocking = incidents.filter(inc =>
|
|
67
|
+
SEVERITY_ORDER.indexOf(inc.severity || 'medium') >= threshold
|
|
68
|
+
);
|
|
69
|
+
if (blocking.length > 0) {
|
|
70
|
+
return `Agent has ${blocking.length} open incident(s) at or above severity '${requireClean.minSeverity}'`;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
48
77
|
const DEFAULT_CACHE_TTL = 300; // 5 minutes
|
|
49
78
|
|
|
50
79
|
// Simple LRU cache
|
|
@@ -106,7 +135,7 @@ export class Provenance {
|
|
|
106
135
|
* // confidence: 0.9, — internal signal, use declared/identity_verified for trust decisions
|
|
107
136
|
* // capabilities: ['read:web', 'write:summaries'],
|
|
108
137
|
* // constraints: ['no:financial:transact', 'no:pii'],
|
|
109
|
-
* // incidents: 0,
|
|
138
|
+
* // incidents: 0, — open/investigating only; resolved don't block gate()
|
|
110
139
|
* // model: { provider: 'anthropic', model_id: 'claude-sonnet-4-5' },
|
|
111
140
|
* // status: 'active',
|
|
112
141
|
* // }
|
|
@@ -141,6 +170,8 @@ export class Provenance {
|
|
|
141
170
|
capabilities: data.capabilities || [],
|
|
142
171
|
constraints: data.constraints || [],
|
|
143
172
|
incidents: data.incident_count || 0,
|
|
173
|
+
incidents_detail: data.incidents || [],
|
|
174
|
+
resolved_incidents: data.resolved_incidents || [],
|
|
144
175
|
model: data.model || null,
|
|
145
176
|
status: data.status || 'unknown',
|
|
146
177
|
first_seen: data.timestamps?.first_seen || null,
|
|
@@ -260,11 +291,18 @@ export class Provenance {
|
|
|
260
291
|
* Run all your trust requirements in one call.
|
|
261
292
|
* Returns { allowed, reason, trust }.
|
|
262
293
|
*
|
|
294
|
+
* requireClean accepts:
|
|
295
|
+
* - true block on any open incident (default)
|
|
296
|
+
* - false ignore incidents entirely
|
|
297
|
+
* - { minSeverity } block only if any open incident meets or exceeds severity
|
|
298
|
+
* severity order: low < medium < high < critical
|
|
299
|
+
* e.g. { minSeverity: 'high' } allows low/medium incidents
|
|
300
|
+
*
|
|
263
301
|
* Example:
|
|
264
302
|
* const result = await provenance.gate('provenance:github:alice/agent', {
|
|
265
303
|
* requireDeclared: true,
|
|
266
304
|
* requireConstraints: ['no:financial:transact', 'no:pii'],
|
|
267
|
-
* requireClean:
|
|
305
|
+
* requireClean: { minSeverity: 'high' },
|
|
268
306
|
* requireMinAge: 30,
|
|
269
307
|
* requireMinConfidence: 0.7,
|
|
270
308
|
* });
|
|
@@ -275,6 +313,7 @@ export class Provenance {
|
|
|
275
313
|
*/
|
|
276
314
|
async gate(provenanceId, {
|
|
277
315
|
requireDeclared = false,
|
|
316
|
+
requireVerified = false,
|
|
278
317
|
requireConstraints = [],
|
|
279
318
|
requireCapabilities = [],
|
|
280
319
|
requireClean = true,
|
|
@@ -321,8 +360,12 @@ export class Provenance {
|
|
|
321
360
|
if (requireDeclared && !trust.declared) {
|
|
322
361
|
return { allowed: false, reason: 'Agent has not declared a PROVENANCE.yml file', trust };
|
|
323
362
|
}
|
|
324
|
-
if (
|
|
325
|
-
return { allowed: false, reason:
|
|
363
|
+
if (requireVerified && !trust.identity_verified) {
|
|
364
|
+
return { allowed: false, reason: 'Agent identity is not cryptographically verified', trust };
|
|
365
|
+
}
|
|
366
|
+
if (requireClean) {
|
|
367
|
+
const blocked = _evaluateClean(requireClean, trust);
|
|
368
|
+
if (blocked) return { allowed: false, reason: blocked, trust };
|
|
326
369
|
}
|
|
327
370
|
if (requireMinConfidence && trust.confidence < requireMinConfidence) {
|
|
328
371
|
return { allowed: false, reason: `Agent confidence ${trust.confidence} below required ${requireMinConfidence}`, trust };
|
|
@@ -466,6 +509,7 @@ export class Provenance {
|
|
|
466
509
|
_evaluateGate(trust, options) {
|
|
467
510
|
const {
|
|
468
511
|
requireDeclared = false,
|
|
512
|
+
requireVerified = false,
|
|
469
513
|
requireConstraints = [],
|
|
470
514
|
requireCapabilities = [],
|
|
471
515
|
requireClean = true,
|
|
@@ -482,6 +526,9 @@ export class Provenance {
|
|
|
482
526
|
if (requireDeclared && !trust.declared) {
|
|
483
527
|
return { allowed: false, reason: 'Agent has not declared a PROVENANCE.yml file', trust };
|
|
484
528
|
}
|
|
529
|
+
if (requireVerified && !trust.identity_verified) {
|
|
530
|
+
return { allowed: false, reason: 'Agent identity is not cryptographically verified', trust };
|
|
531
|
+
}
|
|
485
532
|
for (const c of requireConstraints) {
|
|
486
533
|
if (!trust.constraints?.includes(c)) {
|
|
487
534
|
return { allowed: false, reason: `Agent has not committed to constraint: ${c}`, trust };
|
|
@@ -492,8 +539,9 @@ export class Provenance {
|
|
|
492
539
|
return { allowed: false, reason: `Agent does not have capability: ${c}`, trust };
|
|
493
540
|
}
|
|
494
541
|
}
|
|
495
|
-
if (requireClean
|
|
496
|
-
|
|
542
|
+
if (requireClean) {
|
|
543
|
+
const blocked = _evaluateClean(requireClean, trust);
|
|
544
|
+
if (blocked) return { allowed: false, reason: blocked, trust };
|
|
497
545
|
}
|
|
498
546
|
if (requireMinAge > 0 && (trust.age_days || 0) < requireMinAge) {
|
|
499
547
|
return { allowed: false, reason: `Agent is only ${trust.age_days || 0} days old (minimum: ${requireMinAge})`, trust };
|