@githonllc/avouch 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +161 -2
- package/SPEC.md +337 -0
- package/dist/cli/avouch.d.ts +2 -0
- package/dist/cli/avouch.js +51 -0
- package/dist/cli/check.d.ts +8 -0
- package/dist/cli/check.js +63 -0
- package/dist/cli/query.d.ts +2 -0
- package/dist/cli/query.js +150 -0
- package/dist/src/checker.d.ts +3 -0
- package/dist/src/checker.js +1001 -0
- package/dist/src/contract.d.ts +135 -0
- package/dist/src/contract.js +3 -0
- package/dist/src/evaluate.d.ts +66 -0
- package/dist/src/evaluate.js +478 -0
- package/dist/src/expr.d.ts +190 -0
- package/dist/src/expr.js +426 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +8 -0
- package/dist/src/markdown.d.ts +15 -0
- package/dist/src/markdown.js +476 -0
- package/dist/src/patch.d.ts +6 -0
- package/dist/src/patch.js +37 -0
- package/dist/src/query.d.ts +304 -0
- package/dist/src/query.js +681 -0
- package/dist/src/reconcile.d.ts +53 -0
- package/dist/src/reconcile.js +78 -0
- package/dist/src/stage1-check.d.ts +29 -0
- package/dist/src/stage1-check.js +305 -0
- package/dist/src/unknown.d.ts +10 -0
- package/dist/src/unknown.js +172 -0
- package/docs/adapter-guide.md +155 -0
- package/ontology.schema.json +1690 -0
- package/package.json +53 -4
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
// Read-only queries over an ontology: what an action requires and decides, who writes or reads a property, which claims
|
|
2
|
+
// cite an anchor. Pure functions: they never throw on any parsed YAML, and they return plain JSON (no Map, no Set).
|
|
3
|
+
// Every result field is an ontology value or a mechanical projection of one; a missing or ill-typed value is null ([] for
|
|
4
|
+
// a list). The read sets come from the type check (checkPredicate / derivedPropertyType with collect), as for R4.
|
|
5
|
+
import { checkPredicate, derivedPropertyType, isObj, newReads, own } from "./expr.js";
|
|
6
|
+
const str = (x) => (typeof x === "string" ? x : null);
|
|
7
|
+
const arr = (x) => (Array.isArray(x) ? x : []);
|
|
8
|
+
const strs = (x) => arr(x).filter((y) => typeof y === "string");
|
|
9
|
+
const map = (x) => (isObj(x) ? x : {});
|
|
10
|
+
const ownObj = (r, k) => {
|
|
11
|
+
const v = own(r, k);
|
|
12
|
+
return isObj(v) ? v : undefined;
|
|
13
|
+
};
|
|
14
|
+
const typeOf = (x) => (typeof x === "string" || isObj(x) ? x : null);
|
|
15
|
+
const sortStr = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
16
|
+
const uniqSorted = (xs) => [...new Set(xs)].sort(sortStr);
|
|
17
|
+
const errText = (e) => (e instanceof Error ? e.message : String(e));
|
|
18
|
+
function citeOf(x) {
|
|
19
|
+
return isObj(x) && typeof x.doc === "string" && typeof x.quote === "string" ? { doc: x.doc, quote: x.quote } : null;
|
|
20
|
+
}
|
|
21
|
+
// A cites list: [{cite: {doc, quote}}, ...]; entries without a valid cite are left out.
|
|
22
|
+
const citesOf = (x) => arr(x).flatMap((y) => (isObj(y) && citeOf(y.cite) ? [citeOf(y.cite)] : []));
|
|
23
|
+
const root = (ont) => map(ont);
|
|
24
|
+
const actions = (ont) => map(root(ont).actionTypes);
|
|
25
|
+
const objects = (ont) => map(root(ont).objectTypes);
|
|
26
|
+
const deriveds = (ont) => map(root(ont).derivedProperties);
|
|
27
|
+
// ---------------------------------------------------------------------------------------------------------- showExpr
|
|
28
|
+
const COMPARE = { eq: "=", neq: "!=", lt: "<", lte: "<=", gt: ">", gte: ">=" };
|
|
29
|
+
function unknownNode(e) {
|
|
30
|
+
try {
|
|
31
|
+
return `<?>${JSON.stringify(e)}`;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return "<?>";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function showLit(v) {
|
|
38
|
+
if (typeof v === "string")
|
|
39
|
+
return JSON.stringify(v);
|
|
40
|
+
if (typeof v === "number")
|
|
41
|
+
return String(v);
|
|
42
|
+
if (Array.isArray(v) && v.every((x) => typeof x === "string" || typeof x === "number"))
|
|
43
|
+
return `[${v.map((x) => showLit(x)).join(", ")}]`;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
// Renders an expression as one line of text. Never throws.
|
|
47
|
+
export function showExpr(e) {
|
|
48
|
+
return show(e, true);
|
|
49
|
+
}
|
|
50
|
+
function show(e, atRoot) {
|
|
51
|
+
if (!isObj(e) || Object.keys(e).length !== 1)
|
|
52
|
+
return unknownNode(e);
|
|
53
|
+
const [k] = Object.keys(e);
|
|
54
|
+
const v = e[k];
|
|
55
|
+
const two = Array.isArray(v) && v.length === 2;
|
|
56
|
+
const one = Array.isArray(v) && v.length === 1;
|
|
57
|
+
const wrap = (s) => (atRoot ? s : `(${s})`);
|
|
58
|
+
if (k === "ref" && typeof v === "string")
|
|
59
|
+
return v;
|
|
60
|
+
if (k === "lit")
|
|
61
|
+
return showLit(v) ?? unknownNode(e);
|
|
62
|
+
if (COMPARE[k] !== undefined && two)
|
|
63
|
+
return `${show(v[0], false)} ${COMPARE[k]} ${show(v[1], false)}`;
|
|
64
|
+
if (k === "plus" && two)
|
|
65
|
+
return `${show(v[0], false)} + ${show(v[1], false)}`;
|
|
66
|
+
if (k === "dateIn" && two)
|
|
67
|
+
return `dateIn(${show(v[0], true)}, ${show(v[1], true)})`;
|
|
68
|
+
if (k === "derived" && isObj(v) && typeof v.id === "string" && isObj(v.of) && typeof v.of.ref === "string")
|
|
69
|
+
return `${v.id}(${v.of.ref})`;
|
|
70
|
+
if ((k === "and" || k === "or") && Array.isArray(v) && v.length > 0)
|
|
71
|
+
return wrap(v.map((x) => show(x, false)).join(` ${k} `));
|
|
72
|
+
if (k === "not" && one)
|
|
73
|
+
return `not(${show(v[0], true)})`;
|
|
74
|
+
if (k === "in" && two)
|
|
75
|
+
return `${show(v[0], false)} in ${show(v[1], false)}`;
|
|
76
|
+
if (k === "subsetOf" && two)
|
|
77
|
+
return `${show(v[0], false)} subsetOf ${show(v[1], false)}`;
|
|
78
|
+
if (k === "isNull" && one)
|
|
79
|
+
return `${show(v[0], false)} is null`;
|
|
80
|
+
if (k === "isNotNull" && one)
|
|
81
|
+
return `${show(v[0], false)} is not null`;
|
|
82
|
+
if ((k === "exists" || k === "none" || k === "all") && isObj(v) && typeof v.as === "string" && isObj(v.in) && isObj(v.in.nav)) {
|
|
83
|
+
const n = v.in.nav;
|
|
84
|
+
if (!isObj(n.from) || typeof n.from.ref !== "string" || typeof n.link !== "string" || typeof n.dir !== "string")
|
|
85
|
+
return unknownNode(e);
|
|
86
|
+
const where = v.where !== undefined ? ` where ${show(v.where, false)}` : "";
|
|
87
|
+
return wrap(`${k} ${v.as} in ${n.from.ref}.${n.link}(${n.dir})${where}`);
|
|
88
|
+
}
|
|
89
|
+
return unknownNode(e);
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------------------------------------- read sets
|
|
92
|
+
// The view that the type check reads; non-mappings become empty so that it cannot fail on shape.
|
|
93
|
+
function view(ont) {
|
|
94
|
+
const r = root(ont);
|
|
95
|
+
const objs = {};
|
|
96
|
+
for (const [k, o] of Object.entries(objects(ont)))
|
|
97
|
+
objs[k] = { ...map(o), properties: map(map(o).properties) };
|
|
98
|
+
const params = (a) => (isObj(a.parameters) ? a.parameters : {});
|
|
99
|
+
const acts = {};
|
|
100
|
+
for (const [k, a] of Object.entries(actions(ont)))
|
|
101
|
+
acts[k] = { ...map(a), parameters: params(map(a)) };
|
|
102
|
+
return {
|
|
103
|
+
scope: isObj(r.scope) ? r.scope : undefined,
|
|
104
|
+
dispositions: map(r.dispositions),
|
|
105
|
+
objectTypes: objs,
|
|
106
|
+
linkTypes: arr(r.linkTypes).filter(isObj),
|
|
107
|
+
derivedProperties: { ...deriveds(ont) },
|
|
108
|
+
actionTypes: acts,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// The view with the expression of every derived property removed, except `keep`: a type check on it records only the
|
|
112
|
+
// first hop (the properties read directly, and the derived properties referenced directly).
|
|
113
|
+
function stripped(v, keep) {
|
|
114
|
+
const d = {};
|
|
115
|
+
for (const [k, x] of Object.entries(v.derivedProperties ?? {})) {
|
|
116
|
+
if (!isObj(x) || k === keep)
|
|
117
|
+
d[k] = x;
|
|
118
|
+
else {
|
|
119
|
+
const { expr: _expr, ...rest } = x;
|
|
120
|
+
d[k] = rest;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { ...v, derivedProperties: d };
|
|
124
|
+
}
|
|
125
|
+
// A read string: "<Object>.<prop>", or a derived property named bare or as "<of>.<id>".
|
|
126
|
+
function classify(ont, s) {
|
|
127
|
+
const d = deriveds(ont);
|
|
128
|
+
if (!s.includes(".") && own(d, s) !== undefined)
|
|
129
|
+
return { derived: s };
|
|
130
|
+
const [o, p] = s.split(".");
|
|
131
|
+
if (p !== undefined && ownObj(map(ownObj(objects(ont), o)?.properties), p) === undefined && ownObj(d, p)?.of === o)
|
|
132
|
+
return { derived: p };
|
|
133
|
+
return { prop: s };
|
|
134
|
+
}
|
|
135
|
+
function readsListInputs(ont, reads) {
|
|
136
|
+
const props = [], derived = [];
|
|
137
|
+
for (const r of arr(reads)) {
|
|
138
|
+
if (typeof r === "string") {
|
|
139
|
+
const c = classify(ont, r);
|
|
140
|
+
if ("derived" in c)
|
|
141
|
+
derived.push(c.derived);
|
|
142
|
+
else
|
|
143
|
+
props.push(c.prop);
|
|
144
|
+
}
|
|
145
|
+
else if (isObj(r) && typeof r.prop === "string")
|
|
146
|
+
props.push(r.prop);
|
|
147
|
+
}
|
|
148
|
+
return { props: uniqSorted(props), derived: uniqSorted(derived) };
|
|
149
|
+
}
|
|
150
|
+
// One analysis per ontology value: derived inputs, condition inputs, closures, and specified flags.
|
|
151
|
+
class Analysis {
|
|
152
|
+
ont;
|
|
153
|
+
v;
|
|
154
|
+
dInputs = new Map();
|
|
155
|
+
closures = new Map();
|
|
156
|
+
constructor(ont) {
|
|
157
|
+
this.ont = ont;
|
|
158
|
+
this.v = view(ont);
|
|
159
|
+
}
|
|
160
|
+
derivedInputs(id) {
|
|
161
|
+
let r = this.dInputs.get(id);
|
|
162
|
+
if (r !== undefined)
|
|
163
|
+
return r;
|
|
164
|
+
const dp = ownObj(deriveds(this.ont), id);
|
|
165
|
+
if (dp === undefined)
|
|
166
|
+
r = { props: [], derived: [] };
|
|
167
|
+
else if (dp.expr === undefined)
|
|
168
|
+
r = readsListInputs(this.ont, dp.reads);
|
|
169
|
+
else {
|
|
170
|
+
const collect = newReads();
|
|
171
|
+
try {
|
|
172
|
+
derivedPropertyType(stripped(this.v, id), id, collect);
|
|
173
|
+
r = { props: uniqSorted(collect.reads.map((x) => x.prop)), derived: uniqSorted([...collect.undefinedDerived]) };
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
r = { props: [], derived: [], error: errText(e) };
|
|
177
|
+
}
|
|
178
|
+
// The full view also follows the chain, so a cycle of derived properties is an error here.
|
|
179
|
+
if (r.error === undefined)
|
|
180
|
+
try {
|
|
181
|
+
derivedPropertyType(this.v, id);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
r = { ...r, error: errText(e) };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
this.dInputs.set(id, r);
|
|
188
|
+
return r;
|
|
189
|
+
}
|
|
190
|
+
// Everything a derived property reads, through any chain of derived properties (cycles stop).
|
|
191
|
+
closure(id) {
|
|
192
|
+
const c = this.closures.get(id);
|
|
193
|
+
if (c !== undefined)
|
|
194
|
+
return c;
|
|
195
|
+
const props = new Set(), derived = new Set();
|
|
196
|
+
const stack = [id], seen = new Set([id]);
|
|
197
|
+
while (stack.length) {
|
|
198
|
+
const inp = this.derivedInputs(stack.pop());
|
|
199
|
+
for (const p of inp.props)
|
|
200
|
+
props.add(p);
|
|
201
|
+
for (const d of inp.derived) {
|
|
202
|
+
derived.add(d);
|
|
203
|
+
if (!seen.has(d)) {
|
|
204
|
+
seen.add(d);
|
|
205
|
+
stack.push(d);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const r = { props, derived };
|
|
210
|
+
this.closures.set(id, r);
|
|
211
|
+
return r;
|
|
212
|
+
}
|
|
213
|
+
conditionInputs(action, cond) {
|
|
214
|
+
if (cond.expr === undefined)
|
|
215
|
+
return { ...readsListInputs(this.ont, cond.reads), source: "reads" };
|
|
216
|
+
const collect = newReads();
|
|
217
|
+
try {
|
|
218
|
+
checkPredicate(stripped(this.v), cond.expr, { action, locals: {}, collect });
|
|
219
|
+
return { props: uniqSorted(collect.reads.map((x) => x.prop)), derived: uniqSorted([...collect.undefinedDerived]), source: "expr" };
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
return { props: [], derived: [], error: errText(e), source: "expr" };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// A condition is specified when it has an expression that type-checks and reaches no derived property without one.
|
|
226
|
+
specified(action, cond) {
|
|
227
|
+
if (cond === undefined || cond.expr === undefined)
|
|
228
|
+
return false;
|
|
229
|
+
const collect = newReads();
|
|
230
|
+
try {
|
|
231
|
+
checkPredicate(this.v, cond.expr, { action, locals: {}, collect });
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
return collect.undefinedDerived.size === 0;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const conditionsOf = (a) => arr(a.conditions).filter((c) => isObj(c) && typeof c.id === "string");
|
|
240
|
+
const sortedActions = (ont) => Object.entries(actions(ont))
|
|
241
|
+
.filter((e) => isObj(e[1]))
|
|
242
|
+
.sort((a, b) => sortStr(a[0], b[0]));
|
|
243
|
+
const whenId = (w) => (isObj(w) ? (typeof w.passes === "string" ? w.passes : typeof w.fails === "string" ? w.fails : null) : null);
|
|
244
|
+
// ---------------------------------------------------------------------------------------------------------- action
|
|
245
|
+
function decisionRows(an, id, a) {
|
|
246
|
+
const d = map(a.decision);
|
|
247
|
+
const disp = map(root(an.ont).dispositions);
|
|
248
|
+
const conds = conditionsOf(a);
|
|
249
|
+
const spec = new Map();
|
|
250
|
+
const isSpecified = (c) => {
|
|
251
|
+
if (!spec.has(c))
|
|
252
|
+
spec.set(c, an.specified(id, conds.find((x) => x.id === c)));
|
|
253
|
+
return spec.get(c);
|
|
254
|
+
};
|
|
255
|
+
const base = (r) => {
|
|
256
|
+
const result = str(r.result);
|
|
257
|
+
return { result, class: result !== null ? str(ownObj(disp, result)?.class) : null, cites: citesOf(r.cites), next: r.next ?? null };
|
|
258
|
+
};
|
|
259
|
+
const rows = [];
|
|
260
|
+
const before = [];
|
|
261
|
+
let blockedBy = null;
|
|
262
|
+
for (const [i, r] of arr(d.rows).entries()) {
|
|
263
|
+
const raw = isObj(r) ? r : {};
|
|
264
|
+
const w = raw.when;
|
|
265
|
+
const when = isObj(w) && typeof w.passes === "string" ? { passes: w.passes } : isObj(w) && typeof w.fails === "string" ? { fails: w.fails } : null;
|
|
266
|
+
const requires = [...before];
|
|
267
|
+
if (when !== null) {
|
|
268
|
+
const c = whenId(when);
|
|
269
|
+
const s = isSpecified(c);
|
|
270
|
+
requires.push({ condition: c, must: "passes" in when ? "pass" : "fail", specified: s });
|
|
271
|
+
before.push({ condition: c, must: "passes" in when ? "fail" : "pass", specified: s });
|
|
272
|
+
}
|
|
273
|
+
rows.push({ row: i, when, ...base(raw), requires, blockedBy });
|
|
274
|
+
if (when !== null && blockedBy === null && !requires[requires.length - 1].specified)
|
|
275
|
+
blockedBy = requires[requires.length - 1].condition;
|
|
276
|
+
}
|
|
277
|
+
if (isObj(d.otherwise))
|
|
278
|
+
rows.push({ row: "otherwise", when: null, ...base(d.otherwise), requires: [...before], blockedBy });
|
|
279
|
+
return rows;
|
|
280
|
+
}
|
|
281
|
+
function principalsOf(pr) {
|
|
282
|
+
return isObj(pr) ? { kinds: strs(pr.kinds), cite: citeOf(pr.cite) } : null;
|
|
283
|
+
}
|
|
284
|
+
function alt(x) {
|
|
285
|
+
return {
|
|
286
|
+
keys: strs(x.keys),
|
|
287
|
+
conditionalKeys: strs(x.conditional_keys),
|
|
288
|
+
cite: citeOf(x.cite),
|
|
289
|
+
conditionalCite: citeOf(x.conditional_cite),
|
|
290
|
+
principals: principalsOf(x.principals),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function permissionOf(p) {
|
|
294
|
+
if (p === "unknown")
|
|
295
|
+
return { form: "unknown" };
|
|
296
|
+
if (isObj(p) && Array.isArray(p.any_of))
|
|
297
|
+
return { form: "keys", alternatives: p.any_of.filter(isObj).map(alt) };
|
|
298
|
+
if (isObj(p) && "none" in p)
|
|
299
|
+
return { form: "none", reason: str(p.none), cite: citeOf(p.cite), principals: principalsOf(p.principals) };
|
|
300
|
+
if (isObj(p))
|
|
301
|
+
return { form: "keys", alternatives: [alt(p)] };
|
|
302
|
+
return { form: "keys", alternatives: [] };
|
|
303
|
+
}
|
|
304
|
+
function transitionsBy(ont, id) {
|
|
305
|
+
const out = [];
|
|
306
|
+
for (const [o, sm] of Object.entries(map(root(ont).stateMachines)))
|
|
307
|
+
for (const t of arr(map(sm).transitions))
|
|
308
|
+
if (isObj(t) && strs(t.by).includes(id))
|
|
309
|
+
out.push({ object: o, from: str(t.from), to: str(t.to) });
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
const LINK_KINDS = ["effect", "none", "unspecified"];
|
|
313
|
+
export function queryAction(ont, id) {
|
|
314
|
+
const a = ownObj(actions(ont), id);
|
|
315
|
+
if (a === undefined)
|
|
316
|
+
return null;
|
|
317
|
+
const an = new Analysis(ont);
|
|
318
|
+
const editCites = map(a.edit_cites);
|
|
319
|
+
const ik = a.idempotencyKey;
|
|
320
|
+
const cc = a.crossContext;
|
|
321
|
+
return {
|
|
322
|
+
id,
|
|
323
|
+
context: str(a.context),
|
|
324
|
+
doc: str(a.doc),
|
|
325
|
+
docTerm: str(a.doc_term),
|
|
326
|
+
permission: permissionOf(a.permission),
|
|
327
|
+
idempotencyKey: isObj(ik) && typeof ik.required === "boolean" ? { required: ik.required, cite: citeOf(ik.cite) } : null,
|
|
328
|
+
parameters: Object.entries(map(a.parameters)).map(([name, p]) => ({
|
|
329
|
+
name,
|
|
330
|
+
type: typeOf(map(p).type),
|
|
331
|
+
optional: map(p).optional === true,
|
|
332
|
+
values: Array.isArray(map(p).values) ? map(p).values.filter((x) => typeof x === "string") : null,
|
|
333
|
+
cite: citeOf(map(p).cite),
|
|
334
|
+
})),
|
|
335
|
+
conditions: conditionsOf(a).map((c) => ({
|
|
336
|
+
id: c.id,
|
|
337
|
+
expr: c.expr !== undefined ? c.expr : null,
|
|
338
|
+
exprText: c.expr !== undefined ? showExpr(c.expr) : null,
|
|
339
|
+
unspecified: str(c.unspecified),
|
|
340
|
+
reads: arr(c.reads),
|
|
341
|
+
cites: citesOf(c.cites),
|
|
342
|
+
scenarios: strs(c.scenarios),
|
|
343
|
+
})),
|
|
344
|
+
decision: isObj(a.decision) ? { order: citeOf(map(a.decision.order).cite), rows: decisionRows(an, id, a) } : null,
|
|
345
|
+
edits: strs(a.edits).map((prop) => ({ prop, cite: citeOf(map(own(editCites, prop)).cite) })),
|
|
346
|
+
creates: strs(a.creates),
|
|
347
|
+
emits: strs(a.emits),
|
|
348
|
+
linkEffects: Object.entries(map(a.link_effects)).flatMap(([link, le]) => {
|
|
349
|
+
const e = map(le);
|
|
350
|
+
const kind = LINK_KINDS.find((k) => k in e);
|
|
351
|
+
return kind === undefined ? [] : [{ link, kind, text: str(e[kind]), cite: citeOf(e.cite), scenarios: strs(e.scenarios) }];
|
|
352
|
+
}),
|
|
353
|
+
crossContext: isObj(cc) ? { via: str(cc.via), unspecified: str(cc.unspecified), cite: citeOf(cc.cite) } : null,
|
|
354
|
+
evidence: str(a.evidence),
|
|
355
|
+
transitions: transitionsBy(ont, id),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
// ---------------------------------------------------------------------------------------------------------- result
|
|
359
|
+
export function queryDisposition(ont, id) {
|
|
360
|
+
const d = ownObj(map(root(ont).dispositions), id);
|
|
361
|
+
const an = new Analysis(ont);
|
|
362
|
+
const rows = [];
|
|
363
|
+
for (const [aid, a] of sortedActions(ont)) {
|
|
364
|
+
if (!isObj(a.decision))
|
|
365
|
+
continue;
|
|
366
|
+
for (const r of decisionRows(an, aid, a))
|
|
367
|
+
if (r.result === id)
|
|
368
|
+
rows.push({ action: aid, ...r });
|
|
369
|
+
}
|
|
370
|
+
if (d === undefined && rows.length === 0)
|
|
371
|
+
return null;
|
|
372
|
+
return { disposition: id, class: str(d?.class), cite: citeOf(d?.cite), rows };
|
|
373
|
+
}
|
|
374
|
+
// ---------------------------------------------------------------------------------------------------------- object
|
|
375
|
+
function scopeOf(ont, id) {
|
|
376
|
+
const s = root(ont).scope;
|
|
377
|
+
if (!isObj(s))
|
|
378
|
+
return null;
|
|
379
|
+
if (arr(s.global).some((g) => isObj(g) && g.type === id))
|
|
380
|
+
return "global";
|
|
381
|
+
if (isObj(s.root) && s.root.type === id)
|
|
382
|
+
return "root";
|
|
383
|
+
return "tenant";
|
|
384
|
+
}
|
|
385
|
+
// The property reads of a condition, through derived properties it references.
|
|
386
|
+
function conditionProps(an, inp) {
|
|
387
|
+
return uniqSorted([...inp.props, ...inp.derived.flatMap((d) => [...an.closure(d).props])]);
|
|
388
|
+
}
|
|
389
|
+
export function queryObject(ont, id) {
|
|
390
|
+
const o = ownObj(objects(ont), id);
|
|
391
|
+
const outOfScope = o === undefined ? str(own(map(root(ont).outOfScopeObjectTypes), id)) : null;
|
|
392
|
+
if (o === undefined && outOfScope === null)
|
|
393
|
+
return null;
|
|
394
|
+
const prefix = `${id}.`;
|
|
395
|
+
const writers = [];
|
|
396
|
+
for (const [aid, a] of sortedActions(ont)) {
|
|
397
|
+
const edits = strs(a.edits).filter((e) => e.startsWith(prefix));
|
|
398
|
+
const creates = strs(a.creates).includes(id);
|
|
399
|
+
if (edits.length || creates)
|
|
400
|
+
writers.push({ action: aid, edits, creates });
|
|
401
|
+
}
|
|
402
|
+
if (o === undefined)
|
|
403
|
+
return { id, context: null, datasource: null, doc: null, outOfScope, scope: null, properties: [], derived: [], stateMachine: null, links: [], writers, readers: [], unanalyzed: [] };
|
|
404
|
+
const an = new Analysis(ont);
|
|
405
|
+
const sm = ownObj(map(root(ont).stateMachines), id);
|
|
406
|
+
const links = [];
|
|
407
|
+
for (const l of arr(root(ont).linkTypes)) {
|
|
408
|
+
if (!isObj(l) || typeof l.id !== "string")
|
|
409
|
+
continue;
|
|
410
|
+
if (l.from === id)
|
|
411
|
+
links.push({ id: l.id, end: "from", other: str(l.to), cardinality: str(l.cardinality), via: str(l.via) });
|
|
412
|
+
if (l.to === id)
|
|
413
|
+
links.push({ id: l.id, end: "to", other: str(l.from), cardinality: str(l.cardinality), via: str(l.via) });
|
|
414
|
+
}
|
|
415
|
+
const readers = [];
|
|
416
|
+
const unanalyzed = [];
|
|
417
|
+
for (const [aid, a] of sortedActions(ont))
|
|
418
|
+
for (const c of conditionsOf(a)) {
|
|
419
|
+
const inp = an.conditionInputs(aid, c);
|
|
420
|
+
if (inp.error !== undefined)
|
|
421
|
+
unanalyzed.push({ at: `${aid}:${c.id}`, error: inp.error });
|
|
422
|
+
const props = conditionProps(an, inp).filter((p) => p.startsWith(prefix));
|
|
423
|
+
if (props.length)
|
|
424
|
+
readers.push({ action: aid, condition: c.id, props });
|
|
425
|
+
}
|
|
426
|
+
unanalyzed.push(...derivedErrors(an, Object.keys(deriveds(ont)).filter((d) => isObj(own(deriveds(ont), d))).sort(sortStr)));
|
|
427
|
+
return {
|
|
428
|
+
id,
|
|
429
|
+
context: str(o.context),
|
|
430
|
+
datasource: str(o.datasource),
|
|
431
|
+
doc: str(o.doc),
|
|
432
|
+
outOfScope: null,
|
|
433
|
+
scope: scopeOf(ont, id),
|
|
434
|
+
properties: Object.entries(map(o.properties)).map(([name, p]) => {
|
|
435
|
+
const pr = map(p), cv = pr.canonical_values, mf = pr.materializedFrom;
|
|
436
|
+
return {
|
|
437
|
+
name,
|
|
438
|
+
class: str(pr.class),
|
|
439
|
+
type: typeOf(pr.type),
|
|
440
|
+
cite: citeOf(pr.cite),
|
|
441
|
+
canonicalValues: isObj(cv) ? { values: strs(cv.values), cite: citeOf(cv.cite) } : null,
|
|
442
|
+
materializedFrom: isObj(mf) ? { reads: strs(mf.reads), cites: citesOf(mf.cites) } : null,
|
|
443
|
+
};
|
|
444
|
+
}),
|
|
445
|
+
derived: Object.entries(deriveds(ont))
|
|
446
|
+
.filter(([, d]) => isObj(d) && d.of === id)
|
|
447
|
+
.map(([did, d]) => ({ id: did, type: typeOf(d.type), docTerm: str(d.doc_term), exprText: d.expr !== undefined ? showExpr(d.expr) : null, cite: citeOf(d.cite) })),
|
|
448
|
+
stateMachine: sm
|
|
449
|
+
? {
|
|
450
|
+
doc: str(sm.doc),
|
|
451
|
+
initial: str(sm.initial),
|
|
452
|
+
terminal: strs(sm.terminal),
|
|
453
|
+
transitions: arr(sm.transitions)
|
|
454
|
+
.filter(isObj)
|
|
455
|
+
.map((t) => ({ from: str(t.from), to: str(t.to), by: strs(t.by), outOfScope: str(t.out_of_scope) })),
|
|
456
|
+
}
|
|
457
|
+
: null,
|
|
458
|
+
links,
|
|
459
|
+
writers,
|
|
460
|
+
readers,
|
|
461
|
+
unanalyzed,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
// ---------------------------------------------------------------------------------------------------------- writes / reads
|
|
465
|
+
// "O.p": p is a property of O, or a derived property of O. null when neither is declared.
|
|
466
|
+
function target(ont, t) {
|
|
467
|
+
const parts = t.split(".");
|
|
468
|
+
if (parts.length !== 2)
|
|
469
|
+
return null;
|
|
470
|
+
const [obj, name] = parts;
|
|
471
|
+
const o = ownObj(objects(ont), obj);
|
|
472
|
+
if (o === undefined)
|
|
473
|
+
return null;
|
|
474
|
+
if (own(map(o.properties), name) !== undefined)
|
|
475
|
+
return { obj, name, kind: "property" };
|
|
476
|
+
if (ownObj(deriveds(ont), name)?.of === obj)
|
|
477
|
+
return { obj, name, kind: "derived" };
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const derivedNode = (ont, id) => `${str(ownObj(deriveds(ont), id)?.of) ?? "?"}.${id}`;
|
|
481
|
+
function derivedErrors(an, ids) {
|
|
482
|
+
const out = [];
|
|
483
|
+
for (const id of ids) {
|
|
484
|
+
const e = an.derivedInputs(id).error;
|
|
485
|
+
if (e !== undefined)
|
|
486
|
+
out.push({ at: `derivedProperties.${id}`, error: e });
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
export function queryWrites(ont, t) {
|
|
491
|
+
const tg = target(ont, t);
|
|
492
|
+
if (tg === null)
|
|
493
|
+
return null;
|
|
494
|
+
const acts = sortedActions(ont);
|
|
495
|
+
const writersOf = (prop, path) => acts.filter(([, a]) => strs(a.edits).includes(prop)).map(([aid, a]) => ({ action: aid, cite: citeOf(map(own(map(a.edit_cites), prop)).cite), path }));
|
|
496
|
+
if (tg.kind === "property")
|
|
497
|
+
return { target: t, kind: "property", unanalyzed: [], writers: writersOf(t, []), creators: acts.filter(([, a]) => strs(a.creates).includes(tg.obj)).map(([aid]) => aid) };
|
|
498
|
+
// Breadth first from the derived target: each input once, by its shortest path.
|
|
499
|
+
const an = new Analysis(ont);
|
|
500
|
+
const writers = [];
|
|
501
|
+
const seenProps = new Set(), seenDerived = new Set([tg.name]);
|
|
502
|
+
let frontier = [[tg.name, []]];
|
|
503
|
+
while (frontier.length) {
|
|
504
|
+
const next = [];
|
|
505
|
+
for (const [d, path] of frontier) {
|
|
506
|
+
const inp = an.derivedInputs(d);
|
|
507
|
+
for (const p of inp.props)
|
|
508
|
+
if (!seenProps.has(p)) {
|
|
509
|
+
seenProps.add(p);
|
|
510
|
+
writers.push(...writersOf(p, [...path, p]));
|
|
511
|
+
}
|
|
512
|
+
for (const x of inp.derived)
|
|
513
|
+
if (!seenDerived.has(x)) {
|
|
514
|
+
seenDerived.add(x);
|
|
515
|
+
next.push([x, [...path, derivedNode(ont, x)]]);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
frontier = next;
|
|
519
|
+
}
|
|
520
|
+
writers.sort((a, b) => a.path.length - b.path.length || sortStr(a.path.join(), b.path.join()) || sortStr(a.action, b.action));
|
|
521
|
+
return { target: t, kind: "derived", unanalyzed: derivedErrors(an, seenDerived), writers, creators: [] };
|
|
522
|
+
}
|
|
523
|
+
export function queryReads(ont, t) {
|
|
524
|
+
const tg = target(ont, t);
|
|
525
|
+
if (tg === null)
|
|
526
|
+
return null;
|
|
527
|
+
const an = new Analysis(ont);
|
|
528
|
+
const isDerived = tg.kind === "derived";
|
|
529
|
+
// Whether a derived property reaches the target through its chain.
|
|
530
|
+
const reaches = (d) => (isDerived ? an.closure(d).derived.has(tg.name) : an.closure(d).props.has(t));
|
|
531
|
+
const hit = (inp) => ({
|
|
532
|
+
direct: isDerived ? inp.derived.includes(tg.name) : inp.props.includes(t),
|
|
533
|
+
via: inp.derived.filter((d) => d !== tg.name && reaches(d)),
|
|
534
|
+
});
|
|
535
|
+
const unanalyzed = [];
|
|
536
|
+
const conditions = [];
|
|
537
|
+
for (const [aid, a] of sortedActions(ont)) {
|
|
538
|
+
const rows = arr(map(a.decision).rows);
|
|
539
|
+
for (const c of conditionsOf(a)) {
|
|
540
|
+
const inp = an.conditionInputs(aid, c);
|
|
541
|
+
if (inp.error !== undefined)
|
|
542
|
+
unanalyzed.push({ at: `${aid}:${c.id}`, error: inp.error });
|
|
543
|
+
const h = hit(inp);
|
|
544
|
+
if (!h.direct && h.via.length === 0)
|
|
545
|
+
continue;
|
|
546
|
+
const rowIdx = rows.flatMap((r, i) => (isObj(r) && whenId(r.when) === c.id ? [i] : []));
|
|
547
|
+
conditions.push({ action: aid, condition: c.id, source: inp.source, ...h, rows: rowIdx });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const derived = [];
|
|
551
|
+
const ids = Object.keys(deriveds(ont)).filter((id) => isObj(own(deriveds(ont), id))).sort(sortStr);
|
|
552
|
+
unanalyzed.push(...derivedErrors(an, ids));
|
|
553
|
+
for (const id of ids) {
|
|
554
|
+
if (isDerived && id === tg.name)
|
|
555
|
+
continue;
|
|
556
|
+
const h = hit(an.derivedInputs(id));
|
|
557
|
+
if (h.direct || h.via.length)
|
|
558
|
+
derived.push({ id, source: ownObj(deriveds(ont), id).expr !== undefined ? "expr" : "reads", ...h });
|
|
559
|
+
}
|
|
560
|
+
const materialized = [];
|
|
561
|
+
for (const [o, ob] of Object.entries(objects(ont)))
|
|
562
|
+
for (const [p, pr] of Object.entries(map(map(ob).properties)))
|
|
563
|
+
if (strs(map(map(pr).materializedFrom).reads).some((r) => r === t || (isDerived && r === tg.name)))
|
|
564
|
+
materialized.push(`${o}.${p}`);
|
|
565
|
+
return { target: t, kind: tg.kind, materialized: materialized.sort(sortStr), unanalyzed, conditions, derived };
|
|
566
|
+
}
|
|
567
|
+
// ---------------------------------------------------------------------------------------------------------- cites
|
|
568
|
+
const pointer = (segs) => segs.map((s) => "/" + s.replace(/~/g, "~0").replace(/\//g, "~1")).join("");
|
|
569
|
+
// The subject of a claim: first segment, second segment, and for a list (such as conditions) or a decision, the entry.
|
|
570
|
+
function subjectOf(ont, segs) {
|
|
571
|
+
const name = (container, seg) => {
|
|
572
|
+
if (!Array.isArray(container))
|
|
573
|
+
return `.${seg}`;
|
|
574
|
+
const el = container[Number(seg)];
|
|
575
|
+
if (isObj(el) && typeof el.id === "string")
|
|
576
|
+
return `.${el.id}`;
|
|
577
|
+
if (isObj(el) && typeof el.name === "string")
|
|
578
|
+
return `.${el.name}`;
|
|
579
|
+
return `[${seg}]`;
|
|
580
|
+
};
|
|
581
|
+
if (segs.length === 0)
|
|
582
|
+
return "";
|
|
583
|
+
const r = root(ont);
|
|
584
|
+
let out = segs[0];
|
|
585
|
+
let cur = r[segs[0]];
|
|
586
|
+
if (segs.length < 2)
|
|
587
|
+
return out;
|
|
588
|
+
out += name(cur, segs[1]);
|
|
589
|
+
cur = isObj(cur) || Array.isArray(cur) ? cur[segs[1]] : undefined;
|
|
590
|
+
if (segs.length < 3)
|
|
591
|
+
return out;
|
|
592
|
+
const third = isObj(cur) ? cur[segs[2]] : undefined;
|
|
593
|
+
if (Array.isArray(third) && segs.length >= 4)
|
|
594
|
+
return `${out}.${segs[2]}${name(third, segs[3])}`;
|
|
595
|
+
if (segs[2] === "decision" && segs.length >= 5 && segs[3] === "rows")
|
|
596
|
+
return `${out}.decision.rows[${segs[4]}]`;
|
|
597
|
+
if (segs[2] === "decision")
|
|
598
|
+
return segs.length >= 4 ? `${out}.decision.${segs[3]}` : `${out}.decision`;
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
// Every claim in document order: a mapping with string doc and quote (not entered), or a mapping with its own string doc
|
|
602
|
+
// (structural; listed before its children).
|
|
603
|
+
function claims(ont) {
|
|
604
|
+
const out = [];
|
|
605
|
+
const walk = (x, segs) => {
|
|
606
|
+
if (Array.isArray(x)) {
|
|
607
|
+
x.forEach((y, i) => walk(y, [...segs, String(i)]));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (!isObj(x))
|
|
611
|
+
return;
|
|
612
|
+
if (typeof x.doc === "string" && typeof x.quote === "string") {
|
|
613
|
+
out.push({ doc: x.doc, path: pointer(segs), subject: subjectOf(ont, segs), quote: x.quote });
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (typeof x.doc === "string" && segs.length > 0)
|
|
617
|
+
out.push({ doc: x.doc, path: pointer(segs), subject: subjectOf(ont, segs), quote: str(x.doc_term) });
|
|
618
|
+
for (const [k, y] of Object.entries(x))
|
|
619
|
+
walk(y, [...segs, k]);
|
|
620
|
+
};
|
|
621
|
+
walk(root(ont), []);
|
|
622
|
+
return out;
|
|
623
|
+
}
|
|
624
|
+
export function queryCites(ont, anchor) {
|
|
625
|
+
return { anchor, claims: claims(ont).filter((c) => c.doc === anchor).map(({ path, subject, quote }) => ({ path, subject, quote })) };
|
|
626
|
+
}
|
|
627
|
+
// ---------------------------------------------------------------------------------------------------------- list
|
|
628
|
+
export const QUERY_LIST_KINDS = ["actions", "objects", "links", "derived", "dispositions", "anchors", "gaps"];
|
|
629
|
+
export function queryList(ont, kind) {
|
|
630
|
+
const r = root(ont);
|
|
631
|
+
switch (kind) {
|
|
632
|
+
case "actions":
|
|
633
|
+
return {
|
|
634
|
+
kind,
|
|
635
|
+
items: Object.entries(actions(ont))
|
|
636
|
+
.filter(([, a]) => isObj(a))
|
|
637
|
+
.map(([id, a]) => {
|
|
638
|
+
const p = permissionOf(a.permission);
|
|
639
|
+
const keys = p.form === "keys" ? p.alternatives.flatMap((x) => [...x.keys, ...x.conditionalKeys]) : [];
|
|
640
|
+
return { id, context: str(a.context), doc: str(a.doc), docTerm: str(a.doc_term), permissionKeys: uniqSorted(keys) };
|
|
641
|
+
}),
|
|
642
|
+
};
|
|
643
|
+
case "objects":
|
|
644
|
+
return {
|
|
645
|
+
kind,
|
|
646
|
+
items: [
|
|
647
|
+
...Object.entries(objects(ont)).map(([id, o]) => ({ id, context: str(map(o).context), datasource: str(map(o).datasource), outOfScope: null })),
|
|
648
|
+
...Object.entries(map(r.outOfScopeObjectTypes)).map(([id, reason]) => ({ id, context: null, datasource: null, outOfScope: str(reason) })),
|
|
649
|
+
],
|
|
650
|
+
};
|
|
651
|
+
case "links":
|
|
652
|
+
return {
|
|
653
|
+
kind,
|
|
654
|
+
items: arr(r.linkTypes)
|
|
655
|
+
.filter((l) => isObj(l) && typeof l.id === "string")
|
|
656
|
+
.map((l) => ({ id: l.id, from: str(l.from), to: str(l.to), cardinality: str(l.cardinality), via: str(l.via), table: str(l.table) })),
|
|
657
|
+
};
|
|
658
|
+
case "derived":
|
|
659
|
+
return {
|
|
660
|
+
kind,
|
|
661
|
+
items: Object.entries(deriveds(ont)).map(([id, d]) => ({ id, of: str(map(d).of), type: typeOf(map(d).type), docTerm: str(map(d).doc_term), hasExpr: map(d).expr !== undefined })),
|
|
662
|
+
};
|
|
663
|
+
case "dispositions":
|
|
664
|
+
return { kind, items: Object.entries(map(r.dispositions)).map(([id, d]) => ({ id, class: str(map(d).class), cite: citeOf(map(d).cite) })) };
|
|
665
|
+
case "anchors": {
|
|
666
|
+
const counts = new Map();
|
|
667
|
+
for (const c of claims(ont))
|
|
668
|
+
counts.set(c.doc, (counts.get(c.doc) ?? 0) + 1);
|
|
669
|
+
return { kind, items: [...counts.keys()].sort(sortStr).map((doc) => ({ doc, claims: counts.get(doc) })) };
|
|
670
|
+
}
|
|
671
|
+
case "gaps":
|
|
672
|
+
return {
|
|
673
|
+
kind,
|
|
674
|
+
items: arr(r.knownSourceGaps)
|
|
675
|
+
.filter(isObj)
|
|
676
|
+
.map((g) => ({ id: str(g.id), rule: str(g.rule), kind: str(g.kind), keys: strs(g.keys), conflict: str(g.conflict), proposed: str(g.proposed) })),
|
|
677
|
+
};
|
|
678
|
+
default:
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
}
|