@polycode-projects/the-mechanical-code-talker 2.5.0 → 2.5.3
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 +15 -11
- package/bin/tmct.mjs +8 -5
- package/corpus/LICENSES.json +7 -0
- package/corpus/README.md +3 -3
- package/corpus/reference/LICENSE-NOTICE +36 -0
- package/corpus/reference/README.md +19 -0
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +367 -0
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-01.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-02.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-03.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-04.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-05.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-06.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-07.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-08.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-09.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-0a.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-0b.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-0c.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-0d.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-0e.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-0f.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-10.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-11.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-12.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-13.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-14.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-15.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-16.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-17.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-18.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-19.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-1a.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-1b.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-1c.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-1d.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-1e.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-1f.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-20.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-21.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-22.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-23.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-24.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-25.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-26.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-27.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-28.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-29.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-2a.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-2b.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-2c.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-2d.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-2e.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-2f.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-30.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-31.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-32.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-33.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-34.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-35.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-36.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-37.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-38.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-39.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-3a.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-3b.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-3c.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-3d.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-3e.jsonl.gz +0 -0
- package/corpus/reference/shards/ref-3f.jsonl.gz +0 -0
- package/package.json +8 -4
- package/src/adapters/corpus/reference-pack.mjs +107 -0
- package/src/adapters/memory/core.mjs +104 -10
- package/src/domain/ask.mjs +66 -9
- package/src/domain/cli-verbs.mjs +3 -3
- package/src/domain/codegraph.mjs +6 -0
- package/src/domain/dialogue-acts.mjs +155 -0
- package/src/domain/grammar/lexicon-core.json +1 -1
- package/src/domain/interpret/normalize.mjs +14 -2
- package/src/domain/interpret/strategies/keywords.mjs +10 -1
- package/src/domain/memory/trust.mjs +12 -0
- package/src/domain/reference-pack.mjs +72 -0
- package/src/domain/skos-view.mjs +111 -0
- package/src/domain/syllogise.mjs +647 -56
- package/src/services/chat.mjs +499 -123
- package/src/services/init.mjs +2 -2
- package/src/surfaces/web/chat-browser-entry.mjs +87 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +865 -74
- package/src/tools/definitions.mjs +13 -0
- package/src/tools/handlers/index.mjs +2 -0
- package/src/tools/handlers/tmct-related.mjs +30 -0
- package/ROADMAP.md +0 -130
package/src/domain/syllogise.mjs
CHANGED
|
@@ -72,6 +72,11 @@ export const ENTAILED_SVF1_PROVENANCE = `entailed:${CLS_SVF1_RULE}`;
|
|
|
72
72
|
/** Same sub-1 discount as CAX_DW_RULE_CONFIDENCE, same reason. */
|
|
73
73
|
export const CLS_SVF1_RULE_CONFIDENCE = 0.95;
|
|
74
74
|
|
|
75
|
+
/** The environment-set cap: how many independent premise sets one entailed
|
|
76
|
+
* fact may carry as ' | '-separated mgx:factJustification environments — the
|
|
77
|
+
* bounded-ATMS knob alongside depth/budget/focus. */
|
|
78
|
+
export const DEFAULT_MAX_ENVIRONMENTS = 4;
|
|
79
|
+
|
|
75
80
|
const SEP = "␟"; // an in-key separator no fact term can contain
|
|
76
81
|
const isSubClassOf = (p) => String(p || "").trim().toLowerCase() === "rdfs:subclassof";
|
|
77
82
|
const isType = (p) => String(p || "").trim().toLowerCase() === "rdf:type";
|
|
@@ -166,6 +171,79 @@ export function deriveSubClassClosure(edges, { depth = 32, budget = 50, focus =
|
|
|
166
171
|
return derived;
|
|
167
172
|
}
|
|
168
173
|
|
|
174
|
+
/**
|
|
175
|
+
* PURE semi-naive subClassOf closure: the delta twin of
|
|
176
|
+
* `deriveSubClassClosure`, same contract exactly (only-new `{ subject,
|
|
177
|
+
* object, via }` conclusions, tautology/dedup/focus screens, the same
|
|
178
|
+
* per-round candidate sort, identical `budget`/`depth` semantics). Instead of
|
|
179
|
+
* re-joining the whole relation each round, round one joins only
|
|
180
|
+
* `deltaEdges` against the full relation (Δ∘R and R∘Δ — Δ∘Δ falls out of Δ∘R
|
|
181
|
+
* because `allEdges` contains the delta rows too), and each later round joins
|
|
182
|
+
* only what the previous round committed. When the non-delta part of
|
|
183
|
+
* `allEdges` is already closed (a prior pass ran to fixpoint), the output
|
|
184
|
+
* equals the full kernel's novel output, order included.
|
|
185
|
+
*/
|
|
186
|
+
export function deriveSubClassClosureDelta(allEdges, deltaEdges, { depth = 32, budget = 50, focus = null } = {}) {
|
|
187
|
+
const present = new Set(); // "a\0b" for every edge already known
|
|
188
|
+
const succ = new Map(); // a -> Set(b): the live successor relation
|
|
189
|
+
const pred = new Map(); // b -> Set(a): its inverse, for the R∘Δ join
|
|
190
|
+
for (const [a, b] of allEdges || []) {
|
|
191
|
+
if (!a || !b || a === b) continue;
|
|
192
|
+
present.add(`${a}${SEP}${b}`);
|
|
193
|
+
if (!succ.has(a)) succ.set(a, new Set());
|
|
194
|
+
succ.get(a).add(b);
|
|
195
|
+
if (!pred.has(b)) pred.set(b, new Set());
|
|
196
|
+
pred.get(b).add(a);
|
|
197
|
+
}
|
|
198
|
+
let delta = [];
|
|
199
|
+
const seenDelta = new Set();
|
|
200
|
+
for (const [a, b] of deltaEdges || []) {
|
|
201
|
+
if (!a || !b || a === b) continue;
|
|
202
|
+
const key = `${a}${SEP}${b}`;
|
|
203
|
+
if (seenDelta.has(key)) continue;
|
|
204
|
+
seenDelta.add(key);
|
|
205
|
+
delta.push([a, b]);
|
|
206
|
+
}
|
|
207
|
+
const focusSet = focus instanceof Set ? (focus.size ? focus : null) : normalizeFocus(focus);
|
|
208
|
+
const inFocus = (a, b, c) => !focusSet || focusSet.has(a) || focusSet.has(b) || focusSet.has(c);
|
|
209
|
+
|
|
210
|
+
const derived = [];
|
|
211
|
+
const derivedKeys = new Set();
|
|
212
|
+
for (let round = 0; round < depth && delta.length; round += 1) {
|
|
213
|
+
const additions = [];
|
|
214
|
+
const consider = (a, b, c) => {
|
|
215
|
+
if (a === c) return; // tautology screen (reflexive)
|
|
216
|
+
const key = `${a}${SEP}${c}`;
|
|
217
|
+
if (present.has(key) || derivedKeys.has(key)) return; // dedup / novelty screen
|
|
218
|
+
if (!inFocus(a, b, c)) return; // focus-connection screen
|
|
219
|
+
additions.push([a, b, c, key]);
|
|
220
|
+
};
|
|
221
|
+
for (const [a, b] of delta) {
|
|
222
|
+
for (const c of succ.get(b) || []) consider(a, b, c); // Δ∘R
|
|
223
|
+
for (const z of pred.get(a) || []) consider(z, a, b); // R∘Δ
|
|
224
|
+
}
|
|
225
|
+
if (!additions.length) break; // fixpoint reached
|
|
226
|
+
additions.sort((x, y) => x[0].localeCompare(y[0]) || x[2].localeCompare(y[2]) || x[1].localeCompare(y[1]));
|
|
227
|
+
let progressed = false;
|
|
228
|
+
const nextDelta = [];
|
|
229
|
+
for (const [a, b, c, key] of additions) {
|
|
230
|
+
if (derivedKeys.has(key)) continue; // an earlier addition this round covered it
|
|
231
|
+
if (derived.length >= budget) break;
|
|
232
|
+
derivedKeys.add(key);
|
|
233
|
+
derived.push({ subject: a, object: c, via: b });
|
|
234
|
+
if (!succ.has(a)) succ.set(a, new Set());
|
|
235
|
+
succ.get(a).add(c);
|
|
236
|
+
if (!pred.has(c)) pred.set(c, new Set());
|
|
237
|
+
pred.get(c).add(a);
|
|
238
|
+
nextDelta.push([a, c]);
|
|
239
|
+
progressed = true;
|
|
240
|
+
}
|
|
241
|
+
if (derived.length >= budget || !progressed) break; // budget hit or nothing committed
|
|
242
|
+
delta = nextDelta;
|
|
243
|
+
}
|
|
244
|
+
return derived;
|
|
245
|
+
}
|
|
246
|
+
|
|
169
247
|
/**
|
|
170
248
|
* Shared closure machinery: given `subClassEdges` ([[a,b], …], already-
|
|
171
249
|
* normalized), returns a memoized `ancestorsOf(c)` that walks the FULL
|
|
@@ -200,6 +278,52 @@ function buildAncestorCloser(subClassEdges) {
|
|
|
200
278
|
};
|
|
201
279
|
}
|
|
202
280
|
|
|
281
|
+
/** `buildAncestorCloser` with the edges reversed: a memoized
|
|
282
|
+
* `descendantsOf(c)` walking every SUBclass reachable below `c`. */
|
|
283
|
+
function buildDescendantCloser(subClassEdges) {
|
|
284
|
+
return buildAncestorCloser((subClassEdges || []).map(([a, b]) => [b, a]));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* PURE relevance frontier: the set of terms a change touching `seedTerms`
|
|
289
|
+
* can actually affect, over the fact `rows` given. Seeds (normalized) plus
|
|
290
|
+
* the ⊑-descendant closure of each seed (a new edge above a class affects
|
|
291
|
+
* everything below it), plus every instance carrying a stored type in that
|
|
292
|
+
* affected-class set, plus every declared someValuesFrom restriction node
|
|
293
|
+
* whose target filler class sits in it. Over-approximation is harmless — the
|
|
294
|
+
* kernels' own dedup screens drop anything already known — while a term the
|
|
295
|
+
* frontier misses simply waits for the next full pass. The same structure
|
|
296
|
+
* serves forward relevance (a delta pass's focus) that `retractSubClassOf`'s
|
|
297
|
+
* citedBy index serves backward.
|
|
298
|
+
*/
|
|
299
|
+
export function buildRelevanceFrontier(rows, seedTerms) {
|
|
300
|
+
const subClassEdges = [];
|
|
301
|
+
const typeEdges = [];
|
|
302
|
+
const onPropertyOf = new Map(); // restriction -> owl:onProperty's object
|
|
303
|
+
const someValuesFromOf = new Map(); // restriction -> owl:someValuesFrom's object
|
|
304
|
+
for (const r of rows || []) {
|
|
305
|
+
if (!r || !r.subject || !r.predicate || !r.object) continue;
|
|
306
|
+
if (isSubClassOf(r.predicate)) subClassEdges.push([r.subject, r.object]);
|
|
307
|
+
else if (isType(r.predicate)) typeEdges.push([r.subject, r.object]);
|
|
308
|
+
else if (isOnProperty(r.predicate)) onPropertyOf.set(r.subject, r.object);
|
|
309
|
+
else if (isSomeValuesFrom(r.predicate)) someValuesFromOf.set(r.subject, r.object);
|
|
310
|
+
}
|
|
311
|
+
const descendantsOf = buildDescendantCloser(subClassEdges);
|
|
312
|
+
const affectedClasses = new Set();
|
|
313
|
+
for (const t of seedTerms || []) {
|
|
314
|
+
const n = normFactTerm(t);
|
|
315
|
+
if (!n) continue;
|
|
316
|
+
affectedClasses.add(n);
|
|
317
|
+
for (const d of descendantsOf(n)) affectedClasses.add(d);
|
|
318
|
+
}
|
|
319
|
+
const frontier = new Set(affectedClasses);
|
|
320
|
+
for (const [x, c] of typeEdges) if (affectedClasses.has(c)) frontier.add(x);
|
|
321
|
+
for (const [restriction, target] of someValuesFromOf) {
|
|
322
|
+
if (onPropertyOf.has(restriction) && affectedClasses.has(target)) frontier.add(restriction);
|
|
323
|
+
}
|
|
324
|
+
return frontier;
|
|
325
|
+
}
|
|
326
|
+
|
|
203
327
|
/**
|
|
204
328
|
* PURE cax-sco: rdf:type propagation across a subClassOf chain — (x rdf:type
|
|
205
329
|
* C), (C ⊑ … ⊑ D) ⊨ (x rdf:type D). `subClassEdges` is a fixed input (unlike
|
|
@@ -207,13 +331,18 @@ function buildAncestorCloser(subClassEdges) {
|
|
|
207
331
|
* class (`buildAncestorCloser`) covers the whole chain — no fixpoint rounds
|
|
208
332
|
* needed. Returns ONLY new `{ subject, object, via }` conclusions, bounded by
|
|
209
333
|
* `budget`, focus-filtered, tautology- and dedup-screened, deterministic order.
|
|
334
|
+
*
|
|
335
|
+
* `presentTypeEdges` (defaults to `typeEdges`) feeds ONLY the novelty screen:
|
|
336
|
+
* a delta caller that pre-filters `typeEdges` to the relevant slice passes the
|
|
337
|
+
* FULL list here, so an already-stored conclusion outside the slice is still
|
|
338
|
+
* recognized as known rather than re-derived.
|
|
210
339
|
*/
|
|
211
|
-
export function deriveTypePropagation(typeEdges, subClassEdges, { budget = 50, focus = null } = {}) {
|
|
340
|
+
export function deriveTypePropagation(typeEdges, subClassEdges, { budget = 50, focus = null, presentTypeEdges = typeEdges } = {}) {
|
|
212
341
|
const ancestorsOf = buildAncestorCloser(subClassEdges);
|
|
213
342
|
|
|
214
343
|
const present = new Set(); // "x\0C" for every rdf:type edge already known
|
|
215
344
|
const seenTypeEdge = new Set(); // dedup repeated (x,C) input rows
|
|
216
|
-
for (const [x, c] of
|
|
345
|
+
for (const [x, c] of presentTypeEdges || []) if (x && c) present.add(`${x}${SEP}${c}`);
|
|
217
346
|
const focusSet = focus instanceof Set ? (focus.size ? focus : null) : normalizeFocus(focus);
|
|
218
347
|
const inFocus = (x, c, d) => !focusSet || focusSet.has(x) || focusSet.has(c) || focusSet.has(d);
|
|
219
348
|
|
|
@@ -664,18 +793,47 @@ export function findConsistencyViolations(typeEdges, subClassEdges, disjointEdge
|
|
|
664
793
|
* (`min(premiseTrusts) x ruleConfidence`) when premises are resolvable in the
|
|
665
794
|
* pre-pass snapshot, else falls back to the bare entailed prior.
|
|
666
795
|
*
|
|
796
|
+
* After the kernels, an ALTERNATE-DISCOVERY step (bounded by the same budget
|
|
797
|
+
* number, spent separately) enumerates additional premise environments for
|
|
798
|
+
* this pass's conclusions and for stored purely-entailed facts still under
|
|
799
|
+
* the `maxEnvironments` cap, so a fact's justification accretes every
|
|
800
|
+
* independent derivation route retraction can later check by set membership.
|
|
801
|
+
*
|
|
802
|
+
* SEMI-NAIVE DELTA MODE: when the store carries a watermark from the last
|
|
803
|
+
* complete pass (the optional `loadSyllogiseState`/`saveSyllogiseState` store
|
|
804
|
+
* members), no caller focus is given, no fact was removed since, and `full`
|
|
805
|
+
* was not forced, the pass runs delta evaluation — scm-sco joins only the
|
|
806
|
+
* since-watermark rows (`deriveSubClassClosureDelta`), and the four later
|
|
807
|
+
* kernels are scoped by a relevance frontier built from the delta
|
|
808
|
+
* (`buildRelevanceFrontier`) plus per-kernel input pre-filters. Conclusions
|
|
809
|
+
* are identical to a full pass (the dedup screens make over-approximation
|
|
810
|
+
* harmless); what shrinks is candidate generation and the joins — the pass
|
|
811
|
+
* still pays the store snapshot read. The watermark advances ONLY after an
|
|
812
|
+
* unfocused pass that ends at a natural fixpoint.
|
|
813
|
+
*
|
|
667
814
|
* opts: `depth` (max fixpoint rounds, default 32), `budget` (max new
|
|
668
815
|
* derivations this pass, shared across all five rules, default 50), `focus`
|
|
669
816
|
* (Set|array of class terms scoping derivations to what touches it — omit
|
|
670
|
-
* for a whole-graph pass), `
|
|
817
|
+
* for a whole-graph pass), `expandFocus` (default false: when true, a caller
|
|
818
|
+
* focus expands through `buildRelevanceFrontier` before the kernels run, so
|
|
819
|
+
* a derivation among the focus terms' descendants and instances is in scope
|
|
820
|
+
* even when none of its own three terms was named), `maxEnvironments`
|
|
821
|
+
* (per-fact environment cap,
|
|
822
|
+
* default DEFAULT_MAX_ENVIRONMENTS), `full` (force full evaluation even with
|
|
823
|
+
* a valid watermark), `store` (REQUIRED — the memory store's
|
|
671
824
|
* { loadMemory, readFactRows, appendFacts } read/write functions, injected so
|
|
672
|
-
* this inference module never imports the store itself
|
|
825
|
+
* this inference module never imports the store itself; optional
|
|
826
|
+
* loadSyllogiseState/saveSyllogiseState enable delta mode).
|
|
673
827
|
*
|
|
674
828
|
* Returns { derived: [{ id, subject, object, via, rule }], count, budget,
|
|
675
|
-
* depth, truncated }.
|
|
829
|
+
* depth, truncated, mode, deltaSize, environmentsAdded, alternatesTruncated }.
|
|
676
830
|
*/
|
|
677
|
-
export async function syllogise(repoDir, {
|
|
831
|
+
export async function syllogise(repoDir, {
|
|
832
|
+
depth = 32, budget = 50, focus = null, expandFocus = false,
|
|
833
|
+
maxEnvironments = DEFAULT_MAX_ENVIRONMENTS, full = false, store,
|
|
834
|
+
} = {}) {
|
|
678
835
|
const { loadMemory, readFactRows, appendFacts } = requireStore(store, ["loadMemory", "readFactRows", "appendFacts"], "syllogise");
|
|
836
|
+
const stateFnsPresent = typeof store?.loadSyllogiseState === "function" && typeof store?.saveSyllogiseState === "function";
|
|
679
837
|
const memory = await loadMemory(repoDir);
|
|
680
838
|
const rows = readFactRows(memory);
|
|
681
839
|
const subClassEdges = rows.filter((r) => isSubClassOf(r.predicate)).map((r) => [r.subject, r.object]);
|
|
@@ -699,7 +857,25 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
699
857
|
const target = someValuesFromOf.get(restriction);
|
|
700
858
|
if (target) restrictionEdges.push({ restriction, property, target });
|
|
701
859
|
}
|
|
702
|
-
const
|
|
860
|
+
const callerFocus = normalizeFocus(focus);
|
|
861
|
+
// An expanded focus is the same relevance walk a delta pass runs, seeded by
|
|
862
|
+
// the caller's terms instead of a change set — still a focus (never reads
|
|
863
|
+
// or advances the watermark), just one that reaches the descendants,
|
|
864
|
+
// instances and restrictions the named terms actually govern.
|
|
865
|
+
const normalizedFocus = expandFocus && callerFocus
|
|
866
|
+
? buildRelevanceFrontier(rows, [...callerFocus])
|
|
867
|
+
: callerFocus;
|
|
868
|
+
|
|
869
|
+
// Mode: delta only with a valid watermark (state present, nothing removed
|
|
870
|
+
// since — an id-set diff catches retractions, snapshots and hand-edits),
|
|
871
|
+
// no caller focus, and no forced full. Anything else is a full pass.
|
|
872
|
+
const state = normalizedFocus === null && stateFnsPresent ? await store.loadSyllogiseState(repoDir) : null;
|
|
873
|
+
const currentIdSet = new Set(rows.map((r) => r.id));
|
|
874
|
+
const removedSinceLast = Array.isArray(state?.factIds) ? state.factIds.filter((id) => !currentIdSet.has(id)) : [];
|
|
875
|
+
const mode = state && Array.isArray(state.factIds) && !removedSinceLast.length && !full ? "delta" : "full";
|
|
876
|
+
const watermark = mode === "delta" ? new Set(state.factIds) : null;
|
|
877
|
+
const deltaRows = mode === "delta" ? rows.filter((r) => !watermark.has(r.id)) : rows;
|
|
878
|
+
const deltaSize = deltaRows.length;
|
|
703
879
|
|
|
704
880
|
// Pre-pass trust snapshot for the entailed hook's premiseTrusts lookup,
|
|
705
881
|
// wired for cax-dw/cls-svf1/scm-svf1 only: with ruleConfidence defaulting
|
|
@@ -713,33 +889,93 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
713
889
|
const hasTriple = (s, p, o) => trustByTriple.has(`${s}${SEP}${p}${SEP}${o}`);
|
|
714
890
|
const numericOnly = (arr) => arr.filter((t) => typeof t === "number");
|
|
715
891
|
|
|
716
|
-
|
|
892
|
+
// An empty delta derives nothing by construction — and an empty frontier
|
|
893
|
+
// must never be handed to the kernels as focus, because an empty focus Set
|
|
894
|
+
// means "whole graph" to normalizeFocus. Skipping the kernels outright is
|
|
895
|
+
// both the honest and the cheap reading.
|
|
896
|
+
const deltaEmpty = mode === "delta" && !deltaRows.length;
|
|
897
|
+
const deltaSubEdges = mode === "delta"
|
|
898
|
+
? deltaRows.filter((r) => isSubClassOf(r.predicate)).map((r) => [r.subject, r.object])
|
|
899
|
+
: [];
|
|
900
|
+
const scmDerived = mode === "delta"
|
|
901
|
+
? (deltaSubEdges.length ? deriveSubClassClosureDelta(subClassEdges, deltaSubEdges, { depth, budget, focus: normalizedFocus }) : [])
|
|
902
|
+
: deriveSubClassClosure(subClassEdges, { depth, budget, focus: normalizedFocus });
|
|
717
903
|
// cax-sco sees the ENLARGED subClassOf edge set (stated ∪ this pass's own
|
|
718
904
|
// scm-sco conclusions) so both rules complete in one `tmct syllogise` call.
|
|
719
905
|
const enlargedSubClassEdges = subClassEdges.concat(scmDerived.map((d) => [d.subject, d.object]));
|
|
906
|
+
|
|
907
|
+
// Delta mode scopes the four later kernels by the relevance frontier: built
|
|
908
|
+
// AFTER scm-sco over the enlarged edge set, seeded by every term of every
|
|
909
|
+
// delta row, then applied as their focus plus per-kernel input pre-filters.
|
|
910
|
+
let kernelFocus = normalizedFocus;
|
|
911
|
+
let frontier = null;
|
|
912
|
+
if (mode === "delta" && !deltaEmpty) {
|
|
913
|
+
const frontierRows = rows.concat(scmDerived.map((d) => ({ subject: d.subject, predicate: SUBCLASS_PREDICATE, object: d.object })));
|
|
914
|
+
const seeds = [];
|
|
915
|
+
for (const r of deltaRows) seeds.push(r.subject, r.object);
|
|
916
|
+
frontier = buildRelevanceFrontier(frontierRows, seeds);
|
|
917
|
+
kernelFocus = frontier;
|
|
918
|
+
}
|
|
919
|
+
const inFrontier = (t) => !frontier || frontier.has(t);
|
|
920
|
+
// cax-sco's inputs narrow to the frontier's type edges, but its NOVELTY set
|
|
921
|
+
// must still see every stored type edge (presentTypeEdges) — otherwise the
|
|
922
|
+
// filtered call re-derives stored conclusions and idempotency breaks.
|
|
923
|
+
const caxTypeEdges = frontier ? typeEdges.filter(([x, c]) => inFrontier(x) || inFrontier(c)) : typeEdges;
|
|
924
|
+
// cax-dw additionally keeps any type whose ⊑-ancestry reaches an endpoint
|
|
925
|
+
// of a delta disjointWith row — a new disjointness above an old type is
|
|
926
|
+
// invisible to the frontier's descendant walk.
|
|
927
|
+
const deltaDwEndpoints = new Set();
|
|
928
|
+
if (frontier) {
|
|
929
|
+
for (const r of deltaRows) {
|
|
930
|
+
if (isDisjoint(r.predicate)) { deltaDwEndpoints.add(r.subject); deltaDwEndpoints.add(r.object); }
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const dwAncestorsOf = frontier && deltaDwEndpoints.size ? buildAncestorCloser(enlargedSubClassEdges) : null;
|
|
934
|
+
const dwTypeEdges = frontier
|
|
935
|
+
? typeEdges.filter(([x, c]) => inFrontier(x) || inFrontier(c)
|
|
936
|
+
|| (dwAncestorsOf && [...dwAncestorsOf(c)].some((a) => deltaDwEndpoints.has(a))))
|
|
937
|
+
: typeEdges;
|
|
938
|
+
// cls-svf1's property edges narrow to the frontier — plus every edge over a
|
|
939
|
+
// property whose restriction declaration is itself in the delta, so a new
|
|
940
|
+
// restriction reaches old edges; its type edges stay FULL (they feed the
|
|
941
|
+
// novelty screen and the filler-type join).
|
|
942
|
+
const deltaRestrictionProperties = new Set();
|
|
943
|
+
if (frontier) {
|
|
944
|
+
for (const r of deltaRows) {
|
|
945
|
+
if (isOnProperty(r.predicate) || isSomeValuesFrom(r.predicate)) {
|
|
946
|
+
const property = onPropertyOf.get(r.subject);
|
|
947
|
+
if (property) deltaRestrictionProperties.add(normFactTerm(property));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const svf1PropertyEdges = frontier
|
|
952
|
+
? propertyEdges.filter(([x, p, y]) => inFrontier(x) || inFrontier(y) || deltaRestrictionProperties.has(normFactTerm(p)))
|
|
953
|
+
: propertyEdges;
|
|
954
|
+
|
|
720
955
|
const remainingBudget = Math.max(0, budget - scmDerived.length);
|
|
721
|
-
const caxDerived = remainingBudget > 0
|
|
722
|
-
? deriveTypePropagation(
|
|
956
|
+
const caxDerived = remainingBudget > 0 && !deltaEmpty
|
|
957
|
+
? deriveTypePropagation(caxTypeEdges, enlargedSubClassEdges, { budget: remainingBudget, focus: kernelFocus, presentTypeEdges: typeEdges })
|
|
723
958
|
: [];
|
|
724
959
|
// cax-dw sees the SAME enlarged subClassOf set (so its own ⊑-lift reaches a
|
|
725
960
|
// chain scm-sco just grew this pass) — it doesn't need the enlarged TYPE
|
|
726
961
|
// edge set too, since it walks each direct type's own ⊑-ancestor closure.
|
|
727
962
|
const remainingBudgetDw = Math.max(0, budget - scmDerived.length - caxDerived.length);
|
|
728
|
-
const dwDerived = remainingBudgetDw > 0
|
|
729
|
-
? deriveDisjointViolations(
|
|
963
|
+
const dwDerived = remainingBudgetDw > 0 && !deltaEmpty
|
|
964
|
+
? deriveDisjointViolations(dwTypeEdges, enlargedSubClassEdges, disjointEdges, { budget: remainingBudgetDw, focus: kernelFocus })
|
|
730
965
|
: [];
|
|
731
966
|
// cls-svf1 sees the SAME enlarged subClassOf set (its own ⊑-lift) but NOT
|
|
732
967
|
// the enlarged type edge set, so a same-pass cax-sco conclusion on `y`
|
|
733
968
|
// can't be consumed before a human can audit it.
|
|
734
969
|
const remainingBudgetSvf1 = Math.max(0, budget - scmDerived.length - caxDerived.length - dwDerived.length);
|
|
735
|
-
const svf1Derived = remainingBudgetSvf1 > 0 && restrictionEdges.length
|
|
736
|
-
? deriveSomeValuesFromApplication(
|
|
970
|
+
const svf1Derived = remainingBudgetSvf1 > 0 && restrictionEdges.length && !deltaEmpty
|
|
971
|
+
? deriveSomeValuesFromApplication(svf1PropertyEdges, typeEdges, enlargedSubClassEdges, restrictionEdges, { budget: remainingBudgetSvf1, focus: kernelFocus })
|
|
737
972
|
: [];
|
|
738
973
|
// scm-svf1 reuses the SAME restrictionEdges built for cls-svf1 above —
|
|
739
|
-
// needs at least two restrictions over one property to compare.
|
|
974
|
+
// needs at least two restrictions over one property to compare. In delta
|
|
975
|
+
// mode the frontier scopes it as focus only, no input pre-filter.
|
|
740
976
|
const remainingBudgetScmSvf = Math.max(0, budget - scmDerived.length - caxDerived.length - dwDerived.length - svf1Derived.length);
|
|
741
|
-
const scmSvfDerived = remainingBudgetScmSvf > 0 && restrictionEdges.length > 1
|
|
742
|
-
? deriveSomeValuesFromSubsumption(restrictionEdges, enlargedSubClassEdges, { budget: remainingBudgetScmSvf, focus:
|
|
977
|
+
const scmSvfDerived = remainingBudgetScmSvf > 0 && restrictionEdges.length > 1 && !deltaEmpty
|
|
978
|
+
? deriveSomeValuesFromSubsumption(restrictionEdges, enlargedSubClassEdges, { budget: remainingBudgetScmSvf, focus: kernelFocus })
|
|
743
979
|
: [];
|
|
744
980
|
const restrictionByRid = new Map(restrictionEdges.map((r) => [r.restriction, r]));
|
|
745
981
|
|
|
@@ -749,16 +985,18 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
749
985
|
...scmDerived.map((d) => ({
|
|
750
986
|
subject: d.subject, predicate: SUBCLASS_PREDICATE, object: d.object,
|
|
751
987
|
provenance: ENTAILED_PROVENANCE,
|
|
752
|
-
// Persisted justification:
|
|
753
|
-
//
|
|
754
|
-
//
|
|
988
|
+
// Persisted justification: one environment per independent derivation,
|
|
989
|
+
// each an ordered premise fact-id list — this first one is the premise
|
|
990
|
+
// set the conclusion rode (a⊑b, b⊑c); the alternate-discovery step
|
|
991
|
+
// below may append more. Content-addressed ids work even when a premise
|
|
992
|
+
// is itself an entailment this same pass just derived. Read back by
|
|
755
993
|
// retractSubClassOf (below) to find every entailment a retracted
|
|
756
994
|
// premise could have supported. All five rules persist one, each
|
|
757
995
|
// citing its own premise shape.
|
|
758
|
-
justification: [
|
|
996
|
+
justification: [[
|
|
759
997
|
factIdForTriple(d.subject, SUBCLASS_PREDICATE, d.via),
|
|
760
998
|
factIdForTriple(d.via, SUBCLASS_PREDICATE, d.object),
|
|
761
|
-
],
|
|
999
|
+
]],
|
|
762
1000
|
})),
|
|
763
1001
|
...caxDerived.map((d) => ({
|
|
764
1002
|
subject: d.subject, predicate: TYPE_PREDICATE, object: d.object,
|
|
@@ -767,10 +1005,10 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
767
1005
|
// taught chain is multi-hop: scm-sco materialises that edge (this same
|
|
768
1006
|
// pass or an earlier one), and retraction re-VERIFIES every candidate
|
|
769
1007
|
// anyway, so a citation left dangling by budget truncation is inert.
|
|
770
|
-
justification: [
|
|
1008
|
+
justification: [[
|
|
771
1009
|
factIdForTriple(d.subject, TYPE_PREDICATE, d.via),
|
|
772
1010
|
factIdForTriple(d.via, SUBCLASS_PREDICATE, d.object),
|
|
773
|
-
],
|
|
1011
|
+
]],
|
|
774
1012
|
})),
|
|
775
1013
|
...dwDerived.map((d) => {
|
|
776
1014
|
// disjointWith is symmetric, taught as ONE direction — the premise row
|
|
@@ -788,11 +1026,11 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
788
1026
|
return {
|
|
789
1027
|
subject: d.subject, predicate: DISJOINT_PREDICATE, object: d.object,
|
|
790
1028
|
provenance: ENTAILED_DISJOINT_PROVENANCE,
|
|
791
|
-
justification: [
|
|
1029
|
+
justification: [[
|
|
792
1030
|
factIdForTriple(d.subject, TYPE_PREDICATE, d.viaType),
|
|
793
1031
|
factIdForTriple(dwS, DISJOINT_PREDICATE, dwO),
|
|
794
1032
|
...(d.viaClass !== d.viaType ? [factIdForTriple(d.viaType, SUBCLASS_PREDICATE, d.viaClass)] : []),
|
|
795
|
-
],
|
|
1033
|
+
]],
|
|
796
1034
|
...(premiseTrusts.length ? { premiseTrusts, ruleConfidence: CAX_DW_RULE_CONFIDENCE } : {}),
|
|
797
1035
|
};
|
|
798
1036
|
}),
|
|
@@ -809,13 +1047,13 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
809
1047
|
return {
|
|
810
1048
|
subject: d.subject, predicate: TYPE_PREDICATE, object: d.object,
|
|
811
1049
|
provenance: ENTAILED_SVF1_PROVENANCE,
|
|
812
|
-
justification: [
|
|
1050
|
+
justification: [[
|
|
813
1051
|
factIdForTriple(d.subject, d.viaProperty, d.viaValue),
|
|
814
1052
|
factIdForTriple(d.viaValue, TYPE_PREDICATE, d.viaType),
|
|
815
1053
|
factIdForTriple(d.object, ON_PROPERTY_PREDICATE, d.viaPropertyKey),
|
|
816
1054
|
factIdForTriple(d.object, SOME_VALUES_FROM_PREDICATE, d.viaTarget),
|
|
817
1055
|
...(d.viaType !== d.viaTarget ? [factIdForTriple(d.viaType, SUBCLASS_PREDICATE, d.viaTarget)] : []),
|
|
818
|
-
],
|
|
1056
|
+
]],
|
|
819
1057
|
// same sub-1 discount as cax-dw, same reason (see CAX_DW_RULE_CONFIDENCE).
|
|
820
1058
|
...(premiseTrusts.length ? { premiseTrusts, ruleConfidence: CLS_SVF1_RULE_CONFIDENCE } : {}),
|
|
821
1059
|
};
|
|
@@ -833,18 +1071,76 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
833
1071
|
return {
|
|
834
1072
|
subject: d.subject, predicate: SUBCLASS_PREDICATE, object: d.object,
|
|
835
1073
|
provenance: ENTAILED_SCM_SVF_PROVENANCE,
|
|
836
|
-
justification: [
|
|
1074
|
+
justification: [[
|
|
837
1075
|
...(r1 ? [factIdForTriple(d.subject, ON_PROPERTY_PREDICATE, r1.property)] : []),
|
|
838
1076
|
factIdForTriple(d.subject, SOME_VALUES_FROM_PREDICATE, d.viaY1),
|
|
839
1077
|
...(r2 ? [factIdForTriple(d.object, ON_PROPERTY_PREDICATE, r2.property)] : []),
|
|
840
1078
|
factIdForTriple(d.object, SOME_VALUES_FROM_PREDICATE, d.viaY2),
|
|
841
1079
|
factIdForTriple(d.viaY1, SUBCLASS_PREDICATE, d.viaY2),
|
|
842
|
-
],
|
|
1080
|
+
]],
|
|
843
1081
|
// same sub-1 discount as cax-dw/cls-svf1, same reason (see CAX_DW_RULE_CONFIDENCE).
|
|
844
1082
|
...(premiseTrusts.length ? { premiseTrusts, ruleConfidence: SCM_SVF_RULE_CONFIDENCE } : {}),
|
|
845
1083
|
};
|
|
846
1084
|
}),
|
|
847
1085
|
];
|
|
1086
|
+
|
|
1087
|
+
// ---- alternate-environment discovery ----
|
|
1088
|
+
// Enumerate additional premise environments for this pass's conclusions and
|
|
1089
|
+
// for stored purely-entailed facts still under the cap, so retraction can
|
|
1090
|
+
// later keep a multiply-derived fact by set membership instead of a
|
|
1091
|
+
// re-derivation. The examination spends its own copy of the budget number —
|
|
1092
|
+
// it never competes with the derivation budget above.
|
|
1093
|
+
const conclusionCandidates = toWrite.map((w) => ({
|
|
1094
|
+
id: factIdForTriple(w.subject, w.predicate, w.object),
|
|
1095
|
+
subject: w.subject, predicate: w.predicate, object: w.object,
|
|
1096
|
+
environments: w.justification,
|
|
1097
|
+
write: w,
|
|
1098
|
+
}));
|
|
1099
|
+
const enumerateSupport = buildSupportEnumerator(rows.concat(conclusionCandidates.map((c) => ({
|
|
1100
|
+
id: c.id, subject: c.subject, predicate: c.predicate, object: c.object,
|
|
1101
|
+
}))));
|
|
1102
|
+
const rowById = new Map(rows.map((r) => [r.id, r]));
|
|
1103
|
+
const ownedPredicate = (p) => isSubClassOf(p) || isType(p) || isDisjoint(p);
|
|
1104
|
+
// Delta mode examines only stored facts the frontier touches; an alternate
|
|
1105
|
+
// enabled solely by a change outside it (e.g. a new filler type for
|
|
1106
|
+
// cls-svf1) waits for the next full pass — retraction stays correct either
|
|
1107
|
+
// way through its enumerate/boolean fallbacks.
|
|
1108
|
+
const storedCandidateInScope = (r) => mode !== "delta"
|
|
1109
|
+
|| (frontier !== null && (frontier.has(r.subject) || frontier.has(r.object)));
|
|
1110
|
+
const storedCandidates = rows
|
|
1111
|
+
.filter((r) => ownedPredicate(r.predicate) && isPurelyEntailed(r.provenance)
|
|
1112
|
+
&& environmentsOf(r).length < maxEnvironments && storedCandidateInScope(r))
|
|
1113
|
+
.map((r) => ({
|
|
1114
|
+
id: r.id, subject: r.subject, predicate: r.predicate, object: r.object,
|
|
1115
|
+
environments: environmentsOf(r), provenance: r.provenance,
|
|
1116
|
+
}));
|
|
1117
|
+
const alternateCandidates = [...conclusionCandidates, ...storedCandidates]
|
|
1118
|
+
.sort((a, b) => a.subject.localeCompare(b.subject) || a.predicate.localeCompare(b.predicate) || a.object.localeCompare(b.object));
|
|
1119
|
+
let environmentsAdded = 0;
|
|
1120
|
+
let alternatesTruncated = false;
|
|
1121
|
+
let examined = 0;
|
|
1122
|
+
for (const cand of alternateCandidates) {
|
|
1123
|
+
if (examined >= budget) { alternatesTruncated = true; break; }
|
|
1124
|
+
examined += 1;
|
|
1125
|
+
// Enumerate one PAST the cap so a distinct environment falling to the cap
|
|
1126
|
+
// is visible to the merge below and honestly reported as truncation.
|
|
1127
|
+
const discovered = enumerateSupport(cand, { maxEnvironments: maxEnvironments + 1 });
|
|
1128
|
+
const { kept, truncated: mergeTruncated } = capMergeEnvironments(cand.environments, discovered, maxEnvironments);
|
|
1129
|
+
if (mergeTruncated) alternatesTruncated = true;
|
|
1130
|
+
if (kept.length <= cand.environments.length) continue; // nothing new to record
|
|
1131
|
+
environmentsAdded += kept.length - cand.environments.length;
|
|
1132
|
+
if (cand.write) { cand.write.justification = kept; continue; }
|
|
1133
|
+
// A stored fact gains its newly discovered environments via a minimal
|
|
1134
|
+
// upsert row: provenance omitted (first-write-wins keeps the union), and
|
|
1135
|
+
// the three premise-discounted rules re-state their best environment's
|
|
1136
|
+
// trusts so the entailed hook stays engaged through the trust recompute.
|
|
1137
|
+
toWrite.push({
|
|
1138
|
+
subject: cand.subject, predicate: cand.predicate, object: cand.object,
|
|
1139
|
+
justification: kept,
|
|
1140
|
+
...(bestEnvironmentTrustOpts(cand.provenance, kept, (pid) => rowById.get(pid)?.trust) || {}),
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
848
1144
|
const { ids } = await appendFacts(repoDir, toWrite);
|
|
849
1145
|
const written = [];
|
|
850
1146
|
let i = 0;
|
|
@@ -868,7 +1164,24 @@ export async function syllogise(repoDir, { depth = 32, budget = 50, focus = null
|
|
|
868
1164
|
written.push({ id: ids[i], subject: d.subject, object: d.object, via: d.viaY1, rule: SCM_SVF_RULE });
|
|
869
1165
|
i += 1;
|
|
870
1166
|
}
|
|
871
|
-
|
|
1167
|
+
const truncated = written.length >= budget;
|
|
1168
|
+
|
|
1169
|
+
// The watermark advances ONLY after an unfocused pass whose derivations
|
|
1170
|
+
// ended at a natural fixpoint — a truncated or focused pass has not seen
|
|
1171
|
+
// everything, so its id set must not masquerade as a completed frontier.
|
|
1172
|
+
// alternatesTruncated does not block: alternates change justifications,
|
|
1173
|
+
// never which conclusions exist.
|
|
1174
|
+
if (normalizedFocus === null && stateFnsPresent && !truncated) {
|
|
1175
|
+
const factIds = new Set(currentIdSet);
|
|
1176
|
+
for (const id of ids) factIds.add(id);
|
|
1177
|
+
await store.saveSyllogiseState(repoDir, {
|
|
1178
|
+
version: 1, factIds: [...factIds].sort(), completedAt: new Date().toISOString(),
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
return {
|
|
1182
|
+
derived: written, count: written.length, budget, depth, truncated,
|
|
1183
|
+
mode, deltaSize, environmentsAdded, alternatesTruncated,
|
|
1184
|
+
};
|
|
872
1185
|
}
|
|
873
1186
|
|
|
874
1187
|
/** True when EVERY provenance tag on a fact's (possibly " | "-joined) union is
|
|
@@ -968,6 +1281,219 @@ function buildSurvivorDerivabilityCheck(rows) {
|
|
|
968
1281
|
};
|
|
969
1282
|
}
|
|
970
1283
|
|
|
1284
|
+
/** A row's persisted environments, upgrading a store whose readFactRows
|
|
1285
|
+
* predates the environment field: a bare justification list reads as one
|
|
1286
|
+
* environment. */
|
|
1287
|
+
const environmentsOf = (row) => row.environments
|
|
1288
|
+
|| (Array.isArray(row.justification) && row.justification.length ? [row.justification] : []);
|
|
1289
|
+
|
|
1290
|
+
/** Builds the environment ENUMERATOR: given ONLY the fact rows in `rows`,
|
|
1291
|
+
* returns `(row, { maxEnvironments }) => string[][]` — every premise-id set
|
|
1292
|
+
* (up to the cap) that independently derives the row's (s,p,o) conclusion
|
|
1293
|
+
* under the rule families that own its predicate, in a fixed deterministic
|
|
1294
|
+
* order, each environment citing ids in the same order the write path
|
|
1295
|
+
* cites them. An environment counts only when EVERY cited id resolves to a
|
|
1296
|
+
* row in `rows` and none is the row's own id — stricter than
|
|
1297
|
+
* buildSurvivorDerivabilityCheck's closure walk (a multi-hop ⊑ premise with
|
|
1298
|
+
* no materialised direct edge enumerates nothing), which is why that boolean
|
|
1299
|
+
* check stays the final authority in retraction. Pure, no I/O. */
|
|
1300
|
+
function buildSupportEnumerator(rows) {
|
|
1301
|
+
const storedIds = new Set();
|
|
1302
|
+
const subClassEdges = [];
|
|
1303
|
+
const succ = new Map(); // a -> Set(direct stored superclass)
|
|
1304
|
+
const typesOf = new Map(); // x -> Set(direct stored types)
|
|
1305
|
+
const disjointForward = new Set(); // "a␟b" per stored disjointWith row, as-stored orientation
|
|
1306
|
+
const disjointOf = new Map(); // term -> Set(partners), symmetric
|
|
1307
|
+
const onPropertyOf = new Map(); // restriction -> owl:onProperty's object
|
|
1308
|
+
const someValuesFromOf = new Map(); // restriction -> owl:someValuesFrom's object
|
|
1309
|
+
const propertyEdgesOf = new Map(); // x -> [[rawPredicate, y], …]
|
|
1310
|
+
for (const r of rows) {
|
|
1311
|
+
if (!r || !r.subject || !r.predicate || !r.object) continue;
|
|
1312
|
+
if (r.id) storedIds.add(r.id);
|
|
1313
|
+
const pLower = String(r.predicate || "").trim().toLowerCase();
|
|
1314
|
+
if (isSubClassOf(r.predicate)) {
|
|
1315
|
+
subClassEdges.push([r.subject, r.object]);
|
|
1316
|
+
if (!succ.has(r.subject)) succ.set(r.subject, new Set());
|
|
1317
|
+
succ.get(r.subject).add(r.object);
|
|
1318
|
+
} else if (isType(r.predicate)) {
|
|
1319
|
+
if (!typesOf.has(r.subject)) typesOf.set(r.subject, new Set());
|
|
1320
|
+
typesOf.get(r.subject).add(r.object);
|
|
1321
|
+
} else if (isDisjoint(r.predicate)) {
|
|
1322
|
+
disjointForward.add(`${r.subject}${SEP}${r.object}`);
|
|
1323
|
+
if (!disjointOf.has(r.subject)) disjointOf.set(r.subject, new Set());
|
|
1324
|
+
disjointOf.get(r.subject).add(r.object);
|
|
1325
|
+
if (!disjointOf.has(r.object)) disjointOf.set(r.object, new Set());
|
|
1326
|
+
disjointOf.get(r.object).add(r.subject);
|
|
1327
|
+
} else if (isOnProperty(r.predicate)) onPropertyOf.set(r.subject, r.object);
|
|
1328
|
+
else if (isSomeValuesFrom(r.predicate)) someValuesFromOf.set(r.subject, r.object);
|
|
1329
|
+
else if (!RESERVED_PREDICATES.has(pLower)) {
|
|
1330
|
+
if (!propertyEdgesOf.has(r.subject)) propertyEdgesOf.set(r.subject, []);
|
|
1331
|
+
propertyEdgesOf.get(r.subject).push([r.predicate, r.object]);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const ancestorsOf = buildAncestorCloser(subClassEdges);
|
|
1335
|
+
const succOf = (a) => succ.get(a) || new Set();
|
|
1336
|
+
const restrictionOf = (node) => {
|
|
1337
|
+
const property = onPropertyOf.get(node);
|
|
1338
|
+
const target = someValuesFromOf.get(node);
|
|
1339
|
+
return property && target ? { property, propertyKey: normFactTerm(property), target } : null;
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
return (row, { maxEnvironments = DEFAULT_MAX_ENVIRONMENTS } = {}) => {
|
|
1343
|
+
const out = [];
|
|
1344
|
+
const seen = new Set();
|
|
1345
|
+
const admit = (env) => {
|
|
1346
|
+
if (out.length >= maxEnvironments) return;
|
|
1347
|
+
if (row.id && env.includes(row.id)) return; // self-support is no support
|
|
1348
|
+
if (!env.every((id) => storedIds.has(id))) return; // a dangling citation makes the whole set inert
|
|
1349
|
+
const key = [...env].sort().join(" ");
|
|
1350
|
+
if (seen.has(key)) return;
|
|
1351
|
+
seen.add(key);
|
|
1352
|
+
out.push(env);
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
if (isSubClassOf(row.predicate)) {
|
|
1356
|
+
// scm-sco: each pivot m with stored direct s⊑m and m⊑o edges.
|
|
1357
|
+
for (const m of [...succOf(row.subject)].sort()) {
|
|
1358
|
+
if (out.length >= maxEnvironments) break;
|
|
1359
|
+
if (m === row.subject || m === row.object) continue;
|
|
1360
|
+
if (!succOf(m).has(row.object)) continue;
|
|
1361
|
+
admit([
|
|
1362
|
+
factIdForTriple(row.subject, SUBCLASS_PREDICATE, m),
|
|
1363
|
+
factIdForTriple(m, SUBCLASS_PREDICATE, row.object),
|
|
1364
|
+
]);
|
|
1365
|
+
}
|
|
1366
|
+
// scm-svf1: both ends declared restrictions over the SAME property with
|
|
1367
|
+
// a stored direct filler ⊑.
|
|
1368
|
+
if (out.length < maxEnvironments) {
|
|
1369
|
+
const r1 = restrictionOf(row.subject);
|
|
1370
|
+
const r2 = restrictionOf(row.object);
|
|
1371
|
+
if (r1 && r2 && r1.propertyKey === r2.propertyKey && r1.target !== r2.target
|
|
1372
|
+
&& succOf(r1.target).has(r2.target)) {
|
|
1373
|
+
admit([
|
|
1374
|
+
factIdForTriple(row.subject, ON_PROPERTY_PREDICATE, r1.property),
|
|
1375
|
+
factIdForTriple(row.subject, SOME_VALUES_FROM_PREDICATE, r1.target),
|
|
1376
|
+
factIdForTriple(row.object, ON_PROPERTY_PREDICATE, r2.property),
|
|
1377
|
+
factIdForTriple(row.object, SOME_VALUES_FROM_PREDICATE, r2.target),
|
|
1378
|
+
factIdForTriple(r1.target, SUBCLASS_PREDICATE, r2.target),
|
|
1379
|
+
]);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return out;
|
|
1383
|
+
}
|
|
1384
|
+
if (isType(row.predicate)) {
|
|
1385
|
+
// cax-sco: each stored type c with a stored direct c⊑D edge.
|
|
1386
|
+
for (const c of [...(typesOf.get(row.subject) || [])].sort()) {
|
|
1387
|
+
if (out.length >= maxEnvironments) break;
|
|
1388
|
+
if (c === row.object) continue;
|
|
1389
|
+
if (!succOf(c).has(row.object)) continue;
|
|
1390
|
+
admit([
|
|
1391
|
+
factIdForTriple(row.subject, TYPE_PREDICATE, c),
|
|
1392
|
+
factIdForTriple(c, SUBCLASS_PREDICATE, row.object),
|
|
1393
|
+
]);
|
|
1394
|
+
}
|
|
1395
|
+
// cls-svf1: D a declared restriction, each stored property edge over its
|
|
1396
|
+
// property whose value's type hits the target directly or by one stored
|
|
1397
|
+
// direct ⊑ edge.
|
|
1398
|
+
const rec = restrictionOf(row.object);
|
|
1399
|
+
if (rec) {
|
|
1400
|
+
const edges = [...(propertyEdgesOf.get(row.subject) || [])]
|
|
1401
|
+
.filter(([p]) => normFactTerm(p) === rec.propertyKey)
|
|
1402
|
+
.sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));
|
|
1403
|
+
for (const [p, y] of edges) {
|
|
1404
|
+
if (out.length >= maxEnvironments) break;
|
|
1405
|
+
for (const c of [...(typesOf.get(y) || [])].sort()) {
|
|
1406
|
+
if (out.length >= maxEnvironments) break;
|
|
1407
|
+
if (c !== rec.target && !succOf(c).has(rec.target)) continue;
|
|
1408
|
+
admit([
|
|
1409
|
+
factIdForTriple(row.subject, p, y),
|
|
1410
|
+
factIdForTriple(y, TYPE_PREDICATE, c),
|
|
1411
|
+
factIdForTriple(row.object, ON_PROPERTY_PREDICATE, rec.propertyKey),
|
|
1412
|
+
factIdForTriple(row.object, SOME_VALUES_FROM_PREDICATE, rec.target),
|
|
1413
|
+
...(c !== rec.target ? [factIdForTriple(c, SUBCLASS_PREDICATE, rec.target)] : []),
|
|
1414
|
+
]);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return out;
|
|
1419
|
+
}
|
|
1420
|
+
if (isDisjoint(row.predicate)) {
|
|
1421
|
+
// cax-dw: each stored type c and lift class d whose stored disjoint row
|
|
1422
|
+
// reaches the conclusion's object (orientation as stored).
|
|
1423
|
+
const pairs = [];
|
|
1424
|
+
for (const c of typesOf.get(row.subject) || []) {
|
|
1425
|
+
for (const d of [c, ...ancestorsOf(c)]) {
|
|
1426
|
+
if ((disjointOf.get(d) || new Set()).has(row.object)) pairs.push([c, d]);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
pairs.sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));
|
|
1430
|
+
for (const [c, d] of pairs) {
|
|
1431
|
+
if (out.length >= maxEnvironments) break;
|
|
1432
|
+
const dwStoredForward = disjointForward.has(`${d}${SEP}${row.object}`);
|
|
1433
|
+
const [dwS, dwO] = dwStoredForward ? [d, row.object] : [row.object, d];
|
|
1434
|
+
admit([
|
|
1435
|
+
factIdForTriple(row.subject, TYPE_PREDICATE, c),
|
|
1436
|
+
factIdForTriple(dwS, DISJOINT_PREDICATE, dwO),
|
|
1437
|
+
...(d !== c ? [factIdForTriple(c, SUBCLASS_PREDICATE, d)] : []),
|
|
1438
|
+
]);
|
|
1439
|
+
}
|
|
1440
|
+
return out;
|
|
1441
|
+
}
|
|
1442
|
+
return out; // a predicate no rule family owns enumerates nothing
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/** Merge already-stored environments with newly discovered ones under the
|
|
1447
|
+
* cap: stored first (their order preserved), then discoveries in enumeration
|
|
1448
|
+
* order, deduped by canonical key, truncated at `cap`. Returns
|
|
1449
|
+
* { kept, truncated } — truncated true when a distinct environment was
|
|
1450
|
+
* dropped for the cap. */
|
|
1451
|
+
function capMergeEnvironments(storedEnvs, discoveredEnvs, cap) {
|
|
1452
|
+
const kept = [];
|
|
1453
|
+
const seen = new Set();
|
|
1454
|
+
let truncated = false;
|
|
1455
|
+
for (const env of [...(storedEnvs || []), ...(discoveredEnvs || [])]) {
|
|
1456
|
+
if (!Array.isArray(env) || !env.length) continue;
|
|
1457
|
+
const key = [...env].sort().join(" ");
|
|
1458
|
+
if (seen.has(key)) continue;
|
|
1459
|
+
if (kept.length >= cap) { truncated = true; continue; }
|
|
1460
|
+
seen.add(key);
|
|
1461
|
+
kept.push(env);
|
|
1462
|
+
}
|
|
1463
|
+
return { kept, truncated };
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const ENTAILED_RULE_CONFIDENCE_BY_TAG = new Map([
|
|
1467
|
+
[ENTAILED_DISJOINT_PROVENANCE, CAX_DW_RULE_CONFIDENCE],
|
|
1468
|
+
[ENTAILED_SVF1_PROVENANCE, CLS_SVF1_RULE_CONFIDENCE],
|
|
1469
|
+
[ENTAILED_SCM_SVF_PROVENANCE, SCM_SVF_RULE_CONFIDENCE],
|
|
1470
|
+
]);
|
|
1471
|
+
|
|
1472
|
+
/** The entailed-hook opts for re-stating a conclusion of one of the three
|
|
1473
|
+
* premise-discounted rules: the BEST environment's premise trusts (max over
|
|
1474
|
+
* environments of min(premise trusts); tie → the earlier environment) plus
|
|
1475
|
+
* the rule's confidence — without this, recomputeFactTrust would silently
|
|
1476
|
+
* reset a discounted conclusion to the bare entailed prior. Null for
|
|
1477
|
+
* scm-sco/cax-sco conclusions (which ride the bare prior by design) and when
|
|
1478
|
+
* no environment's premises all resolve to a numeric trust. */
|
|
1479
|
+
function bestEnvironmentTrustOpts(provenance, environments, trustOfId) {
|
|
1480
|
+
let ruleConfidence;
|
|
1481
|
+
for (const tag of String(provenance || "").split(" | ")) {
|
|
1482
|
+
const rc = ENTAILED_RULE_CONFIDENCE_BY_TAG.get(tag);
|
|
1483
|
+
if (rc !== undefined) { ruleConfidence = rc; break; }
|
|
1484
|
+
}
|
|
1485
|
+
if (ruleConfidence === undefined) return null;
|
|
1486
|
+
let best = null;
|
|
1487
|
+
let bestMin = -1;
|
|
1488
|
+
for (const env of environments || []) {
|
|
1489
|
+
const trusts = env.map((id) => trustOfId(id)).filter((t) => typeof t === "number");
|
|
1490
|
+
if (trusts.length !== env.length) continue; // a premise with no resolvable trust can't price the environment
|
|
1491
|
+
const weakest = Math.min(...trusts);
|
|
1492
|
+
if (weakest > bestMin) { bestMin = weakest; best = trusts; }
|
|
1493
|
+
}
|
|
1494
|
+
return best ? { premiseTrusts: best, ruleConfidence } : null;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
971
1497
|
/**
|
|
972
1498
|
* A scoped retraction slice: DRed (delete-and-rederive; Gupta, Mumick &
|
|
973
1499
|
* Subrahmanian, SIGMOD 1993), NOT JTMS. It recomputes the MATERIALISATION —
|
|
@@ -989,16 +1515,26 @@ function buildSurvivorDerivabilityCheck(rows) {
|
|
|
989
1515
|
* justifications into every rule's conclusions (transitive ⊑, propagated
|
|
990
1516
|
* types, disjointness violations, restriction membership and subsumption).
|
|
991
1517
|
*
|
|
992
|
-
*
|
|
993
|
-
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
1518
|
+
* Each candidate is checked in three steps, cheapest first: (1) FAST PATH —
|
|
1519
|
+
* any stored environment untouched by the cascade and fully backed by
|
|
1520
|
+
* surviving rows keeps the fact by set membership alone; (2) ENUMERATE — a
|
|
1521
|
+
* fresh premise environment found among the survivors re-grounds it; (3) the
|
|
1522
|
+
* BOOLEAN BACKSTOP — the closure-walking derivability check, which sees
|
|
1523
|
+
* multi-hop support the enumerator's stored-direct-edge discipline cannot
|
|
1524
|
+
* cite, stays the final authority. A survivor whose environments changed is
|
|
1525
|
+
* RE-GROUNDED after the removal (its pruned or fresh environments written
|
|
1526
|
+
* back) when the store carries `appendFacts` — an OPTIONAL seam member:
|
|
1527
|
+
* without it removal is still correct, the survivor's environments just stay
|
|
1528
|
+
* stale until the next syllogise pass.
|
|
996
1529
|
*
|
|
997
1530
|
* Returns { retracted, count, budget, depth, truncated, found } — `found` is
|
|
998
1531
|
* false when `subject ⊑ object` was never a stored fact.
|
|
999
1532
|
*/
|
|
1000
|
-
export async function retractSubClassOf(repoDir, subject, object, {
|
|
1533
|
+
export async function retractSubClassOf(repoDir, subject, object, {
|
|
1534
|
+
budget = 50, depth = 32, maxEnvironments = DEFAULT_MAX_ENVIRONMENTS, store,
|
|
1535
|
+
} = {}) {
|
|
1001
1536
|
const { loadMemory, readFactRows, removeFacts } = requireStore(store, ["loadMemory", "readFactRows", "removeFacts"], "retractSubClassOf");
|
|
1537
|
+
const appendFactsFn = typeof store?.appendFacts === "function" ? store.appendFacts : null;
|
|
1002
1538
|
const s = normFactTerm(subject);
|
|
1003
1539
|
const o = normFactTerm(object);
|
|
1004
1540
|
const targetId = factIdForTriple(s, SUBCLASS_PREDICATE, o);
|
|
@@ -1009,50 +1545,105 @@ export async function retractSubClassOf(repoDir, subject, object, { budget = 50,
|
|
|
1009
1545
|
|
|
1010
1546
|
// Only a purely-entailed fact ever carries a walkable justification —
|
|
1011
1547
|
// a fact later independently taught is never a cascade candidate at all.
|
|
1012
|
-
const entailedRows = rows.filter((r) => r.
|
|
1548
|
+
const entailedRows = rows.filter((r) => environmentsOf(r).length && isPurelyEntailed(r.provenance));
|
|
1549
|
+
// premise id -> the entailed fact ids whose environments cite it. Built
|
|
1550
|
+
// ONCE; each round's candidate set reads it for the facts the newest
|
|
1551
|
+
// removals could actually touch — backward relevance from the same
|
|
1552
|
+
// structure a forward pass reads forward.
|
|
1553
|
+
const citedBy = new Map();
|
|
1554
|
+
for (const r of entailedRows) {
|
|
1555
|
+
for (const env of environmentsOf(r)) {
|
|
1556
|
+
for (const premiseId of env) {
|
|
1557
|
+
if (!citedBy.has(premiseId)) citedBy.set(premiseId, new Set());
|
|
1558
|
+
citedBy.get(premiseId).add(r.id);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1013
1562
|
|
|
1014
1563
|
const removed = new Set([targetId]);
|
|
1015
1564
|
const order = [targetId]; // deterministic report order: target first, then removal order
|
|
1565
|
+
const reground = new Map(); // survivor fact id -> the environments to persist for it
|
|
1016
1566
|
let truncated = false;
|
|
1017
1567
|
let round = 0;
|
|
1568
|
+
let newlyRemoved = [targetId];
|
|
1018
1569
|
for (; round < depth; round += 1) {
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1570
|
+
const candidateIds = new Set();
|
|
1571
|
+
for (const id of newlyRemoved) {
|
|
1572
|
+
for (const cited of citedBy.get(id) || []) {
|
|
1573
|
+
if (!removed.has(cited)) candidateIds.add(cited);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
const candidates = [...candidateIds].map((id) => byId.get(id))
|
|
1021
1577
|
.sort((a, b) => a.subject.localeCompare(b.subject) || a.predicate.localeCompare(b.predicate) || a.object.localeCompare(b.object));
|
|
1022
|
-
if (!candidates.length) break; // fixpoint — nothing
|
|
1578
|
+
if (!candidates.length) break; // fixpoint — nothing cites what just fell
|
|
1023
1579
|
|
|
1024
|
-
// The surviving fact set for THIS round
|
|
1025
|
-
//
|
|
1026
|
-
//
|
|
1027
|
-
//
|
|
1028
|
-
const
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
);
|
|
1580
|
+
// The surviving fact set for THIS round excludes every candidate's own
|
|
1581
|
+
// row too, not just `removed` — otherwise a candidate could trivially
|
|
1582
|
+
// "reach itself" through its own not-yet-deleted edge, or lean on a
|
|
1583
|
+
// sibling candidate standing on the same broken premise.
|
|
1584
|
+
const survivors = rows.filter((r) => !removed.has(r.id) && !candidateIds.has(r.id));
|
|
1585
|
+
const survivorIds = new Set(survivors.map((r) => r.id));
|
|
1586
|
+
const enumerateSupport = buildSupportEnumerator(survivors);
|
|
1587
|
+
const stillDerivable = buildSurvivorDerivabilityCheck(survivors);
|
|
1032
1588
|
|
|
1033
1589
|
let progressed = false;
|
|
1034
1590
|
let hitBudget = false;
|
|
1591
|
+
newlyRemoved = [];
|
|
1035
1592
|
for (const c of candidates) {
|
|
1036
1593
|
if (removed.size >= budget) { hitBudget = true; break; }
|
|
1037
|
-
//
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1041
|
-
|
|
1594
|
+
// FAST PATH: an environment whose every premise still stands keeps the
|
|
1595
|
+
// fact — pure set membership, no re-derivation. When some environments
|
|
1596
|
+
// broke, queue the pruned set so the next retraction still sees the
|
|
1597
|
+
// survivor (the stale-justification fix).
|
|
1598
|
+
const environments = environmentsOf(c);
|
|
1599
|
+
const intact = environments.filter((env) => env.every((id) => survivorIds.has(id)));
|
|
1600
|
+
if (intact.length) {
|
|
1601
|
+
if (intact.length !== environments.length) reground.set(c.id, intact);
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
// ENUMERATE: a fresh premise environment among the survivors re-grounds
|
|
1605
|
+
// the fact under new citations.
|
|
1606
|
+
const fresh = enumerateSupport(c, { maxEnvironments });
|
|
1607
|
+
if (fresh.length) {
|
|
1608
|
+
reground.set(c.id, fresh);
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
// BOOLEAN BACKSTOP: the closure walk is the final authority — it sees
|
|
1612
|
+
// multi-hop support with no materialised direct edge to cite, so a
|
|
1613
|
+
// still-derivable fact is never removed on a stale citation alone (its
|
|
1614
|
+
// environments stay as they were).
|
|
1615
|
+
if (stillDerivable(c)) continue;
|
|
1042
1616
|
removed.add(c.id);
|
|
1043
1617
|
order.push(c.id);
|
|
1618
|
+
newlyRemoved.push(c.id);
|
|
1044
1619
|
progressed = true;
|
|
1045
1620
|
}
|
|
1046
1621
|
if (hitBudget) { truncated = true; break; }
|
|
1047
|
-
if (!progressed) break; // every candidate this round survived
|
|
1622
|
+
if (!progressed) break; // every candidate this round survived — fixpoint
|
|
1048
1623
|
}
|
|
1049
1624
|
if (!truncated && round >= depth) {
|
|
1050
|
-
// depth exhausted, not a natural fixpoint — honestly flag it if a
|
|
1051
|
-
//
|
|
1625
|
+
// depth exhausted, not a natural fixpoint — honestly flag it if a pending
|
|
1626
|
+
// candidate (any surviving fact whose environment union still cites a
|
|
1627
|
+
// removed id) would have been checked next round.
|
|
1052
1628
|
truncated = entailedRows.some((r) => !removed.has(r.id) && r.justification.some((j) => removed.has(j)));
|
|
1053
1629
|
}
|
|
1054
1630
|
|
|
1055
1631
|
const { removed: actuallyRemoved } = await removeFacts(repoDir, order);
|
|
1632
|
+
if (appendFactsFn) {
|
|
1633
|
+
const regroundWrites = [...reground.entries()]
|
|
1634
|
+
.filter(([id]) => !removed.has(id))
|
|
1635
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
1636
|
+
.map(([id, environments]) => {
|
|
1637
|
+
const row = byId.get(id);
|
|
1638
|
+
return {
|
|
1639
|
+
subject: row.subject, predicate: row.predicate, object: row.object,
|
|
1640
|
+
// provenance omitted — appendFacts' first-write-wins keeps the union
|
|
1641
|
+
justification: environments,
|
|
1642
|
+
...(bestEnvironmentTrustOpts(row.provenance, environments, (pid) => byId.get(pid)?.trust) || {}),
|
|
1643
|
+
};
|
|
1644
|
+
});
|
|
1645
|
+
if (regroundWrites.length) await appendFactsFn(repoDir, regroundWrites);
|
|
1646
|
+
}
|
|
1056
1647
|
return { retracted: actuallyRemoved, count: actuallyRemoved.length, budget, depth, truncated, found: true };
|
|
1057
1648
|
}
|
|
1058
1649
|
|