@irisrun/agent 0.1.0 → 0.2.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/README.md +35 -0
- package/dist/agentfile.d.ts +4 -1
- package/dist/agentfile.js +73 -4
- package/dist/bundle.js +5 -5
- package/dist/image.d.ts +2 -0
- package/dist/image.js +6 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lock.d.ts +1 -1
- package/dist/lock.js +3 -3
- package/dist/pin.d.ts +1 -1
- package/dist/pin.js +2 -2
- package/dist/resolver.d.ts +1 -1
- package/dist/resolver.js +1 -1
- package/dist/schema.js +20 -2
- package/dist/verify.js +5 -5
- package/dist/yaml.js +9 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @irisrun/agent
|
|
2
|
+
|
|
3
|
+
**The image toolchain that makes an agent a build artifact.** Parse an Agentfile,
|
|
4
|
+
resolve + embed + pin every contract by hash, and emit a content-addressed,
|
|
5
|
+
deterministic image — identical inputs produce an identical `imageDigest` — so a
|
|
6
|
+
session resumes from exactly the definition it was born under. This is the
|
|
7
|
+
library behind the `iris` CLI; host-side, zero external deps.
|
|
8
|
+
|
|
9
|
+
## What it is
|
|
10
|
+
|
|
11
|
+
`parseAgentfileYaml` / `parseAgentfileJson` + `validateAgentfile` turn an
|
|
12
|
+
Agentfile into a checked model. `buildImage` resolves and pins each tool/bundle
|
|
13
|
+
contract, embeds content by hash, validates the capability profile, and computes
|
|
14
|
+
`imageDigest = sha256(canonicalize(image-minus-digest))` — the canonical image
|
|
15
|
+
excludes its own self-referential digest, so the digest is reproducible
|
|
16
|
+
(`computeImageDigest`, `canonicalImageOf`). `writeOciLayout` / `readOciLayout`
|
|
17
|
+
serialize the image as a local, files-only OCI layout. `verifyImage` re-checks
|
|
18
|
+
every content hash, re-resolves every contract, and recomputes the digest —
|
|
19
|
+
throwing **loudly** on any drift, never silently. `latestRecord`,
|
|
20
|
+
`governingDigest`, and `migrateDefinition` pin a session to its image digest and
|
|
21
|
+
migrate a live session `from`→`to` at a turn boundary, with **zero** engine change.
|
|
22
|
+
|
|
23
|
+
## Use it
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
iris build --file ./my-agent/agent.yaml --out ./image
|
|
27
|
+
iris inspect ./image
|
|
28
|
+
iris verify ./image
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
See **[docs/Your first agent](../../docs/first-agent.md)** and
|
|
32
|
+
**[docs/Architecture](../../docs/architecture.md)**.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
Part of [Iris](../../README.md) — own, portable, verifiable state.
|
package/dist/agentfile.d.ts
CHANGED
|
@@ -27,7 +27,10 @@ export interface AgentfileModel {
|
|
|
27
27
|
workspace?: string;
|
|
28
28
|
network: string;
|
|
29
29
|
};
|
|
30
|
+
secrets?: string[];
|
|
31
|
+
environment?: Record<string, string>;
|
|
30
32
|
}
|
|
33
|
+
export declare function isEnvName(s: string): boolean;
|
|
31
34
|
/** Parse Agentfile JSON text into a validated model (throws loudly on bad JSON or shape). */
|
|
32
35
|
export declare function parseAgentfileJson(text: string): AgentfileModel;
|
|
33
36
|
/**
|
|
@@ -35,7 +38,7 @@ export declare function parseAgentfileJson(text: string): AgentfileModel;
|
|
|
35
38
|
* (shape, required fields) and enforces the content-vs-contract split: a tool /
|
|
36
39
|
* connection entry is REJECTED if it carries an inline-behavior field
|
|
37
40
|
* (code/script/source) or a ref whose scheme is not mcp/grpc/subprocess
|
|
38
|
-
* (
|
|
41
|
+
* (no behavior in the manifest). Throws loudly; never coerces.
|
|
39
42
|
*/
|
|
40
43
|
export declare function validateAgentfile(raw: unknown): AgentfileModel;
|
|
41
44
|
/** Content paths embedded by hash: instructions, skills, then sandbox.workspace (if any). */
|
package/dist/agentfile.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// A contract ref must use one of these schemes; anything else (incl. an inline
|
|
2
|
-
// `code`/`script`/`source` field) is inlined behavior and is rejected
|
|
2
|
+
// `code`/`script`/`source` field) is inlined behavior and is rejected.
|
|
3
3
|
const CONTRACT_SCHEMES = ["mcp", "grpc", "subprocess"];
|
|
4
4
|
const INLINE_BEHAVIOR_FIELDS = ["code", "script", "source"];
|
|
5
5
|
// `requires` (the capability profile) is strict-when-present so the runtime
|
|
@@ -8,6 +8,14 @@ const INLINE_BEHAVIOR_FIELDS = ["code", "script", "source"];
|
|
|
8
8
|
// boolean. (Initiative 20260620-agentfile-schema — these were untyped before.)
|
|
9
9
|
const TOOL_LOCALITIES = ["in-process", "local", "remote"];
|
|
10
10
|
const BOOLEAN_CAPS = ["long_running", "local_subprocess", "filesystem", "websockets"];
|
|
11
|
+
// A POSIX-style environment-variable NAME: a letter/underscore then letters/
|
|
12
|
+
// digits/underscores. Used for both `secrets` entries and `environment` keys here
|
|
13
|
+
// AND re-exported (index.ts) so the CLI env resolver enforces the SAME shape —
|
|
14
|
+
// authoring-time and runtime agree on what a valid name is.
|
|
15
|
+
const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16
|
+
export function isEnvName(s) {
|
|
17
|
+
return ENV_NAME_RE.test(s);
|
|
18
|
+
}
|
|
11
19
|
/** Parse Agentfile JSON text into a validated model (throws loudly on bad JSON or shape). */
|
|
12
20
|
export function parseAgentfileJson(text) {
|
|
13
21
|
let raw;
|
|
@@ -24,7 +32,7 @@ export function parseAgentfileJson(text) {
|
|
|
24
32
|
* (shape, required fields) and enforces the content-vs-contract split: a tool /
|
|
25
33
|
* connection entry is REJECTED if it carries an inline-behavior field
|
|
26
34
|
* (code/script/source) or a ref whose scheme is not mcp/grpc/subprocess
|
|
27
|
-
* (
|
|
35
|
+
* (no behavior in the manifest). Throws loudly; never coerces.
|
|
28
36
|
*/
|
|
29
37
|
export function validateAgentfile(raw) {
|
|
30
38
|
const o = asObject(raw, "Agentfile");
|
|
@@ -71,6 +79,17 @@ export function validateAgentfile(raw) {
|
|
|
71
79
|
throw new Error("Agentfile: sandbox.workspace must be a string");
|
|
72
80
|
sandboxOut.workspace = sandbox.workspace;
|
|
73
81
|
}
|
|
82
|
+
// Env/secrets — OPTIONAL; validated strictly, OMITTED when absent (canonicalize
|
|
83
|
+
// throws on undefined AND omission keeps existing digests byte-identical).
|
|
84
|
+
const secretsOut = validateSecrets(o.secrets);
|
|
85
|
+
const environmentOut = validateEnvironment(o.environment);
|
|
86
|
+
if (secretsOut !== undefined && environmentOut !== undefined) {
|
|
87
|
+
for (const name of secretsOut) {
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(environmentOut, name)) {
|
|
89
|
+
throw new Error(`Agentfile: "${name}" is declared as both a secret and an environment literal — a name must be one or the other`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
74
93
|
return {
|
|
75
94
|
apiVersion: "iris/v1",
|
|
76
95
|
kind: "Agent",
|
|
@@ -83,8 +102,58 @@ export function validateAgentfile(raw) {
|
|
|
83
102
|
harness: harnessOut,
|
|
84
103
|
requires: requires,
|
|
85
104
|
sandbox: sandboxOut,
|
|
105
|
+
...(secretsOut !== undefined ? { secrets: secretsOut } : {}),
|
|
106
|
+
...(environmentOut !== undefined ? { environment: environmentOut } : {}),
|
|
86
107
|
};
|
|
87
108
|
}
|
|
109
|
+
// Validate the OPTIONAL `secrets` (NAMES of required runtime secrets). Returns the
|
|
110
|
+
// validated array, or undefined when absent (omitted from the model → digest-stable).
|
|
111
|
+
// Order is author-significant and digest-affecting — NOT sorted.
|
|
112
|
+
function validateSecrets(v) {
|
|
113
|
+
if (v === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (!Array.isArray(v)) {
|
|
116
|
+
throw new Error('Agentfile: "secrets" must be an array of environment-variable names');
|
|
117
|
+
}
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
for (const entry of v) {
|
|
120
|
+
if (typeof entry !== "string" || !isEnvName(entry)) {
|
|
121
|
+
throw new Error(`Agentfile: secrets[] entry ${JSON.stringify(entry)} is not a valid env-var name (a letter/underscore then letters/digits/underscores)`);
|
|
122
|
+
}
|
|
123
|
+
if (seen.has(entry)) {
|
|
124
|
+
throw new Error(`Agentfile: duplicate secret name "${entry}"`);
|
|
125
|
+
}
|
|
126
|
+
seen.add(entry);
|
|
127
|
+
}
|
|
128
|
+
return v;
|
|
129
|
+
}
|
|
130
|
+
// Validate the OPTIONAL `environment` (literal NON-secret config). Scalar values
|
|
131
|
+
// (string/number/boolean) are coerced to strings HERE — env vars are inherently
|
|
132
|
+
// strings, so JSON `"3"`, YAML `3`, and YAML `"3"` all produce the SAME model and
|
|
133
|
+
// therefore the SAME imageDigest. null/object/array values are rejected loudly.
|
|
134
|
+
// Returns the coerced map, or undefined when absent.
|
|
135
|
+
function validateEnvironment(v) {
|
|
136
|
+
if (v === undefined)
|
|
137
|
+
return undefined;
|
|
138
|
+
const o = asObject(v, "environment");
|
|
139
|
+
const out = {};
|
|
140
|
+
for (const [key, val] of Object.entries(o)) {
|
|
141
|
+
if (!isEnvName(key)) {
|
|
142
|
+
throw new Error(`Agentfile: environment key ${JSON.stringify(key)} is not a valid env-var name (a letter/underscore then letters/digits/underscores)`);
|
|
143
|
+
}
|
|
144
|
+
if (typeof val === "string") {
|
|
145
|
+
out[key] = val;
|
|
146
|
+
}
|
|
147
|
+
else if (typeof val === "number" || typeof val === "boolean") {
|
|
148
|
+
out[key] = String(val);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const kind = val === null ? "null" : Array.isArray(val) ? "array" : typeof val;
|
|
152
|
+
throw new Error(`Agentfile: environment.${key} must be a string (got ${kind})`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
88
157
|
// Strict-when-present validation of the capability profile (`requires`), so the
|
|
89
158
|
// runtime agrees with the published JSON schema. Unknown keys are still ignored
|
|
90
159
|
// (retained in the model) — only the KNOWN fields are type-checked when present.
|
|
@@ -119,7 +188,7 @@ function requireStringArray(o, key) {
|
|
|
119
188
|
}
|
|
120
189
|
return v;
|
|
121
190
|
}
|
|
122
|
-
// Validate a tools/connections array, rejecting inlined behavior
|
|
191
|
+
// Validate a tools/connections array, rejecting inlined behavior.
|
|
123
192
|
function requireRefArray(o, key) {
|
|
124
193
|
const v = o[key];
|
|
125
194
|
if (!Array.isArray(v)) {
|
|
@@ -129,7 +198,7 @@ function requireRefArray(o, key) {
|
|
|
129
198
|
const e = asObject(entry, `${key}[${i}]`);
|
|
130
199
|
for (const field of INLINE_BEHAVIOR_FIELDS) {
|
|
131
200
|
if (field in e) {
|
|
132
|
-
throw new Error(`Agentfile: ${key}[${i}] carries inline behavior ("${field}") — tools are contracts referenced by digest, not inlined code
|
|
201
|
+
throw new Error(`Agentfile: ${key}[${i}] carries inline behavior ("${field}") — tools are contracts referenced by digest, not inlined code`);
|
|
133
202
|
}
|
|
134
203
|
}
|
|
135
204
|
const ref = e.ref;
|
package/dist/bundle.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Bundle resolution + digest
|
|
2
|
-
//
|
|
1
|
+
// Bundle resolution + digest — the strengthening of the
|
|
2
|
+
// tactic pin. A tactic BUNDLE (e.g. `@irisrun/bundle-coding`) is distributed like
|
|
3
3
|
// a tool contract: an Agentfile `harness.bundle` ref (e.g. "iris/coding@^1")
|
|
4
|
-
// resolves — via an injected BundleResolver, mirroring the
|
|
4
|
+
// resolves — via an injected BundleResolver, mirroring the RegistryResolver —
|
|
5
5
|
// to a concrete BundleDefinition, and the image pins `bundleDigest(def)` (a real
|
|
6
|
-
// content digest over the BEHAVIOR SURFACE) instead of the
|
|
6
|
+
// content digest over the BEHAVIOR SURFACE) instead of the sha256Hex(id)
|
|
7
7
|
// placeholder. The digest is STABLE across a floating `location` (re-resolve by
|
|
8
8
|
// stable ref, not by location — [[lrn-resolve-by-stable-ref-not-floating-location]])
|
|
9
9
|
// yet detects any change to the behavior surface (id/version/seams/...).
|
|
@@ -13,7 +13,7 @@ import { canonicalize } from "@irisrun/core";
|
|
|
13
13
|
// The behavior surface the digest is computed over: the full definition MINUS the
|
|
14
14
|
// floating `location` (and any undefined fields, which canonicalize rejects). Two
|
|
15
15
|
// definitions that differ only in `location` produce the SAME surface → the SAME
|
|
16
|
-
// digest (the
|
|
16
|
+
// digest (the float-impl property); any behavior-surface change differs.
|
|
17
17
|
function behaviorSurface(def) {
|
|
18
18
|
const surface = {};
|
|
19
19
|
for (const [k, v] of Object.entries(def)) {
|
package/dist/image.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface ImageInspection {
|
|
|
34
34
|
digest: string;
|
|
35
35
|
}>;
|
|
36
36
|
capabilities: CapabilityProfile;
|
|
37
|
+
secrets?: string[];
|
|
38
|
+
environment?: Record<string, string>;
|
|
37
39
|
}
|
|
38
40
|
/** Human-readable resolved intent of an image (what `iris inspect` prints). */
|
|
39
41
|
export declare function inspectImage(image: AgentImage): ImageInspection;
|
package/dist/image.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Image build
|
|
1
|
+
// Image build: resolve + pin → embed content by hash → compute the
|
|
2
2
|
// content-addressed, deterministic imageDigest = sha256(canonicalize(canonical
|
|
3
3
|
// image)). The canonical image EXCLUDES the self-referential imageDigest field;
|
|
4
4
|
// content values are base64 STRINGS (canonicalize rejects Buffer/Uint8Array) and
|
|
@@ -60,7 +60,7 @@ export async function buildImage(model, opts) {
|
|
|
60
60
|
if (model.harness.bundle !== undefined) {
|
|
61
61
|
const id = model.harness.bundle;
|
|
62
62
|
if (opts.resolveBundle !== undefined) {
|
|
63
|
-
//
|
|
63
|
+
// Resolve the bundle ref to a definition and pin its REAL content digest.
|
|
64
64
|
// The id stays the STABLE Agentfile ref (re-resolved by verify, location floats).
|
|
65
65
|
const def = await opts.resolveBundle.resolve(id);
|
|
66
66
|
if (def === null) {
|
|
@@ -69,7 +69,7 @@ export async function buildImage(model, opts) {
|
|
|
69
69
|
tactics.bundle = { id, digest: bundleDigest(def) };
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
// Back-compat
|
|
72
|
+
// Back-compat: keep the sha256Hex(id) placeholder unchanged.
|
|
73
73
|
tactics.bundle = { id, digest: sha256Hex(id) };
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -100,9 +100,11 @@ export function inspectImage(image) {
|
|
|
100
100
|
content: image.lock.content,
|
|
101
101
|
tactics: image.lock.tactics,
|
|
102
102
|
capabilities: image.lock.capabilities,
|
|
103
|
+
...(image.agentfile.secrets !== undefined ? { secrets: image.agentfile.secrets } : {}),
|
|
104
|
+
...(image.agentfile.environment !== undefined ? { environment: image.agentfile.environment } : {}),
|
|
103
105
|
};
|
|
104
106
|
}
|
|
105
|
-
// --- OCI image layout (local, files-only
|
|
107
|
+
// --- OCI image layout (local, files-only) -------------------------
|
|
106
108
|
// Real registry push/pull (+ cosign) is the manual smoke; this is the install-free
|
|
107
109
|
// path. Shape: `oci-layout` + `index.json` + `blobs/sha256/<hex>`.
|
|
108
110
|
const IRIS_IMAGE_MEDIA_TYPE = "application/vnd.iris.agent.image+json";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const PACKAGE = "@irisrun/agent";
|
|
2
|
-
export { parseAgentfileJson, validateAgentfile, contentPaths, contractRefs, refScheme, } from "./agentfile.js";
|
|
2
|
+
export { parseAgentfileJson, validateAgentfile, contentPaths, contractRefs, refScheme, isEnvName, } from "./agentfile.js";
|
|
3
3
|
export type { AgentfileModel, CapabilityProfile, ToolRef, } from "./agentfile.js";
|
|
4
4
|
export { parseAgentfileYaml, parseYamlValue } from "./yaml.js";
|
|
5
5
|
export { AGENTFILE_SCHEMA, agentfileSchemaJson, checkAgainstSchema } from "./schema.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @irisrun/agent — public surface (host-side; zero external deps).
|
|
2
2
|
export const PACKAGE = "@irisrun/agent";
|
|
3
|
-
export { parseAgentfileJson, validateAgentfile, contentPaths, contractRefs, refScheme, } from "./agentfile.js";
|
|
3
|
+
export { parseAgentfileJson, validateAgentfile, contentPaths, contractRefs, refScheme, isEnvName, } from "./agentfile.js";
|
|
4
4
|
export { parseAgentfileYaml, parseYamlValue } from "./yaml.js";
|
|
5
5
|
export { AGENTFILE_SCHEMA, agentfileSchemaJson, checkAgainstSchema } from "./schema.js";
|
|
6
6
|
export { makeLocalResolver, refBase } from "./resolver.js";
|
package/dist/lock.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface Lock {
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function resolveLockTools(refs: ToolRef[], resolver: RegistryResolver): Promise<LockTool[]>;
|
|
33
33
|
/**
|
|
34
|
-
* Validate the capability profile against the resolved tools
|
|
34
|
+
* Validate the capability profile against the resolved tools,
|
|
35
35
|
* loudly (no silent inconsistency): a `remote` locality forbids subprocess tools,
|
|
36
36
|
* and any subprocess tool requires `local_subprocess: true`.
|
|
37
37
|
*/
|
package/dist/lock.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The lockfile
|
|
1
|
+
// The lockfile — pins model + tools/connections (by contractDigest) +
|
|
2
2
|
// tactics + capabilities + the embedded content hashes. `imageDigest` is filled by
|
|
3
|
-
// the image build
|
|
3
|
+
// the image build. This module owns the tool-resolution + pinning + the
|
|
4
4
|
// capability validation; Task 4 completes content/model/tactics. Host-side.
|
|
5
5
|
import { contractDigest } from "@irisrun/tools";
|
|
6
6
|
/**
|
|
@@ -31,7 +31,7 @@ export async function resolveLockTools(refs, resolver) {
|
|
|
31
31
|
return out;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Validate the capability profile against the resolved tools
|
|
34
|
+
* Validate the capability profile against the resolved tools,
|
|
35
35
|
* loudly (no silent inconsistency): a `remote` locality forbids subprocess tools,
|
|
36
36
|
* and any subprocess tool requires `local_subprocess: true`.
|
|
37
37
|
*/
|
package/dist/pin.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface MigrateOptions {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Migrate a LIVE session's pinned definition `from`→`to` at a turn boundary
|
|
25
|
-
* (
|
|
25
|
+
* (hold-and-migrate). Appends an `upgraded {from,to,atTurn}` marker
|
|
26
26
|
* stamped with `defDigest: to`; subsequent turns run with `defDigest = to`. Refuses
|
|
27
27
|
* LOUDLY when the session has not started, is mid-turn, or its governing digest is
|
|
28
28
|
* not `from`. `atTurn` = the boundary journal sequence (the engine emits no
|
package/dist/pin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Session pinning + definition migration
|
|
1
|
+
// Session pinning + definition migration — ZERO engine
|
|
2
2
|
// change. A session pins the image digest as its governing `defDigest` (the engine
|
|
3
3
|
// already stamps every record). Pinning/migration ride the EXISTING per-record
|
|
4
4
|
// defDigest + the `upgraded` marker; this module uses only the StateStore port +
|
|
@@ -32,7 +32,7 @@ export async function governingDigest(store, sessionId) {
|
|
|
32
32
|
const TURN_TERMINAL_MARKERS = new Set(["wait", "finish"]);
|
|
33
33
|
/**
|
|
34
34
|
* Migrate a LIVE session's pinned definition `from`→`to` at a turn boundary
|
|
35
|
-
* (
|
|
35
|
+
* (hold-and-migrate). Appends an `upgraded {from,to,atTurn}` marker
|
|
36
36
|
* stamped with `defDigest: to`; subsequent turns run with `defDigest = to`. Refuses
|
|
37
37
|
* LOUDLY when the session has not started, is mid-turn, or its governing digest is
|
|
38
38
|
* not `from`. `atTurn` = the boundary journal sequence (the engine emits no
|
package/dist/resolver.d.ts
CHANGED
|
@@ -7,6 +7,6 @@ export declare function refBase(ref: string): string;
|
|
|
7
7
|
/**
|
|
8
8
|
* A resolver over an in-memory `refBase → ToolContract` map (install-free). Both
|
|
9
9
|
* `mcp://r/x@^2` and `mcp://r/x@^3` resolve via the shared base `mcp://r/x`,
|
|
10
|
-
* modelling "pin the contract, float the implementation"
|
|
10
|
+
* modelling "pin the contract, float the implementation".
|
|
11
11
|
*/
|
|
12
12
|
export declare function makeLocalResolver(map: Record<string, ToolContract>): RegistryResolver;
|
package/dist/resolver.js
CHANGED
|
@@ -7,7 +7,7 @@ export function refBase(ref) {
|
|
|
7
7
|
/**
|
|
8
8
|
* A resolver over an in-memory `refBase → ToolContract` map (install-free). Both
|
|
9
9
|
* `mcp://r/x@^2` and `mcp://r/x@^3` resolve via the shared base `mcp://r/x`,
|
|
10
|
-
* modelling "pin the contract, float the implementation"
|
|
10
|
+
* modelling "pin the contract, float the implementation".
|
|
11
11
|
*/
|
|
12
12
|
export function makeLocalResolver(map) {
|
|
13
13
|
return {
|
package/dist/schema.js
CHANGED
|
@@ -17,7 +17,7 @@ export const AGENTFILE_SCHEMA = {
|
|
|
17
17
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
18
18
|
$id: "https://iris.run/schema/agentfile/v1.json",
|
|
19
19
|
title: "Iris Agentfile (iris/v1, kind Agent)",
|
|
20
|
-
description: "An Iris Agentfile: a declarative RECIPE that references content (embedded by hash) and tool/connection contracts (pinned by digest). It carries NO executable behavior
|
|
20
|
+
description: "An Iris Agentfile: a declarative RECIPE that references content (embedded by hash) and tool/connection contracts (pinned by digest). It carries NO executable behavior.",
|
|
21
21
|
type: "object",
|
|
22
22
|
required: [
|
|
23
23
|
"apiVersion",
|
|
@@ -66,6 +66,24 @@ export const AGENTFILE_SCHEMA = {
|
|
|
66
66
|
harness: { $ref: "#/$defs/harness" },
|
|
67
67
|
requires: { $ref: "#/$defs/capabilityProfile" },
|
|
68
68
|
sandbox: { $ref: "#/$defs/sandbox" },
|
|
69
|
+
// Env/secrets (initiative 20260620-agentfile-env-secrets). `secrets` = NAMES
|
|
70
|
+
// of required runtime secrets (VALUES are supplied at run time, never in the
|
|
71
|
+
// manifest). `environment` = literal NON-secret config defaults.
|
|
72
|
+
// AGREEMENT-CORPUS FOOTGUN: the zero-dep checker can express only what is
|
|
73
|
+
// below — secrets is an array of pattern-valid strings, and environment is an
|
|
74
|
+
// object. It CANNOT express secrets uniqueness, secrets/environment overlap,
|
|
75
|
+
// environment KEY patterns, or environment VALUE typing. Those are runtime-only
|
|
76
|
+
// (validateAgentfile) and must NOT be added to the T3 agreement corpus — the
|
|
77
|
+
// schema accepts them, so a corpus entry would make the two surfaces disagree.
|
|
78
|
+
secrets: {
|
|
79
|
+
type: "array",
|
|
80
|
+
items: { type: "string", minLength: 1, pattern: "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
81
|
+
description: "Names of required runtime secrets — values are supplied at run time, never stored in the manifest/image.",
|
|
82
|
+
},
|
|
83
|
+
environment: {
|
|
84
|
+
type: "object",
|
|
85
|
+
description: "Literal NON-secret config defaults (string values). Object-ness is the shared constraint with the runtime; value coercion/typing is runtime-only.",
|
|
86
|
+
},
|
|
69
87
|
},
|
|
70
88
|
additionalProperties: true,
|
|
71
89
|
$defs: {
|
|
@@ -79,7 +97,7 @@ export const AGENTFILE_SCHEMA = {
|
|
|
79
97
|
pattern: "^(mcp|grpc|subprocess)://",
|
|
80
98
|
description: "Contract ref. Scheme must be mcp, grpc, or subprocess.",
|
|
81
99
|
},
|
|
82
|
-
//
|
|
100
|
+
// A tool/connection is a contract referenced by digest, never
|
|
83
101
|
// inlined code. A present code/script/source field validates against the
|
|
84
102
|
// boolean `false` subschema → rejected.
|
|
85
103
|
code: false,
|
package/dist/verify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Integrity verification
|
|
1
|
+
// Integrity verification — throws LOUDLY on any failure
|
|
2
2
|
// (no silent corruption, [[lrn-no-silent-policy-widening]]). Checks, in order:
|
|
3
3
|
// (1) every embedded content hash matches its bytes; (2) every tool contractDigest
|
|
4
4
|
// is still resolvable + unchanged; (3) the recomputed imageDigest equals the
|
|
@@ -20,7 +20,7 @@ export async function verifyImage(image, opts) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
// 2. every tool contract is still resolvable (BY ITS STABLE ref — not location,
|
|
23
|
-
// which floats
|
|
23
|
+
// which floats) and its digest unchanged
|
|
24
24
|
for (const tool of image.lock.tools) {
|
|
25
25
|
const contract = await opts.resolver.resolve(tool.ref);
|
|
26
26
|
if (contract === null) {
|
|
@@ -37,11 +37,11 @@ export async function verifyImage(image, opts) {
|
|
|
37
37
|
if (recomputed !== image.lock.imageDigest) {
|
|
38
38
|
throw new Error(`verify: imageDigest mismatch — stored ${image.lock.imageDigest}, recomputed ${recomputed}`);
|
|
39
39
|
}
|
|
40
|
-
// 4. (
|
|
41
|
-
// a floating location
|
|
40
|
+
// 4. (NET-NEW) re-resolve the pinned tactic bundle by its STABLE id/ref (NOT
|
|
41
|
+
// a floating location), recompute bundleDigest, and assert it equals
|
|
42
42
|
// the pinned digest. This catches a CONTENT-tampered bundle whose pinned lock
|
|
43
43
|
// digest is left unchanged — invisible to the imageDigest check above. Skipped
|
|
44
|
-
// entirely when no resolveBundle is injected (
|
|
44
|
+
// entirely when no resolveBundle is injected (back-compat).
|
|
45
45
|
if (opts.resolveBundle !== undefined) {
|
|
46
46
|
const pinned = image.lock.tactics.bundle;
|
|
47
47
|
if (pinned !== undefined) {
|
package/dist/yaml.js
CHANGED
|
@@ -125,8 +125,16 @@ function looksScalar(item) {
|
|
|
125
125
|
return !/^[A-Za-z0-9_.-]+:\s/.test(item) && !/^[A-Za-z0-9_.-]+:$/.test(item);
|
|
126
126
|
}
|
|
127
127
|
function parseScalar(s, lineNo) {
|
|
128
|
+
// The ONLY supported flow collections are the EMPTY literals `[]` and `{}` — so a
|
|
129
|
+
// no-skills / no-connections agent (very common) is authorable in YAML, which the
|
|
130
|
+
// block `- ` / `key:` syntax cannot express. NON-empty flow stays rejected (no
|
|
131
|
+
// general flow support). Initiative 20260620-agentfile-env-secrets.
|
|
132
|
+
if (s === "[]")
|
|
133
|
+
return [];
|
|
134
|
+
if (s === "{}")
|
|
135
|
+
return {};
|
|
128
136
|
if (s.startsWith("[") || s.startsWith("{")) {
|
|
129
|
-
throw new Error(`Agentfile YAML: flow collections ([..]/{..}) are unsupported (line ${lineNo})`);
|
|
137
|
+
throw new Error(`Agentfile YAML: non-empty flow collections ([..]/{..}) are unsupported — only the empty literals [] and {} (line ${lineNo})`);
|
|
130
138
|
}
|
|
131
139
|
if (s.startsWith("&") || s.startsWith("*")) {
|
|
132
140
|
throw new Error(`Agentfile YAML: anchors/aliases (&/*) are unsupported (line ${lineNo})`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irisrun/agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Iris agent image toolchain — Agentfile model + parsers, builder (resolve/embed/pin/validate), content-addressed inspectable image + OCI layout, lockfile, integrity verify, and session pinning + definition migration. Host-side, zero external deps.",
|
|
6
6
|
"exports": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@irisrun/core": "^0.
|
|
15
|
-
"@irisrun/tools": "^0.
|
|
14
|
+
"@irisrun/core": "^0.2.0",
|
|
15
|
+
"@irisrun/tools": "^0.2.0"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"engines": {
|