@weftlet/toolkit 0.7.0 → 0.7.2
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 +40 -29
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/dist/application/hydrator.js +18 -15
- package/dist/application/overlay.js +9 -9
- package/dist/application/saved-paths.d.ts +1 -1
- package/dist/application/semantic-run.d.ts +1 -1
- package/dist/extraction/ontology.d.ts +2 -0
- package/dist/extraction/ontology.js +28 -13
- package/dist/graph/code-coverage.js +2 -2
- package/dist/graph/code-rdf.d.ts +3 -2
- package/dist/graph/code-rdf.js +21 -14
- package/dist/graph/impact-signals.d.ts +7 -0
- package/dist/graph/impact-signals.js +54 -0
- package/dist/graph/neighborhood.js +3 -3
- package/dist/graph/partitioned-shacl.d.ts +14 -0
- package/dist/graph/partitioned-shacl.js +66 -0
- package/dist/graph/path-search.d.ts +6 -4
- package/dist/graph/path-search.js +50 -24
- package/dist/graph/paths-renderer.js +1 -1
- package/dist/graph/paths.d.ts +1 -1
- package/dist/graph/paths.js +5 -5
- package/dist/graph/ranking.d.ts +1 -1
- package/dist/graph/ranking.js +17 -9
- package/dist/graph/rdf-partitions.d.ts +24 -0
- package/dist/graph/rdf-partitions.js +170 -0
- package/dist/graph/rdf-projection.d.ts +7 -0
- package/dist/graph/rdf-projection.js +27 -0
- package/dist/graph/rdf-records.d.ts +12 -0
- package/dist/graph/rdf-records.js +90 -0
- package/dist/graph/rdf.d.ts +10 -10
- package/dist/graph/rdf.js +66 -97
- package/dist/graph/validation-progress.d.ts +4 -1
- package/dist/infrastructure/json-digest.d.ts +2 -1
- package/dist/infrastructure/json-digest.js +2 -2
- package/dist/interfaces/cli.js +1 -1
- package/dist/storage/storage.d.ts +3 -0
- package/dist/storage/storage.js +37 -13
- package/dist/storage/streaming-json.d.ts +5 -0
- package/dist/storage/streaming-json.js +45 -0
- package/dist/visualization/viewer-interaction.js +1 -1
- package/docs/1.0-WORK-QUEUE.md +25 -8
- package/docs/ADR-012-impact-ranking.md +2 -1
- package/docs/ADR-030-bounded-evidence-paths.md +21 -18
- package/docs/ADR-040-bounded-graph-io.md +90 -0
- package/docs/ADR-041-bounded-relationship-retrieval.md +77 -0
- package/docs/RELEASING.md +3 -3
- package/docs/VALIDATION.md +121 -0
- package/docs/benchmarks/large-graph-2026-11-02/result.json +156 -0
- package/docs/extraction-manifest.json +37 -11
- package/package.json +5 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { DataFactory, Store } from 'n3';
|
|
2
|
+
import { contextPackEvidenceAuthority } from './authority.js';
|
|
3
|
+
import { stableJson } from '../infrastructure/util.js';
|
|
4
|
+
import { CG, RDF, XSD } from './shacl-shapes.js';
|
|
5
|
+
import { addBindingEvidence } from './binding-rdf.js';
|
|
6
|
+
import { addCallEvidence } from './call-rdf.js';
|
|
7
|
+
import { addObligationEvidence } from './obligation-rdf.js';
|
|
8
|
+
import { addCodeEvidence, addCodeNodeEvidence } from './code-rdf.js';
|
|
9
|
+
const { namedNode: iri, literal } = DataFactory;
|
|
10
|
+
export const nodeIri = (id) => `urn:context-graph:node:${encodeURIComponent(id)}`;
|
|
11
|
+
export const factIri = (id) => `urn:context-graph:fact:${encodeURIComponent(id)}`;
|
|
12
|
+
export function addFactRecord(dataset, fact, ordinal) {
|
|
13
|
+
const record = iri(factIri(fact.id));
|
|
14
|
+
const subject = iri(nodeIri(fact.subject.id));
|
|
15
|
+
const object = iri(nodeIri(fact.object.id));
|
|
16
|
+
const predicate = iri(CG + fact.predicate);
|
|
17
|
+
dataset.addQuad(record, iri(RDF + 'type'), iri(RDF + 'Statement'));
|
|
18
|
+
dataset.addQuad(record, iri(CG + 'ordinal'), literal(String(ordinal), iri(XSD + 'integer')));
|
|
19
|
+
for (const [p, value] of [['subject', subject], ['predicate', predicate], ['object', object]]) {
|
|
20
|
+
dataset.addQuad(record, iri(RDF + p), value);
|
|
21
|
+
}
|
|
22
|
+
for (const [p, value] of Object.entries({
|
|
23
|
+
authority: contextPackEvidenceAuthority(fact), path: fact.provenance.path,
|
|
24
|
+
commit: fact.provenance.commitSha, sourceDigest: fact.provenance.digest,
|
|
25
|
+
extractor: `${fact.extractor.id}@${fact.extractor.version}`,
|
|
26
|
+
snapshotId: fact.snapshotId, record: stableJson(fact),
|
|
27
|
+
...(fact.sourceAuthority ? { sourceAuthority: fact.sourceAuthority } : {}),
|
|
28
|
+
...(fact.extractionReview ? { extractionReview: fact.extractionReview } : {}),
|
|
29
|
+
...(fact.sourceRole !== undefined ? { sourceRole: fact.sourceRole } : {}),
|
|
30
|
+
...(fact.sourcePolicyDigest !== undefined ? { sourcePolicyDigest: fact.sourcePolicyDigest } : {}),
|
|
31
|
+
}))
|
|
32
|
+
dataset.addQuad(record, iri(CG + p), literal(value));
|
|
33
|
+
if (fact.sourceOwnership !== undefined) {
|
|
34
|
+
dataset.addQuad(record, iri(CG + 'ownershipStatus'), literal(fact.sourceOwnership.status));
|
|
35
|
+
for (const ownerDocument of fact.sourceOwnership.ownerDocuments) {
|
|
36
|
+
dataset.addQuad(record, iri(CG + 'ownerDocument'), literal(ownerDocument));
|
|
37
|
+
}
|
|
38
|
+
for (const mappingId of fact.sourceOwnership.mappingIds) {
|
|
39
|
+
dataset.addQuad(record, iri(CG + 'ownershipMapping'), literal(mappingId));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const [p, value] of [['startLine', fact.provenance.startLine], ['endLine', fact.provenance.endLine]]) {
|
|
43
|
+
dataset.addQuad(record, iri(CG + p), literal(String(value), iri(XSD + 'integer')));
|
|
44
|
+
}
|
|
45
|
+
dataset.addQuad(record, iri(CG + 'confidence'), literal(String(fact.confidence), iri(XSD + 'decimal')));
|
|
46
|
+
addCodeEvidence(dataset, record, fact, nodeIri, false);
|
|
47
|
+
addBindingEvidence(dataset, record, fact, factIri);
|
|
48
|
+
addCallEvidence(dataset, record, fact, factIri);
|
|
49
|
+
addObligationEvidence(dataset, record, fact);
|
|
50
|
+
}
|
|
51
|
+
export function addNodeRdf(dataset, node) {
|
|
52
|
+
const term = iri(nodeIri(node.id));
|
|
53
|
+
dataset.addQuad(term, iri(RDF + 'type'), iri(CG + 'Node'));
|
|
54
|
+
dataset.addQuad(term, iri(CG + 'identifier'), literal(node.id));
|
|
55
|
+
dataset.addQuad(term, iri(CG + 'kind'), literal(node.kind));
|
|
56
|
+
dataset.addQuad(term, iri(CG + 'label'), literal(node.label));
|
|
57
|
+
addCodeNodeEvidence(dataset, node, nodeIri);
|
|
58
|
+
}
|
|
59
|
+
export function addAttributes(dataset, node, graph = DataFactory.defaultGraph()) {
|
|
60
|
+
for (const [key, value] of Object.entries(node.attributes ?? {})) {
|
|
61
|
+
const datatype = typeof value === 'boolean' ? 'boolean' : typeof value === 'number' ? 'decimal' : 'string';
|
|
62
|
+
dataset.addQuad(iri(nodeIri(node.id)), iri(`${CG}attribute/${encodeURIComponent(key)}`), literal(String(value), iri(XSD + datatype)), graph);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function addFactEdges(dataset, fact) {
|
|
66
|
+
const record = iri(factIri(fact.id));
|
|
67
|
+
dataset.addQuad(iri(nodeIri(fact.subject.id)), iri(CG + fact.predicate), iri(nodeIri(fact.object.id)), record);
|
|
68
|
+
for (const node of [fact.subject, fact.object])
|
|
69
|
+
addAttributes(dataset, node, record);
|
|
70
|
+
}
|
|
71
|
+
export function addManifest(dataset, snapshot) {
|
|
72
|
+
dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'manifest'), literal(stableJson(snapshot.manifest)));
|
|
73
|
+
for (const scope of snapshot.manifest.scope ?? []) {
|
|
74
|
+
dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'scope'), literal(scope));
|
|
75
|
+
}
|
|
76
|
+
if (snapshot.manifest.configurationDigest) {
|
|
77
|
+
dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'configurationDigest'), literal(snapshot.manifest.configurationDigest));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function evidenceDataset(snapshot) {
|
|
81
|
+
const dataset = new Store();
|
|
82
|
+
for (const [ordinal, fact] of snapshot.facts.entries()) {
|
|
83
|
+
addFactRecord(dataset, fact, ordinal);
|
|
84
|
+
addFactEdges(dataset, fact);
|
|
85
|
+
for (const node of [fact.subject, fact.object])
|
|
86
|
+
addNodeRdf(dataset, node);
|
|
87
|
+
}
|
|
88
|
+
addManifest(dataset, snapshot);
|
|
89
|
+
return dataset;
|
|
90
|
+
}
|
package/dist/graph/rdf.d.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
1
2
|
import type { GraphFact, GraphSnapshot } from "@weftlet/core";
|
|
2
3
|
import { type GraphValidationProgress } from './validation-progress.js';
|
|
3
4
|
export { CG } from './shacl-shapes.js';
|
|
4
|
-
export
|
|
5
|
-
export declare const factIri: (id: string) => string;
|
|
5
|
+
export { factIri, nodeIri } from './rdf-records.js';
|
|
6
6
|
/** Lossless RDF dataset: one named graph per cited assertion, with provenance in the default graph. */
|
|
7
7
|
export declare class RdfEvidenceGraph {
|
|
8
8
|
readonly snapshot: GraphSnapshot;
|
|
9
|
-
private
|
|
9
|
+
private indexed?;
|
|
10
10
|
constructor(snapshot: GraphSnapshot);
|
|
11
11
|
validate(options?: {
|
|
12
12
|
readonly onProgress?: (event: GraphValidationProgress) => void;
|
|
13
13
|
}): Promise<{
|
|
14
14
|
conforms: boolean;
|
|
15
|
-
issues:
|
|
16
|
-
category: "extraction-gap";
|
|
17
|
-
focus: string;
|
|
18
|
-
path: string;
|
|
19
|
-
message: string;
|
|
20
|
-
}[];
|
|
15
|
+
issues: import("./partitioned-shacl.js").ShaclIssue[];
|
|
21
16
|
}>;
|
|
22
17
|
select(sparql: string, maxRows?: number): Promise<{
|
|
23
18
|
rows: Record<string, string>[];
|
|
24
19
|
truncated: boolean;
|
|
25
20
|
}>;
|
|
26
|
-
/**
|
|
21
|
+
/** Lossless optimized projection, checked for equivalence with the public SPARQL path. */
|
|
27
22
|
facts(): Promise<readonly GraphFact[]>;
|
|
23
|
+
/** Complete N-Quads in bounded groups; RDF triple order is not a snapshot identity. */
|
|
24
|
+
serializeChunks(): IterableIterator<string>;
|
|
25
|
+
/** Export large datasets with backpressure, without a whole-dataset string/quad array. */
|
|
26
|
+
serializeTo(destination: Writable): Promise<void>;
|
|
27
|
+
/** Compatibility convenience for small exports; use serializeTo for large graphs. */
|
|
28
28
|
serialize(): Promise<string>;
|
|
29
29
|
}
|
package/dist/graph/rdf.js
CHANGED
|
@@ -1,105 +1,48 @@
|
|
|
1
1
|
import { QueryEngine } from '@comunica/query-sparql';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { constants } from 'node:buffer';
|
|
3
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
5
|
+
import { Writer } from 'n3';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
|
-
import { CG
|
|
7
|
+
import { CG } from './shacl-shapes.js';
|
|
8
8
|
import { measureValidationPhase } from './validation-progress.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
const { namedNode: iri, literal } = DataFactory;
|
|
9
|
+
import { bindingEvidenceIssues } from './binding-rdf.js';
|
|
10
|
+
import { callEvidenceIssues } from './call-rdf.js';
|
|
11
|
+
import { obligationEvidenceIssues } from './obligation-rdf.js';
|
|
12
|
+
import { codeEvidenceIssues, invalidCodeRange } from './code-rdf.js';
|
|
14
13
|
export { CG } from './shacl-shapes.js';
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
import { factIri } from './rdf-records.js';
|
|
15
|
+
import { RdfPartitions } from './rdf-partitions.js';
|
|
16
|
+
import { validatePartitions } from './partitioned-shacl.js';
|
|
17
|
+
import { projectRdfFacts } from './rdf-projection.js';
|
|
18
|
+
export { factIri, nodeIri } from './rdf-records.js';
|
|
17
19
|
const engine = new QueryEngine();
|
|
18
20
|
const SparqlParser = createRequire(import.meta.url)('sparqljs').Parser;
|
|
19
21
|
/** Lossless RDF dataset: one named graph per cited assertion, with provenance in the default graph. */
|
|
20
22
|
export class RdfEvidenceGraph {
|
|
21
23
|
snapshot;
|
|
22
|
-
|
|
24
|
+
indexed;
|
|
23
25
|
constructor(snapshot) {
|
|
24
26
|
this.snapshot = snapshot;
|
|
25
|
-
for (const [ordinal, fact] of snapshot.facts.entries()) {
|
|
26
|
-
const record = iri(factIri(fact.id));
|
|
27
|
-
const subject = iri(nodeIri(fact.subject.id));
|
|
28
|
-
const object = iri(nodeIri(fact.object.id));
|
|
29
|
-
const predicate = iri(CG + fact.predicate);
|
|
30
|
-
this.dataset.addQuad(subject, predicate, object, record);
|
|
31
|
-
this.dataset.addQuad(record, iri(RDF + 'type'), iri(RDF + 'Statement'));
|
|
32
|
-
this.dataset.addQuad(record, iri(CG + 'ordinal'), literal(String(ordinal), iri(XSD + 'integer')));
|
|
33
|
-
for (const [p, value] of [['subject', subject], ['predicate', predicate], ['object', object]]) {
|
|
34
|
-
this.dataset.addQuad(record, iri(RDF + p), value);
|
|
35
|
-
}
|
|
36
|
-
for (const [p, value] of Object.entries({
|
|
37
|
-
authority: contextPackEvidenceAuthority(fact), path: fact.provenance.path,
|
|
38
|
-
commit: fact.provenance.commitSha, sourceDigest: fact.provenance.digest,
|
|
39
|
-
extractor: `${fact.extractor.id}@${fact.extractor.version}`,
|
|
40
|
-
snapshotId: fact.snapshotId, record: stableJson(fact),
|
|
41
|
-
...(fact.sourceAuthority ? { sourceAuthority: fact.sourceAuthority } : {}),
|
|
42
|
-
...(fact.extractionReview ? { extractionReview: fact.extractionReview } : {}),
|
|
43
|
-
...(fact.sourceRole !== undefined ? { sourceRole: fact.sourceRole } : {}),
|
|
44
|
-
...(fact.sourcePolicyDigest !== undefined ? { sourcePolicyDigest: fact.sourcePolicyDigest } : {}),
|
|
45
|
-
}))
|
|
46
|
-
this.dataset.addQuad(record, iri(CG + p), literal(value));
|
|
47
|
-
if (fact.sourceOwnership !== undefined) {
|
|
48
|
-
this.dataset.addQuad(record, iri(CG + 'ownershipStatus'), literal(fact.sourceOwnership.status));
|
|
49
|
-
for (const ownerDocument of fact.sourceOwnership.ownerDocuments) {
|
|
50
|
-
this.dataset.addQuad(record, iri(CG + 'ownerDocument'), literal(ownerDocument));
|
|
51
|
-
}
|
|
52
|
-
for (const mappingId of fact.sourceOwnership.mappingIds) {
|
|
53
|
-
this.dataset.addQuad(record, iri(CG + 'ownershipMapping'), literal(mappingId));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
for (const [p, value] of [['startLine', fact.provenance.startLine], ['endLine', fact.provenance.endLine]]) {
|
|
57
|
-
this.dataset.addQuad(record, iri(CG + p), literal(String(value), iri(XSD + 'integer')));
|
|
58
|
-
}
|
|
59
|
-
this.dataset.addQuad(record, iri(CG + 'confidence'), literal(String(fact.confidence), iri(XSD + 'decimal')));
|
|
60
|
-
addCodeEvidence(this.dataset, record, fact, nodeIri);
|
|
61
|
-
addBindingEvidence(this.dataset, record, fact, factIri);
|
|
62
|
-
addCallEvidence(this.dataset, record, fact, factIri);
|
|
63
|
-
addObligationEvidence(this.dataset, record, fact);
|
|
64
|
-
for (const node of [fact.subject, fact.object]) {
|
|
65
|
-
const term = iri(nodeIri(node.id));
|
|
66
|
-
this.dataset.addQuad(term, iri(RDF + 'type'), iri(CG + 'Node'));
|
|
67
|
-
this.dataset.addQuad(term, iri(CG + 'identifier'), literal(node.id));
|
|
68
|
-
this.dataset.addQuad(term, iri(CG + 'kind'), literal(node.kind));
|
|
69
|
-
this.dataset.addQuad(term, iri(CG + 'label'), literal(node.label));
|
|
70
|
-
for (const [key, value] of Object.entries(node.attributes ?? {})) {
|
|
71
|
-
const datatype = typeof value === 'boolean' ? 'boolean' : typeof value === 'number' ? 'decimal' : 'string';
|
|
72
|
-
this.dataset.addQuad(term, iri(`${CG}attribute/${encodeURIComponent(key)}`), literal(String(value), iri(XSD + datatype)), record);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
this.dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'manifest'), literal(stableJson(snapshot.manifest)));
|
|
77
|
-
for (const scope of snapshot.manifest.scope ?? []) {
|
|
78
|
-
this.dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'scope'), literal(scope));
|
|
79
|
-
}
|
|
80
|
-
if (snapshot.manifest.configurationDigest) {
|
|
81
|
-
this.dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'configurationDigest'), literal(snapshot.manifest.configurationDigest));
|
|
82
|
-
}
|
|
83
27
|
}
|
|
84
28
|
async validate(options = {}) {
|
|
85
|
-
// These graph-independent shapes already read the dataset as a union. Index
|
|
86
|
-
// that union once so every property lookup avoids scanning one graph per fact.
|
|
87
|
-
// Keep every triple; the original named dataset retains assertion provenance
|
|
88
|
-
// and remains the source for queries and serialization.
|
|
89
29
|
const counts = { factCount: this.snapshot.facts.length };
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
30
|
+
const { partitions, types } = await measureValidationPhase('build-validation-union', counts, options.onProgress, () => {
|
|
31
|
+
const started = performance.now();
|
|
32
|
+
const partitions = new RdfPartitions(this.snapshot);
|
|
33
|
+
return { partitions, types: partitions.types(completed => {
|
|
34
|
+
if (completed % 10 === 0)
|
|
35
|
+
options.onProgress?.({ phase: 'build-validation-union', status: 'progress',
|
|
36
|
+
elapsedMs: Math.round(performance.now() - started), ...counts, completed, total: partitions.count });
|
|
37
|
+
}) };
|
|
95
38
|
});
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
message: result.message.map((message) => message.value).join('; '),
|
|
39
|
+
const started = performance.now();
|
|
40
|
+
const report = await measureValidationPhase('validate-shacl', counts, options.onProgress, () => validatePartitions(partitions, types, completed => {
|
|
41
|
+
if (completed % 10 === 0)
|
|
42
|
+
options.onProgress?.({ phase: 'validate-shacl', status: 'progress',
|
|
43
|
+
elapsedMs: Math.round(performance.now() - started), ...counts, completed, total: partitions.count });
|
|
102
44
|
}));
|
|
45
|
+
const issues = report.issues;
|
|
103
46
|
const byId = new Map(this.snapshot.facts.map(fact => [fact.id, fact]));
|
|
104
47
|
for (const fact of this.snapshot.facts) {
|
|
105
48
|
for (const message of obligationEvidenceIssues(fact, this.snapshot.manifest.repositoryId))
|
|
@@ -140,7 +83,12 @@ export class RdfEvidenceGraph {
|
|
|
140
83
|
if (ast.type !== 'query' || ast.queryType !== 'SELECT' || ast.from || hasService(ast)) {
|
|
141
84
|
throw new Error('Only local read queries are supported; remote datasets and updates are disabled');
|
|
142
85
|
}
|
|
143
|
-
const
|
|
86
|
+
const indexed = this.indexed ??= new RdfPartitions(this.snapshot);
|
|
87
|
+
const source = {
|
|
88
|
+
match: (s, p, o, g) => Readable.from(indexed.match(s, p, o, g)),
|
|
89
|
+
countQuads: (s, p, o, g) => indexed.countMatches(s, p, o, g),
|
|
90
|
+
};
|
|
91
|
+
const stream = await engine.queryBindings(sparql, { sources: [{ type: 'rdfjs', value: source }], unionDefaultGraph: true });
|
|
144
92
|
const rows = [];
|
|
145
93
|
let truncated = false;
|
|
146
94
|
for await (const binding of stream) {
|
|
@@ -153,18 +101,39 @@ export class RdfEvidenceGraph {
|
|
|
153
101
|
}
|
|
154
102
|
return { rows, truncated };
|
|
155
103
|
}
|
|
156
|
-
/**
|
|
104
|
+
/** Lossless optimized projection, checked for equivalence with the public SPARQL path. */
|
|
157
105
|
async facts() {
|
|
158
|
-
|
|
159
|
-
?fact <${CG}record> ?record; <${CG}ordinal> ?ordinal
|
|
160
|
-
} ORDER BY ?ordinal`, 100_000);
|
|
161
|
-
if (result.truncated)
|
|
162
|
-
throw new Error('Graph exceeds the 100000-fact projection bound');
|
|
163
|
-
return result.rows.map((row) => JSON.parse(row.record));
|
|
106
|
+
return projectRdfFacts(this.snapshot.facts);
|
|
164
107
|
}
|
|
165
|
-
|
|
108
|
+
/** Complete N-Quads in bounded groups; RDF triple order is not a snapshot identity. */
|
|
109
|
+
*serializeChunks() {
|
|
166
110
|
const writer = new Writer({ format: 'N-Quads' });
|
|
167
|
-
|
|
168
|
-
|
|
111
|
+
let buffer = '';
|
|
112
|
+
for (const batch of new RdfPartitions(this.snapshot).named())
|
|
113
|
+
for (const quad of batch) {
|
|
114
|
+
buffer += writer.quadToString(quad.subject, quad.predicate, quad.object, quad.graph);
|
|
115
|
+
if (buffer.length >= 64 * 1024) {
|
|
116
|
+
yield buffer;
|
|
117
|
+
buffer = '';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (buffer)
|
|
121
|
+
yield buffer;
|
|
122
|
+
}
|
|
123
|
+
/** Export large datasets with backpressure, without a whole-dataset string/quad array. */
|
|
124
|
+
async serializeTo(destination) {
|
|
125
|
+
await pipeline(this.serializeChunks(), destination);
|
|
126
|
+
}
|
|
127
|
+
/** Compatibility convenience for small exports; use serializeTo for large graphs. */
|
|
128
|
+
async serialize() {
|
|
129
|
+
const chunks = [];
|
|
130
|
+
let length = 0;
|
|
131
|
+
for (const chunk of this.serializeChunks()) {
|
|
132
|
+
length += chunk.length;
|
|
133
|
+
if (length > constants.MAX_STRING_LENGTH)
|
|
134
|
+
throw new Error('RDF exceeds the JavaScript string limit; use serializeTo() or serializeChunks()');
|
|
135
|
+
chunks.push(chunk);
|
|
136
|
+
}
|
|
137
|
+
return chunks.join('');
|
|
169
138
|
}
|
|
170
139
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export type GraphValidationPhase = 'validate-identities' | 'build-rdf-evidence' | 'build-validation-union' | 'validate-shacl';
|
|
2
2
|
export interface GraphValidationProgress {
|
|
3
3
|
readonly phase: GraphValidationPhase;
|
|
4
|
-
readonly status: 'started' | 'completed' | 'failed';
|
|
4
|
+
readonly status: 'started' | 'completed' | 'failed' | 'progress';
|
|
5
|
+
/** Completed bounded RDF subject groups, when available. */
|
|
6
|
+
readonly completed?: number;
|
|
7
|
+
readonly total?: number;
|
|
5
8
|
/** Wall time for this phase, not a completion estimate or in-phase heartbeat. */
|
|
6
9
|
readonly elapsedMs: number;
|
|
7
10
|
readonly factCount: number;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { persistedJsonDigest } from './util.js';
|
|
2
2
|
/** Identity of persisted JSON, excluding undefined optional fields that JSON cannot retain. */
|
|
3
|
-
export const jsonDigest =
|
|
3
|
+
export const jsonDigest = persistedJsonDigest;
|
package/dist/interfaces/cli.js
CHANGED
|
@@ -103,7 +103,7 @@ function formatHydrationPlan(plan) {
|
|
|
103
103
|
async function main() {
|
|
104
104
|
const args = process.argv.slice(2);
|
|
105
105
|
if (args.includes('--help') || !args.length) {
|
|
106
|
-
process.stdout.write('weftlet mappings plan|mappings propose|mappings read|documents plan|init|config show|config validate|doctor|hydrate|cache adopt|overlay|impact scan|graph neighbors|graph paths|graph compatibility|graph coverage|sparql|validate|govern|visualize|diagnostics show|invariant check|clean-compare|ontology compile\nmappings plan|propose --request <json-file> [--allow-model for propose] previews or runs one opt-in proposal; mappings read --snapshot <id> --key <key> reads saved unreviewed results.\nUse --config <file>; queries accept --snapshot <id>. impact scan accepts --changed-file, --capability, --max-facts, --max-depth, --continuation.\ngraph neighbors --snapshot <id> --node <exact-id> [--direction incoming|outgoing|both] [--predicate <relation>] [--max-facts <1..200>] [--continuation <token>] [--json] reads one-hop saved evidence; repeat --predicate to select multiple relations.\ngraph paths --snapshot <id> --from <exact-id> --to <exact-id> [--direction incoming|outgoing|both] [--predicate <relation>] [--max-depth <1..32>] [--max-paths <1..20>] [--max-steps <1..10000>] [--continuation <token>] [--json] explores cited simple paths; incomplete searches report frontier and limits.\ngraph coverage --snapshot <id> [--project <exact-package-root>] [--max-items <1..200>] [--continuation <token>] [--json] pages saved package/file coverage; absence or zero references is not proof of no impact.\ngraph compatibility --snapshot <id> --producer-fact <assertion-id> --consumer-fact <assertion-id> [--json] compares two documented numeric obligations; advisory only, with explicit unknowns and no implementation or CI verdict.\ndocuments plan --entry <document> [--entry <document>] [--root <path>] [--document-policy <json-file>] [--ref <commit>] [--json] previews prospective committed link coverage; it does not change hydration inputs or estimate model/cache work. Source classification/ownership completeness is separate from traversal readiness. The preview policy is an operation input. Set documentTraversal in configuration to activate traversal hydration; hydrate --plan describes its execution inputs. Safety limits: --max-read-documents, --max-read-bytes, --max-document-bytes.\nhydrate --plan [--json] [--ref <commit>] [--force-semantic] previews scope, cache and budgets without model calls or graph writes. Traversal coverage and role ambiguity block new model work; direct-source selection retains its existing behavior.\nhydrate --json --quiet emits structured semantic failures on stderr with no progress lines; failure stdout stays empty. hydrate --plan maps opaque source IDs to local document paths.\nhydrate|overlay --validation-details explicitly includes local node/fact identities, extractor names and source citations in identity-conflict errors; default reports use opaque references. This flag writes no diagnostic file. Use --json --quiet for structured identity-conflict stderr and empty failure stdout. Validation
|
|
106
|
+
process.stdout.write('weftlet mappings plan|mappings propose|mappings read|documents plan|init|config show|config validate|doctor|hydrate|cache adopt|overlay|impact scan|graph neighbors|graph paths|graph compatibility|graph coverage|sparql|validate|govern|visualize|diagnostics show|invariant check|clean-compare|ontology compile\nmappings plan|propose --request <json-file> [--allow-model for propose] previews or runs one opt-in proposal; mappings read --snapshot <id> --key <key> reads saved unreviewed results.\nUse --config <file>; queries accept --snapshot <id>. impact scan accepts --changed-file, --capability, --max-facts, --max-depth, --continuation.\ngraph neighbors --snapshot <id> --node <exact-id> [--direction incoming|outgoing|both] [--predicate <relation>] [--max-facts <1..200>] [--continuation <token>] [--json] reads one-hop saved evidence; repeat --predicate to select multiple relations.\ngraph paths --snapshot <id> --from <exact-id> --to <exact-id> [--direction incoming|outgoing|both] [--predicate <relation>] [--max-depth <1..32>] [--max-paths <1..20>] [--max-steps <1..10000>] [--continuation <token>] [--json] explores cited simple paths; incomplete searches report frontier and limits.\ngraph coverage --snapshot <id> [--project <exact-package-root>] [--max-items <1..200>] [--continuation <token>] [--json] pages saved package/file coverage; absence or zero references is not proof of no impact.\ngraph compatibility --snapshot <id> --producer-fact <assertion-id> --consumer-fact <assertion-id> [--json] compares two documented numeric obligations; advisory only, with explicit unknowns and no implementation or CI verdict.\ndocuments plan --entry <document> [--entry <document>] [--root <path>] [--document-policy <json-file>] [--ref <commit>] [--json] previews prospective committed link coverage; it does not change hydration inputs or estimate model/cache work. Source classification/ownership completeness is separate from traversal readiness. The preview policy is an operation input. Set documentTraversal in configuration to activate traversal hydration; hydrate --plan describes its execution inputs. Safety limits: --max-read-documents, --max-read-bytes, --max-document-bytes.\nhydrate --plan [--json] [--ref <commit>] [--force-semantic] previews scope, cache and budgets without model calls or graph writes. Traversal coverage and role ambiguity block new model work; direct-source selection retains its existing behavior.\nhydrate --json --quiet emits structured semantic failures on stderr with no progress lines; failure stdout stays empty. hydrate --plan maps opaque source IDs to local document paths.\nhydrate|overlay --validation-details explicitly includes local node/fact identities, extractor names and source citations in identity-conflict errors; default reports use opaque references. This flag writes no diagnostic file. Use --json --quiet for structured identity-conflict stderr and empty failure stdout. Validation reports phase boundaries, elapsed time and completed groups; group counts are not completion-time estimates.\ncache adopt --from-config <previous.json> [--config <current.json>] [--ref <commit>] [--dry-run] returns JSON and copies only validated compatible artifacts, without model calls or snapshot writes. --dry-run performs no cache writes. Agent configurations accept the same --owner-doc, --task and --capability selection as hydrate.\ninit [--yes] [--directory <project>] [--config <file>] [--repository-id <id>] [--code-root <path>] [--semantic-source <path>] [--evidence-root <path>] [--model <name>] [--no-gitignore]\nSource flags may be repeated. init never overwrites an existing config. See README.md for examples.\n');
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
if (await runDocumentCoverage(args, defaultConfigPath))
|
|
@@ -4,7 +4,10 @@ export declare class FileSnapshotStore implements SnapshotStore {
|
|
|
4
4
|
constructor(root: string);
|
|
5
5
|
write(snapshot: GraphSnapshot): Promise<void>;
|
|
6
6
|
read(snapshotId: string): Promise<GraphSnapshot | undefined>;
|
|
7
|
+
private readManifest;
|
|
7
8
|
readHead(headName: string): Promise<GraphSnapshot | undefined>;
|
|
9
|
+
readHeadManifest(headName: string): Promise<GraphSnapshot['manifest'] | undefined>;
|
|
10
|
+
private headId;
|
|
8
11
|
updateHead(headName: string, snapshotId: string): Promise<void>;
|
|
9
12
|
}
|
|
10
13
|
export declare class SemanticArtifactStore {
|
package/dist/storage/storage.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, writeFile, link, rm } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { atomicRename } from './atomic-rename.js';
|
|
5
5
|
import { physicalArtifactKey, temporaryArtifactPath } from '../infrastructure/artifact-paths.js';
|
|
6
|
+
import { readJsonFile, readJsonProperty, writeJsonFile } from './streaming-json.js';
|
|
6
7
|
async function readJson(path) {
|
|
7
8
|
try {
|
|
8
|
-
return
|
|
9
|
+
return await readJsonFile(path);
|
|
9
10
|
}
|
|
10
11
|
catch (error) {
|
|
11
12
|
if (error.code === 'ENOENT')
|
|
@@ -13,8 +14,8 @@ async function readJson(path) {
|
|
|
13
14
|
throw error;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
async function readWithLegacyPath(path, legacyPath) {
|
|
17
|
-
const current = await
|
|
17
|
+
async function readWithLegacyPath(path, legacyPath, read = (readJson)) {
|
|
18
|
+
const current = await read(path);
|
|
18
19
|
if (current !== undefined || path === legacyPath)
|
|
19
20
|
return current;
|
|
20
21
|
// No old file could have this component on supported filesystems. Do not attempt an
|
|
@@ -23,7 +24,7 @@ async function readWithLegacyPath(path, legacyPath) {
|
|
|
23
24
|
return undefined;
|
|
24
25
|
if (process.platform === 'win32' && /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(basename(legacyPath)))
|
|
25
26
|
return undefined;
|
|
26
|
-
return
|
|
27
|
+
return read(legacyPath);
|
|
27
28
|
}
|
|
28
29
|
async function atomicJson(path, value) {
|
|
29
30
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -65,9 +66,9 @@ export class FileSnapshotStore {
|
|
|
65
66
|
}
|
|
66
67
|
async write(snapshot) {
|
|
67
68
|
const destination = snapshotPath(this.root, snapshot.manifest.snapshotId);
|
|
68
|
-
const prior = await this.
|
|
69
|
+
const prior = await this.readManifest(snapshot.manifest.snapshotId);
|
|
69
70
|
if (prior) {
|
|
70
|
-
if (prior.
|
|
71
|
+
if (prior.contentDigest !== snapshot.manifest.contentDigest) {
|
|
71
72
|
throw new Error(`Refusing to overwrite immutable snapshot ${snapshot.manifest.snapshotId} with different content`);
|
|
72
73
|
}
|
|
73
74
|
return; // Preserve an existing legacy file without copying or rewriting its bytes.
|
|
@@ -75,15 +76,15 @@ export class FileSnapshotStore {
|
|
|
75
76
|
await mkdir(dirname(destination), { recursive: true });
|
|
76
77
|
const temporary = temporaryArtifactPath(destination);
|
|
77
78
|
try {
|
|
78
|
-
await
|
|
79
|
+
await writeJsonFile(temporary, snapshot);
|
|
79
80
|
// Linking a complete file publishes atomically without ever replacing a winner.
|
|
80
81
|
await link(temporary, destination);
|
|
81
82
|
}
|
|
82
83
|
catch (error) {
|
|
83
84
|
if (error.code !== 'EEXIST')
|
|
84
85
|
throw error;
|
|
85
|
-
const existing = await this.
|
|
86
|
-
if (existing?.
|
|
86
|
+
const existing = await this.readManifest(snapshot.manifest.snapshotId);
|
|
87
|
+
if (existing?.contentDigest !== snapshot.manifest.contentDigest) {
|
|
87
88
|
throw new Error(`Refusing to overwrite immutable snapshot ${snapshot.manifest.snapshotId} with different content`);
|
|
88
89
|
}
|
|
89
90
|
}
|
|
@@ -94,13 +95,36 @@ export class FileSnapshotStore {
|
|
|
94
95
|
read(snapshotId) {
|
|
95
96
|
return readWithLegacyPath(snapshotPath(this.root, snapshotId), snapshotPath(this.root, snapshotId, true));
|
|
96
97
|
}
|
|
98
|
+
readManifest(snapshotId) {
|
|
99
|
+
return readWithLegacyPath(snapshotPath(this.root, snapshotId), snapshotPath(this.root, snapshotId, true), async (path) => {
|
|
100
|
+
try {
|
|
101
|
+
const manifest = await readJsonProperty(path, 'manifest');
|
|
102
|
+
if (!manifest || typeof manifest.contentDigest !== 'string')
|
|
103
|
+
throw Error('Invalid snapshot manifest');
|
|
104
|
+
return manifest;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error.code === 'ENOENT')
|
|
108
|
+
return undefined;
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
97
113
|
async readHead(headName) {
|
|
114
|
+
const snapshotId = await this.headId(headName);
|
|
115
|
+
return snapshotId ? this.read(snapshotId) : undefined;
|
|
116
|
+
}
|
|
117
|
+
async readHeadManifest(headName) {
|
|
118
|
+
const snapshotId = await this.headId(headName);
|
|
119
|
+
return snapshotId ? this.readManifest(snapshotId) : undefined;
|
|
120
|
+
}
|
|
121
|
+
async headId(headName) {
|
|
98
122
|
const key = safeKey(headName);
|
|
99
123
|
const head = await readWithLegacyPath(join(this.root, 'heads', `${physicalArtifactKey(key)}.json`), join(this.root, 'heads', `${key}.json`));
|
|
100
|
-
return head
|
|
124
|
+
return head?.snapshotId;
|
|
101
125
|
}
|
|
102
126
|
async updateHead(headName, snapshotId) {
|
|
103
|
-
if (!(await this.
|
|
127
|
+
if (!(await this.readManifest(snapshotId)))
|
|
104
128
|
throw new Error('Cannot point a head to an absent snapshot');
|
|
105
129
|
return atomicJson(join(this.root, 'heads', `${physicalArtifactKey(safeKey(headName))}.json`), { snapshotId });
|
|
106
130
|
}
|
|
@@ -138,7 +162,7 @@ export class SemanticArtifactStore {
|
|
|
138
162
|
await mkdir(dirname(path), { recursive: true });
|
|
139
163
|
const temporary = temporaryArtifactPath(path);
|
|
140
164
|
try {
|
|
141
|
-
await
|
|
165
|
+
await writeJsonFile(temporary, artifact);
|
|
142
166
|
await link(temporary, path);
|
|
143
167
|
}
|
|
144
168
|
catch (error) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Ordinary JSON bytes, incrementally encoded/decoded; legacy files need no migration. */
|
|
2
|
+
export declare function readJsonFile<T>(path: string): Promise<T>;
|
|
3
|
+
/** Validate the complete document while retaining only a selected root property. */
|
|
4
|
+
export declare function readJsonProperty<T>(path: string, property: string): Promise<T | undefined>;
|
|
5
|
+
export declare function writeJsonFile(path: string, value: unknown): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
|
+
import { stringifyChunked } from '@discoveryjs/json-ext';
|
|
4
|
+
import { JSONParser, TokenizerError, TokenParserError } from '@streamparser/json';
|
|
5
|
+
/** Ordinary JSON bytes, incrementally encoded/decoded; legacy files need no migration. */
|
|
6
|
+
export async function readJsonFile(path) {
|
|
7
|
+
return parseJsonFile(path, '$');
|
|
8
|
+
}
|
|
9
|
+
/** Validate the complete document while retaining only a selected root property. */
|
|
10
|
+
export async function readJsonProperty(path, property) {
|
|
11
|
+
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(property))
|
|
12
|
+
throw Error('Invalid JSON property selector');
|
|
13
|
+
return parseJsonFile(path, `$.${property}`);
|
|
14
|
+
}
|
|
15
|
+
async function parseJsonFile(path, selector) {
|
|
16
|
+
let value;
|
|
17
|
+
const parser = new JSONParser({ paths: [selector], keepStack: selector === '$' });
|
|
18
|
+
let complete = false;
|
|
19
|
+
let hasInput = false;
|
|
20
|
+
parser.onValue = result => { value = result.value; };
|
|
21
|
+
parser.onEnd = () => { complete = true; };
|
|
22
|
+
parser.onError = error => {
|
|
23
|
+
// Cache inspection distinguishes invalid JSON from I/O/infrastructure errors.
|
|
24
|
+
// Keep that contract without including artifact contents in the public message.
|
|
25
|
+
// 0.0.26 reports a truncated container as plain Error, unlike invalid tokens.
|
|
26
|
+
const incompleteContainer = error.constructor === Error && error.message.startsWith('Parser ended in mid-parsing (state:');
|
|
27
|
+
if (error instanceof TokenizerError || error instanceof TokenParserError || incompleteContainer) {
|
|
28
|
+
throw new SyntaxError('Invalid JSON document', { cause: error });
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
};
|
|
32
|
+
for await (const chunk of createReadStream(path)) {
|
|
33
|
+
if (!hasInput)
|
|
34
|
+
hasInput = chunk.some(byte => ![9, 10, 13, 32].includes(byte));
|
|
35
|
+
parser.write(chunk);
|
|
36
|
+
}
|
|
37
|
+
if (!parser.isEnded)
|
|
38
|
+
parser.end();
|
|
39
|
+
if (!complete || !hasInput)
|
|
40
|
+
throw new SyntaxError('Incomplete JSON document');
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
export async function writeJsonFile(path, value) {
|
|
44
|
+
await pipeline(stringifyChunked(value), createWriteStream(path, { flags: 'wx' }));
|
|
45
|
+
}
|
|
@@ -102,7 +102,7 @@ export function viewerScript() {
|
|
|
102
102
|
if (!nextPath.disabled) status.textContent += ' Continue to examine remaining branches.';
|
|
103
103
|
if (result.search.depthLimitReached) status.textContent += ' Depth limit reached; increase depth and restart.';
|
|
104
104
|
if (result.search.stopReason === 'search-limit') status.textContent += ' Narrow the selection or choose an intermediate node.';
|
|
105
|
-
status.textContent += ' Pending prefixes: ' + result.frontier.
|
|
105
|
+
status.textContent += ' Pending prefixes: ' + result.frontier.pendingCount + ' (' + result.frontier.omittedPending + ' examples omitted); depth-limited extensions: ' + result.frontier.depthLimited.count + '.';
|
|
106
106
|
search.value = ''; cluster.value = ''; document.getElementById('neighbors-only').checked = false;
|
|
107
107
|
update();
|
|
108
108
|
} catch (error) { clearPath(); status.textContent = error.message; update(); }
|
package/docs/1.0-WORK-QUEUE.md
CHANGED
|
@@ -16,18 +16,35 @@ for this candidate. Prior consumer Codex hydration remains evidence for the esta
|
|
|
16
16
|
flow; it does not validate the new proposal operation. No further feature milestone is scheduled
|
|
17
17
|
before this candidate. B6b and the explicit official 1.0 decision remain open.
|
|
18
18
|
|
|
19
|
+
Consumer blocker, 2026-11-02: 0.7.0's richer code graph exceeded Node's whole-string limit before
|
|
20
|
+
strict SHACL/publication. [ADR 040](ADR-040-bounded-graph-io.md) tracks the hashing, persistence,
|
|
21
|
+
RDF/query and bounded-validation patch. Its public large-corpus lifecycle and hosted Linux/Windows checks
|
|
22
|
+
pass; patch publication and private acceptance remain separate steps. No 1.0 approval is implied.
|
|
23
|
+
|
|
24
|
+
Consumer follow-up: **0.7.1 is published**. The maintainer reports successful strict hydration,
|
|
25
|
+
persistence/reload and unchanged repeat of 561,777 facts / 174,710 nodes with all 18 cached
|
|
26
|
+
artifacts preserved. That capacity blocker is resolved in the reported run. B6b remains open:
|
|
27
|
+
known direct paths and specific worker `references`/`consumes` assertions were displaced outside
|
|
28
|
+
fixed retrieval budgets. [ADR 041](ADR-041-bounded-relationship-retrieval.md) tracks the bounded
|
|
29
|
+
query-order patch and its public regression; no extraction refresh is needed for that patch.
|
|
30
|
+
|
|
31
|
+
All three public 554,516-fact strict SHACL passes succeeded but took about 35–37 minutes each
|
|
32
|
+
in this cloud. This is a capacity result, not a 30-minute hydration qualification. B6b must record
|
|
33
|
+
full hydration and agent-query latency on the consumer workload as well as correctness.
|
|
34
|
+
Any further validation optimization must retain full constraints and parity evidence.
|
|
35
|
+
|
|
19
36
|
| Task | Status / dependency | Deliverable and check |
|
|
20
37
|
| --- | --- | --- |
|
|
21
|
-
| **B1a — Exact node neighborhoods** |
|
|
22
|
-
| **B1b — Bounded dependency paths** |
|
|
38
|
+
| **B1a — Exact node neighborhoods** | Shipped in 0.7.0. Validation in ADR 029 and the change PR | `graph.neighbors` through the shared public API, CLI and MCP. Exact snapshot/node IDs, direction/relation filters, cited assertion pagination, remaining-neighbor counts, further-expansion hints and shared evidence state. Check duplicate labels, cycles, self-links, parallel evidence, missing/deleted nodes, stale/tampered cursors, dirty overlays and real extracted cross-service facts. |
|
|
39
|
+
| **B1b — Bounded dependency paths** | Shipped in 0.7.0. Validation in ADR 030 and the change PR | `graph.paths` enumerates cited simple paths through API/CLI/MCP, with depth/work/output limits, continuation and explicit pending/depth-cut frontier. The offline viewer uses the same search function on displayed facts. Check competing branches, cycles, deep paths, scope omissions and all 4,096 directed four-node graphs against an independent oracle. |
|
|
23
40
|
| **B2a — Freeze code-graph comparison** | Frozen; verifier and fixture typechecks pass | 51 labeled code cases, three explicit gaps, unchanged A1–A3/dispatch controls, eight scenarios, pinned tools and bounded metrics in `evaluations/code-graph`. Read-only verification rejects input/label drift and supports immutable Git refs. No comparative execution or private/model inputs. |
|
|
24
41
|
| **B2b — Run comparison and decide** | Measured; [ADR 031](ADR-031-code-extractor-selection.md) selects richer SCIP projection | Compare current projection, richer raw SCIP and pinned Graphify static extraction. Record correctness, citations, identity stability, cold/edit/repeat cost and packaging. Make an evidence-backed keep/improve/replace decision before B3's code-entity schema. |
|
|
25
|
-
| **B3a — Explicit rule-to-symbol resolution** | Implemented in two increments ([ADR 032](ADR-032-scip-code-evidence.md), [ADR 033](ADR-033-document-symbol-bindings.md));
|
|
26
|
-
| **B3b — Assess missing mappings** | Deterministic assessment complete ([ADR 034](ADR-034-mapping-gaps.md)); bounded opt-in implementation
|
|
27
|
-
| **B3c — Same-file call exploration** | Implemented ([ADR 035](ADR-035-static-call-evidence.md));
|
|
28
|
-
| **B4a — Producer/consumer obligations** | Implemented for explicit declarations ([ADR 036](ADR-036-contract-obligation-declarations.md));
|
|
29
|
-
| **B4b — Compatibility acceptance** | Implemented for selected explicit numeric declarations ([ADR 037](ADR-037-selected-contract-compatibility.md));
|
|
30
|
-
| **B5 — Monorepo coverage** | Implemented for saved TypeScript-family inventory and package boundaries ([ADR 038](ADR-038-monorepo-code-coverage.md));
|
|
42
|
+
| **B3a — Explicit rule-to-symbol resolution** | Implemented in two increments ([ADR 032](ADR-032-scip-code-evidence.md), [ADR 033](ADR-033-document-symbol-bindings.md)); shipped in 0.7.0 | Retain local/private symbols, same-file references and repeated source occurrences before resolving explicit document paths/symbol references. Include aliases, overloads, duplicate names, rename/delete and unresolved targets; retain both source spans/revisions and mapping authority. Follow the canonical [same-file acceptance boundary](PRE-1.0-BASELINE.md#same-file-exploration-acceptance). |
|
|
43
|
+
| **B3b — Assess missing mappings** | Deterministic assessment complete ([ADR 034](ADR-034-mapping-gaps.md)); bounded opt-in implementation shipped in 0.7.0 ([ADR 039](ADR-039-mapping-proposals.md)); live validation tentative and deferred by the maintainer for 0.7.0 | Identify gaps remaining after explicit resolution. Evaluate a bounded inference proposal only if justified, with an approved model budget before paid work. Never infer owner approval or silently convert proposed mappings into proven behavior. Record any scoped deferral for maintainer review. |
|
|
44
|
+
| **B3c — Same-file call exploration** | Implemented ([ADR 035](ADR-035-static-call-evidence.md)); shipped in 0.7.0 | Select and test static call analysis, preserving caller/callee/callsite and unresolved targets. Distinguish direct calls from references and callback registration; never claim observed execution. Meet the public large-file extraction/query acceptance in the canonical boundary, including recursion, unrelated functions, shadowed names, repeated sites and cross-file callers. No duplicate-behavior equivalence or mandatory second indexer. |
|
|
45
|
+
| **B4a — Producer/consumer obligations** | Implemented for explicit declarations ([ADR 036](ADR-036-contract-obligation-declarations.md)); shipped in 0.7.0 | Represent and retrieve cited guarantees/requirements on queue and stored-data contracts, with domain, stage, units, freshness and version where documented. Keep absent fields unknown. |
|
|
46
|
+
| **B4b — Compatibility acceptance** | Implemented for selected explicit numeric declarations ([ADR 037](ADR-037-selected-contract-compatibility.md)); shipped in 0.7.0 | Public queue/stored-data cases cover subset, insufficient guarantee, disjoint limits, revalidation and unknowns. Shared `graph.compatibility` retains two cited assertions and follow-up IDs. This is advisory; hard failures require approved deterministic policy. |
|
|
47
|
+
| **B5 — Monorepo coverage** | Implemented for saved TypeScript-family inventory and package boundaries ([ADR 038](ADR-038-monorepo-code-coverage.md)); shipped in 0.7.0 | `graph.coverage` pages selected/requested/indexed files and package connections; shared overview distinguishes saved scope from returned-page counts. Real barrels, aliases, TSX, independent/omitted services, declaration-only and unresolved external targets are tested. Unknown/zero observations never prove no impact. |
|
|
31
48
|
| **B6a — Candidate qualification** | B1–B5 decisions/increments reviewed | Freeze tasks and expected downstream obligations before measuring. Run public end-to-end, fresh-install and supported Windows/Linux release gates. Publish a minor acceptance candidate with versioned evidence and limitations. |
|
|
32
49
|
| **B6b — Live consumer and 1.0 decision** | Published candidate | TradePilot runs real hydration/repeat and agent follow-up tasks; retain private requests/citations/timings locally and share a sanitized result. Assess usefulness and misses separately from pipeline success. Fix blockers, then explicitly review official 1.0. |
|
|
33
50
|
|