@cynodia/axiom 0.11.2-alpha.1 → 0.12.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
@@ -33,7 +33,7 @@ Shorter forms of the same routing: [`AGENTS.md`](AGENTS.md) and [`llms.txt`](llm
33
33
  at this package's root.
34
34
 
35
35
  **Status: experimental / alpha.** The API may change between alpha releases. The
36
- documentation in `docs/` describes this exact version, `0.11.2-alpha.1`.
36
+ documentation in `docs/` describes this exact version, `0.12.0-alpha.1`.
37
37
 
38
38
  ## Installation
39
39
 
@@ -47,7 +47,7 @@ Every release of this project is a pre-release and npm's `latest` tag points at
47
47
  plain command above installs the current version. **There is no `alpha` dist-tag** — the tag
48
48
  was removed once it stopped tracking releases, and `npm install @cynodia/axiom@alpha` now
49
49
  fails with a 404. Pin the exact version instead when one is needed:
50
- `npm install @cynodia/axiom@0.11.2-alpha.1`.
50
+ `npm install @cynodia/axiom@0.12.0-alpha.1`.
51
51
 
52
52
  These are ES modules compiled to ES2022; import them with `import`, not `require`. There is
53
53
  no published Axiom CLI. `@cynodia/axiom-server`'s SQLite persistence adapter additionally
@@ -191,6 +191,7 @@ focused document when the reference is not specific enough for the question at h
191
191
  | Binary data: `BlobRef`, upload, download, authorization, orphans | [`docs/STORAGE.md`](docs/STORAGE.md) |
192
192
  | Large authoritative datasets: `QueryDef`, relationships, read policy, providers, cursors, cache | [`docs/QUERIES.md`](docs/QUERIES.md) |
193
193
  | Evolving a deployed schema: `MigrationDef`, fingerprint, the startup gate, `executeMigration`, providers | [`docs/MIGRATIONS.md`](docs/MIGRATIONS.md) |
194
+ | Running N authority processes at once: ownership, leases, fencing, delivery guarantees, version skew | [`docs/DISTRIBUTED_AUTHORITY.md`](docs/DISTRIBUTED_AUTHORITY.md) |
194
195
  | Machine queries, mutation impact and graph transformations | [`docs/AGENT_API.md`](docs/AGENT_API.md) |
195
196
 
196
197
  `docs/AGENT_REFERENCE.md` plus the `.d.ts` declarations are intended to be sufficient on
@@ -1,6 +1,6 @@
1
1
  # Actions and transactions
2
2
 
3
- Axiom 0.11.2-alpha.1. An action is behavior expressed as data, executed as a transaction.
3
+ Axiom 0.12.0-alpha.1. An action is behavior expressed as data, executed as a transaction.
4
4
 
5
5
  ```ts
6
6
  {
package/docs/AGENT_API.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent API
2
2
 
3
- Axiom 0.11.2-alpha.1. The machine-facing interface. Agents query semantics and apply
3
+ Axiom 0.12.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
@@ -1,6 +1,6 @@
1
1
  # Agent reference
2
2
 
3
- Axiom 0.11.2-alpha.1. Compressed operational contract. Read this plus the `.d.ts`
3
+ Axiom 0.12.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:
@@ -964,6 +964,76 @@ Diagnostics: `SCHEMA_MIGRATION_REQUIRED` `SCHEMA_INCOMPATIBLE` `MIGRATION_IN_PRO
964
964
  `MIGRATION_DESTRUCTIVE_UNMARKED` `INVALID_MIGRATION_OPERATION` `MIGRATION_TRANSFORM_IMPURE`
965
965
  `MIGRATION_TRANSFORM_TYPE_MISMATCH`.
966
966
 
967
+ ## DISTRIBUTED AUTHORITY
968
+
969
+ Full model: [`DISTRIBUTED_AUTHORITY.md`](DISTRIBUTED_AUTHORITY.md).
970
+
971
+ The authoritative runtime may run as **N processes at once** over one shared persistence
972
+ provider, with **no graph change and no application code that knows a cluster exists**. One
973
+ authority and N authorities produce the same committed state and the same framework-owned
974
+ async work. Deployment topology is not application semantics.
975
+
976
+ Quick answers:
977
+
978
+ | Question | Answer |
979
+ | --- | --- |
980
+ | Do I write locking code? | **No.** Never an application lock, never `SETNX` in a `native` op. |
981
+ | Do I need Redis? | **No.** `memory` and `SQLite` reference providers ship; 0.12 semantics use no provider's vocabulary. |
982
+ | Are external effects generically exactly-once? | **No.** Exactly-once *logical* creation and *durable completion*; at-least-once *physical* execution unless the provider is idempotent. |
983
+ | Can multiple authorities race work safely? | **Yes**, with a capable `coordination` provider — activated automatically, no flag. |
984
+ | Can a stale owner commit after losing ownership? | **No.** Every durable-work write is fenced on a per-resource generation; a reclaim advances it. |
985
+ | Is deployment topology graph semantics? | **No.** No node kind, operation or IR field is added; a distributed graph compiles byte-identically. |
986
+
987
+ `createAxiomServer({ coordination, workStorage, distributed: { instanceId, leaseDurationMs,
988
+ renewIntervalMs, workerConcurrency, claimBatchSize, pollIntervalMs } })` — an unsafe combo
989
+ (`renewIntervalMs >= leaseDurationMs`) **throws**. `server.authority()` →
990
+ `{ instanceId, distributed, coordination: capabilities|null, config, compatibilityKey }`;
991
+ `server.inspectDistributedWork()` → live effect work items + incompatible items.
992
+
993
+ Every framework-owned async unit — outbox effect, scheduled firing, subscription cursor — is
994
+ a leased, fenced, per-item ownership claim: `pending → claimed(ownerId, generation) →
995
+ succeeded/failed/retry`. Lease expiry authorises nothing; only a reclaim (which mints a
996
+ higher generation) fences the prior owner (`WORK_FENCED`). `logicalEffectId` (= the effect
997
+ intent id) never changes across retries; `idempotencyKey` defaults to it (§7 of the full
998
+ doc). Retry backoff is durable, not a process timer. An attempt whose outcome was never
999
+ recorded increments `uncertainAttempts` and is retried with the same key — Axiom never
1000
+ claims physical exactly-once.
1001
+
1002
+ Scheduled `interval` / `delay` firings are gated on a fenced claim keyed by
1003
+ `"<scheduleId>@<dueInstant>"` (epoch-aligned for intervals; the constant `afterMs` for a
1004
+ delay), so N pollers cause one firing. Missed firings: `catchUp` = `latest` (default) / `all`
1005
+ / N, always caught up by one authority.
1006
+
1007
+ External event ingestion deduplicates on `source + externalEventId` (durable payload
1008
+ fingerprint): `accepted` / `duplicate` / `EVENT_ID_CONFLICT` (same id, different payload) /
1009
+ `unidentified` (no stable id → at-least-once). An id is **never synthesised** from a
1010
+ timestamp, an instance id or a UUID.
1011
+
1012
+ Subscription cursors: per-subscription monotonic `sequence` (no cross-subscription order),
1013
+ fenced + monotonic advancement (`fenced` / `stale-sequence`), reconnect from the durable
1014
+ cursor through any authority.
1015
+
1016
+ Cache coherence: durable revision observation — each entry records `observedRevision`,
1017
+ re-checked against `persistence.revision()` before every authoritative read → staleness
1018
+ bound **0** revisions; correctness does not depend on broadcast. `CACHE_COHERENCE` states it.
1019
+
1020
+ Version skew: the **compatibility key** is `{ schemaVersion, schemaFingerprint,
1021
+ serverContract, semanticFingerprint }`, compared **fail-closed**. `semanticFingerprint`
1022
+ (core, versioned) hashes executable meaning — action bodies, operations, triggers, policies,
1023
+ queries, expression defs — and excludes names / UI / routes / themes / metadata / order;
1024
+ distinct from `schemaFingerprint`. Durable work is stamped with its creator's key; an
1025
+ authority whose key differs refuses to claim it (`INCOMPATIBLE_AUTHORITY`). Migration
1026
+ ownership stays 0.11 host-controlled — no second coordination system.
1027
+
1028
+ Providers advertise capabilities (`distributed-lease`, `fencing`, `atomic-work-claim`,
1029
+ `durable-retry`, `event-dedup`, `durable-subscription-cursor`, `revision-observation`); a
1030
+ missing one **fails explicitly**, never a silent single-node fallback. Portable
1031
+ `axiom.conformance.v6` fixtures (`conformance/distributed/`) + `runCoordinationConformanceSuite`.
1032
+ Server IR stays `axiom.server.v7`.
1033
+
1034
+ Diagnostics: `WORK_IN_PROGRESS` `WORK_FENCED` `WORK_NOT_CLAIMABLE` `INCOMPATIBLE_AUTHORITY`
1035
+ `EVENT_ID_CONFLICT`.
1036
+
967
1037
  ## Metadata classes
968
1038
 
969
1039
  ```ts
@@ -1,6 +1,6 @@
1
1
  # Anti-patterns
2
2
 
3
- Axiom 0.11.2-alpha.1. Each of these compiles. Each is wrong. Each is followed by the correct
3
+ Axiom 0.12.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
@@ -682,3 +682,130 @@ The stored version, fingerprint and step history are written only by the migrati
682
682
  executor, atomically with the work they describe. Hand-editing them produces
683
683
  `MIGRATION_FINGERPRINT_MISMATCH` or `MIGRATION_STATE_CORRUPTED` at startup — the gate's
684
684
  whole purpose is to catch exactly this.
685
+
686
+ ## 52. An application-written distributed lock (or `SETNX` in a `native` operation)
687
+
688
+ ```ts
689
+ // WRONG — the graph now encodes a deployment fact, and a second lock system exists.
690
+ { kind: 'native', name: 'acquireRedisLock', arguments: { key: literal('reboot:dev-1') } }
691
+ ```
692
+
693
+ Multi-authority safety is the framework's job, not the application's. `createAxiomServer` is
694
+ given a `coordination` provider and every class of framework-owned async work is leased and
695
+ fenced automatically. There is no `native` operation for locking and there will not be one;
696
+ `SETNX` / `Redlock` / a Postgres advisory lock are provider techniques that never appear in
697
+ a graph. See [`DISTRIBUTED_AUTHORITY.md`](DISTRIBUTED_AUTHORITY.md).
698
+
699
+ ## 53. A process-local "already executed" `Set` as deduplication
700
+
701
+ ```ts
702
+ // WRONG — resets on restart, and authority B has never heard of it.
703
+ const done = new Set<string>();
704
+ if (done.has(effectId)) return;
705
+ done.add(effectId);
706
+ ```
707
+
708
+ Deduplication for an authoritative server must be durable and shared. The outbox keys the
709
+ logical effect by its committed intent id and claims it with a fenced lease; external event
710
+ ingestion deduplicates on `source + externalEventId` against a durable payload fingerprint.
711
+ An in-memory set is neither durable nor cross-authority.
712
+
713
+ ## 54. `leader`-only application branches
714
+
715
+ ```ts
716
+ // WRONG — "am I the leader?" is not a question the graph may ask.
717
+ conditional(call('isLeaderInstance'), doTheWork, doNothing)
718
+ ```
719
+
720
+ Axiom is leaderless: ownership is per work item, and any healthy compatible authority may
721
+ claim any item. There is no leader for an application to branch on. If work should happen
722
+ once, model it as framework-owned async work (an effect, a trigger) and let the claim make
723
+ it once.
724
+
725
+ ## 55. A random UUID (or timestamp, or instance id) as an external-event dedup key
726
+
727
+ ```ts
728
+ // WRONG — every "duplicate" gets a fresh id, so nothing is ever deduplicated.
729
+ fireEvent(EVENT_STATUS, { ...payload, dedupKey: crypto.randomUUID() })
730
+ ```
731
+
732
+ Deduplication needs the *provider's* stable delivery id. If the source has no stable id,
733
+ ingestion is honestly at-least-once (`unidentified`) — synthesising uniqueness from a
734
+ receive timestamp, a random UUID or the authority instance id is not deduplication, it just
735
+ hides the fact that duplicates get through.
736
+
737
+ ## 56. A retry that creates a new logical effect
738
+
739
+ ```ts
740
+ // WRONG — every attempt is a new effect, so the external system is hit N times with N keys.
741
+ async function retry(effect) {
742
+ await outbox.enqueue({ ...effect, id: createNodeId() });
743
+ }
744
+ ```
745
+
746
+ `logicalEffectId` is the committed intent id and is stable for the life of the effect. A
747
+ retry re-claims the *same* work item under a new fencing generation and re-runs the physical
748
+ attempt with the *same* idempotency key. A new id defeats provider idempotency entirely.
749
+
750
+ ## 57. A completion write that is not conditional on the fencing generation
751
+
752
+ ```ts
753
+ // WRONG — a stalled owner that wakes after a reclaim overwrites the new owner's result.
754
+ await store.markComplete(effectId, result);
755
+ ```
756
+
757
+ Every durable-work completion / retry / cursor write must be conditional on the current
758
+ `(ownerId, generation)`. Lease expiry alone does not fence — but once another authority
759
+ reclaims (advancing the generation), the old owner's write must be rejected (`WORK_FENCED`).
760
+ Unconditional completion is a release-blocking defect.
761
+
762
+ ## 58. Assuming a global order across unrelated entities or events
763
+
764
+ ```ts
765
+ // WRONG — there is no total order to observe.
766
+ assert(eventA.sequence < eventB.sequence); // eventA and eventB from different subscriptions
767
+ ```
768
+
769
+ Ordering is **per semantic stream** only: monotonic `sequence` within one subscription.
770
+ There is no ordering across subscriptions, and none between a subscription and any other
771
+ event source. Code that depends on a global order is depending on an accident of which
772
+ authority happened to process what first.
773
+
774
+ ## 59. Relying on pub/sub delivery for cache correctness
775
+
776
+ ```ts
777
+ // WRONG — a dropped invalidation message leaves this authority serving stale data forever.
778
+ bus.on('invalidate', (key) => cache.delete(key));
779
+ // ...and nothing else checks freshness.
780
+ ```
781
+
782
+ Cache coherence is a *durable revision* mechanism, not a broadcast: each cache entry records
783
+ the store revision it was computed at, and every authoritative read re-checks the persisted
784
+ revision before serving. A broadcast invalidation is a latency optimisation; correctness
785
+ must survive it being lost entirely.
786
+
787
+ ## 60. Swallowing an uncertain external effect outcome
788
+
789
+ ```ts
790
+ // WRONG — "we didn't see a success, so it didn't happen" — then a non-idempotent retry.
791
+ if (!recordedSuccess) await chargeCardAgain(amount);
792
+ ```
793
+
794
+ If an authority sent the request and crashed before recording completion, Axiom cannot know
795
+ whether the effect happened. The contract is: retry per the delivery policy **reusing the
796
+ same idempotency key**, and mark the attempt uncertain (`uncertainAttempts`). Never assume
797
+ not-done, and never claim the physical side effect is exactly-once — with a non-idempotent
798
+ provider it may occur twice, and that must be visible, not hidden.
799
+
800
+ ## 61. Executing durable work under an incompatible build
801
+
802
+ ```ts
803
+ // WRONG — authority B, running an older graph, claims and runs work authority A queued.
804
+ await workStore.claim('effect', { ignoreCompatibilityKey: true });
805
+ ```
806
+
807
+ Durable work records the **compatibility key** of the build that created it —
808
+ `{ schemaVersion, schemaFingerprint, serverContract, semanticFingerprint }`. An authority
809
+ whose key differs refuses to claim it (`INCOMPATIBLE_AUTHORITY`); the item waits for a
810
+ compatible authority. A rolling deploy that lets an old build run new-schema work is exactly
811
+ the mixed-semantic execution the fail-closed check exists to prevent.
package/docs/AUTHORITY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Authority
2
2
 
3
- Axiom 0.11.2-alpha.1. How an application crosses the trust boundary.
3
+ Axiom 0.12.0-alpha.1. How an application crosses the trust boundary.
4
4
 
5
5
  Until 0.5.x an Axiom application executed locally. 0.6 adds an **authority**: a generic
6
6
  runtime that owns state, decides mutations and persists them. The same semantic graph
@@ -1,6 +1,6 @@
1
1
  # Constraints
2
2
 
3
- Axiom 0.11.2-alpha.1. Two constructs, answering different questions. They are not
3
+ Axiom 0.12.0-alpha.1. Two constructs, answering different questions. They are not
4
4
  interchangeable.
5
5
 
6
6
  | | Question | Sees |
@@ -0,0 +1,370 @@
1
+ # Distributed authority
2
+
3
+ *This document describes Axiom `0.12.0-alpha.1`.*
4
+
5
+ The authoritative runtime (`docs/AUTHORITY.md`) may run as **more than one process at the
6
+ same time**, over one shared persistence provider, without any change to the
7
+ `ApplicationGraph` and without any application code that knows a cluster exists. This is the
8
+ 0.12 "distributed authority" layer.
9
+
10
+ The whole of it is one sentence:
11
+
12
+ > **One authority instance and N authority instances produce the same committed state and
13
+ > the same framework-owned asynchronous work.** Deployment topology is not application
14
+ > semantics.
15
+
16
+ `docs/AGENT_REFERENCE.md` has the compressed Q&A. This is the full contract.
17
+
18
+ ---
19
+
20
+ ## 1. Topology transparency
21
+
22
+ An application is a graph. Running it on one process or on eight is an operational choice,
23
+ made entirely outside the graph. There is:
24
+
25
+ - no cluster-mode toggle an application calls, no `distributed: true` in the graph, no
26
+ leader election an application can observe;
27
+ - no node kind, operation, expression kind or Server IR field added by 0.12 — a distributed
28
+ graph compiles to the exact same `axiom.server.vN` document a single-authority one does;
29
+ - no `NativeOperation` for locking, and none will be added.
30
+
31
+ Distributed execution **activates automatically** when `createAxiomServer` is given a
32
+ `coordination` provider together with a durable `persistence` adapter. Given neither, the
33
+ authority runs exactly as it did before — single writer, in-process outbox.
34
+
35
+ ## 2. Ownership
36
+
37
+ Every unit of **framework-owned asynchronous work** — a transactional-outbox effect, a
38
+ scheduled trigger firing, a subscription delivery cursor — is executed by **exactly one
39
+ authority at a time** through a durable, leased, fenced, per-work-item ownership claim:
40
+
41
+ ```
42
+ work item
43
+ ↓ created once by a committed transaction
44
+ durable "pending" state
45
+ ↓ an authority acquires a lease
46
+ "claimed" by (ownerId, generation)
47
+ ↓ the authority performs one physical attempt
48
+ durable "completion" / "retry" state (written only while the claim is still current)
49
+ ```
50
+
51
+ Ownership is **exclusive, leased, recoverable, observable, bounded in time and crash-safe**.
52
+ There is no global leader; any healthy, compatible authority may reclaim an expired claim.
53
+ A process crash never permanently owns work.
54
+
55
+ ## 3. Leases
56
+
57
+ A lease is portable plain data:
58
+
59
+ ```
60
+ Lease { resourceId, ownerId, token, generation, acquiredAt, expiresAt }
61
+ ```
62
+
63
+ Operations: `acquire`, `renew`, `release`, `inspect`, `checkOwnership`, `list`.
64
+
65
+ - `acquire` succeeds when the resource is unclaimed or the current lease window has closed.
66
+ It never grants while a live lease is held — an owner that wants to keep working calls
67
+ `renew`.
68
+ - `renew` and `release` are **owner-specific**: they require the opaque per-acquisition
69
+ `token`, so they are safe even between two claims by the same `ownerId`.
70
+ - Lease timing uses the wall clock of whichever authority (or the coordination provider)
71
+ performs the operation. A safe renew cadence — `renewIntervalMs <= leaseDurationMs / 2`,
72
+ enforced by config validation — tolerates one lost renewal and bounded clock skew. Axiom
73
+ does **not** attempt distributed clock synchronisation; a deployment with unbounded skew
74
+ across authority hosts is unsupported and must be rejected operationally.
75
+
76
+ ## 4. Fencing
77
+
78
+ **Lease expiry authorises nothing.** It only makes a claim reclaimable. The stale-owner
79
+ problem —
80
+
81
+ ```
82
+ A owns the work → A pauses → A's lease expires → B acquires → A wakes up → A keeps writing
83
+ ```
84
+
85
+ — is solved by a **fencing generation**: a strictly-increasing, per-resource, crash-durable
86
+ integer minted on every acquisition. Every durable-work mutation carries the generation it
87
+ was claimed under. A mutation whose generation is not the current one is **rejected**
88
+ (`WORK_FENCED`). A stale authority can never commit a semantic completion after ownership
89
+ has moved.
90
+
91
+ A lease that merely expired without anyone reclaiming it does **not** fence its owner: if B
92
+ never took over, A's own completion still applies. Only a reclaim advances the generation.
93
+
94
+ ## 5. Logical effect vs physical attempt
95
+
96
+ ```
97
+ LogicalEffect — the semantic work a committed transaction created. Stable identity.
98
+ EffectAttempt — one physical attempt to perform it. Numbered; may repeat.
99
+ ```
100
+
101
+ `logicalEffectId` is the committed effect-intent id and **never changes across retries**. A
102
+ retry never creates a second logical effect. `attemptNumber` counts physical attempts;
103
+ `ownerGeneration` is the fencing generation of the current attempt.
104
+
105
+ ## 6. Delivery guarantees
106
+
107
+ The contract is precise, documented and machine-inspectable — not "exactly-once", which is
108
+ impossible for a generic external side effect:
109
+
110
+ | Layer | Guarantee |
111
+ | --- | --- |
112
+ | Logical effect creation | **exactly-once** — one committed transaction, one logical effect; idempotent enqueue collapses duplicates. |
113
+ | Physical execution | **at-least-once**, unless the external provider is idempotent. The Axiom-supplied idempotency key (`= logicalEffectId`, see §7) is what lets an idempotent provider collapse retries to one side effect. |
114
+ | Durable Axiom completion transition | **exactly-once** — fenced; only the current owner's generation moves the item to `succeeded` / `failed`, so the declared success/failure event fires once. |
115
+
116
+ A follow-up: dispatching that declared event after the completion commit is at-most-once
117
+ across a crash in the sub-window between the two — unchanged from single-authority 0.8+,
118
+ where the event dispatch was always post-commit and non-durable.
119
+
120
+ ## 7. Effect idempotency
121
+
122
+ An external effect provider *should* accept an Axiom-supplied stable idempotency key. Axiom
123
+ supplies `effect.idempotencyKey = logicalEffectId` whenever the graph declares none; an
124
+ author-declared key (a payment reference, say) is preserved. The application never invents a
125
+ distributed execution id. An adapter maps the key to an HTTP `Idempotency-Key`, a
126
+ payment-provider idempotency key, a message-deduplication id, and so on.
127
+
128
+ ## 8. Effect claiming, retry and uncertain outcomes
129
+
130
+ Multiple authorities may race to claim a pending effect. Exactly one wins the active attempt
131
+ generation; the others observe it as unavailable (`WORK_IN_PROGRESS`) and move on. If the
132
+ owner crashes, its lease expires and another authority claims a **new** generation and
133
+ retries.
134
+
135
+ Retry state is **durable** (`attemptNumber`, last-attempt time, `nextEligibleAt` backoff
136
+ floor, last failure classification, completion state). The backoff floor lives in the store,
137
+ never in a process-local timer — a restart or failover resumes it. The graph-owned retry
138
+ policy (`maxAttempts`, backoff shape, `retryable`) is honoured exactly; infrastructure only
139
+ chooses poll cadence, claim batch size and lease-renew interval.
140
+
141
+ **Uncertain external effect outcome (important).** Suppose an authority sends the external
142
+ request, the external system processes it, and the authority crashes *before* recording
143
+ completion. Axiom cannot generally know whether the effect happened. Required behaviour:
144
+ retry according to the delivery contract, reusing the **same** idempotency key —
145
+ `uncertainAttempts` on the durable work item is incremented so the reclaim is observably a
146
+ retry-after-uncertainty. Axiom never pretends this collapses to a physical exactly-once
147
+ side effect; with a non-idempotent provider the effect may happen twice.
148
+
149
+ ## 9. Crash recovery
150
+
151
+ For a crash at **every** ownership boundary the recovery is defined:
152
+
153
+ | Crash point | Durable state after | Reclaimable | Duplication boundary | Stale owner if it resumes |
154
+ | --- | --- | --- | --- | --- |
155
+ | before claim | pending | yes (claimable) | excluded | n/a |
156
+ | during claim | pending / claimed(dead gen) | after lease expiry | excluded | fenced |
157
+ | after claim, before work | claimed | after lease expiry | excluded (no effect ran) | fenced |
158
+ | during work / after physical effect, before completion | claimed | after lease expiry | **at-least-once** | fenced |
159
+ | before completion commit | claimed | after lease expiry | excluded (no partial completion) | fenced |
160
+ | after completion commit, before release | succeeded / failed (durable) | no (terminal) | excluded | `already-terminal` |
161
+ | during lease renewal | claimed | after lease expiry | excluded | fenced |
162
+ | after lease expiry, before anyone reclaims | claimed | yes | excluded | **self-recovers** — expiry alone does not fence |
163
+
164
+ Durable work has no sub-attempt checkpoint: the unit of progress is the whole attempt.
165
+
166
+ ## 10. Scheduling
167
+
168
+ A scheduled trigger (`interval` / `delay`) fires on a host timer, and every authority runs
169
+ its own timers. Each **logical firing** is a durable work item with a derived, stable id:
170
+
171
+ ```
172
+ workId = "<scheduleId>@<dueInstant>"
173
+ ```
174
+
175
+ `dueInstant` is a wall-clock millisecond every authority derives identically — an `interval`
176
+ boundary is epoch-aligned to a multiple of `everyMs`; a `delay` fires once, so its instant
177
+ is the constant `afterMs`. Because the id is derived, not minted, two authorities observing
178
+ the same due schedule converge on **one** firing. Exactly one authority claims it (fenced);
179
+ a crash permits reclaim of *the same* firing id — no second firing identity is ever created.
180
+
181
+ Missed firings (an outage): a `catchUp` policy (`latest` (default), `all`, or a number)
182
+ decides how many elapsed boundaries are enqueued. Whichever it is, the claim lease
183
+ guarantees each missed firing is caught up by **one** authority, never N. Catch-up state
184
+ vocabulary: `due`, `late`, `currently-owned`, `expired-owner`, `already-fired`,
185
+ `terminally-completed`.
186
+
187
+ ## 11. Event deduplication
188
+
189
+ When more than one authority can receive the same external delivery **and the provider
190
+ contract says it carries a stable id**, ingestion deduplicates on `source + externalEventId`
191
+ against a durable record of a payload fingerprint (a canonical-JSON SHA-256):
192
+
193
+ | Input | Outcome |
194
+ | --- | --- |
195
+ | first `(source, externalEventId)` | `accepted` — dispatch one semantic event |
196
+ | same, byte-equal payload | `duplicate` — dispatch nothing |
197
+ | same id, different payload | **`EVENT_ID_CONFLICT`** — never a silent second event |
198
+ | no `externalEventId` | `unidentified` — at-least-once ingestion, dedup impossible |
199
+
200
+ A stable id is **never synthesised** from a receive timestamp, an authority instance id or a
201
+ random UUID. The window per source is bounded; an id that has fallen out of the window is
202
+ treated as new (bounded, not exactly-once).
203
+
204
+ ## 12. Subscriptions
205
+
206
+ A `SubscriptionDef` separates three things: the semantic subscription (durable), the
207
+ physical client connection (belongs to whichever authority the client reached, may drop),
208
+ and the **delivery cursor** (durable, owned by exactly one authority at a time via a fenced
209
+ lease).
210
+
211
+ - **Ordering** is per subscription only: `sequence` is monotonic within one subscription.
212
+ There is deliberately **no** ordering across subscriptions or against any other event
213
+ source. `subscriptionOrderingGuarantee()` states this machine-readably.
214
+ - **Cursor advancement is fenced and monotonic**: a write carries the generation its owner
215
+ was claimed under; a lower-generation write is rejected (`fenced`), a lower-sequence write
216
+ is rejected (`stale-sequence`). A stalled owner that resumes after takeover can never move
217
+ the cursor.
218
+ - **Reconnect** follows the durable cursor, not process memory: a new authority `acquire`s
219
+ ownership and is handed the durable position to resume from. Reconnect does not depend on
220
+ reaching the same authority instance.
221
+ - Delivery is **at-least-once**; duplicate delivery is possible.
222
+
223
+ ## 13. Cache coherence
224
+
225
+ A cached authoritative read must never be served stale indefinitely because *another*
226
+ authority committed. The correctness mechanism is **durable revision observation**, not
227
+ broadcast:
228
+
229
+ - persistence exposes a monotonic store `revision` that every committed transaction advances
230
+ (on any authority);
231
+ - each cache entry records the `observedRevision` it was computed at;
232
+ - before serving a cached authoritative read, the authority re-observes the persisted
233
+ revision; a behind entry is dropped and the result recomputed.
234
+
235
+ Because the check happens on every authoritative read and any commit advances the revision,
236
+ the **staleness bound is zero revisions** — a read after a committed write, on any
237
+ authority, never observes the pre-write state. A local broadcast invalidation (an authority
238
+ clearing its own cache on its own commit) is a latency optimisation only; a dropped
239
+ notification changes nothing about correctness.
240
+
241
+ `CACHE_COHERENCE` = `{ mechanism: 'durable-revision-observation', stalenessBoundRevisions:
242
+ 0, requiresBroadcast: false, checkPerRead: true }`.
243
+
244
+ ## 14. Version skew
245
+
246
+ Two authorities with different application builds may temporarily coexist during a rolling
247
+ deploy. Axiom **fails closed** when semantic compatibility cannot be established.
248
+
249
+ An authority's **compatibility key** is four fields:
250
+
251
+ ```
252
+ { schemaVersion, schemaFingerprint, serverContract, semanticFingerprint }
253
+ ```
254
+
255
+ - `schemaVersion` / `schemaFingerprint` — the 0.11 persistence-relevant identity. A schema
256
+ mismatch is already fatal per 0.11 migration safety.
257
+ - `serverContract` — the Server IR contract the document declares.
258
+ - `semanticFingerprint` — a **new**, versioned, deterministic hash over the *executable
259
+ server-side meaning*: action bodies, guards and operations, integration operation
260
+ definitions, triggers, events, subscription policy, read-policy predicates, query
261
+ semantics, expression definitions, constraints. It **excludes** everything a rename
262
+ touches — names, descriptions, labels, free-form metadata, all UI / routes / themes /
263
+ presentation, and declaration order. It is distinct from `schemaFingerprint`, which
264
+ deliberately excludes executable meaning: two graphs whose actions do entirely different
265
+ things but store the same shapes have the same `schemaFingerprint` and different
266
+ `semanticFingerprint`s.
267
+
268
+ Durable work records the compatibility key of the build that created it. An authority whose
269
+ key differs **refuses to claim** that work (`INCOMPATIBLE_AUTHORITY` / the item is simply
270
+ not claimed and stays visible as incompatible). A compatible authority runs it. During a
271
+ schema migration, incompatible workers stop claiming new work, ordinary serving is refused
272
+ per 0.11, the migration completes, and compatible new authorities resume — migration
273
+ ownership stays host-controlled and separate from ordinary distributed-work ownership; there
274
+ is no second migration coordination system.
275
+
276
+ `AgentAPI.inspectDistributedSemantics()` exposes the compatibility key and, per work class,
277
+ the guarantee, the provider capability it needs, and where the live runtime state is —
278
+ keeping the semantic guarantee, the runtime state, the provider capability and the
279
+ operational tuning separate.
280
+
281
+ ## 15. Provider capabilities
282
+
283
+ A `CoordinationProvider` advertises capabilities:
284
+
285
+ ```
286
+ distributed-lease · fencing · atomic-work-claim · durable-retry ·
287
+ event-dedup · durable-subscription-cursor · revision-observation
288
+ ```
289
+
290
+ A runtime that needs a capability the provider does not advertise **fails explicitly** with
291
+ a capability diagnostic. There is no silent single-node fallback and no "works as long as
292
+ only one server is running".
293
+
294
+ Two reference providers ship:
295
+
296
+ - **memory** — a full *semantic* reference: every fencing, reclaim and ownership rule holds,
297
+ deterministic with an injected clock and token source, able to simulate an N-authority
298
+ cluster in one process. `physicalDurability: false` — it does not survive across OS
299
+ processes.
300
+ - **SQLite** — the real cross-process reference: independent OS processes, independent
301
+ connections, one database file. `physicalDurability: true`. SQLite provides no independent
302
+ server clock (§3); physical `SQLITE_BUSY` / `SQLITE_LOCKED` contention is absorbed and, if
303
+ sustained, surfaces as a typed contention error — never as a coordination outcome.
304
+
305
+ A future production provider may use PostgreSQL, Redis, DynamoDB, etc. 0.12 semantics are
306
+ **not** defined in any provider's terminology: `SETNX`, `Redlock`, "Redis TTL", "conditional
307
+ check failed" and the like are provider techniques, never Axiom vocabulary.
308
+
309
+ ## 16. Failure semantics — release invariants
310
+
311
+ The following always hold, and each is covered by a real-OS-process test:
312
+
313
+ - Two authorities never validly own the same `(resourceId, generation)`.
314
+ - A stale owner cannot commit completion / retry / cursor state after the generation
315
+ advances.
316
+ - One logical schedule firing never becomes two because N authorities poll.
317
+ - A duplicate stable external event never becomes two semantic events; a same-id /
318
+ different-payload event is an explicit `EVENT_ID_CONFLICT`.
319
+ - An effect retry never creates a second `logicalEffectId`.
320
+ - A crash never permanently strands durable work; failover never loses committed work.
321
+ - An incompatible / older-build authority never executes new-schema work.
322
+ - Ordinary multi-authority operation needs no application SQL, locks or `NativeOperation`.
323
+ - Provider-native contention never leaks as a semantic result.
324
+ - The authoritative cache is never stale past the declared bound (one revision check).
325
+ - A subscription's stale owner never overwrites a newer cursor.
326
+ - Physical external-effect exactly-once is never claimed.
327
+
328
+ ## 17. Host configuration
329
+
330
+ Infrastructure knobs, passed as `createAxiomServer({ distributed: { … } })`. None changes a
331
+ semantic guarantee.
332
+
333
+ | Knob | Meaning | Default |
334
+ | --- | --- | --- |
335
+ | `instanceId` | This authority's identity. | `host.uuid()` at startup |
336
+ | `leaseDurationMs` | Lease window; an unrenewed claim becomes reclaimable after this. | 30000 |
337
+ | `renewIntervalMs` | Renew cadence. MUST be `< leaseDurationMs`; `<= /2` recommended. | 10000 |
338
+ | `workerConcurrency` | Max in-flight durable work items per authority. | 4 |
339
+ | `claimBatchSize` | Max items claimed per poll. | 32 |
340
+ | `pollIntervalMs` | Durable-state re-observation cadence. | 1000 |
341
+
342
+ `createAxiomServer` **throws** on an unsafe combination (e.g. `renewIntervalMs >=
343
+ leaseDurationMs`) rather than start with probabilistically-unsafe fencing.
344
+
345
+ ## 18. Conformance
346
+
347
+ The portable `axiom.conformance.v6` fixture tier (`packages/server/conformance/distributed/`)
348
+ covers lease acquisition, lease fencing, effect claiming, effect reclaim, effect completion,
349
+ schedule firing, schedule reclaim, event deduplication, subscription cursor fencing, cache
350
+ revision visibility and mixed-build refusal. Each fixture is a deterministic step list
351
+ against the memory reference providers with a fixed clock and token sequence; the public
352
+ `runCoordinationConformanceFixture` / `runCoordinationConformanceSuite` runner executes them.
353
+ Server IR stays `axiom.server.v7` — 0.12 adds no IR vocabulary.
354
+
355
+ ## 19. Anti-patterns
356
+
357
+ See `docs/ANTI_PATTERNS.md` for the full list with fixes. In short, none of these belongs in
358
+ an application graph or in application code:
359
+
360
+ - an application-written distributed lock, or Redis `SETNX` in a `native` operation;
361
+ - a process-local "already executed" `Set` used as deduplication;
362
+ - `leader`-only application branches;
363
+ - a random UUID (or a timestamp, or an instance id) used as an external-event dedup key;
364
+ - a retry that constructs a new logical effect id;
365
+ - a completion write that is not conditional on the current fencing generation;
366
+ - assuming a global order across unrelated entities or events;
367
+ - relying on pub/sub delivery for cache correctness;
368
+ - treating an uncertain external effect outcome as definitely-not-done;
369
+ - claiming physical exactly-once for a generic external side effect;
370
+ - executing durable work under a build whose compatibility key does not match.
package/docs/EFFECTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Effects
2
2
 
3
- Axiom 0.11.2-alpha.1. External effects are not rollback-capable state mutations. This file
3
+ Axiom 0.12.0-alpha.1. External effects are not rollback-capable state mutations. This file
4
4
  is the delivery model; [`AUTHORITY.md`](AUTHORITY.md#external-effects) is the load-bearing
5
5
  statement of why, and [`INTEGRATIONS.md`](INTEGRATIONS.md) is the operation vocabulary this
6
6
  builds on.
package/docs/EVENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Events
2
2
 
3
- Axiom 0.11.2-alpha.1. An event is a typed fact — something that happened — never work
3
+ Axiom 0.12.0-alpha.1. An event is a typed fact — something that happened — never work
4
4
  itself. [`AUTHORITY.md`](AUTHORITY.md#external-events) is the load-bearing statement;
5
5
  this file is the vocabulary and the webhook delivery mechanism. A **subscription** is the
6
6
  other way an external fact becomes an `EventDef` payload — see
@@ -1,6 +1,6 @@
1
1
  # Expressions
2
2
 
3
- Axiom 0.11.2-alpha.1. An expression describes **what value is computed**. It is a tree of
3
+ Axiom 0.12.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.11.2-alpha.1. The `ApplicationGraph` is the authoritative representation of an
3
+ Axiom 0.12.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
 
@@ -1,6 +1,6 @@
1
1
  # Integrations
2
2
 
3
- Axiom 0.11.2-alpha.1. How an application declares and calls an external system, without
3
+ Axiom 0.12.0-alpha.1. How an application declares and calls an external system, without
4
4
  embedding a transport, an SDK or a secret in the graph. The authority boundary this
5
5
  depends on is [`AUTHORITY.md`](AUTHORITY.md#external-systems); this file is the vocabulary.
6
6
 
package/docs/LOCATIONS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Locations
2
2
 
3
- Axiom 0.11.2-alpha.1.
3
+ Axiom 0.12.0-alpha.1.
4
4
 
5
5
  ```text
6
6
  Expression = a value
@@ -1,6 +1,6 @@
1
1
  # Schema evolution & semantic migrations
2
2
 
3
- Axiom 0.11.2-alpha.1. The operational contract for evolving a deployed application's
3
+ Axiom 0.12.0-alpha.1. The operational contract for evolving a deployed application's
4
4
  semantic model and its persisted canonical data over time — adding a required field,
5
5
  splitting one field into two, removing an obsolete one, migrating millions of
6
6
  provider-backed rows — **without** an application-authored SQL migration, an ORM migration,
@@ -1,6 +1,6 @@
1
1
  # Presentation
2
2
 
3
- Axiom 0.11.2-alpha.1. Presentation is **semantic UX intent**, expressed as data on a UI
3
+ Axiom 0.12.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/QUERIES.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Semantic data access & the query layer
2
2
 
3
- Axiom 0.11.2-alpha.1. The operational contract for demand-driven reads over authoritative
3
+ Axiom 0.12.0-alpha.1. The operational contract for demand-driven reads over authoritative
4
4
  data that is too large to materialize as a `StateDef` — 500,000 orders, 5,000,000 order
5
5
  lines, years of audit rows. `axiom.server.v6`.
6
6
 
package/docs/RUNTIME.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Runtime
2
2
 
3
- Axiom 0.11.2-alpha.1. The runtime executes an `ApplicationIR`. It is domain-independent: it
3
+ Axiom 0.12.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
@@ -1,6 +1,6 @@
1
1
  # Semantic contract
2
2
 
3
- Axiom 0.11.2-alpha.1. Runtime guarantees, stated formally. This file defines behavior; it
3
+ Axiom 0.12.0-alpha.1. Runtime guarantees, stated formally. This file defines behavior; it
4
4
  does not teach. Where this file and any specification in `../specs/` disagree, this file
5
5
  describes the implementation and is authoritative.
6
6
 
package/docs/STATE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # State
2
2
 
3
- Axiom 0.11.2-alpha.1. A `StateDef` is a named application value: stored, or computed from
3
+ Axiom 0.12.0-alpha.1. A `StateDef` is a named application value: stored, or computed from
4
4
  other state.
5
5
 
6
6
  ```ts
package/docs/STORAGE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Storage and blobs
2
2
 
3
- Axiom 0.11.2-alpha.1. How an application stores, references, serves and deletes binary data —
3
+ Axiom 0.12.0-alpha.1. How an application stores, references, serves and deletes binary data —
4
4
  an attachment, a document, a photograph, a diagnostic log — with no filesystem path, no
5
5
  upload route and no download route anywhere in it.
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Subscriptions
2
2
 
3
- Axiom 0.11.2-alpha.1. How an application receives a stream of external events — an MQTT
3
+ Axiom 0.12.0-alpha.1. How an application receives a stream of external events — an MQTT
4
4
  topic, a WebSocket feed, a queue consumer, a filesystem watcher, a serial port — without a
5
5
  client, a socket or a callback anywhere in the graph.
6
6
 
package/docs/TRIGGERS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Triggers
2
2
 
3
- Axiom 0.11.2-alpha.1. A `TriggerDef` says **when** an action should be invoked, without
3
+ Axiom 0.12.0-alpha.1. A `TriggerDef` says **when** an action should be invoked, without
4
4
  embedding callback code. `docs/AUTHORITY.md`
5
5
  [§ Triggers](AUTHORITY.md#triggers) is the load-bearing statement of the execution model;
6
6
  this file is the vocabulary.
package/docs/UI.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # UI
2
2
 
3
- Axiom 0.11.2-alpha.1. Eleven semantic UI node kinds describe **what exists and what it does**.
3
+ Axiom 0.12.0-alpha.1. Eleven 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 eleven share `UIBase`:
@@ -1,6 +1,6 @@
1
1
  # Validation
2
2
 
3
- Axiom 0.11.2-alpha.1. Validation is authoring-time structural checking. It is not the same
3
+ Axiom 0.12.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
 
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Axiom
2
2
 
3
- > AI-native semantic application framework, version 0.11.2-alpha.1. An Axiom application is a
3
+ > AI-native semantic application framework, version 0.12.0-alpha.1. An Axiom application is a
4
4
  > typed semantic graph — state, behavior, constraints, UI structure, presentation and
5
5
  > authority as structured data — executed by generic runtimes. The JavaScript, HTML and CSS
6
6
  > that reach a browser are compiler output and are never authored or edited. The primary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom",
3
- "version": "0.11.2-alpha.1",
3
+ "version": "0.12.0-alpha.1",
4
4
  "description": "AI-native semantic web application framework.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",
@@ -34,10 +34,10 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "@cynodia/axiom-core": "0.11.2-alpha.1",
38
- "@cynodia/axiom-runtime": "0.11.2-alpha.1",
39
- "@cynodia/axiom-compiler": "0.11.2-alpha.1",
40
- "@cynodia/axiom-agent-api": "0.11.2-alpha.1"
37
+ "@cynodia/axiom-core": "0.12.0-alpha.1",
38
+ "@cynodia/axiom-runtime": "0.12.0-alpha.1",
39
+ "@cynodia/axiom-compiler": "0.12.0-alpha.1",
40
+ "@cynodia/axiom-agent-api": "0.12.0-alpha.1"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsc -b tsconfig.json"