@aid-framework/ir 0.1.39 → 0.1.48
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 +151 -87
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,112 +1,122 @@
|
|
|
1
|
-
# `@aid-framework/ir`
|
|
1
|
+
# `@aid-framework/ir`
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
`retrievers` from their spec sections and derives `embeddings` from the embedding models a spec declares.
|
|
14
|
+
## Install
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
51
|
-
|
|
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
|
-
|
|
38
|
+
|
|
39
|
+
const canonical = serializeIR(built.ir);
|
|
55
40
|
```
|
|
56
41
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
46
|
+
## The IR
|
|
68
47
|
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
uses. Layer 3 supplies the real declarations instead, and nothing else in this package changes.
|
|
72
|
+
## The build-time gates
|
|
97
73
|
|
|
98
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
`
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
133
|
+
const parsed = parsePolicyExpr("hasRole('admin') or hasScope('tickets:write')");
|
|
134
|
+
if (parsed.ok) console.log(formatPolicyExpr(parsed.value));
|
|
135
|
+
```
|
|
126
136
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.48",
|
|
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.
|
|
35
|
+
"@aid-framework/spec": "0.1.48"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^26.6.5"
|