@tangle-network/agent-interface 0.32.0 → 0.33.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.
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ import type { Sha256Digest } from "./agent-candidate.js";
3
+ export type CertifiedContextKind = "prompt" | "skill" | "instructions";
4
+ export type CertifiedContextDelivery = {
5
+ readonly kind: "inline";
6
+ readonly content: string;
7
+ } | {
8
+ readonly kind: "file";
9
+ readonly path: string;
10
+ readonly content: string;
11
+ };
12
+ export interface CertifiedContextProvenance {
13
+ /** SHA-256 of the entry id, kind, name, and delivery. */
14
+ readonly contentHash: Sha256Digest;
15
+ /** Positive release number, or null when the source has no released version. */
16
+ readonly version: number | null;
17
+ readonly promotedAt: string;
18
+ }
19
+ export interface CertifiedContextEntry {
20
+ readonly id: string;
21
+ readonly kind: CertifiedContextKind;
22
+ readonly name: string;
23
+ readonly delivery: CertifiedContextDelivery;
24
+ readonly provenance: CertifiedContextProvenance;
25
+ }
26
+ /**
27
+ * Tenant-bound context delivered by Intelligence.
28
+ *
29
+ * This contract intentionally excludes tools, credentials, executable files,
30
+ * profile patches, MCP servers, and arbitrary network requests.
31
+ */
32
+ export interface CertifiedContext {
33
+ readonly tenantId: string;
34
+ readonly target: string;
35
+ readonly state: "active" | "revoked";
36
+ /** Monotonic decimal revision for this tenant and target. */
37
+ readonly revision: string;
38
+ readonly generatedAt: string;
39
+ readonly expiresAt: string;
40
+ readonly entries: readonly CertifiedContextEntry[];
41
+ /** SHA-256 of tenantId, target, state, revision, and entries. */
42
+ readonly contentHash: Sha256Digest;
43
+ }
44
+ export declare const certifiedContextDeliverySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
45
+ kind: z.ZodLiteral<"inline">;
46
+ content: z.ZodString;
47
+ }, z.core.$strict>, z.ZodObject<{
48
+ kind: z.ZodLiteral<"file">;
49
+ path: z.ZodString;
50
+ content: z.ZodString;
51
+ }, z.core.$strict>], "kind">;
52
+ export declare const certifiedContextProvenanceSchema: z.ZodObject<{
53
+ contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
54
+ version: z.ZodNullable<z.ZodNumber>;
55
+ promotedAt: z.ZodISODateTime;
56
+ }, z.core.$strict>;
57
+ /** Compute the exact digest stored in an entry's provenance. */
58
+ export declare function certifiedContextEntryContentHash(entry: Pick<CertifiedContextEntry, "id" | "kind" | "name" | "delivery">): Sha256Digest;
59
+ export declare const certifiedContextEntrySchema: z.ZodObject<{
60
+ id: z.ZodString;
61
+ kind: z.ZodEnum<{
62
+ prompt: "prompt";
63
+ instructions: "instructions";
64
+ skill: "skill";
65
+ }>;
66
+ name: z.ZodString;
67
+ delivery: z.ZodDiscriminatedUnion<[z.ZodObject<{
68
+ kind: z.ZodLiteral<"inline">;
69
+ content: z.ZodString;
70
+ }, z.core.$strict>, z.ZodObject<{
71
+ kind: z.ZodLiteral<"file">;
72
+ path: z.ZodString;
73
+ content: z.ZodString;
74
+ }, z.core.$strict>], "kind">;
75
+ provenance: z.ZodObject<{
76
+ contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
77
+ version: z.ZodNullable<z.ZodNumber>;
78
+ promotedAt: z.ZodISODateTime;
79
+ }, z.core.$strict>;
80
+ }, z.core.$strict>;
81
+ /** Compute the stable hash for one context revision. */
82
+ export declare function certifiedContextContentHash(context: Pick<CertifiedContext, "tenantId" | "target" | "state" | "revision" | "entries">): Sha256Digest;
83
+ export declare const certifiedContextSchema: z.ZodObject<{
84
+ tenantId: z.ZodString;
85
+ target: z.ZodString;
86
+ state: z.ZodEnum<{
87
+ active: "active";
88
+ revoked: "revoked";
89
+ }>;
90
+ revision: z.ZodString;
91
+ generatedAt: z.ZodISODateTime;
92
+ expiresAt: z.ZodISODateTime;
93
+ entries: z.ZodArray<z.ZodObject<{
94
+ id: z.ZodString;
95
+ kind: z.ZodEnum<{
96
+ prompt: "prompt";
97
+ instructions: "instructions";
98
+ skill: "skill";
99
+ }>;
100
+ name: z.ZodString;
101
+ delivery: z.ZodDiscriminatedUnion<[z.ZodObject<{
102
+ kind: z.ZodLiteral<"inline">;
103
+ content: z.ZodString;
104
+ }, z.core.$strict>, z.ZodObject<{
105
+ kind: z.ZodLiteral<"file">;
106
+ path: z.ZodString;
107
+ content: z.ZodString;
108
+ }, z.core.$strict>], "kind">;
109
+ provenance: z.ZodObject<{
110
+ contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
111
+ version: z.ZodNullable<z.ZodNumber>;
112
+ promotedAt: z.ZodISODateTime;
113
+ }, z.core.$strict>;
114
+ }, z.core.$strict>>;
115
+ contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
116
+ }, z.core.$strict>;
117
+ /** Parse, clone, and recursively freeze one untrusted context response. */
118
+ export declare function parseCertifiedContext(value: unknown): CertifiedContext;
@@ -0,0 +1,228 @@
1
+ import { z } from "zod";
2
+ import { canonicalCandidateDigest, isSafeRelativePath, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
3
+ const MAX_INLINE_CONTEXT_BYTES = 65_536;
4
+ const MAX_TOTAL_INLINE_CONTEXT_BYTES = 131_072;
5
+ const MAX_FILE_CONTEXT_BYTES = 1_048_576;
6
+ const MAX_CERTIFIED_CONTEXT_BYTES = 16_777_216;
7
+ const MAX_CERTIFIED_CONTEXT_LIFETIME_MS = 900_000;
8
+ const revisionPattern = /^(0|[1-9]\d{0,18})$/;
9
+ const nonBlankStringSchema = z
10
+ .string()
11
+ .refine((value) => value.trim().length > 0, "value cannot be blank");
12
+ const identifierSchema = nonBlankStringSchema.max(256);
13
+ const relativePathSchema = nonBlankStringSchema
14
+ .max(1_024)
15
+ .refine((value) => isSafeRelativePath(value, false), "value must be a canonical relative path");
16
+ const inlineContentSchema = z
17
+ .string()
18
+ .max(MAX_INLINE_CONTEXT_BYTES)
19
+ .refine((value) => new TextEncoder().encode(value).byteLength <= MAX_INLINE_CONTEXT_BYTES, `inline content exceeds ${MAX_INLINE_CONTEXT_BYTES} UTF-8 bytes`);
20
+ const fileContentSchema = z
21
+ .string()
22
+ .max(MAX_FILE_CONTEXT_BYTES)
23
+ .refine((value) => new TextEncoder().encode(value).byteLength <= MAX_FILE_CONTEXT_BYTES, `file content exceeds ${MAX_FILE_CONTEXT_BYTES} UTF-8 bytes`);
24
+ export const certifiedContextDeliverySchema = z.discriminatedUnion("kind", [
25
+ z.strictObject({
26
+ kind: z.literal("inline"),
27
+ content: inlineContentSchema,
28
+ }),
29
+ z.strictObject({
30
+ kind: z.literal("file"),
31
+ path: relativePathSchema,
32
+ content: fileContentSchema,
33
+ }),
34
+ ]);
35
+ export const certifiedContextProvenanceSchema = z.strictObject({
36
+ contentHash: sha256DigestSchema,
37
+ version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
38
+ promotedAt: z.iso.datetime(),
39
+ });
40
+ function jsonMaterial(value) {
41
+ const serialized = JSON.stringify(value);
42
+ if (serialized === undefined) {
43
+ throw new Error("certified context material must be JSON serializable");
44
+ }
45
+ return JSON.parse(serialized);
46
+ }
47
+ /** Compute the exact digest stored in an entry's provenance. */
48
+ export function certifiedContextEntryContentHash(entry) {
49
+ return canonicalCandidateDigest(jsonMaterial({
50
+ id: entry.id,
51
+ kind: entry.kind,
52
+ name: entry.name,
53
+ delivery: entry.delivery,
54
+ }));
55
+ }
56
+ export const certifiedContextEntrySchema = z
57
+ .strictObject({
58
+ id: identifierSchema,
59
+ kind: z.enum(["prompt", "skill", "instructions"]),
60
+ name: identifierSchema,
61
+ delivery: certifiedContextDeliverySchema,
62
+ provenance: certifiedContextProvenanceSchema,
63
+ })
64
+ .superRefine((entry, context) => {
65
+ if (entry.kind === "skill" && entry.delivery.kind !== "file") {
66
+ context.addIssue({
67
+ code: "custom",
68
+ path: ["delivery", "kind"],
69
+ message: "skills must be delivered as files",
70
+ });
71
+ }
72
+ if (entry.kind !== "skill" && entry.delivery.kind !== "inline") {
73
+ context.addIssue({
74
+ code: "custom",
75
+ path: ["delivery", "kind"],
76
+ message: "prompts and instructions must be delivered inline",
77
+ });
78
+ }
79
+ if (entry.delivery.kind === "inline" &&
80
+ entry.delivery.content.trim().length === 0) {
81
+ context.addIssue({
82
+ code: "custom",
83
+ path: ["delivery", "content"],
84
+ message: "inline context cannot be blank",
85
+ });
86
+ }
87
+ if (entry.provenance.contentHash !== certifiedContextEntryContentHash(entry)) {
88
+ context.addIssue({
89
+ code: "custom",
90
+ path: ["provenance", "contentHash"],
91
+ message: "entry content hash does not match the delivered context",
92
+ });
93
+ }
94
+ });
95
+ /** Compute the stable hash for one context revision. */
96
+ export function certifiedContextContentHash(context) {
97
+ return canonicalCandidateDigest(jsonMaterial(context));
98
+ }
99
+ export const certifiedContextSchema = z
100
+ .strictObject({
101
+ tenantId: identifierSchema,
102
+ target: identifierSchema,
103
+ state: z.enum(["active", "revoked"]),
104
+ revision: z
105
+ .string()
106
+ .regex(revisionPattern)
107
+ .refine((value) => !revisionPattern.test(value) ||
108
+ BigInt(value) <= 9223372036854775807n, "revision exceeds signed 64-bit range"),
109
+ generatedAt: z.iso.datetime(),
110
+ expiresAt: z.iso.datetime(),
111
+ entries: z.array(certifiedContextEntrySchema).max(128),
112
+ contentHash: sha256DigestSchema,
113
+ })
114
+ .superRefine((context, refinement) => {
115
+ const generatedAt = Date.parse(context.generatedAt);
116
+ const expiresAt = Date.parse(context.expiresAt);
117
+ if (expiresAt <= generatedAt) {
118
+ refinement.addIssue({
119
+ code: "custom",
120
+ path: ["expiresAt"],
121
+ message: "expiresAt must be after generatedAt",
122
+ });
123
+ }
124
+ else if (expiresAt - generatedAt >
125
+ MAX_CERTIFIED_CONTEXT_LIFETIME_MS) {
126
+ refinement.addIssue({
127
+ code: "custom",
128
+ path: ["expiresAt"],
129
+ message: "certified context cannot live longer than 15 minutes",
130
+ });
131
+ }
132
+ const serialized = JSON.stringify(context);
133
+ if (new TextEncoder().encode(serialized).byteLength >
134
+ MAX_CERTIFIED_CONTEXT_BYTES) {
135
+ refinement.addIssue({
136
+ code: "too_big",
137
+ maximum: MAX_CERTIFIED_CONTEXT_BYTES,
138
+ origin: "string",
139
+ inclusive: true,
140
+ message: `serialized context exceeds ${MAX_CERTIFIED_CONTEXT_BYTES} UTF-8 bytes`,
141
+ });
142
+ }
143
+ const ids = new Set();
144
+ const filePaths = new Set();
145
+ let inlineBytes = 0;
146
+ for (const [index, entry] of context.entries.entries()) {
147
+ if (ids.has(entry.id)) {
148
+ refinement.addIssue({
149
+ code: "custom",
150
+ path: ["entries", index, "id"],
151
+ message: `duplicate context id: ${entry.id}`,
152
+ });
153
+ }
154
+ ids.add(entry.id);
155
+ if (entry.delivery.kind === "file") {
156
+ if (filePaths.has(entry.delivery.path)) {
157
+ refinement.addIssue({
158
+ code: "custom",
159
+ path: ["entries", index, "delivery", "path"],
160
+ message: `duplicate file path: ${entry.delivery.path}`,
161
+ });
162
+ }
163
+ filePaths.add(entry.delivery.path);
164
+ }
165
+ else {
166
+ inlineBytes += new TextEncoder().encode(entry.delivery.content).byteLength;
167
+ }
168
+ if (Date.parse(entry.provenance.promotedAt) > generatedAt) {
169
+ refinement.addIssue({
170
+ code: "custom",
171
+ path: ["entries", index, "provenance", "promotedAt"],
172
+ message: "context cannot be promoted after bundle generation",
173
+ });
174
+ }
175
+ }
176
+ if (inlineBytes > MAX_TOTAL_INLINE_CONTEXT_BYTES) {
177
+ refinement.addIssue({
178
+ code: "custom",
179
+ path: ["entries"],
180
+ message: `inline context exceeds ${MAX_TOTAL_INLINE_CONTEXT_BYTES} UTF-8 bytes`,
181
+ });
182
+ }
183
+ if ((context.state === "active" && context.entries.length === 0) ||
184
+ (context.state === "revoked" && context.entries.length !== 0)) {
185
+ refinement.addIssue({
186
+ code: "custom",
187
+ path: ["entries"],
188
+ message: "active context requires entries and revoked context requires none",
189
+ });
190
+ }
191
+ const material = {
192
+ tenantId: context.tenantId,
193
+ target: context.target,
194
+ state: context.state,
195
+ revision: context.revision,
196
+ entries: context.entries,
197
+ };
198
+ if (context.contentHash !== certifiedContextContentHash(material)) {
199
+ refinement.addIssue({
200
+ code: "custom",
201
+ path: ["contentHash"],
202
+ message: "context content hash does not match the delivered context",
203
+ });
204
+ }
205
+ });
206
+ function deepFreeze(value) {
207
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
208
+ for (const child of Object.values(value)) {
209
+ deepFreeze(child);
210
+ }
211
+ Object.freeze(value);
212
+ }
213
+ return value;
214
+ }
215
+ const _certifiedContextDeliverySchemaMatches = true;
216
+ const _certifiedContextProvenanceSchemaMatches = true;
217
+ const _certifiedContextEntrySchemaMatches = true;
218
+ const _certifiedContextSchemaMatches = true;
219
+ void [
220
+ _certifiedContextDeliverySchemaMatches,
221
+ _certifiedContextProvenanceSchemaMatches,
222
+ _certifiedContextEntrySchemaMatches,
223
+ _certifiedContextSchemaMatches,
224
+ ];
225
+ /** Parse, clone, and recursively freeze one untrusted context response. */
226
+ export function parseCertifiedContext(value) {
227
+ return deepFreeze(certifiedContextSchema.parse(value));
228
+ }
package/dist/index.d.ts CHANGED
@@ -615,6 +615,7 @@ export * from "./agent-candidate.js";
615
615
  export * from "./agent-candidate-schema.js";
616
616
  export * from "./agent-candidate-promotion-schema.js";
617
617
  export * from "./agent-profile.js";
618
+ export * from "./certified-context.js";
618
619
  export * from "./profile-diff.js";
619
620
  export * from "./harness.js";
620
621
  export * from "./harness-capabilities.js";
package/dist/index.js CHANGED
@@ -128,6 +128,7 @@ export * from "./agent-candidate.js";
128
128
  export * from "./agent-candidate-schema.js";
129
129
  export * from "./agent-candidate-promotion-schema.js";
130
130
  export * from "./agent-profile.js";
131
+ export * from "./certified-context.js";
131
132
  export * from "./profile-diff.js";
132
133
  export * from "./harness.js";
133
134
  export * from "./harness-capabilities.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",