@origintrail-official/dkg-query 10.0.5 → 10.0.7
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/dist/dkg-query-engine.d.ts +18 -9
- package/dist/dkg-query-engine.d.ts.map +1 -1
- package/dist/dkg-query-engine.js +390 -44
- package/dist/dkg-query-engine.js.map +1 -1
- package/dist/query-engine.d.ts +43 -6
- package/dist/query-engine.d.ts.map +1 -1
- package/dist/query-handler.d.ts.map +1 -1
- package/dist/query-handler.js +5 -4
- package/dist/query-handler.js.map +1 -1
- package/dist/sparql-guard.d.ts +31 -0
- package/dist/sparql-guard.d.ts.map +1 -1
- package/dist/sparql-guard.js +9 -78
- package/dist/sparql-guard.js.map +1 -1
- package/dist/sparql-utils.d.ts +1 -11
- package/dist/sparql-utils.d.ts.map +1 -1
- package/dist/sparql-utils.js +1 -85
- package/dist/sparql-utils.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TripleStore
|
|
2
|
-
import type { QueryResult, QueryOptions,
|
|
1
|
+
import type { TripleStore } from '@origintrail-official/dkg-storage';
|
|
2
|
+
import type { QueryResult, QueryOptions, GraphAwareQueryEngine, ResolvedKnowledgeAsset, ResolvedLegacyKnowledgeAsset } from './query-engine.js';
|
|
3
3
|
import { type GetView, TrustLevel } from '@origintrail-official/dkg-core';
|
|
4
4
|
/**
|
|
5
5
|
* Result of resolving a V10 GET view to concrete graph targets.
|
|
@@ -50,7 +50,7 @@ export declare function resolveViewGraphs(view: GetView, contextGraphId: string,
|
|
|
50
50
|
* Isolation). All data must arrive via protocol messages (publish, access,
|
|
51
51
|
* sync) before it can be queried here.
|
|
52
52
|
*/
|
|
53
|
-
export declare class DKGQueryEngine implements
|
|
53
|
+
export declare class DKGQueryEngine implements GraphAwareQueryEngine {
|
|
54
54
|
private readonly store;
|
|
55
55
|
private readonly graphManager;
|
|
56
56
|
private readonly scopedContentGraphAllowListInFlight;
|
|
@@ -65,6 +65,8 @@ export declare class DKGQueryEngine implements QueryEngine {
|
|
|
65
65
|
* Execute a SPARQL query scoped to a declared V10 state view.
|
|
66
66
|
*/
|
|
67
67
|
private queryWithView;
|
|
68
|
+
/** Canonical graph rewrite for root + per-KA/per-cgId verifiable-memory reads. */
|
|
69
|
+
private wrapVerifiableMemoryGraphSet;
|
|
68
70
|
private queryMultipleGraphs;
|
|
69
71
|
private discoverGraphsByPrefix;
|
|
70
72
|
/**
|
|
@@ -78,6 +80,15 @@ export declare class DKGQueryEngine implements QueryEngine {
|
|
|
78
80
|
* private/SWM rows.
|
|
79
81
|
*/
|
|
80
82
|
private discoverContextGraphPerCgIdDataGraphs;
|
|
83
|
+
/**
|
|
84
|
+
* Resolve only the immutable private graphs referenced by valid current V2
|
|
85
|
+
* metadata in this exact context graph. The broad `/_private/` prefix also
|
|
86
|
+
* contains mutable WM drafts, so prefix-enumerating it would let an
|
|
87
|
+
* `includePrivate` query cross the draft/finalized boundary. Deriving each
|
|
88
|
+
* graph from the canonical UAL + assertion version keeps the allow-list
|
|
89
|
+
* fail-closed and makes GRAPH-variable discovery work for rootless KAs.
|
|
90
|
+
*/
|
|
91
|
+
private discoverGraphScopedPrivateGraphs;
|
|
81
92
|
/**
|
|
82
93
|
* Resolve a WM assertion's allocated KA number (`dkg:kaId`) off its lifecycle URN
|
|
83
94
|
* in `_meta`, for single-graph by-name reads under the uniform layout. Returns
|
|
@@ -92,12 +103,10 @@ export declare class DKGQueryEngine implements QueryEngine {
|
|
|
92
103
|
private discoverRegisteredAssertionGraphs;
|
|
93
104
|
private discoverKnownChildContextGraphUris;
|
|
94
105
|
private execAndNormalize;
|
|
95
|
-
resolveKA(ual: string): Promise<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
quads: Quad[];
|
|
100
|
-
}>;
|
|
106
|
+
resolveKA(ual: string): Promise<ResolvedLegacyKnowledgeAsset>;
|
|
107
|
+
resolveKnowledgeAsset(ual: string): Promise<ResolvedKnowledgeAsset>;
|
|
108
|
+
private resolveGraphScopedKA;
|
|
109
|
+
private resolveLegacyRootScopedKA;
|
|
101
110
|
/**
|
|
102
111
|
* Execute a query across all locally-stored context graphs.
|
|
103
112
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dkg-query-engine.d.ts","sourceRoot":"","sources":["../src/dkg-query-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"dkg-query-engine.d.ts","sourceRoot":"","sources":["../src/dkg-query-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAyC,MAAM,mCAAmC,CAAC;AAO5G,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,qBAAqB,EAErB,sBAAsB,EACtB,4BAA4B,EAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAQL,KAAK,OAAO,EAEZ,UAAU,EAOX,MAAM,gCAAgC,CAAC;AAQxC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,cAAc,EAAE,MAAM,EACtB,IAAI,CAAC,EAAE;IACL,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACA,cAAc,CAgKhB;AAED;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,qBAAqB;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAG5C,OAAO,CAAC,QAAQ,CAAC,mCAAmC,CAAwC;gBAEhF,KAAK,EAAE,WAAW;IAKxB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IA+LzE;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;OAEG;YACW,aAAa;IA+J3B,kFAAkF;IAClF,OAAO,CAAC,4BAA4B;YAQtB,mBAAmB;YAsFnB,sBAAsB;IAOpC;;;;;;;;;OASG;YACW,qCAAqC;IASnD;;;;;;;OAOG;YACW,gCAAgC;IAkE9C;;;;;OAKG;YACW,4BAA4B;YAsB5B,mCAAmC;YAqBnC,kCAAkC;YAsBlC,mCAAmC;YA8BnC,+BAA+B;YAsB/B,iCAAiC;YAqBjC,kCAAkC;YA2BlC,gBAAgB;IAmBxB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAU7D,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;YAe3D,oBAAoB;YAsCpB,yBAAyB;IAkFvC;;OAEG;IACG,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAYlE;AAo6BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoCtE"}
|
package/dist/dkg-query-engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { GraphManager } from '@origintrail-official/dkg-storage';
|
|
2
|
-
import { contextGraphDataUri, contextGraphSharedMemoryUri, contextGraphVerifiableMemoryUri, contextGraphAssertionUri,
|
|
1
|
+
import { ExactGraphReadError, GraphManager, readExactGraphPaged, resolveGraphScopedOrLegacyMetadata, } from '@origintrail-official/dkg-storage';
|
|
2
|
+
import { contextGraphDataUri, contextGraphSharedMemoryUri, contextGraphVerifiableMemoryUri, contextGraphAssertionUri, MemoryLayer, contextGraphLayerUriCandidates, contextGraphLayerPrefixCandidates, contextGraphSubGraphUri, contextGraphMetaUri, contextGraphSharedMemoryMetaUri, assertionLifecycleUri, contextGraphSubGraphMetaUri, contextGraphPrivateUri, contextGraphSubGraphPrivateUri, assertSafeIri, escapeSparqlLiteral, validateSubGraphName, ASSERTION_NAMED_GRAPH_PREFIX, isAssertionScopedChildGraph, REMOVED_VIEWS, TrustLevel, TRUST_LEVEL_PREDICATE, GRAPH_KA_CONTENT_SCOPE_VERSION, createGraphKnowledgeAssetScope, knowledgeAssetLayerGraphUri, buildLegacyKnowledgeAssetMetadataQuery, } from '@origintrail-official/dkg-core';
|
|
3
3
|
import { validateReadOnlySparql, emptyResultForSparql, detectSparqlQueryForm, } from './sparql-guard.js';
|
|
4
4
|
import { stripLiteralsAndComments } from './sparql-utils.js';
|
|
5
5
|
export class ScopedQueryViolationError extends Error {
|
|
@@ -38,32 +38,41 @@ export function resolveViewGraphs(view, contextGraphId, opts) {
|
|
|
38
38
|
// `subGraphName` + `assertionName` read targets the ROOT assertion graph and
|
|
39
39
|
// misses sub-graph assertions (Codex review on PR #1132).
|
|
40
40
|
if (opts.assertionName) {
|
|
41
|
+
if (opts.kaNumber !== undefined) {
|
|
42
|
+
return {
|
|
43
|
+
graphs: contextGraphLayerUriCandidates(contextGraphId, MemoryLayer.WorkingMemory, opts.agentAddress, opts.kaNumber, opts.subGraphName),
|
|
44
|
+
graphPrefixes: [],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
41
47
|
return {
|
|
42
|
-
graphs: [opts.
|
|
43
|
-
? contextGraphLayerUri(contextGraphId, MemoryLayer.WorkingMemory, opts.agentAddress, opts.kaNumber, opts.subGraphName)
|
|
44
|
-
: contextGraphAssertionUri(contextGraphId, opts.agentAddress, opts.assertionName, opts.subGraphName)],
|
|
48
|
+
graphs: [contextGraphAssertionUri(contextGraphId, opts.agentAddress, opts.assertionName, opts.subGraphName)],
|
|
45
49
|
graphPrefixes: [],
|
|
46
50
|
};
|
|
47
51
|
}
|
|
48
52
|
// PR #1107 review (🟡): span the primary address AND every
|
|
49
53
|
// same-identity alias so a node default agent's legacy peerId-keyed
|
|
50
54
|
// drafts and rc.17+ wallet-keyed drafts are both visible from one
|
|
51
|
-
// unscoped WM read.
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// unscoped WM read. New full EVM-address writes use the lowercase core
|
|
56
|
+
// identity, while the caller's original casing is retained as a legacy
|
|
57
|
+
// read prefix for graphs written before canonicalization.
|
|
58
|
+
const graphPrefixes = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (const address of [opts.agentAddress, ...(opts.agentAddressAliases ?? [])]) {
|
|
61
|
+
if (!address)
|
|
57
62
|
continue;
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
for (const candidate of contextGraphLayerPrefixCandidates(contextGraphId, MemoryLayer.WorkingMemory, address, opts.subGraphName)) {
|
|
64
|
+
if (seen.has(candidate))
|
|
65
|
+
continue;
|
|
66
|
+
seen.add(candidate);
|
|
67
|
+
graphPrefixes.push(candidate);
|
|
68
|
+
}
|
|
60
69
|
}
|
|
61
70
|
return {
|
|
62
71
|
graphs: [],
|
|
63
72
|
// Combine main's same-identity alias span (#1107 review 🟡) with
|
|
64
73
|
// #1132's sub-graph scoping (#184/#675): one prefix per alias address,
|
|
65
74
|
// each carrying the optional sub-graph suffix.
|
|
66
|
-
graphPrefixes
|
|
75
|
+
graphPrefixes,
|
|
67
76
|
};
|
|
68
77
|
}
|
|
69
78
|
case 'shared-working-memory':
|
|
@@ -265,6 +274,7 @@ export class DKGQueryEngine {
|
|
|
265
274
|
subGraphName
|
|
266
275
|
? contextGraphSubGraphPrivateUri(effectiveContextGraphId, subGraphName)
|
|
267
276
|
: contextGraphPrivateUri(effectiveContextGraphId),
|
|
277
|
+
...(await this.discoverGraphScopedPrivateGraphs(effectiveContextGraphId, subGraphName)),
|
|
268
278
|
]
|
|
269
279
|
: [];
|
|
270
280
|
const explicitAllowedGraphs = [...allowedGraphs, ...metaAllowList, ...privateAllowList];
|
|
@@ -304,7 +314,8 @@ export class DKGQueryEngine {
|
|
|
304
314
|
// Per-KA VM: read-both the published per-KA …/_verifiable_memory/{addr}/{number} + root.
|
|
305
315
|
const vmGraphsInc = await this.discoverGraphsByPrefix(`${dataGraph}/_verifiable_memory/`);
|
|
306
316
|
const dataSparql = vmGraphsInc.length > 0
|
|
307
|
-
? (
|
|
317
|
+
? (this.wrapVerifiableMemoryGraphSet(sparql, [dataGraph, ...vmGraphsInc])
|
|
318
|
+
?? wrapWithGraph(sparql, dataGraph))
|
|
308
319
|
: wrapWithGraph(sparql, dataGraph);
|
|
309
320
|
// Per-KA SWM: union the discovered …/_shared_memory/{addr}/{number} graphs.
|
|
310
321
|
const swmGraphs = await this.discoverGraphsByPrefix(`${sharedMemoryGraph}/`);
|
|
@@ -327,7 +338,8 @@ export class DKGQueryEngine {
|
|
|
327
338
|
// Per-KA VM: read-both the published per-KA …/_verifiable_memory/{addr}/{number} + root.
|
|
328
339
|
const vmGraphs = await this.discoverGraphsByPrefix(`${dataGraph}/_verifiable_memory/`);
|
|
329
340
|
effectiveSparql = vmGraphs.length > 0
|
|
330
|
-
? (
|
|
341
|
+
? (this.wrapVerifiableMemoryGraphSet(sparql, [dataGraph, ...vmGraphs])
|
|
342
|
+
?? wrapWithGraph(sparql, dataGraph))
|
|
331
343
|
: wrapWithGraph(sparql, dataGraph);
|
|
332
344
|
}
|
|
333
345
|
}
|
|
@@ -486,16 +498,46 @@ export class DKGQueryEngine {
|
|
|
486
498
|
if (allGraphs.length === 1) {
|
|
487
499
|
return this.execAndNormalize(wrapWithGraph(effectiveSparql, allGraphs[0]));
|
|
488
500
|
}
|
|
501
|
+
if (view === 'verifiable-memory') {
|
|
502
|
+
const rewritten = this.wrapVerifiableMemoryGraphSet(effectiveSparql, allGraphs);
|
|
503
|
+
if (rewritten !== null)
|
|
504
|
+
return this.execAndNormalize(rewritten);
|
|
505
|
+
}
|
|
489
506
|
return this.queryMultipleGraphs(effectiveSparql, allGraphs);
|
|
490
507
|
}
|
|
508
|
+
/** Canonical graph rewrite for root + per-KA/per-cgId verifiable-memory reads. */
|
|
509
|
+
wrapVerifiableMemoryGraphSet(sparql, graphs) {
|
|
510
|
+
if (graphs.length === 0)
|
|
511
|
+
return sparql;
|
|
512
|
+
if (graphs.length === 1)
|
|
513
|
+
return wrapWithGraph(sparql, graphs[0]);
|
|
514
|
+
return wrapWithDeduplicatedGraphValues(sparql, graphs)
|
|
515
|
+
?? wrapWithGraphValues(sparql, graphs)
|
|
516
|
+
?? wrapWithGraphUnion(sparql, graphs);
|
|
517
|
+
}
|
|
491
518
|
async queryMultipleGraphs(sparql, graphs) {
|
|
492
519
|
if (graphs.length === 0)
|
|
493
520
|
return { bindings: [] };
|
|
494
521
|
if (graphs.length === 1) {
|
|
495
522
|
return this.execAndNormalize(wrapWithGraph(sparql, graphs[0]));
|
|
496
523
|
}
|
|
497
|
-
//
|
|
498
|
-
//
|
|
524
|
+
// Prefer a single `VALUES ?g { … } GRAPH ?g { … }` query: it scopes the
|
|
525
|
+
// read to the named-graph set as ONE basic graph pattern iterated over a
|
|
526
|
+
// table, instead of one `{ GRAPH <g> { … } }` UNION branch per graph. A
|
|
527
|
+
// per-graph UNION over the ~1.4k SWM graphs of a large public context
|
|
528
|
+
// graph builds a ~1.4k-branch UnionNode that makes the oxigraph-server
|
|
529
|
+
// planner blow up and drop the socket ("fetch failed", issue #1596); the
|
|
530
|
+
// VALUES form plans in constant depth. LIMIT/ORDER BY/DISTINCT/aggregate
|
|
531
|
+
// semantics are preserved because those modifiers stay in the outer query
|
|
532
|
+
// around the injected block.
|
|
533
|
+
const valuesSparql = wrapWithGraphValues(sparql, graphs);
|
|
534
|
+
if (valuesSparql !== null) {
|
|
535
|
+
return this.execAndNormalize(valuesSparql);
|
|
536
|
+
}
|
|
537
|
+
// Residual shapes `wrapWithGraphValues` declines (an inner top-level
|
|
538
|
+
// UNION, no locatable WHERE block, or a sentinel-variable collision) keep
|
|
539
|
+
// the original union / per-graph fallback so the #789 form-aware
|
|
540
|
+
// cross-graph merge below is preserved unchanged.
|
|
499
541
|
const unionSparql = wrapWithGraphUnion(sparql, graphs);
|
|
500
542
|
if (unionSparql !== null) {
|
|
501
543
|
return this.execAndNormalize(unionSparql);
|
|
@@ -577,6 +619,67 @@ export class DKGQueryEngine {
|
|
|
577
619
|
return rest.length > 0 && !rest.includes('/');
|
|
578
620
|
});
|
|
579
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* Resolve only the immutable private graphs referenced by valid current V2
|
|
624
|
+
* metadata in this exact context graph. The broad `/_private/` prefix also
|
|
625
|
+
* contains mutable WM drafts, so prefix-enumerating it would let an
|
|
626
|
+
* `includePrivate` query cross the draft/finalized boundary. Deriving each
|
|
627
|
+
* graph from the canonical UAL + assertion version keeps the allow-list
|
|
628
|
+
* fail-closed and makes GRAPH-variable discovery work for rootless KAs.
|
|
629
|
+
*/
|
|
630
|
+
async discoverGraphScopedPrivateGraphs(contextGraphId, subGraphName) {
|
|
631
|
+
const metaGraph = contextGraphMetaUri(contextGraphId);
|
|
632
|
+
const subGraphClause = subGraphName
|
|
633
|
+
? `?ual <http://dkg.io/ontology/subGraphName> "${escapeSparqlLiteral(subGraphName)}" .`
|
|
634
|
+
: `FILTER NOT EXISTS { ?ual <http://dkg.io/ontology/subGraphName> ?_subGraphName . }`;
|
|
635
|
+
const result = await this.store.query(`SELECT DISTINCT ?ual ?scopeVersion ?kaUal ?assertionVersion ?assertionGraph ?privateCount WHERE {
|
|
636
|
+
GRAPH <${assertSafeIri(metaGraph)}> {
|
|
637
|
+
?ual <http://dkg.io/ontology/contentScopeVersion> ?scopeVersion ;
|
|
638
|
+
<http://dkg.io/ontology/kaUal> ?kaUal ;
|
|
639
|
+
<http://dkg.io/ontology/assertionVersion> ?assertionVersion ;
|
|
640
|
+
<http://dkg.io/ontology/assertionGraph> ?assertionGraph ;
|
|
641
|
+
<http://dkg.io/ontology/privateTripleCount> ?privateCount ;
|
|
642
|
+
<http://dkg.io/ontology/status> ?status .
|
|
643
|
+
FILTER(?status IN ("confirmed", "tentative"))
|
|
644
|
+
${subGraphClause}
|
|
645
|
+
}
|
|
646
|
+
}`);
|
|
647
|
+
if (result.type !== 'bindings')
|
|
648
|
+
return [];
|
|
649
|
+
const privateRoot = subGraphName
|
|
650
|
+
? contextGraphSubGraphPrivateUri(contextGraphId, subGraphName)
|
|
651
|
+
: contextGraphPrivateUri(contextGraphId);
|
|
652
|
+
const graphs = new Set();
|
|
653
|
+
for (const row of result.bindings) {
|
|
654
|
+
const ual = row['ual'];
|
|
655
|
+
const metadataUal = row['kaUal'];
|
|
656
|
+
const assertionGraph = row['assertionGraph'];
|
|
657
|
+
const scopeVersion = parseCanonicalIntegerBinding(row['scopeVersion']);
|
|
658
|
+
const assertionVersion = parseCanonicalIntegerBinding(row['assertionVersion']);
|
|
659
|
+
const privateCount = parseCanonicalIntegerBinding(row['privateCount']);
|
|
660
|
+
if (!ual
|
|
661
|
+
|| metadataUal !== ual
|
|
662
|
+
|| !assertionGraph
|
|
663
|
+
|| scopeVersion !== BigInt(GRAPH_KA_CONTENT_SCOPE_VERSION)
|
|
664
|
+
|| assertionVersion === undefined
|
|
665
|
+
|| assertionVersion < 1n
|
|
666
|
+
|| privateCount === undefined
|
|
667
|
+
|| privateCount < 1n) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
const scope = createGraphKnowledgeAssetScope(ual, assertionVersion);
|
|
672
|
+
const expectedPublicGraph = knowledgeAssetLayerGraphUri(contextGraphId, MemoryLayer.VerifiableMemory, scope, subGraphName);
|
|
673
|
+
if (assertionGraph !== expectedPublicGraph)
|
|
674
|
+
continue;
|
|
675
|
+
graphs.add(assertSafeIri(`${privateRoot}/${scope.agentAddress}/${scope.kaNumber}/assertions/${scope.assertionVersion}`));
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
// Invalid or non-canonical metadata never widens the private allow-list.
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return [...graphs];
|
|
682
|
+
}
|
|
580
683
|
/**
|
|
581
684
|
* Resolve a WM assertion's allocated KA number (`dkg:kaId`) off its lifecycle URN
|
|
582
685
|
* in `_meta`, for single-graph by-name reads under the uniform layout. Returns
|
|
@@ -721,28 +824,55 @@ export class DKGQueryEngine {
|
|
|
721
824
|
return { bindings: [] };
|
|
722
825
|
}
|
|
723
826
|
async resolveKA(ual) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
827
|
+
const resolved = await this.resolveKnowledgeAsset(ual);
|
|
828
|
+
if (resolved.contentScopeVersion === GRAPH_KA_CONTENT_SCOPE_VERSION) {
|
|
829
|
+
throw new Error('Graph-scoped Knowledge Assets do not have root entities; use resolveKnowledgeAsset()');
|
|
830
|
+
}
|
|
831
|
+
return resolved;
|
|
832
|
+
}
|
|
833
|
+
async resolveKnowledgeAsset(ual) {
|
|
834
|
+
const safeUal = assertSafeIri(ual);
|
|
835
|
+
const resolved = await resolveGraphScopedOrLegacyMetadata(this.store, safeUal, () => this.resolveLegacyRootScopedKA(safeUal), { source: 'query.resolveKnowledgeAsset.metadata' });
|
|
836
|
+
if (resolved.kind === 'graph') {
|
|
837
|
+
return this.resolveGraphScopedKA(safeUal, resolved.metadata);
|
|
838
|
+
}
|
|
839
|
+
if (resolved.kind === 'legacy')
|
|
840
|
+
return resolved.metadata;
|
|
841
|
+
throw new Error(`KA not found for UAL: ${safeUal}`);
|
|
842
|
+
}
|
|
843
|
+
async resolveGraphScopedKA(ual, metadata) {
|
|
844
|
+
const expectedGraph = metadata.assertionGraph;
|
|
845
|
+
let quads;
|
|
846
|
+
try {
|
|
847
|
+
quads = await readExactGraphPaged(this.store, expectedGraph, {
|
|
848
|
+
expectedQuadCount: metadata.publicTripleCount,
|
|
849
|
+
queryOptions: { source: 'query.resolveKnowledgeAsset' },
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
if (error instanceof ExactGraphReadError && error.code === 'QUAD_COUNT_MISMATCH') {
|
|
854
|
+
throw new Error(`Graph-scoped KA ${ual} graph integrity mismatch: metadata declares ` +
|
|
855
|
+
`${metadata.publicTripleCount} public triples, found ${error.actual ?? 'an unknown count'}`);
|
|
856
|
+
}
|
|
857
|
+
throw error;
|
|
858
|
+
}
|
|
859
|
+
if (quads.length !== metadata.publicTripleCount) {
|
|
860
|
+
throw new Error(`Graph-scoped KA ${ual} graph integrity mismatch: metadata declares ${metadata.publicTripleCount} public triples, found ${quads.length}`);
|
|
744
861
|
}
|
|
745
|
-
|
|
862
|
+
return {
|
|
863
|
+
ual: metadata.scope.ual,
|
|
864
|
+
contentScopeVersion: GRAPH_KA_CONTENT_SCOPE_VERSION,
|
|
865
|
+
assertionVersion: metadata.scope.assertionVersion,
|
|
866
|
+
assertionGraph: expectedGraph,
|
|
867
|
+
rootEntities: [],
|
|
868
|
+
contextGraphId: metadata.contextGraphId,
|
|
869
|
+
quads,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
async resolveLegacyRootScopedKA(ual) {
|
|
873
|
+
// Existing V10 metadata can use either token-row + partOf membership or
|
|
874
|
+
// the collapsed UAL-subject rootEntity form. This path is read-only.
|
|
875
|
+
const metaResult = await this.store.query(buildLegacyKnowledgeAssetMetadataQuery(ual));
|
|
746
876
|
if (metaResult.type !== 'bindings' || metaResult.bindings.length === 0) {
|
|
747
877
|
throw new Error(`KA not found for UAL: ${ual}`);
|
|
748
878
|
}
|
|
@@ -755,9 +885,21 @@ export class DKGQueryEngine {
|
|
|
755
885
|
throw new Error(`KA not found for UAL: ${ual}`);
|
|
756
886
|
}
|
|
757
887
|
const rootEntity = rootEntities[0];
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
888
|
+
const contextGraphUris = [
|
|
889
|
+
...new Set(metaResult.bindings
|
|
890
|
+
.map((row) => row['ctxGraph'])
|
|
891
|
+
.filter((value) => typeof value === 'string' && value.length > 0)),
|
|
892
|
+
];
|
|
893
|
+
if (contextGraphUris.length !== 1) {
|
|
894
|
+
throw new Error(`Legacy KA ${ual} has ambiguous contextGraph metadata`);
|
|
895
|
+
}
|
|
896
|
+
const contextGraphPrefix = 'did:dkg:context-graph:';
|
|
897
|
+
const contextGraphUri = contextGraphUris[0];
|
|
898
|
+
if (!contextGraphUri.startsWith(contextGraphPrefix)) {
|
|
899
|
+
throw new Error(`Legacy KA ${ual} has invalid contextGraph metadata`);
|
|
900
|
+
}
|
|
901
|
+
const contextGraphId = contextGraphUri.slice(contextGraphPrefix.length);
|
|
902
|
+
const sgNameRaw = metaResult.bindings[0]?.['sgName'];
|
|
761
903
|
const subGraphName = sgNameRaw ? sgNameRaw.replace(/^"(.*)".*$/, '$1') : undefined;
|
|
762
904
|
const dataGraph = subGraphName
|
|
763
905
|
? contextGraphSubGraphUri(contextGraphId, subGraphName)
|
|
@@ -765,7 +907,6 @@ export class DKGQueryEngine {
|
|
|
765
907
|
const rootFilter = rootEntities
|
|
766
908
|
.map((root) => `(?s = <${assertSafeIri(root)}> || STRSTARTS(STR(?s), "${escapeSparqlLiteral(root)}/.well-known/genid/"))`)
|
|
767
909
|
.join(' || ');
|
|
768
|
-
// Fetch all triples for every KA member root from the correct data graph.
|
|
769
910
|
const dataResult = await this.store.query(`SELECT ?s ?p ?o WHERE {
|
|
770
911
|
GRAPH <${assertSafeIri(dataGraph)}> {
|
|
771
912
|
?s ?p ?o .
|
|
@@ -780,7 +921,14 @@ export class DKGQueryEngine {
|
|
|
780
921
|
graph: dataGraph,
|
|
781
922
|
}))
|
|
782
923
|
: [];
|
|
783
|
-
return {
|
|
924
|
+
return {
|
|
925
|
+
ual,
|
|
926
|
+
contentScopeVersion: 1,
|
|
927
|
+
rootEntity,
|
|
928
|
+
rootEntities,
|
|
929
|
+
contextGraphId,
|
|
930
|
+
quads,
|
|
931
|
+
};
|
|
784
932
|
}
|
|
785
933
|
/**
|
|
786
934
|
* Execute a query across all locally-stored context graphs.
|
|
@@ -877,6 +1025,17 @@ function stripSparqlLiteralValue(value) {
|
|
|
877
1025
|
return '';
|
|
878
1026
|
return value.replace(/^"/, '').replace(/"(?:\^\^<[^>]+>|@[a-zA-Z-]+)?$/, '');
|
|
879
1027
|
}
|
|
1028
|
+
function parseCanonicalIntegerBinding(value) {
|
|
1029
|
+
const lexical = stripSparqlLiteralValue(value);
|
|
1030
|
+
if (!/^(?:0|[1-9][0-9]*)$/.test(lexical))
|
|
1031
|
+
return undefined;
|
|
1032
|
+
try {
|
|
1033
|
+
return BigInt(lexical);
|
|
1034
|
+
}
|
|
1035
|
+
catch {
|
|
1036
|
+
return undefined;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
880
1039
|
function assertNoCallerDatasetClauses(sparql) {
|
|
881
1040
|
if (hasCallerDatasetClause(sparql)) {
|
|
882
1041
|
throw new ScopedQueryViolationError('FROM clauses are not allowed on scoped local queries');
|
|
@@ -2172,6 +2331,193 @@ function wrapWithGraphUnion(sparql, graphUris) {
|
|
|
2172
2331
|
.join(' UNION ');
|
|
2173
2332
|
return `${before} ${unionBranches} ${after}`;
|
|
2174
2333
|
}
|
|
2334
|
+
/** The graph variable `wrapWithGraphValues` injects. Double-underscore + a
|
|
2335
|
+
* view-specific name so a user query is astronomically unlikely to bind it;
|
|
2336
|
+
* a collision is nonetheless detected and declined (never silently clamped). */
|
|
2337
|
+
const VIEW_GRAPH_SENTINEL = '?__dkgViewGraph';
|
|
2338
|
+
const DEDUP_GRAPH_SENTINEL = '?__dkgDedupGraph';
|
|
2339
|
+
const DEDUP_RANK_SENTINEL = '?__dkgDedupRank';
|
|
2340
|
+
const DEDUP_PRIOR_GRAPH_SENTINEL = '?__dkgDedupPriorGraph';
|
|
2341
|
+
const DEDUP_PRIOR_RANK_SENTINEL = '?__dkgDedupPriorRank';
|
|
2342
|
+
function wrapWithProjectedGraphSubselect(sparql, graphUris, helperVariables, buildGraphPattern, acceptsInner = () => true) {
|
|
2343
|
+
if (hasGraphClause(sparql))
|
|
2344
|
+
return sparql;
|
|
2345
|
+
if (graphUris.length === 0)
|
|
2346
|
+
return sparql;
|
|
2347
|
+
const braceStart = findWhereBraceStart(sparql);
|
|
2348
|
+
if (braceStart === -1)
|
|
2349
|
+
return null;
|
|
2350
|
+
const braceEnd = findMatchingCloseBrace(sparql, braceStart);
|
|
2351
|
+
if (braceEnd === -1)
|
|
2352
|
+
return null;
|
|
2353
|
+
const before = sparql.slice(0, braceStart + 1);
|
|
2354
|
+
const inner = sparql.slice(braceStart + 1, braceEnd);
|
|
2355
|
+
const after = sparql.slice(braceEnd);
|
|
2356
|
+
const graphs = [...new Set(graphUris)];
|
|
2357
|
+
if (graphs.length === 1) {
|
|
2358
|
+
return `${before} GRAPH <${assertSafeIri(graphs[0])}> { ${inner} } ${after}`;
|
|
2359
|
+
}
|
|
2360
|
+
if (/\bUNION\b/i.test(inner) || !acceptsInner(inner))
|
|
2361
|
+
return null;
|
|
2362
|
+
const helperNames = new Set(helperVariables.map((variable) => variable.slice(1)));
|
|
2363
|
+
if (collectQueryVariables(sparql).some((variable) => helperNames.has(variable.slice(1)))) {
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2366
|
+
const innerVars = collectQueryVariables(inner);
|
|
2367
|
+
if (innerVars.length === 0)
|
|
2368
|
+
return null;
|
|
2369
|
+
const graphPattern = buildGraphPattern(inner, graphs);
|
|
2370
|
+
return `${before} { SELECT ${innerVars.join(' ')} WHERE { ${graphPattern} } } ${after}`;
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Run one graph pattern across an ordered graph set while suppressing only a
|
|
2374
|
+
* solution mapping already produced by an earlier graph. The comparison uses
|
|
2375
|
+
* every variable bound by the caller's inner pattern, before the caller's
|
|
2376
|
+
* projection runs. Thus an identical mirrored triple is emitted once, while
|
|
2377
|
+
* distinct triples that both project to the same `?s` still produce two rows.
|
|
2378
|
+
*
|
|
2379
|
+
* Helper graph/rank variables are hidden inside a sub-SELECT, preserving
|
|
2380
|
+
* `SELECT *` and caller DISTINCT semantics. Unsupported/colliding query shapes
|
|
2381
|
+
* return null so the existing generic multi-graph fallback remains available.
|
|
2382
|
+
*/
|
|
2383
|
+
function wrapWithDeduplicatedGraphValues(sparql, graphUris) {
|
|
2384
|
+
return wrapWithProjectedGraphSubselect(sparql, graphUris, [
|
|
2385
|
+
DEDUP_GRAPH_SENTINEL,
|
|
2386
|
+
DEDUP_RANK_SENTINEL,
|
|
2387
|
+
DEDUP_PRIOR_GRAPH_SENTINEL,
|
|
2388
|
+
DEDUP_PRIOR_RANK_SENTINEL,
|
|
2389
|
+
], (inner, graphs) => {
|
|
2390
|
+
const rows = graphs
|
|
2391
|
+
.map((graph, rank) => `(<${assertSafeIri(graph)}> ${rank})`)
|
|
2392
|
+
.join(' ');
|
|
2393
|
+
return [
|
|
2394
|
+
`VALUES (${DEDUP_GRAPH_SENTINEL} ${DEDUP_RANK_SENTINEL}) { ${rows} }`,
|
|
2395
|
+
`GRAPH ${DEDUP_GRAPH_SENTINEL} { ${inner} }`,
|
|
2396
|
+
'FILTER NOT EXISTS {',
|
|
2397
|
+
` VALUES (${DEDUP_PRIOR_GRAPH_SENTINEL} ${DEDUP_PRIOR_RANK_SENTINEL}) { ${rows} }`,
|
|
2398
|
+
` FILTER (${DEDUP_PRIOR_RANK_SENTINEL} < ${DEDUP_RANK_SENTINEL})`,
|
|
2399
|
+
` GRAPH ${DEDUP_PRIOR_GRAPH_SENTINEL} { ${inner} }`,
|
|
2400
|
+
'}',
|
|
2401
|
+
].join(' ');
|
|
2402
|
+
}, isDedupSafeBasicGraphPattern);
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Wrap a query so it runs over a set of named graphs in ONE execution using a
|
|
2406
|
+
* single `VALUES ?g { <g1> … } GRAPH ?g { inner }` block, instead of one
|
|
2407
|
+
* `{ GRAPH <g> { inner } } UNION …` branch per graph.
|
|
2408
|
+
*
|
|
2409
|
+
* Why this exists (issue #1596): the per-graph UNION form (`wrapWithGraphUnion`)
|
|
2410
|
+
* emits an N-branch UnionNode. At the ~1,424 SWM graphs of a large public
|
|
2411
|
+
* context graph that is a ~257 KB query whose union tree makes the
|
|
2412
|
+
* oxigraph-server query planner blow up and reset the connection ("fetch
|
|
2413
|
+
* failed"). A single VALUES table is one basic graph pattern iterated over the
|
|
2414
|
+
* graph list — constant plan depth — so it scales.
|
|
2415
|
+
*
|
|
2416
|
+
* Semantics are preserved. LIMIT/ORDER BY/DISTINCT/GROUP BY/aggregate all live
|
|
2417
|
+
* in the outer query (the `before`/`after` slices) and reduce over the merged
|
|
2418
|
+
* solution set exactly as the UNION form did. The one hazard is the injected
|
|
2419
|
+
* graph variable leaking into the caller's projection: a bare `SELECT *` would
|
|
2420
|
+
* expose `?__dkgViewGraph` as a mystery column, and cross-graph DISTINCT of an
|
|
2421
|
+
* identical triple would split on the graph. Both are neutralised by scoping
|
|
2422
|
+
* the VALUES+GRAPH block inside a sub-SELECT that projects ONLY the user's own
|
|
2423
|
+
* variables (collected from `inner`), so the sentinel never escapes.
|
|
2424
|
+
*
|
|
2425
|
+
* Returns `null` (caller falls back to `wrapWithGraphUnion` / per-graph
|
|
2426
|
+
* execution) when the WHERE block cannot be located, when `inner` carries a
|
|
2427
|
+
* top-level UNION (kept on the #789 form-aware fallback so its behaviour and
|
|
2428
|
+
* tests are untouched), when the user query already binds the sentinel, or
|
|
2429
|
+
* when the WHERE body binds no user variable at all (a var-less body offers
|
|
2430
|
+
* nowhere to hide the sentinel from a `SELECT *` projection).
|
|
2431
|
+
*/
|
|
2432
|
+
function wrapWithGraphValues(sparql, graphUris) {
|
|
2433
|
+
return wrapWithProjectedGraphSubselect(sparql, graphUris, [VIEW_GRAPH_SENTINEL], (inner, graphs) => {
|
|
2434
|
+
const values = graphs.map((graph) => `<${assertSafeIri(graph)}>`).join(' ');
|
|
2435
|
+
return `VALUES ${VIEW_GRAPH_SENTINEL} { ${values} } GRAPH ${VIEW_GRAPH_SENTINEL} { ${inner} }`;
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
/**
|
|
2439
|
+
* The mirror anti-join is valid only for a flat basic graph pattern (plus
|
|
2440
|
+
* FILTER expressions). Nested graph patterns can differ by boundness, where a
|
|
2441
|
+
* correlated NOT EXISTS compatibility check is not exact mapping equality.
|
|
2442
|
+
*/
|
|
2443
|
+
function isDedupSafeBasicGraphPattern(inner) {
|
|
2444
|
+
const forbidden = ['OPTIONAL', 'MINUS', 'SERVICE', 'VALUES', 'BIND', 'SELECT', 'GRAPH', 'EXISTS'];
|
|
2445
|
+
let i = 0;
|
|
2446
|
+
while (i < inner.length) {
|
|
2447
|
+
const ch = inner[i];
|
|
2448
|
+
if (ch === '#') {
|
|
2449
|
+
while (i < inner.length && inner[i] !== '\n')
|
|
2450
|
+
i++;
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
if (ch === '"' || ch === "'") {
|
|
2454
|
+
i = skipSparqlStringLiteral(inner, i);
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
if (ch === '<') {
|
|
2458
|
+
const end = skipSparqlIriRef(inner, i);
|
|
2459
|
+
i = end ?? i + 1;
|
|
2460
|
+
continue;
|
|
2461
|
+
}
|
|
2462
|
+
if (ch === '{' || ch === '}')
|
|
2463
|
+
return false;
|
|
2464
|
+
if (isKeywordStart(inner, i)) {
|
|
2465
|
+
let end = i + 1;
|
|
2466
|
+
while (end < inner.length && isWordContinuation(inner[end]))
|
|
2467
|
+
end++;
|
|
2468
|
+
if (forbidden.some((keyword) => isSparqlKeyword(inner, i, end, keyword))) {
|
|
2469
|
+
return false;
|
|
2470
|
+
}
|
|
2471
|
+
i = end;
|
|
2472
|
+
continue;
|
|
2473
|
+
}
|
|
2474
|
+
i++;
|
|
2475
|
+
}
|
|
2476
|
+
return true;
|
|
2477
|
+
}
|
|
2478
|
+
/**
|
|
2479
|
+
* Collect every distinct SPARQL variable (`?x` / `$x`, in first-seen order,
|
|
2480
|
+
* sigil included) in a query fragment, skipping tokens inside line comments,
|
|
2481
|
+
* string literals, and IRI refs. Reuses the same literal/comment/IRI-aware
|
|
2482
|
+
* primitives as `collectGraphVariables` so a `?`/`$` inside a literal or IRI is
|
|
2483
|
+
* never mistaken for a variable.
|
|
2484
|
+
*/
|
|
2485
|
+
function collectQueryVariables(sparql) {
|
|
2486
|
+
const variables = [];
|
|
2487
|
+
const seen = new Set();
|
|
2488
|
+
const n = sparql.length;
|
|
2489
|
+
let i = 0;
|
|
2490
|
+
while (i < n) {
|
|
2491
|
+
const ch = sparql[i];
|
|
2492
|
+
if (ch === '#') {
|
|
2493
|
+
while (i < n && sparql[i] !== '\n')
|
|
2494
|
+
i++;
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
if (ch === '"' || ch === "'") {
|
|
2498
|
+
i = skipSparqlStringLiteral(sparql, i);
|
|
2499
|
+
continue;
|
|
2500
|
+
}
|
|
2501
|
+
if (ch === '<') {
|
|
2502
|
+
const end = skipSparqlIriRef(sparql, i);
|
|
2503
|
+
i = end ?? i + 1;
|
|
2504
|
+
continue;
|
|
2505
|
+
}
|
|
2506
|
+
if (ch === '?' || ch === '$') {
|
|
2507
|
+
const variable = readSparqlVariable(sparql, i);
|
|
2508
|
+
if (variable) {
|
|
2509
|
+
if (!seen.has(variable)) {
|
|
2510
|
+
seen.add(variable);
|
|
2511
|
+
variables.push(variable);
|
|
2512
|
+
}
|
|
2513
|
+
i += variable.length;
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
i++;
|
|
2518
|
+
}
|
|
2519
|
+
return variables;
|
|
2520
|
+
}
|
|
2175
2521
|
/**
|
|
2176
2522
|
* Rewrites a SPARQL query so EVERY subject variable used in its WHERE
|
|
2177
2523
|
* block also matches `<http://dkg.io/ontology/trustLevel> ?__trustN`
|