@cruba/crubal 0.1.0 → 0.3.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/README.md +30 -2
- package/dist/bin/crubal.js +70 -4
- package/dist/bin/crubal.js.map +1 -1
- package/dist/src/access.d.ts +126 -0
- package/dist/src/access.js +517 -0
- package/dist/src/access.js.map +1 -0
- package/dist/src/address.d.ts +29 -0
- package/dist/src/address.js +59 -0
- package/dist/src/address.js.map +1 -0
- package/dist/src/arrival.d.ts +55 -0
- package/dist/src/arrival.js +124 -0
- package/dist/src/arrival.js.map +1 -0
- package/dist/src/bundle.d.ts +66 -0
- package/dist/src/bundle.js +753 -0
- package/dist/src/bundle.js.map +1 -0
- package/dist/src/conditions.d.ts +7 -1
- package/dist/src/conditions.js +52 -11
- package/dist/src/conditions.js.map +1 -1
- package/dist/src/corpus.js +568 -34
- package/dist/src/corpus.js.map +1 -1
- package/dist/src/defs.d.ts +37 -2
- package/dist/src/defs.js +95 -14
- package/dist/src/defs.js.map +1 -1
- package/dist/src/dictionary.d.ts +15 -0
- package/dist/src/dictionary.js +72 -13
- package/dist/src/dictionary.js.map +1 -1
- package/dist/src/exchange.d.ts +204 -11
- package/dist/src/exchange.js +762 -45
- package/dist/src/exchange.js.map +1 -1
- package/dist/src/forms.d.ts +16 -0
- package/dist/src/forms.js +169 -0
- package/dist/src/forms.js.map +1 -0
- package/dist/src/index.d.ts +19 -6
- package/dist/src/index.js +18 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/lifecycle.d.ts +84 -0
- package/dist/src/lifecycle.js +415 -0
- package/dist/src/lifecycle.js.map +1 -0
- package/dist/src/log.d.ts +78 -0
- package/dist/src/log.js +506 -0
- package/dist/src/log.js.map +1 -0
- package/dist/src/migrate.d.ts +24 -1
- package/dist/src/migrate.js +104 -9
- package/dist/src/migrate.js.map +1 -1
- package/dist/src/model.d.ts +33 -1
- package/dist/src/model.js +158 -2
- package/dist/src/model.js.map +1 -1
- package/dist/src/packages.d.ts +44 -0
- package/dist/src/packages.js +137 -3
- package/dist/src/packages.js.map +1 -1
- package/dist/src/print-text.js +22 -0
- package/dist/src/print-text.js.map +1 -1
- package/dist/src/publish.d.ts +27 -0
- package/dist/src/publish.js +275 -0
- package/dist/src/publish.js.map +1 -0
- package/dist/src/read-text.d.ts +2 -0
- package/dist/src/read-text.js +72 -4
- package/dist/src/read-text.js.map +1 -1
- package/dist/src/records.d.ts +32 -0
- package/dist/src/records.js +104 -0
- package/dist/src/records.js.map +1 -0
- package/dist/src/review.d.ts +22 -0
- package/dist/src/review.js +198 -0
- package/dist/src/review.js.map +1 -0
- package/dist/src/rules.d.ts +38 -3
- package/dist/src/rules.js +184 -30
- package/dist/src/rules.js.map +1 -1
- package/dist/src/schedule.d.ts +24 -0
- package/dist/src/schedule.js +126 -0
- package/dist/src/schedule.js.map +1 -0
- package/dist/src/std-packages.d.ts +9 -0
- package/dist/src/std-packages.js +268 -0
- package/dist/src/std-packages.js.map +1 -0
- package/dist/src/store.d.ts +38 -7
- package/dist/src/store.js +166 -24
- package/dist/src/store.js.map +1 -1
- package/dist/src/tree.d.ts +3 -0
- package/dist/src/tree.js +36 -0
- package/dist/src/tree.js.map +1 -1
- package/dist/src/units-table.d.ts +1 -0
- package/dist/src/units-table.js +200 -0
- package/dist/src/units-table.js.map +1 -0
- package/dist/src/units.d.ts +42 -4
- package/dist/src/units.js +91 -30
- package/dist/src/units.js.map +1 -1
- package/dist/src/views.d.ts +116 -6
- package/dist/src/views.js +484 -74
- package/dist/src/views.js.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// review.ts: review decisions as writes (spec 9.5, 10.4, 11.9, 14.7; docs/design/completion.md A). A
|
|
2
|
+
// decision is one entry: the `review` write that records it, and the writes it causes: a `define` of the
|
|
3
|
+
// facet, contract or units extension it changed, a value made current from a suggestion or a held bag, a
|
|
4
|
+
// suggestion denied. Nothing here replays or runs; a store does that after the entry lands.
|
|
5
|
+
import { contractJson } from "./bundle.js";
|
|
6
|
+
import { facetJson } from "./dictionary.js";
|
|
7
|
+
import { split } from "./lifecycle.js";
|
|
8
|
+
import { CrubalError } from "./tree.js";
|
|
9
|
+
import { extendUnits } from "./units.js";
|
|
10
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11
|
+
/** The units extension as text, for the units table (11.9). */
|
|
12
|
+
export function unitsExtensionText(measures) {
|
|
13
|
+
return measures
|
|
14
|
+
.map((m) => {
|
|
15
|
+
const lines = [`measure: ${m.measure}`];
|
|
16
|
+
for (const k of ["dimension", "factor", "offset", "rounding", "uncefact"])
|
|
17
|
+
if (m[k] !== undefined)
|
|
18
|
+
lines.push(` ${k}: ${m[k]}`);
|
|
19
|
+
if (Array.isArray(m.spellings) && m.spellings.length)
|
|
20
|
+
lines.push(` spellings: ${m.spellings.join(", ")}`);
|
|
21
|
+
return lines.join("\n");
|
|
22
|
+
})
|
|
23
|
+
.join("\n");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Apply a decision to the draft and return the writes the entry carries beyond what the draft shows:
|
|
27
|
+
* the review write and any define. Throws a CrubalError for a decision that can't be made.
|
|
28
|
+
*/
|
|
29
|
+
export function decide(d, store, defs) {
|
|
30
|
+
const review = { ...d };
|
|
31
|
+
const key = d.key;
|
|
32
|
+
if (key.startsWith("facet:")) {
|
|
33
|
+
const f = defs.dict.facets.get(key.slice(6));
|
|
34
|
+
if (!f)
|
|
35
|
+
throw new CrubalError(`${key}: no such facet`);
|
|
36
|
+
const next = { ...f, values: f.values ? f.values.map((v) => ({ ...v, aliases: [...v.aliases] })) : f.values, aliases: [...f.aliases] };
|
|
37
|
+
if (d.decision === "add value") {
|
|
38
|
+
if (!next.values)
|
|
39
|
+
throw new CrubalError(`${key} isn't a choice`);
|
|
40
|
+
if (next.values.some((v) => v.value === d.value))
|
|
41
|
+
throw new CrubalError(`${d.value} is already a value of ${f.key}`);
|
|
42
|
+
next.values.push({ value: String(d.value), aliases: [] });
|
|
43
|
+
}
|
|
44
|
+
else if (d.decision === "add alias") {
|
|
45
|
+
if (!next.values)
|
|
46
|
+
throw new CrubalError(`${key} isn't a choice`);
|
|
47
|
+
const v = next.values.find((x) => x.value === d.value);
|
|
48
|
+
if (!v)
|
|
49
|
+
throw new CrubalError(`${d.value} is not a value of ${f.key}`);
|
|
50
|
+
v.aliases.push(String(d.alias));
|
|
51
|
+
}
|
|
52
|
+
else if (d.decision !== "reject")
|
|
53
|
+
throw new CrubalError(`${key}: ${d.decision} isn't a decision`);
|
|
54
|
+
if (d.decision === "reject")
|
|
55
|
+
return [{ review }];
|
|
56
|
+
defs.dict.facets.set(f.key, next);
|
|
57
|
+
return [{ review }, { define: facetJson(next) }];
|
|
58
|
+
}
|
|
59
|
+
if (key.startsWith("contract:")) {
|
|
60
|
+
const c = defs.contracts.get(key.slice(9));
|
|
61
|
+
if (!c)
|
|
62
|
+
throw new CrubalError(`${key}: no such contract`);
|
|
63
|
+
const next = { ...c, reads: c.reads.map((m) => ({ ...m, values: [...m.values] })), ignore: [...c.ignore] };
|
|
64
|
+
const field = String(d.field);
|
|
65
|
+
if (d.decision === "map") {
|
|
66
|
+
if (!defs.dict.facets.has(String(d.to)) && !String(d.to).includes(":"))
|
|
67
|
+
throw new CrubalError(`${d.to} isn't a facet`);
|
|
68
|
+
next.reads.push({ from: field, to: String(d.to), values: [], line: c.line, valueLines: [] });
|
|
69
|
+
}
|
|
70
|
+
else if (d.decision === "map value") {
|
|
71
|
+
const m = next.reads.find((x) => x.from === field);
|
|
72
|
+
if (!m)
|
|
73
|
+
throw new CrubalError(`${c.name} doesn't read ${field}`);
|
|
74
|
+
m.values.push([String(d.theirs), String(d.ours)]);
|
|
75
|
+
}
|
|
76
|
+
else if (d.decision === "ignore")
|
|
77
|
+
next.ignore.push(field);
|
|
78
|
+
else
|
|
79
|
+
throw new CrubalError(`${key}: ${d.decision} isn't a decision`);
|
|
80
|
+
defs.contracts.set(c.name, next);
|
|
81
|
+
return [{ review }, { define: contractJson(next) }];
|
|
82
|
+
}
|
|
83
|
+
if (key.startsWith("unit:")) {
|
|
84
|
+
const spelling = key.slice(5);
|
|
85
|
+
const measures = [...(defs.units ?? [])];
|
|
86
|
+
if (d.decision === "alias") {
|
|
87
|
+
const have = measures.find((m) => m.measure === d.unit);
|
|
88
|
+
if (have)
|
|
89
|
+
have.spellings = [...(have.spellings ?? []), spelling];
|
|
90
|
+
else
|
|
91
|
+
measures.push({ measure: String(d.unit), spellings: [spelling] });
|
|
92
|
+
}
|
|
93
|
+
else if (d.decision === "new unit") {
|
|
94
|
+
const m = { measure: String(d.unit), dimension: String(d.dimension), factor: String(d.factor), spellings: [spelling] };
|
|
95
|
+
if (d.offset !== undefined)
|
|
96
|
+
m.offset = String(d.offset);
|
|
97
|
+
measures.push(m);
|
|
98
|
+
}
|
|
99
|
+
else if (d.decision === "not a unit")
|
|
100
|
+
return [{ review }];
|
|
101
|
+
else
|
|
102
|
+
throw new CrubalError(`${key}: ${d.decision} isn't a decision`);
|
|
103
|
+
defs.units = measures;
|
|
104
|
+
extendUnits(unitsExtensionText(measures));
|
|
105
|
+
const writes = [{ review }, { define: { units: "extension", measures } }];
|
|
106
|
+
// The facets that accept the unit from now on (4.2 `accepts`), each a define of its own.
|
|
107
|
+
for (const key of Array.isArray(d.accepts) ? d.accepts : []) {
|
|
108
|
+
const f = defs.dict.facets.get(key);
|
|
109
|
+
if (!f)
|
|
110
|
+
throw new CrubalError(`${key} isn't a facet`);
|
|
111
|
+
if (!f.unit)
|
|
112
|
+
throw new CrubalError(`${key} has no unit to accept ${d.unit} into`);
|
|
113
|
+
const unit = String(d.unit);
|
|
114
|
+
if (f.accepts.includes(unit))
|
|
115
|
+
continue;
|
|
116
|
+
const next = { ...f, accepts: [...f.accepts, unit] };
|
|
117
|
+
defs.dict.facets.set(key, next);
|
|
118
|
+
writes.push({ define: facetJson(next) });
|
|
119
|
+
}
|
|
120
|
+
return writes;
|
|
121
|
+
}
|
|
122
|
+
if (key === "suggestion") {
|
|
123
|
+
const r = store.records.get(String(d.record));
|
|
124
|
+
if (!r)
|
|
125
|
+
throw new CrubalError(`no record ${d.record}`);
|
|
126
|
+
const address = String(d.address);
|
|
127
|
+
const list = r.suggestions?.[address] ?? [];
|
|
128
|
+
const pick = d.value === undefined ? list[0] : list.find((s) => JSON.stringify(s.value) === JSON.stringify(d.value));
|
|
129
|
+
if (!pick)
|
|
130
|
+
throw new CrubalError(`${r.id} has no suggestion at ${address}${d.value !== undefined ? ` with that value` : ""}`);
|
|
131
|
+
const rest = list.filter((s) => s !== pick);
|
|
132
|
+
const suggestions = { ...(r.suggestions ?? {}) };
|
|
133
|
+
if (rest.length)
|
|
134
|
+
suggestions[address] = rest;
|
|
135
|
+
else
|
|
136
|
+
delete suggestions[address];
|
|
137
|
+
const next = { ...r, suggestions };
|
|
138
|
+
if (!Object.keys(suggestions).length)
|
|
139
|
+
delete next.suggestions;
|
|
140
|
+
if (d.decision === "accept") {
|
|
141
|
+
// The value with its original source (14.7).
|
|
142
|
+
const source = { origin: String(pick.origin ?? "reported"), via: String(pick.via ?? "") };
|
|
143
|
+
if (typeof pick.owner === "string")
|
|
144
|
+
source.owner = pick.owner;
|
|
145
|
+
next.values = { ...r.values, [address]: pick.value };
|
|
146
|
+
next.sources = { ...(r.sources ?? {}), [address]: source };
|
|
147
|
+
}
|
|
148
|
+
else if (d.decision !== "deny")
|
|
149
|
+
throw new CrubalError(`suggestion: ${d.decision} isn't a decision`);
|
|
150
|
+
store.records.set(r.id, next);
|
|
151
|
+
return [{ review: { ...review, value: pick.value, ...(d.decision === "deny" ? { denied: true } : {}) } }];
|
|
152
|
+
}
|
|
153
|
+
if (key === "held") {
|
|
154
|
+
const r = store.records.get(String(d.record));
|
|
155
|
+
if (!r)
|
|
156
|
+
throw new CrubalError(`no record ${d.record}`);
|
|
157
|
+
const address = String(d.address);
|
|
158
|
+
const h = r.held?.[address];
|
|
159
|
+
if (!h)
|
|
160
|
+
throw new CrubalError(`${r.id} holds nothing at ${address}`);
|
|
161
|
+
const held = { ...(r.held ?? {}) };
|
|
162
|
+
delete held[address];
|
|
163
|
+
const next = { ...r, held };
|
|
164
|
+
if (!Object.keys(held).length)
|
|
165
|
+
delete next.held;
|
|
166
|
+
if (d.decision === "value") {
|
|
167
|
+
const value = d.value === undefined ? h.value : d.value;
|
|
168
|
+
next.values = { ...r.values, [address]: value };
|
|
169
|
+
next.sources = { ...(r.sources ?? {}), [address]: (isObj(h.source) ? h.source : { origin: "declared", via: "review" }) };
|
|
170
|
+
}
|
|
171
|
+
else if (d.decision !== "drop")
|
|
172
|
+
throw new CrubalError(`held: ${d.decision} isn't a decision`);
|
|
173
|
+
store.records.set(r.id, next);
|
|
174
|
+
return [{ review }];
|
|
175
|
+
}
|
|
176
|
+
if (key.startsWith("split:")) {
|
|
177
|
+
const record = key.slice(6);
|
|
178
|
+
// The decision closes the item it answers: the same record, address and value (openItems).
|
|
179
|
+
if (review.record === undefined)
|
|
180
|
+
review.record = record;
|
|
181
|
+
if (d.decision === "not a version")
|
|
182
|
+
return [{ review }];
|
|
183
|
+
if (d.decision !== "split")
|
|
184
|
+
throw new CrubalError(`${key}: ${d.decision} isn't a decision`);
|
|
185
|
+
const instances = Array.isArray(d.instances) ? d.instances : [];
|
|
186
|
+
const values = typeof d.address === "string" && d.value !== undefined ? { [d.address]: d.value } : {};
|
|
187
|
+
split(store, record, String(d.into), { children: instances, values }, { origin: "observed", via: String(d.via ?? "review") });
|
|
188
|
+
return [{ review }];
|
|
189
|
+
}
|
|
190
|
+
throw new CrubalError(`${key}: not a review item kind a decision applies to here`);
|
|
191
|
+
}
|
|
192
|
+
/** The review items still open: every item without a decision that no later decision names. */
|
|
193
|
+
export function openItems(items) {
|
|
194
|
+
const identity = (it) => JSON.stringify(["key", "record", "address", "run", "ref", "field", "value", "step"].map((k) => it[k] ?? null));
|
|
195
|
+
const decided = new Set(items.filter((it) => it.decision !== undefined).map(identity));
|
|
196
|
+
return items.filter((it) => it.decision === undefined && !decided.has(identity(it)));
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/review.ts"],"names":[],"mappings":"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"}
|
package/dist/src/rules.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Expr } from "./conditions.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type Dictionary, type Facet } from "./defs.ts";
|
|
3
3
|
import type { Json, JsonObject, Record } from "./model.ts";
|
|
4
|
+
import type { RecordSet } from "./records.ts";
|
|
4
5
|
export type Strength = "must" | "should" | "suggest";
|
|
5
6
|
export type Rule = {
|
|
6
7
|
name: string;
|
|
@@ -12,6 +13,7 @@ export type Rule = {
|
|
|
12
13
|
then: Expr | null;
|
|
13
14
|
strength: Strength;
|
|
14
15
|
because?: string;
|
|
16
|
+
version?: string;
|
|
15
17
|
/** A production: `key is <value | template | aggregate>`. */
|
|
16
18
|
produces?: {
|
|
17
19
|
key: string;
|
|
@@ -37,18 +39,51 @@ export type Result = {
|
|
|
37
39
|
export declare class Engine {
|
|
38
40
|
readonly dict: Dictionary;
|
|
39
41
|
readonly rules: Rule[];
|
|
42
|
+
/** The moment `now` means in conditions; unset means the clock's. */
|
|
43
|
+
now?: string;
|
|
40
44
|
private store;
|
|
41
45
|
private order;
|
|
42
|
-
|
|
46
|
+
/** The records a global rule reads (6.6), when the engine runs inside a store; else its own cache. */
|
|
47
|
+
private readonly records?;
|
|
48
|
+
constructor(dict: Dictionary, rules: Rule[], records?: () => RecordSet);
|
|
43
49
|
/** The values a record reads: its own over everything above it (1.7). */
|
|
44
50
|
effective(rec: Record): JsonObject;
|
|
45
51
|
private applies;
|
|
52
|
+
/** Whether any rollup here reads a lower level (`count of unit::…`): then a child's write concerns its ancestors. */
|
|
53
|
+
get readsLowerLevels(): boolean;
|
|
54
|
+
/** The chain up from a record along `of`, nearest first. */
|
|
55
|
+
ancestorsOf(id: string): string[];
|
|
56
|
+
private recordOf;
|
|
57
|
+
/**
|
|
58
|
+
* The records at a lower level under a record, along `of`: merged ones left out always; archived ones left
|
|
59
|
+
* out unless asked for (6.4: a rollup reads the active records below; a filter naming `status` reads them all).
|
|
60
|
+
*/
|
|
61
|
+
private descendants;
|
|
62
|
+
/**
|
|
63
|
+
* Run the productions again for a record as it stands (6.4: recomputed whenever what it reads changes):
|
|
64
|
+
* what a store calls on the ancestors of a changed record, and on a record a plain write touched.
|
|
65
|
+
*/
|
|
66
|
+
reproduce(id: string): Result[];
|
|
46
67
|
/** Where a produced key may be stored: the top of its chain, or a level where it varies. */
|
|
47
68
|
private storesAt;
|
|
48
69
|
private states;
|
|
49
70
|
/** A path a check tests, for reporting. */
|
|
50
71
|
private pathOf;
|
|
72
|
+
/** `is unique` compares records of the same level, and never one archived as merged (6.6). */
|
|
51
73
|
private uniqueClash;
|
|
74
|
+
/** Keep a record as it stands, without running the rules: how a store keeps the engine in step (7.4). */
|
|
75
|
+
put(rec: Record): void;
|
|
76
|
+
/** A record as the engine last left it: its values with what the rules produced, and the keys held. */
|
|
77
|
+
get(id: string): {
|
|
78
|
+
rec: Record;
|
|
79
|
+
produced: Set<string>;
|
|
80
|
+
held: Set<string>;
|
|
81
|
+
} | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Would this write break a `must` rule on a value it touches (6.2)? Nothing is stored. Intake uses it to
|
|
84
|
+
* park such a value as a contradiction citing the rule (9.3) instead of landing it.
|
|
85
|
+
*/
|
|
86
|
+
probe(incoming: Record): Result | null;
|
|
52
87
|
/** Write a record (a create, or values merged onto one already stored). Returns its results. */
|
|
53
88
|
write(incoming: Record): Result[];
|
|
54
89
|
private layered;
|
|
@@ -56,7 +91,7 @@ export declare class Engine {
|
|
|
56
91
|
private text;
|
|
57
92
|
/** A template (6.4.1): `{a}`, `{a ?? b}`, `{a?}`, `{{`, `}}`. Nothing when a needed value is empty. */
|
|
58
93
|
template(right: string, vals: JsonObject): string | undefined;
|
|
59
|
-
/** A rollup over parts
|
|
94
|
+
/** A rollup over parts (`worst of parts[*].condition`) or over a lower level (`count of unit::cashierfuId`). */
|
|
60
95
|
rollup(right: string, rec: Record): Json | undefined;
|
|
61
96
|
/** Completeness (6.9): asked facets with a value ÷ asked facets, per record and per part. */
|
|
62
97
|
completeness(rec: Record): Result[];
|
package/dist/src/rules.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// one spelling); and writes run through the rules in order (6.2, 6.4, 6.7, 6.9): refused, held, filled,
|
|
4
4
|
// derived, flagged, incomplete, complete and completeness.
|
|
5
5
|
import { evaluate, parseCondition, reads } from "./conditions.js";
|
|
6
|
+
import { aggregateOf, levelStep, rowStep } from "./address.js";
|
|
7
|
+
import { unquote } from "./defs.js";
|
|
6
8
|
import { resolve } from "./dictionary.js";
|
|
7
9
|
import { itemText } from "./print-text.js";
|
|
8
10
|
import { CrubalError, readTree } from "./tree.js";
|
|
9
|
-
const AGG = /^(join|sum|best|narrow|worst|deduct|replace|keep|count|least|most) of (\S+)$/;
|
|
10
11
|
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11
12
|
function production(thenText, strength) {
|
|
12
13
|
const m = thenText.match(/^([A-Za-z_][\w:]*) is (.+)$/);
|
|
@@ -16,7 +17,7 @@ function production(thenText, strength) {
|
|
|
16
17
|
const right = m[2].trim();
|
|
17
18
|
if (right.startsWith('"'))
|
|
18
19
|
return { key, kind: "template", right };
|
|
19
|
-
if (
|
|
20
|
+
if (aggregateOf(right))
|
|
20
21
|
return { key, kind: "rollup", right };
|
|
21
22
|
if (right === "unique" || right === "_" || right === "-")
|
|
22
23
|
return undefined;
|
|
@@ -46,7 +47,9 @@ export function readRules(source) {
|
|
|
46
47
|
const thenNode = get("then");
|
|
47
48
|
if (!thenNode)
|
|
48
49
|
throw new CrubalError(`the rule ${node.value} needs a then`, node.line);
|
|
49
|
-
|
|
50
|
+
// A printed `because` holding a comma or a colon comes back quoted (the printer quotes it); the quotes are not its text.
|
|
51
|
+
const because = get("because") ? unquote(get("because")?.value ?? "") : undefined;
|
|
52
|
+
const version = get("version")?.value.trim();
|
|
50
53
|
out.push(makeRule({
|
|
51
54
|
name: node.value.trim(),
|
|
52
55
|
line: node.line,
|
|
@@ -55,6 +58,7 @@ export function readRules(source) {
|
|
|
55
58
|
thenText: thenNode.value,
|
|
56
59
|
strength,
|
|
57
60
|
...(because ? { because } : {}),
|
|
61
|
+
...(version ? { version } : {}),
|
|
58
62
|
}));
|
|
59
63
|
}
|
|
60
64
|
return out;
|
|
@@ -90,8 +94,12 @@ function readsOf(r) {
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
// A rollup reads its parts' addresses (parts[*].condition), not the key it produces on the whole.
|
|
93
|
-
if (r.produces?.kind === "rollup")
|
|
94
|
-
|
|
97
|
+
if (r.produces?.kind === "rollup") {
|
|
98
|
+
const address = aggregateOf(r.produces.right)?.address ?? "";
|
|
99
|
+
// Rows of the record's own compound: the rollup reads that compound (the key before `[*]`).
|
|
100
|
+
const rows = address.match(/^(\w+)\[\*\](?:\.(\w+))?$/);
|
|
101
|
+
keys.push(rows && rows[1] !== "parts" ? rows[1] : address);
|
|
102
|
+
}
|
|
95
103
|
return keys.map((k) => k.replace(/^\w+::/, ""));
|
|
96
104
|
}
|
|
97
105
|
/** Top-level `and`-terms of a condition that pin a key to a set of values. */
|
|
@@ -181,9 +189,14 @@ function emptyOf(v) {
|
|
|
181
189
|
export class Engine {
|
|
182
190
|
dict;
|
|
183
191
|
rules;
|
|
192
|
+
/** The moment `now` means in conditions; unset means the clock's. */
|
|
193
|
+
now;
|
|
184
194
|
store = new Map();
|
|
185
195
|
order = [];
|
|
186
|
-
|
|
196
|
+
/** The records a global rule reads (6.6), when the engine runs inside a store; else its own cache. */
|
|
197
|
+
records;
|
|
198
|
+
constructor(dict, rules, records) {
|
|
199
|
+
this.records = records;
|
|
187
200
|
this.dict = dict;
|
|
188
201
|
this.rules = [...shortcutRules(dict), ...rules];
|
|
189
202
|
}
|
|
@@ -195,11 +208,69 @@ export class Engine {
|
|
|
195
208
|
applies(r, rec) {
|
|
196
209
|
if (r.for === "everywhere")
|
|
197
210
|
return true;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (this.dict.
|
|
201
|
-
return rec.
|
|
202
|
-
|
|
211
|
+
// A level is written with its prefix (`unit::`, 6.1), the same spelling conditions use.
|
|
212
|
+
const scope = r.for.replace(/::$/, "");
|
|
213
|
+
if (this.dict.classes.has(scope))
|
|
214
|
+
return (rec.wears ?? []).some((w) => w === scope || resolveUses(this.dict, w).includes(scope));
|
|
215
|
+
if (this.dict.levels.has(scope))
|
|
216
|
+
return rec.level === scope;
|
|
217
|
+
return rec.kind === scope;
|
|
218
|
+
}
|
|
219
|
+
/** Whether any rollup here reads a lower level (`count of unit::…`): then a child's write concerns its ancestors. */
|
|
220
|
+
get readsLowerLevels() {
|
|
221
|
+
return this.rules.some((r) => r.produces?.kind === "rollup" && /\b\w+::/.test(r.produces.right));
|
|
222
|
+
}
|
|
223
|
+
/** The chain up from a record along `of`, nearest first. */
|
|
224
|
+
ancestorsOf(id) {
|
|
225
|
+
const out = [];
|
|
226
|
+
const seen = new Set([id]);
|
|
227
|
+
let cur = this.recordOf(id)?.of;
|
|
228
|
+
while (cur && !seen.has(cur)) {
|
|
229
|
+
out.push(cur);
|
|
230
|
+
seen.add(cur);
|
|
231
|
+
cur = this.recordOf(cur)?.of;
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
recordOf(id) {
|
|
236
|
+
return this.store.get(id)?.rec ?? this.records?.().get(id);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The records at a lower level under a record, along `of`: merged ones left out always; archived ones left
|
|
240
|
+
* out unless asked for (6.4: a rollup reads the active records below; a filter naming `status` reads them all).
|
|
241
|
+
*/
|
|
242
|
+
descendants(rec, level, archivedToo = false) {
|
|
243
|
+
const set = this.records?.();
|
|
244
|
+
const out = [];
|
|
245
|
+
const walk = (id, depth) => {
|
|
246
|
+
if (depth > 8)
|
|
247
|
+
return;
|
|
248
|
+
const kids = set ? set.childrenOf(id, "of") : [...this.store.values()].map((s) => s.rec).filter((r) => r.of === id);
|
|
249
|
+
for (const k of kids) {
|
|
250
|
+
if (k.status?.reason === "merged")
|
|
251
|
+
continue;
|
|
252
|
+
if (k.level === level) {
|
|
253
|
+
if (archivedToo || k.status?.state !== "archived")
|
|
254
|
+
out.push(this.store.get(k.id)?.rec ?? k);
|
|
255
|
+
}
|
|
256
|
+
else
|
|
257
|
+
walk(k.id, depth + 1);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
walk(rec.id, 0);
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Run the productions again for a record as it stands (6.4: recomputed whenever what it reads changes):
|
|
265
|
+
* what a store calls on the ancestors of a changed record, and on a record a plain write touched.
|
|
266
|
+
*/
|
|
267
|
+
reproduce(id) {
|
|
268
|
+
const rec = this.recordOf(id);
|
|
269
|
+
if (!rec)
|
|
270
|
+
return [];
|
|
271
|
+
if (!this.store.has(id))
|
|
272
|
+
this.put(rec);
|
|
273
|
+
return this.write({ ...rec, values: {} });
|
|
203
274
|
}
|
|
204
275
|
/** Where a produced key may be stored: the top of its chain, or a level where it varies. */
|
|
205
276
|
storesAt(key, rec) {
|
|
@@ -232,23 +303,76 @@ export class Engine {
|
|
|
232
303
|
return this.pathOf(e.a);
|
|
233
304
|
return this.pathOf(e.a);
|
|
234
305
|
}
|
|
306
|
+
/** `is unique` compares records of the same level, and never one archived as merged (6.6). */
|
|
235
307
|
uniqueClash(r, rec) {
|
|
236
308
|
const key = r.thenText.replace(/ is unique$/, "");
|
|
237
309
|
const mine = rec.values[key];
|
|
238
310
|
if (emptyOf(mine) || (isObj(mine) && "$empty" in mine))
|
|
239
311
|
return false;
|
|
312
|
+
const set = this.records?.();
|
|
313
|
+
if (set && mine !== undefined)
|
|
314
|
+
return set.byKey(key, mine, rec.level).some((r) => r.id !== rec.id && r.status?.reason !== "merged");
|
|
240
315
|
const want = JSON.stringify(mine);
|
|
241
|
-
for (const [id, s] of this.store)
|
|
242
|
-
if (id
|
|
316
|
+
for (const [id, s] of this.store) {
|
|
317
|
+
if (id === rec.id || s.rec.level !== rec.level || s.rec.status?.reason === "merged")
|
|
318
|
+
continue;
|
|
319
|
+
if (JSON.stringify(s.rec.values[key]) === want)
|
|
243
320
|
return true;
|
|
321
|
+
}
|
|
244
322
|
return false;
|
|
245
323
|
}
|
|
324
|
+
/** Keep a record as it stands, without running the rules: how a store keeps the engine in step (7.4). */
|
|
325
|
+
put(rec) {
|
|
326
|
+
const was = this.store.get(rec.id);
|
|
327
|
+
this.store.set(rec.id, { rec: { ...rec, values: { ...rec.values } }, produced: new Set(was?.produced ?? []), held: new Set(was?.held ?? []) });
|
|
328
|
+
if (!this.order.includes(rec.id))
|
|
329
|
+
this.order.push(rec.id);
|
|
330
|
+
}
|
|
331
|
+
/** A record as the engine last left it: its values with what the rules produced, and the keys held. */
|
|
332
|
+
get(id) {
|
|
333
|
+
return this.store.get(id);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Would this write break a `must` rule on a value it touches (6.2)? Nothing is stored. Intake uses it to
|
|
337
|
+
* park such a value as a contradiction citing the rule (9.3) instead of landing it.
|
|
338
|
+
*/
|
|
339
|
+
probe(incoming) {
|
|
340
|
+
const before = this.store.get(incoming.id);
|
|
341
|
+
const rec = before ? { ...before.rec, values: { ...before.rec.values, ...incoming.values } } : { ...incoming, values: { ...incoming.values } };
|
|
342
|
+
const touched = new Set(Object.keys(incoming.values));
|
|
343
|
+
const none = new Set();
|
|
344
|
+
for (const r of this.rules) {
|
|
345
|
+
if (r.strength !== "must" || r.produces || !this.applies(r, rec))
|
|
346
|
+
continue;
|
|
347
|
+
const vals = this.effective(rec);
|
|
348
|
+
if (r.when && evaluate(r.when, vals, this.states(rec, none), this.now) !== true)
|
|
349
|
+
continue;
|
|
350
|
+
if (r.thenText.endsWith(" is unique")) {
|
|
351
|
+
const key = r.thenText.replace(/ is unique$/, "");
|
|
352
|
+
if (touched.has(key) && this.uniqueClash(r, rec))
|
|
353
|
+
return { record: rec.id, result: "refused", address: key, rule: r.name };
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const path = this.pathOf(r.then);
|
|
357
|
+
if (!touched.has(path) || emptyOf(vals[path]))
|
|
358
|
+
continue;
|
|
359
|
+
if (r.then && evaluate(r.then, vals, this.states(rec, none), this.now) === false)
|
|
360
|
+
return { record: rec.id, result: "refused", address: path, rule: r.name };
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
246
364
|
/** Write a record (a create, or values merged onto one already stored). Returns its results. */
|
|
247
365
|
write(incoming) {
|
|
248
366
|
const before = this.store.get(incoming.id);
|
|
249
367
|
const rec = before
|
|
250
368
|
? { ...before.rec, values: { ...before.rec.values, ...incoming.values }, parts: incoming.parts ?? before.rec.parts }
|
|
251
369
|
: { ...incoming, values: { ...incoming.values } };
|
|
370
|
+
// A value a record holds (6.7) is evaluated as if present, so a verdict that no longer holds releases it.
|
|
371
|
+
const heldBag = (incoming.held ?? before?.rec?.held ?? {});
|
|
372
|
+
for (const [k, h] of Object.entries(heldBag))
|
|
373
|
+
if (rec.values[k] === undefined && h.value !== undefined)
|
|
374
|
+
rec.values[k] = h.value;
|
|
375
|
+
const heldBefore = new Set([...(before?.held ?? []), ...Object.keys(heldBag)]);
|
|
252
376
|
const touched = new Set(Object.keys(incoming.values));
|
|
253
377
|
const produced = new Set(before?.produced ?? []);
|
|
254
378
|
const held = new Set();
|
|
@@ -258,7 +382,7 @@ export class Engine {
|
|
|
258
382
|
if (r.strength !== "must" || r.produces || !this.applies(r, rec))
|
|
259
383
|
continue;
|
|
260
384
|
const vals = this.effective(rec);
|
|
261
|
-
if (r.when && evaluate(r.when, vals, this.states(rec, held)) !== true)
|
|
385
|
+
if (r.when && evaluate(r.when, vals, this.states(rec, held), this.now) !== true)
|
|
262
386
|
continue;
|
|
263
387
|
if (r.thenText.endsWith(" is unique")) {
|
|
264
388
|
const key = r.thenText.replace(/ is unique$/, "");
|
|
@@ -273,7 +397,7 @@ export class Engine {
|
|
|
273
397
|
const path = this.pathOf(r.then);
|
|
274
398
|
if (emptyOf(vals[path]))
|
|
275
399
|
continue; // an empty value is incompleteness, reported below
|
|
276
|
-
if (r.then && evaluate(r.then, vals, this.states(rec, held)) === false) {
|
|
400
|
+
if (r.then && evaluate(r.then, vals, this.states(rec, held), this.now) === false) {
|
|
277
401
|
if (touched.has(path))
|
|
278
402
|
return [{ record: rec.id, result: "refused", address: path, rule: r.name }];
|
|
279
403
|
held.add(path);
|
|
@@ -296,7 +420,7 @@ export class Engine {
|
|
|
296
420
|
const key = gate[1];
|
|
297
421
|
if (held.has(key) || rec.values[key] !== undefined || !this.storesAt(key, rec))
|
|
298
422
|
continue;
|
|
299
|
-
if (r.when && evaluate(r.when, visible(), this.states(rec, held)) !== true)
|
|
423
|
+
if (r.when && evaluate(r.when, visible(), this.states(rec, held), this.now) !== true)
|
|
300
424
|
continue;
|
|
301
425
|
rec.values[key] = { $empty: "n/a" };
|
|
302
426
|
produced.add(key);
|
|
@@ -310,7 +434,7 @@ export class Engine {
|
|
|
310
434
|
if (!this.storesAt(key, rec))
|
|
311
435
|
continue;
|
|
312
436
|
const vals = visible();
|
|
313
|
-
if (r.when && evaluate(r.when, vals, this.states(rec, held)) !== true)
|
|
437
|
+
if (r.when && evaluate(r.when, vals, this.states(rec, held), this.now) !== true)
|
|
314
438
|
continue;
|
|
315
439
|
const current = rec.values[key];
|
|
316
440
|
const mine = produced.has(key);
|
|
@@ -324,8 +448,11 @@ export class Engine {
|
|
|
324
448
|
results.push({ record: rec.id, result: "filled", address: key, value: v, rule: r.name });
|
|
325
449
|
continue;
|
|
326
450
|
}
|
|
327
|
-
|
|
328
|
-
|
|
451
|
+
// A value the rules produced keeps following them, whichever engine produced it (its source says so);
|
|
452
|
+
// a person's value is adopted and never overwritten.
|
|
453
|
+
const derivedBefore = rec.sources?.[key]?.origin === "derived" && !rec.sources?.[key]?.adopted;
|
|
454
|
+
if (!emptyOf(current) && !mine && !derivedBefore)
|
|
455
|
+
continue;
|
|
329
456
|
const v = kind === "template" ? this.template(right, vals) : this.rollup(right, rec);
|
|
330
457
|
if (v === undefined)
|
|
331
458
|
continue;
|
|
@@ -333,17 +460,23 @@ export class Engine {
|
|
|
333
460
|
produced.add(key);
|
|
334
461
|
results.push({ record: rec.id, result: "derived", address: key, value: v, rule: r.name });
|
|
335
462
|
}
|
|
463
|
+
// 2b. A value held before whose verdict no longer holds comes back (6.7).
|
|
464
|
+
for (const k of heldBefore) {
|
|
465
|
+
if (held.has(k) || rec.values[k] === undefined)
|
|
466
|
+
continue;
|
|
467
|
+
results.push({ record: rec.id, result: "released", address: k, value: rec.values[k] });
|
|
468
|
+
}
|
|
336
469
|
// 3. Results: incomplete, flagged, complete, completeness.
|
|
337
470
|
let complete = held.size === 0;
|
|
338
471
|
const vals = visible();
|
|
339
472
|
for (const r of this.rules) {
|
|
340
473
|
if (r.produces || !this.applies(r, rec))
|
|
341
474
|
continue;
|
|
342
|
-
if (r.when && evaluate(r.when, vals, this.states(rec, held)) !== true)
|
|
475
|
+
if (r.when && evaluate(r.when, vals, this.states(rec, held), this.now) !== true)
|
|
343
476
|
continue;
|
|
344
477
|
if (r.thenText.endsWith(" is unique"))
|
|
345
478
|
continue;
|
|
346
|
-
const truth = r.then ? evaluate(r.then, vals, this.states(rec, held)) : true;
|
|
479
|
+
const truth = r.then ? evaluate(r.then, vals, this.states(rec, held), this.now) : true;
|
|
347
480
|
const path = this.pathOf(r.then);
|
|
348
481
|
const needsValue = path !== undefined && emptyOf(vals[path]);
|
|
349
482
|
if (r.strength === "must" && (truth === "undetermined" || (truth === false && needsValue))) {
|
|
@@ -422,16 +555,37 @@ export class Engine {
|
|
|
422
555
|
}
|
|
423
556
|
return out;
|
|
424
557
|
}
|
|
425
|
-
/** A rollup over parts
|
|
558
|
+
/** A rollup over parts (`worst of parts[*].condition`) or over a lower level (`count of unit::cashierfuId`). */
|
|
426
559
|
rollup(right, rec) {
|
|
427
|
-
const
|
|
428
|
-
if (!
|
|
560
|
+
const agg = aggregateOf(right);
|
|
561
|
+
if (!agg)
|
|
429
562
|
return undefined;
|
|
430
|
-
const how =
|
|
431
|
-
const
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
563
|
+
const { how, address } = agg;
|
|
564
|
+
const where = agg.where ? parseCondition(agg.where) : null;
|
|
565
|
+
const lower = levelStep(address);
|
|
566
|
+
const rows = lower ? null : rowStep(address, this.dict);
|
|
567
|
+
const key = lower ? lower.rest : rows ? (rows.child ?? "") : address.replace(/^parts\[\*\]\./, "");
|
|
568
|
+
let raw;
|
|
569
|
+
if (lower) {
|
|
570
|
+
// A filter over the lower records reads each with its envelope; one that names `status` sees the archived too.
|
|
571
|
+
const namesStatus = where ? reads(where).some((k) => k === "status" || k === "crubal:status") : false;
|
|
572
|
+
const below = this.descendants(rec, lower.level, namesStatus);
|
|
573
|
+
const kept = where ? below.filter((d) => evaluate(where, { ...d.values, id: d.id, status: d.status?.state ?? "active", "crubal:status": d.status?.state ?? "active" }, undefined, this.now) === true) : below;
|
|
574
|
+
raw = kept.map((d) => d.values[key]);
|
|
575
|
+
}
|
|
576
|
+
else if (rows) {
|
|
577
|
+
// The rows of one of the record's own compounds (6.11): a count needs no child; a filter reads each row.
|
|
578
|
+
const list = rec.values[rows.compound];
|
|
579
|
+
const all = (Array.isArray(list) ? list : list === undefined ? [] : [list]).filter(isObj);
|
|
580
|
+
const kept = where ? all.filter((row) => evaluate(where, row, undefined, this.now) === true) : all;
|
|
581
|
+
raw = kept.map((row) => (key ? row[key] : row));
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
const parts = (rec.parts ?? []).filter((p) => isObj(p) && isObj(p.values));
|
|
585
|
+
const kept = where ? parts.filter((p) => evaluate(where, p.values, undefined, this.now) === true) : parts;
|
|
586
|
+
raw = kept.map((p) => p.values[key]);
|
|
587
|
+
}
|
|
588
|
+
const items = raw.filter((v) => v !== undefined && !(isObj(v) && "$empty" in v));
|
|
435
589
|
if (how === "count")
|
|
436
590
|
return String(items.length);
|
|
437
591
|
if (!items.length)
|
|
@@ -457,7 +611,7 @@ export class Engine {
|
|
|
457
611
|
completeness(rec) {
|
|
458
612
|
const out = [];
|
|
459
613
|
const measure = (worn, values) => {
|
|
460
|
-
const asked = resolve(this.dict, worn.filter((w) => this.dict.classes.has(w))).filter((l) => !l.when || evaluate(parseCondition(l.when), values) === true);
|
|
614
|
+
const asked = resolve(this.dict, worn.filter((w) => this.dict.classes.has(w))).filter((l) => !l.when || evaluate(parseCondition(l.when), values, undefined, this.now) === true);
|
|
461
615
|
if (!asked.length)
|
|
462
616
|
return null;
|
|
463
617
|
const had = asked.filter((l) => !emptyOf(values[l.facet])).length;
|