@cynodia/axiom 0.4.0-alpha.1 → 0.4.1-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.
Files changed (2) hide show
  1. package/README.md +75 -2
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -104,6 +104,57 @@ seventeenth breaks an invariant, the first sixteen do not survive — you never
104
104
  rollback logic yourself, and `runtime.getMutationLog()` shows every attempted write with
105
105
  its `outcome` of `committed` or `rolled-back`.
106
106
 
107
+ Within one action, each `for-each` iteration reads the state the previous iterations
108
+ proposed. Two lines for the same product debit it twice:
109
+
110
+ ```text
111
+ stock 5 → line A (−3) → 2 → line A (−3) → −1 → stock >= 0 fails → all rolled back
112
+ ```
113
+
114
+ That is a guarantee, not an implementation detail: an aggregate rule can be expressed as a
115
+ simple per-record invariant.
116
+
117
+ ## What is enforced, and where
118
+
119
+ Two kinds of rule, and they answer different questions:
120
+
121
+ | | Question | Applies to |
122
+ | --- | --- | --- |
123
+ | **Constraint** | Is this state allowed? | Every instance of an entity, wherever it is stored — including instances nested inside other entities. |
124
+ | **Transition constraint** | Is this *change* allowed? | The instance as it was when the transaction began, compared with the instance the transaction proposes. |
125
+
126
+ A transition constraint is what makes a rule like "a confirmed order never changes" hold
127
+ no matter which path attempts the write:
128
+
129
+ ```ts
130
+ graph.addNode<TransitionConstraintDef>({
131
+ id: ORDER_SEALED,
132
+ kind: 'transition-constraint',
133
+ entityId: ORDER,
134
+ previousScopeId: PREVIOUS,
135
+ proposedScopeId: PROPOSED,
136
+ message: 'A confirmed order cannot be changed.',
137
+ expression: binary(
138
+ 'or',
139
+ binary('neq', field(ref(PREVIOUS), STATUS), literal('confirmed')),
140
+ binary('eq', ref(PROPOSED), ref(PREVIOUS)),
141
+ ),
142
+ });
143
+ ```
144
+
145
+ **Governed paths** — actions, `for-each` iterations, and input bindings — all evaluate
146
+ entity constraints *and* transition constraints against the proposed state, and roll the
147
+ whole transaction back if either refuses. You do not have to remember not to bind an input
148
+ to a protected location: binding it and typing into it is simply refused, with a
149
+ `TRANSITION_CONSTRAINT_VIOLATION` naming the rule, the entity, and the previous and
150
+ proposed values.
151
+
152
+ **`hydrateState` is not governed.** It replaces a state value outright for hosts, tests and
153
+ seeding, and evaluates nothing. It is deliberately not named like a normal write.
154
+
155
+ **"Previous" means transaction entry** — committed state as it was before the outermost
156
+ transaction began. Not the previous operation, and not the previous iteration.
157
+
107
158
  ## Collections
108
159
 
109
160
  Values are described by expressions, writable positions by **locations**. Collections add
@@ -140,8 +191,30 @@ const confirm = forEach(ref(LINES), LINE, [
140
191
  ]);
141
192
  ```
142
193
 
143
- None of this is a callback. `map`, `sort`, `filter`, `find` and `for-each` are data: they
144
- serialize, they validate, and an agent can ask what they read and write.
194
+ None of this is a callback. `map`, `sort`, `filter`, `find`, `every`, `some`, `flatten`,
195
+ `conditional` and `for-each` are data: they serialize, they validate, and an agent can ask
196
+ what they read and write.
197
+
198
+ **Collection operators are strict about their source.** `null` means a missing or invalid
199
+ collection and fails the evaluation; `[]` means an empty collection and works normally
200
+ (`sum([])` is `0`, `count([])` is `0`, `every([])` is `true`). Nothing returns a
201
+ plausible-looking value while reporting a failure. Where a collection may legitimately be
202
+ absent, say so:
203
+
204
+ ```ts
205
+ coalesce(field(ref(CURRENT_ORDER), LINES), literal([]))
206
+ ```
207
+
208
+ **Presence is not emptiness.** `required(value)` asks only whether a value exists:
209
+
210
+ ```text
211
+ required(null) → false required([]) → true
212
+ required(0) → true required('') → true
213
+ required(false)→ true
214
+ ```
215
+
216
+ Use `is-empty` / `non-empty` for collections and strings, and `coalesce` to fall back on
217
+ absence — which means falling back *to* an empty collection now works.
145
218
 
146
219
  ## Diagnostics
147
220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom",
3
- "version": "0.4.0-alpha.1",
3
+ "version": "0.4.1-alpha.1",
4
4
  "description": "AI-native semantic web application framework.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",
@@ -31,10 +31,10 @@
31
31
  }
32
32
  },
33
33
  "dependencies": {
34
- "@cynodia/axiom-core": "0.4.0-alpha.1",
35
- "@cynodia/axiom-runtime": "0.4.0-alpha.1",
36
- "@cynodia/axiom-compiler": "0.4.0-alpha.1",
37
- "@cynodia/axiom-agent-api": "0.4.0-alpha.1"
34
+ "@cynodia/axiom-core": "0.4.1-alpha.1",
35
+ "@cynodia/axiom-runtime": "0.4.1-alpha.1",
36
+ "@cynodia/axiom-compiler": "0.4.1-alpha.1",
37
+ "@cynodia/axiom-agent-api": "0.4.1-alpha.1"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc -b tsconfig.json"