@kya-os/contracts 1.8.0 → 1.9.0
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/dist/agentshield-api/index.js +0 -1
- package/dist/agentshield-api/schemas.d.ts +193 -187
- package/dist/agentshield-api/schemas.js +0 -1
- package/dist/agentshield-api/types.js +0 -1
- package/dist/compute.d.ts +47 -17
- package/dist/compute.js +23 -1
- package/dist/consent/index.d.ts +2 -3
- package/dist/consent/index.js +14 -3
- package/dist/handshake.d.ts +4 -3
- package/dist/handshake.js +4 -3
- package/dist/proof/index.d.ts +3 -4
- package/dist/proof/index.js +2 -2
- package/dist/proof.d.ts +98 -67
- package/dist/proof.js +29 -2
- package/dist/runtime/headers.d.ts +16 -12
- package/dist/runtime/headers.js +16 -12
- package/dist/verifier.d.ts +28 -17
- package/dist/verifier.js +20 -9
- package/package.json +2 -1
- package/parity-vectors/delegation-conformance.json +265 -0
- package/parity-vectors/did-key.json +134 -0
- package/parity-vectors/index.ts +253 -0
- package/parity-vectors/scope-matching.json +330 -0
- package/parity-vectors/vc-jwt.json +210 -0
package/dist/verifier.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare const AgentContextSchema: z.ZodObject<{
|
|
|
9
9
|
scopes: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
10
10
|
session: z.ZodString;
|
|
11
11
|
confidence: z.ZodLiteral<"verified">;
|
|
12
|
+
outcome: z.ZodOptional<z.ZodEnum<["allowed", "denied", "step_up_required", "needs_authorization"]>>;
|
|
12
13
|
delegationRef: z.ZodOptional<z.ZodString>;
|
|
13
14
|
registry: z.ZodString;
|
|
14
15
|
verifiedAt: z.ZodNumber;
|
|
@@ -22,6 +23,7 @@ export declare const AgentContextSchema: z.ZodObject<{
|
|
|
22
23
|
verifiedAt: number;
|
|
23
24
|
subject?: string | undefined;
|
|
24
25
|
delegationRef?: string | undefined;
|
|
26
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
25
27
|
}, {
|
|
26
28
|
did: string;
|
|
27
29
|
kid: string;
|
|
@@ -31,6 +33,7 @@ export declare const AgentContextSchema: z.ZodObject<{
|
|
|
31
33
|
verifiedAt: number;
|
|
32
34
|
subject?: string | undefined;
|
|
33
35
|
delegationRef?: string | undefined;
|
|
36
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
34
37
|
scopes?: string[] | undefined;
|
|
35
38
|
}>;
|
|
36
39
|
export declare const VerifierResultSchema: z.ZodObject<{
|
|
@@ -43,6 +46,7 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
43
46
|
scopes: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
44
47
|
session: z.ZodString;
|
|
45
48
|
confidence: z.ZodLiteral<"verified">;
|
|
49
|
+
outcome: z.ZodOptional<z.ZodEnum<["allowed", "denied", "step_up_required", "needs_authorization"]>>;
|
|
46
50
|
delegationRef: z.ZodOptional<z.ZodString>;
|
|
47
51
|
registry: z.ZodString;
|
|
48
52
|
verifiedAt: z.ZodNumber;
|
|
@@ -56,6 +60,7 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
56
60
|
verifiedAt: number;
|
|
57
61
|
subject?: string | undefined;
|
|
58
62
|
delegationRef?: string | undefined;
|
|
63
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
59
64
|
}, {
|
|
60
65
|
did: string;
|
|
61
66
|
kid: string;
|
|
@@ -65,6 +70,7 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
65
70
|
verifiedAt: number;
|
|
66
71
|
subject?: string | undefined;
|
|
67
72
|
delegationRef?: string | undefined;
|
|
73
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
68
74
|
scopes?: string[] | undefined;
|
|
69
75
|
}>>;
|
|
70
76
|
error: z.ZodOptional<z.ZodObject<{
|
|
@@ -85,6 +91,12 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
85
91
|
}>>;
|
|
86
92
|
}, "strip", z.ZodTypeAny, {
|
|
87
93
|
success: boolean;
|
|
94
|
+
error?: {
|
|
95
|
+
code: string;
|
|
96
|
+
message: string;
|
|
97
|
+
httpStatus: number;
|
|
98
|
+
details?: any;
|
|
99
|
+
} | undefined;
|
|
88
100
|
headers?: Record<string, string> | undefined;
|
|
89
101
|
agentContext?: {
|
|
90
102
|
did: string;
|
|
@@ -96,15 +108,16 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
96
108
|
verifiedAt: number;
|
|
97
109
|
subject?: string | undefined;
|
|
98
110
|
delegationRef?: string | undefined;
|
|
111
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
99
112
|
} | undefined;
|
|
113
|
+
}, {
|
|
114
|
+
success: boolean;
|
|
100
115
|
error?: {
|
|
101
116
|
code: string;
|
|
102
117
|
message: string;
|
|
103
118
|
httpStatus: number;
|
|
104
119
|
details?: any;
|
|
105
120
|
} | undefined;
|
|
106
|
-
}, {
|
|
107
|
-
success: boolean;
|
|
108
121
|
headers?: Record<string, string> | undefined;
|
|
109
122
|
agentContext?: {
|
|
110
123
|
did: string;
|
|
@@ -115,14 +128,9 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
115
128
|
verifiedAt: number;
|
|
116
129
|
subject?: string | undefined;
|
|
117
130
|
delegationRef?: string | undefined;
|
|
131
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
118
132
|
scopes?: string[] | undefined;
|
|
119
133
|
} | undefined;
|
|
120
|
-
error?: {
|
|
121
|
-
code: string;
|
|
122
|
-
message: string;
|
|
123
|
-
httpStatus: number;
|
|
124
|
-
details?: any;
|
|
125
|
-
} | undefined;
|
|
126
134
|
}>;
|
|
127
135
|
export declare const StructuredErrorSchema: z.ZodObject<{
|
|
128
136
|
code: z.ZodString;
|
|
@@ -169,15 +177,16 @@ export type AgentContext = z.infer<typeof AgentContextSchema>;
|
|
|
169
177
|
export type VerifierResult = z.infer<typeof VerifierResultSchema>;
|
|
170
178
|
export type StructuredError = z.infer<typeof StructuredErrorSchema>;
|
|
171
179
|
export declare const AGENT_HEADERS: {
|
|
172
|
-
readonly DID: "KYA-Agent-DID";
|
|
173
|
-
readonly KEY_ID: "KYA-Agent-KeyId";
|
|
174
|
-
readonly SUBJECT: "KYA-Agent-Subject";
|
|
175
|
-
readonly SCOPES: "KYA-Agent-Scopes";
|
|
176
|
-
readonly SESSION: "KYA-Agent-Session";
|
|
177
|
-
readonly CONFIDENCE: "KYA-Agent-Confidence";
|
|
178
|
-
readonly
|
|
179
|
-
readonly
|
|
180
|
-
readonly
|
|
180
|
+
readonly DID: "KYA-OS-Agent-DID";
|
|
181
|
+
readonly KEY_ID: "KYA-OS-Agent-KeyId";
|
|
182
|
+
readonly SUBJECT: "KYA-OS-Agent-Subject";
|
|
183
|
+
readonly SCOPES: "KYA-OS-Agent-Scopes";
|
|
184
|
+
readonly SESSION: "KYA-OS-Agent-Session";
|
|
185
|
+
readonly CONFIDENCE: "KYA-OS-Agent-Confidence";
|
|
186
|
+
readonly OUTCOME: "KYA-OS-Agent-Outcome";
|
|
187
|
+
readonly DELEGATION_REF: "KYA-OS-Delegation-Ref";
|
|
188
|
+
readonly REGISTRY: "KYA-OS-Agent-Registry";
|
|
189
|
+
readonly VERIFIED_AT: "KYA-OS-Verified-At";
|
|
181
190
|
};
|
|
182
191
|
export declare const VERIFIER_ERROR_CODES: {
|
|
183
192
|
readonly PROOF_INVALID_TS: "XMCP_I_PROOF_INVALID_TS";
|
|
@@ -186,6 +195,7 @@ export declare const VERIFIER_ERROR_CODES: {
|
|
|
186
195
|
readonly PROOF_SKEW_EXCEEDED: "XMCP_I_PROOF_SKEW_EXCEEDED";
|
|
187
196
|
readonly SESSION_IDLE_EXPIRED: "XMCP_I_SESSION_IDLE_EXPIRED";
|
|
188
197
|
readonly SERVER_TIME_INVALID: "XMCP_I_SERVER_TIME_INVALID";
|
|
198
|
+
readonly OUTCOME_NOT_ALLOWED: "XMCP_I_OUTCOME_NOT_ALLOWED";
|
|
189
199
|
};
|
|
190
200
|
export declare const ERROR_HTTP_STATUS: {
|
|
191
201
|
readonly XMCP_I_EBADPROOF: 403;
|
|
@@ -202,4 +212,5 @@ export declare const ERROR_HTTP_STATUS: {
|
|
|
202
212
|
readonly XMCP_I_PROOF_SKEW_EXCEEDED: 401;
|
|
203
213
|
readonly XMCP_I_SESSION_IDLE_EXPIRED: 401;
|
|
204
214
|
readonly XMCP_I_SERVER_TIME_INVALID: 500;
|
|
215
|
+
readonly XMCP_I_OUTCOME_NOT_ALLOWED: 403;
|
|
205
216
|
};
|
package/dist/verifier.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ERROR_HTTP_STATUS = exports.VERIFIER_ERROR_CODES = exports.AGENT_HEADERS = exports.StructuredErrorSchema = exports.VerifierResultSchema = exports.AgentContextSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
+
const proof_js_1 = require("./proof.js");
|
|
5
6
|
/**
|
|
6
7
|
* Verifier middleware schemas and headers
|
|
7
8
|
*/
|
|
@@ -12,6 +13,13 @@ exports.AgentContextSchema = zod_1.z.object({
|
|
|
12
13
|
scopes: zod_1.z.array(zod_1.z.string()).default([]),
|
|
13
14
|
session: zod_1.z.string().min(1),
|
|
14
15
|
confidence: zod_1.z.literal("verified"),
|
|
16
|
+
// Policy outcome the verified proof attests to (ratified DIF decision).
|
|
17
|
+
// `confidence: "verified"` means the proof is authentic; `outcome` is the
|
|
18
|
+
// separate authorization dimension. A PEP must gate on `outcome === "allowed"`
|
|
19
|
+
// — a verified `denied` / `step_up_required` / `needs_authorization` proof is
|
|
20
|
+
// authentic but does NOT authorize the call. Optional for backward
|
|
21
|
+
// compatibility; the verifier always populates it (legacy proofs → "allowed").
|
|
22
|
+
outcome: proof_js_1.ProofOutcomeSchema.optional(),
|
|
15
23
|
delegationRef: zod_1.z.string().optional(),
|
|
16
24
|
registry: zod_1.z.string().url(),
|
|
17
25
|
verifiedAt: zod_1.z.number().int().positive(),
|
|
@@ -44,15 +52,16 @@ exports.StructuredErrorSchema = zod_1.z.object({
|
|
|
44
52
|
});
|
|
45
53
|
// Header constants (frozen names)
|
|
46
54
|
exports.AGENT_HEADERS = {
|
|
47
|
-
DID: "KYA-Agent-DID",
|
|
48
|
-
KEY_ID: "KYA-Agent-KeyId",
|
|
49
|
-
SUBJECT: "KYA-Agent-Subject",
|
|
50
|
-
SCOPES: "KYA-Agent-Scopes",
|
|
51
|
-
SESSION: "KYA-Agent-Session",
|
|
52
|
-
CONFIDENCE: "KYA-Agent-Confidence",
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
DID: "KYA-OS-Agent-DID",
|
|
56
|
+
KEY_ID: "KYA-OS-Agent-KeyId",
|
|
57
|
+
SUBJECT: "KYA-OS-Agent-Subject",
|
|
58
|
+
SCOPES: "KYA-OS-Agent-Scopes",
|
|
59
|
+
SESSION: "KYA-OS-Agent-Session",
|
|
60
|
+
CONFIDENCE: "KYA-OS-Agent-Confidence",
|
|
61
|
+
OUTCOME: "KYA-OS-Agent-Outcome",
|
|
62
|
+
DELEGATION_REF: "KYA-OS-Delegation-Ref",
|
|
63
|
+
REGISTRY: "KYA-OS-Agent-Registry",
|
|
64
|
+
VERIFIED_AT: "KYA-OS-Verified-At",
|
|
56
65
|
};
|
|
57
66
|
// Verifier-specific error codes
|
|
58
67
|
exports.VERIFIER_ERROR_CODES = {
|
|
@@ -62,6 +71,7 @@ exports.VERIFIER_ERROR_CODES = {
|
|
|
62
71
|
PROOF_SKEW_EXCEEDED: "XMCP_I_PROOF_SKEW_EXCEEDED",
|
|
63
72
|
SESSION_IDLE_EXPIRED: "XMCP_I_SESSION_IDLE_EXPIRED",
|
|
64
73
|
SERVER_TIME_INVALID: "XMCP_I_SERVER_TIME_INVALID",
|
|
74
|
+
OUTCOME_NOT_ALLOWED: "XMCP_I_OUTCOME_NOT_ALLOWED",
|
|
65
75
|
};
|
|
66
76
|
// HTTP status mappings
|
|
67
77
|
exports.ERROR_HTTP_STATUS = {
|
|
@@ -80,4 +90,5 @@ exports.ERROR_HTTP_STATUS = {
|
|
|
80
90
|
[exports.VERIFIER_ERROR_CODES.PROOF_SKEW_EXCEEDED]: 401,
|
|
81
91
|
[exports.VERIFIER_ERROR_CODES.SESSION_IDLE_EXPIRED]: 401,
|
|
82
92
|
[exports.VERIFIER_ERROR_CODES.SERVER_TIME_INVALID]: 500,
|
|
93
|
+
[exports.VERIFIER_ERROR_CODES.OUTCOME_NOT_ALLOWED]: 403,
|
|
83
94
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kya-os/contracts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Shared contracts, types, and schemas for MCP-I framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -142,6 +142,7 @@
|
|
|
142
142
|
},
|
|
143
143
|
"files": [
|
|
144
144
|
"dist",
|
|
145
|
+
"parity-vectors",
|
|
145
146
|
"package.json",
|
|
146
147
|
"README.md"
|
|
147
148
|
],
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"name": "Delegation chain conformance vectors",
|
|
4
|
+
"description": "Cascading-revocation, chain-integrity, per-hop attenuation, and multi-hop delegation chains (E3.5 / issue #2904). `core` = full reference semantics (mcp-i-core cascading-revocation + delegation-graph, contracts areChildConstraintsValid). `edge` = reduced-profile kya-os-engine verdict: single-hop is evaluated; multi-hop fails closed to Block(PolicyDenied) unless a higher-priority reason (Revoked/Expired) fires first.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"vectors": [
|
|
7
|
+
{
|
|
8
|
+
"id": "cascade-revoke-ancestor-revokes-leaf",
|
|
9
|
+
"category": "cascading-revocation",
|
|
10
|
+
"profiles": ["core", "edge"],
|
|
11
|
+
"description": "Two-hop chain whose root delegation is revoked while the leaf's own credential is clean. Core: leaf invalid via ancestor cascade. Edge: the engine sees only the leaf's top-level credentialStatus (clean) and cannot observe the ancestor revocation, so it fails closed on the multi-hop chain (PolicyDenied) rather than over-permitting.",
|
|
12
|
+
"input": {
|
|
13
|
+
"chain": [
|
|
14
|
+
{
|
|
15
|
+
"id": "cr-root",
|
|
16
|
+
"parentId": null,
|
|
17
|
+
"issuerDid": "did:web:org.example",
|
|
18
|
+
"subjectDid": "did:web:agent.example",
|
|
19
|
+
"grantedScopes": ["read:*"],
|
|
20
|
+
"credentialStatusId": "https://status.example/revocation/v1#5"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "cr-leaf",
|
|
24
|
+
"parentId": "cr-root",
|
|
25
|
+
"issuerDid": "did:web:agent.example",
|
|
26
|
+
"subjectDid": "did:key:zSubLeaf",
|
|
27
|
+
"grantedScopes": ["read:email"],
|
|
28
|
+
"credentialStatusId": "https://status.example/revocation/v1#9"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"requestedScope": "read:email",
|
|
32
|
+
"revokedStatusIds": ["https://status.example/revocation/v1#5"],
|
|
33
|
+
"currentTime": 1735689600
|
|
34
|
+
},
|
|
35
|
+
"expected": {
|
|
36
|
+
"core": {
|
|
37
|
+
"valid": false,
|
|
38
|
+
"failureReason": "cascaded-revocation",
|
|
39
|
+
"revokedAncestor": "cr-root"
|
|
40
|
+
},
|
|
41
|
+
"edge": {
|
|
42
|
+
"mode": "fail-closed",
|
|
43
|
+
"decision": {
|
|
44
|
+
"kind": "Block",
|
|
45
|
+
"reason": {
|
|
46
|
+
"kind": "PolicyDenied",
|
|
47
|
+
"detail": "multi-hop delegation chain (len=2) not evaluated by reduced edge profile; Layer-2 policy authoritative"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"note": "The leaf's own credentialStatus (#9) is NOT revoked, so the engine's Stage 3 does not fire Block(Revoked); the multi-hop guard yields PolicyDenied. If the leaf's own status WERE revoked, Stage 3's Revoked (priority 0) would outrank PolicyDenied — covered by the cross-runtime matrix, not here."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "chain-integrity-broken-issuer-linkage",
|
|
56
|
+
"category": "chain-integrity",
|
|
57
|
+
"profiles": ["core", "edge"],
|
|
58
|
+
"description": "Two-hop chain where the leaf's issuer is NOT the root's subject (forged intermediate). Core: invalid-chain via delegation-graph linkage check. Edge: fails closed on the multi-hop chain.",
|
|
59
|
+
"input": {
|
|
60
|
+
"chain": [
|
|
61
|
+
{
|
|
62
|
+
"id": "ci-root",
|
|
63
|
+
"parentId": null,
|
|
64
|
+
"issuerDid": "did:web:org.example",
|
|
65
|
+
"subjectDid": "did:web:agent.example",
|
|
66
|
+
"grantedScopes": ["read:*"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "ci-leaf",
|
|
70
|
+
"parentId": "ci-root",
|
|
71
|
+
"issuerDid": "did:web:evil.example",
|
|
72
|
+
"subjectDid": "did:key:zSubLeaf",
|
|
73
|
+
"grantedScopes": ["read:email"]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"requestedScope": "read:email",
|
|
77
|
+
"currentTime": 1735689600
|
|
78
|
+
},
|
|
79
|
+
"expected": {
|
|
80
|
+
"core": {
|
|
81
|
+
"valid": false,
|
|
82
|
+
"failureReason": "invalid-chain"
|
|
83
|
+
},
|
|
84
|
+
"edge": {
|
|
85
|
+
"mode": "fail-closed",
|
|
86
|
+
"decision": {
|
|
87
|
+
"kind": "Block",
|
|
88
|
+
"reason": {
|
|
89
|
+
"kind": "PolicyDenied",
|
|
90
|
+
"detail": "multi-hop delegation chain (len=2) not evaluated by reduced edge profile; Layer-2 policy authoritative"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"note": "delegation-graph.validateChain requires child.issuerDid === parent.subjectDid (delegation-graph.ts:243). did:web:evil.example breaks the linkage. The flat engine carries no per-hop issuer/subject, so it cannot detect this — fail-closed is the safe reduced-profile behavior."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "attenuation-child-exceeds-parent-scope",
|
|
99
|
+
"category": "per-hop-attenuation",
|
|
100
|
+
"profiles": ["core", "edge"],
|
|
101
|
+
"description": "Two-hop chain where the child grants MORE than the parent (parent read:* via prefix, child write:billing). Core: attenuation-violation via areChildConstraintsValid. Edge: fails closed. This is the canonical over-permit case the reduced profile closes.",
|
|
102
|
+
"input": {
|
|
103
|
+
"chain": [
|
|
104
|
+
{
|
|
105
|
+
"id": "at-root",
|
|
106
|
+
"parentId": null,
|
|
107
|
+
"issuerDid": "did:web:org.example",
|
|
108
|
+
"subjectDid": "did:web:agent.example",
|
|
109
|
+
"grantedScopes": ["read:*"],
|
|
110
|
+
"constraints": {
|
|
111
|
+
"crisp": {
|
|
112
|
+
"scopes": [{ "resource": "read:", "matcher": "prefix" }]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "at-leaf",
|
|
118
|
+
"parentId": "at-root",
|
|
119
|
+
"issuerDid": "did:web:agent.example",
|
|
120
|
+
"subjectDid": "did:key:zSubLeaf",
|
|
121
|
+
"grantedScopes": ["write:billing"],
|
|
122
|
+
"constraints": {
|
|
123
|
+
"crisp": {
|
|
124
|
+
"scopes": [{ "resource": "write:billing", "matcher": "exact" }]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"requestedScope": "write:billing",
|
|
130
|
+
"currentTime": 1735689600
|
|
131
|
+
},
|
|
132
|
+
"expected": {
|
|
133
|
+
"core": {
|
|
134
|
+
"valid": false,
|
|
135
|
+
"failureReason": "attenuation-violation"
|
|
136
|
+
},
|
|
137
|
+
"edge": {
|
|
138
|
+
"mode": "fail-closed",
|
|
139
|
+
"decision": {
|
|
140
|
+
"kind": "Block",
|
|
141
|
+
"reason": {
|
|
142
|
+
"kind": "PolicyDenied",
|
|
143
|
+
"detail": "multi-hop delegation chain (len=2) not evaluated by reduced edge profile; Layer-2 policy authoritative"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"note": "Today the flat engine would flatten grants to [read:*, write:billing] and scope_satisfies('write:billing', ...) === true → silent Permit. areChildConstraintsValid rejects the widening (write:billing does not start with read:). Fail-closed converts the silent over-permit into Block(PolicyDenied)."
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "multihop-valid-chain-still-fail-closed",
|
|
152
|
+
"category": "multi-hop-valid",
|
|
153
|
+
"profiles": ["core", "edge"],
|
|
154
|
+
"description": "A WELL-FORMED two-hop chain: valid linkage, no revocation, child scopes within parent, request within leaf grant. Core: valid. Edge: STILL fails closed — the reduced profile does not certify multi-hop even when the chain is valid; the over-permit risk is structural, not data-dependent.",
|
|
155
|
+
"input": {
|
|
156
|
+
"chain": [
|
|
157
|
+
{
|
|
158
|
+
"id": "hp-root",
|
|
159
|
+
"parentId": null,
|
|
160
|
+
"issuerDid": "did:web:org.example",
|
|
161
|
+
"subjectDid": "did:web:agent.example",
|
|
162
|
+
"grantedScopes": ["read:email"],
|
|
163
|
+
"credentialStatusId": "https://status.example/revocation/v1#20",
|
|
164
|
+
"constraints": {
|
|
165
|
+
"crisp": {
|
|
166
|
+
"scopes": [{ "resource": "read:email", "matcher": "exact" }]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "hp-leaf",
|
|
172
|
+
"parentId": "hp-root",
|
|
173
|
+
"issuerDid": "did:web:agent.example",
|
|
174
|
+
"subjectDid": "did:key:zSubLeaf",
|
|
175
|
+
"grantedScopes": ["read:email"],
|
|
176
|
+
"credentialStatusId": "https://status.example/revocation/v1#21",
|
|
177
|
+
"constraints": {
|
|
178
|
+
"crisp": {
|
|
179
|
+
"scopes": [{ "resource": "read:email", "matcher": "exact" }]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"requestedScope": "read:email",
|
|
185
|
+
"revokedStatusIds": [],
|
|
186
|
+
"currentTime": 1735689600
|
|
187
|
+
},
|
|
188
|
+
"expected": {
|
|
189
|
+
"core": {
|
|
190
|
+
"valid": true
|
|
191
|
+
},
|
|
192
|
+
"edge": {
|
|
193
|
+
"mode": "fail-closed",
|
|
194
|
+
"decision": {
|
|
195
|
+
"kind": "Block",
|
|
196
|
+
"reason": {
|
|
197
|
+
"kind": "PolicyDenied",
|
|
198
|
+
"detail": "multi-hop delegation chain (len=2) not evaluated by reduced edge profile; Layer-2 policy authoritative"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"note": "Deliberate negative-of-convenience: proves the edge profile fails closed even on a VALID multi-hop chain. This is the honest cost of the reduced profile — Layer-2 tenant policy must opt these back in. Prevents a future maintainer from 'optimizing' the guard to permit valid-looking chains."
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"id": "singlehop-scope-permit",
|
|
207
|
+
"category": "scope-match",
|
|
208
|
+
"profiles": ["edge"],
|
|
209
|
+
"description": "Single-hop delegation whose granted scope covers the requested scope. Edge: evaluated → Permit (regression guard that the multi-hop guard does NOT change the len<=1 path).",
|
|
210
|
+
"input": {
|
|
211
|
+
"chain": [
|
|
212
|
+
{
|
|
213
|
+
"id": "sh-permit",
|
|
214
|
+
"parentId": null,
|
|
215
|
+
"issuerDid": "did:web:org.example",
|
|
216
|
+
"subjectDid": "did:web:agent.example",
|
|
217
|
+
"grantedScopes": ["read:email"]
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"requestedScope": "read:email",
|
|
221
|
+
"currentTime": 1735689600
|
|
222
|
+
},
|
|
223
|
+
"expected": {
|
|
224
|
+
"edge": {
|
|
225
|
+
"mode": "evaluated",
|
|
226
|
+
"decision": { "kind": "Permit" }
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"note": "Single-hop is within the engine's competence: it flattens the one step's grants and matches requestedScope. No core counterpart — for single-hop scope the engine is the reference."
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "singlehop-scope-out-of-scope",
|
|
233
|
+
"category": "scope-match",
|
|
234
|
+
"profiles": ["edge"],
|
|
235
|
+
"description": "Single-hop delegation whose granted scope does NOT cover the requested scope. Edge: evaluated → Block(OutOfScope). Proves the guard leaves the genuine single-hop scope rejection intact.",
|
|
236
|
+
"input": {
|
|
237
|
+
"chain": [
|
|
238
|
+
{
|
|
239
|
+
"id": "sh-oos",
|
|
240
|
+
"parentId": null,
|
|
241
|
+
"issuerDid": "did:web:org.example",
|
|
242
|
+
"subjectDid": "did:web:agent.example",
|
|
243
|
+
"grantedScopes": ["read:email"]
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
"requestedScope": "write:billing",
|
|
247
|
+
"currentTime": 1735689600
|
|
248
|
+
},
|
|
249
|
+
"expected": {
|
|
250
|
+
"edge": {
|
|
251
|
+
"mode": "evaluated",
|
|
252
|
+
"decision": {
|
|
253
|
+
"kind": "Block",
|
|
254
|
+
"reason": {
|
|
255
|
+
"kind": "OutOfScope",
|
|
256
|
+
"requested": "write:billing",
|
|
257
|
+
"granted": ["read:email"]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"note": "Single-hop OutOfScope stays a genuine evaluated rejection (not fail-closed). granted is the flattened single-step grant set."
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"name": "did:key resolution test vectors",
|
|
4
|
+
"description": "Test vectors for did:key resolution parity between TypeScript and Rust",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"vectors": [
|
|
7
|
+
{
|
|
8
|
+
"id": "valid-ed25519-1",
|
|
9
|
+
"description": "Valid Ed25519 did:key",
|
|
10
|
+
"input": {
|
|
11
|
+
"did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
|
12
|
+
},
|
|
13
|
+
"expected": {
|
|
14
|
+
"success": true,
|
|
15
|
+
"keyType": "Ed25519",
|
|
16
|
+
"publicKeyHex": "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
|
|
17
|
+
"keyId": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "valid-ed25519-2",
|
|
22
|
+
"description": "Another valid Ed25519 did:key",
|
|
23
|
+
"input": {
|
|
24
|
+
"did": "did:key:z6MkfrNzYH9ygZbUjWKFW3BrN5SvXqWMQGQwYVaR8GGJqWLb"
|
|
25
|
+
},
|
|
26
|
+
"expected": {
|
|
27
|
+
"success": true,
|
|
28
|
+
"keyType": "Ed25519"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "invalid-empty",
|
|
33
|
+
"description": "Empty DID string",
|
|
34
|
+
"input": {
|
|
35
|
+
"did": ""
|
|
36
|
+
},
|
|
37
|
+
"expected": {
|
|
38
|
+
"success": false,
|
|
39
|
+
"errorType": "InvalidFormat"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "invalid-not-did-key",
|
|
44
|
+
"description": "Wrong DID method (did:web instead of did:key)",
|
|
45
|
+
"input": {
|
|
46
|
+
"did": "did:web:example.com"
|
|
47
|
+
},
|
|
48
|
+
"expected": {
|
|
49
|
+
"success": false,
|
|
50
|
+
"errorType": "InvalidPrefix"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "invalid-multibase-prefix",
|
|
55
|
+
"description": "Invalid multibase prefix (not z)",
|
|
56
|
+
"input": {
|
|
57
|
+
"did": "did:key:f6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
|
58
|
+
},
|
|
59
|
+
"expected": {
|
|
60
|
+
"success": false,
|
|
61
|
+
"errorType": "InvalidMultibase"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "invalid-base58",
|
|
66
|
+
"description": "Invalid base58 encoding (contains 0, O, I, l)",
|
|
67
|
+
"input": {
|
|
68
|
+
"did": "did:key:z6Mk0OIl"
|
|
69
|
+
},
|
|
70
|
+
"expected": {
|
|
71
|
+
"success": false,
|
|
72
|
+
"errorType": "InvalidBase58"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "unsupported-curve-p256",
|
|
77
|
+
"description": "Unsupported curve (P-256)",
|
|
78
|
+
"input": {
|
|
79
|
+
"did": "did:key:zDnaeucDGfNLZfcPeDpQWAcfJMCvzNjrB5F9Xut7VH8fFYnDW"
|
|
80
|
+
},
|
|
81
|
+
"expected": {
|
|
82
|
+
"success": false,
|
|
83
|
+
"errorType": "UnsupportedKeyType"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "unsupported-curve-secp256k1",
|
|
88
|
+
"description": "Unsupported curve (secp256k1)",
|
|
89
|
+
"input": {
|
|
90
|
+
"did": "did:key:zQ3shNFbC9Wk5yXnNMEsNz1hL5NPcUHbBYC8TmUHxAHhVWFqe"
|
|
91
|
+
},
|
|
92
|
+
"expected": {
|
|
93
|
+
"success": false,
|
|
94
|
+
"errorType": "UnsupportedKeyType"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "invalid-key-length",
|
|
99
|
+
"description": "Invalid key length (too short)",
|
|
100
|
+
"input": {
|
|
101
|
+
"did": "did:key:z6MkTest"
|
|
102
|
+
},
|
|
103
|
+
"expected": {
|
|
104
|
+
"success": false,
|
|
105
|
+
"errorType": "InvalidKeyLength"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "valid-key-id-validation",
|
|
110
|
+
"description": "Validate key ID format",
|
|
111
|
+
"input": {
|
|
112
|
+
"did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
|
113
|
+
"keyId": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
|
114
|
+
},
|
|
115
|
+
"expected": {
|
|
116
|
+
"success": true,
|
|
117
|
+
"keyIdValid": true
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "invalid-key-id-mismatch",
|
|
122
|
+
"description": "Key ID does not match DID",
|
|
123
|
+
"input": {
|
|
124
|
+
"did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
|
|
125
|
+
"keyId": "did:key:z6MkfrNzYH9ygZbUjWKFW3BrN5SvXqWMQGQwYVaR8GGJqWLb#z6MkfrNzYH9ygZbUjWKFW3BrN5SvXqWMQGQwYVaR8GGJqWLb"
|
|
126
|
+
},
|
|
127
|
+
"expected": {
|
|
128
|
+
"success": false,
|
|
129
|
+
"keyIdValid": false,
|
|
130
|
+
"errorType": "KeyIdMismatch"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|