@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.
- package/Authors.md +5 -0
- package/License.md +6 -0
- package/README.md +146 -0
- package/dist/src/bind.d.ts +6 -0
- package/dist/src/bind.d.ts.map +1 -0
- package/dist/src/bind.js +347 -0
- package/dist/src/bind.js.map +1 -0
- package/dist/src/error.d.ts +7 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +11 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/exact.d.ts +13 -0
- package/dist/src/exact.d.ts.map +1 -0
- package/dist/src/exact.js +65 -0
- package/dist/src/exact.js.map +1 -0
- package/dist/src/explain.d.ts +5 -0
- package/dist/src/explain.d.ts.map +1 -0
- package/dist/src/explain.js +59 -0
- package/dist/src/explain.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/model.d.ts +141 -0
- package/dist/src/model.d.ts.map +1 -0
- package/dist/src/model.js +2 -0
- package/dist/src/model.js.map +1 -0
- package/dist/src/record.d.ts +100 -0
- package/dist/src/record.d.ts.map +1 -0
- package/dist/src/record.js +189 -0
- package/dist/src/record.js.map +1 -0
- package/package.json +61 -0
- package/src/bind.ts +401 -0
- package/src/error.ts +22 -0
- package/src/exact.ts +93 -0
- package/src/explain.ts +68 -0
- package/src/index.ts +7 -0
- package/src/model.ts +120 -0
- package/src/record.ts +299 -0
|
@@ -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
|
+
}
|