provenance-protocol 0.1.4 → 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.
@@ -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/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: true,
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 (requireClean && trust.incidents > 0) {
325
- return { allowed: false, reason: `Agent has ${trust.incidents} open incident(s)`, trust };
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 && trust.incidents > 0) {
496
- return { allowed: false, reason: `Agent has ${trust.incidents} open incident(s)`, trust };
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 };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * provenance-protocol — offline verification (TypeScript definitions)
3
+ *
4
+ * Verifies a PROVENANCE.yml declaration without contacting any service.
5
+ * Works anywhere the Web Crypto API exists: modern browsers, Node 18+.
6
+ */
7
+
8
+ /** Whether a declaration was served from the location its provenance_id names. */
9
+ export type LocationCheck = 'match' | 'mismatch' | 'unchecked';
10
+
11
+ export interface VerificationResult {
12
+ /** An identity.signature was present to check. */
13
+ signed: boolean;
14
+ /** The signature verified against identity.public_key. */
15
+ valid: boolean;
16
+ /** Why the result is not a clean pass, or null when it is. */
17
+ reason: string | null;
18
+ provenanceId: string | null;
19
+ publicKey: string | null;
20
+ /** SHA-256 of the public key, hex. Store it to detect key rotation. */
21
+ fingerprint: string | null;
22
+ location: LocationCheck;
23
+ /**
24
+ * Signature valid AND retrieval location confirmed. Only both together
25
+ * justify treating the declaration as the named project owner's.
26
+ */
27
+ trustworthy: boolean;
28
+ }
29
+
30
+ export interface VerifyOptions {
31
+ /** URL the declaration was fetched from, for the location check. */
32
+ retrievedFrom?: string;
33
+ }
34
+
35
+ /**
36
+ * Verify a parsed PROVENANCE.yml declaration offline.
37
+ *
38
+ * Declarations are YAML — parse with your own library and pass the object;
39
+ * this module is dependency-free by design.
40
+ *
41
+ * A valid signature proves the declaration came from the holder of that
42
+ * private key and is unaltered. It does not prove who that holder is, that
43
+ * the declared capabilities are accurate, or that the declaration is current.
44
+ * Revocation and standing cannot be checked offline.
45
+ */
46
+ export function verifyDeclaration(
47
+ declaration: unknown,
48
+ options?: VerifyOptions
49
+ ): Promise<VerificationResult>;
50
+
51
+ /** SHA-256 of the raw public key bytes, hex encoded. Use it to detect key rotation. */
52
+ export function keyFingerprint(publicKeyBase64: string): Promise<string>;
53
+
54
+ /** Split a provenance id into its platform and path, or null if malformed. */
55
+ export function parseProvenanceId(
56
+ provenanceId: string
57
+ ): { platform: string; path: string } | null;
58
+
59
+ /**
60
+ * Does a retrieval location agree with the declaration's own provenance_id?
61
+ * Returns 'unchecked' when the location cannot be interpreted, so an unknown
62
+ * host is never reported as agreement.
63
+ */
64
+ export function checkLocation(provenanceId: string, retrievedFrom: string): LocationCheck;
65
+
66
+ /**
67
+ * Verify a live challenge response against a key you already hold.
68
+ * The nonce must be single-use and unpredictable.
69
+ */
70
+ export function verifyChallenge(
71
+ publicKeyBase64: string,
72
+ provenanceId: string,
73
+ nonce: string,
74
+ signatureBase64: string
75
+ ): Promise<boolean>;
76
+
77
+ /**
78
+ * Verify an owner-signed revocation. Confirms it came from the key holder;
79
+ * it does not tell you whether a revocation exists.
80
+ */
81
+ export function verifyRevocation(
82
+ publicKeyBase64: string,
83
+ provenanceId: string,
84
+ signatureBase64: string
85
+ ): Promise<boolean>;