@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 +1 -1
- package/docs/ACTIONS_TRANSACTIONS.md +19 -1
- package/docs/AGENT_API.md +19 -1
- package/docs/AGENT_REFERENCE.md +61 -2
- package/docs/ANTI_PATTERNS.md +61 -2
- package/docs/AUTHORITY.md +330 -0
- package/docs/CONSTRAINTS.md +1 -1
- package/docs/EXPRESSIONS.md +1 -1
- package/docs/GRAPH_MODEL.md +2 -2
- package/docs/LOCATIONS.md +1 -1
- package/docs/PRESENTATION.md +1 -1
- package/docs/RUNTIME.md +21 -1
- package/docs/SEMANTIC_CONTRACT.md +23 -1
- package/docs/STATE.md +6 -2
- package/docs/UI.md +1 -1
- package/docs/VALIDATION.md +18 -2
- package/package.json +5 -5
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.
|
|
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.
|
|
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.
|
|
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
|
package/docs/AGENT_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent reference
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
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.
|
|
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()` /
|
package/docs/ANTI_PATTERNS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Anti-patterns
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
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.
|
|
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.**
|
package/docs/CONSTRAINTS.md
CHANGED
package/docs/EXPRESSIONS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Expressions
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
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
|
|
package/docs/GRAPH_MODEL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Graph model
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
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.
|
|
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
package/docs/PRESENTATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Presentation
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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`:
|
package/docs/VALIDATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Validation
|
|
2
2
|
|
|
3
|
-
Axiom 0.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
36
|
-
"@cynodia/axiom-runtime": "0.
|
|
37
|
-
"@cynodia/axiom-compiler": "0.
|
|
38
|
-
"@cynodia/axiom-agent-api": "0.
|
|
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"
|