@cavelang/scenario 0.35.0 → 0.36.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 +447 -6
- package/dist/src/bind.d.ts +3 -0
- package/dist/src/bind.d.ts.map +1 -1
- package/dist/src/bind.js +188 -33
- package/dist/src/bind.js.map +1 -1
- package/dist/src/capture.d.ts +4 -0
- package/dist/src/capture.d.ts.map +1 -0
- package/dist/src/capture.js +33 -0
- package/dist/src/capture.js.map +1 -0
- package/dist/src/error.d.ts +1 -1
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js.map +1 -1
- package/dist/src/exact.d.ts.map +1 -1
- package/dist/src/exact.js +27 -18
- package/dist/src/exact.js.map +1 -1
- package/dist/src/explain.d.ts.map +1 -1
- package/dist/src/explain.js +23 -0
- package/dist/src/explain.js.map +1 -1
- package/dist/src/explanation-entries.d.ts +4 -0
- package/dist/src/explanation-entries.d.ts.map +1 -0
- package/dist/src/explanation-entries.js +89 -0
- package/dist/src/explanation-entries.js.map +1 -0
- package/dist/src/model.d.ts +2 -0
- package/dist/src/model.d.ts.map +1 -1
- package/dist/src/record.d.ts +1 -1
- package/dist/src/record.d.ts.map +1 -1
- package/dist/src/record.js +286 -46
- package/dist/src/record.js.map +1 -1
- package/package.json +7 -7
- package/src/bind.ts +182 -31
- package/src/capture.ts +33 -0
- package/src/error.ts +1 -0
- package/src/exact.ts +24 -22
- package/src/explain.ts +21 -0
- package/src/explanation-entries.ts +85 -0
- package/src/model.ts +2 -0
- package/src/record.ts +268 -49
package/README.md
CHANGED
|
@@ -3,8 +3,56 @@
|
|
|
3
3
|
Typed, replayable inputs for decision evaluators and formal solver models.
|
|
4
4
|
The package binds ordinary CAVE-Q patterns against an explicit transaction-
|
|
5
5
|
and valid-time snapshot, then overlays hypothetical CAVE claims inside a
|
|
6
|
-
rolled-back savepoint. The returned record is plain immutable data;
|
|
7
|
-
|
|
6
|
+
rolled-back savepoint. The returned record is plain immutable data; the scenario
|
|
7
|
+
operation leaves no transaction open while an evaluator or Wasm solver runs.
|
|
8
|
+
A caller-owned outer transaction remains under the caller's control.
|
|
9
|
+
|
|
10
|
+
| Task | Start here |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Choose binding, snapshot and missing-data policies | [Contracts](#contracts) |
|
|
13
|
+
| Convert units or combine exact measurements | [Combining measurements](#combining-measurements) |
|
|
14
|
+
| Save an evaluation, recommendation or decision | [Explicit result governance](#explicit-result-governance) |
|
|
15
|
+
| Recover from a rejected record or retry an existing ID | [Recording and retry](#recording-and-retry) |
|
|
16
|
+
| Check a stored solver result against a model/backend | [Replay compatibility](#replay-compatibility) |
|
|
17
|
+
| Inspect measured parsing and arithmetic costs | [Performance measurements](#performance-measurements) |
|
|
18
|
+
|
|
19
|
+
`bind()` first captures the definition's own enumerable data, including nested
|
|
20
|
+
snapshot and binding fields. Validation, materialization and digesting use that
|
|
21
|
+
captured data, so changing getters cannot give the resulting record a different
|
|
22
|
+
identity or policy from the one validated. The capture does not freeze the
|
|
23
|
+
caller-owned definition.
|
|
24
|
+
Capture traverses nested data iteratively, so deeply nested malformed values
|
|
25
|
+
reach field validation instead of exhausting the JavaScript call stack. It
|
|
26
|
+
preserves shared references and captures sibling getters before descending into
|
|
27
|
+
their values. This removes a stack-depth failure; it does not impose a general
|
|
28
|
+
memory or execution-time budget on caller data.
|
|
29
|
+
After field validation, binding computes the definition digest before database
|
|
30
|
+
access. The complete captured definition, including extra caller metadata, must
|
|
31
|
+
be JSON serializable within the runtime's serialization depth. Cycles, BigInt
|
|
32
|
+
values and excessive serialization depth reject with `invalid-definition`;
|
|
33
|
+
correcting the definition permits a fresh binding on the same store.
|
|
34
|
+
|
|
35
|
+
`bind()` deeply freezes its returned record, including nested values, evidence
|
|
36
|
+
arrays and snapshot fields; `run()` passes that same immutable form to the
|
|
37
|
+
evaluator. Strict-mode mutation attempts throw instead of silently changing data
|
|
38
|
+
under an already-computed digest. The caller's definition remains mutable and
|
|
39
|
+
is not frozen. The returned snapshot contains only the documented snapshot
|
|
40
|
+
fields, not unrelated caller metadata. JSON serialization remains available;
|
|
41
|
+
parsed copies are ordinary mutable objects whose digests are checked on replay.
|
|
42
|
+
Binding checks SQLite's external commit revision and local change count around
|
|
43
|
+
materialization. A change that could mix database states throws
|
|
44
|
+
`ScenarioInputError` with code `snapshot-changed`; retry the binding before
|
|
45
|
+
evaluating. Even a backdated import below the captured transaction head
|
|
46
|
+
invalidates that attempt. Rolled-back overlay inserts are accounted for, and
|
|
47
|
+
bindings without an overlay remain usable on read-only connections.
|
|
48
|
+
|
|
49
|
+
Each binding reads the current requested snapshot without retaining a projection
|
|
50
|
+
cache. Inside a caller transaction it can observe staged rows. An outer or
|
|
51
|
+
savepoint rollback does not rewrite an input record already returned, including
|
|
52
|
+
its values, digest and evidence IDs; a fresh binding reflects the restored store.
|
|
53
|
+
|
|
54
|
+
Given an open `store` containing `system HAS team-size: 8 people`, this
|
|
55
|
+
definition evaluates a hypothetical team of 12 without changing that claim:
|
|
8
56
|
|
|
9
57
|
```ts
|
|
10
58
|
import { bind, explanationContext, Model } from '@cavelang/scenario'
|
|
@@ -38,17 +86,149 @@ const inputs = bind(store, definition)
|
|
|
38
86
|
// inputs.values['team-size'] is exact integer 12; the store still says 8.
|
|
39
87
|
```
|
|
40
88
|
|
|
89
|
+
## Combining measurements
|
|
90
|
+
|
|
91
|
+
Choose a common target unit before summing or comparing measurements. This
|
|
92
|
+
complete example converts seconds to milliseconds and sums exactly. Conversion
|
|
93
|
+
factors describe target units per source unit; conversions are directed and
|
|
94
|
+
are not chained automatically.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import assert from 'node:assert/strict'
|
|
98
|
+
import { open } from '@cavelang/store'
|
|
99
|
+
import { bind, type Model } from '@cavelang/scenario'
|
|
100
|
+
|
|
101
|
+
const store = open()
|
|
102
|
+
try {
|
|
103
|
+
store.ingest('a HAS duration: 1 s\nb HAS duration: 500 ms')
|
|
104
|
+
const definition: Model.Definition = {
|
|
105
|
+
id: 'total-duration',
|
|
106
|
+
modelDigest: `sha256:${'0'.repeat(64)}`, // replace with your evaluator's model digest
|
|
107
|
+
snapshot: { aliases: 'exact', resolution: 'winner', minimumConfidence: 0.5 },
|
|
108
|
+
bindings: [{
|
|
109
|
+
id: 'duration',
|
|
110
|
+
query: '?job HAS duration: ?value',
|
|
111
|
+
select: 'value',
|
|
112
|
+
expected: {
|
|
113
|
+
kind: 'integer', unit: 'ms',
|
|
114
|
+
conversions: [{ from: 's', to: 'ms', factor: '1000' }]
|
|
115
|
+
},
|
|
116
|
+
cardinality: 'many', reduce: 'sum', scenarioOverride: false,
|
|
117
|
+
policies: { missing: 'reject', contested: 'reject', retracted: 'exclude', unresolved: 'reject' }
|
|
118
|
+
}]
|
|
119
|
+
}
|
|
120
|
+
const inputs = bind(store, definition)
|
|
121
|
+
assert.deepEqual(inputs.values['duration'], {
|
|
122
|
+
kind: 'integer', value: '1500', unit: 'ms', approximate: false
|
|
123
|
+
})
|
|
124
|
+
} finally {
|
|
125
|
+
store.close()
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Without the target unit and conversion, these mixed-unit inputs fail with
|
|
130
|
+
`incompatible-unit`. Use `reduce: 'all'` to retain separate measurements instead
|
|
131
|
+
of computing a total. Each candidate retains its evidence even when reduced.
|
|
132
|
+
|
|
41
133
|
## Contracts
|
|
42
134
|
|
|
43
135
|
- `asOf`, `at`, alias handling, resolution, and minimum confidence are frozen
|
|
44
136
|
into every input record. Omitting `asOf` records the exact current head.
|
|
137
|
+
Scenario boundaries use the shared CAVE-Q parsers. Transaction-time `asOf`
|
|
138
|
+
includes the full UTC year, month, day or timestamp second; a UUIDv7 includes
|
|
139
|
+
that exact append. Transaction periods are clipped at the UUID epoch, so
|
|
140
|
+
wholly pre-1970 `asOf` periods contain no recorded transactions. Valid-time
|
|
141
|
+
`at` remains independent and anchors at the start of its named period
|
|
142
|
+
or at the specified timestamp. Both boundaries require valid strings and
|
|
143
|
+
reject impossible calendar dates before database access, even when no
|
|
144
|
+
bindings are requested. UUIDv7 is accepted only for `asOf`.
|
|
145
|
+
- `definitionDigest` identifies the complete authored definition, including
|
|
146
|
+
binding queries, types, policies, snapshot settings, and overlay text. Object
|
|
147
|
+
key order and omitted undefined optional fields do not change the digest.
|
|
148
|
+
- Winner-mode bindings query resolved candidates directly. Coexisting mode
|
|
149
|
+
reads both unresolved and resolved candidates to identify contested evidence;
|
|
150
|
+
winner mode does not perform that unused unresolved read.
|
|
45
151
|
- Cardinality and missing/contested/retracted/unresolved behavior are explicit.
|
|
46
152
|
`many` additionally requires `all`, `min`, `max`, or `sum`; there is no
|
|
47
153
|
implicit first-row policy.
|
|
154
|
+
Unknown or omitted choices in snapshot modes, cardinality, reduction, expected
|
|
155
|
+
value kind, or these policies fail with `ScenarioInputError` (`invalid-definition`)
|
|
156
|
+
before database access. `scenarioOverride` must be an explicit Boolean.
|
|
157
|
+
Malformed definition, snapshot, binding, expected-value and policy containers
|
|
158
|
+
use that same classified error, including nulls and arrays supplied as objects.
|
|
159
|
+
`bindings` must be an array of binding objects without holes; query and supplied
|
|
160
|
+
overlay text must be strings. These checks also protect JavaScript callers
|
|
161
|
+
loading definitions from JSON.
|
|
162
|
+
Numeric target units, when supplied, must be non-empty strings. This is
|
|
163
|
+
checked even for optional or many-valued inputs with no matching rows; omit
|
|
164
|
+
`unit` to retain the authored unit without requesting a conversion.
|
|
165
|
+
A supplied `select` must be a string naming a variable bound by the query,
|
|
166
|
+
without its leading `?` (for example, `select: 'n'` for `?n`). Subject,
|
|
167
|
+
verb, object and attribute value variables are selectable. Invalid selections
|
|
168
|
+
fail before reads, including optional inputs with no matching rows. Only
|
|
169
|
+
Boolean existence inputs may omit `select`.
|
|
170
|
+
Numeric inputs parse the selected variable, just like other input kinds;
|
|
171
|
+
they never substitute an unrelated attribute value from the matching row.
|
|
172
|
+
Selecting an attribute value retains its valid-time interpolation and
|
|
173
|
+
uncertainty. Selecting another slot does not inherit that attribute's
|
|
174
|
+
uncertainty, including when the match comes from an overlay.
|
|
175
|
+
Valid binding names such as `constructor` and `toString` are ordinary keys;
|
|
176
|
+
absent overlay entries never resolve through JavaScript's object prototype.
|
|
177
|
+
Scenario IDs, binding IDs and model digests must be strings at runtime;
|
|
178
|
+
numbers, missing IDs, boxed strings and other coercible values fail before
|
|
179
|
+
database access. The string binding ID `"1"` remains valid, while numeric `1`
|
|
180
|
+
cannot bypass duplicate detection and overwrite that same input slot.
|
|
181
|
+
Enum definitions require a non-empty, dense array of unique strings. A
|
|
182
|
+
string, set, array hole or non-string entry is rejected before reads, even
|
|
183
|
+
for an optional binding with no matching rows. Enum membership uses exact,
|
|
184
|
+
case-sensitive string equality; a substring is not an allowed enum value.
|
|
185
|
+
Boolean `trueValue` and `falseValue` mappings must be strings when supplied
|
|
186
|
+
and must remain distinct after applying the defaults `"true"` and `"false"`.
|
|
187
|
+
Overlapping mappings fail before reads instead of silently favoring true.
|
|
188
|
+
Empty strings remain valid explicit mappings when the two values differ.
|
|
189
|
+
- Decimal trailing zeros cancel before large bigint operands are constructed.
|
|
190
|
+
This preserves multiplier and conversion semantics: `1.25000M USD` is exactly
|
|
191
|
+
`1250000 USD`, and `0.125000ms` converts to `1/8000 s` with factor `0.001`.
|
|
192
|
+
The approximation flag is retained independently of exact arithmetic.
|
|
193
|
+
Scalars without a K/M/B/T multiplier reuse the normalized fraction directly.
|
|
194
|
+
Stored long decimal spellings retain exact converted uncertainty, confidence
|
|
195
|
+
and source row evidence; repeating the binding does not append store history.
|
|
48
196
|
- Authored decimals and multiplier spellings become exact rationals without a
|
|
49
197
|
JavaScript `number` round trip. Unit changes require a declared exact
|
|
50
198
|
conversion. Approximation, uncertainty, sigma level, and confidence remain
|
|
51
199
|
separate fields and never become solver weights automatically.
|
|
200
|
+
Conversion definitions must be an array of entries with non-empty string
|
|
201
|
+
endpoints and positive exact rational factors. Each directed unit pair may
|
|
202
|
+
appear only once, even with identical factors; the reverse direction is a
|
|
203
|
+
separate pair. Malformed entries and duplicate pairs fail as invalid
|
|
204
|
+
definitions before reads, so array order cannot choose between conflicting
|
|
205
|
+
factors.
|
|
206
|
+
Numeric `sum`, `min` and `max` require all candidates to have the same unit
|
|
207
|
+
after conversion, or all to be unitless. Mixed units (including unitless
|
|
208
|
+
values mixed with units) fail with `incompatible-unit`. Declare a target
|
|
209
|
+
`unit` and explicit conversions to combine compatible measurements. The
|
|
210
|
+
`all` reducer retains each candidate's own unit without combining values.
|
|
211
|
+
Equal-denominator sums add numerators before reduction, and comparisons of
|
|
212
|
+
normalized equal-denominator values compare numerators directly. With positive
|
|
213
|
+
denominators, zero or opposite-sign numerators also determine ordering without
|
|
214
|
+
products; equal nonzero numerators use denominator order with the appropriate
|
|
215
|
+
sign. Same-sign fractions also avoid products when numerator and denominator
|
|
216
|
+
orders reinforce each other, as in the solver's exact comparison. These
|
|
217
|
+
shortcuts avoid expanding intermediate integers in those cases. A 16,900-pair
|
|
218
|
+
integer cross-product regression covers both denominator signs, and large
|
|
219
|
+
reinforcing-order cases check differences below floating-point precision.
|
|
220
|
+
Other comparisons compute each cross-product once and account for the sign of its
|
|
221
|
+
common denominator. Parsing and conversion normalize denominators positive;
|
|
222
|
+
direct internal comparison also orders equivalent negative-denominator forms
|
|
223
|
+
correctly. Binding regressions also cover negative converted values separated
|
|
224
|
+
below floating-point precision: `min` and `max` retain their exact ordering,
|
|
225
|
+
including a positive conversion factor written with two negative components.
|
|
226
|
+
Repeated binding preserves the input record and store history. These paths retain exactness
|
|
227
|
+
while avoiding unnecessarily large intermediates; they impose no work cap.
|
|
228
|
+
|
|
229
|
+
See [Performance measurements](#performance-measurements) for reproducible
|
|
230
|
+
parsing, arithmetic and reduction benchmarks.
|
|
231
|
+
|
|
52
232
|
- Durable evidence uses exact CAVE row IDs. Rolled-back assumptions use stable
|
|
53
233
|
`scenario:<scenario-id>:<overlay-digest>#<claim>` IDs, so replay identity
|
|
54
234
|
does not depend on transient SQLite row IDs.
|
|
@@ -56,18 +236,58 @@ const inputs = bind(store, definition)
|
|
|
56
236
|
by their authored entity names. This prevents current alias state from
|
|
57
237
|
leaking into a historical snapshot; transitive overlay composition waits on
|
|
58
238
|
the shared snapshot-query primitives tracked in the backlog.
|
|
239
|
+
Confidence, tag, context and numeric `WHERE` filters use the shared query
|
|
240
|
+
engine for both stored and hypothetical claims. Overlay candidates retain
|
|
241
|
+
their authored confidence and scenario evidence IDs. Temporary rows are
|
|
242
|
+
rolled back before typed input conversion, including conversions that fail.
|
|
243
|
+
Invalid conversion factors report `invalid-definition`; missing unit mappings
|
|
244
|
+
or unitless values with a required target unit report `incompatible-unit`.
|
|
245
|
+
A valid conversion that produces a fraction for an integer binding reports
|
|
246
|
+
`invalid-value`. These failures preserve transaction history. Corrected
|
|
247
|
+
bindings can run immediately, and subsequent unpinned bindings observe new
|
|
248
|
+
committed facts rather than retaining failed overlay values.
|
|
59
249
|
|
|
60
250
|
Use `run(store, definition, evaluate)` when convenient. It calls `bind`
|
|
61
251
|
synchronously and invokes `evaluate` only after the overlay and verb registry
|
|
62
252
|
have been restored, including when the evaluator later times out or crashes.
|
|
253
|
+
If overlay insertion throws after writing temporary claims or vocabulary,
|
|
254
|
+
`run` rejects with that error without invoking the evaluator. The transaction
|
|
255
|
+
restores history and the verb registry; the same definition can be retried
|
|
256
|
+
after the insertion failure is resolved.
|
|
257
|
+
An asynchronous evaluator holds no overlay transaction while its promise is
|
|
258
|
+
pending. Subsequent store writes persist whether that promise resolves or
|
|
259
|
+
rejects. `run` forwards the evaluator's result or rejection; it does not impose
|
|
260
|
+
a deadline or provide cancellation. Configure those in the evaluator itself.
|
|
63
261
|
|
|
64
262
|
For solver runs, `explanationContext(definition, inputs)` converts the frozen
|
|
65
263
|
record into `@cavelang/solver` explanation metadata. It retains every binding's
|
|
66
264
|
query, typed and authored value, exact belief/scenario evidence IDs, snapshot
|
|
67
|
-
policy, input-record digest, and overlay digest.
|
|
265
|
+
policy, input-record digest, and overlay digest. Input records must use
|
|
266
|
+
`cave.scenario/inputs@1`; unsupported schemas reject even when their content
|
|
267
|
+
digest matches. Each declared binding must occur exactly once in the record;
|
|
268
|
+
missing, duplicate and unknown binding IDs reject before metadata is returned.
|
|
269
|
+
Passing that context to
|
|
68
270
|
`Solve.runWithExplanation` also rejects replay against a different canonical
|
|
69
271
|
model digest.
|
|
70
272
|
|
|
273
|
+
Before attaching queries to evidence, `explanationContext` verifies both the
|
|
274
|
+
definition digest and the input record's content digest. Changed definitions
|
|
275
|
+
or modified records fail explicitly. It captures the supplied definition once
|
|
276
|
+
before those checks and uses that captured definition for the returned queries.
|
|
277
|
+
Changing getters cannot attach a different query after digest validation;
|
|
278
|
+
a later call captures fresh values and rejects a changed definition.
|
|
279
|
+
|
|
280
|
+
The input record is also copied before validation. Checked values and evidence
|
|
281
|
+
come from that copy, so changing record getters or later mutations of a
|
|
282
|
+
JSON-loaded record cannot rewrite the explanation's validated inputs. A new
|
|
283
|
+
call rechecks the supplied record and rejects contents carrying a stale digest.
|
|
284
|
+
|
|
285
|
+
Older input records without
|
|
286
|
+
`definitionDigest` remain readable as stored artifacts but must be rebound
|
|
287
|
+
with the intended definition before generating a verified explanation.
|
|
288
|
+
Rebinding produces a new input digest; record any new evaluation under a new
|
|
289
|
+
run ID so the earlier artifact keeps its original content and provenance.
|
|
290
|
+
|
|
71
291
|
```ts
|
|
72
292
|
const inputs = bind(store, definition)
|
|
73
293
|
const report = await Solve.runWithExplanation(
|
|
@@ -128,11 +348,104 @@ runs only after rollback; recording, recommendation, and human decision remain
|
|
|
128
348
|
three explicit operations. The package test suite exercises this complete
|
|
129
349
|
non-solver architecture-choice workflow.
|
|
130
350
|
|
|
351
|
+
`run()` returns the evaluator's result without copying or freezing it. Its input
|
|
352
|
+
record is frozen, but ownership of the returned result stays with the caller.
|
|
353
|
+
JSON validation and snapshot capture happen when `Record.result()` is called;
|
|
354
|
+
changes to an output before that call become part of the recorded evaluation.
|
|
355
|
+
|
|
356
|
+
Before recording an external evaluation, `Record.result` requires non-empty
|
|
357
|
+
string evaluator names and versions, an explicit JSON output (`null` is valid),
|
|
358
|
+
and an input digest matching the supplied input record's contents. Missing
|
|
359
|
+
metadata or modified inputs carrying an old digest throw `TypeError` without
|
|
360
|
+
appending a claim. This uses the same content-digest check as solver explanation
|
|
361
|
+
construction. Previously stored artifacts remain readable; rebind changed
|
|
362
|
+
inputs and use a new result ID for a corrected evaluation.
|
|
363
|
+
|
|
364
|
+
### Recording and retry
|
|
365
|
+
|
|
366
|
+
Recording captures one JSON snapshot of the supplied artifact before opening
|
|
367
|
+
its transaction. Getters are read during that capture; validation, predecessor
|
|
368
|
+
checks, identity and stored content all use the captured values. The returned
|
|
369
|
+
outcome contains that stored snapshot, so later changes to caller-owned objects
|
|
370
|
+
do not change it. Predecessor checks and writes still share one transaction.
|
|
371
|
+
The outcome's artifact is a detached, mutable JSON object. Editing it does not
|
|
372
|
+
edit stored history; a fresh `Record.read()` returns the stored content, and
|
|
373
|
+
recording that unchanged content again returns `existing`.
|
|
374
|
+
|
|
375
|
+
When several prepared artifacts must be recorded together, wrap their writes
|
|
376
|
+
in one synchronous caller transaction:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
store.transaction(() => {
|
|
380
|
+
Record.result(store, evaluation)
|
|
381
|
+
Record.recommendation(store, recommendation)
|
|
382
|
+
Record.decision(store, decision)
|
|
383
|
+
})
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Here `recommendation` and `decision` are already prepared artifacts referencing
|
|
387
|
+
the preceding IDs. If any call throws, let the exception escape the callback:
|
|
388
|
+
all writes in this transaction roll back, including newly recorded predecessors.
|
|
389
|
+
Correct the rejected artifact and retry with the same IDs. Repeating a committed
|
|
390
|
+
chain with identical content returns `existing` for each artifact. Perform
|
|
391
|
+
evaluation and obtain the human decision before opening this transaction.
|
|
392
|
+
|
|
131
393
|
Each stable ID owns one append-only artifact series. Re-recording identical
|
|
132
|
-
content returns `existing`; reusing the ID for different content
|
|
133
|
-
`RecordConflictError`.
|
|
394
|
+
content returns `existing`; reusing the ID for different content anywhere in
|
|
395
|
+
its history throws `RecordConflictError`. A retraction makes the record
|
|
396
|
+
unavailable to reads and predecessor checks, but retains ownership of its ID.
|
|
397
|
+
Explicitly recording identical content restores it with a new claim; different
|
|
398
|
+
content requires a new ID. The write is one atomic claim, with canonical JSON
|
|
134
399
|
encoded as a code value so export, backup, and sync preserve the complete
|
|
135
400
|
model digest, backend/version, snapshot, inputs, evidence, limits, and outcome.
|
|
401
|
+
Result reads reuse recording validation: solver explanation schemas must match,
|
|
402
|
+
and solver model digests, backend names and versions must be non-empty strings.
|
|
403
|
+
External evaluations require evaluator identity, output and a matching input
|
|
404
|
+
digest. These checks also apply when a result is read as a predecessor for a
|
|
405
|
+
new recommendation or decision. Invalid imported results fail without appending
|
|
406
|
+
history; a corrected payload can be read again.
|
|
407
|
+
Governance record reads also enforce required payloads, author/action fields,
|
|
408
|
+
status choices and non-empty string predecessor IDs using the same validators
|
|
409
|
+
as recording. Optional recommendation rationale/author, decision rationale and
|
|
410
|
+
action message fields must be strings when present. Empty strings and omission
|
|
411
|
+
are accepted; null, numbers, booleans, arrays and objects are rejected without
|
|
412
|
+
appending. Imported reads enforce the same rule and recover after a corrected
|
|
413
|
+
payload is imported. These checks also reject a malformed immediate predecessor
|
|
414
|
+
before a decision, action or external-effect record can be appended. Correcting
|
|
415
|
+
that predecessor permits retry with the previously rejected child ID.
|
|
416
|
+
Reads validate the local
|
|
417
|
+
artifact; predecessor existence and cross-record relationships are checked when
|
|
418
|
+
recording a new artifact.
|
|
419
|
+
A rejected write with a missing predecessor appends nothing and does not reserve
|
|
420
|
+
the attempted artifact ID. Record the required predecessor, then retry the same
|
|
421
|
+
artifact ID; subsequent identical retries return the existing row.
|
|
422
|
+
|
|
423
|
+
Stored artifact payloads must use valid base64url encoding and decode as UTF-8
|
|
424
|
+
JSON. Reads accept unpadded output and correctly padded base64url, and reject
|
|
425
|
+
ignored characters, excess padding and nonzero unused padding bits. Reading malformed
|
|
426
|
+
bytes throws instead of silently replacing them and returning altered audit
|
|
427
|
+
data; reads leave stored history unchanged. Explicit replacement characters
|
|
428
|
+
and other valid Unicode remain readable.
|
|
429
|
+
|
|
430
|
+
Artifact JSON accepts finite numbers, strings, booleans, null, dense arrays,
|
|
431
|
+
and plain objects. Imported JSON numbers that overflow to infinity, such as
|
|
432
|
+
`1e400`, reject at any nesting depth without changing stored history.
|
|
433
|
+
Arrays are captured by length and numeric index; custom
|
|
434
|
+
iterators cannot replace their contents or hide holes. Object keys are sorted, and optional fields whose value is
|
|
435
|
+
`undefined` are omitted. Sparse or undefined array entries, non-JSON objects
|
|
436
|
+
such as dates/maps/sets, and cycles throw `TypeError` before any write. Reusing
|
|
437
|
+
the same non-cyclic object in multiple fields remains valid; each occurrence
|
|
438
|
+
is preserved as JSON data.
|
|
439
|
+
Recording validates and serializes containers with explicit stacks, as reads
|
|
440
|
+
already do, so deeply nested valid artifacts can be recorded and retried without
|
|
441
|
+
call-stack overflow. Serialization retains existing payload bytes, including
|
|
442
|
+
numeric-index property ordering. This removes an engine stack limitation; it
|
|
443
|
+
does not impose a general artifact memory or size budget.
|
|
444
|
+
An ID must remain exactly one CAVE entity name after its namespace prefix is
|
|
445
|
+
added. The recorder verifies the complete parsed claim, including the intended
|
|
446
|
+
subject and artifact payload, before insertion. Whitespace, comments, or extra
|
|
447
|
+
claim syntax that alter this identity are rejected without appending anything;
|
|
448
|
+
the same validation applies when reading predecessor references.
|
|
136
449
|
|
|
137
450
|
`Record.recommendation`, `Record.decision`, `Record.action`, and
|
|
138
451
|
`Record.externalEffect` use different versioned schemas and entity namespaces.
|
|
@@ -140,7 +453,135 @@ Each checks its predecessor before appending. These latter two are audit
|
|
|
140
453
|
records only: they never invoke `@cavelang/act` or a hook. Governed action
|
|
141
454
|
execution remains the sole authority for an external effect.
|
|
142
455
|
|
|
456
|
+
Recording entry points also enforce their artifact schema at runtime, before
|
|
457
|
+
opening a transaction. `Record.result` accepts solver results and external
|
|
458
|
+
evaluations; each other entry point accepts only its own versioned schema.
|
|
459
|
+
Passing an action to `Record.result`, for example, throws `TypeError` instead
|
|
460
|
+
of bypassing the action's decision check. Missing and unknown schemas are
|
|
461
|
+
rejected at this same boundary, including for JavaScript callers.
|
|
462
|
+
|
|
463
|
+
Recommendations require `value`, decisions require `selected`, and actions
|
|
464
|
+
require `parameters`; explicit JSON `null` is valid for each. Decision
|
|
465
|
+
`decidedBy`, action `name` and external-effect `kind` must be non-empty strings.
|
|
466
|
+
Action status must be `validated`, `executed` or `failed`; external-effect
|
|
467
|
+
status must be `succeeded`, `failed` or `unknown`. Missing required fields and
|
|
468
|
+
unsupported statuses throw `TypeError` without appending, while predecessor
|
|
469
|
+
checks still run under the recording transaction. Optional rationale, message
|
|
470
|
+
and effect details remain optional. These audit records never execute effects.
|
|
471
|
+
|
|
472
|
+
Solver result records require a supported explanation outcome: `satisfied`,
|
|
473
|
+
`optimal`, `unsatisfied`, or `unknown`. Optimal outcomes require literal
|
|
474
|
+
`optimalityProved: true`; unsatisfied outcomes require literal
|
|
475
|
+
`infeasibilityProved: true` and `coreMinimal: false`, even when no core was
|
|
476
|
+
returned. The portable explanation contract does not certify core minimality;
|
|
477
|
+
missing, true or coerced minimality markers are rejected. Recording, reading
|
|
478
|
+
imported artifacts, replay and
|
|
479
|
+
predecessor checks all enforce these markers before a result can support a
|
|
480
|
+
recommendation or decision. This checks the report contract; it does not
|
|
481
|
+
independently verify a backend's proof. Report inputs and diagnostics must be
|
|
482
|
+
arrays, as must feasible assignments and hard/soft constraints, optimal
|
|
483
|
+
objectives, and an unsatisfied core when supplied. Missing required collections
|
|
484
|
+
and non-array values reject at the same recording and read boundaries.
|
|
485
|
+
Unknown outcomes require a portable reason kind, string message and, when
|
|
486
|
+
present, a supported limit name. Recording and reading use the same
|
|
487
|
+
`Validate.unknownReason` check as solver-result handling, so replay cannot accept
|
|
488
|
+
a malformed unknown reason based only on matching model/backend identity.
|
|
489
|
+
Shared `Validate.resultMetadata` validation also requires finite non-negative
|
|
490
|
+
elapsed time and well-formed diagnostic entries, with supported severity and
|
|
491
|
+
string code/message fields. These checks apply before imported results can be
|
|
492
|
+
replayed or used as predecessors, as well as before initial recording.
|
|
493
|
+
Input and outcome entries must be objects with string IDs and the appropriate
|
|
494
|
+
string provenance arrays. Optional descriptions, queries and evaluation reasons
|
|
495
|
+
must be strings; declaration positions must be positive safe integers.
|
|
496
|
+
Assignments, objectives and core entries require Boolean `declared` flags.
|
|
497
|
+
Constraint evaluation states, objective directions and soft-weight field shapes
|
|
498
|
+
are checked before recording or replay. These structural checks do not resolve
|
|
499
|
+
referenced IDs or independently evaluate a solution. JSON-compatible malformed
|
|
500
|
+
raw backend assignment/objective values remain recordable for inspection and render with the
|
|
501
|
+
invalid-value marker.
|
|
502
|
+
Recorded run context also passes `Validate.explanationContext`, enforcing the
|
|
503
|
+
same snapshot policy, scenario identity and unique input-ID checks as generated
|
|
504
|
+
explanations. Limits must be an object with supported names and positive safe
|
|
505
|
+
integer values. Historical objects that omit newer limits retain exactly their
|
|
506
|
+
declared fields: validation does not insert current defaults into old evidence.
|
|
507
|
+
These checks apply to recording, imported reads, replay and predecessor use.
|
|
508
|
+
Indeterminate local constraint
|
|
509
|
+
evaluations remain recordable, including their optional `evaluationReason`.
|
|
510
|
+
This includes local `maxExplanationBits` exhaustion: replay retains the recorded
|
|
511
|
+
budget and indeterminate evaluations without running arithmetic again or
|
|
512
|
+
reclassifying the backend outcome.
|
|
513
|
+
A fresh solve with a larger explanation budget may evaluate constraints that
|
|
514
|
+
were previously indeterminate, without changing the model digest. Record that
|
|
515
|
+
new report under a new result ID; the earlier result remains immutable, including
|
|
516
|
+
after canonical export/import. Replay reads its recorded evaluation and does
|
|
517
|
+
not invoke the backend or apply the current caller's budget.
|
|
518
|
+
|
|
519
|
+
### Replay compatibility
|
|
520
|
+
|
|
143
521
|
`Record.replay` reads an immutable solver report without solving again and returns
|
|
144
522
|
explicit incompatibility reasons for a different model digest, backend, or
|
|
145
|
-
solver version.
|
|
523
|
+
solver version. Each replay captures the expected model digest, backend name
|
|
524
|
+
and version once, so compatibility checks and their diagnostics use the same
|
|
525
|
+
values even when supplied through getters. A later replay captures fresh
|
|
526
|
+
expectations; neither call changes recorded history.
|
|
527
|
+
Expected model digests and supplied backend names/versions must be nonempty
|
|
528
|
+
strings. Malformed expectation containers or identities throw `TypeError` before
|
|
529
|
+
store reads; valid but different identities return incompatibility reasons.
|
|
530
|
+
Omit `backend` to compare only the model digest.
|
|
531
|
+
External evaluations retain their exact evaluator identity and
|
|
146
532
|
input digest through `Record.read`; they are never mistaken for solver replay.
|
|
533
|
+
|
|
534
|
+
## Performance measurements
|
|
535
|
+
|
|
536
|
+
Run these benchmarks alone from the repository root. They check exact results
|
|
537
|
+
outside the timed calls. Measurements describe the audit machine and the named
|
|
538
|
+
runtime versions; they are not test thresholds or general resource guarantees.
|
|
539
|
+
|
|
540
|
+
### Scalar parsing
|
|
541
|
+
|
|
542
|
+
Run `node scripts/scenario-parse-normalization-bench.mjs` alone to measure
|
|
543
|
+
parsing at 1,000, 10,000 and 100,000 decimal places. Five-sample medians for
|
|
544
|
+
the 100,000-place fixture fell from 37.64 to 18.64 ms on Node 24.21.0 and
|
|
545
|
+
37.23 to 18.44 ms on Node 26.8.1 after removing duplicate normalization.
|
|
546
|
+
These are local measurements, not a general input-size or latency guarantee.
|
|
547
|
+
|
|
548
|
+
### Arithmetic and fraction reduction
|
|
549
|
+
|
|
550
|
+
Run `node scripts/scenario-exact-bench.mjs` from the repository root, alone,
|
|
551
|
+
to measure addition and comparison of near-one fractions at 1,000–64,000
|
|
552
|
+
decimal places. Three-sample medians at 64,000 places on the audit machine:
|
|
553
|
+
|
|
554
|
+
| Runtime | Addition before → after | Comparison before → after |
|
|
555
|
+
|---|---:|---:|
|
|
556
|
+
| Node 24.16.0 | 26.11 → 13.82 ms | 14.56 → 5.67 ms |
|
|
557
|
+
| Node 26.5.0 | 25.93 → 13.75 ms | 14.47 → 5.71 ms |
|
|
558
|
+
|
|
559
|
+
The same script also measures unequal-denominator comparisons at 10,000 and
|
|
560
|
+
100,000 digits: opposite signs, equal numerators and a general cross-product
|
|
561
|
+
control. These additional cases use two warmups and eleven measured calls,
|
|
562
|
+
reporting the median with runtime/platform and measurement-scope metadata.
|
|
563
|
+
Input construction and exact result assertions are outside timing; BigInt
|
|
564
|
+
parsing inside comparison is included. Run the script alone on each runtime;
|
|
565
|
+
these observations are not a CI threshold or an intermediate-size budget.
|
|
566
|
+
|
|
567
|
+
The before/after table covers common denominators. Unit-conversion products,
|
|
568
|
+
unequal-denominator addition and general intermediate growth remain separate
|
|
569
|
+
resource boundaries; timings are observations, not test thresholds.
|
|
570
|
+
|
|
571
|
+
Fraction reduction shares the solver's `Exact.fromBigInts` normalizer, using
|
|
572
|
+
its guarded Lehmer batches directly on bigint intermediates. This avoids both
|
|
573
|
+
a second GCD implementation and an intermediate decimal serialization step.
|
|
574
|
+
`node scripts/scenario-reduction-bench.mjs` measures adding zero and converting
|
|
575
|
+
one source unit using consecutive Fibonacci fractions at indices
|
|
576
|
+
5,000–100,000, with fixture construction outside timing and exact-result
|
|
577
|
+
checks afterward. Three-sample medians at index 100,000 (41,798 numerator plus
|
|
578
|
+
denominator digits), measured alone on the audit machine:
|
|
579
|
+
|
|
580
|
+
| Runtime | Add zero before → after | Conversion before → after |
|
|
581
|
+
|---|---:|---:|
|
|
582
|
+
| Node 24.16.0 | 1461.95 → 34.04 ms | 1492.44 → 70.80 ms |
|
|
583
|
+
| Node 26.5.0 | 1449.63 → 32.66 ms | 1483.08 → 66.02 ms |
|
|
584
|
+
|
|
585
|
+
The 64,000-place common-denominator addition benchmark still measured about
|
|
586
|
+
14 ms on both runtimes. Conversion includes normalizing the declared factor
|
|
587
|
+
and its product; general product growth and normalization work remain unbounded.
|
package/dist/src/bind.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Store } from '@cavelang/store';
|
|
2
2
|
import { type Definition, type InputRecord } from './model.ts';
|
|
3
|
+
/** JSON semantics omit undefined optional fields before stable key ordering. */
|
|
4
|
+
export declare const definitionDigest: (definition: Definition) => string;
|
|
5
|
+
export declare const inputDigest: (record: Omit<InputRecord, "digest">) => string;
|
|
3
6
|
export declare const bind: (store: Store, definition: Definition) => InputRecord;
|
|
4
7
|
/** Materializes and rolls back inputs before `evaluate` is invoked. */
|
|
5
8
|
export declare const run: <Result>(store: Store, definition: Definition, evaluate: (inputs: InputRecord) => Result | Promise<Result>) => Promise<Result>;
|
package/dist/src/bind.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../../src/bind.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../../src/bind.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAG5C,OAAO,EAKL,KAAK,UAAU,EAGf,KAAK,WAAW,EAIjB,MAAM,YAAY,CAAA;AAuCnB,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,GAAI,YAAY,UAAU,KAAG,MACe,CAAA;AAEzE,eAAO,MAAM,WAAW,GAAI,QAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,KAAG,MACG,CAAA;AAqZrE,eAAO,MAAM,IAAI,GAAI,OAAO,KAAK,EAAE,YAAY,UAAU,KAAG,WAyE3D,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,GAAG,GAAU,MAAM,EAC9B,OAAO,KAAK,EACZ,YAAY,UAAU,EACtB,UAAU,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAC1D,OAAO,CAAC,MAAM,CAAsC,CAAA"}
|