@weftlet/toolkit 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/application/application.d.ts +10 -0
- package/dist/application/application.js +20 -0
- package/dist/application/document-code-bindings.d.ts +11 -0
- package/dist/application/document-code-bindings.js +25 -0
- package/dist/application/document-obligations.d.ts +8 -0
- package/dist/application/document-obligations.js +11 -0
- package/dist/application/draft-facts.d.ts +3 -0
- package/dist/application/draft-facts.js +37 -0
- package/dist/application/extraction-identity.d.ts +2 -2
- package/dist/application/extraction-identity.js +7 -1
- package/dist/application/extractor-results.d.ts +6 -0
- package/dist/application/extractor-results.js +6 -0
- package/dist/application/hydrator.js +20 -3
- package/dist/application/mapping-proposals.d.ts +108 -0
- package/dist/application/mapping-proposals.js +142 -0
- package/dist/application/mapping-service.d.ts +2 -0
- package/dist/application/mapping-service.js +5 -0
- package/dist/application/overlay.js +36 -22
- package/dist/application/saved-code-coverage.d.ts +40 -0
- package/dist/application/saved-code-coverage.js +20 -0
- package/dist/application/saved-compatibility.d.ts +38 -0
- package/dist/application/saved-compatibility.js +20 -0
- package/dist/application/saved-evidence.d.ts +17 -11
- package/dist/application/saved-evidence.js +44 -40
- package/dist/application/saved-exploration.d.ts +57 -0
- package/dist/application/saved-exploration.js +21 -0
- package/dist/application/saved-paths.d.ts +58 -0
- package/dist/application/saved-paths.js +20 -0
- package/dist/extraction/call-syntax.d.ts +18 -0
- package/dist/extraction/call-syntax.js +61 -0
- package/dist/extraction/canonical-document-links.d.ts +5 -1
- package/dist/extraction/canonical-document-links.js +4 -4
- package/dist/extraction/code-inventory.d.ts +6 -0
- package/dist/extraction/code-inventory.js +26 -0
- package/dist/extraction/document-code-bindings.d.ts +14 -0
- package/dist/extraction/document-code-bindings.js +121 -0
- package/dist/extraction/document-obligations.d.ts +12 -0
- package/dist/extraction/document-obligations.js +112 -0
- package/dist/extraction/mapping-candidates.d.ts +13 -0
- package/dist/extraction/mapping-candidates.js +46 -0
- package/dist/extraction/mapping-output.d.ts +75 -0
- package/dist/extraction/mapping-output.js +53 -0
- package/dist/extraction/mapping-plan.d.ts +7 -0
- package/dist/extraction/mapping-plan.js +117 -0
- package/dist/extraction/mapping-prompt.d.ts +2 -0
- package/dist/extraction/mapping-prompt.js +17 -0
- package/dist/extraction/mapping-provider.d.ts +15 -0
- package/dist/extraction/mapping-provider.js +60 -0
- package/dist/extraction/mapping-request.d.ts +3 -0
- package/dist/extraction/mapping-request.js +32 -0
- package/dist/extraction/ontology.js +2 -2
- package/dist/extraction/routing.js +5 -20
- package/dist/extraction/scip-occurrences.d.ts +3 -0
- package/dist/extraction/scip-occurrences.js +102 -0
- package/dist/extraction/scip-ranges.d.ts +6 -0
- package/dist/extraction/scip-ranges.js +31 -0
- package/dist/extraction/scip-runtime.d.ts +39 -0
- package/dist/extraction/scip-runtime.js +8 -0
- package/dist/extraction/scip-symbols.d.ts +21 -0
- package/dist/extraction/scip-symbols.js +91 -0
- package/dist/extraction/semantic.d.ts +1 -1
- package/dist/extraction/semantic.js +5 -17
- package/dist/extraction/static-calls.d.ts +10 -0
- package/dist/extraction/static-calls.js +133 -0
- package/dist/extraction/static.d.ts +3 -20
- package/dist/extraction/static.js +33 -104
- package/dist/extraction/symbol-resolution.d.ts +12 -0
- package/dist/extraction/symbol-resolution.js +51 -0
- package/dist/extraction/symbol-selectors.d.ts +11 -0
- package/dist/extraction/symbol-selectors.js +51 -0
- package/dist/graph/binding-rdf.d.ts +7 -0
- package/dist/graph/binding-rdf.js +109 -0
- package/dist/graph/call-rdf.d.ts +4 -0
- package/dist/graph/call-rdf.js +94 -0
- package/dist/graph/call-shapes.d.ts +2 -0
- package/dist/graph/call-shapes.js +28 -0
- package/dist/graph/code-coverage-projection.d.ts +9 -0
- package/dist/graph/code-coverage-projection.js +117 -0
- package/dist/graph/code-coverage-renderer.d.ts +2 -0
- package/dist/graph/code-coverage-renderer.js +15 -0
- package/dist/graph/code-coverage-request.d.ts +6 -0
- package/dist/graph/code-coverage-request.js +19 -0
- package/dist/graph/code-coverage.d.ts +6 -0
- package/dist/graph/code-coverage.js +54 -0
- package/dist/graph/code-description.d.ts +4 -0
- package/dist/graph/code-description.js +24 -0
- package/dist/graph/code-rdf.d.ts +5 -0
- package/dist/graph/code-rdf.js +72 -0
- package/dist/graph/compatibility-context.d.ts +2 -0
- package/dist/graph/compatibility-context.js +18 -0
- package/dist/graph/compatibility-renderer.d.ts +2 -0
- package/dist/graph/compatibility-renderer.js +45 -0
- package/dist/graph/compatibility-request.d.ts +3 -0
- package/dist/graph/compatibility-request.js +18 -0
- package/dist/graph/compatibility.d.ts +7 -0
- package/dist/graph/compatibility.js +67 -0
- package/dist/graph/constraint-comparison.d.ts +7 -0
- package/dist/graph/constraint-comparison.js +49 -0
- package/dist/graph/coverage.d.ts +2 -2
- package/dist/graph/coverage.js +8 -3
- package/dist/graph/diagnostics.d.ts +1 -1
- package/dist/graph/diagnostics.js +10 -6
- package/dist/graph/evidence-description.d.ts +3 -0
- package/dist/graph/evidence-description.js +13 -0
- package/dist/graph/identity-preflight.js +1 -1
- package/dist/graph/neighborhood-renderer.d.ts +2 -0
- package/dist/graph/neighborhood-renderer.js +38 -0
- package/dist/graph/neighborhood-request.d.ts +9 -0
- package/dist/graph/neighborhood-request.js +37 -0
- package/dist/graph/neighborhood.d.ts +8 -0
- package/dist/graph/neighborhood.js +100 -0
- package/dist/graph/obligation-rdf.d.ts +5 -0
- package/dist/graph/obligation-rdf.js +67 -0
- package/dist/graph/obligation-shapes.d.ts +2 -0
- package/dist/graph/obligation-shapes.js +32 -0
- package/dist/graph/path-request.d.ts +11 -0
- package/dist/graph/path-request.js +24 -0
- package/dist/graph/path-search.d.ts +45 -0
- package/dist/graph/path-search.js +101 -0
- package/dist/graph/paths-renderer.d.ts +2 -0
- package/dist/graph/paths-renderer.js +36 -0
- package/dist/graph/paths.d.ts +7 -0
- package/dist/graph/paths.js +74 -0
- package/dist/graph/query.js +23 -6
- package/dist/graph/rdf.js +26 -0
- package/dist/graph/shacl-shapes.js +70 -0
- package/dist/graph/snapshot-context.d.ts +3 -0
- package/dist/graph/snapshot-context.js +8 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +22 -0
- package/dist/infrastructure/artifact-paths.d.ts +4 -0
- package/dist/infrastructure/artifact-paths.js +14 -0
- package/dist/infrastructure/json-digest.d.ts +2 -0
- package/dist/infrastructure/json-digest.js +3 -0
- package/dist/infrastructure/unique-json.d.ts +2 -0
- package/dist/infrastructure/unique-json.js +20 -0
- package/dist/interfaces/cli.js +33 -1
- package/dist/interfaces/code-coverage.d.ts +46 -0
- package/dist/interfaces/code-coverage.js +18 -0
- package/dist/interfaces/graph-compatibility.d.ts +35 -0
- package/dist/interfaces/graph-compatibility.js +21 -0
- package/dist/interfaces/graph-exploration.d.ts +59 -0
- package/dist/interfaces/graph-exploration.js +27 -0
- package/dist/interfaces/graph-paths.d.ts +77 -0
- package/dist/interfaces/graph-paths.js +31 -0
- package/dist/interfaces/graph-query-args.d.ts +2 -0
- package/dist/interfaces/graph-query-args.js +25 -0
- package/dist/interfaces/mapping-proposals.d.ts +204 -0
- package/dist/interfaces/mapping-proposals.js +74 -0
- package/dist/interfaces/mcp.js +31 -3
- package/dist/storage/mapping-proposals.d.ts +9 -0
- package/dist/storage/mapping-proposals.js +42 -0
- package/dist/storage/storage.js +41 -13
- package/dist/visualization/evidence-sources.d.ts +6 -0
- package/dist/visualization/evidence-sources.js +19 -0
- package/dist/visualization/viewer-interaction.d.ts +5 -8
- package/dist/visualization/viewer-interaction.js +46 -44
- package/dist/visualization/visualization.js +14 -5
- package/docs/1.0-WORK-QUEUE.md +38 -0
- package/docs/ADR-028-bounded-artifact-paths.md +58 -0
- package/docs/ADR-029-exact-graph-neighborhoods.md +70 -0
- package/docs/ADR-030-bounded-evidence-paths.md +97 -0
- package/docs/ADR-031-code-extractor-selection.md +91 -0
- package/docs/ADR-032-scip-code-evidence.md +73 -0
- package/docs/ADR-033-document-symbol-bindings.md +83 -0
- package/docs/ADR-034-mapping-gaps.md +80 -0
- package/docs/ADR-035-static-call-evidence.md +78 -0
- package/docs/ADR-036-contract-obligation-declarations.md +124 -0
- package/docs/ADR-037-selected-contract-compatibility.md +93 -0
- package/docs/ADR-038-monorepo-code-coverage.md +105 -0
- package/docs/ADR-039-mapping-proposals.md +119 -0
- package/docs/CONSUMER-TRIAL.md +6 -4
- package/docs/GRAPHIFY-EVALUATION.md +87 -0
- package/docs/PRE-1.0-BASELINE.md +141 -0
- package/docs/RELEASE-1.0-PLAN.md +37 -15
- package/docs/RELEASING.md +3 -3
- package/docs/ROADMAP.md +39 -11
- package/docs/VALIDATION.md +501 -0
- package/docs/assessments/pre-1.0-2026-10-28.json +200 -0
- package/docs/extraction-manifest.json +258 -23
- package/package.json +8 -4
- package/templates/context-graph-development/SKILL.md +30 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { snapshotContext } from './snapshot-context.js';
|
|
2
|
+
import { sha256, stableJson } from '../infrastructure/util.js';
|
|
3
|
+
import { RdfEvidenceGraph } from './rdf.js';
|
|
4
|
+
import { coverageForFacts } from './coverage.js';
|
|
5
|
+
import { parseGraphNeighborsRequest } from './neighborhood-request.js';
|
|
6
|
+
import { renderGraphNeighborhood } from './neighborhood-renderer.js';
|
|
7
|
+
import { selectDiagnostics } from './diagnostics.js';
|
|
8
|
+
export const GRAPH_NEIGHBORHOOD_VERSION = 'exact-neighborhood-v1';
|
|
9
|
+
function pageOffset(token, binding, length, pageSize) {
|
|
10
|
+
if (token === undefined)
|
|
11
|
+
return 0;
|
|
12
|
+
let value;
|
|
13
|
+
try {
|
|
14
|
+
if (!/^[A-Za-z0-9_-]+$/.test(token))
|
|
15
|
+
throw new Error();
|
|
16
|
+
value = JSON.parse(Buffer.from(token, 'base64url').toString('utf8'));
|
|
17
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) ||
|
|
18
|
+
Object.keys(value).sort().join(',') !== 'binding,offset,version')
|
|
19
|
+
throw new Error();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new Error('Invalid graph.neighbors continuation');
|
|
23
|
+
}
|
|
24
|
+
if (value.version !== GRAPH_NEIGHBORHOOD_VERSION || value.binding !== binding) {
|
|
25
|
+
throw new Error('Continuation does not match this snapshot, node, filters or page size; restart graph.neighbors');
|
|
26
|
+
}
|
|
27
|
+
if (!Number.isSafeInteger(value.offset) || Number(value.offset) < 1 || Number(value.offset) >= length ||
|
|
28
|
+
Number(value.offset) % pageSize !== 0)
|
|
29
|
+
throw new Error('Invalid graph.neighbors continuation offset');
|
|
30
|
+
return Number(value.offset);
|
|
31
|
+
}
|
|
32
|
+
function opposite(fact, nodeId) {
|
|
33
|
+
return fact.subject.id === nodeId ? fact.object.id : fact.subject.id;
|
|
34
|
+
}
|
|
35
|
+
/** One-hop, exact-ID exploration over the same lossless RDF projection as impact.scan. */
|
|
36
|
+
export class SnapshotGraphExplorationService {
|
|
37
|
+
store;
|
|
38
|
+
constructor(store) {
|
|
39
|
+
this.store = store;
|
|
40
|
+
}
|
|
41
|
+
async neighbors(input) {
|
|
42
|
+
const request = parseGraphNeighborsRequest(input);
|
|
43
|
+
const saved = await this.store.read(request.snapshotId);
|
|
44
|
+
if (!saved)
|
|
45
|
+
throw new Error('No graph snapshot is available for that identifier');
|
|
46
|
+
if (saved.manifest.snapshotId !== request.snapshotId)
|
|
47
|
+
throw new Error('Saved snapshot identity does not match the requested identifier');
|
|
48
|
+
const all = await new RdfEvidenceGraph(saved).facts();
|
|
49
|
+
const adjacency = new Map();
|
|
50
|
+
const factIds = new Set();
|
|
51
|
+
for (const fact of all) {
|
|
52
|
+
if (factIds.has(fact.id))
|
|
53
|
+
throw new Error('Duplicate fact identities prevent stable graph.neighbors pagination');
|
|
54
|
+
factIds.add(fact.id);
|
|
55
|
+
for (const nodeId of new Set([fact.subject.id, fact.object.id])) {
|
|
56
|
+
const adjacent = adjacency.get(nodeId) ?? [];
|
|
57
|
+
adjacent.push(fact);
|
|
58
|
+
adjacency.set(nodeId, adjacent);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const incident = adjacency.get(request.nodeId) ?? [];
|
|
62
|
+
const selected = incident.filter(fact => (request.direction === 'both' || (request.direction === 'outgoing' ? fact.subject.id : fact.object.id) === request.nodeId) &&
|
|
63
|
+
(!request.predicates.length || request.predicates.includes(fact.predicate)))
|
|
64
|
+
.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
65
|
+
const { continuation: _continuation, ...query } = request;
|
|
66
|
+
// Bind to actual projected facts too: even a corrupt store reusing a manifest digest
|
|
67
|
+
// must not silently turn a saved continuation into a different neighborhood/frontier.
|
|
68
|
+
const binding = sha256(stableJson({ version: GRAPH_NEIGHBORHOOD_VERSION, query, manifest: saved.manifest,
|
|
69
|
+
facts: all, diagnostics: saved.diagnostics, unresolved: saved.unresolved }));
|
|
70
|
+
const offset = pageOffset(request.continuation, binding, selected.length, request.maxFacts);
|
|
71
|
+
const facts = selected.slice(offset, offset + request.maxFacts);
|
|
72
|
+
const end = offset + facts.length;
|
|
73
|
+
const next = end < selected.length ? Buffer.from(JSON.stringify({
|
|
74
|
+
version: GRAPH_NEIGHBORHOOD_VERSION, binding, offset: end,
|
|
75
|
+
})).toString('base64url') : undefined;
|
|
76
|
+
const neighborIds = (items) => new Set(items.map(fact => opposite(fact, request.nodeId))
|
|
77
|
+
.filter(id => id !== request.nodeId));
|
|
78
|
+
const totalNeighbors = neighborIds(selected).size;
|
|
79
|
+
const neighbors = [...neighborIds(facts)].sort().map(nodeId => ({
|
|
80
|
+
nodeId,
|
|
81
|
+
factIds: facts.filter(fact => opposite(fact, request.nodeId) === nodeId).map(fact => fact.id),
|
|
82
|
+
otherIncidentFacts: adjacency.get(nodeId).filter(fact => fact.subject.id !== request.nodeId && fact.object.id !== request.nodeId).length,
|
|
83
|
+
}));
|
|
84
|
+
const manifest = saved.manifest;
|
|
85
|
+
const plain = {
|
|
86
|
+
snapshot: snapshotContext(manifest),
|
|
87
|
+
focus: { nodeId: request.nodeId, status: incident.length ? 'present' : 'absent-from-snapshot' },
|
|
88
|
+
facts,
|
|
89
|
+
coverage: coverageForFacts(facts, saved),
|
|
90
|
+
diagnostics: selectDiagnostics(saved, {}, facts, [], [], new Set([request.nodeId, ...facts.flatMap(fact => [fact.subject.id, fact.object.id])])),
|
|
91
|
+
selection: { version: GRAPH_NEIGHBORHOOD_VERSION, direction: request.direction, predicates: request.predicates,
|
|
92
|
+
maxFacts: request.maxFacts, incidentFacts: incident.length, matchingFacts: selected.length,
|
|
93
|
+
filteredFacts: incident.length - selected.length, offset, returnedFacts: facts.length,
|
|
94
|
+
remainingFacts: selected.length - end, truncated: Boolean(next), ...(next ? { continuation: next } : {}) },
|
|
95
|
+
frontier: { scope: 'selected-one-hop-neighborhood', totalNeighbors,
|
|
96
|
+
remainingNeighbors: totalNeighbors - neighborIds(selected.slice(0, end)).size, neighbors },
|
|
97
|
+
};
|
|
98
|
+
return { ...plain, markdown: renderGraphNeighborhood(plain) };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Store, type NamedNode } from 'n3';
|
|
2
|
+
import { type GraphFact } from '@weftlet/core';
|
|
3
|
+
export declare function addObligationEvidence(dataset: Store, record: NamedNode, fact: GraphFact): void;
|
|
4
|
+
/** Structural integrity only: declarations cannot prove fulfillment, compatibility or runtime delivery. */
|
|
5
|
+
export declare function obligationEvidenceIssues(fact: GraphFact, repositoryId: string): readonly string[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DataFactory } from 'n3';
|
|
2
|
+
import { obligationContractNode, parseObligationDeclaration, repositoryFileNode } from '@weftlet/core';
|
|
3
|
+
import { CG, RDF, XSD } from './shacl-shapes.js';
|
|
4
|
+
const { namedNode: iri, literal } = DataFactory;
|
|
5
|
+
export function addObligationEvidence(dataset, record, fact) {
|
|
6
|
+
const evidence = fact.contractObligation;
|
|
7
|
+
if (!evidence && fact.extractor.id !== 'document-obligation-extractor')
|
|
8
|
+
return;
|
|
9
|
+
dataset.addQuad(record, iri(RDF + 'type'), iri(CG + 'ContractObligation'));
|
|
10
|
+
if (!evidence)
|
|
11
|
+
return;
|
|
12
|
+
const prop = (node, key, value, datatype = 'string') => {
|
|
13
|
+
if (value !== undefined)
|
|
14
|
+
dataset.addQuad(node, iri(CG + key), literal(String(value), iri(XSD + datatype)));
|
|
15
|
+
};
|
|
16
|
+
const item = evidence.declaration ?? {};
|
|
17
|
+
const contract = item.contract, constraint = item.constraint;
|
|
18
|
+
prop(record, 'obligationVersion', item.schemaVersion, 'integer');
|
|
19
|
+
for (const [key, value] of Object.entries({ obligationMethod: evidence.method, obligationRepository: evidence.repositoryId,
|
|
20
|
+
obligationId: item.id, obligationRole: item.role, obligationStatement: item.statement,
|
|
21
|
+
obligationScope: contract?.scope, obligationContract: contract?.id, obligationTransport: contract?.kind,
|
|
22
|
+
obligationContractVersion: contract?.version, obligationDomain: item.domain, obligationStage: item.stage,
|
|
23
|
+
obligationRevalidation: item.revalidation, obligationMetric: constraint?.metric,
|
|
24
|
+
obligationOperator: constraint?.operator, obligationUnit: constraint?.unit }))
|
|
25
|
+
prop(record, key, value);
|
|
26
|
+
prop(record, 'obligationValue', constraint?.value, 'double');
|
|
27
|
+
if (evidence.endpointSource) {
|
|
28
|
+
const endpoint = iri(`${record.value}:obligation-endpoint`);
|
|
29
|
+
dataset.addQuad(record, iri(CG + 'obligationEndpoint'), endpoint);
|
|
30
|
+
dataset.addQuad(endpoint, iri(RDF + 'type'), iri(CG + 'ObligationEndpoint'));
|
|
31
|
+
for (const [key, value] of Object.entries(evidence.endpointSource))
|
|
32
|
+
prop(endpoint, key, value, typeof value === 'number' ? 'integer' : 'string');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function identityMatches(fact, declaration, repositoryId) {
|
|
36
|
+
const expected = repositoryFileNode(declaration.endpoint);
|
|
37
|
+
return fact.contractObligation.repositoryId === repositoryId &&
|
|
38
|
+
fact.object.id === obligationContractNode(repositoryId, declaration.contract).id && fact.object.kind === 'Contract' &&
|
|
39
|
+
fact.subject.id === expected.id && fact.subject.kind === expected.kind &&
|
|
40
|
+
fact.predicate === (declaration.role === 'guarantee' ? 'emits' : 'consumes');
|
|
41
|
+
}
|
|
42
|
+
/** Structural integrity only: declarations cannot prove fulfillment, compatibility or runtime delivery. */
|
|
43
|
+
export function obligationEvidenceIssues(fact, repositoryId) {
|
|
44
|
+
const evidence = fact.contractObligation;
|
|
45
|
+
if (!evidence)
|
|
46
|
+
return fact.extractor.id === 'document-obligation-extractor' ? ['Obligation extraction requires declaration metadata'] : [];
|
|
47
|
+
let declaration;
|
|
48
|
+
try {
|
|
49
|
+
declaration = parseObligationDeclaration(evidence.declaration);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return ['Malformed obligation declaration'];
|
|
53
|
+
}
|
|
54
|
+
const issues = [], endpoint = evidence.endpointSource;
|
|
55
|
+
if (!identityMatches(fact, declaration, repositoryId))
|
|
56
|
+
issues.push('Obligation role or scoped contract/endpoint identity does not match its declaration');
|
|
57
|
+
if (!endpoint || endpoint.path !== declaration.endpoint || endpoint.commitSha !== fact.provenance.commitSha ||
|
|
58
|
+
endpoint.endLine < endpoint.startLine)
|
|
59
|
+
issues.push('Obligation endpoint citation does not match its declared file/revision');
|
|
60
|
+
if (fact.codeBinding || fact.codeCall || fact.codeOccurrence)
|
|
61
|
+
issues.push('Obligation declarations must remain separate from implementation evidence');
|
|
62
|
+
if (fact.sourceRole === 'validation')
|
|
63
|
+
issues.push('Validation evidence cannot declare product obligations');
|
|
64
|
+
if (fact.authority === 'draft' && fact.sourceAuthority !== 'draft')
|
|
65
|
+
issues.push('Draft obligations require draft source authority');
|
|
66
|
+
return issues;
|
|
67
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Shapes target only obligation records, keeping ordinary-fact validation cost unchanged. */
|
|
2
|
+
export declare const obligationShapes = "\ncg:ContractObligationShape a sh:NodeShape; sh:targetClass cg:ContractObligation;\n sh:property [ sh:path cg:obligationVersion; sh:minCount 1; sh:maxCount 1; sh:hasValue 1 ];\n sh:property [ sh:path cg:obligationMethod; sh:minCount 1; sh:maxCount 1; sh:hasValue \"explicit-markdown\" ];\n sh:property [ sh:path cg:obligationRole; sh:minCount 1; sh:maxCount 1; sh:in (\"guarantee\" \"requirement\") ];\n sh:property [ sh:path cg:obligationId; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationStatement; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationRepository; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationScope; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationContract; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationTransport; sh:minCount 1; sh:maxCount 1; sh:in (\"queue\" \"stored-data\") ];\n sh:property [ sh:path cg:obligationContractVersion; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationDomain; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationStage; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationRevalidation; sh:maxCount 1; sh:in (\"required\" \"not-required\") ];\n sh:property [ sh:path cg:obligationEndpoint; sh:minCount 1; sh:maxCount 1; sh:class cg:ObligationEndpoint ];\n sh:property [ sh:path cg:authority; sh:in (\"inferred\" \"draft\") ];\n sh:property [ sh:path cg:extractionReview; sh:minCount 1; sh:hasValue \"deterministic\" ];\n sh:property [ sh:path rdf:predicate; sh:in (cg:emits cg:consumes) ].\ncg:ObligationEndpointShape a sh:NodeShape; sh:targetClass cg:ObligationEndpoint;\n sh:property [ sh:path cg:path; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:digest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:commitSha; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:startLine; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ];\n sh:property [ sh:path cg:endLine; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ].\ncg:ObligationConstraintShape a sh:NodeShape; sh:targetSubjectsOf cg:obligationMetric, cg:obligationOperator, cg:obligationValue, cg:obligationUnit;\n sh:property [ sh:path cg:obligationMetric; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];\n sh:property [ sh:path cg:obligationOperator; sh:minCount 1; sh:maxCount 1; sh:in (\"at-most\" \"at-least\" \"equal\") ];\n sh:property [ sh:path cg:obligationValue; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:double ];\n sh:property [ sh:path cg:obligationUnit; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ].\n";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Shapes target only obligation records, keeping ordinary-fact validation cost unchanged. */
|
|
2
|
+
export const obligationShapes = `
|
|
3
|
+
cg:ContractObligationShape a sh:NodeShape; sh:targetClass cg:ContractObligation;
|
|
4
|
+
sh:property [ sh:path cg:obligationVersion; sh:minCount 1; sh:maxCount 1; sh:hasValue 1 ];
|
|
5
|
+
sh:property [ sh:path cg:obligationMethod; sh:minCount 1; sh:maxCount 1; sh:hasValue "explicit-markdown" ];
|
|
6
|
+
sh:property [ sh:path cg:obligationRole; sh:minCount 1; sh:maxCount 1; sh:in ("guarantee" "requirement") ];
|
|
7
|
+
sh:property [ sh:path cg:obligationId; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
8
|
+
sh:property [ sh:path cg:obligationStatement; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
9
|
+
sh:property [ sh:path cg:obligationRepository; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
10
|
+
sh:property [ sh:path cg:obligationScope; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
11
|
+
sh:property [ sh:path cg:obligationContract; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
12
|
+
sh:property [ sh:path cg:obligationTransport; sh:minCount 1; sh:maxCount 1; sh:in ("queue" "stored-data") ];
|
|
13
|
+
sh:property [ sh:path cg:obligationContractVersion; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
14
|
+
sh:property [ sh:path cg:obligationDomain; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
15
|
+
sh:property [ sh:path cg:obligationStage; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
16
|
+
sh:property [ sh:path cg:obligationRevalidation; sh:maxCount 1; sh:in ("required" "not-required") ];
|
|
17
|
+
sh:property [ sh:path cg:obligationEndpoint; sh:minCount 1; sh:maxCount 1; sh:class cg:ObligationEndpoint ];
|
|
18
|
+
sh:property [ sh:path cg:authority; sh:in ("inferred" "draft") ];
|
|
19
|
+
sh:property [ sh:path cg:extractionReview; sh:minCount 1; sh:hasValue "deterministic" ];
|
|
20
|
+
sh:property [ sh:path rdf:predicate; sh:in (cg:emits cg:consumes) ].
|
|
21
|
+
cg:ObligationEndpointShape a sh:NodeShape; sh:targetClass cg:ObligationEndpoint;
|
|
22
|
+
sh:property [ sh:path cg:path; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
23
|
+
sh:property [ sh:path cg:digest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
24
|
+
sh:property [ sh:path cg:commitSha; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
25
|
+
sh:property [ sh:path cg:startLine; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ];
|
|
26
|
+
sh:property [ sh:path cg:endLine; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ].
|
|
27
|
+
cg:ObligationConstraintShape a sh:NodeShape; sh:targetSubjectsOf cg:obligationMetric, cg:obligationOperator, cg:obligationValue, cg:obligationUnit;
|
|
28
|
+
sh:property [ sh:path cg:obligationMetric; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ];
|
|
29
|
+
sh:property [ sh:path cg:obligationOperator; sh:minCount 1; sh:maxCount 1; sh:in ("at-most" "at-least" "equal") ];
|
|
30
|
+
sh:property [ sh:path cg:obligationValue; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:double ];
|
|
31
|
+
sh:property [ sh:path cg:obligationUnit; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ].
|
|
32
|
+
`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function parseGraphPathsRequest(value: unknown): {
|
|
2
|
+
continuation?: string | undefined;
|
|
3
|
+
snapshotId: string;
|
|
4
|
+
fromNodeId: string;
|
|
5
|
+
toNodeId: string;
|
|
6
|
+
direction: "incoming" | "outgoing" | "both";
|
|
7
|
+
predicates: ("references" | "realizes" | "implementedBy" | "consumes" | "emits" | "persistsTo" | "governedBy" | "definedBy" | "verifiedBy" | "operationallyOwnedBy" | "decisionAuthority" | "routesThrough" | "authorizedBy" | "sourcedFrom" | "contains" | "calls")[];
|
|
8
|
+
maxDepth: number;
|
|
9
|
+
maxPaths: number;
|
|
10
|
+
maxSteps: number;
|
|
11
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parseGraphNeighborsRequest } from './neighborhood-request.js';
|
|
2
|
+
const fields = new Set(['snapshotId', 'fromNodeId', 'toNodeId', 'direction', 'predicates', 'maxDepth', 'maxPaths', 'maxSteps', 'continuation']);
|
|
3
|
+
export function parseGraphPathsRequest(value) {
|
|
4
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
5
|
+
throw new Error('graph.paths arguments must be an object');
|
|
6
|
+
const record = value;
|
|
7
|
+
if (Object.keys(record).some(key => !fields.has(key)))
|
|
8
|
+
throw new Error('Unknown graph.paths field');
|
|
9
|
+
const common = { snapshotId: record.snapshotId, direction: record.direction === undefined ? 'outgoing' : record.direction,
|
|
10
|
+
predicates: record.predicates, continuation: record.continuation };
|
|
11
|
+
const from = parseGraphNeighborsRequest({ ...common, nodeId: record.fromNodeId });
|
|
12
|
+
const to = parseGraphNeighborsRequest({ ...common, nodeId: record.toNodeId });
|
|
13
|
+
const bound = (name, fallback, maximum) => {
|
|
14
|
+
const item = record[name] === undefined ? fallback : record[name];
|
|
15
|
+
if (!Number.isInteger(item) || Number(item) < 1 || Number(item) > maximum) {
|
|
16
|
+
throw new Error(`${name} must be an integer between 1 and ${maximum}`);
|
|
17
|
+
}
|
|
18
|
+
return Number(item);
|
|
19
|
+
};
|
|
20
|
+
return { snapshotId: from.snapshotId, fromNodeId: from.nodeId, toNodeId: to.nodeId,
|
|
21
|
+
direction: from.direction, predicates: from.predicates, maxDepth: bound('maxDepth', 8, 32),
|
|
22
|
+
maxPaths: bound('maxPaths', 5, 20), maxSteps: bound('maxSteps', 1000, 10_000),
|
|
23
|
+
...(from.continuation ? { continuation: from.continuation } : {}) };
|
|
24
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { EvidencePath } from '@weftlet/core';
|
|
2
|
+
export interface PathEdge {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly from: string;
|
|
5
|
+
readonly to: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PathSearchOptions {
|
|
8
|
+
readonly maxDepth: number;
|
|
9
|
+
readonly maxPaths: number;
|
|
10
|
+
readonly maxSteps: number;
|
|
11
|
+
readonly offset?: number;
|
|
12
|
+
}
|
|
13
|
+
/** Pure, dependency-free search shared verbatim with the offline viewer. No global visited set:
|
|
14
|
+
* distinct simple paths and parallel assertions survive; cycles do not repeat a node. */
|
|
15
|
+
export declare function searchEvidencePaths(edges: readonly PathEdge[], start: string, target: string, options: PathSearchOptions, knownNodeIds?: ReadonlySet<string>): {
|
|
16
|
+
paths: EvidencePath[];
|
|
17
|
+
frontier: {
|
|
18
|
+
pending: {
|
|
19
|
+
nodeId: string;
|
|
20
|
+
viaFactIds: string[];
|
|
21
|
+
nextFactId: string;
|
|
22
|
+
remainingCandidates: number;
|
|
23
|
+
}[];
|
|
24
|
+
depthLimited: {
|
|
25
|
+
count: number;
|
|
26
|
+
examples: {
|
|
27
|
+
readonly nodeId: string;
|
|
28
|
+
readonly viaFactIds: readonly string[];
|
|
29
|
+
readonly nextFactId: string;
|
|
30
|
+
readonly nextNodeId: string;
|
|
31
|
+
}[];
|
|
32
|
+
omitted: number;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
search: {
|
|
36
|
+
searchLimit: number;
|
|
37
|
+
replayedSteps: number;
|
|
38
|
+
examinedSteps: number;
|
|
39
|
+
totalSteps: number;
|
|
40
|
+
stopReason: "exhausted" | "search-limit" | "path-page-limit" | "work-page-limit";
|
|
41
|
+
exhaustedWithinDepth: boolean;
|
|
42
|
+
depthLimitReached: boolean;
|
|
43
|
+
truncated: boolean;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Pure, dependency-free search shared verbatim with the offline viewer. No global visited set:
|
|
2
|
+
* distinct simple paths and parallel assertions survive; cycles do not repeat a node. */
|
|
3
|
+
export function searchEvidencePaths(edges, start, target, options, knownNodeIds) {
|
|
4
|
+
const searchLimit = 100_000;
|
|
5
|
+
const offset = options.offset ?? 0;
|
|
6
|
+
const validateBounds = () => {
|
|
7
|
+
for (const [value, maximum] of [[options.maxDepth, 32], [options.maxPaths, 20], [options.maxSteps, 10_000]]) {
|
|
8
|
+
if (!Number.isInteger(value) || value < 1 || value > maximum)
|
|
9
|
+
throw new Error('Invalid path search bound');
|
|
10
|
+
}
|
|
11
|
+
if (!Number.isInteger(offset) || offset < 0 || offset >= searchLimit)
|
|
12
|
+
throw new Error('Invalid path continuation offset');
|
|
13
|
+
};
|
|
14
|
+
validateBounds();
|
|
15
|
+
const adjacency = new Map();
|
|
16
|
+
const nodeIds = new Set();
|
|
17
|
+
for (const edge of [...edges].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) {
|
|
18
|
+
const entries = adjacency.get(edge.from) ?? [];
|
|
19
|
+
entries.push(edge);
|
|
20
|
+
adjacency.set(edge.from, entries);
|
|
21
|
+
nodeIds.add(edge.from);
|
|
22
|
+
nodeIds.add(edge.to);
|
|
23
|
+
}
|
|
24
|
+
const paths = [];
|
|
25
|
+
const stack = nodeIds.has(start) && nodeIds.has(target) && start !== target ? [{ nodeId: start, next: 0 }] : [];
|
|
26
|
+
const depthExamples = [];
|
|
27
|
+
let depthCount = 0;
|
|
28
|
+
let totalSteps = 0;
|
|
29
|
+
const trim = () => {
|
|
30
|
+
while (stack.length && stack.at(-1).next >= (adjacency.get(stack.at(-1).nodeId)?.length ?? 0))
|
|
31
|
+
stack.pop();
|
|
32
|
+
};
|
|
33
|
+
const step = () => {
|
|
34
|
+
const frame = stack.at(-1);
|
|
35
|
+
const edge = adjacency.get(frame.nodeId)[frame.next++];
|
|
36
|
+
totalSteps++;
|
|
37
|
+
if (stack.some(item => item.nodeId === edge.to))
|
|
38
|
+
return;
|
|
39
|
+
const viaFactIds = stack.slice(1).map(item => item.via);
|
|
40
|
+
if (viaFactIds.length === options.maxDepth) {
|
|
41
|
+
depthCount++;
|
|
42
|
+
if (depthExamples.length < 20)
|
|
43
|
+
depthExamples.push({ nodeId: frame.nodeId, viaFactIds,
|
|
44
|
+
nextFactId: edge.id, nextNodeId: edge.to });
|
|
45
|
+
}
|
|
46
|
+
else if (edge.to === target) {
|
|
47
|
+
return { nodeIds: [...stack.map(item => item.nodeId), target], factIds: [...viaFactIds, edge.id] };
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
stack.push({ nodeId: edge.to, next: 0, via: edge.id });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
trim();
|
|
54
|
+
// Reconstruct only bounded search state. Never accept a caller-supplied stack or facts.
|
|
55
|
+
const replay = () => {
|
|
56
|
+
let replayPageSteps = 0;
|
|
57
|
+
let replayPagePaths = 0;
|
|
58
|
+
while (totalSteps < offset && stack.length) {
|
|
59
|
+
if (step())
|
|
60
|
+
replayPagePaths++;
|
|
61
|
+
replayPageSteps++;
|
|
62
|
+
if (replayPagePaths === options.maxPaths || replayPageSteps === options.maxSteps) {
|
|
63
|
+
replayPagePaths = 0;
|
|
64
|
+
replayPageSteps = 0;
|
|
65
|
+
}
|
|
66
|
+
trim();
|
|
67
|
+
}
|
|
68
|
+
if (totalSteps !== offset || (offset && (!stack.length || replayPagePaths || replayPageSteps))) {
|
|
69
|
+
throw new Error('Invalid path continuation boundary');
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
replay();
|
|
73
|
+
if (!offset && start === target && (nodeIds.has(start) || knownNodeIds?.has(start)))
|
|
74
|
+
paths.push({ nodeIds: [start], factIds: [] });
|
|
75
|
+
while (stack.length && paths.length < options.maxPaths && totalSteps - offset < options.maxSteps && totalSteps < searchLimit) {
|
|
76
|
+
const path = step();
|
|
77
|
+
if (path)
|
|
78
|
+
paths.push(path);
|
|
79
|
+
trim();
|
|
80
|
+
}
|
|
81
|
+
const pending = stack.flatMap((frame, index) => {
|
|
82
|
+
const entries = adjacency.get(frame.nodeId) ?? [];
|
|
83
|
+
return frame.next < entries.length ? [{ nodeId: frame.nodeId,
|
|
84
|
+
viaFactIds: stack.slice(1, index + 1).map(item => item.via), nextFactId: entries[frame.next].id,
|
|
85
|
+
remainingCandidates: entries.length - frame.next }] : [];
|
|
86
|
+
});
|
|
87
|
+
const exhaustedWithinDepth = stack.length === 0;
|
|
88
|
+
const stopReason = () => {
|
|
89
|
+
if (exhaustedWithinDepth)
|
|
90
|
+
return 'exhausted';
|
|
91
|
+
if (totalSteps === searchLimit)
|
|
92
|
+
return 'search-limit';
|
|
93
|
+
if (paths.length === options.maxPaths)
|
|
94
|
+
return 'path-page-limit';
|
|
95
|
+
return 'work-page-limit';
|
|
96
|
+
};
|
|
97
|
+
return { paths, frontier: { pending, depthLimited: { count: depthCount, examples: depthExamples,
|
|
98
|
+
omitted: depthCount - depthExamples.length } }, search: { searchLimit, replayedSteps: offset,
|
|
99
|
+
examinedSteps: totalSteps - offset, totalSteps, stopReason: stopReason(), exhaustedWithinDepth,
|
|
100
|
+
depthLimitReached: depthCount > 0, truncated: !exhaustedWithinDepth || depthCount > 0 } };
|
|
101
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { factEvidenceDescription } from './evidence-description.js';
|
|
2
|
+
import { sourceLocation } from '@weftlet/core';
|
|
3
|
+
import { contextPackEvidenceAuthority } from './authority.js';
|
|
4
|
+
import { coverageDescription } from './coverage.js';
|
|
5
|
+
import { diagnosticSummary } from './diagnostics.js';
|
|
6
|
+
import { documentSelectionDescription } from './document-role-description.js';
|
|
7
|
+
const quoted = (value) => JSON.stringify(value).replace(/[\\`*_{}\[\]()<>!|]/g, '\\$&');
|
|
8
|
+
export function renderGraphPaths(result) {
|
|
9
|
+
const { snapshot, endpoints, search, frontier, evidenceState } = result;
|
|
10
|
+
return [
|
|
11
|
+
'# Cited evidence paths',
|
|
12
|
+
`Snapshot: ${quoted(snapshot.snapshotId)} (${snapshot.commitSha}); configuration: ${snapshot.configurationDigest ?? 'unknown'}`,
|
|
13
|
+
`Scope: ${snapshot.scope?.map(quoted).join(', ') ?? 'unknown'}`,
|
|
14
|
+
documentSelectionDescription(snapshot.documentSelection),
|
|
15
|
+
`From ${quoted(endpoints.from.nodeId)} (${endpoints.from.status}) to ${quoted(endpoints.to.nodeId)} (${endpoints.to.status})`,
|
|
16
|
+
`Direction: ${search.direction}; predicates: ${search.predicates.join(', ') || 'all'}; order: ${search.order}`,
|
|
17
|
+
`Depth: ${search.maxDepth}; paths on page: ${result.paths.length}/${search.maxPaths}; stop: ${search.stopReason}; truncated: ${search.truncated}`,
|
|
18
|
+
`Candidate steps: ${search.examinedSteps} new + ${search.replayedSteps} replay; ${search.totalSteps}/${search.searchLimit} total ceiling.`,
|
|
19
|
+
`Unexamined prefixes: ${frontier.pending.length}; depth-limited extensions: ${frontier.depthLimited.count} (${frontier.depthLimited.omitted} examples omitted).`,
|
|
20
|
+
...(search.continuation ? [`Continuation (same request): ${search.continuation}`] : []),
|
|
21
|
+
...(search.depthLimitReached ? ['Depth limit reached: restart with a larger maxDepth or explore a frontier node. Continuation does not lift the depth bound.'] : []),
|
|
22
|
+
...(search.stopReason === 'search-limit' ? ['Total search limit reached: narrow the relations or explore an intermediate node; no further continuation is available.'] : []),
|
|
23
|
+
...(evidenceState ? [`Stale: ${evidenceState.stale}; configuration matches: ${evidenceState.configurationMatches}; extraction compatible: ${evidenceState.extractionCompatible ?? 'unknown'}; overlay freshness: ${evidenceState.overlayFreshness ?? 'not an overlay'}`,
|
|
24
|
+
`Excluded working changes: ${evidenceState.excludedWorkingChanges.map(quoted).join(', ') || '(none)'}`] : []),
|
|
25
|
+
'Paths are cited associations, not proof of causality, business compatibility or current-diff validation. A completed search covers only the selected saved facts and depth; absence is not proof of no product dependency.',
|
|
26
|
+
coverageDescription(result.coverage), ...result.coverage.gaps.map(quoted),
|
|
27
|
+
'', '## Paths',
|
|
28
|
+
...result.paths.map((path, index) => `${index + 1}. ${path.nodeIds.map(quoted).join(' → ')}; facts: ${path.factIds.map(quoted).join(', ') || '(zero-length identity path)'}`),
|
|
29
|
+
'', '## Supporting assertions',
|
|
30
|
+
...result.facts.map(fact => `- ${quoted(fact.id)} [${contextPackEvidenceAuthority(fact)}] ${quoted(fact.subject.id)} —${fact.predicate}→ ${quoted(fact.object.id)} (${quoted(sourceLocation(fact.provenance))}–${fact.provenance.endLine}${fact.provenance.endColumn === undefined ? '' : `:${fact.provenance.endColumn}`}; confidence ${fact.confidence}; source ${fact.sourceAuthority ?? 'unspecified'}; extraction ${fact.extractionReview ?? 'unspecified'}${factEvidenceDescription(fact) ? `; ${factEvidenceDescription(fact)}` : ''})`),
|
|
31
|
+
'', '## Unexplored frontier',
|
|
32
|
+
...frontier.pending.map(item => `- ${quoted(item.nodeId)} via ${item.viaFactIds.map(quoted).join(', ') || '(start)'}: ${item.remainingCandidates} candidates; next ${quoted(item.nextFactId)}`),
|
|
33
|
+
...frontier.depthLimited.examples.map(item => `- Depth boundary: ${quoted(item.nodeId)} → ${quoted(item.nextNodeId)} via ${quoted(item.nextFactId)}`),
|
|
34
|
+
'', '## Scoped diagnostics', ...diagnosticSummary(result).map(quoted),
|
|
35
|
+
].filter(line => line !== undefined).join('\n');
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { GraphPathQueryService, GraphPaths, GraphPathsRequest, SnapshotStore } from '@weftlet/core';
|
|
2
|
+
export declare const GRAPH_PATHS_VERSION = "simple-evidence-paths-v1";
|
|
3
|
+
export declare class SnapshotGraphPathService implements GraphPathQueryService {
|
|
4
|
+
private readonly store;
|
|
5
|
+
constructor(store: Pick<SnapshotStore, 'read'>);
|
|
6
|
+
paths(input: GraphPathsRequest): Promise<GraphPaths>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { snapshotContext } from './snapshot-context.js';
|
|
2
|
+
import { sha256, stableJson } from '../infrastructure/util.js';
|
|
3
|
+
import { RdfEvidenceGraph } from './rdf.js';
|
|
4
|
+
import { coverageForFacts } from './coverage.js';
|
|
5
|
+
import { selectDiagnostics } from './diagnostics.js';
|
|
6
|
+
import { parseGraphPathsRequest } from './path-request.js';
|
|
7
|
+
import { searchEvidencePaths } from './path-search.js';
|
|
8
|
+
import { renderGraphPaths } from './paths-renderer.js';
|
|
9
|
+
export const GRAPH_PATHS_VERSION = 'simple-evidence-paths-v1';
|
|
10
|
+
function continuationOffset(token, binding) {
|
|
11
|
+
if (!token)
|
|
12
|
+
return 0;
|
|
13
|
+
try {
|
|
14
|
+
if (!/^[A-Za-z0-9_-]+$/.test(token))
|
|
15
|
+
throw new Error();
|
|
16
|
+
const value = JSON.parse(Buffer.from(token, 'base64url').toString('utf8'));
|
|
17
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) ||
|
|
18
|
+
Object.keys(value).sort().join(',') !== 'binding,offset,version' ||
|
|
19
|
+
value.version !== GRAPH_PATHS_VERSION || value.binding !== binding ||
|
|
20
|
+
!Number.isInteger(value.offset) || value.offset < 1 || value.offset >= 100_000)
|
|
21
|
+
throw new Error();
|
|
22
|
+
return value.offset;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error('Invalid graph.paths continuation or query/snapshot mismatch; restart graph.paths');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class SnapshotGraphPathService {
|
|
29
|
+
store;
|
|
30
|
+
constructor(store) {
|
|
31
|
+
this.store = store;
|
|
32
|
+
}
|
|
33
|
+
async paths(input) {
|
|
34
|
+
const request = parseGraphPathsRequest(input);
|
|
35
|
+
const saved = await this.store.read(request.snapshotId);
|
|
36
|
+
if (!saved)
|
|
37
|
+
throw new Error('No graph snapshot is available for that identifier');
|
|
38
|
+
if (saved.manifest.snapshotId !== request.snapshotId)
|
|
39
|
+
throw new Error('Saved snapshot identity does not match the requested identifier');
|
|
40
|
+
const all = await new RdfEvidenceGraph(saved).facts();
|
|
41
|
+
if (new Set(all.map(fact => fact.id)).size !== all.length)
|
|
42
|
+
throw new Error('Duplicate fact identities prevent stable graph.paths pagination');
|
|
43
|
+
const { continuation, ...query } = request;
|
|
44
|
+
const binding = sha256(stableJson({ version: GRAPH_PATHS_VERSION, query, manifest: saved.manifest,
|
|
45
|
+
facts: all, diagnostics: saved.diagnostics, unresolved: saved.unresolved }));
|
|
46
|
+
const offset = continuationOffset(continuation, binding);
|
|
47
|
+
const selected = all.filter(fact => !request.predicates.length || request.predicates.includes(fact.predicate));
|
|
48
|
+
const edges = selected.flatMap(fact => {
|
|
49
|
+
const forward = { id: fact.id, from: fact.subject.id, to: fact.object.id };
|
|
50
|
+
const reverse = { id: fact.id, from: fact.object.id, to: fact.subject.id };
|
|
51
|
+
return request.direction === 'incoming' ? [reverse] : request.direction === 'outgoing' || forward.from === forward.to
|
|
52
|
+
? [forward] : [forward, reverse];
|
|
53
|
+
});
|
|
54
|
+
const nodes = new Set(all.flatMap(fact => [fact.subject.id, fact.object.id]));
|
|
55
|
+
const result = searchEvidencePaths(edges, request.fromNodeId, request.toNodeId, { ...request, offset }, nodes);
|
|
56
|
+
const factIds = new Set(result.paths.flatMap(path => path.factIds));
|
|
57
|
+
const facts = all.filter(fact => factIds.has(fact.id));
|
|
58
|
+
const endpoint = (nodeId) => ({ nodeId, status: nodes.has(nodeId) ? 'present' : 'absent-from-snapshot' });
|
|
59
|
+
const next = !result.search.exhaustedWithinDepth && result.search.stopReason !== 'search-limit'
|
|
60
|
+
? Buffer.from(JSON.stringify({ version: GRAPH_PATHS_VERSION, binding, offset: result.search.totalSteps })).toString('base64url') : undefined;
|
|
61
|
+
const manifest = saved.manifest;
|
|
62
|
+
const plain = {
|
|
63
|
+
snapshot: snapshotContext(manifest),
|
|
64
|
+
endpoints: { from: endpoint(request.fromNodeId), to: endpoint(request.toNodeId) },
|
|
65
|
+
paths: result.paths, facts, coverage: coverageForFacts(facts, saved),
|
|
66
|
+
diagnostics: selectDiagnostics(saved, {}, facts, [], [], new Set([request.fromNodeId, request.toNodeId,
|
|
67
|
+
...facts.flatMap(fact => [fact.subject.id, fact.object.id])])),
|
|
68
|
+
search: { version: GRAPH_PATHS_VERSION, order: 'fact-id-depth-first', direction: request.direction,
|
|
69
|
+
predicates: request.predicates, maxDepth: request.maxDepth, maxPaths: request.maxPaths, maxSteps: request.maxSteps,
|
|
70
|
+
...result.search, ...(next ? { continuation: next } : {}) }, frontier: result.frontier,
|
|
71
|
+
};
|
|
72
|
+
return { ...plain, markdown: renderGraphPaths(plain) };
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/graph/query.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
|
+
import { unexpandedCodeDescription } from './code-description.js';
|
|
3
|
+
import { factEvidenceDescription } from './evidence-description.js';
|
|
4
|
+
import { sourceLocation } from '@weftlet/core';
|
|
2
5
|
import { contextPackEvidenceAuthority } from "./authority.js";
|
|
3
6
|
import { RdfEvidenceGraph } from "./rdf.js";
|
|
4
7
|
import { sha256, stableJson } from "../infrastructure/util.js";
|
|
@@ -61,7 +64,8 @@ function markdownFactDetails(fact) {
|
|
|
61
64
|
const document = sourceDocumentDescription(fact)?.replace(/[\\`*_{}\[\]()<>!|]/g, '\\$&').replace(/[\r\n]/g, ' ');
|
|
62
65
|
return `confidence ${fact.confidence}` +
|
|
63
66
|
(fact.sourceAuthority ? `; source ${fact.sourceAuthority}; extraction ${fact.extractionReview ?? 'unknown'}` : '') +
|
|
64
|
-
(document ? `; ${document}` : '')
|
|
67
|
+
(document ? `; ${document}` : '') +
|
|
68
|
+
(factEvidenceDescription(fact) ? `; ${factEvidenceDescription(fact)}` : '');
|
|
65
69
|
}
|
|
66
70
|
export class MarkdownContextPackRenderer {
|
|
67
71
|
render(pack) {
|
|
@@ -106,7 +110,7 @@ export class MarkdownContextPackRenderer {
|
|
|
106
110
|
...pack.facts
|
|
107
111
|
.filter((fact) => contextPackLaneForFact(fact) === lane)
|
|
108
112
|
.map((fact) => `- [${contextPackEvidenceAuthority(fact)}] ${fact.subject.label} —${fact.predicate}→ ${fact.object.label} ` +
|
|
109
|
-
`(\`${fact.provenance
|
|
113
|
+
`(\`${sourceLocation(fact.provenance)}\`, ${markdownFactDetails(fact)})`),
|
|
110
114
|
]),
|
|
111
115
|
'',
|
|
112
116
|
'## Runtime evidence',
|
|
@@ -122,6 +126,7 @@ export class MarkdownContextPackRenderer {
|
|
|
122
126
|
'## Suggested files',
|
|
123
127
|
...pack.suggestedFiles.map((path) => `- \`${path}\``),
|
|
124
128
|
'',
|
|
129
|
+
...unexpandedCodeDescription(pack.exploration),
|
|
125
130
|
'## Scoped diagnostics',
|
|
126
131
|
...diagnosticSummary(pack).map((item) => `- ${item}`),
|
|
127
132
|
];
|
|
@@ -165,8 +170,16 @@ export class SnapshotImpactQueryService {
|
|
|
165
170
|
const directTerms = anchorTerms.length > 0 ? anchorTerms : focus;
|
|
166
171
|
const direct = snapshot.facts.filter((fact) => this.matches(fact, directTerms));
|
|
167
172
|
const ranked = new Map(direct.map((fact) => [fact.id, fact]));
|
|
173
|
+
const unexpandedCodeTargets = new Set();
|
|
174
|
+
const expand = (node) => {
|
|
175
|
+
if (node.kind === 'CodeSymbol' && node.codeSymbol?.resolution === 'no-indexed-definition') {
|
|
176
|
+
unexpandedCodeTargets.add(node.id);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return node.kind !== 'Project';
|
|
180
|
+
};
|
|
168
181
|
let frontier = new Set(direct.flatMap((fact) => [fact.subject, fact.object]
|
|
169
|
-
.filter(
|
|
182
|
+
.filter(expand)
|
|
170
183
|
.map((node) => node.id)));
|
|
171
184
|
const visitedNodes = new Set(frontier);
|
|
172
185
|
for (let depth = 0; depth < maxDepth && frontier.size > 0; depth += 1) {
|
|
@@ -176,7 +189,7 @@ export class SnapshotImpactQueryService {
|
|
|
176
189
|
continue;
|
|
177
190
|
ranked.set(fact.id, fact);
|
|
178
191
|
for (const node of [fact.subject, fact.object]) {
|
|
179
|
-
if (node
|
|
192
|
+
if (expand(node) && !visitedNodes.has(node.id))
|
|
180
193
|
next.add(node.id);
|
|
181
194
|
}
|
|
182
195
|
}
|
|
@@ -196,7 +209,7 @@ export class SnapshotImpactQueryService {
|
|
|
196
209
|
...tier.filter((fact) => contextPackLaneForFact(fact) === undefined),
|
|
197
210
|
]);
|
|
198
211
|
const { continuation: _continuation, snapshotId: _snapshotId, ...query } = request;
|
|
199
|
-
const queryDigest = sha256(stableJson({ ranking: IMPACT_RANKING_VERSION, query }));
|
|
212
|
+
const queryDigest = sha256(stableJson({ ranking: IMPACT_RANKING_VERSION, codeTraversal: 'indexed-symbols-v1', query }));
|
|
200
213
|
let offset = 0;
|
|
201
214
|
if (request.continuation) {
|
|
202
215
|
if (request.continuation.length > 2048)
|
|
@@ -215,7 +228,7 @@ export class SnapshotImpactQueryService {
|
|
|
215
228
|
offset = token.offset;
|
|
216
229
|
}
|
|
217
230
|
const facts = allBalanced.slice(offset, offset + maxFacts);
|
|
218
|
-
const coverage = coverageForFacts(facts);
|
|
231
|
+
const coverage = coverageForFacts(facts, snapshot);
|
|
219
232
|
const nextOffset = offset + facts.length;
|
|
220
233
|
const continuation = nextOffset < allBalanced.length ? Buffer.from(JSON.stringify({
|
|
221
234
|
snapshot: snapshot.manifest.snapshotId, digest: snapshot.manifest.contentDigest,
|
|
@@ -306,6 +319,10 @@ export class SnapshotImpactQueryService {
|
|
|
306
319
|
diagnostics: selectDiagnostics(snapshot, request, facts, focus, queryDiagnostics),
|
|
307
320
|
exploration: {
|
|
308
321
|
rankingVersion: IMPACT_RANKING_VERSION,
|
|
322
|
+
codeTraversalVersion: 'indexed-symbols-v1',
|
|
323
|
+
unexpandedCodeTargets: [...unexpandedCodeTargets].sort().slice(0, 100),
|
|
324
|
+
unexpandedCodeTargetCount: unexpandedCodeTargets.size,
|
|
325
|
+
unexpandedCodeTargetsTruncated: unexpandedCodeTargets.size > 100,
|
|
309
326
|
returned: facts.length, reachableFacts: allBalanced.length, maxDepth,
|
|
310
327
|
depthLimitReached: frontier.size > 0, truncated: Boolean(continuation) || frontier.size > 0,
|
|
311
328
|
continuation,
|