@weftlet/toolkit 0.4.0 → 0.6.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 +149 -14
- package/dist/application/cache-adoption.d.ts +2 -1
- package/dist/application/cache-adoption.js +12 -5
- package/dist/application/document-coverage.d.ts +28 -0
- package/dist/application/document-coverage.js +58 -0
- package/dist/application/document-overlay.d.ts +14 -0
- package/dist/application/document-overlay.js +31 -0
- package/dist/application/extraction-identity.d.ts +2 -1
- package/dist/application/extraction-identity.js +7 -0
- package/dist/application/hydration-inputs.d.ts +15 -0
- package/dist/application/hydration-inputs.js +55 -0
- package/dist/application/hydration-plan.d.ts +7 -0
- package/dist/application/hydration-plan.js +18 -5
- package/dist/application/hydrator.d.ts +2 -0
- package/dist/application/hydrator.js +40 -18
- package/dist/application/overlay.d.ts +3 -1
- package/dist/application/overlay.js +29 -9
- package/dist/application/saved-evidence.d.ts +2 -2
- package/dist/application/semantic-run.d.ts +3 -0
- package/dist/application/semantic-scope.d.ts +1 -1
- package/dist/application/semantic-scope.js +2 -2
- package/dist/configuration/cache.js +13 -1
- package/dist/configuration/config.d.ts +2 -0
- package/dist/configuration/config.js +6 -1
- package/dist/configuration/document-traversal.d.ts +7 -0
- package/dist/configuration/document-traversal.js +13 -0
- package/dist/extraction/canonical-document-links.d.ts +28 -0
- package/dist/extraction/canonical-document-links.js +95 -0
- package/dist/extraction/document-classification.d.ts +44 -0
- package/dist/extraction/document-classification.js +161 -0
- package/dist/extraction/document-evidence.d.ts +21 -0
- package/dist/extraction/document-evidence.js +83 -0
- package/dist/extraction/document-traversal.d.ts +76 -0
- package/dist/extraction/document-traversal.js +210 -0
- package/dist/extraction/document.d.ts +3 -1
- package/dist/extraction/document.js +3 -1
- package/dist/graph/authority.js +3 -0
- package/dist/graph/coverage.d.ts +8 -0
- package/dist/graph/coverage.js +96 -0
- package/dist/graph/document-role-description.d.ts +5 -0
- package/dist/graph/document-role-description.js +18 -0
- package/dist/graph/identity-preflight.d.ts +57 -0
- package/dist/graph/identity-preflight.js +71 -0
- package/dist/graph/query.d.ts +2 -2
- package/dist/graph/query.js +22 -62
- package/dist/graph/rdf.d.ts +4 -1
- package/dist/graph/rdf.js +21 -6
- package/dist/graph/shacl-shapes.js +26 -0
- package/dist/graph/validation-progress.d.ts +14 -0
- package/dist/graph/validation-progress.js +18 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/interfaces/cli.js +29 -5
- package/dist/interfaces/document-coverage.d.ts +9 -0
- package/dist/interfaces/document-coverage.js +117 -0
- package/dist/repository/git.d.ts +11 -0
- package/dist/repository/git.js +38 -0
- package/dist/visualization/visualization.js +21 -6
- package/docs/ADR-017-shacl-validation-union.md +4 -0
- package/docs/ADR-018-operational-semantic-cache.md +2 -2
- package/docs/ADR-019-semantic-failure-diagnostics.md +2 -2
- package/docs/ADR-020-canonical-physical-file-producers.md +2 -2
- package/docs/ADR-021-identity-preflight-and-validation-timing.md +84 -0
- package/docs/ADR-022-release-documentation.md +44 -0
- package/docs/ADR-023-document-coverage-preview.md +64 -0
- package/docs/ADR-024-implementation-coverage.md +51 -0
- package/docs/ADR-025-document-classification.md +78 -0
- package/docs/ADR-026-traversal-hydration.md +94 -0
- package/docs/ADR-027-validation-closeout.md +155 -0
- package/docs/CONSUMER-TRIAL.md +1 -1
- package/docs/MIGRATING-TO-WEFTLET.md +1 -1
- package/docs/PROVIDERS.md +17 -1
- package/docs/RELEASE-1.0-PLAN.md +177 -48
- package/docs/RELEASING.md +46 -8
- package/docs/ROADMAP.md +59 -28
- package/docs/VALIDATION.md +451 -0
- package/docs/benchmarks/validation-2026-10-28/README.md +25 -0
- package/docs/benchmarks/validation-2026-10-28/large.json +1 -0
- package/docs/benchmarks/validation-2026-10-28/lifecycle.json +1 -0
- package/docs/benchmarks/validation-2026-10-28/native.json +1 -0
- package/docs/benchmarks/validation-2026-10-28/rich.json +1 -0
- package/docs/extraction-manifest.json +73 -17
- package/examples/generic-typescript/document-policy.json +9 -0
- package/package.json +7 -4
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { repositoryFileNode } from '@weftlet/core';
|
|
2
|
+
import { contextPackEvidenceAuthority } from './authority.js';
|
|
3
|
+
export const contextPackLanes = [
|
|
4
|
+
'productIntent',
|
|
5
|
+
'contractsAndInvariants',
|
|
6
|
+
'implementationAndConsumers',
|
|
7
|
+
'validationEvidence',
|
|
8
|
+
];
|
|
9
|
+
function isValidationFact(fact) {
|
|
10
|
+
if (fact.sourceRole !== undefined) {
|
|
11
|
+
return fact.sourceRole === 'validation' || fact.subject.kind === 'Evidence' ||
|
|
12
|
+
fact.object.kind === 'Evidence' || fact.predicate === 'verifiedBy';
|
|
13
|
+
}
|
|
14
|
+
const path = fact.provenance.path.toLowerCase();
|
|
15
|
+
return (fact.subject.kind === 'Evidence' ||
|
|
16
|
+
fact.object.kind === 'Evidence' ||
|
|
17
|
+
fact.predicate === 'verifiedBy' ||
|
|
18
|
+
/(?:^|\/)(?:test|tests|testing|validation|evidence)(?:\/|$)/.test(path) ||
|
|
19
|
+
/\.(?:spec|test)\.[cm]?[jt]sx?$/.test(path));
|
|
20
|
+
}
|
|
21
|
+
export function isDeclaredFact(fact) {
|
|
22
|
+
if (fact.sourceRole !== undefined && fact.sourceRole !== 'intent')
|
|
23
|
+
return false;
|
|
24
|
+
if (fact.authority)
|
|
25
|
+
return fact.authority === 'declared' && !isValidationFact(fact);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
export function contextPackLaneForFact(fact) {
|
|
29
|
+
if (fact.sourceRole === 'validation')
|
|
30
|
+
return 'validationEvidence';
|
|
31
|
+
if (fact.extractor.id === 'otlp-runtime-evidence')
|
|
32
|
+
return undefined;
|
|
33
|
+
const kinds = new Set([fact.subject.kind, fact.object.kind]);
|
|
34
|
+
if (isValidationFact(fact)) {
|
|
35
|
+
return 'validationEvidence';
|
|
36
|
+
}
|
|
37
|
+
if (kinds.has('Contract') ||
|
|
38
|
+
kinds.has('Invariant') ||
|
|
39
|
+
kinds.has('PersistenceModel') ||
|
|
40
|
+
['governedBy', 'definedBy', 'persistsTo', 'authorizedBy'].includes(fact.predicate)) {
|
|
41
|
+
return 'contractsAndInvariants';
|
|
42
|
+
}
|
|
43
|
+
if ((fact.sourceRole === undefined || fact.sourceRole === 'intent') &&
|
|
44
|
+
(isDeclaredFact(fact) || fact.sourceAuthority === 'declared') &&
|
|
45
|
+
(kinds.has('Document') ||
|
|
46
|
+
kinds.has('Capability') ||
|
|
47
|
+
kinds.has('Workflow') ||
|
|
48
|
+
['decisionAuthority', 'operationallyOwnedBy', 'references'].includes(fact.predicate))) {
|
|
49
|
+
return 'productIntent';
|
|
50
|
+
}
|
|
51
|
+
return 'implementationAndConsumers';
|
|
52
|
+
}
|
|
53
|
+
export function coverageDescription(coverage) {
|
|
54
|
+
return coverage.scope === 'returned-page' && coverage.countsAreNonExclusive
|
|
55
|
+
? 'Counts describe evidence on this returned page and may overlap; a code relationship can also describe a contract. They are not a percentage, a count of display groups, or proof that behavior is correct.'
|
|
56
|
+
: 'Coverage scope and overlap metadata are unavailable for this Context Pack; re-query saved evidence for an explicit report.';
|
|
57
|
+
}
|
|
58
|
+
/** Reporting only: never changes a fact's authority, identity, primary lane or stored bytes. */
|
|
59
|
+
export function isImplementationEvidence(fact) {
|
|
60
|
+
if (fact.sourceRole !== undefined || isValidationFact(fact) || fact.extractor.id === 'otlp-runtime-evidence' ||
|
|
61
|
+
fact.extractionReview === 'unreviewed' || contextPackEvidenceAuthority(fact) !== 'implemented')
|
|
62
|
+
return false;
|
|
63
|
+
const path = fact.provenance.path;
|
|
64
|
+
if (!path || /(?:^|\/)(?:test|tests|testing|validation|evidence|__tests__|__mocks__)(?:\/|$)/i.test(path.replaceAll('\\', '/')))
|
|
65
|
+
return false;
|
|
66
|
+
const file = repositoryFileNode(path);
|
|
67
|
+
if (file.kind !== 'Component')
|
|
68
|
+
return false;
|
|
69
|
+
const nodes = [fact.subject, fact.object];
|
|
70
|
+
// Explicit adapter authority also supports logical component/provider identities.
|
|
71
|
+
if (fact.authority === 'implemented')
|
|
72
|
+
return nodes.some(node => ['Component', 'Provider'].includes(node.kind));
|
|
73
|
+
// Legacy deterministic adapters omitted authority. Require their cited physical file,
|
|
74
|
+
// rather than treating every fact in the implementation display group as code evidence.
|
|
75
|
+
return nodes.some(node => node.kind === 'Component' && node.id === file.id);
|
|
76
|
+
}
|
|
77
|
+
export function coverageForFacts(facts) {
|
|
78
|
+
const counts = {
|
|
79
|
+
productIntent: 0, contractsAndInvariants: 0, implementationAndConsumers: 0, validationEvidence: 0,
|
|
80
|
+
};
|
|
81
|
+
for (const fact of facts) {
|
|
82
|
+
const lane = contextPackLaneForFact(fact);
|
|
83
|
+
if (lane && lane !== 'implementationAndConsumers')
|
|
84
|
+
counts[lane]++;
|
|
85
|
+
if (isImplementationEvidence(fact))
|
|
86
|
+
counts.implementationAndConsumers++;
|
|
87
|
+
}
|
|
88
|
+
return { scope: 'returned-page', countsAreNonExclusive: true, counts,
|
|
89
|
+
gaps: [
|
|
90
|
+
...contextPackLanes.filter(lane => counts[lane] === 0)
|
|
91
|
+
.map(lane => `No ${lane} evidence appears on this returned page.`),
|
|
92
|
+
...(facts.some(fact => fact.sourceAuthority === 'declared' && fact.extractionReview === 'unreviewed')
|
|
93
|
+
? ['Approved-source extraction remains unreviewed; verify cited intent before using it as a rule or gate.'] : []),
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DocumentSelectionRecord, GraphFact } from '@weftlet/core';
|
|
2
|
+
/** Plain text for evidence details; the rendering context must escape it. */
|
|
3
|
+
export declare function sourceDocumentDescription(fact: GraphFact): string | undefined;
|
|
4
|
+
/** Completeness is scoped to the configured canonical links, not all product knowledge. */
|
|
5
|
+
export declare function documentSelectionDescription(selection?: DocumentSelectionRecord): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Plain text for evidence details; the rendering context must escape it. */
|
|
2
|
+
export function sourceDocumentDescription(fact) {
|
|
3
|
+
const parts = [];
|
|
4
|
+
if (fact.sourceRole !== undefined)
|
|
5
|
+
parts.push(`source role ${fact.sourceRole}`);
|
|
6
|
+
if (fact.sourceOwnership) {
|
|
7
|
+
parts.push(`ownership ${fact.sourceOwnership.status}`, `owner documents ${fact.sourceOwnership.ownerDocuments.join(', ') || '(none)'}`);
|
|
8
|
+
}
|
|
9
|
+
return parts.length ? parts.join('; ') : undefined;
|
|
10
|
+
}
|
|
11
|
+
/** Completeness is scoped to the configured canonical links, not all product knowledge. */
|
|
12
|
+
export function documentSelectionDescription(selection) {
|
|
13
|
+
if (!selection)
|
|
14
|
+
return '';
|
|
15
|
+
return `Document traversal: ${selection.complete ? 'complete within configured links' : 'INCOMPLETE'}; ` +
|
|
16
|
+
`${selection.documentPaths.length} discovered documents; ${selection.semanticPaths.length} semantic candidates; ` +
|
|
17
|
+
`${selection.excluded.length} excluded from semantic extraction. Ownership and assertion approval are separate.`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ExtractorIdentity, type GraphFact, type GraphNodeKind, type SourceSpan } from '@weftlet/core';
|
|
2
|
+
/** Diagnostic detail is an invocation option, not extraction policy or a cache identity input. */
|
|
3
|
+
export interface GraphIdentityPreflightOptions {
|
|
4
|
+
readonly repositoryId: string;
|
|
5
|
+
readonly includeDetails?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type ReportedIdentityKind = GraphNodeKind | `unknown-kind-${string}`;
|
|
8
|
+
export interface GraphIdentityOccurrenceDetails {
|
|
9
|
+
readonly nodeId: string;
|
|
10
|
+
readonly nodeKind: GraphNodeKind;
|
|
11
|
+
readonly factId: string;
|
|
12
|
+
readonly extractor: ExtractorIdentity;
|
|
13
|
+
readonly provenance: SourceSpan;
|
|
14
|
+
readonly confidence: number;
|
|
15
|
+
readonly authority?: GraphFact['authority'];
|
|
16
|
+
readonly sourceAuthority?: GraphFact['sourceAuthority'];
|
|
17
|
+
readonly extractionReview?: GraphFact['extractionReview'];
|
|
18
|
+
}
|
|
19
|
+
export interface GraphIdentityOccurrence {
|
|
20
|
+
readonly factIndex: number;
|
|
21
|
+
readonly side: 'subject' | 'object';
|
|
22
|
+
readonly kind: ReportedIdentityKind;
|
|
23
|
+
readonly factRef: string;
|
|
24
|
+
readonly sourceRef: string;
|
|
25
|
+
readonly extractorRef: string;
|
|
26
|
+
readonly details?: GraphIdentityOccurrenceDetails;
|
|
27
|
+
}
|
|
28
|
+
export interface GraphIdentityConflict {
|
|
29
|
+
readonly nodeRef: string;
|
|
30
|
+
readonly kinds: readonly ReportedIdentityKind[];
|
|
31
|
+
readonly occurrences: readonly GraphIdentityOccurrence[];
|
|
32
|
+
readonly details?: {
|
|
33
|
+
readonly nodeId: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface GraphIdentityPreflightReport {
|
|
37
|
+
readonly schemaVersion: 1;
|
|
38
|
+
readonly kind: 'graph-identity-preflight';
|
|
39
|
+
readonly category: 'extraction-gap';
|
|
40
|
+
readonly phase: 'validate-identities';
|
|
41
|
+
readonly passed: boolean;
|
|
42
|
+
/** A passing identity preflight never establishes full graph conformance. */
|
|
43
|
+
readonly shaclValidated: false;
|
|
44
|
+
readonly factCount: number;
|
|
45
|
+
readonly nodeCount: number;
|
|
46
|
+
readonly conflictCount: number;
|
|
47
|
+
readonly conflicts: readonly GraphIdentityConflict[];
|
|
48
|
+
}
|
|
49
|
+
/** Find the single-kind constraint's conflicts without constructing RDF or changing any fact. */
|
|
50
|
+
export declare function inspectGraphIdentities(facts: readonly GraphFact[], options: GraphIdentityPreflightOptions): GraphIdentityPreflightReport;
|
|
51
|
+
/** The message is safe even when the caller explicitly requested local report details. */
|
|
52
|
+
export declare class GraphIdentityConflictError extends Error {
|
|
53
|
+
readonly report: GraphIdentityPreflightReport;
|
|
54
|
+
readonly code = "node-kind-conflict";
|
|
55
|
+
constructor(report: GraphIdentityPreflightReport);
|
|
56
|
+
}
|
|
57
|
+
export declare function assertGraphIdentities(facts: readonly GraphFact[], options: GraphIdentityPreflightOptions): GraphIdentityPreflightReport;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { sha256, stableJson } from '@weftlet/core';
|
|
2
|
+
const publicKinds = new Set([
|
|
3
|
+
'Project', 'Document', 'Capability', 'Workflow', 'Component', 'Contract', 'Evidence',
|
|
4
|
+
'Invariant', 'Snapshot', 'Provider', 'PersistenceModel', 'Scope',
|
|
5
|
+
]);
|
|
6
|
+
/** Find the single-kind constraint's conflicts without constructing RDF or changing any fact. */
|
|
7
|
+
export function inspectGraphIdentities(facts, options) {
|
|
8
|
+
const identities = new Map();
|
|
9
|
+
for (const fact of facts)
|
|
10
|
+
for (const node of [fact.subject, fact.object]) {
|
|
11
|
+
let kinds = identities.get(node.id);
|
|
12
|
+
if (!kinds)
|
|
13
|
+
identities.set(node.id, kinds = new Set());
|
|
14
|
+
kinds.add(node.kind);
|
|
15
|
+
}
|
|
16
|
+
const ref = (domain, ...values) => `identity-${domain}-${sha256(stableJson(['graph-identity-preflight-v1', options.repositoryId, domain, ...values]))}`;
|
|
17
|
+
const reportedKind = (kind) => publicKinds.has(kind)
|
|
18
|
+
? kind : `unknown-kind-${sha256(stableJson(['graph-identity-kind-v1', options.repositoryId, kind]))}`;
|
|
19
|
+
const conflicts = new Map();
|
|
20
|
+
for (const [id, kinds] of identities)
|
|
21
|
+
if (kinds.size > 1) {
|
|
22
|
+
conflicts.set(id, { nodeRef: ref('node', id), kinds: [...kinds].map(reportedKind), occurrences: [],
|
|
23
|
+
...(options.includeDetails ? { details: { nodeId: id } } : {}) });
|
|
24
|
+
}
|
|
25
|
+
// The common conflict-free path allocates no evidence reports. Preserve every conflicting
|
|
26
|
+
// occurrence, including duplicate fact IDs and the two ends of a self-reference.
|
|
27
|
+
if (conflicts.size)
|
|
28
|
+
for (const [factIndex, fact] of facts.entries()) {
|
|
29
|
+
const sides = ['subject', 'object'].filter(side => conflicts.has(fact[side].id));
|
|
30
|
+
if (!sides.length)
|
|
31
|
+
continue;
|
|
32
|
+
const references = { factRef: ref('fact', fact.id), sourceRef: ref('source', fact.provenance.path),
|
|
33
|
+
extractorRef: ref('extractor', fact.extractor.id, fact.extractor.version) };
|
|
34
|
+
for (const side of sides) {
|
|
35
|
+
const node = fact[side];
|
|
36
|
+
conflicts.get(node.id).occurrences.push({ factIndex, side, kind: reportedKind(node.kind), ...references,
|
|
37
|
+
...(options.includeDetails ? { details: {
|
|
38
|
+
nodeId: node.id, nodeKind: node.kind, factId: fact.id,
|
|
39
|
+
extractor: { id: fact.extractor.id, version: fact.extractor.version },
|
|
40
|
+
provenance: { path: fact.provenance.path, startLine: fact.provenance.startLine,
|
|
41
|
+
endLine: fact.provenance.endLine, digest: fact.provenance.digest, commitSha: fact.provenance.commitSha },
|
|
42
|
+
confidence: fact.confidence,
|
|
43
|
+
...(fact.authority === undefined ? {} : { authority: fact.authority }),
|
|
44
|
+
...(fact.sourceAuthority === undefined ? {} : { sourceAuthority: fact.sourceAuthority }),
|
|
45
|
+
...(fact.extractionReview === undefined ? {} : { extractionReview: fact.extractionReview }),
|
|
46
|
+
} } : {}) });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { schemaVersion: 1, kind: 'graph-identity-preflight', category: 'extraction-gap',
|
|
50
|
+
phase: 'validate-identities', passed: conflicts.size === 0, shaclValidated: false,
|
|
51
|
+
factCount: facts.length, nodeCount: identities.size, conflictCount: conflicts.size,
|
|
52
|
+
conflicts: [...conflicts.values()] };
|
|
53
|
+
}
|
|
54
|
+
/** The message is safe even when the caller explicitly requested local report details. */
|
|
55
|
+
export class GraphIdentityConflictError extends Error {
|
|
56
|
+
report;
|
|
57
|
+
code = 'node-kind-conflict';
|
|
58
|
+
constructor(report) {
|
|
59
|
+
super(`Graph identity preflight found ${report.conflictCount} node kind conflict(s) across ${report.factCount} facts. ` +
|
|
60
|
+
'Full SHACL validation was not run. Use --validation-details for local node and cited evidence details. ' +
|
|
61
|
+
'Correct the extractor or assertion identity disagreement, then retry; no graph facts were rewritten by this check.');
|
|
62
|
+
this.report = report;
|
|
63
|
+
this.name = 'GraphIdentityConflictError';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function assertGraphIdentities(facts, options) {
|
|
67
|
+
const report = inspectGraphIdentities(facts, options);
|
|
68
|
+
if (!report.passed)
|
|
69
|
+
throw new GraphIdentityConflictError(report);
|
|
70
|
+
return report;
|
|
71
|
+
}
|
package/dist/graph/query.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ContextPack,
|
|
1
|
+
import type { ContextPack, ContextPackRenderer, ImpactQueryService, ImpactScanRequest, SnapshotStore } from "@weftlet/core";
|
|
2
|
+
export { contextPackLaneForFact } from './coverage.js';
|
|
2
3
|
export declare const DEFAULT_CONTEXT_PACK_FACTS = 60;
|
|
3
4
|
export declare const MAX_CONTEXT_PACK_FACTS = 200;
|
|
4
|
-
export declare function contextPackLaneForFact(fact: GraphFact): ContextPackLane | undefined;
|
|
5
5
|
export { contextPackEvidenceAuthority } from "./authority.js";
|
|
6
6
|
export declare class MarkdownContextPackRenderer implements ContextPackRenderer {
|
|
7
7
|
render(pack: Omit<ContextPack, 'markdown'>): string;
|
package/dist/graph/query.js
CHANGED
|
@@ -4,49 +4,11 @@ import { RdfEvidenceGraph } from "./rdf.js";
|
|
|
4
4
|
import { sha256, stableJson } from "../infrastructure/util.js";
|
|
5
5
|
import { diagnosticSummary, selectDiagnostics } from "./diagnostics.js";
|
|
6
6
|
import { factSearchText, IMPACT_RANKING_VERSION, rankImpactFacts } from './ranking.js';
|
|
7
|
+
import { contextPackLanes, contextPackLaneForFact, isDeclaredFact, coverageForFacts, coverageDescription } from './coverage.js';
|
|
8
|
+
import { documentSelectionDescription, sourceDocumentDescription } from './document-role-description.js';
|
|
9
|
+
export { contextPackLaneForFact } from './coverage.js';
|
|
7
10
|
export const DEFAULT_CONTEXT_PACK_FACTS = 60;
|
|
8
11
|
export const MAX_CONTEXT_PACK_FACTS = 200;
|
|
9
|
-
const contextPackLanes = [
|
|
10
|
-
'productIntent',
|
|
11
|
-
'contractsAndInvariants',
|
|
12
|
-
'implementationAndConsumers',
|
|
13
|
-
'validationEvidence',
|
|
14
|
-
];
|
|
15
|
-
function isValidationFact(fact) {
|
|
16
|
-
const path = fact.provenance.path.toLowerCase();
|
|
17
|
-
return (fact.subject.kind === 'Evidence' ||
|
|
18
|
-
fact.object.kind === 'Evidence' ||
|
|
19
|
-
fact.predicate === 'verifiedBy' ||
|
|
20
|
-
/(?:^|\/)(?:test|tests|testing|validation|evidence)(?:\/|$)/.test(path) ||
|
|
21
|
-
/\.(?:spec|test)\.[cm]?[jt]sx?$/.test(path));
|
|
22
|
-
}
|
|
23
|
-
function isDeclaredFact(fact) {
|
|
24
|
-
if (fact.authority)
|
|
25
|
-
return fact.authority === 'declared' && !isValidationFact(fact);
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
export function contextPackLaneForFact(fact) {
|
|
29
|
-
if (fact.extractor.id === 'otlp-runtime-evidence')
|
|
30
|
-
return undefined;
|
|
31
|
-
const kinds = new Set([fact.subject.kind, fact.object.kind]);
|
|
32
|
-
if (isValidationFact(fact)) {
|
|
33
|
-
return 'validationEvidence';
|
|
34
|
-
}
|
|
35
|
-
if (kinds.has('Contract') ||
|
|
36
|
-
kinds.has('Invariant') ||
|
|
37
|
-
kinds.has('PersistenceModel') ||
|
|
38
|
-
['governedBy', 'definedBy', 'persistsTo', 'authorizedBy'].includes(fact.predicate)) {
|
|
39
|
-
return 'contractsAndInvariants';
|
|
40
|
-
}
|
|
41
|
-
if ((isDeclaredFact(fact) || fact.sourceAuthority === 'declared') &&
|
|
42
|
-
(kinds.has('Document') ||
|
|
43
|
-
kinds.has('Capability') ||
|
|
44
|
-
kinds.has('Workflow') ||
|
|
45
|
-
['decisionAuthority', 'operationallyOwnedBy', 'references'].includes(fact.predicate))) {
|
|
46
|
-
return 'productIntent';
|
|
47
|
-
}
|
|
48
|
-
return 'implementationAndConsumers';
|
|
49
|
-
}
|
|
50
12
|
export { contextPackEvidenceAuthority } from "./authority.js";
|
|
51
13
|
function selectBalancedFacts(ranked, maxFacts) {
|
|
52
14
|
const queues = Object.fromEntries(contextPackLanes.map((lane) => [
|
|
@@ -63,11 +25,7 @@ function selectBalancedFacts(ranked, maxFacts) {
|
|
|
63
25
|
break;
|
|
64
26
|
}
|
|
65
27
|
}
|
|
66
|
-
|
|
67
|
-
lane,
|
|
68
|
-
facts.filter((fact) => contextPackLaneForFact(fact) === lane).length,
|
|
69
|
-
]));
|
|
70
|
-
return { facts, counts };
|
|
28
|
+
return facts;
|
|
71
29
|
}
|
|
72
30
|
const searchStopWords = new Set([
|
|
73
31
|
'about',
|
|
@@ -99,6 +57,12 @@ function boundedFactLimit(value) {
|
|
|
99
57
|
}
|
|
100
58
|
return Math.min(value, MAX_CONTEXT_PACK_FACTS);
|
|
101
59
|
}
|
|
60
|
+
function markdownFactDetails(fact) {
|
|
61
|
+
const document = sourceDocumentDescription(fact)?.replace(/[\\`*_{}\[\]()<>!|]/g, '\\$&').replace(/[\r\n]/g, ' ');
|
|
62
|
+
return `confidence ${fact.confidence}` +
|
|
63
|
+
(fact.sourceAuthority ? `; source ${fact.sourceAuthority}; extraction ${fact.extractionReview ?? 'unknown'}` : '') +
|
|
64
|
+
(document ? `; ${document}` : '');
|
|
65
|
+
}
|
|
102
66
|
export class MarkdownContextPackRenderer {
|
|
103
67
|
render(pack) {
|
|
104
68
|
const lines = [
|
|
@@ -106,6 +70,7 @@ export class MarkdownContextPackRenderer {
|
|
|
106
70
|
'',
|
|
107
71
|
`Snapshot: \`${pack.snapshot.snapshotId}\` (${pack.snapshot.commitSha})`,
|
|
108
72
|
`Configuration: ${pack.snapshot.configurationDigest ?? 'legacy/unknown'}; scope: ${pack.snapshot.scope?.join(', ') ?? 'legacy/unknown'}`,
|
|
73
|
+
...[documentSelectionDescription(pack.snapshot.documentSelection)].filter(Boolean),
|
|
109
74
|
...(pack.exploration ? [
|
|
110
75
|
`Ranking: ${pack.exploration.rankingVersion ?? 'legacy/unknown'}`,
|
|
111
76
|
`Relationships returned: ${pack.exploration.returned}/${pack.exploration.reachableFacts}; truncated: ${pack.exploration.truncated}; depth limit reached: ${pack.exploration.depthLimitReached}`,
|
|
@@ -130,17 +95,18 @@ export class MarkdownContextPackRenderer {
|
|
|
130
95
|
...pack.invariants.map((item) => `- ${item.status}: ${item.message}`),
|
|
131
96
|
'',
|
|
132
97
|
'## Coverage',
|
|
98
|
+
coverageDescription(pack.coverage),
|
|
133
99
|
...contextPackLanes.map((lane) => `- ${lane}: ${pack.coverage.counts[lane]} relationships`),
|
|
134
100
|
...(pack.coverage.gaps.length
|
|
135
101
|
? pack.coverage.gaps.map((gap) => `- GAP: ${gap}`)
|
|
136
|
-
: ['- No
|
|
102
|
+
: ['- No coverage gaps detected on this returned page.']),
|
|
137
103
|
...contextPackLanes.flatMap((lane) => [
|
|
138
104
|
'',
|
|
139
105
|
`## ${lane}`,
|
|
140
106
|
...pack.facts
|
|
141
107
|
.filter((fact) => contextPackLaneForFact(fact) === lane)
|
|
142
108
|
.map((fact) => `- [${contextPackEvidenceAuthority(fact)}] ${fact.subject.label} —${fact.predicate}→ ${fact.object.label} ` +
|
|
143
|
-
`(\`${fact.provenance.path}:${fact.provenance.startLine}\`,
|
|
109
|
+
`(\`${fact.provenance.path}:${fact.provenance.startLine}\`, ${markdownFactDetails(fact)})`),
|
|
144
110
|
]),
|
|
145
111
|
'',
|
|
146
112
|
'## Runtime evidence',
|
|
@@ -226,7 +192,7 @@ export class SnapshotImpactQueryService {
|
|
|
226
192
|
}
|
|
227
193
|
// Balance evidence lanes among similarly ranked facts, not across unrelated distance tiers.
|
|
228
194
|
const allBalanced = [...tiers.values()].flatMap((tier) => [
|
|
229
|
-
...selectBalancedFacts(tier, tier.length)
|
|
195
|
+
...selectBalancedFacts(tier, tier.length),
|
|
230
196
|
...tier.filter((fact) => contextPackLaneForFact(fact) === undefined),
|
|
231
197
|
]);
|
|
232
198
|
const { continuation: _continuation, snapshotId: _snapshotId, ...query } = request;
|
|
@@ -249,7 +215,7 @@ export class SnapshotImpactQueryService {
|
|
|
249
215
|
offset = token.offset;
|
|
250
216
|
}
|
|
251
217
|
const facts = allBalanced.slice(offset, offset + maxFacts);
|
|
252
|
-
const
|
|
218
|
+
const coverage = coverageForFacts(facts);
|
|
253
219
|
const nextOffset = offset + facts.length;
|
|
254
220
|
const continuation = nextOffset < allBalanced.length ? Buffer.from(JSON.stringify({
|
|
255
221
|
snapshot: snapshot.manifest.snapshotId, digest: snapshot.manifest.contentDigest,
|
|
@@ -294,13 +260,13 @@ export class SnapshotImpactQueryService {
|
|
|
294
260
|
for (const [kind, missing, reason] of [
|
|
295
261
|
[
|
|
296
262
|
'missingIntent',
|
|
297
|
-
|
|
298
|
-
'No
|
|
263
|
+
coverage.counts.productIntent === 0,
|
|
264
|
+
'No product-intent relationships appear on this returned page. Inspect continuation, scope and owner sources before concluding a requirement is missing.',
|
|
299
265
|
],
|
|
300
266
|
[
|
|
301
267
|
'implementationGaps',
|
|
302
|
-
|
|
303
|
-
'No implementation
|
|
268
|
+
coverage.counts.implementationAndConsumers === 0,
|
|
269
|
+
'No production implementation evidence appears on this returned page. Inspect continuation, scope and code before concluding behavior is missing.',
|
|
304
270
|
],
|
|
305
271
|
[
|
|
306
272
|
'runtimeGaps',
|
|
@@ -329,20 +295,14 @@ export class SnapshotImpactQueryService {
|
|
|
329
295
|
overlayDigest: snapshot.manifest.overlayDigest,
|
|
330
296
|
overlayCapture: snapshot.manifest.overlayCapture,
|
|
331
297
|
extractorVersions: snapshot.manifest.extractorVersions,
|
|
298
|
+
...(snapshot.manifest.documentSelection ? { documentSelection: snapshot.manifest.documentSelection } : {}),
|
|
332
299
|
},
|
|
333
300
|
capabilities,
|
|
334
301
|
facts,
|
|
335
302
|
invariants: snapshot.invariants.filter((item) => item.provenance.some((span) => suggestedFiles.includes(span.path))),
|
|
336
303
|
runtimeEvidence,
|
|
337
304
|
suggestedFiles,
|
|
338
|
-
coverage
|
|
339
|
-
counts: balanced.counts,
|
|
340
|
-
gaps: [...contextPackLanes
|
|
341
|
-
.filter((lane) => balanced.counts[lane] === 0)
|
|
342
|
-
.map((lane) => `No ${lane} evidence matched this request.`),
|
|
343
|
-
...(facts.some((fact) => fact.sourceAuthority === 'declared' && fact.extractionReview === 'unreviewed')
|
|
344
|
-
? ['Approved-source extraction remains unreviewed; verify cited intent before using it as a rule or gate.'] : [])],
|
|
345
|
-
},
|
|
305
|
+
coverage,
|
|
346
306
|
diagnostics: selectDiagnostics(snapshot, request, facts, focus, queryDiagnostics),
|
|
347
307
|
exploration: {
|
|
348
308
|
rankingVersion: IMPACT_RANKING_VERSION,
|
package/dist/graph/rdf.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GraphFact, GraphSnapshot } from "@weftlet/core";
|
|
2
|
+
import { type GraphValidationProgress } from './validation-progress.js';
|
|
2
3
|
export { CG } from './shacl-shapes.js';
|
|
3
4
|
export declare const nodeIri: (id: string) => string;
|
|
4
5
|
export declare const factIri: (id: string) => string;
|
|
@@ -7,7 +8,9 @@ export declare class RdfEvidenceGraph {
|
|
|
7
8
|
readonly snapshot: GraphSnapshot;
|
|
8
9
|
private readonly dataset;
|
|
9
10
|
constructor(snapshot: GraphSnapshot);
|
|
10
|
-
validate(
|
|
11
|
+
validate(options?: {
|
|
12
|
+
readonly onProgress?: (event: GraphValidationProgress) => void;
|
|
13
|
+
}): Promise<{
|
|
11
14
|
conforms: boolean;
|
|
12
15
|
issues: {
|
|
13
16
|
category: "extraction-gap";
|
package/dist/graph/rdf.js
CHANGED
|
@@ -5,6 +5,7 @@ import { contextPackEvidenceAuthority } from "./authority.js";
|
|
|
5
5
|
import { stableJson } from "../infrastructure/util.js";
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
7
|
import { CG, RDF, XSD, evidenceShapes } from './shacl-shapes.js';
|
|
8
|
+
import { measureValidationPhase } from './validation-progress.js';
|
|
8
9
|
const { namedNode: iri, literal } = DataFactory;
|
|
9
10
|
export { CG } from './shacl-shapes.js';
|
|
10
11
|
export const nodeIri = (id) => `urn:context-graph:node:${encodeURIComponent(id)}`;
|
|
@@ -35,8 +36,19 @@ export class RdfEvidenceGraph {
|
|
|
35
36
|
snapshotId: fact.snapshotId, record: stableJson(fact),
|
|
36
37
|
...(fact.sourceAuthority ? { sourceAuthority: fact.sourceAuthority } : {}),
|
|
37
38
|
...(fact.extractionReview ? { extractionReview: fact.extractionReview } : {}),
|
|
39
|
+
...(fact.sourceRole !== undefined ? { sourceRole: fact.sourceRole } : {}),
|
|
40
|
+
...(fact.sourcePolicyDigest !== undefined ? { sourcePolicyDigest: fact.sourcePolicyDigest } : {}),
|
|
38
41
|
}))
|
|
39
42
|
this.dataset.addQuad(record, iri(CG + p), literal(value));
|
|
43
|
+
if (fact.sourceOwnership !== undefined) {
|
|
44
|
+
this.dataset.addQuad(record, iri(CG + 'ownershipStatus'), literal(fact.sourceOwnership.status));
|
|
45
|
+
for (const ownerDocument of fact.sourceOwnership.ownerDocuments) {
|
|
46
|
+
this.dataset.addQuad(record, iri(CG + 'ownerDocument'), literal(ownerDocument));
|
|
47
|
+
}
|
|
48
|
+
for (const mappingId of fact.sourceOwnership.mappingIds) {
|
|
49
|
+
this.dataset.addQuad(record, iri(CG + 'ownershipMapping'), literal(mappingId));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
40
52
|
for (const [p, value] of [['startLine', fact.provenance.startLine], ['endLine', fact.provenance.endLine]]) {
|
|
41
53
|
this.dataset.addQuad(record, iri(CG + p), literal(String(value), iri(XSD + 'integer')));
|
|
42
54
|
}
|
|
@@ -61,16 +73,19 @@ export class RdfEvidenceGraph {
|
|
|
61
73
|
this.dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'configurationDigest'), literal(snapshot.manifest.configurationDigest));
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
|
-
async validate() {
|
|
76
|
+
async validate(options = {}) {
|
|
65
77
|
// These graph-independent shapes already read the dataset as a union. Index
|
|
66
78
|
// that union once so every property lookup avoids scanning one graph per fact.
|
|
67
79
|
// Keep every triple; the original named dataset retains assertion provenance
|
|
68
80
|
// and remains the source for queries and serialization.
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
const counts = { factCount: this.snapshot.facts.length };
|
|
82
|
+
const validationGraph = await measureValidationPhase('build-validation-union', counts, options.onProgress, () => {
|
|
83
|
+
const union = new Store();
|
|
84
|
+
for (const { subject, predicate, object } of this.dataset)
|
|
85
|
+
union.addQuad(subject, predicate, object);
|
|
86
|
+
return union;
|
|
87
|
+
});
|
|
88
|
+
const report = await measureValidationPhase('validate-shacl', counts, options.onProgress, () => new SHACLValidator(new Store(evidenceShapes)).validate(validationGraph));
|
|
74
89
|
const issues = report.results.map((result) => ({
|
|
75
90
|
category: 'extraction-gap',
|
|
76
91
|
focus: result.focusNode?.value,
|
|
@@ -24,4 +24,30 @@ cg:FactShape a sh:NodeShape; sh:targetClass rdf:Statement;
|
|
|
24
24
|
sh:property [ sh:path cg:extractor; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ].
|
|
25
25
|
cg:NodeShape a sh:NodeShape; sh:targetClass cg:Node;
|
|
26
26
|
sh:property [ sh:path cg:kind; sh:minCount 1; sh:maxCount 1 ].
|
|
27
|
+
|
|
28
|
+
# Optional source metadata targets only assertions that carry those predicates.
|
|
29
|
+
# Legacy facts do not acquire additional property or ownership consistency checks.
|
|
30
|
+
cg:SourceRoleShape a sh:NodeShape; sh:targetSubjectsOf cg:sourceRole;
|
|
31
|
+
sh:property [ sh:path cg:sourceRole; sh:maxCount 1; sh:datatype xsd:string;
|
|
32
|
+
sh:in ("intent" "plan" "validation" "reference" "unclassified") ].
|
|
33
|
+
cg:SourcePolicyShape a sh:NodeShape; sh:targetSubjectsOf cg:sourcePolicyDigest;
|
|
34
|
+
sh:property [ sh:path cg:sourcePolicyDigest; sh:maxCount 1; sh:datatype xsd:string; sh:minLength 1 ].
|
|
35
|
+
cg:SourceOwnershipShape a sh:NodeShape;
|
|
36
|
+
sh:targetSubjectsOf cg:ownershipStatus, cg:ownerDocument, cg:ownershipMapping;
|
|
37
|
+
sh:property [ sh:path cg:ownershipStatus; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string;
|
|
38
|
+
sh:in ("resolved" "missing" "ambiguous" "unavailable" "unapproved" "ineligible" "not-required") ];
|
|
39
|
+
sh:property [ sh:path cg:ownerDocument; sh:datatype xsd:string; sh:minLength 1 ];
|
|
40
|
+
sh:property [ sh:path cg:ownershipMapping; sh:datatype xsd:string; sh:minLength 1 ];
|
|
41
|
+
# Structural consistency does not prove approval, ownership correctness or source availability.
|
|
42
|
+
sh:or (
|
|
43
|
+
[ sh:property [ sh:path cg:ownershipStatus; sh:in ("resolved" "unavailable" "unapproved" "ineligible") ];
|
|
44
|
+
sh:property [ sh:path cg:ownerDocument; sh:minCount 1; sh:maxCount 1 ];
|
|
45
|
+
sh:property [ sh:path cg:ownershipMapping; sh:minCount 1 ] ]
|
|
46
|
+
[ sh:property [ sh:path cg:ownershipStatus; sh:hasValue "ambiguous" ];
|
|
47
|
+
sh:property [ sh:path cg:ownerDocument; sh:minCount 2 ];
|
|
48
|
+
sh:property [ sh:path cg:ownershipMapping; sh:minCount 1 ] ]
|
|
49
|
+
[ sh:property [ sh:path cg:ownershipStatus; sh:in ("missing" "not-required") ];
|
|
50
|
+
sh:property [ sh:path cg:ownerDocument; sh:maxCount 0 ];
|
|
51
|
+
sh:property [ sh:path cg:ownershipMapping; sh:maxCount 0 ] ]
|
|
52
|
+
).
|
|
27
53
|
`);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type GraphValidationPhase = 'validate-identities' | 'build-rdf-evidence' | 'build-validation-union' | 'validate-shacl';
|
|
2
|
+
export interface GraphValidationProgress {
|
|
3
|
+
readonly phase: GraphValidationPhase;
|
|
4
|
+
readonly status: 'started' | 'completed' | 'failed';
|
|
5
|
+
/** Wall time for this phase, not a completion estimate or in-phase heartbeat. */
|
|
6
|
+
readonly elapsedMs: number;
|
|
7
|
+
readonly factCount: number;
|
|
8
|
+
readonly nodeCount?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Phase boundaries remain observable even when an operation blocks same-thread timers. */
|
|
11
|
+
export declare function measureValidationPhase<T>(phase: GraphValidationPhase, counts: {
|
|
12
|
+
readonly factCount: number;
|
|
13
|
+
readonly nodeCount?: number;
|
|
14
|
+
}, onProgress: ((event: GraphValidationProgress) => void) | undefined, operation: () => T | Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Phase boundaries remain observable even when an operation blocks same-thread timers. */
|
|
2
|
+
export async function measureValidationPhase(phase, counts, onProgress, operation) {
|
|
3
|
+
const started = performance.now();
|
|
4
|
+
const emit = (status) => onProgress?.({
|
|
5
|
+
phase, status, ...counts, elapsedMs: Math.max(0, Math.round(performance.now() - started)),
|
|
6
|
+
});
|
|
7
|
+
emit('started');
|
|
8
|
+
let result;
|
|
9
|
+
try {
|
|
10
|
+
result = await operation();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
emit('failed');
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
emit('completed');
|
|
17
|
+
return result;
|
|
18
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,3 +13,10 @@ export * from './application/hydration-plan.js';
|
|
|
13
13
|
export * from './application/cache-adoption.js';
|
|
14
14
|
export * from './application/hydration-failure.js';
|
|
15
15
|
export * from './extraction/semantic-diagnostics.js';
|
|
16
|
+
export * from './graph/identity-preflight.js';
|
|
17
|
+
export * from './graph/validation-progress.js';
|
|
18
|
+
export * from './application/document-coverage.js';
|
|
19
|
+
export * from './extraction/document-traversal.js';
|
|
20
|
+
export * from './extraction/document-classification.js';
|
|
21
|
+
export * from './extraction/document-evidence.js';
|
|
22
|
+
export type { DocumentTraversalConfig } from './configuration/document-traversal.js';
|
package/dist/index.js
CHANGED
|
@@ -14,3 +14,9 @@ export * from './application/hydration-plan.js';
|
|
|
14
14
|
export * from './application/cache-adoption.js';
|
|
15
15
|
export * from './application/hydration-failure.js';
|
|
16
16
|
export * from './extraction/semantic-diagnostics.js';
|
|
17
|
+
export * from './graph/identity-preflight.js';
|
|
18
|
+
export * from './graph/validation-progress.js';
|
|
19
|
+
export * from './application/document-coverage.js';
|
|
20
|
+
export * from './extraction/document-traversal.js';
|
|
21
|
+
export * from './extraction/document-classification.js';
|
|
22
|
+
export * from './extraction/document-evidence.js';
|