@cynodia/axiom 0.5.0-alpha.1 → 0.5.2-alpha.1

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 CHANGED
@@ -1,14 +1,13 @@
1
1
  # Axiom
2
2
 
3
- AI-native semantic web application framework.
3
+ AI-native semantic application framework.
4
4
 
5
- **Status: experimental / alpha.** Axiom is a research prototype. Its API may change
6
- between alpha releases, and it is not production-ready.
5
+ Axiom represents application behavior, state, UI structure and presentation as structured
6
+ semantic data executed by generic runtimes. An application is a typed graph, not source
7
+ files: the JavaScript and HTML that reach the browser are output, and are never edited.
7
8
 
8
- An Axiom application is not source code. It is a typed semantic graph of entities,
9
- fields, state, actions, constraints, routes and UI nodes. A generic compiler normalizes
10
- that graph and a generic runtime executes it in an unmodified browser — the JavaScript
11
- and HTML that reach the browser are output, never something you maintain by hand.
9
+ **Status: experimental / alpha (0.5.2-alpha.x).** The API may change between alpha
10
+ releases. The documentation in `docs/` describes this exact version.
12
11
 
13
12
  ## Installation
14
13
 
@@ -16,433 +15,134 @@ and HTML that reach the browser are output, never something you maintain by hand
16
15
  npm install @cynodia/axiom@alpha
17
16
  ```
18
17
 
19
- ## A minimal application
18
+ ## Canonical mental model
19
+
20
+ | Concept | Is |
21
+ | --- | --- |
22
+ | `ApplicationGraph` | The authoritative representation of an application. |
23
+ | `Expression` | What value is computed. Pure; never writes. |
24
+ | `Location` | Where a writable value lives. An address, not a value. |
25
+ | `StateDef` | A stored or derived application value. |
26
+ | `ActionDef` | A transactional semantic operation. |
27
+ | `ConstraintDef` | An invariant over proposed state. |
28
+ | `TransitionConstraintDef` | An invariant over previous committed state → proposed state. |
29
+ | UI nodes | Semantic interaction structure. |
30
+ | `Presentation` | Semantic UX intent. Roles and tokens, never CSS. |
31
+ | `Theme` | Translation of presentation intent into visual design. |
32
+ | Renderer | Platform-specific materialization. Not part of the graph. |
33
+
34
+ ```text
35
+ ApplicationGraph → validateGraph → compileToIR → runtime (+ theme → renderer) → application
36
+ ```
37
+
38
+ ## Load-bearing invariants
39
+
40
+ Know these before authoring an application. Each is stated in full in
41
+ `docs/SEMANTIC_CONTRACT.md`.
42
+
43
+ 1. **Entity runtime values are keyed by `FieldId`**, not by field name.
44
+ 2. **Expressions produce values; Locations name writable addresses.** Stored state is deeply frozen.
45
+ 3. **Derived state is read-only.**
46
+ 4. **An action is a transaction.** Every mutation commits, or every mutation rolls back.
47
+ 5. **Operations see provisional state**, and `for-each` iteration N sees the writes of iterations `< N`.
48
+ 6. **Constraints are evaluated against proposed state**, per instance, wherever it is stored.
49
+ 7. **Transition constraints compare the instance at transaction entry with the instance proposed.** A newly inserted instance has no previous state and is not evaluated.
50
+ 8. **Input writes are governed** by the same constraints, through the same engine and transaction.
51
+ 9. **`hydrateState` bypasses semantic enforcement.** It is administrative.
52
+ 10. **Presentation never authorizes behavior.** `hidden` ≠ forbidden.
53
+ 11. **`null` and `[]` are distinct.** `null` fails a collection operator; `[]` does not. A collection is truthy only when non-empty.
54
+ 12. **`required(x)` asks only whether a value exists.** `required([])` is `true`.
55
+ 13. **A theme changes presentation only.**
56
+
57
+ ## Minimal application
20
58
 
21
59
  ```ts
22
60
  import {
23
- ApplicationGraph,
24
- binary,
25
- call,
26
- compileToHtml,
27
- compileToIR,
28
- createAxiomRuntime,
29
- createMemoryHost,
30
- literal,
31
- nodeId,
32
- primitiveType,
33
- ref,
34
- stateLocation,
35
- synchronizeEdges,
36
- validateGraph,
61
+ ApplicationGraph, binary, compileToIR, createAxiomRuntime, createMemoryHost,
62
+ literal, nodeId, primitiveType, ref, stateLocation, validateGraph,
37
63
  } from '@cynodia/axiom';
38
64
  import type { ActionDef, ButtonNode, RouteDef, StateDef, TextNode, ViewNode } from '@cynodia/axiom';
39
65
 
40
66
  const COUNT = nodeId('state_count');
41
67
  const INCREMENT = nodeId('action_increment');
42
- const DISPLAY = nodeId('ui_display');
43
- const BUTTON = nodeId('ui_increment');
44
68
  const VIEW = nodeId('ui_view');
45
69
 
46
70
  const graph = new ApplicationGraph('counter', 'Counter');
47
71
 
48
72
  graph.addNode<StateDef>({
49
- id: COUNT,
50
- kind: 'state',
51
- name: 'count',
52
- valueType: primitiveType('number'),
53
- initialValue: 0,
73
+ id: COUNT, kind: 'state', name: 'count',
74
+ valueType: primitiveType('number'), initialValue: 0,
54
75
  });
55
76
 
56
- // Values are expressions; writable positions are locations.
77
+ // A value is an Expression. The position written to is a Location.
57
78
  graph.addNode<ActionDef>({
58
- id: INCREMENT,
59
- kind: 'action',
60
- name: 'increment',
79
+ id: INCREMENT, kind: 'action', name: 'increment',
61
80
  operations: [
62
81
  { kind: 'set', target: stateLocation(COUNT), value: binary('add', ref(COUNT), literal(1)) },
63
82
  ],
64
83
  });
65
84
 
66
85
  graph.addNode<TextNode>({
67
- id: DISPLAY,
68
- kind: 'text',
69
- value: call('concat', literal('Count: '), call('to-string', ref(COUNT))),
86
+ id: nodeId('ui_display'), kind: 'text', value: ref(COUNT),
87
+ presentation: { textRole: 'title', format: { kind: 'number' } },
88
+ });
89
+ graph.addNode<ButtonNode>({
90
+ id: nodeId('ui_increment'), kind: 'button', label: 'Add one', actionId: INCREMENT,
91
+ presentation: { uxRole: 'primary-action', icon: 'add' },
92
+ });
93
+ graph.addNode<ViewNode>({
94
+ id: VIEW, kind: 'view', name: 'Counter',
95
+ children: [nodeId('ui_display'), nodeId('ui_increment')],
70
96
  });
71
- graph.addNode<ButtonNode>({ id: BUTTON, kind: 'button', label: 'Add one', actionId: INCREMENT });
72
- graph.addNode<ViewNode>({ id: VIEW, kind: 'view', children: [DISPLAY, BUTTON] });
73
97
  graph.addNode<RouteDef>({ id: nodeId('route_root'), kind: 'route', path: '/', viewId: VIEW });
74
98
 
75
- synchronizeEdges(graph);
76
-
77
- const result = validateGraph(graph);
78
- if (!result.valid) {
79
- throw new Error(result.errors.map((problem) => problem.message).join('\n'));
99
+ if (!validateGraph(graph).valid) {
100
+ throw new Error('invalid graph');
80
101
  }
81
102
 
82
- // Run it headlessly...
83
103
  const host = createMemoryHost({ path: '/' });
84
104
  const app = createAxiomRuntime({ ir: compileToIR(graph), rootElement: host.root, host });
85
105
  app.start();
86
106
  app.invokeAction(INCREMENT);
87
107
  console.log(app.getState(COUNT)); // 1
88
-
89
- // ...or emit a self-contained page.
90
- const page = compileToHtml(graph);
91
- ```
92
-
93
- ## Actions are transactions
94
-
95
- This is the guarantee the framework is built around:
96
-
97
- > An Axiom action executes as a semantic transaction. Its mutations are applied
98
- > provisionally, the relevant constraints are evaluated against the resulting **proposed
99
- > state**, and either the complete action commits or every one of its state mutations is
100
- > rolled back.
101
-
102
- That includes iteration. If an action reduces stock for twenty order lines and the
103
- seventeenth breaks an invariant, the first sixteen do not survive — you never write
104
- rollback logic yourself, and `runtime.getMutationLog()` shows every attempted write with
105
- its `outcome` of `committed` or `rolled-back`.
106
-
107
- Within one action, each `for-each` iteration reads the state the previous iterations
108
- proposed. Two lines for the same product debit it twice:
109
-
110
- ```text
111
- stock 5 → line A (−3) → 2 → line A (−3) → −1 → stock >= 0 fails → all rolled back
112
- ```
113
-
114
- That is a guarantee, not an implementation detail: an aggregate rule can be expressed as a
115
- simple per-record invariant.
116
-
117
- ## What is enforced, and where
118
-
119
- Two kinds of rule, and they answer different questions:
120
-
121
- | | Question | Applies to |
122
- | --- | --- | --- |
123
- | **Constraint** | Is this state allowed? | Every instance of an entity, wherever it is stored — including instances nested inside other entities. |
124
- | **Transition constraint** | Is this *change* allowed? | The instance as it was when the transaction began, compared with the instance the transaction proposes. |
125
-
126
- A transition constraint is what makes a rule like "a confirmed order never changes" hold
127
- no matter which path attempts the write:
128
-
129
- ```ts
130
- graph.addNode<TransitionConstraintDef>({
131
- id: ORDER_SEALED,
132
- kind: 'transition-constraint',
133
- entityId: ORDER,
134
- previousScopeId: PREVIOUS,
135
- proposedScopeId: PROPOSED,
136
- message: 'A confirmed order cannot be changed.',
137
- expression: binary(
138
- 'or',
139
- binary('neq', field(ref(PREVIOUS), STATUS), literal('confirmed')),
140
- binary('eq', ref(PROPOSED), ref(PREVIOUS)),
141
- ),
142
- });
143
- ```
144
-
145
- **Governed paths** — actions, `for-each` iterations, and input bindings — all evaluate
146
- entity constraints *and* transition constraints against the proposed state, and roll the
147
- whole transaction back if either refuses. You do not have to remember not to bind an input
148
- to a protected location: binding it and typing into it is simply refused, with a
149
- `TRANSITION_CONSTRAINT_VIOLATION` naming the rule, the entity, and the previous and
150
- proposed values.
151
-
152
- **`hydrateState` is not governed.** It replaces a state value outright for hosts, tests and
153
- seeding, and evaluates nothing. It is deliberately not named like a normal write.
154
-
155
- **"Previous" means transaction entry** — committed state as it was before the outermost
156
- transaction began. Not the previous operation, and not the previous iteration.
157
-
158
- ## Collections
159
-
160
- Values are described by expressions, writable positions by **locations**. Collections add
161
- projection, aggregation and ordering to the first, and iteration to the second.
162
-
163
- ```ts
164
- import { binary, field, filter, forEach, map, ref, sum } from '@cynodia/axiom';
165
-
166
- // An order total: project each line to its amount, then sum the projection.
167
- const orderTotal = sum(
168
- map(ref(LINES), LINE, binary('multiply', field(ref(LINE), QUANTITY), field(ref(LINE), PRICE))),
169
- );
170
-
171
- // How much of one product this order asks for, across every line that mentions it.
172
- const requested = sum(
173
- map(
174
- filter(ref(LINES), LINE, binary('eq', field(ref(LINE), PRODUCT), field(ref(P), PRODUCT_ID))),
175
- LINE,
176
- field(ref(LINE), QUANTITY),
177
- ),
178
- );
179
-
180
- // Reduce the stock of every product the order mentions — one mutation per line, one
181
- // transaction for the action.
182
- const confirm = forEach(ref(LINES), LINE, [
183
- {
184
- kind: 'set',
185
- target: fieldLocation(
186
- itemLocation(stateLocation(PRODUCTS), identitySelector(PRODUCT_ID, field(ref(LINE), PRODUCT))),
187
- STOCK,
188
- ),
189
- value: binary('subtract', currentStock, field(ref(LINE), QUANTITY)),
190
- },
191
- ]);
192
- ```
193
-
194
- None of this is a callback. `map`, `sort`, `filter`, `find`, `every`, `some`, `flatten`,
195
- `conditional` and `for-each` are data: they serialize, they validate, and an agent can ask
196
- what they read and write.
197
-
198
- **Collection operators are strict about their source.** `null` means a missing or invalid
199
- collection and fails the evaluation; `[]` means an empty collection and works normally
200
- (`sum([])` is `0`, `count([])` is `0`, `every([])` is `true`). Nothing returns a
201
- plausible-looking value while reporting a failure. Where a collection may legitimately be
202
- absent, say so:
203
-
204
- ```ts
205
- coalesce(field(ref(CURRENT_ORDER), LINES), literal([]))
206
- ```
207
-
208
- **Presence is not emptiness.** `required(value)` asks only whether a value exists:
209
-
210
- ```text
211
- required(null) → false required([]) → true
212
- required(0) → true required('') → true
213
- required(false)→ true
214
- ```
215
-
216
- Use `is-empty` / `non-empty` for collections and strings, and `coalesce` to fall back on
217
- absence — which means falling back *to* an empty collection now works.
218
-
219
- ## Presentation and UX intent
220
-
221
- Four things are kept apart on purpose:
222
-
223
- | | Describes | Lives in |
224
- | --- | --- | --- |
225
- | **UI semantics** | What exists — views, containers, text, repeats, forms, inputs, buttons. | The graph |
226
- | **Presentation semantics** | What it *means* and how it is organized — roles, layout, spacing, sizing, device classes. | The graph |
227
- | **Theme** | What those meanings look like — colours, type scale, spacing values, radii, breakpoints. | The graph's `theme` |
228
- | **Renderer** | How any of it reaches a screen — CSS, classes, media queries, DOM. | The framework |
229
-
230
- An application says `role: 'destructive'`, not `color: '#c62a20'`. There is no inline style
231
- model, no CSS property model, and no way to store a function anywhere in the graph.
232
-
233
- ```ts
234
- graph.addNode<ContainerNode>({
235
- id: ACTIONS,
236
- kind: 'container',
237
- children: [CANCEL, SAVE],
238
- presentation: {
239
- uxRole: 'action-group',
240
- responsive: { compact: { layout: 'vertical' } },
241
- },
242
- });
243
- ```
244
-
245
- `uxRole: 'action-group'` is high-information: it already implies a horizontal, wrapping,
246
- centre-aligned, end-justified group. You annotate where intent differs from the default,
247
- not on every node.
248
-
249
- ### Resolution order
250
-
251
- Every resolved property is decided by exactly one layer, lowest first:
252
-
253
- ```text
254
- renderer defaults → theme → inherited → semantic inference → node → responsive
255
- ```
256
-
257
- `resolvePresentation` records which layer won, so precedence is inspectable rather than
258
- folklore:
259
-
260
- ```ts
261
- const agent = new AgentAPI(graph);
262
- agent.resolvePresentation(DELETE_BUTTON);
263
- // { role: 'destructive', uxRole: 'destructive-action', density: 'comfortable', … ,
264
- // origins: { role: 'inferred', density: 'theme', 'layout.kind': 'inferred' } }
265
- ```
266
-
267
- **Inheritance is deliberately narrow.** Only `density` cascades from a parent
268
- (`INHERITED_PROPERTIES`). Nothing else does — a container with `emphasis: 'strong'` does
269
- not make its whole subtree bold.
270
-
271
- **Semantic inference** means presentation is derived from what the application already
272
- says, rather than declared twice:
273
-
274
- ```ts
275
- graph.addNode<ActionDef>({ id: DELETE, kind: 'action', destructive: true, operations: [...] });
276
- graph.addNode<ButtonNode>({ id: BUTTON, kind: 'button', label: 'Delete', actionId: DELETE });
277
- // The button is presented as destructive. It declares no role at all.
278
108
  ```
279
109
 
280
- A button that submits its enclosing form becomes the primary action the same way. An
281
- explicit `presentation.role` always wins; a contradiction — a destructive action presented
282
- as a success — is reported as `DESTRUCTIVE_ACTION_PRESENTED_AS_SUCCESS`.
110
+ `compileToHtml(graph)` emits the same application as one self-contained page.
283
111
 
284
- ### Responsive behaviour without breakpoints
112
+ ## Documentation
285
113
 
286
- Presentation names device classes — `compact`, `regular`, `wide` — never pixels. The
287
- renderer owns the breakpoints (`theme.responsive`), and provides sensible behaviour with no
288
- configuration at all: rows wrap, fixed grids give up columns, bounded widths stop being
289
- bounded, and controls go full width on a narrow screen.
290
-
291
- ```ts
292
- presentation: {
293
- layout: { kind: 'grid', gap: 'medium', columns: { mode: 'adaptive', minimum: 'medium' } },
294
- responsive: { compact: { padding: 'small' } },
295
- }
296
- ```
114
+ The complete operational contract ships with this package, in `docs/`.
297
115
 
298
- ### Vocabulary
299
-
300
- Everything below is a closed set. A token outside it is a validation **error**, because a
301
- renderer cannot act on a value it does not know.
302
-
303
- | | Values |
116
+ | Need to understand | Read |
304
117
  | --- | --- |
305
- | `role` | `primary` `secondary` `tertiary` `destructive` `success` `warning` `informational` `muted` |
306
- | `uxRole` | `primary-action` `secondary-action` `destructive-action` `navigation-action` `form-section` `action-group` `navigation-group` `empty-state` `error-state` `warning-state` `success-state` `informational-state` `toolbar` `sidebar` `content-region` `header-region` `footer-region` |
307
- | `emphasis` | `subtle` `normal` `strong` |
308
- | `density` | `compact` `comfortable` `spacious` |
309
- | `textRole` | `body` `caption` `label` `heading` `title` `display` |
310
- | `surface` | `transparent` `base` `subtle` `raised` `inset` |
311
- | `layout.kind` | `vertical` `horizontal` `grid` `stack` |
312
- | `gap`, `padding` | `none` `xsmall` `small` `medium` `large` `xlarge` |
313
- | `sizing.width` | `fit` `fill` `content` `narrow` `medium` `wide` |
314
- | `align`, `justify` | `start` `center` `end` `stretch` (+ `between` for `justify`) |
315
- | `treatment` | `plain` `badge` `pill` |
316
- | `control` | `default` `switch` `checkbox` `radio-group` `select` `multiline` `stepper` |
317
- | `icon` | `add` `delete` `edit` `save` `close` `warning` `success` `error` `information` `navigation-back` `navigation-forward` `menu` `search` `refresh` `settings` `more` |
318
-
319
- ### Value formatting
320
-
321
- Formatting is presentation. The stored value never changes, and there is no way to supply
322
- a function:
323
-
324
- ```ts
325
- presentation: { format: { kind: 'currency', currency: 'NOK' } } // 1250 → "NOK 1,250.00"
326
- presentation: { format: { kind: 'percentage', decimals: 1 } } // 0.421 → "42.1%"
327
- presentation: { format: { kind: 'boolean', trueLabel: 'Read', falseLabel: 'Unread' } }
328
- ```
329
-
330
- A display of a boolean, date or datetime field is formatted by inference, without being
331
- asked. A value a format cannot describe falls back to its plain text rather than inventing
332
- a plausible result.
333
-
334
- ### Theme
335
-
336
- A theme is the one place concrete values belong, and it is plain serializable data. Declare
337
- only what differs:
338
-
339
- ```ts
340
- graph.setTheme({ appearance: 'dark', defaults: { density: 'compact' }, spacing: { medium: 8 } });
341
- ```
342
-
343
- Light, dark and system appearances need no second graph. `DEFAULT_THEME` is a neutral,
344
- accessible, responsive theme intended for business applications, and
345
- `createThemeStylesheet(theme)` is the web renderer's translation of it into CSS custom
346
- properties and rules.
347
-
348
- **A theme cannot change behaviour.** Actions, constraints, transition constraints,
349
- locations, state and routing are untouched by it — which is why "use a denser enterprise
350
- identity" is one `setTheme` call rather than an edit to every node.
351
-
352
- ### Accessibility
353
-
354
- Semantic roles produce accessible structure, so the two cannot drift apart:
355
-
356
- - `header-region`, `navigation-group`, `content-region`, `footer-region`, `sidebar` and
357
- `form-section` become `<header>`, `<nav>`, `<main>`, `<footer>`, `<aside>`, `<section>`.
358
- - `textRole` `display` / `title` / `heading` become `<h1>` / `<h2>` / `<h3>`.
359
- - `error-state` announces itself as an alert; the other status roles as a status.
360
- - An input's label names its control by id, a required field is marked from the model's own
361
- `required`, help text is related with `aria-describedby`, and a refused write is
362
- announced next to the control it was refused on with `aria-invalid`.
363
-
364
- Validation reports what it can determine reliably — `FORM_INPUT_MISSING_LABEL`,
365
- `INTERACTIVE_ELEMENT_MISSING_LABEL`, `INVALID_HEADING_STRUCTURE`,
366
- `DESTRUCTIVE_ACTION_UNMARKED` — and nothing speculative.
367
-
368
- ### UX findings
369
-
370
- Presentation validation reports what a stylesheet could never tell you. All of it is
371
- warnings; none of it stops an application from compiling:
372
-
373
- ```text
374
- MULTIPLE_PRIMARY_ACTIONS FORM_WITHOUT_PRIMARY_ACTION
375
- DESTRUCTIVE_ACTION_PRESENTED_AS_SUCCESS DESTRUCTIVE_ACTION_UNMARKED
376
- EMPTY_STATE_WITHOUT_RECOVERY_ACTION EXCESSIVE_HORIZONTAL_ACTIONS
377
- RIGID_HORIZONTAL_LAYOUT CONFLICTING_SIZING
378
- PRESENTATION_SEMANTIC_CONFLICT OPAQUE_PRESENTATION
379
- ```
380
-
381
- And it is queryable, which is the point:
382
-
383
- ```ts
384
- agent.getPrimaryActions(VIEW); // which action is the emphasised one here?
385
- agent.getDestructiveActions(VIEW); // which controls are dangerous?
386
- agent.getFormsWithoutPrimaryAction(); // where is the hierarchy missing?
387
- agent.getFormStructure(FORM); // sections, required controls, action groups
388
- agent.getResponsiveBehavior(ROW); // what happens on a phone?
389
- agent.findNodesByUxRole('empty-state'); // where are the empty states?
390
- agent.getPresentationWarnings(VIEW); // what is wrong with this screen?
391
- ```
392
-
393
- ### Presentation state
394
-
395
- `StateDef.ephemeral: true` marks state that is a UI fact rather than a domain fact — which
396
- panel is expanded, which tab is selected. Instance validation skips it and it may not be
397
- persisted, and an agent can tell it from domain state with `getEphemeralStates()`.
398
-
399
- Presentation never authorizes anything. Hiding a control is not the same as prohibiting an
400
- operation: a rule belongs in a precondition or a transition constraint, and a governed
401
- write is checked whether or not any control for it is visible.
402
-
403
- ### The escape hatch
404
-
405
- `rendererOverrides` attaches renderer-specific presentation, and is explicitly the thing
406
- semantic analysis does not understand:
407
-
408
- ```ts
409
- presentation: { rendererOverrides: { web: { className: 'legacy-panel' } } }
410
- ```
411
-
412
- It is keyed by renderer, it makes the node `opaque` in resolved presentation, it is
413
- reported as `OPAQUE_PRESENTATION`, and `AgentAPI.getOpaquePresentationNodes()` lists every
414
- node using it. Ordinary applications need none of it, and the acceptance fixtures use none.
415
-
416
- ## Diagnostics
417
-
418
- Failures are structured. Match on `code` rather than reading the message:
419
-
420
- ```ts
421
- import { RUNTIME_DIAGNOSTIC_CODES } from '@cynodia/axiom';
422
-
423
- const result = app.invokeAction(CONFIRM_ORDER);
424
- if (!result.ok) {
425
- const stock = result.diagnostics.find(
426
- (diagnostic) => diagnostic.code === RUNTIME_DIAGNOSTIC_CODES.PRECONDITION_FAILED,
427
- );
428
- console.log(stock?.details); // { preconditionIndex: 2, failureMode: 'insufficient-stock' }
429
- }
430
- ```
431
-
432
- `result.diagnostics` belongs to that invocation. `app.diagnostics()` keeps the history and
433
- `app.clearDiagnostics()` empties it.
118
+ | Compressed reference for authoring or modifying an app | `docs/AGENT_REFERENCE.md` |
119
+ | Exact runtime guarantees | `docs/SEMANTIC_CONTRACT.md` |
120
+ | Graph, ids, types, entity value representation | `docs/GRAPH_MODEL.md` |
121
+ | Every expression kind, builtin and scope rule | `docs/EXPRESSIONS.md` |
122
+ | Addressing writable positions | `docs/LOCATIONS.md` |
123
+ | Stored, derived, draft and ephemeral state | `docs/STATE.md` |
124
+ | Actions, operations, transactions, iteration | `docs/ACTIONS_TRANSACTIONS.md` |
125
+ | Constraints and transition constraints | `docs/CONSTRAINTS.md` |
126
+ | Semantic UI nodes and bindings | `docs/UI.md` |
127
+ | Presentation, UX intent, themes, formatting | `docs/PRESENTATION.md` |
128
+ | Runtime API and diagnostic codes | `docs/RUNTIME.md` |
129
+ | Machine queries and graph transformations | `docs/AGENT_API.md` |
130
+ | Validation codes | `docs/VALIDATION.md` |
131
+ | Mistakes that compile but are wrong | `docs/ANTI_PATTERNS.md` |
132
+
133
+ Start with `docs/AGENT_REFERENCE.md`. It plus the `.d.ts` declarations are intended to be
134
+ sufficient on their own.
434
135
 
435
136
  ## What is in the box
436
137
 
437
- `@cynodia/axiom` re-exports the framework packages, which can also be installed
438
- individually:
138
+ This package re-exports four, which can also be installed individually:
439
139
 
440
- | Package | Contents |
441
- | ------- | -------- |
442
- | `@cynodia/axiom-core` | The Application Graph, semantic types, expressions, locations, validation. |
443
- | `@cynodia/axiom-compiler` | Normalization into an IR, and self-contained page emission. |
444
- | `@cynodia/axiom-runtime` | The generic runtime: state, mutation engine, renderer, routing. |
445
- | `@cynodia/axiom-agent-api` | Semantic queries, mutation impact, transactional transformations. |
140
+ | Package | Responsibility |
141
+ | --- | --- |
142
+ | `@cynodia/axiom-core` | Graph, semantic types, expressions, locations, presentation, themes, validation. |
143
+ | `@cynodia/axiom-compiler` | Normalization into an IR, theme stylesheet, page emission. |
144
+ | `@cynodia/axiom-runtime` | State store, evaluation, mutation engine, constraint checking, renderer, routing. |
145
+ | `@cynodia/axiom-agent-api` | Semantic and presentation queries, mutation impact, transactional transformations. |
446
146
 
447
147
  ## License
448
148