@tangle-network/agent-interface 5.2.1 → 5.3.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/agent-candidate-code-schema.d.ts +63 -14
- package/dist/agent-candidate-code-schema.js +2 -1
- package/dist/agent-candidate-execution-plan-schema.d.ts +262 -20
- package/dist/agent-candidate-execution-plan-schema.js +30 -7
- package/dist/agent-candidate-profile-schema.d.ts +63 -8
- package/dist/agent-candidate-profile-schema.js +2 -0
- package/dist/agent-candidate-promotion-schema.d.ts +952 -100
- package/dist/agent-candidate-receipt-schema.d.ts +104 -8
- package/dist/agent-candidate-schema-common.d.ts +30 -3
- package/dist/agent-candidate-schema-common.js +14 -8
- package/dist/agent-candidate-schema.d.ts +72 -10
- package/dist/agent-candidate.d.ts +12 -4
- package/dist/agent-execution-preparation-receipt.d.ts +2 -0
- package/dist/agent-execution-preparation.js +4 -2
- package/dist/agent-profile-materialization.d.ts +1 -1
- package/dist/agent-profile-materialization.js +3 -0
- package/dist/agent-profile.d.ts +6 -0
- package/dist/environment-interactive-control.d.ts +10 -0
- package/dist/environment-interactive-start.d.ts +33 -6
- package/dist/environment-profile-capabilities.d.ts +18 -0
- package/dist/environment-profile-capabilities.js +2 -0
- package/dist/environment-provider.d.ts +1 -0
- package/dist/environment-provider.js +1 -0
- package/dist/environment-runtime.d.ts +176 -18
- package/dist/environment-runtime.js +14 -1
- package/dist/harness-capabilities.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/portable-context-transfer.d.ts +33 -6
- package/dist/profile-diff.d.ts +4 -3
- package/dist/profile-diff.js +3 -0
- package/dist/profile-schema.d.ts +50 -21
- package/dist/profile-schema.js +6 -13
- package/dist/proxy-secrets.d.ts +329 -0
- package/dist/proxy-secrets.js +183 -0
- package/fixtures/proxy-secret-wire-v1.json +1119 -0
- package/package.json +2 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { agentCandidateSecretRefSchema } from "./agent-candidate-schema-common.js";
|
|
3
|
+
import { boundedIdentifierSchema, CONTRACT_MAX_ARRAY_LENGTH } from "./contract-limits.js";
|
|
4
|
+
/**
|
|
5
|
+
* Declarations request authority, never grant it. Trusted resolution uses authenticated tenant
|
|
6
|
+
* and issuer authority, with no personal/team fallback, and pins immutable secret ID/revision.
|
|
7
|
+
* The issuer intersects requested scope/expiry with its policy and authenticated lineage.
|
|
8
|
+
* Placeholders are independent unpredictable values per sandbox epoch, generated by the trusted
|
|
9
|
+
* lifecycle owner, never from secret bytes or public refs. JWT placeholders carry only synthetic
|
|
10
|
+
* public sub/aud/exp compatibility claims, no upstream claims, account identity or authority.
|
|
11
|
+
* The sandbox is the isolation boundary; these schemas cannot authenticate guest processes.
|
|
12
|
+
*/
|
|
13
|
+
const unique = (values) => new Set(values).size === values.length;
|
|
14
|
+
const hostSchema = z.string().max(253).regex(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "host must be an exact lowercase FQDN without scheme, port, wildcard or trailing dot");
|
|
15
|
+
const methodSchema = z.string().max(32).regex(/^[A-Z]+$/);
|
|
16
|
+
/** Paths are exact or terminal slash-star subtrees. /v1/* includes /v1/ and /v1/a, never /v1
|
|
17
|
+
* or /v10. V1 rejects all percent encoding, dot segments, repeated slashes, query, fragments,
|
|
18
|
+
* backslashes, non-ASCII and controls, rather than normalizing ambiguous spellings.
|
|
19
|
+
*/
|
|
20
|
+
const pathSchema = z.string().max(2048).refine((path) => {
|
|
21
|
+
const exact = path.endsWith("/*") ? path.slice(0, -1) : path;
|
|
22
|
+
return /^\/[A-Za-z0-9._~!$&'()+,;=:@/-]*$/.test(exact) && !exact.includes("//") &&
|
|
23
|
+
!exact.split("/").some((part) => part === "." || part === "..");
|
|
24
|
+
}, "path must be canonical ASCII, exact or a terminal /* subtree");
|
|
25
|
+
const list = (schema) => z.array(schema).min(1).max(CONTRACT_MAX_ARRAY_LENGTH).refine(unique, "selectors must be unique");
|
|
26
|
+
const envNameSchema = z.string().max(256).regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
|
|
27
|
+
const headerSchema = z.string().max(256).regex(/^[!#$%&'*+.^_\x60|~0-9A-Za-z-]+$/).refine((name) => !["authorization", "proxy-authorization", "host", "connection", "content-length", "transfer-encoding", "trailer", "upgrade", "te"].includes(name.toLowerCase()), "use bearer/basic for Authorization; routing and framing headers cannot carry secrets");
|
|
28
|
+
/** Match complete components only. Selected header names are case-insensitive; duplicate fields
|
|
29
|
+
* are refused before coalescing. Query names are case-sensitive after one strict UTF-8 percent
|
|
30
|
+
* decode (+ means space); malformed encoding and duplicate decoded selector names are refused.
|
|
31
|
+
* Never scan other query fields. Basic is one strict base64 value decoding to exactly public
|
|
32
|
+
* username:placeholder; the username is printable ASCII without colon. Bearer is exactly
|
|
33
|
+
* "Bearer SP placeholder". Embedded placeholders never match. Authority, TLS SNI and validated
|
|
34
|
+
* destination must agree on the declared FQDN over HTTPS/443; explicit :443 is equivalent.
|
|
35
|
+
*/
|
|
36
|
+
export const ProxySecretPresentationSchema = z.discriminatedUnion("type", [
|
|
37
|
+
z.strictObject({ type: z.literal("bearer") }),
|
|
38
|
+
z.strictObject({ type: z.literal("basic"), username: z.string().min(1).max(256).regex(/^[\x21-\x39\x3b-\x7e]+$/) }),
|
|
39
|
+
z.strictObject({ type: z.literal("header"), name: headerSchema }),
|
|
40
|
+
z.strictObject({ type: z.literal("query"), name: z.string().max(256).regex(/^[A-Za-z0-9._~-]+$/) }),
|
|
41
|
+
]);
|
|
42
|
+
export const ProxySecretScopeSchema = z.strictObject({
|
|
43
|
+
hosts: list(hostSchema).readonly(), methods: list(methodSchema).readonly().optional(), paths: list(pathSchema).readonly().optional(),
|
|
44
|
+
});
|
|
45
|
+
const comparableScopeSchema = ProxySecretScopeSchema.strip();
|
|
46
|
+
export const ProxySecretBindingSchema = ProxySecretScopeSchema.extend({
|
|
47
|
+
envName: envNameSchema,
|
|
48
|
+
secretRef: agentCandidateSecretRefSchema.omit({ format: true }),
|
|
49
|
+
presentation: ProxySecretPresentationSchema,
|
|
50
|
+
placeholderFormat: z.enum(["opaque", "jwt"]).optional(),
|
|
51
|
+
ttlSeconds: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
52
|
+
});
|
|
53
|
+
export const ProxySecretsSchema = z.array(ProxySecretBindingSchema).max(CONTRACT_MAX_ARRAY_LENGTH)
|
|
54
|
+
.refine((bindings) => unique(bindings.map((binding) => binding.envName)), "proxy secret env names must be unique");
|
|
55
|
+
export const ProxySecretResolvedIdentitySchema = z.strictObject({
|
|
56
|
+
tenantId: boundedIdentifierSchema, issuerId: boundedIdentifierSchema,
|
|
57
|
+
secretId: boundedIdentifierSchema, revision: boundedIdentifierSchema,
|
|
58
|
+
});
|
|
59
|
+
const timestamp = z.iso.datetime();
|
|
60
|
+
export const ProxySecretGrantSchema = z.strictObject({
|
|
61
|
+
protocolVersion: z.literal(1), grantId: boundedIdentifierSchema,
|
|
62
|
+
sandboxId: boundedIdentifierSchema, epoch: boundedIdentifierSchema,
|
|
63
|
+
identity: ProxySecretResolvedIdentitySchema, scope: ProxySecretScopeSchema,
|
|
64
|
+
presentation: ProxySecretPresentationSchema, placeholderFormat: z.enum(["opaque", "jwt"]),
|
|
65
|
+
issuedAt: timestamp, expiresAt: timestamp, sourceExpiresAt: timestamp.optional(), parentGrantId: boundedIdentifierSchema.optional(),
|
|
66
|
+
}).refine((grant) => grant.parentGrantId !== grant.grantId && Date.parse(grant.expiresAt) > Date.parse(grant.issuedAt) &&
|
|
67
|
+
(grant.sourceExpiresAt === undefined || Date.parse(grant.expiresAt) <= Date.parse(grant.sourceExpiresAt)), "grant expiry must follow issuance");
|
|
68
|
+
const receiptShape = {
|
|
69
|
+
protocolVersion: z.literal(1), sandboxId: boundedIdentifierSchema,
|
|
70
|
+
generation: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
71
|
+
bindings: z.array(z.strictObject({ envName: envNameSchema, secretRef: agentCandidateSecretRefSchema.omit({ format: true }), grant: ProxySecretGrantSchema })).max(CONTRACT_MAX_ARRAY_LENGTH),
|
|
72
|
+
proxyAcknowledged: z.literal(true),
|
|
73
|
+
};
|
|
74
|
+
/** proxy-installed is a phase-two input, never dispatch authority. Only installed acknowledges
|
|
75
|
+
* proxy and placeholder-only materialization. Revoked confirms binding revocation, not upstream
|
|
76
|
+
* credential revocation. Receipts contain metadata only, never secret or placeholder bytes.
|
|
77
|
+
*/
|
|
78
|
+
export const ProxySecretReceiptSchema = z.discriminatedUnion("status", [
|
|
79
|
+
z.strictObject({ ...receiptShape, status: z.literal("proxy-installed"), materializationAcknowledged: z.literal(false) }),
|
|
80
|
+
z.strictObject({ ...receiptShape, status: z.literal("installed"), materializationAcknowledged: z.literal(true) }),
|
|
81
|
+
z.strictObject({ ...receiptShape, status: z.literal("revoked"), materializationAcknowledged: z.literal(true) }),
|
|
82
|
+
]).superRefine((receipt, ctx) => {
|
|
83
|
+
if (!unique(receipt.bindings.map((binding) => binding.envName)) ||
|
|
84
|
+
!unique(receipt.bindings.map((binding) => binding.grant.grantId))) {
|
|
85
|
+
ctx.addIssue({ code: "custom", path: ["bindings"], message: "receipt binding names and grant IDs must be unique" });
|
|
86
|
+
}
|
|
87
|
+
if (receipt.bindings.some((binding) => binding.grant.sandboxId !== receipt.sandboxId)) {
|
|
88
|
+
ctx.addIssue({ code: "custom", path: ["bindings"], message: "receipt grants must belong to its sandbox" });
|
|
89
|
+
}
|
|
90
|
+
if (new Set(receipt.bindings.map((binding) => binding.grant.epoch)).size > 1) {
|
|
91
|
+
ctx.addIssue({ code: "custom", path: ["bindings"], message: "receipt grants must share one sandbox lifecycle epoch" });
|
|
92
|
+
}
|
|
93
|
+
if (receipt.status === "revoked" && receipt.bindings.length !== 0) {
|
|
94
|
+
ctx.addIssue({ code: "custom", path: ["bindings"], message: "revoked receipt cannot retain bindings" });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
/** Advertise only when served API, driver, host, proxy and materializer agree. Absence,
|
|
98
|
+
* protocol skew, or unsupported driver/presentation/format refuses before resolution or launch.
|
|
99
|
+
*/
|
|
100
|
+
export const ProxySecretCapabilitiesSchema = z.strictObject({
|
|
101
|
+
protocolVersion: z.literal(1), supportedDrivers: list(boundedIdentifierSchema),
|
|
102
|
+
presentations: list(z.enum(["bearer", "basic", "header", "query"])),
|
|
103
|
+
placeholderFormats: list(z.enum(["opaque", "jwt"])),
|
|
104
|
+
enforcement: z.literal("exact"), transports: z.tuple([z.literal("https:443")]),
|
|
105
|
+
lifecycle: z.literal(true), placementReceiptRequired: z.literal(true),
|
|
106
|
+
});
|
|
107
|
+
export const ProxySecretUpdateControlsSchema = z.strictObject({
|
|
108
|
+
expectedProxySecretGeneration: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
109
|
+
requireInPlace: z.boolean().optional(),
|
|
110
|
+
});
|
|
111
|
+
/** Bounded delegation comparison, not proof of issuer authorization, installation, freshness
|
|
112
|
+
* or authenticated lineage. The trusted lifecycle owner must cascade parent revocation.
|
|
113
|
+
*/
|
|
114
|
+
export function isProxySecretGrantAttenuation(parent, child) {
|
|
115
|
+
const p = ProxySecretGrantSchema.safeParse(parent);
|
|
116
|
+
const c = ProxySecretGrantSchema.safeParse(child);
|
|
117
|
+
if (!p.success || !c.success)
|
|
118
|
+
return false;
|
|
119
|
+
const from = p.data, to = c.data;
|
|
120
|
+
return to.parentGrantId === from.grantId &&
|
|
121
|
+
Object.keys(from.identity).every((key) => from.identity[key] === to.identity[key]) &&
|
|
122
|
+
proxySecretPresentationsEqual(from.presentation, to.presentation) && from.placeholderFormat === to.placeholderFormat &&
|
|
123
|
+
Date.parse(to.issuedAt) >= Date.parse(from.issuedAt) && Date.parse(to.expiresAt) <= Date.parse(from.expiresAt) &&
|
|
124
|
+
isProxySecretScopeAttenuation(from.scope, to.scope);
|
|
125
|
+
}
|
|
126
|
+
/** Early declaration guard only: matching public refs do not establish resolved authority. */
|
|
127
|
+
export function isProxySecretBindingAttenuation(parent, child) {
|
|
128
|
+
const p = ProxySecretBindingSchema.safeParse(parent), c = ProxySecretBindingSchema.safeParse(child);
|
|
129
|
+
if (!p.success || !c.success)
|
|
130
|
+
return false;
|
|
131
|
+
const from = p.data, to = c.data;
|
|
132
|
+
return from.secretRef.key === to.secretRef.key &&
|
|
133
|
+
proxySecretPresentationsEqual(from.presentation, to.presentation) &&
|
|
134
|
+
(from.placeholderFormat ?? "opaque") === (to.placeholderFormat ?? "opaque") &&
|
|
135
|
+
(from.ttlSeconds === undefined || (to.ttlSeconds !== undefined && to.ttlSeconds <= from.ttlSeconds)) &&
|
|
136
|
+
isProxySecretScopeAttenuation(from, to);
|
|
137
|
+
}
|
|
138
|
+
/** Omitted methods/paths mean all; a bounded parent cannot be widened by omission. */
|
|
139
|
+
export function isProxySecretScopeAttenuation(parent, child) {
|
|
140
|
+
const p = comparableScopeSchema.safeParse(parent), c = comparableScopeSchema.safeParse(child);
|
|
141
|
+
if (!p.success || !c.success)
|
|
142
|
+
return false;
|
|
143
|
+
return subset(p.data.hosts, c.data.hosts) && subset(p.data.methods, c.data.methods) &&
|
|
144
|
+
(p.data.paths === undefined || (c.data.paths !== undefined && c.data.paths.every((path) => p.data.paths.some((allowed) => allowed === path || (allowed.endsWith("/*") && path.startsWith(allowed.slice(0, -1)))))));
|
|
145
|
+
}
|
|
146
|
+
/** Header selectors compare case-insensitively; query selectors and Basic usernames are exact. */
|
|
147
|
+
export function proxySecretPresentationsEqual(left, right) {
|
|
148
|
+
const p = ProxySecretPresentationSchema.safeParse(left), c = ProxySecretPresentationSchema.safeParse(right);
|
|
149
|
+
if (!p.success || !c.success)
|
|
150
|
+
return false;
|
|
151
|
+
const from = p.data, to = c.data;
|
|
152
|
+
if (from.type !== to.type)
|
|
153
|
+
return false;
|
|
154
|
+
if (from.type === "header" && to.type === "header")
|
|
155
|
+
return from.name.toLowerCase() === to.name.toLowerCase();
|
|
156
|
+
if (from.type === "query" && to.type === "query")
|
|
157
|
+
return from.name === to.name;
|
|
158
|
+
if (from.type === "basic" && to.type === "basic")
|
|
159
|
+
return from.username === to.username;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
function subset(parent, child) {
|
|
163
|
+
return parent === undefined || (child !== undefined && child.every((value) => parent.includes(value)));
|
|
164
|
+
}
|
|
165
|
+
/** Merge independent declaration sources. Identical names deduplicate only when the full
|
|
166
|
+
* schema-parsed declaration agrees; callers preserve omission versus explicit empty selection.
|
|
167
|
+
* The result is detached public metadata. Conflicts fail without exposing declaration contents.
|
|
168
|
+
*/
|
|
169
|
+
export function mergeProxySecretBindings(...collections) {
|
|
170
|
+
const merged = new Map();
|
|
171
|
+
for (const collection of collections) {
|
|
172
|
+
if (collection === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
for (const binding of ProxySecretsSchema.parse(collection)) {
|
|
175
|
+
const existing = merged.get(binding.envName);
|
|
176
|
+
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(binding)) {
|
|
177
|
+
throw new Error("proxy-secret declarations disagree for the same environment name");
|
|
178
|
+
}
|
|
179
|
+
merged.set(binding.envName, binding);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return ProxySecretsSchema.parse([...merged.values()]);
|
|
183
|
+
}
|