@aid-framework/ir 0.1.39 → 0.1.51

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.
Files changed (2) hide show
  1. package/README.md +151 -87
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,112 +1,122 @@
1
- # `@aid-framework/ir` — the intermediate representation
1
+ # `@aid-framework/ir`
2
2
 
3
- The IR is the one stack-independent, normalized, versioned representation of an app that every generator
4
- consumes. Generators never read the spec: `@aid-framework/ir` resolves it once, at build time, into something a
5
- generator can emit code from directly.
3
+ The intermediate representation of [AID Framework](https://github.com/aid-framework/aid-framework): the one
4
+ normalized, stack-independent, versioned form of an application that every code generator consumes.
6
5
 
7
- Layering is strictly one-directional. `@aid-framework/ir` imports `@aid-framework/spec`; `@aid-framework/spec` never imports `@aid-framework/ir`.
8
-
9
- ## IR v0.1
10
-
11
- `IR_VERSION` is `"0.1.0"` and `specVersion` is carried through from `@aid-framework/spec`, so a consumer can tell which
12
- spec revision produced an IR. The two are independent and neither implies the other.
13
-
14
- Every IR carries all 19 top-level keys of [`docs/design.md` §7.2](../../docs/design.md):
6
+ Generators never read a spec. This package resolves the spec **once** into an IR, so a Python target and a
7
+ TypeScript target are looking at the same program — the same names, the same defaults, the same resolved
8
+ references.
15
9
 
16
10
  ```text
17
- irVersion, specVersion, project, types, models, prompts, tools, retrievers, embeddings,
18
- stores, memory, agents, pipelines, evals, guardrails, deployments, observability, runtime,
19
- requiredCapabilities
11
+ spec.yaml ──buildIR──▶ IR ──runGates──▶ buildable ──▶ generator plan ──▶ code
20
12
  ```
21
13
 
22
- Phase 0 populates `types`, `models`, `prompts`, `tools`, `pipelines`, and `evals`. Phase 1 adds `stores` and
23
- `retrievers` from their spec sections and derives `embeddings` from the embedding models a spec declares.
14
+ ## Install
24
15
 
25
- ### Empty collections are a contract, not an omission
26
-
27
- Five collections are **present and empty by design**. They are `[]` rather than omitted or `undefined`, and
28
- the JSON Schema requires each of them, so a later phase can fill one in without touching a single consumer.
29
- An empty array is otherwise indistinguishable from "not implemented", so the two reasons are worth keeping
30
- apart:
31
-
32
- - `agents`, `guardrails`, `memory`, `deployments` — sections the spec **defers**. Layer 1 lists them in
33
- `DEFERRED_SECTIONS`, so no spec can populate them yet.
34
- - `embeddings` — not a spec section at all. It exists only in the IR: a spec declares embedding capacity by
35
- giving a model the `embeddings` capability and pointing a retriever at it, so the collection is derived
36
- from that content rather than written out by hand. Empty means "this app embeds nothing".
16
+ ```sh
17
+ npm install @aid-framework/ir
18
+ ```
37
19
 
38
- `observability` and `runtime` are objects rather than arrays, present with Phase 0 defaults: tracing on, and
39
- a runtime descriptor naming plain-async control flow.
20
+ Requires Node.js 24 or newer. Depends only on `@aid-framework/spec`.
40
21
 
41
- ## Building
22
+ ## Quick start
42
23
 
43
24
  ```ts
44
25
  import { checkSpecText, formatDiagnostics } from '@aid-framework/spec';
45
26
  import { buildIR, runGates, serializeIR } from '@aid-framework/ir';
46
27
 
47
- const checked = checkSpecText(source, { file: 'app.yaml' });
28
+ const checked = checkSpecText(source, { file: 'app/app.spec.yaml' });
48
29
  if (!checked.ok) throw new Error(formatDiagnostics(checked.diagnostics));
49
30
 
50
- const { ir, diagnostics } = buildIR(checked.value, { file: 'app.yaml' });
51
- const gates = runGates(ir, { file: 'app.yaml' });
31
+ const built = buildIR(checked.value, { file: 'app/app.spec.yaml' });
32
+ if (built.diagnostics.some((d) => d.severity === 'error')) {
33
+ throw new Error(formatDiagnostics(built.diagnostics));
34
+ }
52
35
 
36
+ const gates = runGates(built.ir, { file: 'app/app.spec.yaml' });
53
37
  if (!gates.ok) throw new Error(formatDiagnostics(gates.diagnostics));
54
- console.log(serializeIR(ir));
38
+
39
+ const canonical = serializeIR(built.ir);
55
40
  ```
56
41
 
57
- - `buildIR(value, { file? })` returns `{ ir, diagnostics }`. It resolves every `TypeRef` and every
58
- `model`/`prompt`/`tool` reference eagerly, materializes every default, and derives each stable identity
59
- from identity rather than content, so an unrelated edit never churns a generated diff.
60
- - `runGates(ir, { targetCapabilities?, file? })` returns `{ ok, diagnostics, suppressed, byGate }`. `ok` is
61
- false when any gate emitted an error-severity diagnostic.
62
- - `serializeIR(ir)` renders canonical JSON.
63
- - `deriveRequiredCapabilities(ir)` returns the capabilities the IR's own content requires, in vocabulary
64
- order. `runGates` sets `requiredCapabilities` from it before gating, so the value on the IR is the one the
65
- generator should read.
42
+ `buildIR` resolves every type, model, prompt and tool reference eagerly, materializes every default, and derives
43
+ each stable identity from identity rather than from content — so editing one prompt does not renumber an unrelated
44
+ pipeline.
66
45
 
67
- ## The six build-time gates
46
+ ## The IR
68
47
 
69
- All six run before code generation. They emit diagnostics, never exceptions, and use the same
70
- compiler-style shape as `@aid-framework/spec`, so a user sees one report whatever layer rejected the input. IR codes
71
- are namespaced `ir/*` and never reuse a `spec/*` code.
48
+ `IR_VERSION` is `"0.1.0"`. `specVersion` is carried through from the spec, so a consumer can always tell which
49
+ spec revision produced an IR. The two are independent: neither implies the other.
72
50
 
73
- | Gate | Rejects |
74
- | --- | --- |
75
- | `referential` | An unresolvable `TypeRef` or reference, a reference whose kind does not match (a `tool` step naming a prompt), prompt variables that do not match the template, an undefined or duplicated step binding, a malformed eval target, a `fallbacks` cycle |
76
- | `normalize` | A value left implicit for the generator to invent, a provider Phase 0 does not serve, params above a model's limits |
77
- | `capability` | A target that declares no capabilities, an unknown target, a capability outside the vocabulary, a required capability the target does not declare |
78
- | `cost` | A budget declared against an unpriced model. Warns when an estimate exceeds a budget, and when a model is unpriced but unbudgeted |
79
- | `security` | A `write`/`destructive` tool with no `auth`, a `destructive` tool without `requiresConfirmation`, an under-redacting trace config, an HTTP path that reaches a write with no `auth` |
80
- | `eval-coverage` | Nothing: warns when an HTTP-served pipeline has no `eval` with `gate.ci: true` |
51
+ An IR always carries all nineteen top-level keys, even when a collection is empty:
52
+
53
+ ```text
54
+ irVersion, specVersion, project, types, models, prompts, tools, retrievers, embeddings,
55
+ stores, memory, agents, pipelines, evals, guardrails, deployments, observability, runtime,
56
+ requiredCapabilities
57
+ ```
81
58
 
82
- **Suppression is explicit.** A check Phase 0 genuinely cannot enforce appears in `suppressed` with a stated
83
- reason rather than being skipped silently, because a skipped check is indistinguishable from a passing one.
84
- Phase 0 suppresses the guardrail rule: there is no guardrail runtime to enforce it against.
59
+ ### An empty collection is a contract
85
60
 
86
- ### Capability declarations are injected
61
+ An empty array here is not "missing" — it is a statement. The collections are present and required by the schema
62
+ even when empty, so a later version can fill one in without touching a single consumer:
87
63
 
88
- `@aid-framework/ir` must not import `packages/generator-sdk`, which does not exist yet, so a target's capability
89
- declaration arrives as a gate argument:
64
+ - `agents`, `guardrails`, `memory`, `deployments` — declared by the spec language but unused by this document.
65
+ - `embeddings` — not a spec section at all. A spec declares embedding capacity by giving a model the `embeddings`
66
+ capability and pointing a retriever at it, so this collection is **derived** from that content. Empty means the
67
+ app embeds nothing.
90
68
 
91
- ```ts
92
- runGates(ir, { targetCapabilities: { 'py-fastapi': ['http-trigger', 'json-schema', 'otel'] } });
93
- ```
69
+ `observability` and `runtime` are objects with defaults rather than arrays: tracing on, and a runtime descriptor
70
+ naming plain-async control flow.
94
71
 
95
- `src/catalog/` exports the Phase 0 reference declaration (`PHASE_0_TARGET_CAPABILITIES`) that the default
96
- uses. Layer 3 supplies the real declarations instead, and nothing else in this package changes.
72
+ ## The build-time gates
97
73
 
98
- ## Canonical serialization
74
+ Gates decide whether an IR is buildable, before a single byte of code is emitted. There are sixteen, run as one
75
+ report by `runGates`. All of them emit diagnostics rather than throwing, in the same compiler-style shape as
76
+ `@aid-framework/spec`, so a user gets one report whatever layer rejected the input.
99
77
 
100
- `serializeIR` is deterministic so that regeneration is byte-reproducible and golden files are stable: object
101
- keys sorted, LF endings, 2-space indent, exactly one trailing newline, no `NaN`/`Infinity`/`undefined`, and
102
- `undefined` fields omitted rather than written as `null`. Array order is preserved because an array's order
103
- is meaningful; an object's is not. Two spec documents that differ only in key order or whitespace produce
104
- byte-identical IR.
78
+ | Gate | Rejects |
79
+ | --- | --- |
80
+ | `referential` | An unresolvable type or reference, a reference of the wrong kind, prompt variables that do not match the template, an undefined or duplicated step binding, a malformed eval target, a fallback cycle |
81
+ | `normalize` | A value left implicit for the generator to invent, a provider this build does not serve, params above a model's limits |
82
+ | `capability` | A target declaring no capabilities, an unknown target, a capability outside the vocabulary, a required capability the target does not declare |
83
+ | `cost` | A budget declared against an unpriced model. Warns for an estimate above a budget, and for an unpriced, unbudgeted model |
84
+ | `security` | A write or destructive tool with no auth policy, a destructive tool without confirmation, an under-redacting trace config, an HTTP path reaching a write with no auth |
85
+ | `eval-coverage` | Nothing — warns when an HTTP-served pipeline has no CI-gating eval |
86
+ | `agents/refs-resolve` | An agent naming a tool, prompt or model that does not exist |
87
+ | `agents/handoff-cycles` | Transfer or delegation graphs that can re-enter an agent |
88
+ | `agents/budgets` | An agent budget that cannot be honoured |
89
+ | `agents/budget-degrade` | `onBudgetExhausted: degrade` with no strictly cheaper alias to step down to |
90
+ | `guardrails/coverage` | A guardrail not attached to an input or output it can act on |
91
+ | `guardrails/policies` | A guardrail policy that does not parse, or names an unknown builtin |
92
+ | `hitl/side-effects` | A human-in-the-loop pause guarding a side effect it cannot actually hold |
93
+ | `memory/refs` | A memory reference that does not resolve to a declared scope |
94
+ | `deploy/streaming` | A deployment declaring streaming the target cannot emit |
95
+ | `orchestration/deepagents` | An agent engine declaration the runtime cannot honour |
96
+
97
+ **Suppression is explicit.** A check this build genuinely cannot enforce appears in `suppressed` with a stated
98
+ reason rather than being skipped silently — because a skipped check and a passing check look identical from the
99
+ outside.
100
+
101
+ The capability set the gates check is derived from the IR's own content and written back onto
102
+ `requiredCapabilities`, so the value a generator reads is the value that was actually checked:
103
+
104
+ ```ts
105
+ const result = runGates(ir, {
106
+ targetCapabilities: {
107
+ 'py-fastapi': ['http-trigger', 'json-schema', 'otel'],
108
+ },
109
+ });
110
+
111
+ result.ok; // false when any gate emitted an error
112
+ result.byGate; // per-gate diagnostics
113
+ result.suppressed; // what was not enforced, and why
114
+ ```
105
115
 
106
116
  ## PolicyExpr
107
117
 
108
- `src/policy/` parses the policy mini-language used by `tool.auth` into a plain-JSON AST, so the AST can be
109
- embedded in the IR, serialized canonically, and evaluated by any target.
118
+ Tool auth policies are written in a small expression language. This package parses them into a plain-JSON AST, so
119
+ the AST can live in the IR, serialize canonically, and be evaluated by any target in any language.
110
120
 
111
121
  ```text
112
122
  or := and ('or' and)*
@@ -117,19 +127,73 @@ primary := 'true' | 'false' | builtin '(' args ')' | '(' or ')'
117
127
  builtin := hasRole | hasScope | isAuthenticated
118
128
  ```
119
129
 
120
- Parsing is a total function: `parsePolicyExpr(source, context?)` returns either an AST or position-bearing
121
- diagnostics, never a thrown exception. **Anything outside this grammar is a validation error**, including an
122
- unknown builtin. That is the fail-closed rule: a policy the runtime cannot evaluate must never be treated as
123
- `true`.
130
+ ```ts
131
+ import { parsePolicyExpr, formatPolicyExpr } from '@aid-framework/ir';
124
132
 
125
- ## Development
133
+ const parsed = parsePolicyExpr("hasRole('admin') or hasScope('tickets:write')");
134
+ if (parsed.ok) console.log(formatPolicyExpr(parsed.value));
135
+ ```
126
136
 
127
- ```sh
128
- pnpm build
129
- pnpm test
137
+ Parsing is a total function: it returns either an AST or positioned diagnostics, never a thrown exception.
138
+ Anything outside this grammar is a validation error, including an unknown builtin. That is the fail-closed rule —
139
+ a policy the runtime cannot evaluate must never be treated as `true`.
140
+
141
+ Eval predicates use a companion grammar with the same properties (`parsePredicateExpr`, `PREDICATE_BUILTINS`), so
142
+ a gate condition is as strict as an auth policy.
143
+
144
+ ## Canonical serialization
145
+
146
+ `serializeIR` is deterministic, so regeneration is byte-reproducible and golden files are stable:
147
+
148
+ - object keys sorted, LF endings, two-space indent, exactly one trailing newline;
149
+ - no `NaN`, `Infinity` or `undefined`; an absent field is omitted rather than written as `null`;
150
+ - array order preserved, because an array's order is meaningful and an object's is not.
151
+
152
+ Two documents that differ only in key order or whitespace therefore produce byte-identical IR.
153
+
154
+ ## Model catalog
155
+
156
+ The catalog is the build-time source of truth for model pricing and limits, used by the `cost` and `normalize`
157
+ gates.
158
+
159
+ ```ts
160
+ import {
161
+ CATALOG_VERSION, catalogCostProfile, catalogCostProfiles, catalogModel,
162
+ } from '@aid-framework/ir';
130
163
  ```
131
164
 
132
- Fixtures live in `test/fixtures/`, so this package's tests do not depend on `@aid-framework/spec`'s own fixtures.
133
- `test/__golden__/` holds the committed canonical IR for the full fixture, compared byte for byte.
165
+ The catalog JSON is also exported as `@aid-framework/ir/catalog/models.json`, and the IR schema as
166
+ `@aid-framework/ir/ir.schema.json`.
167
+
168
+ ## Capabilities
134
169
 
135
- Reference: [`docs/design.md` §7](../../docs/design.md).
170
+ ```ts
171
+ import {
172
+ DEFERRED_CAPABILITIES, MODEL_CAPABILITIES, REQUIRED_CAPABILITIES,
173
+ deriveRequiredCapabilities, isRequiredCapability,
174
+ } from '@aid-framework/ir';
175
+ ```
176
+
177
+ `deriveRequiredCapabilities(ir)` returns the capabilities the IR's own content requires, in vocabulary order.
178
+ `runGates` calls it before gating, so the gates and the generator never disagree about what is required.
179
+
180
+ ## API
181
+
182
+ | Export | Purpose |
183
+ | --- | --- |
184
+ | `buildIR(value, options?)` | Spec value → `{ ir, diagnostics }` |
185
+ | `runGates(ir, options?)` | Run all gates; returns `{ ok, diagnostics, suppressed, byGate }` |
186
+ | `GATES` | The gate registry, in execution order |
187
+ | `serializeIR(ir)`, `canonicalJson(value)` | Canonical JSON rendering |
188
+ | `IR_VERSION` | The IR shape version (`0.1.0`) |
189
+ | `IR_COLLECTIONS`, `IR_ONLY_COLLECTIONS` | The collection vocabulary |
190
+ | `deriveRequiredCapabilities(ir)` | The capabilities the content requires |
191
+ | `REQUIRED_CAPABILITIES`, `MODEL_CAPABILITIES`, `DEFERRED_CAPABILITIES` | The capability vocabularies |
192
+ | `parsePolicyExpr`, `formatPolicyExpr`, `POLICY_BUILTINS` | The tool-auth policy language |
193
+ | `parsePredicateExpr`, `formatPredicateExpr`, `PREDICATE_BUILTINS` | The eval predicate language |
194
+ | `catalogModel`, `catalogCostProfile`, `catalogCostProfiles`, `CATALOG_VERSION` | Model pricing and limits |
195
+
196
+ ## License
197
+
198
+ [Apache-2.0](https://github.com/aid-framework/aid-framework/blob/main/LICENSE). Code generated by AID Framework is
199
+ not subject to this license.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aid-framework/ir",
3
- "version": "0.1.39",
3
+ "version": "0.1.51",
4
4
  "description": "The AID Framework intermediate representation: IR v0.1 shapes, the spec-to-IR builder, six build-time gates, the PolicyExpr parser, and canonical JSON serialization.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@aid-framework/spec": "0.1.39"
35
+ "@aid-framework/spec": "0.1.51"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^26.6.5"