@cynodia/axiom 0.5.2-alpha.1 → 0.6.0-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
@@ -6,7 +6,7 @@ Axiom represents application behavior, state, UI structure and presentation as s
6
6
  semantic data executed by generic runtimes. An application is a typed graph, not source
7
7
  files: the JavaScript and HTML that reach the browser are output, and are never edited.
8
8
 
9
- **Status: experimental / alpha (0.5.2-alpha.x).** The API may change between alpha
9
+ **Status: experimental / alpha (0.6.0-alpha.x).** The API may change between alpha
10
10
  releases. The documentation in `docs/` describes this exact version.
11
11
 
12
12
  ## Installation
@@ -1,6 +1,6 @@
1
1
  # Actions and transactions
2
2
 
3
- Axiom 0.5.2-alpha.1. An action is behavior expressed as data, executed as a transaction.
3
+ Axiom 0.6.0-alpha.1. An action is behavior expressed as data, executed as a transaction.
4
4
 
5
5
  ```ts
6
6
  {
@@ -62,6 +62,10 @@ re-render
62
62
  `invokeAction(id, args?)` returns `{ ok, diagnostics }` for **that invocation**. No diffing
63
63
  of global history is needed.
64
64
 
65
+ An action that writes server-authoritative state runs this lifecycle **on the authority**
66
+ instead, unchanged; the client dispatches it and receives the result. See
67
+ [`AUTHORITY.md`](AUTHORITY.md).
68
+
65
69
  All four failure sources are evaluated; the action does not stop at the first.
66
70
 
67
71
  ## Operations
@@ -159,6 +163,20 @@ The controlled boundary for behavior the operation vocabulary cannot express.
159
163
  **Use it only where no semantic primitive exists.** A native operation is opaque to every
160
164
  analysis Axiom offers.
161
165
 
166
+ ## Authorization
167
+
168
+ ```ts
169
+ authorization?: Expression
170
+ ```
171
+
172
+ Whether the caller may invoke this action at all, evaluated **on the authority** with the
173
+ caller bound to `PRINCIPAL`, before any guard and before any transaction opens. It is
174
+ stripped from the client IR, so a client never learns the rule and cannot satisfy it by
175
+ claiming to.
176
+
177
+ A guard asks whether the application's state permits this invocation. Authorization asks
178
+ whether this *caller* may make it. See [`AUTHORITY.md`](AUTHORITY.md#authentication-and-authorization).
179
+
162
180
  ## Postconditions
163
181
 
164
182
  Evaluated after the operations, inside the transaction, against proposed state. A failure
package/docs/AGENT_API.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent API
2
2
 
3
- Axiom 0.5.2-alpha.1. The machine-facing interface. Agents query semantics and apply
3
+ Axiom 0.6.0-alpha.1. The machine-facing interface. Agents query semantics and apply
4
4
  structural transformations; they never edit generated code.
5
5
 
6
6
  ```ts
@@ -141,6 +141,24 @@ Presentation resolution is recomputed per call rather than cached: a transaction
141
141
  the graph underneath these queries, and a stale presentation answer would be worse than a
142
142
  slow one.
143
143
 
144
+ ## Authority queries
145
+
146
+ ```ts
147
+ agent.getAuthority(stateId) // 'client' | 'server'
148
+ agent.getActionAuthority(actionId) // where it executes — derived, not declared
149
+ agent.getServerActions()
150
+ agent.getClientWritableStates()
151
+ agent.getServerWritableStates()
152
+ agent.getServerOnlyStates() // what the client never receives
153
+ agent.getActionsAffectingServerState() // [{ action, stateIds }]
154
+ agent.getAuthorizationForAction(actionId) // the rule, or undefined
155
+ agent.getUnauthorizedServerActions() // server actions any caller may invoke
156
+ agent.getPersistenceForState(stateId)
157
+ ```
158
+
159
+ Authority is derived from what an action writes, so these answers cannot disagree with what
160
+ the graph does. See [`AUTHORITY.md`](AUTHORITY.md).
161
+
144
162
  ## Transactions
145
163
 
146
164
  Every change is staged on a private copy. The graph an agent or a runtime can observe is
@@ -1,6 +1,6 @@
1
1
  # Agent reference
2
2
 
3
- Axiom 0.5.2-alpha.1. Compressed operational contract. Read this plus the `.d.ts`
3
+ Axiom 0.6.0-alpha.1. 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:
@@ -31,7 +31,7 @@ One canonical term per concept. These are not interchangeable.
31
31
  ## Graph construction
32
32
 
33
33
  ```ts
34
- const graph = new ApplicationGraph(id, name); // version defaults to '0.5.2'
34
+ const graph = new ApplicationGraph(id, name); // version defaults to '0.6.0'
35
35
  graph.addNode<StateDef>({ id, kind: 'state', ... }); // returns NodeId; throws if id exists
36
36
  graph.getNode<StateDef>(id); // deep clone, or undefined
37
37
  graph.updateNode(node); // write a modified node back
@@ -469,6 +469,65 @@ agent.transact((tx) => { tx.setDensity(FORM, 'compact'); }, { reason });
469
469
  a native operation with undeclared effects — cannot be analyzed. **An incomplete answer
470
470
  says so; it is never presented as exhaustive.** Detail: [`AGENT_API.md`](AGENT_API.md).
471
471
 
472
+ ## SERVER AUTHORITY
473
+
474
+ Full model: [`AUTHORITY.md`](AUTHORITY.md). These are the invariants to know before
475
+ authoring an application that crosses the trust boundary.
476
+
477
+ 1. **AUTHORITY** — a client cannot commit server-authoritative state, by any path.
478
+ 2. **EXECUTION** — a server action executes against state the authority owns, on the authority.
479
+ 3. **TRUST** — a client's validation results, derived values and claims are never authoritative.
480
+ 4. **TRANSACTION** — one semantic action commits atomically or not at all, wherever it runs.
481
+ 5. **CONCURRENCY** — two actions cannot both commit from incompatible snapshots.
482
+ 6. **PROTOCOL** — a client requests semantic actions, never mutation programs.
483
+ 7. **SERIALIZATION** — authoritative behavior is data. No closure, no arbitrary code.
484
+
485
+ ```ts
486
+ { id: STATE_PRODUCTS, kind: 'state', authority: 'server' } // the authority owns it
487
+ { id: STATE_AUDIT, kind: 'state', authority: 'server', serverOnly: true } // and the client never sees it
488
+ ```
489
+
490
+ - `authority` defaults to `'client'`. **Every 0.5.x graph is unchanged and still runs with no server.**
491
+ - Authority is separate from persistence: one says who decides a value, the other where a decided value survives.
492
+ - **Where an action executes is derived, never declared**: an action that writes any server-authoritative state is a server action.
493
+ - A server action reaches the client as its id, name and parameters only — no operations, no guards, no failure modes, no authorization.
494
+ - A server action MUST NOT read client state. Pass the value as an action parameter; that is what a draft is for.
495
+
496
+ ```ts
497
+ compileToIR(graph) // the client half, filtered at the boundary
498
+ compileToServerIR(graph) // what an authority executes: no UI, no presentation, no routes
499
+ ```
500
+
501
+ ### Authorization
502
+
503
+ ```ts
504
+ graph.setPrincipalEntity(ENTITY_USER);
505
+ { kind: 'action', authorization: binary('eq', field(ref(PRINCIPAL), F_ROLE), literal('admin')), … }
506
+ ```
507
+
508
+ `PRINCIPAL` is bound to a record keyed by the principal entity's field ids, and **only where
509
+ an authority evaluates**. A rule that cannot be evaluated denies. `requiresConfirmation` is
510
+ interaction, not authorization.
511
+
512
+ ### Running one
513
+
514
+ ```ts
515
+ const server = createAxiomServer({ ir: compileToServerIR(graph), persistence, host });
516
+ await server.start();
517
+ await serveOverHttp({ server, port: 3000 });
518
+
519
+ const app = createAxiomRuntime({ ir, rootElement, host, remote: createRemoteGateway(transport) });
520
+ await app.syncAuthoritativeState();
521
+ ```
522
+
523
+ A remote invocation returns `{ ok: false, pending: true }` and its outcome arrives later,
524
+ through the same action-outcome lifecycle a local refusal uses — so a `diagnostic` node
525
+ presents a server refusal exactly as it presents a local one.
526
+
527
+ Boundary diagnostics: `UNKNOWN_SERVER_ACTION` `ARGUMENT_TYPE_MISMATCH` `AUTHORIZATION_DENIED`
528
+ `CONCURRENCY_CONFLICT` `MALFORMED_REQUEST` `AUTHORITY_UNREACHABLE`, plus `SERVER_STATE_WRITE`
529
+ and `REMOTE_ACTION_UNAVAILABLE` on the client.
530
+
472
531
  ## Serialization
473
532
 
474
533
  The whole graph, theme included, is JSON. `graph.serialize()` /
@@ -1,6 +1,6 @@
1
1
  # Anti-patterns
2
2
 
3
- Axiom 0.5.2-alpha.1. Each of these compiles. Each is wrong. Each is followed by the correct
3
+ Axiom 0.6.0-alpha.1. 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
@@ -334,7 +334,66 @@ Renderer-generated ids are keyed by **render instance**: `data-node` is the sema
334
334
  all — see anti-pattern 3 — but a test or a host that must locate an element should select on
335
335
  `data-node` and pick the instance it means.
336
336
 
337
- ## 26. Restating what the model already knows
337
+ ## 26. Binding an input into server-authoritative state
338
+
339
+ ```ts
340
+ // WRONG — rejected by validateGraph (CLIENT_WRITE_TO_SERVER_STATE), and refused at run time.
341
+ { kind: 'input', binding: { location: itemFieldLocation(STATE_PRODUCTS, F_ID, …, F_STOCK) } }
342
+
343
+ // RIGHT — edit a client draft, and commit it through an action the authority executes.
344
+ { kind: 'input', binding: { location: stateLocation(STATE_DRAFT_QUANTITY) } }
345
+ { kind: 'button', actionId: ACTION_PLACE_ORDER, arguments: { [PARAM_QUANTITY]: ref(STATE_DRAFT_QUANTITY) } }
346
+ ```
347
+
348
+ Do not force every keystroke across the boundary either: a draft is client-local on purpose.
349
+
350
+ ## 27. A server action reading client state
351
+
352
+ ```ts
353
+ // WRONG — the authority does not have the client's draft. SERVER_DEPENDS_ON_CLIENT_STATE.
354
+ { kind: 'action', operations: [{ kind: 'insert', target: stateLocation(STATE_ORDERS), value: ref(STATE_DRAFT) }] }
355
+
356
+ // RIGHT — the client proposes values as arguments; the authority decides.
357
+ {
358
+ kind: 'action',
359
+ parameters: [{ id: PARAM_QUANTITY, valueType: primitiveType('number'), required: true }],
360
+ operations: [{ kind: 'insert', target: stateLocation(STATE_ORDERS), value: object([…]) }],
361
+ }
362
+ ```
363
+
364
+ ## 28. Treating client-side checks as authoritative
365
+
366
+ ```ts
367
+ // WRONG — a guard the client evaluated proves nothing. So does an argument saying so.
368
+ invoke(ACTION_PLACE_ORDER, { quantity: 5, alreadyValidated: true })
369
+ ```
370
+
371
+ The authority re-evaluates guards, constraints, transition rules, argument types and
372
+ authorization against **its own** state, every time. An extra argument is not a parameter and
373
+ is refused outright. Confirmation is interaction, never authorization.
374
+
375
+ ## 29. Putting a secret in the graph
376
+
377
+ ```ts
378
+ // WRONG — a graph is a serializable artifact, and this state ships to the client.
379
+ { id: STATE_API_KEY, kind: 'state', valueType: primitiveType('string'), initialValue: 'sk-…' }
380
+ ```
381
+
382
+ Server-only information belongs behind `authority: 'server'` with `serverOnly: true`, and a
383
+ credential belongs to the host, not the graph.
384
+
385
+ ## 30. Reaching for a native operation to make an external call
386
+
387
+ ```ts
388
+ // WRONG — a database write rolls back; an email does not. Nothing makes this participate
389
+ // in the transaction, and pretending it does is worse than not having it.
390
+ { kind: 'native', implementationId: 'app.sendEmail' }
391
+ ```
392
+
393
+ External effects are deliberately unsolved in 0.6. A design involving commands, a
394
+ transactional outbox and idempotency is future work; do not smuggle one in meanwhile.
395
+
396
+ ## 31. Restating what the model already knows
338
397
 
339
398
  ```ts
340
399
  // UNNECESSARY — required comes from the field, the label from the field's name, the
@@ -0,0 +1,330 @@
1
+ # Authority
2
+
3
+ Axiom 0.6.0-alpha.1. How an application crosses the trust boundary.
4
+
5
+ Until 0.5.x an Axiom application executed locally. 0.6 adds an **authority**: a generic
6
+ runtime that owns state, decides mutations and persists them. The same semantic graph
7
+ describes both halves, so there is no backend to write.
8
+
9
+ ```text
10
+ ApplicationGraph
11
+ │
12
+ ┌────────────────┴────────────────┐
13
+ compileToIR compileToServerIR
14
+ │ │
15
+ Client runtime ──semantic protocol──▶ Authority
16
+ │ │
17
+ presentation PersistenceAdapter
18
+ ```
19
+
20
+ ## Load-bearing server invariants
21
+
22
+ 1. **AUTHORITY** — a client cannot commit server-authoritative state, by any path.
23
+ 2. **EXECUTION** — a server action executes against state the authority owns, on the authority.
24
+ 3. **TRUST** — a client's validation results, derived values and claims are never authoritative.
25
+ 4. **TRANSACTION** — one semantic action commits atomically or not at all, wherever it runs.
26
+ 5. **CONCURRENCY** — two actions cannot both commit from incompatible snapshots.
27
+ 6. **PROTOCOL** — a client requests semantic actions, never mutation programs.
28
+ 7. **SERIALIZATION** — authoritative behavior is data. No closure, no arbitrary code.
29
+
30
+ ## Authority and persistence are different questions
31
+
32
+ | | Asks | Declared by |
33
+ | --- | --- | --- |
34
+ | **Authority** | who may commit this value | `StateDef.authority` |
35
+ | **Persistence** | where a committed value survives | `StateDef.persistence`, and the adapter the authority runs with |
36
+
37
+ A server-authoritative state may live only in memory; a client-authoritative state may be
38
+ persisted to local storage. Do not conflate them.
39
+
40
+ ```ts
41
+ { id: STATE_PRODUCTS, kind: 'state', authority: 'server', valueType: … } // the authority owns it
42
+ { id: STATE_DRAFT, kind: 'state', draft: true, valueType: … } // the client owns it
43
+ { id: STATE_AUDIT, kind: 'state', authority: 'server', serverOnly: true } // and the client never sees it
44
+ ```
45
+
46
+ - `authority` defaults to `'client'`, so **every 0.5.x graph is unchanged** and still runs with no server at all.
47
+ - `serverOnly: true` excludes the state from the client IR entirely. It is not merely unwritable; it is absent.
48
+
49
+ ## Where an action executes
50
+
51
+ **Derived, never declared.** An action that writes any server-authoritative state is a
52
+ server action — following `for-each`, `invoke` and declared native effects. It cannot
53
+ disagree with what the action actually does.
54
+
55
+ ```ts
56
+ agent.getActionAuthority(ACTION_PLACE_ORDER); // 'server'
57
+ agent.getServerActions();
58
+ ```
59
+
60
+ A server action is dispatched by the client, not executed by it: its operations, guards and
61
+ authorization are **absent** from the client IR.
62
+
63
+ ## The trust boundary
64
+
65
+ The client is untrusted. The authority never trusts client state, client-derived values,
66
+ client constraint results, client permission checks, client presentation state, or any
67
+ claim that validation already happened.
68
+
69
+ | Attempt | Refused by |
70
+ | --- | --- |
71
+ | Bind an input into server state | `validateGraph` → `CLIENT_WRITE_TO_SERVER_STATE` |
72
+ | Write server state from the client at run time | the client store's single write path → `SERVER_STATE_WRITE` |
73
+ | Execute a server action locally | it has no operations in the client IR |
74
+ | Send mutation operations | the protocol has no such request |
75
+ | Forge an action id | resolved from the authority's own IR → `UNKNOWN_SERVER_ACTION` |
76
+ | Send an argument of the wrong shape | checked against the declared type → `ARGUMENT_TYPE_MISMATCH` |
77
+ | Invoke without permission | `authorization`, evaluated on the authority → `AUTHORIZATION_DENIED` |
78
+ | Read server-only state | it is not in the client IR, the snapshot or any answer |
79
+
80
+ ## Server IR
81
+
82
+ ```ts
83
+ const serverIR = compileToServerIR(graph); // throws GraphValidationError if invalid
84
+ ```
85
+
86
+ `ServerIR` is what an authority executes: entities, the state authoritative execution needs,
87
+ the server actions in full, constraints, transition constraints, the principal entity, and
88
+ which states a client may observe. It carries **no UI, no presentation, no theme and no
89
+ routes**, because none of that decides anything.
90
+
91
+ It is plain JSON — deterministic, closure-free, and specific to no language or host. It
92
+ declares `contract: 'axiom.server.v1'`, and a runtime that does not recognize the value MUST
93
+ refuse it rather than interpret it partially.
94
+
95
+ Guards are normalized into aligned `preconditions` / `failureModes`, exactly as in the
96
+ client IR, so an authority that read one and not the other cannot silently skip a check.
97
+
98
+ ## Client IR
99
+
100
+ `compileToIR` is authority-aware. From the same graph it produces the client's half:
101
+
102
+ | In the graph | In the client IR |
103
+ | --- | --- |
104
+ | A server-authoritative state | present, typed, with `initialValue` stripped — the authority owns the value |
105
+ | A `serverOnly` state | **absent**, along with any edge, constraint or transition rule that names it |
106
+ | A server action | its id, name, parameters and confirmation only — no operations, no guards, no failure modes, no authorization |
107
+ | `authority` | `ApplicationIR.authority`, so the client runtime refuses to write what it does not own |
108
+ | Server actions | `ApplicationIR.remoteActionIds`, so the runtime dispatches instead of executing |
109
+
110
+ Entity type declarations are shared: they are the schema, not the rules.
111
+
112
+ ## Executing an action
113
+
114
+ The authority runs **the same semantic engine** the client runs. Transaction boundaries,
115
+ provisional writes, `for-each` ordering, constraint and transition evaluation, rollback and
116
+ the mutation log are not reimplemented, so a graph cannot behave differently merely because
117
+ execution moved.
118
+
119
+ ```text
120
+ resolve the action from the authority's own IR → UNKNOWN_SERVER_ACTION
121
+ authenticate the credential (host)
122
+ validate arguments against declared types → ARGUMENT_TYPE_MISMATCH
123
+ evaluate authorization with PRINCIPAL bound → AUTHORIZATION_DENIED
124
+ ── nothing above opens a transaction ──
125
+ BEGIN TRANSACTION (the ordinary Axiom lifecycle, unchanged)
126
+ COMMIT to persistence, atomically → CONCURRENCY_CONFLICT
127
+ answer with diagnostics and authoritative changes
128
+ ```
129
+
130
+ A refusal at any stage before the commit leaves authoritative state exactly as it was.
131
+
132
+ ## Authentication and authorization
133
+
134
+ They are separate. **Authentication** — who is asking — is the host's business:
135
+
136
+ ```ts
137
+ createServerHost({
138
+ authenticate: (credential) => resolveUser(credential), // → a PrincipalRecord, or null
139
+ });
140
+ ```
141
+
142
+ Axiom 0.6 ships no authentication provider. **Authorization** — whether this caller may
143
+ perform this operation — is semantic:
144
+
145
+ ```ts
146
+ graph.setPrincipalEntity(ENTITY_USER);
147
+
148
+ {
149
+ kind: 'action',
150
+ authorization: binary('eq', field(ref(PRINCIPAL), F_USER_ROLE), literal('admin')),
151
+ …
152
+ }
153
+ ```
154
+
155
+ - `PRINCIPAL` is bound to a record keyed by the **principal entity's field ids**, so a rule is written with the ordinary `field`/`ref` vocabulary. There is no separate policy language.
156
+ - It is bound **only where an authority evaluates**. Reading it in a derivation or a UI expression is `PRINCIPAL_REFERENCE_ON_CLIENT`.
157
+ - A rule that cannot be evaluated **denies**, exactly as an unevaluable constraint counts as violated.
158
+ - No `authorization` means every caller may invoke the action. An application with server state and no authorization anywhere gets a warning.
159
+ - `requiresConfirmation` is interaction, asked by the client. It is **not** an authorization mechanism and the authority never treats it as one.
160
+
161
+ **Read authorization is not solved in 0.6.** Observability is per state
162
+ (`serverOnly` or not), not per caller and not per record. An application whose users must
163
+ see different rows of the same collection needs a mechanism this release does not provide.
164
+
165
+ ## Persistence
166
+
167
+ ```ts
168
+ interface PersistenceAdapter {
169
+ load(): Promise<PersistedState[]>;
170
+ commit(commit: PersistenceCommit): Promise<CommitOutcome>;
171
+ revision(): Promise<number>;
172
+ close?(): Promise<void>;
173
+ }
174
+ ```
175
+
176
+ - A semantic transaction that writes several states MUST persist as **one unit**. An adapter must never apply a subset.
177
+ - `commit` carries the revision each written state had when the transaction began. A mismatch MUST refuse the commit rather than overwrite.
178
+ - Derived state is recomputed, never stored.
179
+
180
+ | Adapter | For |
181
+ | --- | --- |
182
+ | `createMemoryPersistence(seed?)` | Deterministic tests, conformance runs, experimentation. Implements the whole model, revision checks included. |
183
+ | `createSqlitePersistence({ location })` | The durable reference, on Node's built-in `node:sqlite`. |
184
+
185
+ State is stored in **document form** — one row per state, holding its serialized semantic
186
+ value and its revision. That is deliberate: 0.6 is about persistence semantics. Relational
187
+ projection, migrations and schema generation are future work, and no ApplicationGraph
188
+ mentions SQL.
189
+
190
+ ## Concurrency
191
+
192
+ The authority **serializes** execution: one action at a time, and its commit completes
193
+ before the next begins. Within one process that alone prevents a lost update.
194
+
195
+ The revision check is the second layer, and the one that matters beyond a single process: a
196
+ commit whose expected revisions no longer hold is refused, in-memory state is restored to
197
+ what it was, and the caller receives `CONCURRENCY_CONFLICT`.
198
+
199
+ ```text
200
+ stock 5 · caller A wants 4 · caller B wants 4 → exactly one commits
201
+ ```
202
+
203
+ Multi-node execution is not a 0.6 goal, but the interface does not preclude it: an adapter
204
+ backed by a shared store already refuses a stale commit.
205
+
206
+ ## Idempotency
207
+
208
+ A network retry must not run an action twice.
209
+
210
+ ```ts
211
+ { kind: 'invoke', actionId, arguments, requestId: 'a-stable-key' }
212
+ ```
213
+
214
+ The authority remembers recent request ids and answers a repeat from the record, marking it
215
+ `replayed: true`. The client runtime generates one per invocation automatically. Exactly-once
216
+ delivery is not assumed.
217
+
218
+ ## The protocol
219
+
220
+ Transport-independent by construction. One endpoint, semantic requests:
221
+
222
+ ```ts
223
+ { kind: 'snapshot', protocol: 'axiom.protocol.v1', credential? }
224
+ { kind: 'invoke', protocol: 'axiom.protocol.v1', actionId, arguments?, credential?, requestId? }
225
+ ```
226
+
227
+ ```ts
228
+ { kind: 'result', ok, diagnostics, changes, revision, requestId?, replayed? }
229
+ { kind: 'snapshot', snapshot: { revision, states } }
230
+ { kind: 'error', diagnostics }
231
+ ```
232
+
233
+ No graph declares `POST /orders`. Transports:
234
+
235
+ | Adapter | Use |
236
+ | --- | --- |
237
+ | `createDirectTransport(server, { credential? })` | In-process. A whole client/authority test with no port. |
238
+ | `createHttpTransport({ url, credential?, timeoutMs? })` | The reference network transport. |
239
+
240
+ A later WebSocket, worker or IPC transport changes nothing in a graph.
241
+
242
+ ## Observing authoritative state
243
+
244
+ The model is the simplest correct one: **request, decide, apply**.
245
+
246
+ ```ts
247
+ const app = createAxiomRuntime({ ir, rootElement, host, remote: createRemoteGateway(transport) });
248
+ app.start();
249
+ await app.syncAuthoritativeState(); // load the authoritative snapshot
250
+ ```
251
+
252
+ - A remote invocation returns `{ ok: false, pending: true }` immediately, so a click never blocks on the network. The outcome arrives later and is recorded through the ordinary action-outcome lifecycle — which is how a `diagnostic` node presents a *server* refusal exactly as it presents a local one.
253
+ - `invokeActionAsync(id, args)` awaits the answer, for tests and programmatic callers.
254
+ - The answer's `changes` are applied through the client's single write path, under the one flag that permits writing server-owned state.
255
+ - **Optimistic updates are not implemented.** There is no client-side rollback to get wrong.
256
+ - A derivation that depends only on observed state is recomputed locally rather than transferred.
257
+
258
+ ## Diagnostics
259
+
260
+ Server codes join the same structured vocabulary; a client matches on `code` exactly as it
261
+ does for a local failure.
262
+
263
+ | Code | Meaning |
264
+ | --- | --- |
265
+ | `UNKNOWN_SERVER_ACTION` | The request named an action this authority does not execute. |
266
+ | `ARGUMENT_TYPE_MISMATCH` | An argument did not conform to its declared parameter type, or is not a parameter at all. |
267
+ | `AUTHORIZATION_DENIED` | The caller may not invoke this action, or the rule could not be evaluated. |
268
+ | `CONCURRENCY_CONFLICT` | Another transaction committed the same state first. Nothing was applied. |
269
+ | `MALFORMED_REQUEST` | The request was not an Axiom semantic request, or spoke an unknown protocol. |
270
+ | `AUTHORITY_UNREACHABLE` | The authority could not be reached, timed out, or answered with a transport error. |
271
+
272
+ Two client-side codes belong to the boundary as well:
273
+
274
+ | Code | Meaning |
275
+ | --- | --- |
276
+ | `SERVER_STATE_WRITE` | A local write was attempted against state the authority owns. It did not occur. |
277
+ | `REMOTE_ACTION_UNAVAILABLE` | A remote action was invoked with no gateway configured, or the transport failed. |
278
+
279
+ A network failure becomes a diagnostic, never an exception escaping into application code.
280
+
281
+ ## Observability
282
+
283
+ ```ts
284
+ createServerHost({ report: (event) => log(event) });
285
+ ```
286
+
287
+ Every invocation reports its kind, action, the caller's **identity field only**, request id,
288
+ outcome, duration, revision, diagnostics and the states it committed. No application
289
+ implements logging of its own.
290
+
291
+ ## Running one
292
+
293
+ ```bash
294
+ node packages/cli/dist/index.js serve app.js --export=createGraph --port=3000 --store=state.db
295
+ ```
296
+
297
+ One process serves the client page and the authority on one semantic endpoint. Nothing
298
+ application-specific is generated: no routes, no controllers, no handlers, no SQL. The
299
+ deployment artifact is the client page plus a serialized Server IR, executed by a generic
300
+ runtime.
301
+
302
+ ```ts
303
+ const server = createAxiomServer({ ir: serverIR, persistence, host });
304
+ await server.start();
305
+ await serveOverHttp({ server, port: 3000 });
306
+ ```
307
+
308
+ ## Conformance
309
+
310
+ `@cynodia/axiom-server` ships `conformance/*.json`. Each fixture is pure data — a Server IR,
311
+ the state to start from, invocations, and expected results — covering expression evaluation,
312
+ guards, mutation, rollback, constraints, transition constraints, `for-each` provisional
313
+ writes, authorization, argument validation, persistence, restart, idempotency and concurrent
314
+ mutation.
315
+
316
+ Running them requires no part of this implementation. That is the point: the Server IR
317
+ specification plus these fixtures are the whole contract, so an independent runtime in
318
+ another language can be held to exactly the same standard.
319
+
320
+ ## Not in 0.6
321
+
322
+ Stated plainly rather than left to discovery:
323
+
324
+ - **Read authorization per caller or per record.** Visibility is per state.
325
+ - **External effects.** A database write rolls back; an email does not. Nothing here makes an external side effect participate in a transaction, and `NativeOperation` MUST NOT be used to smuggle one in. A deliberate effect model — commands, a transactional outbox, idempotency — is future work.
326
+ - **Realtime synchronization**, subscriptions and collaboration. Request/response only.
327
+ - **Query semantics.** Authoritative collections are loaded into runtime state; large-data querying needs its own design.
328
+ - **Relational schema generation**, migrations and ORM behaviour.
329
+ - **Multi-node distributed execution.** Correctness is guaranteed within one authority process.
330
+ - **File storage, background jobs, scheduling.**
@@ -1,6 +1,6 @@
1
1
  # Constraints
2
2
 
3
- Axiom 0.5.2-alpha.1. Two constructs, answering different questions. They are not
3
+ Axiom 0.6.0-alpha.1. Two constructs, answering different questions. They are not
4
4
  interchangeable.
5
5
 
6
6
  | | Question | Sees |
@@ -1,6 +1,6 @@
1
1
  # Expressions
2
2
 
3
- Axiom 0.5.2-alpha.1. An expression describes **what value is computed**. It is a tree of
3
+ Axiom 0.6.0-alpha.1. An expression describes **what value is computed**. It is a tree of
4
4
  plain data, never source text and never a callback. Evaluation is pure: an expression MUST
5
5
  NOT change state.
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Graph model
2
2
 
3
- Axiom 0.5.2-alpha.1. The `ApplicationGraph` is the authoritative representation of an
3
+ Axiom 0.6.0-alpha.1. The `ApplicationGraph` is the authoritative representation of an
4
4
  application. Everything else — the IR, the page, the DOM — is derived from it and is never
5
5
  edited.
6
6
 
@@ -25,7 +25,7 @@ edited.
25
25
  ## API
26
26
 
27
27
  ```ts
28
- const graph = new ApplicationGraph(id, name, version?); // version defaults to '0.5.2'
28
+ const graph = new ApplicationGraph(id, name, version?); // version defaults to '0.6.0'
29
29
 
30
30
  graph.addNode<T>(node): NodeId // generates an id if omitted; throws if it exists
31
31
  graph.getNode<T>(id): T | undefined // deep clone
package/docs/LOCATIONS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Locations
2
2
 
3
- Axiom 0.5.2-alpha.1.
3
+ Axiom 0.6.0-alpha.1.
4
4
 
5
5
  ```text
6
6
  Expression = a value
@@ -1,6 +1,6 @@
1
1
  # Presentation
2
2
 
3
- Axiom 0.5.2-alpha.1. Presentation is **semantic UX intent**, expressed as data on a UI
3
+ Axiom 0.6.0-alpha.1. Presentation is **semantic UX intent**, expressed as data on a UI
4
4
  node. It names roles, tokens and device classes. It never names a colour, a length, a media
5
5
  query or a CSS property.
6
6
 
package/docs/RUNTIME.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Runtime
2
2
 
3
- Axiom 0.5.2-alpha.1. The runtime executes an `ApplicationIR`. It is domain-independent: it
3
+ Axiom 0.6.0-alpha.1. The runtime executes an `ApplicationIR`. It is domain-independent: it
4
4
  contains no knowledge of any application.
5
5
 
6
6
  ## Constructing
@@ -14,6 +14,7 @@ const app = createAxiomRuntime({
14
14
  rootElement, // a DomElement
15
15
  host, // a HostEnvironment
16
16
  nativeOperations?: Record<string, (inputs) => unknown>,
17
+ remote?: RemoteGateway, // required for server-authoritative state
17
18
  inputValidation?: 'immediate' | 'deferred', // DEFAULT: 'immediate'
18
19
  recordMutationValues?: boolean, // default true
19
20
  });
@@ -71,6 +72,9 @@ carries `data-node="<node id>"`.
71
72
  | `getActionOutcome(id)` | — | The outcome of that action's most recent invocation. See below. |
72
73
  | `getMutationLog()` | — | Every attempted mutation, with source, path and outcome. |
73
74
  | `registerNativeOperation(id, fn)` | — | Registers an implementation for a `native` operation. |
75
+ | `invokeActionAsync(id, args?)` | **yes** | Awaits the outcome, including an authority's answer. |
76
+ | `syncAuthoritativeState()` | — | Loads the authoritative snapshot and applies it. See [`AUTHORITY.md`](AUTHORITY.md). |
77
+ | `evaluate(expression)` | — | Evaluates in the root scope, reporting rather than throwing. A pure read. |
74
78
 
75
79
  ### `hydrateState` bypasses semantic enforcement
76
80
 
@@ -196,6 +200,7 @@ interface RuntimeDiagnostic {
196
200
  | `DERIVED_STATE_WRITE` | An attempt to write derived state. The write did not occur. | any write path | — |
197
201
  | `UNKNOWN_STATE` | An attempt to write a state that does not exist. | any write path | — |
198
202
  | `INPUT_REJECTED` | **Warning.** An input write was rolled back; the control kept its previous value. Accompanied by the violation that caused it. | input binding | `source: 'input'` |
203
+ | `SERVER_STATE_WRITE` | A local write was attempted against state whose authority is the server. It did not occur — through any path, `hydrateState` included. | any write path | — |
199
204
 
200
205
  ### Operations, routing, UI
201
206
 
@@ -204,6 +209,7 @@ interface RuntimeDiagnostic {
204
209
  | `UNSUPPORTED_OPERATION` | An operation kind the runtime does not execute. | action | — |
205
210
  | `ROUTE_NOT_FOUND` | A `navigate` operation naming an unresolvable route. | action | — |
206
211
  | `NATIVE_OPERATION_MISSING` | No implementation registered for an `implementationId`. | `native` operation | — |
212
+ | `REMOTE_ACTION_UNAVAILABLE` | An action belonging to the authority was invoked with no gateway configured, or the transport failed. | remote invocation | — |
207
213
  | `UI_NODE_MISSING` | A child id that is not a UI node in the IR. | render | — |
208
214
  | `UNSUPPORTED_UI_NODE` | An unknown UI node kind. | render | — |
209
215
  | `PERSISTED_STATE_UNREADABLE` | **Warning.** A stored value could not be parsed; the initial value was used. | startup | — |
@@ -244,6 +250,20 @@ app.getMutationLog();
244
250
  - Only the outermost transaction decides an outcome, so the log never suggests that early iterations of a failed loop committed.
245
251
  - `recordMutationValues: false` omits `oldValue` / `newValue`.
246
252
 
253
+ ## Reaching an authority
254
+
255
+ An application with server-authoritative state gives the runtime a gateway:
256
+
257
+ ```ts
258
+ const app = createAxiomRuntime({ ir, rootElement, host, remote: createRemoteGateway(transport) });
259
+ app.start();
260
+ await app.syncAuthoritativeState();
261
+ ```
262
+
263
+ `invokeAction` on a remote action returns `{ ok: false, pending: true }` immediately — a
264
+ click never blocks on the network — and the outcome arrives through the ordinary
265
+ action-outcome lifecycle. Full model: [`AUTHORITY.md`](AUTHORITY.md).
266
+
247
267
  ## The browser bundle
248
268
 
249
269
  The runtime modules are ordinary type-checked TypeScript that import nothing at run time
@@ -1,6 +1,6 @@
1
1
  # Semantic contract
2
2
 
3
- Axiom 0.5.2-alpha.1. Runtime guarantees, stated formally. This file defines behavior; it
3
+ Axiom 0.6.0-alpha.1. Runtime guarantees, stated formally. This file defines behavior; it
4
4
  does not teach. Where this file and any specification in `doc/` disagree, this file
5
5
  describes the implementation and is authoritative.
6
6
 
@@ -181,6 +181,28 @@ location.
181
181
 
182
182
  Full per-kind semantics: [`EXPRESSIONS.md`](EXPRESSIONS.md).
183
183
 
184
+ ## Authority
185
+
186
+ Full model: [`AUTHORITY.md`](AUTHORITY.md).
187
+
188
+ - `StateDef.authority` is `'client'` unless declared otherwise, so a graph with no authority metadata behaves exactly as it did in 0.5.x and needs no server.
189
+ - A client MUST NOT commit a mutation to server-authoritative state through any path. An input bound into one is a validation error; a runtime write is refused at the store's single write path.
190
+ - Where an action executes is **derived** from what it writes, following `for-each`, `invoke` and declared native effects. It is never declared, so it cannot disagree with the action.
191
+ - A server action MUST NOT read client-authoritative state, and server state MUST NOT derive from it.
192
+ - State marked `serverOnly` MUST be absent from the client IR, from snapshots, and from every answer — along with anything the client receives that reads it.
193
+ - An authority MUST resolve an action from its own IR, and MUST NOT accept semantic definitions, operations or validation results from a caller.
194
+ - An authority MUST validate arguments against declared parameter types before executing. Network data is untyped input.
195
+ - `authorization` is evaluated on the authority, before any guard and before any transaction opens. A rule that cannot be evaluated MUST deny. `requiresConfirmation` is interaction and MUST NOT be treated as authorization.
196
+ - The transaction guarantees above hold unchanged on the authority: the same semantic engine executes both halves.
197
+ - A semantic transaction MUST persist atomically. An adapter MUST NOT apply a subset of a transaction's writes, and MUST refuse a commit whose expected revisions no longer hold.
198
+ - Two actions MUST NOT both commit from incompatible snapshots. The authority serializes execution, and a stale commit is refused with `CONCURRENCY_CONFLICT`.
199
+ - A repeated `requestId` MUST be answered from the record rather than executed again.
200
+ - Server IR MUST be serializable, deterministic and free of closures, and MUST declare a contract version a runtime can refuse.
201
+
202
+ **Not guaranteed in 0.6**, and stated so rather than implied: read authorization per caller
203
+ or per record, external side effects participating in a transaction, realtime
204
+ synchronization, query semantics, and multi-node execution.
205
+
184
206
  ## Diagnostics as semantic UI
185
207
 
186
208
  - The runtime MUST record the outcome of each action's most recent invocation: `'ok'`, `'failed'` or `'cancelled'`, with that invocation's diagnostics.
package/docs/STATE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # State
2
2
 
3
- Axiom 0.5.2-alpha.1. A `StateDef` is a named application value: stored, or computed from
3
+ Axiom 0.6.0-alpha.1. A `StateDef` is a named application value: stored, or computed from
4
4
  other state.
5
5
 
6
6
  ```ts
@@ -24,6 +24,10 @@ other state.
24
24
  | **Draft** | `draft: true` | as stored | skipped | no |
25
25
  | **Ephemeral** | `ephemeral: true` | as stored | skipped | no |
26
26
 
27
+ `authority: 'server'` is a separate axis: it says **who may commit** the value, not where it
28
+ is stored or whether it is validated. A client observes such a state and can never write it.
29
+ See [`AUTHORITY.md`](AUTHORITY.md).
30
+
27
31
  **Canonical** state means stored state that is neither draft nor ephemeral. It is the state
28
32
  constraints apply to.
29
33
 
@@ -144,7 +148,7 @@ rooted in.
144
148
  | --- | --- |
145
149
  | `memory` | The default. Nothing outside the runtime. |
146
150
  | `local-storage` | Read at startup, written through on every write, keyed by `key ?? "<graphId>:<stateId>"`. Requires a host that provides `storage`. |
147
- | `remote` | **Validates and does nothing.** Declared, not executed. |
151
+ | `remote` | **Validates and does nothing.** Declared, not executed. For state an authority owns, use `authority: 'server'` and give the authority a `PersistenceAdapter`. |
148
152
 
149
153
  ## Validation
150
154
 
package/docs/UI.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # UI
2
2
 
3
- Axiom 0.5.2-alpha.1. Ten semantic UI node kinds describe **what exists and what it does**.
3
+ Axiom 0.6.0-alpha.1. Ten semantic UI node kinds describe **what exists and what it does**.
4
4
  How it looks is [presentation](PRESENTATION.md).
5
5
 
6
6
  All nine share `UIBase`:
@@ -1,6 +1,6 @@
1
1
  # Validation
2
2
 
3
- Axiom 0.5.2-alpha.1. Validation is authoring-time structural checking. It is not the same
3
+ Axiom 0.6.0-alpha.1. Validation is authoring-time structural checking. It is not the same
4
4
  as runtime constraint evaluation — see [`CONSTRAINTS.md`](CONSTRAINTS.md) for the four
5
5
  layers of correctness.
6
6
 
@@ -36,7 +36,7 @@ non-numeric collections, and obviously incompatible assignments.
36
36
 
37
37
  ## Codes
38
38
 
39
- 49 codes, exported as `VALIDATION_CODES`. Every one is reachable.
39
+ 55 codes, exported as `VALIDATION_CODES`. Every one is reachable.
40
40
 
41
41
  ### Ids and references
42
42
 
@@ -78,6 +78,7 @@ non-numeric collections, and obviously incompatible assignments.
78
78
  | `IDENTITY_FIELD_MISMATCH` | An identity selector using a field of the wrong entity. |
79
79
  | `INVALID_SELECTOR_TYPE` | An index selector that is statically not a number. |
80
80
  | `EPHEMERAL_STATE_PERSISTED` | `ephemeral: true` together with `persistence`. |
81
+ | `CLIENT_WRITE_TO_SERVER_STATE` | An input bound into server-authoritative state. See [Authority](#authority). |
81
82
 
82
83
  ### Initial values
83
84
 
@@ -134,6 +135,21 @@ application from compiling.
134
135
  | `CONFLICTING_SIZING` | `minWidth` wider than `maxWidth`. | warning |
135
136
  | `OPAQUE_PRESENTATION` | A node carries `rendererOverrides`, which semantic analysis cannot understand. | warning |
136
137
 
138
+ ### Authority
139
+
140
+ The boundary between a client and an authority is structural, so a graph that could let a
141
+ client commit authoritative state does not compile. Full model:
142
+ [`AUTHORITY.md`](AUTHORITY.md).
143
+
144
+ | Code | Raised when | Severity |
145
+ | --- | --- | --- |
146
+ | `CLIENT_WRITE_TO_SERVER_STATE` | An input is bound into server-authoritative state. Bind it to a draft and commit through an action. | error |
147
+ | `SERVER_DEPENDS_ON_CLIENT_STATE` | A server action reads, or server state derives from, state the authority does not own. Pass the value as an action parameter. | error |
148
+ | `SERVER_ONLY_STATE_OBSERVED` | Something the client receives reads a `serverOnly` state — an input, a derivation, a UI expression. | error |
149
+ | `AUTHORIZATION_WITHOUT_PRINCIPAL` | An `authorization` expression with no principal entity declared. **Also raised as a warning** when an application has server state but no action declares authorization, so every caller may invoke everything. | error / warning |
150
+ | `PRINCIPAL_REFERENCE_ON_CLIENT` | `PRINCIPAL` is read where a client evaluates, or an `authorization` sits on an action no authority executes. | error |
151
+ | `INVALID_PRINCIPAL_ENTITY` | `principalEntityId` names something that is not an entity. | error |
152
+
137
153
  ### Accessibility
138
154
 
139
155
  Only structurally determinable checks. Nothing speculative.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom",
3
- "version": "0.5.2-alpha.1",
3
+ "version": "0.6.0-alpha.1",
4
4
  "description": "AI-native semantic web application framework.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",
@@ -32,10 +32,10 @@
32
32
  }
33
33
  },
34
34
  "dependencies": {
35
- "@cynodia/axiom-core": "0.5.2-alpha.1",
36
- "@cynodia/axiom-runtime": "0.5.2-alpha.1",
37
- "@cynodia/axiom-compiler": "0.5.2-alpha.1",
38
- "@cynodia/axiom-agent-api": "0.5.2-alpha.1"
35
+ "@cynodia/axiom-core": "0.6.0-alpha.1",
36
+ "@cynodia/axiom-runtime": "0.6.0-alpha.1",
37
+ "@cynodia/axiom-compiler": "0.6.0-alpha.1",
38
+ "@cynodia/axiom-agent-api": "0.6.0-alpha.1"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b tsconfig.json"