@cynodia/axiom 0.8.2-alpha.1 → 0.9.0-alpha.2
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/AGENTS.md +68 -0
- package/README.md +71 -25
- package/docs/ACTIONS_TRANSACTIONS.md +35 -1
- package/docs/AGENT_API.md +28 -1
- package/docs/AGENT_REFERENCE.md +137 -3
- package/docs/ANTI_PATTERNS.md +95 -1
- package/docs/AUTHORITY.md +24 -4
- package/docs/CONSTRAINTS.md +1 -1
- package/docs/EFFECTS.md +4 -1
- package/docs/EVENTS.md +5 -2
- package/docs/EXPRESSIONS.md +1 -1
- package/docs/GRAPH_MODEL.md +1 -1
- package/docs/INTEGRATIONS.md +5 -1
- package/docs/LOCATIONS.md +1 -1
- package/docs/PRESENTATION.md +1 -1
- package/docs/RUNTIME.md +3 -1
- package/docs/SEMANTIC_CONTRACT.md +1 -1
- package/docs/STATE.md +1 -1
- package/docs/STORAGE.md +253 -0
- package/docs/SUBSCRIPTIONS.md +315 -0
- package/docs/TRIGGERS.md +7 -1
- package/docs/UI.md +5 -3
- package/docs/VALIDATION.md +14 -1
- package/llms.txt +65 -0
- package/package.json +7 -5
package/AGENTS.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Instructions for AI coding agents
|
|
2
|
+
|
|
3
|
+
You are consuming **Axiom** as an application framework. This file is routing only; the
|
|
4
|
+
contract lives in `docs/`.
|
|
5
|
+
|
|
6
|
+
## Read this first
|
|
7
|
+
|
|
8
|
+
**Read [`docs/AGENT_REFERENCE.md`](docs/AGENT_REFERENCE.md), in full, before writing any
|
|
9
|
+
application code.** It is the compressed operational contract for application authors and it
|
|
10
|
+
ships inside this package.
|
|
11
|
+
|
|
12
|
+
Then escalate in this order, and only as far as the question requires:
|
|
13
|
+
|
|
14
|
+
1. **`docs/AGENT_REFERENCE.md`** — the contract. Start here, every time.
|
|
15
|
+
2. **The `.d.ts` declarations in `dist/`** — the API contract. Signatures, unions and
|
|
16
|
+
branded types are authoritative there.
|
|
17
|
+
3. **The focused document in `docs/`** for the topic — the full contract for one area. See
|
|
18
|
+
the map in [`README.md`](README.md#documentation-map).
|
|
19
|
+
4. **A minimal public-API probe** — build the smallest graph that isolates the question,
|
|
20
|
+
call `validateGraph` and read the returned codes. Diagnostics are structured and name
|
|
21
|
+
what is wrong.
|
|
22
|
+
|
|
23
|
+
Only if all four leave the question open is reading framework implementation source
|
|
24
|
+
justified. That is framework debugging, not application authoring.
|
|
25
|
+
|
|
26
|
+
## Do not
|
|
27
|
+
|
|
28
|
+
- **Do not clone or reverse-engineer the Axiom repository to learn normal usage.** Everything
|
|
29
|
+
needed to author an application is in this package. There is no published Axiom CLI.
|
|
30
|
+
- **Do not search the web or scrape npm for documentation.** This package is the primary
|
|
31
|
+
source, and it describes this exact version.
|
|
32
|
+
- **Do not read, edit or patch generated output.** The emitted JavaScript, HTML and CSS are
|
|
33
|
+
build products. Nothing is authored there.
|
|
34
|
+
- **Do not guess the API from React, Vue, Angular, Svelte or Express conventions.** Axiom has
|
|
35
|
+
no component, no hook, no JSX, no route handler, no ORM and no callback anywhere in the
|
|
36
|
+
graph. Guessing from those conventions produces graphs `validateGraph` rejects.
|
|
37
|
+
- **Do not reach for an escape hatch.** There is no `formatter: fn`, no `validator: fn`, no
|
|
38
|
+
raw-CSS channel and no stored closure. If a capability seems missing, it is expressed as a
|
|
39
|
+
graph node; look it up rather than working around it.
|
|
40
|
+
|
|
41
|
+
## Prefer canonical Axiom semantics
|
|
42
|
+
|
|
43
|
+
Each row is a pointer, not the rule. The rule itself lives in
|
|
44
|
+
[`docs/AGENT_REFERENCE.md`](docs/AGENT_REFERENCE.md) and in the topic document; read it
|
|
45
|
+
there rather than treating this table as a specification.
|
|
46
|
+
|
|
47
|
+
| Instead of | Use |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| Mutating an object an expression returned | A `Location`, addressing the writable position |
|
|
50
|
+
| A field name key in a record | The `FieldId` — runtime records are keyed by field id |
|
|
51
|
+
| A hand-written validation function | A `ConstraintDef` or `TransitionConstraintDef` |
|
|
52
|
+
| A hidden control to forbid an operation | A guard or a transition constraint. `hidden` is not `forbidden` |
|
|
53
|
+
| CSS, a colour or a length | A presentation role or token, and a `Theme` |
|
|
54
|
+
| A route handler, controller or SQL statement | `StateDef.authority` plus an `ActionDef` |
|
|
55
|
+
| A callback for new capability | The graph node that expresses it |
|
|
56
|
+
|
|
57
|
+
## When something fails
|
|
58
|
+
|
|
59
|
+
`validateGraph` and the runtime both report structured diagnostics with a code and a path.
|
|
60
|
+
Match on the code, never on the message, and look the code up:
|
|
61
|
+
[`docs/VALIDATION.md`](docs/VALIDATION.md) for authoring-time codes,
|
|
62
|
+
[`docs/RUNTIME.md`](docs/RUNTIME.md) for runtime codes. A construct that validates and then
|
|
63
|
+
does nothing is a framework defect, not something to work around — report it.
|
|
64
|
+
|
|
65
|
+
Mistakes that compile but are wrong are collected in
|
|
66
|
+
[`docs/ANTI_PATTERNS.md`](docs/ANTI_PATTERNS.md). Read it before the **first** attempt, not the
|
|
67
|
+
second: collection nulls, how a `repeat` binds its current item, and addressing a collection
|
|
68
|
+
member by identity rather than index all shape a first draft rather than repairing it.
|
package/README.md
CHANGED
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
# Axiom
|
|
2
2
|
|
|
3
|
-
AI-native semantic application framework.
|
|
3
|
+
AI-native semantic application framework. An application is a typed semantic graph, not
|
|
4
|
+
source files.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
## AI agents: read this first
|
|
7
|
+
|
|
8
|
+
**Read [`docs/AGENT_REFERENCE.md`](docs/AGENT_REFERENCE.md) before writing any Axiom code.**
|
|
9
|
+
It ships inside this package, so no repository access, web search or framework source
|
|
10
|
+
inspection is needed to obtain the contract.
|
|
11
|
+
|
|
12
|
+
| | |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| **What** | A semantic application framework. State, behavior, constraints, UI structure, presentation and authority are structured data executed by generic runtimes. |
|
|
15
|
+
| **Who for** | AI-authored applications. The primary author of an Axiom application is a coding agent; human readability is explicitly not the optimization target. |
|
|
16
|
+
| **Start** | [`docs/AGENT_REFERENCE.md`](docs/AGENT_REFERENCE.md) — the compressed operational contract for application authors. |
|
|
17
|
+
| **API contract** | The published `.d.ts` declarations in `dist/`. Authoritative for signatures, unions and branded types. |
|
|
18
|
+
| **Deeper semantics** | The focused documents in `docs/` — [map below](#documentation-map). |
|
|
19
|
+
| **Escalation** | `docs/AGENT_REFERENCE.md` → `.d.ts` → the focused `docs/` document for the topic → a minimal public-API probe. |
|
|
20
|
+
|
|
21
|
+
Axiom's vocabulary is deliberately unlike React, Vue, Angular, Svelte or Express: there is no
|
|
22
|
+
component, no hook, no JSX, no route handler, no ORM and no callback anywhere in the graph.
|
|
23
|
+
Guessing the API from those conventions produces graphs `validateGraph` rejects, so reading
|
|
24
|
+
the reference first is cheaper than any number of attempts.
|
|
25
|
+
|
|
26
|
+
**Reading the framework's implementation source should not be necessary to author an
|
|
27
|
+
application.** `docs/` plus the `.d.ts` declarations are intended to be sufficient on their
|
|
28
|
+
own. Source inspection is a legitimate tool for debugging Axiom itself; it is not the way to
|
|
29
|
+
discover normal consumer usage, and cloning the repository for that purpose is a sign the
|
|
30
|
+
documentation above was missed.
|
|
31
|
+
|
|
32
|
+
Shorter forms of the same routing: [`AGENTS.md`](AGENTS.md) and [`llms.txt`](llms.txt), both
|
|
33
|
+
at this package's root.
|
|
8
34
|
|
|
9
35
|
**Status: experimental / alpha.** The API may change between alpha releases. The
|
|
10
|
-
documentation in `docs/` describes this exact version.
|
|
36
|
+
documentation in `docs/` describes this exact version, `0.9.0-alpha.2`.
|
|
11
37
|
|
|
12
38
|
## Installation
|
|
13
39
|
|
|
@@ -17,6 +43,17 @@ npm install @cynodia/axiom-ui # semantic UI authoring patterns (build ti
|
|
|
17
43
|
npm install @cynodia/axiom-server # only if the application has an authority
|
|
18
44
|
```
|
|
19
45
|
|
|
46
|
+
Every release of this project is a pre-release and npm's `latest` tag points at it, so the
|
|
47
|
+
plain command above installs the current version. **There is no `alpha` dist-tag** — the tag
|
|
48
|
+
was removed once it stopped tracking releases, and `npm install @cynodia/axiom@alpha` now
|
|
49
|
+
fails with a 404. Pin the exact version instead when one is needed:
|
|
50
|
+
`npm install @cynodia/axiom@0.9.0-alpha.2`.
|
|
51
|
+
|
|
52
|
+
These are ES modules compiled to ES2022; import them with `import`, not `require`. There is
|
|
53
|
+
no published Axiom CLI. `@cynodia/axiom-server`'s SQLite persistence adapter additionally
|
|
54
|
+
needs a Node build that provides `node:sqlite` (Node 22 or newer); `isSqliteAvailable()`
|
|
55
|
+
reports its absence rather than failing at import.
|
|
56
|
+
|
|
20
57
|
## Canonical mental model
|
|
21
58
|
|
|
22
59
|
| Concept | Is |
|
|
@@ -124,30 +161,39 @@ console.log(app.getState(COUNT)); // 1
|
|
|
124
161
|
|
|
125
162
|
`compileToHtml(graph)` emits the same application as one self-contained page.
|
|
126
163
|
|
|
127
|
-
## Documentation
|
|
164
|
+
## Documentation map
|
|
128
165
|
|
|
129
|
-
The complete operational contract ships with this package, in `docs/`.
|
|
166
|
+
The complete operational contract ships with this package, in `docs/`. Every path below
|
|
167
|
+
resolves inside the installed package. Read `docs/AGENT_REFERENCE.md` first; reach for a
|
|
168
|
+
focused document when the reference is not specific enough for the question at hand.
|
|
130
169
|
|
|
131
170
|
| Need to understand | Read |
|
|
132
171
|
| --- | --- |
|
|
133
|
-
| Compressed
|
|
134
|
-
| Exact runtime guarantees | `docs/SEMANTIC_CONTRACT.md` |
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
| **Compressed contract for authoring or modifying an app — start here** | [`docs/AGENT_REFERENCE.md`](docs/AGENT_REFERENCE.md) |
|
|
173
|
+
| Exact runtime guarantees, stated formally | [`docs/SEMANTIC_CONTRACT.md`](docs/SEMANTIC_CONTRACT.md) |
|
|
174
|
+
| Mistakes that compile but are wrong | [`docs/ANTI_PATTERNS.md`](docs/ANTI_PATTERNS.md) |
|
|
175
|
+
| Graph, node kinds, ids, types, entity value representation | [`docs/GRAPH_MODEL.md`](docs/GRAPH_MODEL.md) |
|
|
176
|
+
| Every expression kind, builtin, scope, presence and null rule | [`docs/EXPRESSIONS.md`](docs/EXPRESSIONS.md) |
|
|
177
|
+
| Addressing writable positions | [`docs/LOCATIONS.md`](docs/LOCATIONS.md) |
|
|
178
|
+
| Stored, derived, draft and ephemeral state | [`docs/STATE.md`](docs/STATE.md) |
|
|
179
|
+
| Actions, operations, guards, transactions, iteration | [`docs/ACTIONS_TRANSACTIONS.md`](docs/ACTIONS_TRANSACTIONS.md) |
|
|
180
|
+
| Constraints and transition constraints | [`docs/CONSTRAINTS.md`](docs/CONSTRAINTS.md) |
|
|
181
|
+
| Semantic UI nodes, interaction primitives and bindings | [`docs/UI.md`](docs/UI.md) |
|
|
182
|
+
| Presentation, UX intent, themes, value formatting | [`docs/PRESENTATION.md`](docs/PRESENTATION.md) |
|
|
183
|
+
| Runtime API, startup lifecycle and diagnostic codes | [`docs/RUNTIME.md`](docs/RUNTIME.md) |
|
|
184
|
+
| Validation codes and what rejects a graph | [`docs/VALIDATION.md`](docs/VALIDATION.md) |
|
|
185
|
+
| Server authority, the trust boundary, Server IR, the protocol, persistence, deployment | [`docs/AUTHORITY.md`](docs/AUTHORITY.md) |
|
|
186
|
+
| External systems: integration definitions, query operations, adapters, secrets | [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md) |
|
|
187
|
+
| External effects: the outbox, retries, delivery guarantees, outcomes | [`docs/EFFECTS.md`](docs/EFFECTS.md) |
|
|
188
|
+
| Typed events, webhooks, the dispatch pipeline | [`docs/EVENTS.md`](docs/EVENTS.md) |
|
|
189
|
+
| Timed and lifecycle execution, event-invoked actions | [`docs/TRIGGERS.md`](docs/TRIGGERS.md) |
|
|
190
|
+
| Live inbound streams: lifecycle, delivery, deduplication, backpressure | [`docs/SUBSCRIPTIONS.md`](docs/SUBSCRIPTIONS.md) |
|
|
191
|
+
| Binary data: `BlobRef`, upload, download, authorization, orphans | [`docs/STORAGE.md`](docs/STORAGE.md) |
|
|
192
|
+
| Machine queries, mutation impact and graph transformations | [`docs/AGENT_API.md`](docs/AGENT_API.md) |
|
|
193
|
+
|
|
194
|
+
`docs/AGENT_REFERENCE.md` plus the `.d.ts` declarations are intended to be sufficient on
|
|
195
|
+
their own. If they are not, that is a documentation defect worth reporting rather than a
|
|
196
|
+
reason to read framework source.
|
|
151
197
|
|
|
152
198
|
## What is in the box
|
|
153
199
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Actions and transactions
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
3
|
+
Axiom 0.9.0-alpha.2. An action is behavior expressed as data, executed as a transaction.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
{
|
|
@@ -210,6 +210,40 @@ like a mutation is. The adapter runs only after the transaction commits, and the
|
|
|
210
210
|
this action's caller gets back never waits for it — "committed, effect pending." Never legal
|
|
211
211
|
inside `for-each`. Full model: [`EFFECTS.md`](EFFECTS.md).
|
|
212
212
|
|
|
213
|
+
### `blob-metadata`
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
{ kind: 'blob-metadata', storageId: NodeId, blobKey: Expression, bindAs: NodeId }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Reads a stored object's metadata from a `StorageDef` and binds the `BlobRef` into scope —
|
|
220
|
+
query-like in exactly the sense `integration-query` is, and resolved in the same
|
|
221
|
+
pre-transaction phase. It returns the reference, never the bytes. A key that names nothing,
|
|
222
|
+
or names a still-staged upload, **fails the invocation** rather than binding a plausible
|
|
223
|
+
empty record. Never legal inside `for-each`. Full model: [`STORAGE.md`](STORAGE.md).
|
|
224
|
+
|
|
225
|
+
### `blob-commit`
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
{ kind: 'blob-commit', storageId: NodeId, blobKey: Expression, succeededEventId?: NodeId, failedEventId?: NodeId }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Promotes a staged upload to a stored object. Effect-like, and for the same reason every
|
|
232
|
+
effect is: an object store cannot join an Axiom transaction. Reaching it records intent,
|
|
233
|
+
committed atomically with the state that references the object and dispatched only once that
|
|
234
|
+
state is durable. A rolled-back transaction dispatches nothing and leaves the upload staged.
|
|
235
|
+
Never legal inside `for-each`.
|
|
236
|
+
|
|
237
|
+
### `blob-delete`
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
{ kind: 'blob-delete', storageId: NodeId, blobKey: Expression, succeededEventId?: NodeId, failedEventId?: NodeId }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Removes a stored object, post-commit. The state that stopped referencing it commits first; if
|
|
244
|
+
the external deletion then fails, state is still correct and the orphan is visible in
|
|
245
|
+
`server.blobLog()`. Never legal inside `for-each`.
|
|
246
|
+
|
|
213
247
|
## Authorization
|
|
214
248
|
|
|
215
249
|
```ts
|
package/docs/AGENT_API.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent API
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
3
|
+
Axiom 0.9.0-alpha.2. The machine-facing interface. Agents query semantics and apply
|
|
4
4
|
structural transformations; they never edit generated code.
|
|
5
5
|
|
|
6
6
|
```ts
|
|
@@ -160,6 +160,33 @@ agent.getPersistenceForState(stateId)
|
|
|
160
160
|
Authority is derived from what an action writes, so these answers cannot disagree with what
|
|
161
161
|
the graph does. See [`AUTHORITY.md`](AUTHORITY.md).
|
|
162
162
|
|
|
163
|
+
## External-world queries
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
agent.listIntegrations() / agent.listIntegrationOperations(integrationId?)
|
|
167
|
+
agent.getActionsUsingIntegration(integrationId) / agent.getEffectsForAction(actionId)
|
|
168
|
+
agent.getTriggersForAction(actionId) / agent.getTimedTriggers()
|
|
169
|
+
agent.getActionsTriggeredByEvent(eventId) / agent.getTriggeredEvents()
|
|
170
|
+
agent.getExternalDependencies() // { integrations, operations }
|
|
171
|
+
|
|
172
|
+
agent.listSubscriptions()
|
|
173
|
+
agent.getSubscriptionsForIntegration(integrationId)
|
|
174
|
+
agent.getEventForSubscription(subscriptionId)
|
|
175
|
+
agent.getActionsReachableFromSubscription(subscriptionId)
|
|
176
|
+
agent.getExternalEventSources() // { subscriptions, events, integrations }
|
|
177
|
+
|
|
178
|
+
agent.listStorages()
|
|
179
|
+
agent.getActionsUsingStorage(storageId)
|
|
180
|
+
agent.getStoragesWithoutAccessRules() // stores that serve and accept nothing
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
All of these are **graph-static**: they answer what the application *can* reach, not what a
|
|
184
|
+
running authority has done. `getActionsReachableFromSubscription` is the one to reach for
|
|
185
|
+
before changing a live feed — it follows the subscription's event through every bound
|
|
186
|
+
trigger, so "what can this feed actually change" needs no traversal by the consumer. Runtime
|
|
187
|
+
answers come from `AxiomServer.subscriptionLog()` and `blobLog()` instead; see
|
|
188
|
+
[`SUBSCRIPTIONS.md`](SUBSCRIPTIONS.md) and [`STORAGE.md`](STORAGE.md).
|
|
189
|
+
|
|
163
190
|
## Transactions
|
|
164
191
|
|
|
165
192
|
Every change is staged on a private copy. The graph an agent or a runtime can observe is
|
package/docs/AGENT_REFERENCE.md
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
# Agent reference
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
3
|
+
Axiom 0.9.0-alpha.2. Compressed operational contract. Read this plus the `.d.ts`
|
|
4
4
|
declarations before authoring or modifying an Axiom application.
|
|
5
5
|
|
|
6
6
|
Formal guarantees: [`SEMANTIC_CONTRACT.md`](SEMANTIC_CONTRACT.md). Mistakes that compile:
|
|
7
7
|
[`ANTI_PATTERNS.md`](ANTI_PATTERNS.md).
|
|
8
8
|
|
|
9
|
+
## Start here
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @cynodia/axiom # graph, compiler, runtime, agent API
|
|
13
|
+
npm install @cynodia/axiom-ui # semantic UI authoring patterns, build time only
|
|
14
|
+
npm install @cynodia/axiom-server # only if a StateDef declares server authority
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Everything is imported from `@cynodia/axiom`; the four re-exported packages need not be
|
|
18
|
+
installed individually. There is no published CLI.
|
|
19
|
+
|
|
20
|
+
A complete runnable skeleton — graph, state, action, UI, route, compile, run — is the
|
|
21
|
+
minimal application in [`../README.md`](../README.md). Read this document for the rules;
|
|
22
|
+
copy that for the shape.
|
|
23
|
+
|
|
24
|
+
**Escalation order.** This document → the `.d.ts` declarations → the focused document for the
|
|
25
|
+
topic → a minimal public-API probe: build the smallest graph that isolates the question, call
|
|
26
|
+
`validateGraph`, read the returned codes. Reading Axiom's own implementation source is for
|
|
27
|
+
debugging the framework, not for authoring an application.
|
|
28
|
+
|
|
29
|
+
**How much of this to read.** Everything up to and including
|
|
30
|
+
[Agent API](#agent-api) applies to every Axiom application. If no `StateDef` declares
|
|
31
|
+
`authority: 'server'`, the application is client-only and
|
|
32
|
+
[SERVER AUTHORITY](#server-authority) onwards — authority, integrations, effects, triggers,
|
|
33
|
+
subscriptions, storage — describes capability it does not use; skim the headings and stop.
|
|
34
|
+
Read [`ANTI_PATTERNS.md`](ANTI_PATTERNS.md) **before** the first attempt, not the second:
|
|
35
|
+
collection nulls, repeat scope binding and identity-over-index selectors all shape a first
|
|
36
|
+
draft.
|
|
37
|
+
|
|
38
|
+
**Do not guess from React, Vue, Angular, Svelte or Express.** Axiom has no component, no hook,
|
|
39
|
+
no JSX, no route handler, no ORM and no callback in the graph. There is no `formatter: fn`, no
|
|
40
|
+
`validator: fn`, no raw-CSS channel and no stored closure anywhere; new capability arrives as
|
|
41
|
+
an inspectable node, never as a function you supply.
|
|
42
|
+
|
|
9
43
|
## Glossary
|
|
10
44
|
|
|
11
45
|
One canonical term per concept. These are not interchangeable.
|
|
@@ -31,7 +65,7 @@ One canonical term per concept. These are not interchangeable.
|
|
|
31
65
|
## Graph construction
|
|
32
66
|
|
|
33
67
|
```ts
|
|
34
|
-
const graph = new ApplicationGraph(id, name); // version defaults to '0.
|
|
68
|
+
const graph = new ApplicationGraph(id, name); // version defaults to '0.9.0'
|
|
35
69
|
graph.addNode<StateDef>({ id, kind: 'state', ... }); // returns NodeId; throws if id exists
|
|
36
70
|
graph.getNode<StateDef>(id); // deep clone, or undefined
|
|
37
71
|
graph.updateNode(node); // write a modified node back
|
|
@@ -323,6 +357,13 @@ boolean types (`TYPE_MISMATCH`).
|
|
|
323
357
|
Every kind is in `UI_NODE_KINDS`: `view` `container` `text` `repeat` `field-display` `form`
|
|
324
358
|
`input` `button` `conditional` `diagnostic` `dialog`. Detail: [`UI.md`](UI.md).
|
|
325
359
|
|
|
360
|
+
Every kind carries the same base, and `visibleWhen` lives here rather than in
|
|
361
|
+
`presentation`:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
{ id, kind, name?, visibleWhen?: Expression, presentation?: Presentation, metadata? }
|
|
365
|
+
```
|
|
366
|
+
|
|
326
367
|
- `RepeatNode` binds the current item to **the repeat node's own id**; the template refers to it as `ref(repeatNodeId)`.
|
|
327
368
|
- `InputNode.binding` is `{ location }` — no expression, no field id. An input write goes through the same mutation engine and transaction as an action.
|
|
328
369
|
- `ButtonNode.arguments` is keyed by **action parameter id**.
|
|
@@ -412,6 +453,22 @@ presentation: {
|
|
|
412
453
|
}
|
|
413
454
|
```
|
|
414
455
|
|
|
456
|
+
Every value is a closed vocabulary, exported as an array; a token outside it is a validation
|
|
457
|
+
**error**, never a silently ignored value. The four an application reaches for constantly:
|
|
458
|
+
|
|
459
|
+
| Property | Vocabulary | Array |
|
|
460
|
+
| --- | --- | --- |
|
|
461
|
+
| `layout` | `vertical` `horizontal` `grid` `stack` | `LAYOUT_KINDS` |
|
|
462
|
+
| `gap`, `padding` | `none` `xsmall` `small` `medium` `large` `xlarge` | `SPACING_TOKENS` |
|
|
463
|
+
| `textRole` | `body` `caption` `label` `heading` `title` `display` | `TEXT_ROLES` |
|
|
464
|
+
| `density` | `compact` `comfortable` `spacious` | `DENSITIES` |
|
|
465
|
+
|
|
466
|
+
`layout: 'horizontal'` is shorthand for `layout: { kind: 'horizontal', gap?, align?, justify?,
|
|
467
|
+
wrap?, columns? }` — a bare token or the object, never a third form. `stack` is a tight
|
|
468
|
+
vertical column, not overlapping children. Every other vocabulary — roles, UX roles, surfaces,
|
|
469
|
+
treatments, icons, control variants, sizing, value formats, device classes —
|
|
470
|
+
is in [`PRESENTATION.md`](PRESENTATION.md); read it before guessing a token.
|
|
471
|
+
|
|
415
472
|
Resolution precedence, lowest first:
|
|
416
473
|
|
|
417
474
|
```text
|
|
@@ -668,7 +725,8 @@ Portable artifacts, for a runtime written in another language:
|
|
|
668
725
|
@cynodia/axiom-server/schema/server-ir.v1.schema.json JSON Schema for axiom.server.v1 (frozen)
|
|
669
726
|
@cynodia/axiom-server/schema/server-ir.v2.schema.json JSON Schema for axiom.server.v2
|
|
670
727
|
@cynodia/axiom-server/schema/server-ir.v3.schema.json JSON Schema for axiom.server.v3
|
|
671
|
-
@cynodia/axiom-server/schema/server-ir.v4.schema.json JSON Schema for axiom.server.v4
|
|
728
|
+
@cynodia/axiom-server/schema/server-ir.v4.schema.json JSON Schema for axiom.server.v4
|
|
729
|
+
@cynodia/axiom-server/schema/server-ir.v5.schema.json JSON Schema for axiom.server.v5 (latest)
|
|
672
730
|
@cynodia/axiom-server/schema/protocol.v1.schema.json JSON Schema for the protocol
|
|
673
731
|
```
|
|
674
732
|
|
|
@@ -686,6 +744,82 @@ Boundary diagnostics: `UNKNOWN_SERVER_ACTION` `ARGUMENT_TYPE_MISMATCH` `AUTHORIZ
|
|
|
686
744
|
`AUTHORITY_UNREACHABLE`, plus `SERVER_STATE_WRITE` and `REMOTE_ACTION_UNAVAILABLE` on the
|
|
687
745
|
client.
|
|
688
746
|
|
|
747
|
+
## SUBSCRIPTIONS AND STORAGE
|
|
748
|
+
|
|
749
|
+
Full model: [`SUBSCRIPTIONS.md`](SUBSCRIPTIONS.md), [`STORAGE.md`](STORAGE.md). The external
|
|
750
|
+
world reaching *in*, and binary data, which 0.8 had no vocabulary for.
|
|
751
|
+
|
|
752
|
+
The external-interaction model is exactly three directions. Anything else is one of them
|
|
753
|
+
wearing a different name.
|
|
754
|
+
|
|
755
|
+
| Direction | Shape | Vocabulary |
|
|
756
|
+
| --- | --- | --- |
|
|
757
|
+
| Query | Ask; wait for a finite answer. | `integration-query`, `blob-metadata` |
|
|
758
|
+
| Effect | Tell; no answer joins the transaction. | `integration-effect`, `blob-commit`, `blob-delete` |
|
|
759
|
+
| Subscription | The world tells you, while you are listening. | `SubscriptionDef` → `EventDef` |
|
|
760
|
+
|
|
761
|
+
1. **SUBSCRIPTION INVARIANT** — a long-lived external source is a `SubscriptionDef`, never a client, a socket or a callback in the graph. A delivery becomes an `EventDef` payload and enters the existing `EventDef → TriggerDef → ActionDef` pipeline; there is no second event system.
|
|
762
|
+
2. **RAW-I/O INVARIANT** — OS I/O primitives are not graph vocabulary. No `readFile(path)`, `openSocket(host, port)`, `exec(command)`, `spawn(process)`, file descriptor, Node stream or POSIX path exists or will. They live inside an adapter, which is exactly what lets a Rust runtime implement the same graph with different primitives.
|
|
763
|
+
3. **DELIVERY INVARIANT** — at-least-once; effectively-once where `delivery.deduplicateBy` names an external identity, and that deduplication survives a restart when the persistence adapter is durable. Per-subscription ordering is guaranteed; cross-subscription ordering is guaranteed to be **nothing**.
|
|
764
|
+
4. **BACKPRESSURE INVARIANT** — the queue is always bounded (`maxQueued`, default 64) and the default policy (`block`) cannot lose an event. A policy that may discard one (`drop-oldest`/`drop-newest`) is declared in the graph and reports `SUBSCRIPTION_DELIVERY_DROPPED` every time. Loss is never silent and never a default.
|
|
765
|
+
5. **SHUTDOWN INVARIANT** — after `server.stop()`, no delivery reaches application state. A stopped subscription answers `stopped` to everything, including deliveries already in flight.
|
|
766
|
+
6. **BLOB INVARIANT** — bytes never enter the graph, the Server IR or canonical state. A `BlobRef` (`blobRefEntity()` — key, media type, size, filename?, checksum?) is what state holds, and it discloses nothing about the provider.
|
|
767
|
+
7. **BLOB AUTHORIZATION INVARIANT** — possession of a key is not permission. `StorageDef.readAuthorization` is evaluated with the caller bound to `PRINCIPAL` and the `BlobRef` bound to `ref(<storageId>)`; a store with no rule serves nothing. A key that names nothing is refused identically to one the caller may not read.
|
|
768
|
+
8. **STAGED-COMMIT INVARIANT** — an object store does not join an Axiom transaction, and nothing pretends it does. An upload lands `staged`; `blob-commit` promotes it post-commit. A refused transaction leaves a sweepable staged object, never a state referencing bytes that were never claimed. A failed `blob-delete` leaves state correct and the orphan visible in `blobLog()`.
|
|
769
|
+
|
|
770
|
+
```ts
|
|
771
|
+
{ kind: 'subscription', id: SUB_STATUS, integrationId: INTEGRATION, source: 'device-status',
|
|
772
|
+
eventId: EVENT_STATUS_CHANGED,
|
|
773
|
+
lifecycle: { autoStart: true, required: false, reconnect: { policy: 'exponential', maxAttempts: 5, delayMs: 1000 } },
|
|
774
|
+
delivery: { maxQueued: 32, backpressure: 'block', deduplicateBy: F_DELIVERY_ID, maxAttempts: 1, onFailure: 'report' } }
|
|
775
|
+
|
|
776
|
+
{ kind: 'storage', id: STORAGE_LOGS, blobEntityId: ENTITY_BLOB,
|
|
777
|
+
readAuthorization: <Expression>, uploadAuthorization: <Expression>,
|
|
778
|
+
acceptedMediaTypes: ['text/plain'], maxSizeBytes: 8388608 }
|
|
779
|
+
|
|
780
|
+
{ kind: 'blob-metadata', storageId: STORAGE_LOGS, blobKey: <Expression>, bindAs: SCOPE_BLOB }
|
|
781
|
+
{ kind: 'blob-commit', storageId: STORAGE_LOGS, blobKey: <Expression> }
|
|
782
|
+
{ kind: 'blob-delete', storageId: STORAGE_LOGS, blobKey: <Expression> }
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
Lifecycle: `inactive → starting → active → reconnecting → failed`, plus `stopped`. Startup
|
|
786
|
+
decides what activates; application code never calls `start()`. A failed source leaves the
|
|
787
|
+
application running unless `lifecycle.required`.
|
|
788
|
+
|
|
789
|
+
Subscription vs. webhook vs. polling — three different things, do not conflate them:
|
|
790
|
+
|
|
791
|
+
| | What it is | Vocabulary |
|
|
792
|
+
| --- | --- | --- |
|
|
793
|
+
| Webhook | Externally initiated finite request; each delivery enters independently. | host `webhooks` → `EventRequest` |
|
|
794
|
+
| Subscription | Standing semantic interest in a long-lived source. | `SubscriptionDef` |
|
|
795
|
+
| Polling | You ask, repeatedly, on a schedule. | `interval` `TriggerDef` → `integration-query` |
|
|
796
|
+
|
|
797
|
+
Upload is `POST /axiom/blob/<storageId>` and download is `GET /axiom/blob/<storageId>/<key>`
|
|
798
|
+
— one host transport for every Axiom application. Application-authored upload/download
|
|
799
|
+
routes: zero.
|
|
800
|
+
|
|
801
|
+
```ts
|
|
802
|
+
agent.listSubscriptions() / agent.getSubscriptionsForIntegration(id);
|
|
803
|
+
agent.getEventForSubscription(id) / agent.getActionsReachableFromSubscription(id);
|
|
804
|
+
agent.getExternalEventSources(); // { subscriptions, events, integrations }
|
|
805
|
+
agent.listStorages() / agent.getActionsUsingStorage(id) / agent.getStoragesWithoutAccessRules();
|
|
806
|
+
|
|
807
|
+
server.subscriptionLog() / server.subscriptionStatus(id); // state, counters, last delivery, last failure
|
|
808
|
+
server.blobLog(); // storage effects and their outcomes
|
|
809
|
+
server.stageBlob(storageId, principal, upload);
|
|
810
|
+
server.authorizeBlobRead(storageId, key, principal);
|
|
811
|
+
server.authorizeBlobUpload(storageId, principal, { mediaType, size });
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
Adapters: `SubscriptionAdapter` (`createScriptedSubscriptionAdapter` is the deterministic
|
|
815
|
+
fake) and `BlobStorageAdapter` (`createMemoryBlobStore`). A declared subscription or store
|
|
816
|
+
with no registered adapter fails `start()` rather than staying silently inert.
|
|
817
|
+
|
|
818
|
+
Diagnostics: `SUBSCRIPTION_ADAPTER_MISSING` `SUBSCRIPTION_START_FAILED`
|
|
819
|
+
`SUBSCRIPTION_DELIVERY_DROPPED` `SUBSCRIPTION_DELIVERY_FAILED` `BLOB_STORE_MISSING`
|
|
820
|
+
`BLOB_NOT_FOUND` `BLOB_ACCESS_DENIED` `BLOB_TOO_LARGE` `BLOB_MEDIA_TYPE_REJECTED`
|
|
821
|
+
`BLOB_OPERATION_FAILED` `BLOB_STORAGE_UNAVAILABLE` `BLOB_METADATA_FAILED`.
|
|
822
|
+
|
|
689
823
|
## Metadata classes
|
|
690
824
|
|
|
691
825
|
```ts
|
package/docs/ANTI_PATTERNS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Anti-patterns
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
3
|
+
Axiom 0.9.0-alpha.2. Each of these compiles. Each is wrong. Each is followed by the correct
|
|
4
4
|
alternative.
|
|
5
5
|
|
|
6
6
|
## 1. Field names as entity runtime keys
|
|
@@ -405,3 +405,97 @@ a side effect nothing can roll back. See [`INTEGRATIONS.md`](INTEGRATIONS.md) an
|
|
|
405
405
|
|
|
406
406
|
// RIGHT — declare `required: true` on the FieldDef and let the renderer mark it.
|
|
407
407
|
```
|
|
408
|
+
|
|
409
|
+
## 32. OS I/O primitives are not graph vocabulary
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
// WRONG — every one of these. None exists, and none will.
|
|
413
|
+
readFile(path); writeFile(path); openSocket(host, port); exec(command); spawn(process);
|
|
414
|
+
openSerialPort(device);
|
|
415
|
+
{ kind: 'native', implementationId: 'app.readAttachment', inputs: { path: literal('/var/uploads/x') } }
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
An `ApplicationGraph` exposes no filesystem path, no socket, no stream, no file descriptor
|
|
419
|
+
and no subprocess. That is not squeamishness about I/O — the Node host uses all of them
|
|
420
|
+
freely. It is that a graph naming one stops being:
|
|
421
|
+
|
|
422
|
+
| | Why |
|
|
423
|
+
| --- | --- |
|
|
424
|
+
| **portable** | A Rust runtime, or a browser, has different primitives — or none. |
|
|
425
|
+
| **analyzable for authority** | "What can this action reach?" has no answer once `exec` is in the vocabulary. |
|
|
426
|
+
| **secure** | A path is a capability the graph hands out; a key checked against a declared rule is not. |
|
|
427
|
+
| **deterministically testable** | A conformance fixture cannot script a real socket. |
|
|
428
|
+
| **introspectable** | `getExternalDependencies()` can enumerate typed operations. It cannot enumerate what a shell command does. |
|
|
429
|
+
|
|
430
|
+
Low-level I/O is permitted, and expected, **inside an adapter**:
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
PrinterIntegration.print() → adapter implementation → TCP
|
|
434
|
+
VideoIntegration.transcode() → adapter implementation → ffmpeg subprocess
|
|
435
|
+
DeviceStream subscription → adapter implementation → serial port, MQTT, WebSocket
|
|
436
|
+
DiagnosticLogs storage → adapter implementation → local directory, or S3
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The graph says *what the interaction means*; the adapter decides *how*. Replace Node with
|
|
440
|
+
Rust, MQTT with a WebSocket, or a local directory with S3, and the graph does not change —
|
|
441
|
+
which is the test that the abstraction is at the right level.
|
|
442
|
+
|
|
443
|
+
Use [`INTEGRATIONS.md`](INTEGRATIONS.md), [`SUBSCRIPTIONS.md`](SUBSCRIPTIONS.md) or
|
|
444
|
+
[`STORAGE.md`](STORAGE.md) instead.
|
|
445
|
+
|
|
446
|
+
## 33. `setInterval` + `fetch` for polling, or a client in application code
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
// WRONG — all four, in application code.
|
|
450
|
+
setInterval(() => fetch('/api/devices').then(apply), 5000);
|
|
451
|
+
const socket = new WebSocket('wss://…');
|
|
452
|
+
const client = mqtt.connect('mqtt://…');
|
|
453
|
+
socket.onmessage = (event) => applyStatus(JSON.parse(event.data));
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Each of these puts scheduling, transport and a callback-driven mutation path into the
|
|
457
|
+
application, where nothing can analyze, test or authorize them.
|
|
458
|
+
|
|
459
|
+
| Instead of | Declare |
|
|
460
|
+
| --- | --- |
|
|
461
|
+
| `setInterval` + `fetch` | `TriggerDef{when:{kind:'interval'}}` → `integration-query` |
|
|
462
|
+
| `new WebSocket` / an MQTT client | `SubscriptionDef` → `EventDef` → `TriggerDef` |
|
|
463
|
+
| `socket.onmessage = handler` | The trigger's target action. Deliveries never invoke a callback. |
|
|
464
|
+
| A hand-rolled webhook route | The host's `webhooks` option → `EventRequest` |
|
|
465
|
+
|
|
466
|
+
## 34. A client-authored subscription event
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
// WRONG — an action a subscription's trigger invokes, left open to clients.
|
|
470
|
+
{ kind: 'action', id: ACTION_APPLY_STATUS, /* no `invocation` */ operations: [ … ] }
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Any anonymous client that guesses the id can then assert whatever the live feed asserts.
|
|
474
|
+
Declare `invocation: { allowedSources: ['system'] }`: a client-sourced call is refused with
|
|
475
|
+
`INVOCATION_SOURCE_NOT_ALLOWED` before identity is even consulted.
|
|
476
|
+
|
|
477
|
+
## 35. base64 bytes in canonical state
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
// WRONG — the attachment's contents in the record, in the Server IR, in every snapshot.
|
|
481
|
+
{ id: F_DOCUMENT_ATTACHMENT, valueType: primitiveType('string') } // "data:application/pdf;base64,…"
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Every read, every snapshot, every persisted write and every `changes` map then carries the
|
|
485
|
+
whole object. Store a `BlobRef` (`blobRefEntity()`) and let the bytes move through the
|
|
486
|
+
host's own upload and download transport — see [`STORAGE.md`](STORAGE.md). A 5MB attachment
|
|
487
|
+
leaves the record exactly as large as a 5-byte one.
|
|
488
|
+
|
|
489
|
+
## 36. An application-authored upload or download route
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
// WRONG — a route the graph does not know about, guarding data the graph does own.
|
|
493
|
+
express.post('/upload', (request, response) => { /* … */ });
|
|
494
|
+
express.get('/files/:key', (request, response) => response.sendFile(`/var/uploads/${request.params.key}`));
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
The second is also a path traversal waiting to happen, and neither can be reached by
|
|
498
|
+
`validateGraph`, by `AgentAPI`, or by a conformance fixture. `POST /axiom/blob/<storageId>`
|
|
499
|
+
and `GET /axiom/blob/<storageId>/<key>` already exist, for every Axiom application, with
|
|
500
|
+
`StorageDef.uploadAuthorization` and `StorageDef.readAuthorization` enforced by the
|
|
501
|
+
authority. Application-authored upload/download routes: zero.
|
package/docs/AUTHORITY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Authority
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
3
|
+
Axiom 0.9.0-alpha.2. How an application crosses the trust boundary.
|
|
4
4
|
|
|
5
5
|
Until 0.5.x an Axiom application executed locally. 0.6 adds an **authority**: a generic
|
|
6
6
|
runtime that owns state, decides mutations and persists them. The same semantic graph
|
|
@@ -477,6 +477,16 @@ does for a local failure.
|
|
|
477
477
|
| `INTEGRATION_ADAPTER_MISSING` | The Server IR requires an integration with no registered adapter — refused at `start()`, never deferred to first invocation. |
|
|
478
478
|
| `EVENT_DISPATCH_DEPTH_EXCEEDED` | An event → action → effect → event chain was stopped before it could recurse unboundedly. |
|
|
479
479
|
| `WEBHOOK_VERIFICATION_FAILED` | A webhook delivery failed provider signature verification and was refused before an event was ever constructed. |
|
|
480
|
+
| `SUBSCRIPTION_ADAPTER_MISSING` | The Server IR declares a subscription whose integration has no registered `SubscriptionAdapter`. Startup refuses, rather than leaving a declared live source permanently inactive. |
|
|
481
|
+
| `SUBSCRIPTION_START_FAILED` | A subscription's source could not be established, after every attempt its declared reconnect policy allows. |
|
|
482
|
+
| `SUBSCRIPTION_DELIVERY_DROPPED` | A delivery was discarded by a declared lossy backpressure policy. Loss is always declared and never silent. |
|
|
483
|
+
| `SUBSCRIPTION_DELIVERY_FAILED` | A delivery's triggered action failed after every permitted attempt. |
|
|
484
|
+
| `BLOB_STORE_MISSING` | The Server IR declares a `StorageDef` with no registered `BlobStorageAdapter`. |
|
|
485
|
+
| `BLOB_NOT_FOUND` | No object with that key, or the key names a still-staged upload. |
|
|
486
|
+
| `BLOB_ACCESS_DENIED` | The caller may not read, download or upload this object. Also the answer for a key that names nothing at all, so the endpoint is not an oracle for enumerating keys. |
|
|
487
|
+
| `BLOB_TOO_LARGE` | An upload exceeded the store's declared `maxSizeBytes`. |
|
|
488
|
+
| `BLOB_MEDIA_TYPE_REJECTED` | An upload's media type is not in the store's declared `acceptedMediaTypes`. |
|
|
489
|
+
| `BLOB_OPERATION_FAILED` | A `blob-commit` or `blob-delete` failed at the store, after its retry policy. |
|
|
480
490
|
| `INVOCATION_SOURCE_NOT_ALLOWED` | The action's `invocation.allowedSources` does not include this invocation's source (spec 8.1 §3-9) — refused before `authorization` is even evaluated, because no caller reaching the authority this way may invoke the action at all. |
|
|
481
491
|
|
|
482
492
|
Two client-side codes belong to the boundary as well:
|
|
@@ -626,8 +636,9 @@ page plus the conformance fixtures.
|
|
|
626
636
|
| `axiom.server.v2` | the expression kinds `group` and `expression-ref`, and the `expressionDefs` they resolve against | 0.7.0 |
|
|
627
637
|
| `axiom.server.v3` | integrations, integration operations, events, triggers, and the `integration-query`/`integration-effect` operation kinds | 0.8.0 |
|
|
628
638
|
| `axiom.server.v4` | `ActionDef.invocation.allowedSources` invocation-source restriction, and the structured effect-outcome envelope (`effectOutcomeEntity`, `EFFECT_ID_FIELD` and its sibling reserved fields) that every effect dispatch uses from 8.1 onward | 0.8.1 |
|
|
639
|
+
| `axiom.server.v5` | `SubscriptionDef` and `StorageDef`, and the `blob-metadata`/`blob-commit`/`blob-delete` operation kinds — the inbound external-I/O direction and binary object storage | 0.9.0 |
|
|
629
640
|
|
|
630
|
-
`SERVER_IR_CONTRACTS` enumerates all
|
|
641
|
+
`SERVER_IR_CONTRACTS` enumerates all five, and is the single source of truth this table is
|
|
631
642
|
tested against — `packages/demo/test/documentation.test.ts` fails if a contract in
|
|
632
643
|
`SERVER_IR_CONTRACTS` has no row here, or a row here names a contract the code does not
|
|
633
644
|
declare (spec 8.2 §7-8). The rules:
|
|
@@ -635,11 +646,20 @@ declare (spec 8.2 §7-8). The rules:
|
|
|
635
646
|
- **A document declares the oldest contract that can carry it.** `compileToServerIR` computes the label from the vocabulary the document actually uses, so an application that uses nothing from 0.7 or 0.8 produces a byte-identical `axiom.server.v1` document, and the committed v1 conformance fixtures are unchanged. `usesV4Semantics` computes the v4 case specifically: an action's `invocation.allowedSources` genuinely restricting the default two-source set, or any `integration-operation` with `mode: 'effect'` (since every effect dispatch uses the structured v4 envelope) — a document that merely mentions `invocation` without restricting it only needs `axiom.server.v2`, the same tier `group`/`expression-ref` occupy.
|
|
636
647
|
- **A runtime MUST refuse a contract it does not implement**, and MUST refuse a document whose vocabulary exceeds its declared contract. A v2 runtime executing a v1-labelled document that uses `group` would accept what a conforming v1 runtime elsewhere refuses, and the two would then disagree about the same file. `createAxiomServer` raises rather than executing one — including refusing a document that **understates** its own contract (`understatedContract`).
|
|
637
648
|
- **A frozen contract gains nothing.** `axiom.server.v1` does not contain `group`, `expression-ref`, `expressionDefs`, an integration, a trigger, an event, the `integration-query`/`integration-effect` operation kinds, `invocation`, or the structured effect-outcome envelope, and `server-ir.v1.schema.json` is byte-frozen. Vocabulary arrives under a new identifier or not at all.
|
|
638
|
-
- **`axiom.server.
|
|
649
|
+
- **`axiom.server.v5` is the latest contract as of 0.9.0** (`SERVER_IR_LATEST_CONTRACT`). `usesExternalIOVocabulary` computes it from the document: any `subscriptions`, any `storages`, or any of the three blob operation kinds. The reason it is an incompatible change rather than an additive one is exact — a v4 runtime that ignored `subscriptions` would start an application whose declared live event source never activates, and one that ignored a `blob-commit` would leave state referencing an object that stays staged forever. Both are silent divergence, which is what a contract label exists to prevent.
|
|
639
650
|
|
|
640
651
|
There is one JSON Schema per contract, each generated from the runtime's own vocabulary and
|
|
641
652
|
each shipped: `server-ir.v1.schema.json`, `server-ir.v2.schema.json`, `server-ir.v3.schema.json`,
|
|
642
|
-
`server-ir.v4.schema.json`.
|
|
653
|
+
`server-ir.v4.schema.json`, `server-ir.v5.schema.json`.
|
|
654
|
+
|
|
655
|
+
**`SubscriptionDef`, `StorageDef` and the blob operations.** Their normative semantics —
|
|
656
|
+
lifecycle states and transitions, at-least-once delivery, per-subscription ordering and the
|
|
657
|
+
explicit absence of cross-subscription ordering, bounded queues and every backpressure policy,
|
|
658
|
+
deduplication and its restart durability, poison-delivery bounds, staged-then-committed object
|
|
659
|
+
lifecycle, and the authorization rule a store evaluates before serving a byte — are documented
|
|
660
|
+
in [`SUBSCRIPTIONS.md`](SUBSCRIPTIONS.md) and [`STORAGE.md`](STORAGE.md), and are executable in
|
|
661
|
+
the `subscription-*` and `blob-*` conformance fixtures. An independent implementer needs those
|
|
662
|
+
two documents, the v5 schema and those fixtures, and no TypeScript.
|
|
643
663
|
|
|
644
664
|
**`group`.** Partitions a collection: `Collection<A>` → `Collection<Group<K, A>>`. Groups appear
|
|
645
665
|
in the order their key was **first seen** in the source; members keep source order; two keys are
|