@cavelang/scenario 0.29.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/record.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@cavelang/scenario",
3
+ "version": "0.29.0",
4
+ "type": "module",
5
+ "description": "Typed, replayable CAVE snapshot and ephemeral-overlay bindings for decision and solver inputs.",
6
+ "keywords": [
7
+ "cave",
8
+ "knowledge-graph",
9
+ "knowledge-representation",
10
+ "typescript"
11
+ ],
12
+ "homepage": "https://github.com/mirek/cave#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/mirek/cave/issues"
15
+ },
16
+ "license": "CC0-1.0",
17
+ "author": "Mirek Rusin (https://github.com/mirek)",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/mirek/cave.git",
21
+ "directory": "packages/scenario"
22
+ },
23
+ "engines": {
24
+ "node": ">=22.18"
25
+ },
26
+ "exports": {
27
+ ".": "./src/index.ts"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "!dist/test",
32
+ "!dist/tsconfig.tsbuildinfo",
33
+ "src",
34
+ "!src/**/*.test.ts",
35
+ "README.md",
36
+ "License.md",
37
+ "Authors.md"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/src/index.d.ts",
44
+ "default": "./dist/src/index.js"
45
+ }
46
+ }
47
+ },
48
+ "scripts": {
49
+ "build": "tsc -b",
50
+ "test": "node --disable-warning=ExperimentalWarning --test \"test/*.test.ts\"",
51
+ "typecheck": "tsc -b",
52
+ "prepack": "node ../../scripts/prepare-package-legal.mjs && pnpm run build"
53
+ },
54
+ "dependencies": {
55
+ "@cavelang/canonical": "workspace:*",
56
+ "@cavelang/core": "workspace:*",
57
+ "@cavelang/query": "workspace:*",
58
+ "@cavelang/solver": "workspace:*",
59
+ "@cavelang/store": "workspace:*"
60
+ }
61
+ }
package/src/bind.ts ADDED
@@ -0,0 +1,401 @@
1
+ import { createHash } from 'node:crypto'
2
+ import * as Canonical from '@cavelang/canonical'
3
+ import { Uuidv7 } from '@cavelang/core'
4
+ import { Pattern, query, type Match, type Options as QueryOptions } from '@cavelang/query'
5
+ import { Exact } from '@cavelang/solver'
6
+ import type { Store } from '@cavelang/store'
7
+ import { ScenarioInputError } from './error.ts'
8
+ import * as Numeric from './exact.ts'
9
+ import {
10
+ schema,
11
+ type Binding,
12
+ type BindingResult,
13
+ type Candidate,
14
+ type Definition,
15
+ type Evidence,
16
+ type Expected,
17
+ type InputRecord,
18
+ type Snapshot,
19
+ type Uncertainty,
20
+ type Value
21
+ } from './model.ts'
22
+
23
+ type Json = null | boolean | number | string | readonly Json[] | { readonly [key: string]: Json }
24
+
25
+ type RawCandidate = {
26
+ readonly match: Match
27
+ readonly origin: 'belief' | 'scenario'
28
+ readonly contested: boolean
29
+ readonly retracted: boolean
30
+ readonly unresolved: boolean
31
+ readonly scenarioClaimId?: string
32
+ }
33
+
34
+ const compareText = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0
35
+
36
+ const stableStringify = (value: Json): string => {
37
+ if (value === null || typeof value !== 'object') return JSON.stringify(value)
38
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`
39
+ const record = value as Readonly<Record<string, Json>>
40
+ return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${stableStringify(record[key]!)}`).join(',')}}`
41
+ }
42
+
43
+ const sha256 = (text: string): string =>
44
+ `sha256:${createHash('sha256').update(text).digest('hex')}`
45
+
46
+ const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/
47
+
48
+ const invalid = (message: string, bindingId?: string): never => {
49
+ throw new ScenarioInputError('invalid-definition', message, bindingId)
50
+ }
51
+
52
+ const validateBinding = (binding: Binding): void => {
53
+ if (!identifierPattern.test(binding.id)) invalid('identifier must use letters, numbers, dot, underscore, slash, or dash', binding.id)
54
+ if (binding.expected.kind !== 'boolean' && binding.select === undefined) {
55
+ invalid('select is required for non-Boolean inputs', binding.id)
56
+ }
57
+ if (binding.cardinality === 'one' && binding.policies.missing !== 'reject') {
58
+ invalid('cardinality one requires missing: reject', binding.id)
59
+ }
60
+ if (binding.cardinality === 'optional' && binding.policies.missing === 'empty') {
61
+ invalid('cardinality optional supports missing: reject or omit', binding.id)
62
+ }
63
+ if (binding.cardinality === 'many' && binding.policies.missing === 'omit') {
64
+ invalid('cardinality many supports missing: reject or empty', binding.id)
65
+ }
66
+ if (binding.cardinality === 'many' && binding.reduce !== 'all' &&
67
+ binding.expected.kind !== 'number' && binding.expected.kind !== 'integer') {
68
+ invalid(`reduction ${binding.reduce} requires a numeric input`, binding.id)
69
+ }
70
+ if (binding.expected.kind === 'enum') {
71
+ if (binding.expected.values.length === 0 || new Set(binding.expected.values).size !== binding.expected.values.length) {
72
+ invalid('enum values must be non-empty and unique', binding.id)
73
+ }
74
+ }
75
+ if (binding.expected.kind === 'number' || binding.expected.kind === 'integer') {
76
+ for (const conversion of binding.expected.conversions ?? []) {
77
+ if (conversion.from === conversion.to) invalid('unit conversion must change the unit', binding.id)
78
+ if (Exact.compare(conversion.factor, '0') <= 0) invalid('unit conversion factor must be positive', binding.id)
79
+ }
80
+ }
81
+ const pattern: Pattern.t = (() => {
82
+ try {
83
+ return Pattern.parse(binding.query)
84
+ } catch (error) {
85
+ return invalid(`invalid CAVE-Q pattern: ${error instanceof Error ? error.message : String(error)}`, binding.id)
86
+ }
87
+ })()
88
+ if (pattern.verb.kind === 'verb' && pattern.verb.transitive) {
89
+ invalid('transitive bindings are deferred until snapshot-aware shared query primitives are available', binding.id)
90
+ }
91
+ }
92
+
93
+ const validateDefinition = (definition: Definition): void => {
94
+ if (!identifierPattern.test(definition.id)) invalid('scenario identifier must use letters, numbers, dot, underscore, slash, or dash')
95
+ if (!/^sha256:[0-9a-f]{64}$/.test(definition.modelDigest)) invalid('modelDigest must be a lowercase SHA-256 digest')
96
+ if (!Number.isFinite(definition.snapshot.minimumConfidence) ||
97
+ definition.snapshot.minimumConfidence < 0 || definition.snapshot.minimumConfidence > 1) {
98
+ invalid('minimumConfidence must be between 0 and 1')
99
+ }
100
+ const ids = new Set<string>()
101
+ for (const binding of definition.bindings) {
102
+ if (ids.has(binding.id)) invalid(`duplicate binding identifier ${JSON.stringify(binding.id)}`)
103
+ ids.add(binding.id)
104
+ validateBinding(binding)
105
+ }
106
+ }
107
+
108
+ const boundaryUpper = (text: string): string => {
109
+ const start = Date.parse(text.includes('T') ? text : `${text}T00:00:00Z`)
110
+ if (Number.isNaN(start)) throw new ScenarioInputError('invalid-definition', `cannot parse as-of boundary ${JSON.stringify(text)}`)
111
+ return Uuidv7.at(start + (text.includes('T') ? 1_000 : 86_400_000), 0, new Uint8Array(8))
112
+ }
113
+
114
+ const transactionTime = (store: Store, asOf: string | undefined): string | null => {
115
+ if (asOf === undefined) {
116
+ return (store.db.prepare('SELECT MAX(tx) AS tx FROM cave_claim').get() as { tx: string | null }).tx
117
+ }
118
+ const id = asOf.toLowerCase()
119
+ return Uuidv7.is(id) ?
120
+ (store.db.prepare('SELECT MAX(tx) AS tx FROM cave_claim WHERE tx <= ?').get(id) as { tx: string | null }).tx :
121
+ (store.db.prepare('SELECT MAX(tx) AS tx FROM cave_claim WHERE tx < ?').get(boundaryUpper(asOf)) as { tx: string | null }).tx
122
+ }
123
+
124
+ const queryOptions = (snapshot: Snapshot, resolve: boolean): QueryOptions => ({
125
+ ...(snapshot.asOf === undefined ? {} : { asOf: snapshot.asOf }),
126
+ ...(snapshot.at === undefined ? {} : { at: snapshot.at }),
127
+ aliases: snapshot.aliases === 'closure',
128
+ resolve,
129
+ support: true
130
+ })
131
+
132
+ /** Explicit confidence filter opts CAVE-Q into returning current `@ 0%` rows. */
133
+ const includingRetractions = (input: string): string => `${input}\nWHERE conf >= 0`
134
+
135
+ const supportIds = (match: Match): readonly string[] =>
136
+ match.row === undefined ? (match.rows ?? []).map(row => row.id) : [match.row.id]
137
+
138
+ const rawBaseCandidates = (store: Store, binding: Binding, snapshot: Snapshot): RawCandidate[] => {
139
+ const pattern = includingRetractions(binding.query)
140
+ const unresolved = query(store, pattern, queryOptions(snapshot, false))
141
+ const winners = query(store, pattern, queryOptions(snapshot, true))
142
+ const winnerIds = new Set(winners.flatMap(supportIds))
143
+ const selected = snapshot.resolution === 'winner' ? winners : unresolved
144
+ return selected.map(match => {
145
+ const ids = supportIds(match)
146
+ return {
147
+ match,
148
+ origin: 'belief',
149
+ contested: snapshot.resolution === 'coexisting' && ids.some(id => !winnerIds.has(id)),
150
+ retracted: match.row?.conf === 0,
151
+ unresolved: ids.length === 0
152
+ }
153
+ })
154
+ }
155
+
156
+ const overlayResult = (
157
+ store: Store,
158
+ definition: Definition,
159
+ canonical: Canonical.Result,
160
+ stableIds: readonly string[]
161
+ ): Readonly<Record<string, readonly RawCandidate[]>> => {
162
+ if (canonical.claims.length === 0) return {}
163
+ const rolledBack = Symbol('cave-scenario-overlay')
164
+ type Rollback = Error & { readonly [rolledBack]: Readonly<Record<string, readonly RawCandidate[]>> }
165
+ try {
166
+ return store.transaction(() => {
167
+ const inserted = store.insertResult(canonical, {
168
+ source: `scenario/${definition.id}`,
169
+ lifecycle: true
170
+ })
171
+ const stableByRow = new Map(inserted.ids.map((id, index) => [id, stableIds[index]!]))
172
+ const result: Record<string, readonly RawCandidate[]> = {}
173
+ for (const binding of definition.bindings) {
174
+ // Overlay claims are matched as authored. Base aliases and historical
175
+ // rows cannot make a hypothetical claim appear under a different name.
176
+ const matches = query(store, includingRetractions(binding.query), {
177
+ ...(definition.snapshot.at === undefined ? {} : { at: definition.snapshot.at }),
178
+ all: true,
179
+ aliases: false,
180
+ support: true
181
+ }).filter(match => match.row !== undefined && stableByRow.has(match.row.id))
182
+ result[binding.id] = matches.map(match => ({
183
+ match,
184
+ origin: 'scenario',
185
+ contested: false,
186
+ retracted: match.row?.conf === 0,
187
+ unresolved: false,
188
+ scenarioClaimId: stableByRow.get(match.row!.id)!
189
+ }))
190
+ }
191
+ throw Object.assign(new Error('scenario overlay materialized'), { [rolledBack]: result })
192
+ })
193
+ } catch (error) {
194
+ if (error instanceof Error && rolledBack in error) return (error as Rollback)[rolledBack]
195
+ throw error
196
+ }
197
+ }
198
+
199
+ const selectedText = (candidate: RawCandidate, binding: Binding): string | undefined => {
200
+ if (binding.select !== undefined) return candidate.match.bindings[binding.select]
201
+ if (binding.expected.kind === 'boolean') return undefined
202
+ return candidate.match.at?.text ?? candidate.match.row?.value_text ?? undefined
203
+ }
204
+
205
+ const plain = (text: string): string =>
206
+ text.length >= 2 && ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith('`') && text.endsWith('`'))) ?
207
+ text.slice(1, -1) : text
208
+
209
+ const uncertainty = (candidate: RawCandidate, expected: Extract<Expected, { kind: 'number' | 'integer' }>, bindingId: string): Uncertainty | undefined => {
210
+ const row = candidate.match.row
211
+ if (row?.delta_text === null || row?.delta_text === undefined) return undefined
212
+ const parsed = Numeric.convert(Numeric.parse(row.delta_text, bindingId), expected.unit, expected.conversions, bindingId)
213
+ return {
214
+ authored: row.delta_text,
215
+ exact: parsed.exact,
216
+ ...(parsed.unit === undefined ? {} : { unit: parsed.unit }),
217
+ sigmaLevel: row.sigma_level ?? 2
218
+ }
219
+ }
220
+
221
+ const valueOf = (candidate: RawCandidate, binding: Binding): Value => {
222
+ const expected = binding.expected
223
+ const text = selectedText(candidate, binding)
224
+ switch (expected.kind) {
225
+ case 'boolean': {
226
+ if (binding.select === undefined) return { kind: 'boolean', value: true }
227
+ if (text === undefined) throw new ScenarioInputError('invalid-value', `query did not bind ?${binding.select}`, binding.id)
228
+ const value = plain(text)
229
+ const yes = expected.trueValue ?? 'true'
230
+ const no = expected.falseValue ?? 'false'
231
+ if (value !== yes && value !== no) {
232
+ throw new ScenarioInputError('invalid-value', `expected ${JSON.stringify(yes)} or ${JSON.stringify(no)}, received ${JSON.stringify(value)}`, binding.id)
233
+ }
234
+ return { kind: 'boolean', value: value === yes }
235
+ }
236
+ case 'enum': {
237
+ if (text === undefined) throw new ScenarioInputError('invalid-value', `query did not bind ?${binding.select}`, binding.id)
238
+ const value = plain(text)
239
+ if (!expected.values.includes(value)) {
240
+ throw new ScenarioInputError('invalid-value', `value ${JSON.stringify(value)} is outside [${expected.values.join(', ')}]`, binding.id)
241
+ }
242
+ return { kind: 'enum', value }
243
+ }
244
+ case 'text':
245
+ if (text === undefined) throw new ScenarioInputError('invalid-value', `query did not bind ?${binding.select}`, binding.id)
246
+ return { kind: 'text', value: plain(text) }
247
+ case 'integer':
248
+ case 'number': {
249
+ const authored = candidate.match.at?.text ?? candidate.match.row?.value_text ?? text
250
+ if (authored === undefined) throw new ScenarioInputError('invalid-value', 'matched row has no numeric value', binding.id)
251
+ const parsed = Numeric.convert(Numeric.parse(authored, binding.id), expected.unit, expected.conversions, binding.id)
252
+ const uncertainty_ = uncertainty(candidate, expected, binding.id)
253
+ const common = {
254
+ ...(parsed.unit === undefined ? {} : { unit: parsed.unit }),
255
+ authored,
256
+ approximate: parsed.approximate,
257
+ ...(uncertainty_ === undefined ? {} : { uncertainty: uncertainty_ })
258
+ }
259
+ if (expected.kind === 'integer') {
260
+ if (parsed.exact.denominator !== '1') {
261
+ throw new ScenarioInputError('invalid-value', `expected an integer, received ${JSON.stringify(authored)}`, binding.id)
262
+ }
263
+ return { kind: 'integer', value: parsed.exact.numerator, ...common }
264
+ }
265
+ return { kind: 'number', value: parsed.exact, ...common }
266
+ }
267
+ }
268
+ }
269
+
270
+ const evidenceOf = (candidate: RawCandidate): readonly Evidence[] => candidate.origin === 'scenario' ?
271
+ [{ origin: 'scenario', claimIds: [candidate.scenarioClaimId!] }] :
272
+ [{ origin: 'belief', rowIds: supportIds(candidate.match) }]
273
+
274
+ const publicCandidate = (candidate: RawCandidate, binding: Binding): Candidate => ({
275
+ value: valueOf(candidate, binding),
276
+ confidence: candidate.match.row?.conf ?? 1,
277
+ evidence: evidenceOf(candidate)
278
+ })
279
+
280
+ const evidenceKey = (candidate: RawCandidate): string => candidate.origin === 'scenario' ?
281
+ `scenario:${candidate.scenarioClaimId}` : `belief:${supportIds(candidate.match).join(',')}`
282
+
283
+ const reduce = (values: readonly Value[], binding: Binding): Value | readonly Value[] => {
284
+ if (binding.cardinality !== 'many' || binding.reduce === 'all') return values
285
+ const numeric = values as readonly Extract<Value, { kind: 'number' | 'integer' }>[]
286
+ if (binding.reduce === 'sum') {
287
+ const exact = numeric.reduce(
288
+ (total, value) => Numeric.add(total, value.kind === 'integer' ? { numerator: value.value, denominator: '1' } : value.value),
289
+ { numerator: '0', denominator: '1' }
290
+ )
291
+ const unit = numeric[0]?.unit
292
+ const approximate = numeric.some(value => value.approximate)
293
+ return binding.expected.kind === 'integer' ?
294
+ { kind: 'integer', value: exact.numerator, ...(unit === undefined ? {} : { unit }), approximate } :
295
+ { kind: 'number', value: exact, ...(unit === undefined ? {} : { unit }), approximate }
296
+ }
297
+ let selected = numeric[0]!
298
+ for (const value of numeric.slice(1)) {
299
+ const left = selected.kind === 'integer' ? { numerator: selected.value, denominator: '1' } : selected.value
300
+ const right = value.kind === 'integer' ? { numerator: value.value, denominator: '1' } : value.value
301
+ const comparison = Numeric.compare(right, left)
302
+ if ((binding.reduce === 'min' && comparison < 0) || (binding.reduce === 'max' && comparison > 0)) selected = value
303
+ }
304
+ return selected
305
+ }
306
+
307
+ const bindOne = (
308
+ binding: Binding,
309
+ base: readonly RawCandidate[],
310
+ overlay: readonly RawCandidate[],
311
+ minimumConfidence: number
312
+ ): BindingResult => {
313
+ const eligible = (values: readonly RawCandidate[]): RawCandidate[] => values
314
+ .filter(candidate => candidate.retracted && binding.policies.retracted === 'reject' ||
315
+ candidate.match.row === undefined || candidate.match.row.conf >= minimumConfidence)
316
+ .filter(candidate => binding.policies.retracted !== 'exclude' || !candidate.retracted)
317
+ const baseEligible = eligible(base)
318
+ const overlayEligible = eligible(overlay)
319
+ const selected = (binding.scenarioOverride && overlayEligible.length > 0 ? overlayEligible : [...baseEligible, ...overlayEligible])
320
+ .sort((left, right) => compareText(evidenceKey(left), evidenceKey(right)))
321
+
322
+ const rejected = selected.find(candidate => candidate.retracted && binding.policies.retracted === 'reject')
323
+ if (rejected !== undefined) throw new ScenarioInputError('retracted-input', 'matched a retracted belief', binding.id)
324
+ if (binding.policies.contested === 'reject' && selected.some(candidate => candidate.contested)) {
325
+ throw new ScenarioInputError('contested-input', 'matched coexisting beliefs that do not win resolution', binding.id)
326
+ }
327
+ if (binding.policies.unresolved === 'reject' && selected.some(candidate => candidate.unresolved)) {
328
+ throw new ScenarioInputError('unresolved-input', 'query result has no supporting claim row', binding.id)
329
+ }
330
+
331
+ const countOk = binding.cardinality === 'many' || selected.length <= 1
332
+ if (!countOk) throw new ScenarioInputError('ambiguous-input', `expected ${binding.cardinality}, matched ${selected.length} values`, binding.id)
333
+ if (selected.length === 0 && binding.policies.missing === 'reject') {
334
+ throw new ScenarioInputError('missing-input', 'query matched no eligible value', binding.id)
335
+ }
336
+
337
+ const candidates = selected.map(candidate => publicCandidate(candidate, binding))
338
+ if (selected.length === 0 && binding.policies.missing === 'omit') return { id: binding.id, candidates }
339
+ if (selected.length === 0) return { id: binding.id, candidates, value: [] }
340
+ const values = candidates.map(candidate => candidate.value)
341
+ if (binding.cardinality === 'many') return { id: binding.id, candidates, value: reduce(values, binding) }
342
+ return { id: binding.id, candidates, ...(values[0] === undefined ? {} : { value: values[0] }) }
343
+ }
344
+
345
+ export const bind = (store: Store, definition: Definition): InputRecord => {
346
+ validateDefinition(definition)
347
+
348
+ const frozenTx = transactionTime(store, definition.snapshot.asOf)
349
+ const effectiveSnapshot: Snapshot = definition.snapshot.asOf === undefined && frozenTx !== null ?
350
+ { ...definition.snapshot, asOf: frozenTx } : definition.snapshot
351
+ const registry = effectiveSnapshot.asOf === undefined ? store.registry() : store.registryAsOf(effectiveSnapshot.asOf)
352
+ const canonical = Canonical.canonicalizeText(definition.overlay ?? '', registry)
353
+ if (canonical.problems.length > 0) {
354
+ const details = canonical.problems.map(problem => `line ${problem.line}: ${problem.message}`).join('; ')
355
+ throw new ScenarioInputError('invalid-overlay', `overlay has ${canonical.problems.length} problem(s): ${details}`)
356
+ }
357
+ const overlayText = Canonical.emit(canonical)
358
+ const overlayDigest = sha256(overlayText)
359
+ const claimIds = canonical.claims.map((_, index) =>
360
+ `scenario:${definition.id}:${overlayDigest.slice(7)}#${index + 1}`)
361
+
362
+ const base = Object.fromEntries(definition.bindings.map(binding => [
363
+ binding.id,
364
+ rawBaseCandidates(store, binding, effectiveSnapshot)
365
+ ])) as Readonly<Record<string, readonly RawCandidate[]>>
366
+ const overlay = overlayResult(store, { ...definition, snapshot: effectiveSnapshot }, canonical, claimIds)
367
+ const bindings = definition.bindings.map(binding => bindOne(
368
+ binding,
369
+ base[binding.id] ?? [],
370
+ overlay[binding.id] ?? [],
371
+ definition.snapshot.minimumConfidence
372
+ ))
373
+ const values: Record<string, Value | readonly Value[]> = {}
374
+ for (const binding of bindings) if (binding.value !== undefined) values[binding.id] = binding.value
375
+
376
+ const supportingRowIds = [...new Set(bindings.flatMap(binding => binding.candidates.flatMap(candidate =>
377
+ candidate.evidence.flatMap(evidence => evidence.origin === 'belief' ? evidence.rowIds : []))))].sort(compareText)
378
+ const scenarioClaimIds = [...new Set(bindings.flatMap(binding => binding.candidates.flatMap(candidate =>
379
+ candidate.evidence.flatMap(evidence => evidence.origin === 'scenario' ? evidence.claimIds : []))))].sort(compareText)
380
+
381
+ const withoutDigest = {
382
+ schema,
383
+ scenarioId: definition.id,
384
+ modelDigest: definition.modelDigest,
385
+ snapshot: { ...definition.snapshot, transactionTime: frozenTx },
386
+ overlay: { digest: overlayDigest, source: `scenario/${definition.id}`, claimIds },
387
+ values,
388
+ bindings,
389
+ supportingRowIds,
390
+ scenarioClaimIds
391
+ }
392
+ const digest = sha256(stableStringify(withoutDigest as unknown as Json))
393
+ return { ...withoutDigest, digest }
394
+ }
395
+
396
+ /** Materializes and rolls back inputs before `evaluate` is invoked. */
397
+ export const run = async <Result>(
398
+ store: Store,
399
+ definition: Definition,
400
+ evaluate: (inputs: InputRecord) => Result | Promise<Result>
401
+ ): Promise<Result> => evaluate(bind(store, definition))
package/src/error.ts ADDED
@@ -0,0 +1,22 @@
1
+ export type Code =
2
+ | 'invalid-definition'
3
+ | 'invalid-overlay'
4
+ | 'missing-input'
5
+ | 'ambiguous-input'
6
+ | 'contested-input'
7
+ | 'retracted-input'
8
+ | 'unresolved-input'
9
+ | 'invalid-value'
10
+ | 'incompatible-unit'
11
+
12
+ export class ScenarioInputError extends Error {
13
+ readonly code: Code
14
+ readonly bindingId?: string
15
+
16
+ constructor(code: Code, message: string, bindingId?: string) {
17
+ super(bindingId === undefined ? message : `scenario input ${JSON.stringify(bindingId)}: ${message}`)
18
+ this.name = 'ScenarioInputError'
19
+ this.code = code
20
+ this.bindingId = bindingId
21
+ }
22
+ }
package/src/exact.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { Exact, type Model } from '@cavelang/solver'
2
+ import type { Conversion, ExactNumber } from './model.ts'
3
+ import { ScenarioInputError } from './error.ts'
4
+
5
+ const multiplier: Readonly<Record<string, bigint>> = {
6
+ K: 1_000n,
7
+ M: 1_000_000n,
8
+ B: 1_000_000_000n,
9
+ T: 1_000_000_000_000n
10
+ }
11
+
12
+ const hasMultiplier = (value: string): boolean => Object.hasOwn(multiplier, value)
13
+
14
+ type Parsed = {
15
+ readonly exact: ExactNumber
16
+ readonly unit?: string
17
+ readonly approximate: boolean
18
+ }
19
+
20
+ const reduce = (numerator: bigint, denominator: bigint): ExactNumber => {
21
+ const divisor = (() => {
22
+ let left = numerator < 0n ? -numerator : numerator
23
+ let right = denominator < 0n ? -denominator : denominator
24
+ while (right !== 0n) [left, right] = [right, left % right]
25
+ return left
26
+ })()
27
+ const sign = denominator < 0n ? -1n : 1n
28
+ return {
29
+ numerator: String((numerator / divisor) * sign),
30
+ denominator: String((denominator / divisor) * sign)
31
+ }
32
+ }
33
+
34
+ const multiply = (left: ExactNumber, right: ExactNumber): ExactNumber =>
35
+ reduce(BigInt(left.numerator) * BigInt(right.numerator), BigInt(left.denominator) * BigInt(right.denominator))
36
+
37
+ export const add = (left: ExactNumber, right: ExactNumber): ExactNumber =>
38
+ reduce(
39
+ BigInt(left.numerator) * BigInt(right.denominator) + BigInt(right.numerator) * BigInt(left.denominator),
40
+ BigInt(left.denominator) * BigInt(right.denominator)
41
+ )
42
+
43
+ export const compare = (left: ExactNumber, right: ExactNumber): number =>
44
+ BigInt(left.numerator) * BigInt(right.denominator) < BigInt(right.numerator) * BigInt(left.denominator) ? -1 :
45
+ BigInt(left.numerator) * BigInt(right.denominator) > BigInt(right.numerator) * BigInt(left.denominator) ? 1 : 0
46
+
47
+ const rational = (value: Model.Rational): ExactNumber => Exact.rational(value)
48
+
49
+ /** Parses CAVE's exact scalar spelling without passing the digits through `number`. */
50
+ export const parse = (authored: string, bindingId: string): Parsed => {
51
+ const approximate = authored.startsWith('~')
52
+ const body = approximate ? authored.slice(1) : authored
53
+ const [head = '', ...tailParts] = body.trim().split(/\s+/)
54
+ const tail = tailParts.length === 0 ? undefined : tailParts.join(' ')
55
+ const match = /^([+-]?\d+(?:\.\d+)?)([A-Za-z%]*)$/.exec(head)
56
+ if (match === null || tailParts.length > 1) {
57
+ throw new ScenarioInputError('invalid-value', `expected a scalar number, received ${JSON.stringify(authored)}`, bindingId)
58
+ }
59
+ const digits = match[1]!
60
+ const suffix = match[2] ?? ''
61
+ const factor = hasMultiplier(suffix) ? multiplier[suffix]! : 1n
62
+ const unit = hasMultiplier(suffix) ? tail : suffix === '' ? tail : suffix
63
+ if (suffix !== '' && !hasMultiplier(suffix) && tail !== undefined) {
64
+ throw new ScenarioInputError('invalid-value', `cannot combine glued unit ${JSON.stringify(suffix)} with ${JSON.stringify(tail)}`, bindingId)
65
+ }
66
+ const base = rational(digits)
67
+ return {
68
+ exact: reduce(BigInt(base.numerator) * factor, BigInt(base.denominator)),
69
+ ...(unit === undefined ? {} : { unit }),
70
+ approximate
71
+ }
72
+ }
73
+
74
+ export const convert = (
75
+ parsed: Parsed,
76
+ expectedUnit: string | undefined,
77
+ conversions: readonly Conversion[] | undefined,
78
+ bindingId: string
79
+ ): Parsed => {
80
+ if (expectedUnit === undefined || parsed.unit === expectedUnit) return parsed
81
+ if (parsed.unit === undefined) {
82
+ throw new ScenarioInputError('incompatible-unit', `expected unit ${JSON.stringify(expectedUnit)}, received a unitless value`, bindingId)
83
+ }
84
+ const conversion = conversions?.find(item => item.from === parsed.unit && item.to === expectedUnit)
85
+ if (conversion === undefined) {
86
+ throw new ScenarioInputError(
87
+ 'incompatible-unit',
88
+ `cannot convert ${JSON.stringify(parsed.unit)} to ${JSON.stringify(expectedUnit)} without an explicit conversion`,
89
+ bindingId
90
+ )
91
+ }
92
+ return { ...parsed, exact: multiply(parsed.exact, rational(conversion.factor)), unit: expectedUnit }
93
+ }
package/src/explain.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { Explain } from '@cavelang/solver'
2
+ import type { Binding, Definition, InputRecord, Value } from './model.ts'
3
+
4
+ const compareText = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0
5
+
6
+ const unique = (values: readonly string[]): readonly string[] => [...new Set(values)].sort(compareText)
7
+
8
+ const authored = (value: Value): Explain.Json => {
9
+ switch (value.kind) {
10
+ case 'boolean': return value.value
11
+ case 'integer': return value.authored ?? value.value
12
+ case 'number': return value.authored ?? `${value.value.numerator}/${value.value.denominator}`
13
+ case 'enum': return value.value
14
+ case 'text': return value.value
15
+ }
16
+ }
17
+
18
+ const authoredValue = (value: Value | readonly Value[] | undefined): Explain.Json | undefined =>
19
+ value === undefined ? undefined : Array.isArray(value) ? value.map(authored) : authored(value as Value)
20
+
21
+ const bindingInput = (
22
+ binding: Binding,
23
+ result: InputRecord['bindings'][number]
24
+ ): Explain.Input => {
25
+ const evidence = result.candidates.flatMap(candidate => candidate.evidence)
26
+ const value = result.value
27
+ const authoredInput = authoredValue(value)
28
+ return {
29
+ id: result.id,
30
+ query: binding.query,
31
+ ...(value === undefined ? {} : { value: value as Explain.Json }),
32
+ ...(authoredInput === undefined ? {} : { authoredValue: authoredInput }),
33
+ evidenceRowIds: unique(evidence.flatMap(item => item.origin === 'belief' ? item.rowIds : [])),
34
+ scenarioClaimIds: unique(evidence.flatMap(item => item.origin === 'scenario' ? item.claimIds : []))
35
+ }
36
+ }
37
+
38
+ /** Convert a frozen scenario binding into solver-neutral run provenance. */
39
+ export const explanationContext = (definition: Definition, record: InputRecord): Explain.Context => {
40
+ if (record.scenarioId !== definition.id) {
41
+ throw new TypeError(`scenario record ${JSON.stringify(record.scenarioId)} does not match ${JSON.stringify(definition.id)}`)
42
+ }
43
+ if (record.modelDigest !== definition.modelDigest) {
44
+ throw new TypeError(`scenario record model digest does not match its definition`)
45
+ }
46
+ const definitions = new Map(definition.bindings.map(binding => [binding.id, binding]))
47
+ const inputs = record.bindings.map(result => {
48
+ const binding = definitions.get(result.id)
49
+ if (binding === undefined) throw new TypeError(`scenario record contains unknown binding ${JSON.stringify(result.id)}`)
50
+ return bindingInput(binding, result)
51
+ })
52
+ return {
53
+ modelDigest: record.modelDigest,
54
+ scenario: {
55
+ id: record.scenarioId,
56
+ inputDigest: record.digest,
57
+ overlayDigest: record.overlay.digest
58
+ },
59
+ snapshot: {
60
+ transactionTime: record.snapshot.transactionTime,
61
+ ...(record.snapshot.at === undefined ? {} : { validTime: record.snapshot.at }),
62
+ aliases: record.snapshot.aliases,
63
+ resolution: record.snapshot.resolution,
64
+ minimumConfidence: record.snapshot.minimumConfidence
65
+ },
66
+ inputs
67
+ }
68
+ }