eyeling 1.25.3 → 1.25.5

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.
@@ -5973,6 +5973,158 @@ function __varOccursElsewhereInPremise(premise, name, idx, field) {
5973
5973
  return false;
5974
5974
  }
5975
5975
 
5976
+
5977
+ function __scopedPriorityForTerm(t) {
5978
+ if (t instanceof GraphTerm) return 0;
5979
+ const p0 = __logNaturalPriorityFromTerm(t);
5980
+ return p0 !== null && p0 >= 1 ? p0 : 1;
5981
+ }
5982
+
5983
+ function __pushScopedQueryTriplesFromGraph(term, out) {
5984
+ if (!(term instanceof GraphTerm)) return;
5985
+ for (const tr of term.triples) out.push(tr);
5986
+ }
5987
+
5988
+ function __analyzeForwardRuleScopedUse(rule) {
5989
+ const out = {
5990
+ needsSnap: false,
5991
+ baseLevel: 0,
5992
+ queryTriples: [],
5993
+ hasScopedAggregate: false,
5994
+ };
5995
+
5996
+ if (!rule || !Array.isArray(rule.premise)) return out;
5997
+
5998
+ for (let i = 0; i < rule.premise.length; i++) {
5999
+ const tr = rule.premise[i];
6000
+ if (!(tr && tr.p instanceof Iri)) continue;
6001
+ const pv = tr.p.value;
6002
+
6003
+ if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
6004
+ // Explicit quoted scopes are local formulas, not snapshots of the global closure.
6005
+ if (tr.s instanceof GraphTerm) continue;
6006
+
6007
+ // A scope variable that is bound by another premise may become an explicit GraphTerm.
6008
+ // Still, if it remains unbound at runtime the builtin treats it as priority 1.
6009
+ // We therefore record the scoped use, but only use quoted object triples for
6010
+ // dependency analysis; variables inside the quoted object do not bind the scope.
6011
+ out.needsSnap = true;
6012
+ out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.s));
6013
+ __pushScopedQueryTriplesFromGraph(tr.o, out.queryTriples);
6014
+ continue;
6015
+ }
6016
+
6017
+ if (pv === LOG_NS + 'collectAllIn') {
6018
+ if (tr.o instanceof GraphTerm) continue;
6019
+
6020
+ out.needsSnap = true;
6021
+ out.hasScopedAggregate = true;
6022
+ out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
6023
+ if (tr.s instanceof ListTerm && tr.s.elems.length === 3) {
6024
+ __pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
6025
+ }
6026
+ continue;
6027
+ }
6028
+
6029
+ if (pv === LOG_NS + 'forAllIn') {
6030
+ if (tr.o instanceof GraphTerm) continue;
6031
+
6032
+ out.needsSnap = true;
6033
+ out.hasScopedAggregate = true;
6034
+ out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
6035
+ if (tr.s instanceof ListTerm && tr.s.elems.length === 2) {
6036
+ __pushScopedQueryTriplesFromGraph(tr.s.elems[0], out.queryTriples);
6037
+ __pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
6038
+ }
6039
+ }
6040
+ }
6041
+
6042
+ return out;
6043
+ }
6044
+
6045
+ function __triplePatternsMayOverlap(a, b) {
6046
+ return unifyTriple(a, b, __emptySubst()) !== null || unifyTriple(b, a, __emptySubst()) !== null;
6047
+ }
6048
+
6049
+ function __ruleConclusionsMayFeedScopedQueries(producer, consumerMeta) {
6050
+ if (!consumerMeta || !consumerMeta.queryTriples || consumerMeta.queryTriples.length === 0) return false;
6051
+
6052
+ // Dynamic conclusions are only known after a proof solution. Conservatively
6053
+ // assume they can feed any scoped query if the producing rule is scoped.
6054
+ if (producer && producer.__dynamicConclusionTerm) return true;
6055
+
6056
+ if (!producer || !Array.isArray(producer.conclusion) || producer.conclusion.length === 0) return false;
6057
+ for (const q of consumerMeta.queryTriples) {
6058
+ for (const h of producer.conclusion) {
6059
+ if (__triplePatternsMayOverlap(q, h)) return true;
6060
+ }
6061
+ }
6062
+ return false;
6063
+ }
6064
+
6065
+ function __setForwardRuleScopedStratumInfo(rule, level) {
6066
+ const value =
6067
+ level > 0
6068
+ ? { needsSnap: true, requiredLevel: level, exactLevel: true }
6069
+ : { needsSnap: false, requiredLevel: 0, exactLevel: false };
6070
+
6071
+ if (!hasOwn.call(rule, '__scopedStratumInfo')) {
6072
+ Object.defineProperty(rule, '__scopedStratumInfo', {
6073
+ value,
6074
+ enumerable: false,
6075
+ writable: true,
6076
+ configurable: true,
6077
+ });
6078
+ } else {
6079
+ rule.__scopedStratumInfo = value;
6080
+ }
6081
+ }
6082
+
6083
+ function __computeForwardRuleScopedStrata(forwardRules) {
6084
+ if (!Array.isArray(forwardRules) || forwardRules.length === 0) return 0;
6085
+
6086
+ const metas = forwardRules.map((r) => __analyzeForwardRuleScopedUse(r));
6087
+ const levels = metas.map((m) => (m.needsSnap ? Math.max(1, m.baseLevel || 1) : 0));
6088
+
6089
+ let maxLevel = 0;
6090
+ for (const lvl of levels) if (lvl > maxLevel) maxLevel = lvl;
6091
+
6092
+ // Stratify scoped rules by data dependency: if a scoped query can read facts
6093
+ // produced by another scoped rule, the reader must run against a later frozen
6094
+ // snapshot. This prevents non-monotonic scoped builtins such as collectAllIn
6095
+ // from emitting an early result before lower strata have derived their facts.
6096
+ const passLimit = Math.max(1, forwardRules.length) + maxLevel + 1;
6097
+ for (let pass = 0; pass < passLimit; pass++) {
6098
+ let changed = false;
6099
+ for (let ci = 0; ci < forwardRules.length; ci++) {
6100
+ if (!metas[ci].needsSnap || !metas[ci].hasScopedAggregate) continue;
6101
+ let needed = levels[ci];
6102
+ for (let pi = 0; pi < forwardRules.length; pi++) {
6103
+ if (pi === ci) continue;
6104
+ if (levels[pi] <= 0) continue;
6105
+ if (metas[pi].hasScopedAggregate) continue;
6106
+ if (!__ruleConclusionsMayFeedScopedQueries(forwardRules[pi], metas[ci])) continue;
6107
+ needed = Math.max(needed, levels[pi] + 1);
6108
+ }
6109
+ if (needed !== levels[ci]) {
6110
+ levels[ci] = needed;
6111
+ if (needed > maxLevel) maxLevel = needed;
6112
+ changed = true;
6113
+ }
6114
+ }
6115
+ if (!changed) break;
6116
+ }
6117
+
6118
+
6119
+ maxLevel = 0;
6120
+ for (let i = 0; i < forwardRules.length; i++) {
6121
+ __setForwardRuleScopedStratumInfo(forwardRules[i], levels[i]);
6122
+ if (levels[i] > maxLevel) maxLevel = levels[i];
6123
+ }
6124
+
6125
+ return maxLevel;
6126
+ }
6127
+
5976
6128
  function __computeForwardRuleScopedSkipInfo(rule) {
5977
6129
  let needsSnap = false;
5978
6130
  let requiredLevel = 0;
@@ -6523,6 +6675,12 @@ function alphaEqGraphTriples(xs, ys, opts) {
6523
6675
  // - __byPred: Map<predicateId, number[]> (indices into facts array)
6524
6676
  // - __byPS: Map<predicateId, Map<subjectId, number[]>>
6525
6677
  // - __byPO: Map<predicateId, Map<objectId, number[]>>
6678
+ // - __byPNonFastS / __byPNonFastO: Map<predicateId, number[]>
6679
+ // IRI-predicate facts whose subject/object cannot be indexed by fast key.
6680
+ // These are the fallback clauses for a constrained subject/object, just
6681
+ // like a Prolog clause index keeps variable-headed clauses as fallback.
6682
+ // - __varPred* indexes: facts whose predicate is a Var. Only these non-IRI
6683
+ // predicate facts can unify with a ground IRI predicate goal.
6526
6684
  // - __keySet: Set<"S\tP\tO"> for Iri/Literal/Blank-only triples (fast dup check)
6527
6685
  //
6528
6686
  // Backward rules:
@@ -6538,6 +6696,8 @@ const __compoundKeyToTid = new Map();
6538
6696
  // Use a negative id space so we never collide with __tid (which is positive).
6539
6697
  let __nextCompoundTid = -1;
6540
6698
 
6699
+ const EMPTY_FACT_INDEX_BUCKET = Object.freeze([]);
6700
+
6541
6701
  function __internCompoundTid(key) {
6542
6702
  const hit = __compoundKeyToTid.get(key);
6543
6703
  if (hit !== undefined) return hit;
@@ -6589,6 +6749,71 @@ function termFastKey(t) {
6589
6749
  return null;
6590
6750
  }
6591
6751
 
6752
+ function encodeLookupKeyPart(k) {
6753
+ if (typeof k === 'number') return 'T' + k;
6754
+ const s = String(k);
6755
+ return 'K' + s.length + ':' + s;
6756
+ }
6757
+
6758
+ function literalLookupKey(t) {
6759
+ const boolInfo = parseBooleanLiteralInfo(t);
6760
+ if (boolInfo) return '\u0000B' + (boolInfo.value ? '1' : '0');
6761
+
6762
+ const numInfo = parseNumericLiteralInfo(t);
6763
+ if (numInfo) {
6764
+ if (numInfo.kind === 'bigint') return '\u0000N' + numInfo.dt + '\u0000' + numInfo.value.toString();
6765
+
6766
+ const n = numInfo.value;
6767
+ // Normal unification intentionally does not make NaN value-equal to NaN;
6768
+ // only identical lexical literals match through the ordinary __tid path.
6769
+ if (!Number.isNaN(n)) return '\u0000N' + numInfo.dt + '\u0000' + String(n);
6770
+ }
6771
+
6772
+ // Covers exact literals plus plain string / xsd:string canonicalization, which
6773
+ // Literal construction already normalizes into a shared __tid.
6774
+ return termFastKey(t);
6775
+ }
6776
+
6777
+ function termLookupKey(t) {
6778
+ // Lookup keys summarize the equality accepted by ordinary unifyTerm(), not
6779
+ // merely object identity. This keeps literal-index pruning complete for
6780
+ // value-equivalent booleans/numerics such as true/"1"^^xsd:boolean and
6781
+ // 1.0/1.00, while preserving exact fast ids for IRIs, blanks, and strings.
6782
+ if (t instanceof Iri) {
6783
+ if (t.value === RDF_NIL_IRI) return '\u0000L0';
6784
+ return t.__tid;
6785
+ }
6786
+ if (t instanceof Blank) return t.__tid;
6787
+ if (t instanceof Literal) return literalLookupKey(t);
6788
+
6789
+ if (t instanceof ListTerm) {
6790
+ const cached = t.__lookupKey;
6791
+ if (cached !== undefined) return cached === false ? null : cached;
6792
+
6793
+ const xs = t.elems;
6794
+ if (xs.length === 0) {
6795
+ Object.defineProperty(t, '__lookupKey', { value: '\u0000L0', enumerable: false });
6796
+ return '\u0000L0';
6797
+ }
6798
+
6799
+ const parts = new Array(xs.length);
6800
+ for (let i = 0; i < xs.length; i++) {
6801
+ const k = termLookupKey(xs[i]);
6802
+ if (k === null) {
6803
+ Object.defineProperty(t, '__lookupKey', { value: false, enumerable: false });
6804
+ return null;
6805
+ }
6806
+ parts[i] = encodeLookupKeyPart(k);
6807
+ }
6808
+
6809
+ const key = '\u0000L' + xs.length + '\u0001' + parts.join('\u0001');
6810
+ Object.defineProperty(t, '__lookupKey', { value: key, enumerable: false });
6811
+ return key;
6812
+ }
6813
+
6814
+ return null;
6815
+ }
6816
+
6592
6817
  function tripleFastKey(tr) {
6593
6818
  const ks = termFastKey(tr.s);
6594
6819
  const kp = termFastKey(tr.p);
@@ -6602,9 +6827,13 @@ function ensureFactIndexes(facts) {
6602
6827
  facts.__byPred &&
6603
6828
  facts.__byPS &&
6604
6829
  facts.__byPO &&
6605
- facts.__wildPred &&
6606
- facts.__wildPS &&
6607
- facts.__wildPO &&
6830
+ facts.__byPNonFastS &&
6831
+ facts.__byPNonFastO &&
6832
+ facts.__varPred &&
6833
+ facts.__varPredPS &&
6834
+ facts.__varPredPO &&
6835
+ facts.__varPredNonFastS &&
6836
+ facts.__varPredNonFastO &&
6608
6837
  facts.__keySet
6609
6838
  )
6610
6839
  return;
@@ -6624,21 +6853,41 @@ function ensureFactIndexes(facts) {
6624
6853
  enumerable: false,
6625
6854
  writable: true,
6626
6855
  });
6627
- Object.defineProperty(facts, '__wildPred', {
6856
+ Object.defineProperty(facts, '__byPNonFastS', {
6857
+ value: new Map(),
6858
+ enumerable: false,
6859
+ writable: true,
6860
+ });
6861
+ Object.defineProperty(facts, '__byPNonFastO', {
6862
+ value: new Map(),
6863
+ enumerable: false,
6864
+ writable: true,
6865
+ });
6866
+ Object.defineProperty(facts, '__varPred', {
6628
6867
  value: [],
6629
6868
  enumerable: false,
6630
6869
  writable: true,
6631
6870
  });
6632
- Object.defineProperty(facts, '__wildPS', {
6871
+ Object.defineProperty(facts, '__varPredPS', {
6633
6872
  value: new Map(),
6634
6873
  enumerable: false,
6635
6874
  writable: true,
6636
6875
  });
6637
- Object.defineProperty(facts, '__wildPO', {
6876
+ Object.defineProperty(facts, '__varPredPO', {
6638
6877
  value: new Map(),
6639
6878
  enumerable: false,
6640
6879
  writable: true,
6641
6880
  });
6881
+ Object.defineProperty(facts, '__varPredNonFastS', {
6882
+ value: [],
6883
+ enumerable: false,
6884
+ writable: true,
6885
+ });
6886
+ Object.defineProperty(facts, '__varPredNonFastO', {
6887
+ value: [],
6888
+ enumerable: false,
6889
+ writable: true,
6890
+ });
6642
6891
  Object.defineProperty(facts, '__keySet', {
6643
6892
  value: new Set(),
6644
6893
  enumerable: false,
@@ -6679,16 +6928,53 @@ function cloneFactIndexesForSnapshot(src, dest) {
6679
6928
  Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
6680
6929
  Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
6681
6930
  Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
6682
- Object.defineProperty(dest, '__wildPred', { value: src.__wildPred.slice(), enumerable: false, writable: true });
6683
- Object.defineProperty(dest, '__wildPS', { value: cloneArrayMap(src.__wildPS), enumerable: false, writable: true });
6684
- Object.defineProperty(dest, '__wildPO', { value: cloneArrayMap(src.__wildPO), enumerable: false, writable: true });
6931
+ Object.defineProperty(dest, '__byPNonFastS', {
6932
+ value: cloneArrayMap(src.__byPNonFastS),
6933
+ enumerable: false,
6934
+ writable: true,
6935
+ });
6936
+ Object.defineProperty(dest, '__byPNonFastO', {
6937
+ value: cloneArrayMap(src.__byPNonFastO),
6938
+ enumerable: false,
6939
+ writable: true,
6940
+ });
6941
+ Object.defineProperty(dest, '__varPred', { value: src.__varPred.slice(), enumerable: false, writable: true });
6942
+ Object.defineProperty(dest, '__varPredPS', {
6943
+ value: cloneArrayMap(src.__varPredPS),
6944
+ enumerable: false,
6945
+ writable: true,
6946
+ });
6947
+ Object.defineProperty(dest, '__varPredPO', {
6948
+ value: cloneArrayMap(src.__varPredPO),
6949
+ enumerable: false,
6950
+ writable: true,
6951
+ });
6952
+ Object.defineProperty(dest, '__varPredNonFastS', {
6953
+ value: src.__varPredNonFastS.slice(),
6954
+ enumerable: false,
6955
+ writable: true,
6956
+ });
6957
+ Object.defineProperty(dest, '__varPredNonFastO', {
6958
+ value: src.__varPredNonFastO.slice(),
6959
+ enumerable: false,
6960
+ writable: true,
6961
+ });
6685
6962
  Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
6686
6963
  Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
6687
6964
  }
6688
6965
 
6966
+ function addToIndexArrayMap(map, key, value) {
6967
+ let bucket = map.get(key);
6968
+ if (!bucket) {
6969
+ bucket = [];
6970
+ map.set(key, bucket);
6971
+ }
6972
+ bucket.push(value);
6973
+ }
6974
+
6689
6975
  function indexFact(facts, tr, idx, addKeySet = true) {
6690
- const sk = termFastKey(tr.s);
6691
- const ok = termFastKey(tr.o);
6976
+ const sk = termLookupKey(tr.s);
6977
+ const ok = termLookupKey(tr.o);
6692
6978
  let pkForKey = null;
6693
6979
 
6694
6980
  if (tr.p instanceof Iri) {
@@ -6709,12 +6995,9 @@ function indexFact(facts, tr, idx, addKeySet = true) {
6709
6995
  ps = new Map();
6710
6996
  facts.__byPS.set(pk, ps);
6711
6997
  }
6712
- let psb = ps.get(sk);
6713
- if (!psb) {
6714
- psb = [];
6715
- ps.set(sk, psb);
6716
- }
6717
- psb.push(idx);
6998
+ addToIndexArrayMap(ps, sk, idx);
6999
+ } else {
7000
+ addToIndexArrayMap(facts.__byPNonFastS, pk, idx);
6718
7001
  }
6719
7002
 
6720
7003
  if (ok !== null) {
@@ -6723,32 +7006,23 @@ function indexFact(facts, tr, idx, addKeySet = true) {
6723
7006
  po = new Map();
6724
7007
  facts.__byPO.set(pk, po);
6725
7008
  }
6726
- let pob = po.get(ok);
6727
- if (!pob) {
6728
- pob = [];
6729
- po.set(ok, pob);
6730
- }
6731
- pob.push(idx);
7009
+ addToIndexArrayMap(po, ok, idx);
7010
+ } else {
7011
+ addToIndexArrayMap(facts.__byPNonFastO, pk, idx);
6732
7012
  }
6733
- } else {
6734
- facts.__wildPred.push(idx);
7013
+ } else if (tr.p instanceof Var) {
7014
+ facts.__varPred.push(idx);
6735
7015
 
6736
7016
  if (sk !== null) {
6737
- let psb = facts.__wildPS.get(sk);
6738
- if (!psb) {
6739
- psb = [];
6740
- facts.__wildPS.set(sk, psb);
6741
- }
6742
- psb.push(idx);
7017
+ addToIndexArrayMap(facts.__varPredPS, sk, idx);
7018
+ } else {
7019
+ facts.__varPredNonFastS.push(idx);
6743
7020
  }
6744
7021
 
6745
7022
  if (ok !== null) {
6746
- let pob = facts.__wildPO.get(ok);
6747
- if (!pob) {
6748
- pob = [];
6749
- facts.__wildPO.set(ok, pob);
6750
- }
6751
- pob.push(idx);
7023
+ addToIndexArrayMap(facts.__varPredPO, ok, idx);
7024
+ } else {
7025
+ facts.__varPredNonFastO.push(idx);
6752
7026
  }
6753
7027
  }
6754
7028
 
@@ -6758,55 +7032,86 @@ function indexFact(facts, tr, idx, addKeySet = true) {
6758
7032
  }
6759
7033
  }
6760
7034
 
7035
+ function mergeIndexBuckets(primary, fallback) {
7036
+ const a = primary && primary.length ? primary : null;
7037
+ const b = fallback && fallback.length ? fallback : null;
7038
+ if (!a && !b) return EMPTY_FACT_INDEX_BUCKET;
7039
+ if (!a) return b;
7040
+ if (!b) return a;
7041
+ const out = new Array(a.length + b.length);
7042
+ for (let i = 0; i < a.length; i++) out[i] = a[i];
7043
+ for (let i = 0; i < b.length; i++) out[a.length + i] = b[i];
7044
+ return out;
7045
+ }
7046
+
7047
+ function selectPositionIndexedCandidates(all, exactByS, fallbackS, sk, exactByO, fallbackO, ok) {
7048
+ if (sk === null && ok === null) return all && all.length ? all : EMPTY_FACT_INDEX_BUCKET;
7049
+
7050
+ let sBucket = null;
7051
+ if (sk !== null) sBucket = mergeIndexBuckets(exactByS || null, fallbackS || null);
7052
+
7053
+ let oBucket = null;
7054
+ if (ok !== null) oBucket = mergeIndexBuckets(exactByO || null, fallbackO || null);
7055
+
7056
+ if (sk !== null && ok !== null) return sBucket.length <= oBucket.length ? sBucket : oBucket;
7057
+ return sk !== null ? sBucket : oBucket;
7058
+ }
7059
+
6761
7060
  function candidateFacts(facts, goal) {
6762
7061
  ensureFactIndexes(facts);
6763
7062
 
6764
7063
  if (goal.p instanceof Iri) {
6765
7064
  const pk = goal.p.__tid;
6766
7065
 
6767
- const sk = termFastKey(goal.s);
6768
- const ok = termFastKey(goal.o);
7066
+ const sk = termLookupKey(goal.s);
7067
+ const ok = termLookupKey(goal.o);
6769
7068
 
6770
- /** @type {number[] | null} */
6771
7069
  let byPS = null;
6772
7070
  if (sk !== null) {
6773
7071
  const ps = facts.__byPS.get(pk);
6774
7072
  if (ps) byPS = ps.get(sk) || null;
6775
7073
  }
7074
+ const byPNonFastS = sk !== null ? facts.__byPNonFastS.get(pk) || null : null;
6776
7075
 
6777
- /** @type {number[] | null} */
6778
7076
  let byPO = null;
6779
7077
  if (ok !== null) {
6780
7078
  const po = facts.__byPO.get(pk);
6781
7079
  if (po) byPO = po.get(ok) || null;
6782
7080
  }
7081
+ const byPNonFastO = ok !== null ? facts.__byPNonFastO.get(pk) || null : null;
6783
7082
 
6784
- let exact = null;
6785
- if (byPS && byPO) exact = byPS.length <= byPO.length ? byPS : byPO;
6786
- else if (byPS) exact = byPS;
6787
- else if (byPO) exact = byPO;
6788
- else exact = facts.__byPred.get(pk) || null;
7083
+ const exact = selectPositionIndexedCandidates(
7084
+ facts.__byPred.get(pk) || null,
7085
+ byPS,
7086
+ byPNonFastS,
7087
+ sk,
7088
+ byPO,
7089
+ byPNonFastO,
7090
+ ok,
7091
+ );
6789
7092
 
6790
- /** @type {number[] | null} */
6791
- let wildPS = null;
6792
- if (sk !== null) wildPS = facts.__wildPS.get(sk) || null;
7093
+ let varPredPS = null;
7094
+ if (sk !== null) varPredPS = facts.__varPredPS.get(sk) || null;
6793
7095
 
6794
- /** @type {number[] | null} */
6795
- let wildPO = null;
6796
- if (ok !== null) wildPO = facts.__wildPO.get(ok) || null;
7096
+ let varPredPO = null;
7097
+ if (ok !== null) varPredPO = facts.__varPredPO.get(ok) || null;
6797
7098
 
6798
- let wild = null;
6799
- if (wildPS && wildPO) wild = wildPS.length <= wildPO.length ? wildPS : wildPO;
6800
- else if (wildPS) wild = wildPS;
6801
- else if (wildPO) wild = wildPO;
6802
- else wild = facts.__wildPred.length ? facts.__wildPred : null;
7099
+ const wild = selectPositionIndexedCandidates(
7100
+ facts.__varPred,
7101
+ varPredPS,
7102
+ sk !== null ? facts.__varPredNonFastS : null,
7103
+ sk,
7104
+ varPredPO,
7105
+ ok !== null ? facts.__varPredNonFastO : null,
7106
+ ok,
7107
+ );
6803
7108
 
6804
7109
  return {
6805
- exact: exact || null,
6806
- wild: wild || null,
6807
- exactLen: exact ? exact.length : 0,
6808
- wildLen: wild ? wild.length : 0,
6809
- totalLen: (exact ? exact.length : 0) + (wild ? wild.length : 0),
7110
+ exact,
7111
+ wild,
7112
+ exactLen: exact.length,
7113
+ wildLen: wild.length,
7114
+ totalLen: exact.length + wild.length,
6810
7115
  };
6811
7116
  }
6812
7117
 
@@ -6824,20 +7129,28 @@ function hasFactIndexed(facts, tr) {
6824
7129
 
6825
7130
  if (tr.p instanceof Iri) {
6826
7131
  const pk = tr.p.__tid;
7132
+ const sk = termLookupKey(tr.s);
7133
+ let best = null;
6827
7134
 
6828
- const ok = termFastKey(tr.o);
7135
+ if (sk !== null) {
7136
+ const ps = facts.__byPS.get(pk);
7137
+ if (!ps) return false;
7138
+ const psb = ps.get(sk);
7139
+ if (!psb || psb.length === 0) return false;
7140
+ best = psb;
7141
+ }
7142
+
7143
+ const ok = termLookupKey(tr.o);
6829
7144
  if (ok !== null) {
6830
7145
  const po = facts.__byPO.get(pk);
6831
- if (po) {
6832
- const pob = po.get(ok) || [];
6833
- // Facts are all in the same graph. Different blank node labels represent
6834
- // different existentials unless explicitly connected. Do NOT treat
6835
- // triples as duplicates modulo blank renaming, or you'll incorrectly
6836
- // drop facts like: _:sk_0 :x 8.0 (because _:b8 :x 8.0 exists).
6837
- return pob.some((i) => triplesEqual(facts[i], tr));
6838
- }
7146
+ if (!po) return false;
7147
+ const pob = po.get(ok);
7148
+ if (!pob || pob.length === 0) return false;
7149
+ if (!best || pob.length < best.length) best = pob;
6839
7150
  }
6840
7151
 
7152
+ if (best) return best.some((i) => triplesEqual(facts[i], tr));
7153
+
6841
7154
  const pb = facts.__byPred.get(pk) || [];
6842
7155
  return pb.some((i) => triplesEqual(facts[i], tr));
6843
7156
  }
@@ -6946,11 +7259,25 @@ function mergeSinglePremiseAgendaBuckets() {
6946
7259
  return out;
6947
7260
  }
6948
7261
 
7262
+ function termContainsVarForAgenda(t) {
7263
+ if (t instanceof Var) return true;
7264
+ if (t instanceof ListTerm) return t.elems.some(termContainsVarForAgenda);
7265
+ if (t instanceof OpenListTerm) return true;
7266
+ if (t instanceof GraphTerm)
7267
+ return t.triples.some(
7268
+ (tr) =>
7269
+ termContainsVarForAgenda(tr.s) || termContainsVarForAgenda(tr.p) || termContainsVarForAgenda(tr.o),
7270
+ );
7271
+ return false;
7272
+ }
7273
+
6949
7274
  function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
6950
7275
  const index = {
6951
7276
  byPred: new Map(),
7277
+ byPredAll: new Map(),
6952
7278
  byPS: new Map(),
6953
7279
  byPO: new Map(),
7280
+ allIriPred: [],
6954
7281
  wildPred: [],
6955
7282
  wildPS: new Map(),
6956
7283
  wildPO: new Map(),
@@ -6972,8 +7299,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
6972
7299
  if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
6973
7300
 
6974
7301
  const goal = r.premise[0];
6975
- const goalSKey = termFastKey(goal.s);
6976
- const goalOKey = termFastKey(goal.o);
7302
+ const goalSKey = termLookupKey(goal.s);
7303
+ const goalOKey = termLookupKey(goal.o);
6977
7304
  const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
6978
7305
  const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
6979
7306
  const entry = {
@@ -6983,7 +7310,7 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
6983
7310
  goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
6984
7311
  goalSKey,
6985
7312
  goalOKey,
6986
- needsSkipCheck: !!r.__needsForwardSkipCheck,
7313
+ needsSkipCheck: !!(r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)),
6987
7314
  fastSubjectVar,
6988
7315
  fastObjectVar,
6989
7316
  };
@@ -6992,6 +7319,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
6992
7319
  index.size += 1;
6993
7320
 
6994
7321
  if (entry.goalPredTid !== null) {
7322
+ addToMapArray(index.byPredAll, entry.goalPredTid, entry);
7323
+ index.allIriPred.push(entry);
6995
7324
  if (entry.goalSKey === null && entry.goalOKey === null) addToMapArray(index.byPred, entry.goalPredTid, entry);
6996
7325
  if (entry.goalSKey !== null) {
6997
7326
  let ps = index.byPS.get(entry.goalPredTid);
@@ -7022,25 +7351,37 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
7022
7351
  function getSinglePremiseAgendaCandidates(index, fact) {
7023
7352
  if (!index || index.size === 0) return null;
7024
7353
 
7025
- const sk = termFastKey(fact.s);
7026
- const ok = termFastKey(fact.o);
7354
+ const sk = termLookupKey(fact.s);
7355
+ const ok = termLookupKey(fact.o);
7027
7356
 
7028
7357
  let exact = null;
7029
7358
  if (fact.p instanceof Iri) {
7030
7359
  const pk = fact.p.__tid;
7031
- const byPred = index.byPred.get(pk) || null;
7032
- let byPS = null;
7033
- if (sk !== null) {
7360
+ if ((sk === null && termContainsVarForAgenda(fact.s)) || (ok === null && termContainsVarForAgenda(fact.o))) {
7361
+ // A fact with a variable-bearing subject/object (most importantly a
7362
+ // top-level variable fact such as `?S :p ?O.`) can match rules whose
7363
+ // premise is fixed in that position. The ordinary `(p,s)` / `(p,o)` lookup
7364
+ // would miss those rules, so fall back to all agenda-indexed rules for
7365
+ // this predicate. Do not do this merely for non-fast quoted formulas:
7366
+ // they are not wildcards, and broad fallback would over-fire rules that
7367
+ // rely on protected blank-node/formula unification semantics.
7368
+ exact = index.byPredAll.get(pk) || null;
7369
+ } else {
7370
+ const byPred = index.byPred.get(pk) || null;
7371
+ let byPS = null;
7034
7372
  const ps = index.byPS.get(pk);
7035
7373
  if (ps) byPS = ps.get(sk) || null;
7036
- }
7037
- let byPO = null;
7038
- if (ok !== null) {
7374
+ let byPO = null;
7039
7375
  const po = index.byPO.get(pk);
7040
7376
  if (po) byPO = po.get(ok) || null;
7041
- }
7042
7377
 
7043
- exact = mergeSinglePremiseAgendaBuckets(byPred, byPS, byPO);
7378
+ exact = mergeSinglePremiseAgendaBuckets(byPred, byPS, byPO);
7379
+ }
7380
+ } else if (fact.p instanceof Var) {
7381
+ // A variable-predicate fact can match any IRI-predicate agenda rule.
7382
+ // This is deliberately broad and relies on final unification below; such
7383
+ // facts are uncommon and correctness matters more than over-indexing them.
7384
+ exact = index.allIriPred.length ? index.allIriPred : null;
7044
7385
  }
7045
7386
 
7046
7387
  const wildPred = index.wildPred.length ? index.wildPred : null;
@@ -8417,7 +8758,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8417
8758
  let scopedClosureLevel = 0;
8418
8759
 
8419
8760
  // Scan known rules for the maximum requested closure priority in scoped log:* goals.
8420
- let maxScopedClosurePriorityNeeded = __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules);
8761
+ let maxScopedClosurePriorityNeeded = Math.max(
8762
+ __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
8763
+ __computeForwardRuleScopedStrata(forwardRules),
8764
+ );
8421
8765
 
8422
8766
  // Conservative fast-skip for forward rules that cannot possibly succeed
8423
8767
  // until a scoped snapshot exists (or a given closure level is reached).
@@ -8471,12 +8815,16 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8471
8815
  // until a snapshot exists (and a certain closure level is reached).
8472
8816
  // This prevents expensive proofs that will definitely fail in Phase A
8473
8817
  // and in early closure levels.
8474
- const info = r.__scopedSkipInfo;
8818
+ const info = r.__scopedStratumInfo || r.__scopedSkipInfo;
8475
8819
  if (info && info.needsSnap) {
8476
8820
  const snapHere = facts.__scopedSnapshot || null;
8477
8821
  const lvlHere = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
8478
8822
  if (!snapHere) return true;
8479
- if (lvlHere < info.requiredLevel) return true;
8823
+ if (info.exactLevel) {
8824
+ if (lvlHere !== info.requiredLevel) return true;
8825
+ } else if (lvlHere < info.requiredLevel) {
8826
+ return true;
8827
+ }
8480
8828
  }
8481
8829
 
8482
8830
  // Optimization: if the rule head is **structurally ground** (no vars anywhere, even inside
@@ -8676,6 +9024,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8676
9024
 
8677
9025
  const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
8678
9026
  if (outcome.rulesChanged) {
9027
+ maxScopedClosurePriorityNeeded = Math.max(
9028
+ __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9029
+ __computeForwardRuleScopedStrata(forwardRules),
9030
+ );
8679
9031
  agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
8680
9032
  agendaCursor = 0;
8681
9033
  }
@@ -8689,7 +9041,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8689
9041
  for (let i = 0; i < forwardRules.length; i++) {
8690
9042
  const r = forwardRules[i];
8691
9043
  if (agendaIndex.indexed.has(r)) continue;
8692
- if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
9044
+ if ((r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)) && __skipForwardRuleNow(r)) continue;
8693
9045
 
8694
9046
  const headIsStrictGround = r.__headIsStrictGround;
8695
9047
  const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
@@ -8710,6 +9062,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8710
9062
  for (const s of sols) {
8711
9063
  const outcome = __emitForwardRuleSolution(r, i, s);
8712
9064
  if (outcome.rulesChanged) {
9065
+ maxScopedClosurePriorityNeeded = Math.max(
9066
+ __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9067
+ __computeForwardRuleScopedStrata(forwardRules),
9068
+ );
8713
9069
  agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
8714
9070
  agendaCursor = 0;
8715
9071
  }
@@ -8737,8 +9093,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8737
9093
  // Rules may have been added dynamically (rule-producing triples), possibly
8738
9094
  // introducing scoped builtins and/or higher closure priorities.
8739
9095
  maxScopedClosurePriorityNeeded = Math.max(
8740
- maxScopedClosurePriorityNeeded,
8741
9096
  __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9097
+ __computeForwardRuleScopedStrata(forwardRules),
8742
9098
  );
8743
9099
 
8744
9100
  // If there are no scoped builtins in the entire program, Phase B is pure
@@ -8756,8 +9112,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8756
9112
 
8757
9113
  // Phase B can also derive rule-producing triples.
8758
9114
  maxScopedClosurePriorityNeeded = Math.max(
8759
- maxScopedClosurePriorityNeeded,
8760
9115
  __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9116
+ __computeForwardRuleScopedStrata(forwardRules),
8761
9117
  );
8762
9118
 
8763
9119
  if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
@@ -9647,15 +10003,6 @@ function isIdentChar(c) {
9647
10003
  return c === ':' || isPnChars(c);
9648
10004
  }
9649
10005
 
9650
- function canContinueAfterDot(next) {
9651
- // PN_LOCAL allows '.' but it cannot appear at the end.
9652
- // We include '.' only if it is followed by something that could continue a name.
9653
- if (next === null) return false;
9654
- if (isIdentChar(next)) return true;
9655
- if (next === '%' || next === '\\') return true;
9656
- return false;
9657
- }
9658
-
9659
10006
  function isForbiddenNoncharacterCodePoint(cp) {
9660
10007
  return (cp & 0xffff) === 0xfffe || (cp & 0xffff) === 0xffff;
9661
10008
  }