@cynodia/axiom 0.8.1-alpha.1 → 0.8.2-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  # Actions and transactions
2
2
 
3
- Axiom 0.8.1-alpha.1. An action is behavior expressed as data, executed as a transaction.
3
+ Axiom 0.8.2-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.8.1-alpha.1. The machine-facing interface. Agents query semantics and apply
3
+ Axiom 0.8.2-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.8.1-alpha.1. Compressed operational contract. Read this plus the `.d.ts`
3
+ Axiom 0.8.2-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.8.0'
34
+ const graph = new ApplicationGraph(id, name); // version defaults to '0.8.2'
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
@@ -496,6 +496,22 @@ if (!result.ok) {
496
496
  warnings never make a graph invalid. 72 codes in `VALIDATION_CODES`, grouped in
497
497
  [`VALIDATION.md`](VALIDATION.md).
498
498
 
499
+ **`validateGraph(graph)` with no options is target-neutral by design** (spec 8.2 §2-4): a
500
+ graph is never rejected for a renderer or trigger runtime nobody named. This means a bare
501
+ `validateGraph(graph).valid === true` does **not** by itself guarantee the graph is
502
+ executable by the browser client — a UI node kind no renderer implements, or a
503
+ client-authority trigger kind the browser trigger runtime does not execute
504
+ (`CLIENT_TRIGGER_UNSUPPORTED`), both validate silently under the no-options call. Use
505
+ `validateForBrowser(graph)` (`@cynodia/axiom-compiler`) for a validate-only check against
506
+ real browser capabilities, or call `compileToIR(graph)` directly — it applies those same
507
+ capabilities and throws `GraphValidationError` on exactly what `validateForBrowser` would
508
+ report as an error. See [Renderability](#renderability) below for the parallel UI-kind gate.
509
+
510
+ ```ts
511
+ validateGraph(graph).valid; // target-neutral: accepts every UI kind and trigger kind
512
+ validateForBrowser(graph).valid; // browser-real: same as compileToIR's validation step
513
+ ```
514
+
499
515
  ## Agent API
500
516
 
501
517
  ```ts
@@ -638,7 +654,7 @@ event.
638
654
  agent.listIntegrations() / agent.listIntegrationOperations(id?);
639
655
  agent.getActionsUsingIntegration(id) / agent.getEffectsForAction(actionId);
640
656
  agent.getTriggersForAction(actionId) / agent.getTimedTriggers();
641
- agent.getActionsTriggeredByEvent(eventId) / agent.getWebhookEvents();
657
+ agent.getActionsTriggeredByEvent(eventId) / agent.getTriggeredEvents(); // graph-static: has a bound trigger, not "was delivered"
642
658
  agent.getExternalDependencies(); // { integrations, operations } — the deployment manifest
643
659
  agent.getSystemOnlyActions() / agent.getTriggersTargetingClientOnlyActions();
644
660
  agent.isClientInvocable(actionId) / agent.isSystemOnly(actionId);
@@ -647,12 +663,24 @@ agent.isClientInvocable(actionId) / agent.isSystemOnly(actionId);
647
663
  Portable artifacts, for a runtime written in another language:
648
664
 
649
665
  ```
650
- @cynodia/axiom-server/conformance the fixture manifest
666
+ @cynodia/axiom-server/conformance the fixture manifest (fixture.format, below)
651
667
  @cynodia/axiom-server/conformance/<name>.json one fixture, pure data
652
- @cynodia/axiom-server/schema/server-ir.v1.schema.json JSON Schema for the IR
668
+ @cynodia/axiom-server/schema/server-ir.v1.schema.json JSON Schema for axiom.server.v1 (frozen)
669
+ @cynodia/axiom-server/schema/server-ir.v2.schema.json JSON Schema for axiom.server.v2
670
+ @cynodia/axiom-server/schema/server-ir.v3.schema.json JSON Schema for axiom.server.v3
671
+ @cynodia/axiom-server/schema/server-ir.v4.schema.json JSON Schema for axiom.server.v4 (latest)
653
672
  @cynodia/axiom-server/schema/protocol.v1.schema.json JSON Schema for the protocol
654
673
  ```
655
674
 
675
+ This list is generated/tested content, not hand-maintained prose: `packages/demo/test
676
+ /documentation.test.ts` fails if a shipped `schema/*.json` file is missing from it or a
677
+ listed file no longer exists (spec 8.2 §43-45). `runConformanceFixture`/
678
+ `runConformanceSuite` (`@cynodia/axiom-server`) are the public runner over that fixture
679
+ format — see [`AUTHORITY.md`](AUTHORITY.md#conformance) for `fixture.conformance` (the
680
+ fixture-format version) vs `fixture.serverIR.contract` (the per-fixture Server IR contract)
681
+ vs the manifest's own `conformance`/`protocol`/`release` fields — three separate concepts,
682
+ never conflated under one "contract" name (spec 8.2 §9-10).
683
+
656
684
  Boundary diagnostics: `UNKNOWN_SERVER_ACTION` `ARGUMENT_TYPE_MISMATCH` `AUTHORIZATION_DENIED`
657
685
  `INVOCATION_SOURCE_NOT_ALLOWED` `CONCURRENCY_CONFLICT` `MALFORMED_REQUEST`
658
686
  `AUTHORITY_UNREACHABLE`, plus `SERVER_STATE_WRITE` and `REMOTE_ACTION_UNAVAILABLE` on the
@@ -683,6 +711,19 @@ Capabilities describe **node-kind** support, not partial support: a renderer can
683
711
  that it draws a kind but not one of its options. Nothing in the current vocabulary needs that,
684
712
  and it is a deliberate future extension rather than an oversight.
685
713
 
714
+ The same gate exists for client-authority triggers, since the browser trigger runtime
715
+ implements no `TriggerSpec.kind` at all:
716
+
717
+ ```ts
718
+ validateGraph(graph, { triggerRuntime: BROWSER_TRIGGER_CAPABILITIES }); // CLIENT_TRIGGER_UNSUPPORTED
719
+ compileToIR(graph); // applies it by default
720
+ ```
721
+
722
+ `CLIENT_TRIGGER_UNSUPPORTED`'s message states the remediation, not only the refusal: move
723
+ the trigger's target action to server authority, or compile for a trigger runtime that
724
+ publishes the kind (`compileToIR(graph, { triggerRuntime })`). Full model:
725
+ [`TRIGGERS.md`](TRIGGERS.md#where-a-trigger-executes).
726
+
686
727
  ## Named expressions
687
728
 
688
729
  ```ts
@@ -1,6 +1,6 @@
1
1
  # Anti-patterns
2
2
 
3
- Axiom 0.8.1-alpha.1. Each of these compiles. Each is wrong. Each is followed by the correct
3
+ Axiom 0.8.2-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
package/docs/AUTHORITY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Authority
2
2
 
3
- Axiom 0.8.1-alpha.1. How an application crosses the trust boundary.
3
+ Axiom 0.8.2-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
@@ -549,6 +549,26 @@ Running them requires no part of this implementation. That is the point: the Ser
549
549
  specification plus these fixtures are the whole contract, so an independent runtime in
550
550
  another language can be held to exactly the same standard.
551
551
 
552
+ **A public reference runner is exported for the TypeScript reference runtime itself**
553
+ (spec 8.2 §14-16):
554
+
555
+ ```ts
556
+ import { runConformanceFixture, runConformanceSuite } from '@cynodia/axiom-server';
557
+
558
+ const result = await runConformanceFixture(fixtureJson); // { name, ok, failures[] }
559
+ ```
560
+
561
+ It imports only `@cynodia/axiom-server` and fixture data — no graph, no compiler, no
562
+ builder — and reports structured pass/fail rather than throwing on an ordinary fixture
563
+ mismatch. `packages/server/test/conformance.test.ts` runs every shipped fixture through
564
+ this exact function, and `npm run conformance:run` (`scripts/run-conformance.mjs`) runs the
565
+ whole suite from a standalone script using nothing but this public API and the manifest —
566
+ the "held to the same standard by an outside caller" claim, demonstrated rather than
567
+ asserted. The fixture **model** (`ConformanceFixture` and friends, `conformance-types.ts`)
568
+ is deliberately kept separate from this **adapter**
569
+ (`conformance-runner.ts`): a non-TypeScript implementation needs only the model's shape and
570
+ the semantics on this page, never this file.
571
+
552
572
  Enumerate the suite from its manifest rather than by listing a directory:
553
573
 
554
574
  ```
@@ -556,12 +576,24 @@ Enumerate the suite from its manifest rather than by listing a directory:
556
576
  @cynodia/axiom-server/conformance/<name>.json → one fixture
557
577
  ```
558
578
 
559
- The manifest names the contracts the fixtures are written against (`axiom.conformance.v1`,
560
- `axiom.server.v1`, `axiom.protocol.v1`), the release, every area covered, and every fixture
561
- with its file. A runtime that does not implement a contract the manifest names should refuse
562
- the suite rather than discover the mismatch one assertion at a time. The files are plain JSON
563
- in a documented package directory, so a non-JavaScript consumer can read them straight out of
564
- the tarball without Node's module resolver.
579
+ The manifest carries three **separate, non-overlapping** version concepts (spec 8.2 §9-10;
580
+ do not conflate them under one "contract" name):
581
+
582
+ | Field | Means |
583
+ | --- | --- |
584
+ | `manifest.conformance` | The **fixture-format** version (`axiom.conformance.v1`/`v2`) — the shape of the JSON documents themselves: what top-level keys a fixture may have (`invocations` vs `steps`/`externalAdapters`, and so on). |
585
+ | `manifest.baseContract` | The **oldest** Server IR contract any fixture in this manifest may use — `axiom.server.v1`, always, since new fixtures are added without ever raising this floor. It does **not** describe what the newest fixture needs. |
586
+ | `manifest.fixtures[].contract` | The Server IR contract **that specific fixture** requires — this is what is authoritative for what running it needs. The suite ships fixtures spanning `v1` through `v4` simultaneously, each correctly labelled. |
587
+ | `manifest.release` | The `@cynodia/axiom` package version this snapshot of the suite shipped with — unrelated to either version above. |
588
+
589
+ Before 8.2 the manifest's top-level field was named `contract` and fixed at
590
+ `axiom.server.v1`, which read as a claim about the whole suite even after `v3`/`v4`
591
+ fixtures were added; it is now named `baseContract` with the meaning above, precisely
592
+ because per-fixture `contract` is what is actually authoritative. A runtime that does not
593
+ implement a contract a fixture names should refuse that fixture rather than discover the
594
+ mismatch mid-assertion. The files are plain JSON in a documented package directory, so a
595
+ non-JavaScript consumer can read them straight out of the tarball without Node's module
596
+ resolver.
565
597
 
566
598
  Every fixture is executed against the reference runtime by this repository's own test suite,
567
599
  and its expectations are exhaustive — a fixture that says which states changed must name all
@@ -573,6 +605,7 @@ of them and no others. No fixture is permitted to disagree with the shipped runt
573
605
  @cynodia/axiom-server/schema/server-ir.v1.schema.json
574
606
  @cynodia/axiom-server/schema/server-ir.v2.schema.json
575
607
  @cynodia/axiom-server/schema/server-ir.v3.schema.json
608
+ @cynodia/axiom-server/schema/server-ir.v4.schema.json
576
609
  @cynodia/axiom-server/schema/protocol.v1.schema.json
577
610
  ```
578
611
 
@@ -592,15 +625,21 @@ page plus the conformance fixtures.
592
625
  | `axiom.server.v1` | the frozen 0.6.1 contract, below | 0.6.1 |
593
626
  | `axiom.server.v2` | the expression kinds `group` and `expression-ref`, and the `expressionDefs` they resolve against | 0.7.0 |
594
627
  | `axiom.server.v3` | integrations, integration operations, events, triggers, and the `integration-query`/`integration-effect` operation kinds | 0.8.0 |
628
+ | `axiom.server.v4` | `ActionDef.invocation.allowedSources` invocation-source restriction, and the structured effect-outcome envelope (`effectOutcomeEntity`, `EFFECT_ID_FIELD` and its sibling reserved fields) that every effect dispatch uses from 8.1 onward | 0.8.1 |
595
629
 
596
- `SERVER_IR_CONTRACTS` enumerates all three. The rules:
630
+ `SERVER_IR_CONTRACTS` enumerates all four, and is the single source of truth this table is
631
+ tested against — `packages/demo/test/documentation.test.ts` fails if a contract in
632
+ `SERVER_IR_CONTRACTS` has no row here, or a row here names a contract the code does not
633
+ declare (spec 8.2 §7-8). The rules:
597
634
 
598
- - **A document declares the oldest contract that can carry it.** `compileToServerIR` computes the label from the vocabulary the document actually uses, so an application that uses nothing from 0.7 or 0.8 produces a byte-identical `axiom.server.v1` document, and the committed v1 conformance fixtures are unchanged.
599
- - **A runtime MUST refuse a contract it does not implement**, and MUST refuse a document whose vocabulary exceeds its declared contract. A v2 runtime executing a v1-labelled document that uses `group` would accept what a conforming v1 runtime elsewhere refuses, and the two would then disagree about the same file. `createAxiomServer` raises rather than executing one.
600
- - **A frozen contract gains nothing.** `axiom.server.v1` does not contain `group`, `expression-ref`, `expressionDefs`, an integration, a trigger, an event, or the `integration-query`/`integration-effect` operation kinds, and `server-ir.v1.schema.json` is byte-frozen. Vocabulary arrives under a new identifier or not at all.
635
+ - **A document declares the oldest contract that can carry it.** `compileToServerIR` computes the label from the vocabulary the document actually uses, so an application that uses nothing from 0.7 or 0.8 produces a byte-identical `axiom.server.v1` document, and the committed v1 conformance fixtures are unchanged. `usesV4Semantics` computes the v4 case specifically: an action's `invocation.allowedSources` genuinely restricting the default two-source set, or any `integration-operation` with `mode: 'effect'` (since every effect dispatch uses the structured v4 envelope) — a document that merely mentions `invocation` without restricting it only needs `axiom.server.v2`, the same tier `group`/`expression-ref` occupy.
636
+ - **A runtime MUST refuse a contract it does not implement**, and MUST refuse a document whose vocabulary exceeds its declared contract. A v2 runtime executing a v1-labelled document that uses `group` would accept what a conforming v1 runtime elsewhere refuses, and the two would then disagree about the same file. `createAxiomServer` raises rather than executing one — including refusing a document that **understates** its own contract (`understatedContract`).
637
+ - **A frozen contract gains nothing.** `axiom.server.v1` does not contain `group`, `expression-ref`, `expressionDefs`, an integration, a trigger, an event, the `integration-query`/`integration-effect` operation kinds, `invocation`, or the structured effect-outcome envelope, and `server-ir.v1.schema.json` is byte-frozen. Vocabulary arrives under a new identifier or not at all.
638
+ - **`axiom.server.v4` is the latest contract as of 0.8.2** (`SERVER_IR_LATEST_CONTRACT`). 0.8.2 is polish-only — documentation, effect observability timing, fixture coverage and AgentAPI aliasing — and introduces no incompatible IR vocabulary change, so no `axiom.server.v5` was created (spec 8.2 §55-56).
601
639
 
602
640
  There is one JSON Schema per contract, each generated from the runtime's own vocabulary and
603
- each shipped: `server-ir.v1.schema.json`, `server-ir.v2.schema.json`, `server-ir.v3.schema.json`.
641
+ each shipped: `server-ir.v1.schema.json`, `server-ir.v2.schema.json`, `server-ir.v3.schema.json`,
642
+ `server-ir.v4.schema.json`.
604
643
 
605
644
  **`group`.** Partitions a collection: `Collection<A>` → `Collection<Group<K, A>>`. Groups appear
606
645
  in the order their key was **first seen** in the source; members keep source order; two keys are
@@ -1,6 +1,6 @@
1
1
  # Constraints
2
2
 
3
- Axiom 0.8.1-alpha.1. Two constructs, answering different questions. They are not
3
+ Axiom 0.8.2-alpha.1. Two constructs, answering different questions. They are not
4
4
  interchangeable.
5
5
 
6
6
  | | Question | Sees |
package/docs/EFFECTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Effects
2
2
 
3
- Axiom 0.8.1-alpha.1. External effects are not rollback-capable state mutations. This file
3
+ Axiom 0.8.2-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.
@@ -83,6 +83,18 @@ doubles it each time. The wait uses the host's own scheduling
83
83
  remaining retry policy immediately, regardless of `maxAttempts` — an adapter that always
84
84
  answers `retryable: false` never retries at all, whatever `policy` says (spec 8.1 §73).
85
85
 
86
+ **The three states of `retryable` (spec 8.2 §38-39):**
87
+
88
+ | `retryable` | Meaning | Effect on the declared policy |
89
+ | --- | --- | --- |
90
+ | `false` | The adapter determined a retry cannot succeed. | Stops immediately — the remaining `maxAttempts` are never spent. |
91
+ | `true` | The adapter determined a retry may succeed. | Continues — the next attempt runs after the policy's delay. |
92
+ | absent | The adapter could not determine retryability either way. | Continues, exactly as `true` — an unknown answer is not treated as a refusal. |
93
+
94
+ `packages/server/test/effect-retry.test.ts` is the regression coverage for all three,
95
+ including a genuine multi-attempt retry sequence with a stable `idempotencyKey` across
96
+ every attempt.
97
+
86
98
  ## Effect status and observability
87
99
 
88
100
  ```ts
@@ -106,6 +118,50 @@ mutation, and mixing the two would misrepresent what actually happened (spec §7
106
118
  `report()` events cover the whole lifecycle: `effect-requested`, `effect-attempted`,
107
119
  `effect-succeeded`, `effect-failed`.
108
120
 
121
+ **Exact status semantics (spec 8.2 §17-21):**
122
+
123
+ | Status | Means |
124
+ | --- | --- |
125
+ | `pending` | A durable effect intent exists (committed in the outbox), but no attempt is currently executing — either dispatch has not started yet, or it is waiting between retry attempts. |
126
+ | `running` | An adapter attempt has been started and has not yet settled. Set **before** `IntegrationAdapter.effect(...)` is called, synchronously with the durable `attempts` increment, so `effectLog()`'s public view reflects reality rather than lagging behind it. |
127
+ | `succeeded` | An attempt returned `{ ok: true }`. Terminal. |
128
+ | `failed` | Every attempt the retry policy allows was exhausted, or one returned `retryable: false`. Terminal. |
129
+
130
+ Before 8.2, `running` existed in the type but nothing updated `AxiomServer`'s in-memory
131
+ `effectLog()` view when an attempt actually started — a hung adapter call was
132
+ indistinguishable from an effect nobody had dispatched yet, both showing `status: 'pending',
133
+ attempts: 0` forever even though the adapter had genuinely been invoked. `attempts` counts
134
+ **invocation attempts started**, not merely attempts that settled — it increments at the
135
+ same moment `status` moves to `'running'`, before the adapter is called, so a hung attempt
136
+ is still counted. `packages/server/test/integrations.test.ts`'s hung-effect regression is
137
+ the test for this.
138
+
139
+ **Restart semantics for a `running` effect (spec 8.2 §20).** A record persisted as
140
+ `'running'` when the authority restarts means a previous process called the adapter and was
141
+ never told the outcome — that attempt is unaccounted for, not spent. It is treated as
142
+ resumable/unknown outstanding work: the resumed dispatch gets a **fresh** full retry budget
143
+ (local attempt count restarts at zero; the persisted `attempts` total still carries forward
144
+ honestly across the restart) rather than silently going idle at zero remaining attempts.
145
+ Axiom never claims to know whether the old process's call actually reached the provider —
146
+ only that at-least-once redelivery, paired with `idempotencyKey`, is what makes resuming it
147
+ safe.
148
+
149
+ **No runtime-enforced effect timeout in 0.8.2.** Unlike an `integration-query`'s
150
+ `timeoutMs` (deadline enforced by the runtime itself — see [`INTEGRATIONS.md`](INTEGRATIONS.md#timeout)),
151
+ a `running` effect attempt has no deadline: a non-cooperating adapter call can remain
152
+ `running` indefinitely, and nothing in this release will time it out or reclassify it. This
153
+ is a deliberate scope boundary, not an oversight (spec 8.2 §24) — see the note below for why.
154
+
155
+ **Research note: effect timeout is deferred, not merely unbuilt (spec 8.2 §25).** A future
156
+ effect timeout cannot safely map a timed-out attempt directly to `failed`: unlike a query,
157
+ an effect may have genuinely reached the provider and caused the external side effect
158
+ before the response was lost — declaring it `failed` could make an idempotent caller retry
159
+ a side effect that already happened once, and declaring it `succeeded` could be simply
160
+ wrong. The honest state that later work would need is something like `unknown` — distinct
161
+ from both terminal states — but that state is deliberately **not** introduced in 0.8.2. Do
162
+ not add a deadline just because `running` is now observable; effect timeout remains a
163
+ distinct future design topic.
164
+
109
165
  ## The result reaches an action only through an event
110
166
 
111
167
  An effect's outcome is never folded back into the transaction that requested it. Instead,
@@ -146,6 +202,77 @@ dispatched value is always this entity shape.
146
202
  Both are checked against the declared `EventDef.payloadType` the same way any event is,
147
203
  so a mismatched declaration is caught rather than silently dropped.
148
204
 
205
+ **The full envelope, field by field (spec 8.2 §31-33):**
206
+
207
+ | Field | Present | Source, lifetime and stability |
208
+ | --- | --- | --- |
209
+ | `EFFECT_ID_FIELD` | always | The framework-generated id of this effect intent. Stable for the life of the intent (survives restart — it is what `EffectRecord.id` is keyed by), never consumer-settable. |
210
+ | `EFFECT_INTEGRATION_ID_FIELD` | always | The `IntegrationDef.id` the operation belongs to. Graph-static. |
211
+ | `EFFECT_OPERATION_ID_FIELD` | always | The `IntegrationOperationDef.id` invoked. Graph-static. |
212
+ | `EFFECT_IDEMPOTENCY_KEY_FIELD` | when the `integration-effect` operation declared one | Whatever the action's `idempotencyKey` expression evaluated to at commit time. Application-controlled, and the field the framework recommends for business correlation (below). |
213
+ | `EFFECT_CORRELATION_ID_FIELD` | when the committing action's transaction has one | See below — this is **not** a business correlation key. |
214
+ | `EFFECT_RESULT_FIELD` | success only | The operation's own `resultType` value, unwrapped from the adapter's `IntegrationSuccess.value`. |
215
+ | `EFFECT_CODE_FIELD` | failure only | The adapter's `IntegrationFailure.code`. |
216
+ | `EFFECT_MESSAGE_FIELD` | failure only | The adapter's `IntegrationFailure.message` — see the security note below before copying it into state. |
217
+ | `EFFECT_RETRYABLE_FIELD` | failure only | The adapter's `IntegrationFailure.retryable`, coerced to a boolean (`retryable === true`; both `false` and absent read as `false` here — the three-way distinction that matters for retry control flow is internal to the retry policy, not exposed on the terminal payload). |
218
+
219
+ Success and failure share this one envelope family — the always-present fields plus
220
+ `idempotencyKey`/`correlationId` when applicable — but they are **not symmetric**: success
221
+ never carries `EFFECT_CODE_FIELD`/`EFFECT_MESSAGE_FIELD`/`EFFECT_RETRYABLE_FIELD`, and
222
+ failure never carries `EFFECT_RESULT_FIELD`. Do not describe the two shapes as identical;
223
+ describe them as one family with disjoint success-only and failure-only fields.
224
+
225
+ **What `EFFECT_CORRELATION_ID_FIELD` actually is.** Its value is the internal Axiom
226
+ transaction id (`Transaction.id`, formatted like `tx_<n>`) of the action's transaction that
227
+ recorded the effect intent — the same id `RuntimeDiagnostic.transactionId` already carries
228
+ elsewhere. Concretely:
229
+
230
+ - **Source**: assigned by the runtime's own per-process transaction counter, never supplied
231
+ or influenced by the application or the adapter.
232
+ - **Lifetime**: exists only as long as the process that created it is running.
233
+ - **Uniqueness scope**: unique only within one running authority process — it is not a
234
+ globally unique identifier.
235
+ - **Stable across restart?** No. The counter resets when the authority restarts, so this
236
+ value cannot be used to correlate an effect across a restart, or across two different
237
+ authority processes.
238
+ - **Consumer-settable?** No — there is no way for a graph or an adapter to choose it.
239
+ - **Always present?** In practice yes, since every `integration-effect` operation only ever
240
+ runs inside an action's transaction, and every transaction is assigned an id — but it is
241
+ documented as conditional (`when the committing action's transaction has one`) because
242
+ that is a property of the runtime's transaction model, not a graph-level guarantee this
243
+ document freezes.
244
+
245
+ **Its string format (`tx_<n>`) is explicitly not part of the public contract** (spec 8.2
246
+ §27) — only its semantics are: "identifies the Axiom transaction that created this effect
247
+ intent, within this process's lifetime." A future implementation may change the format
248
+ without that being a breaking change, as long as the semantics hold.
249
+
250
+ **Business correlation guidance (spec 8.2 §28-30).** `EFFECT_CORRELATION_ID_FIELD` is an
251
+ **internal diagnostic aid**, not a business correlation key — it identifies "which
252
+ transaction" in this process, not "which order" or "which device" to a follow-up action.
253
+ **Effect outcomes do not automatically carry the original operation's arguments.** An
254
+ application that needs to correlate an outcome back to a specific business entity (which
255
+ order this reboot was for, which customer this notification was about) should use
256
+ `idempotencyKey` for that purpose when the value is naturally unique per business operation
257
+ (an order id, a reservation id) — it is already carried through to both the success and
258
+ failure envelope, application-controlled, and exists for exactly this shape of problem.
259
+ 0.8.2 deliberately does **not** add a separate `correlationKey`/`correlationValue`
260
+ primitive: `idempotencyKey` already covers the common case cleanly, and a second field
261
+ whose only job is to avoid a documentation inconvenience was judged not worth the added
262
+ vocabulary (spec 8.2 §29-30). This may be revisited if a concrete use case proves
263
+ `idempotencyKey` alone is not conceptually clean enough — none has yet.
264
+
265
+ **Effect message security (spec 8.2 §33).** Framework diagnostics — anything Axiom itself
266
+ emits as a `RuntimeDiagnostic`/`ServerDiagnostic` — are sanitized according to
267
+ `DISCLOSABLE_DETAIL_KEYS` (see [`AUTHORITY.md`](AUTHORITY.md#diagnostics)) before crossing
268
+ the trust boundary. `EFFECT_MESSAGE_FIELD`/`EFFECT_CODE_FIELD` are different: they are
269
+ **application data**, populated verbatim from whatever `IntegrationFailure.message`/`.code`
270
+ the adapter returned, and dispatched into ordinary graph state through the event pipeline
271
+ like any other value. Once an application's own action copies an adapter's message into
272
+ state, **that application is responsible for what the message contains** — Axiom's
273
+ diagnostic sanitization does not reach into adapter-authored text. Never place a secret or
274
+ credential in an adapter's `IntegrationFailure.message`.
275
+
149
276
  ## No automatic compensation
150
277
 
151
278
  0.8 does not implement compensation. If an effect has a semantic inverse, it is another
package/docs/EVENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Events
2
2
 
3
- Axiom 0.8.1-alpha.1. An event is a typed fact — something that happened — never work
3
+ Axiom 0.8.2-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.
6
6
 
@@ -110,10 +110,20 @@ Full tables: [`VALIDATION.md`](VALIDATION.md#integrations-effects-triggers-and-e
110
110
  ## AgentAPI
111
111
 
112
112
  ```ts
113
- agent.getWebhookEvents(); // EventDef[] — events at least one trigger reacts to
113
+ agent.getTriggeredEvents(); // EventDef[] — events at least one trigger reacts to
114
114
  agent.getActionsTriggeredByEvent(id); // ActionDef[]
115
115
  ```
116
116
 
117
- "Which webhook can mutate `Order`?" (spec §78) is answerable by following
118
- `getWebhookEvents()` through `getActionsTriggeredByEvent()` to the actions it can reach,
117
+ `getTriggeredEvents()` is a **graph-static** query — "which `EventDef`s have a `TriggerDef`
118
+ bound to them" — not "which webhook deliveries has this server received" (spec 8.2 §34-37).
119
+ An event it returns may be dispatched by a verified external webhook, by an effect's
120
+ `succeededEventId`/`failedEventId`, or by any other internal source; the graph does not
121
+ record which, so nothing here claims to. Webhook routes and deployment-level registration
122
+ are host/deployment concerns, deliberately outside what `GraphQueries` infers.
123
+
124
+ `getWebhookEvents()` is a deprecated alias, kept for backward compatibility; new code should
125
+ call `getTriggeredEvents()`.
126
+
127
+ "Which trigger-bound event can mutate `Order`?" (spec §78) is answerable by following
128
+ `getTriggeredEvents()` through `getActionsTriggeredByEvent()` to the actions it can reach,
119
129
  without reading a single handler.
@@ -1,6 +1,6 @@
1
1
  # Expressions
2
2
 
3
- Axiom 0.8.1-alpha.1. An expression describes **what value is computed**. It is a tree of
3
+ Axiom 0.8.2-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.8.1-alpha.1. The `ApplicationGraph` is the authoritative representation of an
3
+ Axiom 0.8.2-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.8.0'
28
+ const graph = new ApplicationGraph(id, name, version?); // version defaults to '0.8.2'
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
@@ -1,6 +1,6 @@
1
1
  # Integrations
2
2
 
3
- Axiom 0.8.1-alpha.1. How an application declares and calls an external system, without
3
+ Axiom 0.8.2-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
 
@@ -101,6 +101,24 @@ via `AbortController`) to cancel the underlying provider call early, but this is
101
101
  optimization, never a correctness requirement: the runtime's own enforcement is what a graph
102
102
  author can rely on regardless of which adapter is registered.
103
103
 
104
+ ### A hung query delays other work, bounded by `timeoutMs` (spec 8.2 §40-42)
105
+
106
+ The Axiom authority executes every invocation it runs — an ordinary client request, a
107
+ trigger tick, an effect-outcome event dispatch — through one serialized FIFO queue
108
+ (`AxiomServer`'s internal `serialize`, spec 8.1 §26-30). A query that hangs therefore does
109
+ not merely block *its own* invocation: it also blocks whatever unrelated request happened
110
+ to queue up behind it, even one that shares no state, integration or trigger with it at
111
+ all. This is expected, not a bug to route around — it is what makes same-instant triggers
112
+ commit one at a time identically on the deterministic and the real host.
113
+
114
+ **The delay this can cause is bounded by `timeoutMs`, never indefinite.** The hung query
115
+ itself cannot run past its declared deadline (above), so the request queued behind it is
116
+ released as soon as that deadline fires — worst case, `timeoutMs`, not forever. Do not
117
+ introduce concurrent query execution to remove this delay; the observed serialization is
118
+ correct under the current authority model (spec 8.2 §41) and changing it is out of scope.
119
+ See `'a hung query delays a genuinely unrelated queued Action, bounded by timeoutMs'`
120
+ (`packages/server/test/timeout-and-scheduling.test.ts`) for the regression test.
121
+
104
122
  ## Registering an adapter
105
123
 
106
124
  ```ts
package/docs/LOCATIONS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Locations
2
2
 
3
- Axiom 0.8.1-alpha.1.
3
+ Axiom 0.8.2-alpha.1.
4
4
 
5
5
  ```text
6
6
  Expression = a value
@@ -1,6 +1,6 @@
1
1
  # Presentation
2
2
 
3
- Axiom 0.8.1-alpha.1. Presentation is **semantic UX intent**, expressed as data on a UI
3
+ Axiom 0.8.2-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.8.1-alpha.1. The runtime executes an `ApplicationIR`. It is domain-independent: it
3
+ Axiom 0.8.2-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.8.1-alpha.1. Runtime guarantees, stated formally. This file defines behavior; it
3
+ Axiom 0.8.2-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.8.1-alpha.1. A `StateDef` is a named application value: stored, or computed from
3
+ Axiom 0.8.2-alpha.1. A `StateDef` is a named application value: stored, or computed from
4
4
  other state.
5
5
 
6
6
  ```ts
package/docs/TRIGGERS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Triggers
2
2
 
3
- Axiom 0.8.1-alpha.1. A `TriggerDef` says **when** an action should be invoked, without
3
+ Axiom 0.8.2-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.
@@ -69,15 +69,25 @@ triggers targeting a server-authority one, are rejected at validation
69
69
  do nothing with it.
70
70
 
71
71
  **A client-authority trigger of a kind the browser cannot execute is a validation error,
72
- not a silent no-op.** The browser runtime implements no trigger kind at all today
73
- (`BROWSER_TRIGGER_CAPABILITIES.supportedTriggerKinds` is empty), so `validateGraph`/
74
- `compileToIR` reject such a trigger with `CLIENT_TRIGGER_UNSUPPORTED` — the same
75
- capability-gate pattern `RendererCapabilities` already applies to UI node kinds. Before
76
- spec 8.1, the trigger validated and compiled into `ApplicationIR.triggers` and simply never
77
- fired, which is exactly the "publicly declared, typechecks, passes validation, has no
78
- defined runtime behaviour" shape the framework forbids. Compiling for a trigger runtime
79
- that *does* implement a kind (`compileToIR(graph, { triggerRuntime })`) accepts it. Only the
80
- authoritative runtime executes triggers today. See [Not in 0.8.0](AUTHORITY.md#not-in-080).
72
+ not a silent no-op — for any call that actually names the browser's capabilities.** The
73
+ browser runtime implements no trigger kind at all today
74
+ (`BROWSER_TRIGGER_CAPABILITIES.supportedTriggerKinds` is empty), so `compileToIR` — which
75
+ applies `BROWSER_TRIGGER_CAPABILITIES` by default — and `validateForBrowser` both reject
76
+ such a trigger with `CLIENT_TRIGGER_UNSUPPORTED`, the same capability-gate pattern
77
+ `RendererCapabilities` already applies to UI node kinds. Before spec 8.1, the trigger
78
+ validated and compiled into `ApplicationIR.triggers` and simply never fired, which is
79
+ exactly the "publicly declared, typechecks, passes validation, has no defined runtime
80
+ behaviour" shape the framework forbids.
81
+
82
+ **`validateGraph(graph)` with no options is target-neutral by design (spec 8.2 §2-4) and
83
+ does not raise `CLIENT_TRIGGER_UNSUPPORTED`** — a graph is never rejected for a trigger
84
+ runtime nobody named, exactly as it is never rejected for a renderer nobody named. A
85
+ validate-only workflow that wants the browser-real answer without compiling calls
86
+ `validateForBrowser(graph)` (`@cynodia/axiom-compiler`) instead of the bare call; both it
87
+ and `compileToIR` apply the identical `BROWSER_TRIGGER_CAPABILITIES`, so they never
88
+ disagree. Compiling for a trigger runtime that *does* implement a kind
89
+ (`compileToIR(graph, { triggerRuntime })`) accepts it. Only the authoritative runtime
90
+ executes triggers today. See [Not in 0.8.0](AUTHORITY.md#not-in-080).
81
91
 
82
92
  ## Interval semantics
83
93
 
package/docs/UI.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # UI
2
2
 
3
- Axiom 0.8.1-alpha.1. Eleven semantic UI node kinds describe **what exists and what it does**.
3
+ Axiom 0.8.2-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.8.1-alpha.1. Validation is authoring-time structural checking. It is not the same
3
+ Axiom 0.8.2-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
 
@@ -132,7 +132,7 @@ Full model: [`INTEGRATIONS.md`](INTEGRATIONS.md), [`EFFECTS.md`](EFFECTS.md),
132
132
  | `TRIGGER_WRONG_AUTHORITY` | An `event` trigger targeting a client-authority action (only the server dispatches events), or a `route-enter`/`route-leave` trigger targeting a server-authority one (only the client router dispatches those). |
133
133
  | `TRIGGER_TARGET_SOURCE_MISMATCH` | A trigger — which always invokes with `source: 'system'` — targets an action whose `invocation.allowedSources` excludes `'system'`; the trigger could never succeed (spec 8.1 §3-9). |
134
134
  | `INVALID_INVOCATION_SOURCE` | `ActionDef.invocation.allowedSources` is present but empty — the action could never be invoked at all. |
135
- | `CLIENT_TRIGGER_UNSUPPORTED` | A client-authority trigger of a kind the intended trigger runtime does not execute — before 8.1 this validated and compiled anyway, and simply never fired (spec 8.1 §31-36). The browser trigger runtime executes no kind today. |
135
+ | `CLIENT_TRIGGER_UNSUPPORTED` | A client-authority trigger of a kind the intended trigger runtime does not execute — before 8.1 this validated and compiled anyway, and simply never fired (spec 8.1 §31-36). The browser trigger runtime executes no kind today. Only raised when a `triggerRuntime` is actually named — `validateGraph(graph)` with no options is target-neutral and never raises it; `validateForBrowser(graph)`/`compileToIR(graph)` name the real browser capabilities and do (spec 8.2 §2-6). The message states the remediation: move the target action to server authority, or compile for a trigger runtime that publishes the kind. |
136
136
 
137
137
  ### UI and routing
138
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom",
3
- "version": "0.8.1-alpha.1",
3
+ "version": "0.8.2-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.8.1-alpha.1",
36
- "@cynodia/axiom-runtime": "0.8.1-alpha.1",
37
- "@cynodia/axiom-compiler": "0.8.1-alpha.1",
38
- "@cynodia/axiom-agent-api": "0.8.1-alpha.1"
35
+ "@cynodia/axiom-core": "0.8.2-alpha.1",
36
+ "@cynodia/axiom-runtime": "0.8.2-alpha.1",
37
+ "@cynodia/axiom-compiler": "0.8.2-alpha.1",
38
+ "@cynodia/axiom-agent-api": "0.8.2-alpha.1"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b tsconfig.json"