@kya-os/contracts 1.8.1 → 1.9.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/dist/agentshield-api/schemas.d.ts +7 -0
- package/dist/compute.d.ts +31 -0
- package/dist/compute.js +23 -0
- package/dist/handshake.d.ts +4 -3
- package/dist/handshake.js +4 -3
- package/dist/proof/index.d.ts +3 -3
- package/dist/proof/index.js +2 -1
- package/dist/proof.d.ts +32 -0
- package/dist/proof.js +29 -1
- package/dist/reputation/index.d.ts +1 -0
- package/dist/reputation/index.js +2 -0
- package/dist/reputation/interactions.d.ts +146 -0
- package/dist/reputation/interactions.js +82 -0
- package/dist/runtime/headers.d.ts +16 -12
- package/dist/runtime/headers.js +16 -12
- package/dist/verifier.d.ts +20 -9
- 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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Interaction Report Request Contract
|
|
4
|
+
*
|
|
5
|
+
* The per-tool-call attestation body that identity-aware runtimes POST to
|
|
6
|
+
* KTA's `POST /api/v1/agents/{did}/interactions` endpoint (xmcp-i#413). KTA
|
|
7
|
+
* verifies the embedded JWS against the agent's registered public key and
|
|
8
|
+
* folds the outcome into the Bayesian reputation engine.
|
|
9
|
+
*
|
|
10
|
+
* WHY this lives in @kya-os/contracts: per the repo's API-parity rule, the
|
|
11
|
+
* wire shape of any cross-service POST must be a shared, Zod-validated
|
|
12
|
+
* contract — never inlined at the call site. The producer here is
|
|
13
|
+
* `@kya-os/compute`'s attestation reporter; the consumer is the KTA route.
|
|
14
|
+
*
|
|
15
|
+
* PARITY CAVEAT: KTA's receiving schema lives in a different repository
|
|
16
|
+
* (Know-That-Ai/know-that-ai → `src/app/api/v1/agents/[did]/interactions/`)
|
|
17
|
+
* and cannot be imported or validated from here. This schema mirrors the body
|
|
18
|
+
* shape already accepted in production (the existing attestation flow), and
|
|
19
|
+
* MUST be kept in lock-step with that route when either side changes.
|
|
20
|
+
*
|
|
21
|
+
* Related Spec: MCP-I §5 (Reputation)
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.InteractionReportRequestSchema = exports.InteractionContextSchema = exports.InteractionOutcomeSchema = exports.INTERACTION_TYPE_SCOPE_EXECUTED = void 0;
|
|
25
|
+
exports.validateInteractionReportRequest = validateInteractionReportRequest;
|
|
26
|
+
const zod_1 = require("zod");
|
|
27
|
+
/**
|
|
28
|
+
* Interaction classification. Open-ended on the wire (KTA may accept more
|
|
29
|
+
* types over time), but the per-tool-call attestation path emits exactly
|
|
30
|
+
* `"scope_executed"`. Modeled as a string with that documented default rather
|
|
31
|
+
* than a closed enum so adding a new KTA-side type does not require a
|
|
32
|
+
* contracts bump on the producer before the receiver is ready.
|
|
33
|
+
*/
|
|
34
|
+
exports.INTERACTION_TYPE_SCOPE_EXECUTED = "scope_executed";
|
|
35
|
+
/**
|
|
36
|
+
* Coarse success/failure outcome KTA folds into the reputation score. The
|
|
37
|
+
* compute reporter collapses its finer 4-value status (success/error/denied/
|
|
38
|
+
* timeout) onto this two-value axis — anything that is not an unambiguous
|
|
39
|
+
* success counts as a failure for reputation purposes.
|
|
40
|
+
*/
|
|
41
|
+
exports.InteractionOutcomeSchema = zod_1.z.enum(["success", "failure"]);
|
|
42
|
+
/**
|
|
43
|
+
* Evidentiary context carried alongside the signed attestation. `client_did`
|
|
44
|
+
* is optional because the agent may receive calls from un-attested clients
|
|
45
|
+
* during the rollout window; the attestation still has value without it.
|
|
46
|
+
*/
|
|
47
|
+
exports.InteractionContextSchema = zod_1.z.object({
|
|
48
|
+
/** MCP session id from the originating tools/call. May be empty for headless flows. */
|
|
49
|
+
session_id: zod_1.z.string(),
|
|
50
|
+
/** Calling client's DID, when known. Omitted (not null) when un-attested. */
|
|
51
|
+
client_did: zod_1.z.string().optional(),
|
|
52
|
+
/** Compact JWS attestation of the tool call, signed by the agent's key. */
|
|
53
|
+
attestation_jws: zod_1.z.string(),
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Interaction Report Request Schema
|
|
57
|
+
*
|
|
58
|
+
* Fire-and-forget body POSTed per tool call. Authenticated by the
|
|
59
|
+
* `x-api-key` header (not part of the body); idempotency is the caller's
|
|
60
|
+
* responsibility via `idempotency_key`.
|
|
61
|
+
*/
|
|
62
|
+
exports.InteractionReportRequestSchema = zod_1.z.object({
|
|
63
|
+
/** Interaction classification — `"scope_executed"` for tool-call attestations. */
|
|
64
|
+
interaction_type: zod_1.z.string(),
|
|
65
|
+
/** Reputation-facing outcome. */
|
|
66
|
+
outcome: exports.InteractionOutcomeSchema,
|
|
67
|
+
/** Authorized scopes for the call, when a delegation context supplies them. */
|
|
68
|
+
scopes: zod_1.z.array(zod_1.z.string()).optional(),
|
|
69
|
+
/** ISO 8601 timestamp of the call, set when the attestation is signed. */
|
|
70
|
+
timestamp: zod_1.z.string(),
|
|
71
|
+
/** Evidentiary context including the signed attestation. */
|
|
72
|
+
context: exports.InteractionContextSchema,
|
|
73
|
+
/** Per-call key so a retried POST of the same call is deduped by KTA. */
|
|
74
|
+
idempotency_key: zod_1.z.string(),
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Validate an interaction report request. Mirrors the `validate*` helpers in
|
|
78
|
+
* `./api.ts` so call sites can branch on `.success` without importing zod.
|
|
79
|
+
*/
|
|
80
|
+
function validateInteractionReportRequest(data) {
|
|
81
|
+
return exports.InteractionReportRequestSchema.safeParse(data);
|
|
82
|
+
}
|
|
@@ -13,31 +13,34 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export interface DownstreamHeaders {
|
|
15
15
|
/** DID of the verified agent */
|
|
16
|
-
'KYA-Agent-DID': string;
|
|
17
|
-
/** Optional delegation ID */
|
|
18
|
-
'KYA-Delegation-Id'?: string;
|
|
16
|
+
'KYA-OS-Agent-DID': string;
|
|
19
17
|
/** Optional delegation chain (format: vc_id>del_id>...) */
|
|
20
|
-
'KYA-Delegation-Chain'?: string;
|
|
18
|
+
'KYA-OS-Delegation-Chain'?: string;
|
|
19
|
+
/** Optional Layer 2 delegation proof (signed JWT) */
|
|
20
|
+
'KYA-OS-Delegation-Proof'?: string;
|
|
21
|
+
/** Optional JWS-compact DelegationCredential VC */
|
|
22
|
+
'KYA-OS-Delegation-Credential'?: string;
|
|
21
23
|
/** Proof ID for audit trail */
|
|
22
24
|
'KYA-Proof-Id': string;
|
|
23
25
|
/** Optional CRISP spend info (JSON string: {unit, delta, remaining}) */
|
|
24
26
|
'KYA-CRISP-Spend'?: string;
|
|
25
27
|
/** Optional session ID */
|
|
26
|
-
'KYA-Session-Id'?: string;
|
|
28
|
+
'KYA-OS-Session-Id'?: string;
|
|
27
29
|
/** Optional scopes */
|
|
28
|
-
'KYA-Granted-Scopes'?: string;
|
|
30
|
+
'KYA-OS-Granted-Scopes'?: string;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* Header names as constants for type safety
|
|
32
34
|
*/
|
|
33
35
|
export declare const DOWNSTREAM_HEADER_NAMES: Readonly<{
|
|
34
|
-
readonly AGENT_DID: "KYA-Agent-DID";
|
|
35
|
-
readonly
|
|
36
|
-
readonly
|
|
36
|
+
readonly AGENT_DID: "KYA-OS-Agent-DID";
|
|
37
|
+
readonly DELEGATION_CHAIN: "KYA-OS-Delegation-Chain";
|
|
38
|
+
readonly DELEGATION_PROOF: "KYA-OS-Delegation-Proof";
|
|
39
|
+
readonly DELEGATION_CREDENTIAL: "KYA-OS-Delegation-Credential";
|
|
37
40
|
readonly PROOF_ID: "KYA-Proof-Id";
|
|
38
41
|
readonly CRISP_SPEND: "KYA-CRISP-Spend";
|
|
39
|
-
readonly SESSION_ID: "KYA-Session-Id";
|
|
40
|
-
readonly SCOPES: "KYA-Granted-Scopes";
|
|
42
|
+
readonly SESSION_ID: "KYA-OS-Session-Id";
|
|
43
|
+
readonly SCOPES: "KYA-OS-Granted-Scopes";
|
|
41
44
|
}>;
|
|
42
45
|
/**
|
|
43
46
|
* CRISP Spend Info
|
|
@@ -75,8 +78,9 @@ export declare function parseCrispSpend(headerValue: string): CrispSpendInfo | n
|
|
|
75
78
|
export declare function createDownstreamHeaders(config: {
|
|
76
79
|
agentDid: string;
|
|
77
80
|
proofId: string;
|
|
78
|
-
delegationId?: string;
|
|
79
81
|
delegationChain?: string;
|
|
82
|
+
delegationProof?: string;
|
|
83
|
+
delegationCredential?: string;
|
|
80
84
|
crispSpend?: CrispSpendInfo;
|
|
81
85
|
sessionId?: string;
|
|
82
86
|
scopes?: string[];
|
package/dist/runtime/headers.js
CHANGED
|
@@ -16,13 +16,14 @@ exports.createDownstreamHeaders = createDownstreamHeaders;
|
|
|
16
16
|
* Header names as constants for type safety
|
|
17
17
|
*/
|
|
18
18
|
exports.DOWNSTREAM_HEADER_NAMES = Object.freeze({
|
|
19
|
-
AGENT_DID: 'KYA-Agent-DID',
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
AGENT_DID: 'KYA-OS-Agent-DID',
|
|
20
|
+
DELEGATION_CHAIN: 'KYA-OS-Delegation-Chain',
|
|
21
|
+
DELEGATION_PROOF: 'KYA-OS-Delegation-Proof',
|
|
22
|
+
DELEGATION_CREDENTIAL: 'KYA-OS-Delegation-Credential',
|
|
22
23
|
PROOF_ID: 'KYA-Proof-Id',
|
|
23
24
|
CRISP_SPEND: 'KYA-CRISP-Spend',
|
|
24
|
-
SESSION_ID: 'KYA-Session-Id',
|
|
25
|
-
SCOPES: 'KYA-Granted-Scopes',
|
|
25
|
+
SESSION_ID: 'KYA-OS-Session-Id',
|
|
26
|
+
SCOPES: 'KYA-OS-Granted-Scopes',
|
|
26
27
|
});
|
|
27
28
|
/**
|
|
28
29
|
* Helper to serialize CRISP spend info to header value
|
|
@@ -59,23 +60,26 @@ function parseCrispSpend(headerValue) {
|
|
|
59
60
|
*/
|
|
60
61
|
function createDownstreamHeaders(config) {
|
|
61
62
|
const headers = {
|
|
62
|
-
'KYA-Agent-DID': config.agentDid,
|
|
63
|
+
'KYA-OS-Agent-DID': config.agentDid,
|
|
63
64
|
'KYA-Proof-Id': config.proofId,
|
|
64
65
|
};
|
|
65
|
-
if (config.delegationId) {
|
|
66
|
-
headers['KYA-Delegation-Id'] = config.delegationId;
|
|
67
|
-
}
|
|
68
66
|
if (config.delegationChain) {
|
|
69
|
-
headers['KYA-Delegation-Chain'] = config.delegationChain;
|
|
67
|
+
headers['KYA-OS-Delegation-Chain'] = config.delegationChain;
|
|
68
|
+
}
|
|
69
|
+
if (config.delegationProof) {
|
|
70
|
+
headers['KYA-OS-Delegation-Proof'] = config.delegationProof;
|
|
71
|
+
}
|
|
72
|
+
if (config.delegationCredential) {
|
|
73
|
+
headers['KYA-OS-Delegation-Credential'] = config.delegationCredential;
|
|
70
74
|
}
|
|
71
75
|
if (config.crispSpend) {
|
|
72
76
|
headers['KYA-CRISP-Spend'] = serializeCrispSpend(config.crispSpend);
|
|
73
77
|
}
|
|
74
78
|
if (config.sessionId) {
|
|
75
|
-
headers['KYA-Session-Id'] = config.sessionId;
|
|
79
|
+
headers['KYA-OS-Session-Id'] = config.sessionId;
|
|
76
80
|
}
|
|
77
81
|
if (config.scopes && config.scopes.length > 0) {
|
|
78
|
-
headers['KYA-Granted-Scopes'] = config.scopes.join(',');
|
|
82
|
+
headers['KYA-OS-Granted-Scopes'] = config.scopes.join(',');
|
|
79
83
|
}
|
|
80
84
|
return headers;
|
|
81
85
|
}
|
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;
|
|
@@ -32,6 +34,7 @@ export declare const AgentContextSchema: z.ZodObject<{
|
|
|
32
34
|
scopes?: string[] | undefined;
|
|
33
35
|
subject?: string | undefined;
|
|
34
36
|
delegationRef?: string | undefined;
|
|
37
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
35
38
|
}>;
|
|
36
39
|
export declare const VerifierResultSchema: z.ZodObject<{
|
|
37
40
|
success: z.ZodBoolean;
|
|
@@ -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;
|
|
@@ -66,6 +71,7 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
66
71
|
scopes?: string[] | undefined;
|
|
67
72
|
subject?: string | undefined;
|
|
68
73
|
delegationRef?: string | undefined;
|
|
74
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
69
75
|
}>>;
|
|
70
76
|
error: z.ZodOptional<z.ZodObject<{
|
|
71
77
|
code: z.ZodString;
|
|
@@ -102,6 +108,7 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
102
108
|
verifiedAt: number;
|
|
103
109
|
subject?: string | undefined;
|
|
104
110
|
delegationRef?: string | undefined;
|
|
111
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
105
112
|
} | undefined;
|
|
106
113
|
}, {
|
|
107
114
|
success: boolean;
|
|
@@ -122,6 +129,7 @@ export declare const VerifierResultSchema: z.ZodObject<{
|
|
|
122
129
|
scopes?: string[] | undefined;
|
|
123
130
|
subject?: string | undefined;
|
|
124
131
|
delegationRef?: string | undefined;
|
|
132
|
+
outcome?: "allowed" | "denied" | "step_up_required" | "needs_authorization" | undefined;
|
|
125
133
|
} | undefined;
|
|
126
134
|
}>;
|
|
127
135
|
export declare const StructuredErrorSchema: z.ZodObject<{
|
|
@@ -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.1",
|
|
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
|
+
}
|