@weftlet/toolkit 0.6.1 → 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.
Files changed (177) hide show
  1. package/README.md +168 -1
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/dist/application/application.d.ts +10 -0
  4. package/dist/application/application.js +20 -0
  5. package/dist/application/document-code-bindings.d.ts +11 -0
  6. package/dist/application/document-code-bindings.js +25 -0
  7. package/dist/application/document-obligations.d.ts +8 -0
  8. package/dist/application/document-obligations.js +11 -0
  9. package/dist/application/draft-facts.d.ts +3 -0
  10. package/dist/application/draft-facts.js +37 -0
  11. package/dist/application/extraction-identity.d.ts +2 -2
  12. package/dist/application/extraction-identity.js +7 -1
  13. package/dist/application/extractor-results.d.ts +6 -0
  14. package/dist/application/extractor-results.js +6 -0
  15. package/dist/application/hydrator.js +20 -3
  16. package/dist/application/mapping-proposals.d.ts +108 -0
  17. package/dist/application/mapping-proposals.js +142 -0
  18. package/dist/application/mapping-service.d.ts +2 -0
  19. package/dist/application/mapping-service.js +5 -0
  20. package/dist/application/overlay.js +36 -22
  21. package/dist/application/saved-code-coverage.d.ts +40 -0
  22. package/dist/application/saved-code-coverage.js +20 -0
  23. package/dist/application/saved-compatibility.d.ts +38 -0
  24. package/dist/application/saved-compatibility.js +20 -0
  25. package/dist/application/saved-evidence.d.ts +17 -11
  26. package/dist/application/saved-evidence.js +44 -40
  27. package/dist/application/saved-exploration.d.ts +57 -0
  28. package/dist/application/saved-exploration.js +21 -0
  29. package/dist/application/saved-paths.d.ts +58 -0
  30. package/dist/application/saved-paths.js +20 -0
  31. package/dist/extraction/call-syntax.d.ts +18 -0
  32. package/dist/extraction/call-syntax.js +61 -0
  33. package/dist/extraction/canonical-document-links.d.ts +5 -1
  34. package/dist/extraction/canonical-document-links.js +4 -4
  35. package/dist/extraction/code-inventory.d.ts +6 -0
  36. package/dist/extraction/code-inventory.js +26 -0
  37. package/dist/extraction/document-code-bindings.d.ts +14 -0
  38. package/dist/extraction/document-code-bindings.js +121 -0
  39. package/dist/extraction/document-obligations.d.ts +12 -0
  40. package/dist/extraction/document-obligations.js +112 -0
  41. package/dist/extraction/mapping-candidates.d.ts +13 -0
  42. package/dist/extraction/mapping-candidates.js +46 -0
  43. package/dist/extraction/mapping-output.d.ts +75 -0
  44. package/dist/extraction/mapping-output.js +53 -0
  45. package/dist/extraction/mapping-plan.d.ts +7 -0
  46. package/dist/extraction/mapping-plan.js +117 -0
  47. package/dist/extraction/mapping-prompt.d.ts +2 -0
  48. package/dist/extraction/mapping-prompt.js +17 -0
  49. package/dist/extraction/mapping-provider.d.ts +15 -0
  50. package/dist/extraction/mapping-provider.js +60 -0
  51. package/dist/extraction/mapping-request.d.ts +3 -0
  52. package/dist/extraction/mapping-request.js +32 -0
  53. package/dist/extraction/routing.js +5 -20
  54. package/dist/extraction/scip-occurrences.d.ts +3 -0
  55. package/dist/extraction/scip-occurrences.js +102 -0
  56. package/dist/extraction/scip-ranges.d.ts +6 -0
  57. package/dist/extraction/scip-ranges.js +31 -0
  58. package/dist/extraction/scip-runtime.d.ts +39 -0
  59. package/dist/extraction/scip-runtime.js +8 -0
  60. package/dist/extraction/scip-symbols.d.ts +21 -0
  61. package/dist/extraction/scip-symbols.js +91 -0
  62. package/dist/extraction/semantic.d.ts +1 -1
  63. package/dist/extraction/semantic.js +5 -17
  64. package/dist/extraction/static-calls.d.ts +10 -0
  65. package/dist/extraction/static-calls.js +133 -0
  66. package/dist/extraction/static.d.ts +3 -20
  67. package/dist/extraction/static.js +8 -84
  68. package/dist/extraction/symbol-resolution.d.ts +12 -0
  69. package/dist/extraction/symbol-resolution.js +51 -0
  70. package/dist/extraction/symbol-selectors.d.ts +11 -0
  71. package/dist/extraction/symbol-selectors.js +51 -0
  72. package/dist/graph/binding-rdf.d.ts +7 -0
  73. package/dist/graph/binding-rdf.js +109 -0
  74. package/dist/graph/call-rdf.d.ts +4 -0
  75. package/dist/graph/call-rdf.js +94 -0
  76. package/dist/graph/call-shapes.d.ts +2 -0
  77. package/dist/graph/call-shapes.js +28 -0
  78. package/dist/graph/code-coverage-projection.d.ts +9 -0
  79. package/dist/graph/code-coverage-projection.js +117 -0
  80. package/dist/graph/code-coverage-renderer.d.ts +2 -0
  81. package/dist/graph/code-coverage-renderer.js +15 -0
  82. package/dist/graph/code-coverage-request.d.ts +6 -0
  83. package/dist/graph/code-coverage-request.js +19 -0
  84. package/dist/graph/code-coverage.d.ts +6 -0
  85. package/dist/graph/code-coverage.js +54 -0
  86. package/dist/graph/code-description.d.ts +4 -0
  87. package/dist/graph/code-description.js +24 -0
  88. package/dist/graph/code-rdf.d.ts +5 -0
  89. package/dist/graph/code-rdf.js +72 -0
  90. package/dist/graph/compatibility-context.d.ts +2 -0
  91. package/dist/graph/compatibility-context.js +18 -0
  92. package/dist/graph/compatibility-renderer.d.ts +2 -0
  93. package/dist/graph/compatibility-renderer.js +45 -0
  94. package/dist/graph/compatibility-request.d.ts +3 -0
  95. package/dist/graph/compatibility-request.js +18 -0
  96. package/dist/graph/compatibility.d.ts +7 -0
  97. package/dist/graph/compatibility.js +67 -0
  98. package/dist/graph/constraint-comparison.d.ts +7 -0
  99. package/dist/graph/constraint-comparison.js +49 -0
  100. package/dist/graph/coverage.d.ts +2 -2
  101. package/dist/graph/coverage.js +8 -3
  102. package/dist/graph/diagnostics.d.ts +1 -1
  103. package/dist/graph/diagnostics.js +10 -6
  104. package/dist/graph/evidence-description.d.ts +3 -0
  105. package/dist/graph/evidence-description.js +13 -0
  106. package/dist/graph/identity-preflight.js +1 -1
  107. package/dist/graph/neighborhood-renderer.d.ts +2 -0
  108. package/dist/graph/neighborhood-renderer.js +38 -0
  109. package/dist/graph/neighborhood-request.d.ts +9 -0
  110. package/dist/graph/neighborhood-request.js +37 -0
  111. package/dist/graph/neighborhood.d.ts +8 -0
  112. package/dist/graph/neighborhood.js +100 -0
  113. package/dist/graph/obligation-rdf.d.ts +5 -0
  114. package/dist/graph/obligation-rdf.js +67 -0
  115. package/dist/graph/obligation-shapes.d.ts +2 -0
  116. package/dist/graph/obligation-shapes.js +32 -0
  117. package/dist/graph/path-request.d.ts +11 -0
  118. package/dist/graph/path-request.js +24 -0
  119. package/dist/graph/path-search.d.ts +45 -0
  120. package/dist/graph/path-search.js +101 -0
  121. package/dist/graph/paths-renderer.d.ts +2 -0
  122. package/dist/graph/paths-renderer.js +36 -0
  123. package/dist/graph/paths.d.ts +7 -0
  124. package/dist/graph/paths.js +74 -0
  125. package/dist/graph/query.js +23 -6
  126. package/dist/graph/rdf.js +26 -0
  127. package/dist/graph/shacl-shapes.js +70 -0
  128. package/dist/graph/snapshot-context.d.ts +3 -0
  129. package/dist/graph/snapshot-context.js +8 -0
  130. package/dist/index.d.ts +23 -0
  131. package/dist/index.js +22 -0
  132. package/dist/infrastructure/json-digest.d.ts +2 -0
  133. package/dist/infrastructure/json-digest.js +3 -0
  134. package/dist/infrastructure/unique-json.d.ts +2 -0
  135. package/dist/infrastructure/unique-json.js +20 -0
  136. package/dist/interfaces/cli.js +33 -1
  137. package/dist/interfaces/code-coverage.d.ts +46 -0
  138. package/dist/interfaces/code-coverage.js +18 -0
  139. package/dist/interfaces/graph-compatibility.d.ts +35 -0
  140. package/dist/interfaces/graph-compatibility.js +21 -0
  141. package/dist/interfaces/graph-exploration.d.ts +59 -0
  142. package/dist/interfaces/graph-exploration.js +27 -0
  143. package/dist/interfaces/graph-paths.d.ts +77 -0
  144. package/dist/interfaces/graph-paths.js +31 -0
  145. package/dist/interfaces/graph-query-args.d.ts +2 -0
  146. package/dist/interfaces/graph-query-args.js +25 -0
  147. package/dist/interfaces/mapping-proposals.d.ts +204 -0
  148. package/dist/interfaces/mapping-proposals.js +74 -0
  149. package/dist/interfaces/mcp.js +31 -3
  150. package/dist/storage/mapping-proposals.d.ts +9 -0
  151. package/dist/storage/mapping-proposals.js +42 -0
  152. package/dist/visualization/evidence-sources.d.ts +6 -0
  153. package/dist/visualization/evidence-sources.js +19 -0
  154. package/dist/visualization/viewer-interaction.d.ts +5 -8
  155. package/dist/visualization/viewer-interaction.js +46 -44
  156. package/dist/visualization/visualization.js +14 -5
  157. package/docs/1.0-WORK-QUEUE.md +38 -0
  158. package/docs/ADR-029-exact-graph-neighborhoods.md +70 -0
  159. package/docs/ADR-030-bounded-evidence-paths.md +97 -0
  160. package/docs/ADR-031-code-extractor-selection.md +91 -0
  161. package/docs/ADR-032-scip-code-evidence.md +73 -0
  162. package/docs/ADR-033-document-symbol-bindings.md +83 -0
  163. package/docs/ADR-034-mapping-gaps.md +80 -0
  164. package/docs/ADR-035-static-call-evidence.md +78 -0
  165. package/docs/ADR-036-contract-obligation-declarations.md +124 -0
  166. package/docs/ADR-037-selected-contract-compatibility.md +93 -0
  167. package/docs/ADR-038-monorepo-code-coverage.md +105 -0
  168. package/docs/ADR-039-mapping-proposals.md +119 -0
  169. package/docs/GRAPHIFY-EVALUATION.md +21 -4
  170. package/docs/PRE-1.0-BASELINE.md +49 -2
  171. package/docs/RELEASE-1.0-PLAN.md +14 -2
  172. package/docs/RELEASING.md +3 -3
  173. package/docs/ROADMAP.md +27 -6
  174. package/docs/VALIDATION.md +437 -14
  175. package/docs/extraction-manifest.json +248 -21
  176. package/package.json +8 -4
  177. package/templates/context-graph-development/SKILL.md +30 -1
@@ -0,0 +1,45 @@
1
+ import { sourceLocation } from '@weftlet/core';
2
+ import { factEvidenceDescription } from './evidence-description.js';
3
+ import { diagnosticSummary } from './diagnostics.js';
4
+ import { coverageDescription } from './coverage.js';
5
+ const quoted = (value) => JSON.stringify(value).replace(/[\\`*_{}\[\]()<>!|]/g, '\\$&');
6
+ const explanations = {
7
+ 'missing-assertion': 'One or both exact assertion IDs are absent from this snapshot.',
8
+ 'invalid-evidence': 'The selected records lack valid obligation metadata or matching snapshot/revision provenance.',
9
+ 'wrong-roles': 'Select a producer guarantee first and a consumer requirement second.',
10
+ 'different-contracts': 'The assertions do not identify the same exact repository/scoped/transport/versioned contract.',
11
+ 'unknown-version': 'A contract version is unknown; no version equivalence is assumed.',
12
+ 'missing-context': 'Documented domain, stage, numeric constraint or unit is missing.',
13
+ 'different-domain': 'The documented domains differ.',
14
+ 'different-metric': 'The documented metrics differ; no semantic equivalence is inferred.',
15
+ 'different-unit': 'The units differ; no unit conversion is inferred.',
16
+ 'different-stage': 'The measurement stages differ; no elapsed-time or transformation model is available.',
17
+ 'unsupported-number': 'A numeric limit is nonfinite or outside the supported safe numeric magnitude.',
18
+ 'competing-declarations': 'Other assertions describe this endpoint/property; no winning declaration was selected.',
19
+ 'extraction-gaps': 'Relevant source/extraction gaps remain in the saved evidence.',
20
+ subset: 'At the same declared stage, every value allowed by the producer constraint is allowed by the consumer constraint.',
21
+ 'overlap-only': 'Some allowed producer values do not satisfy the consumer constraint. This alone is not a contradiction.',
22
+ disjoint: 'At the same declared stage, no value satisfies both constraints. This is a documented range incompatibility for an untransformed accepted value.',
23
+ 'consumer-revalidation': 'The consumer explicitly requires revalidation. Verify its check and rejection behavior; the declaration alone does not prove them.',
24
+ };
25
+ export function renderContractCompatibility(report) {
26
+ const { snapshot, assessment, context, evidenceState } = report;
27
+ return ['# Selected contract constraint assessment',
28
+ `Snapshot: ${quoted(snapshot.snapshotId)} (${snapshot.commitSha}); configuration ${snapshot.configurationDigest ?? 'unknown'}`,
29
+ `Scope: ${snapshot.scope?.map(quoted).join(', ') ?? 'unknown'}; selected assertions: ${quoted(report.selection.producerFactId)}, ${quoted(report.selection.consumerFactId)}`,
30
+ `Result: ${assessment.status}; authority: ${assessment.authority}; method: ${assessment.version}; CI gate eligible: false; fulfillment: not evaluated`,
31
+ ...assessment.reasons.map(reason => `- ${explanations[reason]}`),
32
+ ...(evidenceState ? [`Stale: ${evidenceState.stale}; configuration matches: ${evidenceState.configurationMatches}; extraction compatible: ${evidenceState.extractionCompatible ?? 'unknown'}`,
33
+ `Excluded working changes: ${evidenceState.excludedWorkingChanges.map(quoted).join(', ') || '(none)'}; overlay freshness: ${evidenceState.overlayFreshness ?? 'not an overlay'}`] : []),
34
+ '', '## Cited declarations', ...report.facts.map(fact => `- ${quoted(fact.id)} [${fact.authority ?? 'unspecified'}; source ${fact.sourceAuthority ?? 'unspecified'}; review ${fact.extractionReview ?? 'unspecified'}] ` +
35
+ `${quoted(sourceLocation(fact.provenance))}–${fact.provenance.endLine}@${fact.provenance.commitSha}: ${quoted(factEvidenceDescription(fact) ?? 'Not a structured obligation')}`),
36
+ '', '## Assessment limits', ...assessment.conditions.map(condition => `- ${condition}`),
37
+ coverageDescription(report.coverage), ...report.coverage.gaps.map(quoted),
38
+ '', '## Further evidence',
39
+ `Other contract-incident assertions: ${context.otherIncidentFacts}; competing declarations: ${context.competingFacts}; omitted competing IDs: ${context.omittedCompetingFacts}.`,
40
+ ...context.competingFactIds.map(id => `- Competing assertion: ${quoted(id)}`),
41
+ ...context.contractNodeIds.map(id => `- Use graph.neighbors with contract ${quoted(id)} and the same snapshot for all declarations and continuation.`),
42
+ ...context.endpointNodeIds.map(id => `- Use graph.neighbors with endpoint ${quoted(id)} for indexed definitions, mappings and tests; file existence alone proves no fulfillment.`),
43
+ '', '## Scoped diagnostics', ...diagnosticSummary(report).map(quoted),
44
+ ].join('\n');
45
+ }
@@ -0,0 +1,3 @@
1
+ import type { ContractCompatibilityRequest } from '@weftlet/core';
2
+ /** No search, implicit current head, policy mutation or caller-supplied authority. */
3
+ export declare function parseContractCompatibilityRequest(value: unknown): ContractCompatibilityRequest;
@@ -0,0 +1,18 @@
1
+ /** No search, implicit current head, policy mutation or caller-supplied authority. */
2
+ export function parseContractCompatibilityRequest(value) {
3
+ if (!value || typeof value !== 'object' || Array.isArray(value))
4
+ throw new Error('graph.compatibility arguments must be an object');
5
+ const item = value, fields = ['snapshotId', 'producerFactId', 'consumerFactId'];
6
+ if (Object.keys(item).some(key => !fields.includes(key)))
7
+ throw new Error('Unknown graph.compatibility field');
8
+ const identifier = (key, maximum) => {
9
+ const value = item[key];
10
+ if (typeof value !== 'string' || !value.trim() || value.length > maximum)
11
+ throw new Error(`${key} must be a nonempty string of at most ${maximum} characters`);
12
+ return value;
13
+ };
14
+ const request = { snapshotId: identifier('snapshotId', 256), producerFactId: identifier('producerFactId', 8192), consumerFactId: identifier('consumerFactId', 8192) };
15
+ if (request.producerFactId === request.consumerFactId)
16
+ throw new Error('Select distinct producer and consumer assertion IDs');
17
+ return request;
18
+ }
@@ -0,0 +1,7 @@
1
+ import type { ContractCompatibilityReport, ContractCompatibilityRequest, ContractCompatibilityService, SnapshotStore } from '@weftlet/core';
2
+ /** Read-only exact pair assessment over the same lossless RDF facts used by exploration and visualization. */
3
+ export declare class SnapshotContractCompatibilityService implements ContractCompatibilityService {
4
+ private readonly store;
5
+ constructor(store: Pick<SnapshotStore, 'read'>);
6
+ assess(input: ContractCompatibilityRequest): Promise<ContractCompatibilityReport>;
7
+ }
@@ -0,0 +1,67 @@
1
+ import { RdfEvidenceGraph } from './rdf.js';
2
+ import { snapshotContext } from './snapshot-context.js';
3
+ import { selectDiagnostics } from './diagnostics.js';
4
+ import { parseContractCompatibilityRequest } from './compatibility-request.js';
5
+ import { compareDeclaredConstraints } from './constraint-comparison.js';
6
+ import { compatibilityContext } from './compatibility-context.js';
7
+ import { renderContractCompatibility } from './compatibility-renderer.js';
8
+ import { coverageForFacts } from './coverage.js';
9
+ const conditions = [
10
+ 'Only the two selected numeric declarations are compared, within this saved scope; other product obligations may exist.',
11
+ 'Matching metric/unit names assume the same meaning. No unit conversion, transformations or elapsed-time model is inferred.',
12
+ 'Source approval and assertion review stay on the cited facts. This assessment supplies no policy approval or CI pass/fail.',
13
+ 'Implementation correctness, runtime delivery, consumer rejection/availability and current-diff validity are not established.',
14
+ ];
15
+ async function assessSelected(snapshot, facts, context, diagnostics) {
16
+ const base = { version: 'declared-constraint-comparison-v1', scope: 'selected-assertion-pair',
17
+ authority: facts.some(fact => fact.authority === 'draft') ? 'draft' : 'inferred',
18
+ gateEligible: false, fulfillment: 'not-evaluated', conditions };
19
+ const unknown = (...reasons) => ({ ...base, status: 'unknown', reasons });
20
+ if (facts.length !== 2)
21
+ return unknown('missing-assertion');
22
+ if (facts.some(fact => !fact.contractObligation || fact.snapshotId !== snapshot.manifest.snapshotId ||
23
+ fact.provenance.commitSha !== snapshot.manifest.commitSha))
24
+ return unknown('invalid-evidence');
25
+ // Bound structural validation to these two records. Never revalidate the complete saved graph on a read query.
26
+ if (!(await new RdfEvidenceGraph({ ...snapshot, facts }).validate()).conforms)
27
+ return unknown('invalid-evidence');
28
+ const [producer, consumer] = facts;
29
+ if (producer.object.id !== consumer.object.id)
30
+ return unknown('different-contracts');
31
+ const gaps = [];
32
+ if (context.competingFacts)
33
+ gaps.push('competing-declarations');
34
+ if (['missingIntent', 'ambiguousIntent', 'implementationGaps'].some(key => {
35
+ const section = diagnostics[key];
36
+ return section.items.length || section.omittedRelevant;
37
+ }))
38
+ gaps.push('extraction-gaps');
39
+ if (gaps.length)
40
+ return unknown(...gaps);
41
+ return { ...base, ...compareDeclaredConstraints(producer.contractObligation.declaration, consumer.contractObligation.declaration) };
42
+ }
43
+ /** Read-only exact pair assessment over the same lossless RDF facts used by exploration and visualization. */
44
+ export class SnapshotContractCompatibilityService {
45
+ store;
46
+ constructor(store) {
47
+ this.store = store;
48
+ }
49
+ async assess(input) {
50
+ const request = parseContractCompatibilityRequest(input), snapshot = await this.store.read(request.snapshotId);
51
+ if (!snapshot)
52
+ throw new Error('No graph snapshot is available for that identifier');
53
+ if (snapshot.manifest.snapshotId !== request.snapshotId)
54
+ throw new Error('Saved snapshot identity does not match the requested identifier');
55
+ const all = await new RdfEvidenceGraph(snapshot).facts();
56
+ const byId = new Map(all.map(fact => [fact.id, fact]));
57
+ if (byId.size !== all.length)
58
+ throw new Error('Duplicate fact identities prevent exact compatibility selection');
59
+ const facts = [request.producerFactId, request.consumerFactId].flatMap(id => byId.has(id) ? [byId.get(id)] : []);
60
+ const context = compatibilityContext(all, facts);
61
+ const diagnostics = selectDiagnostics(snapshot, {}, facts, [], [], new Set(context.contractNodeIds));
62
+ const assessment = await assessSelected(snapshot, facts, context, diagnostics);
63
+ const plain = { snapshot: snapshotContext(snapshot.manifest), selection: request, facts,
64
+ coverage: coverageForFacts(facts, snapshot), assessment, context, diagnostics };
65
+ return { ...plain, markdown: renderContractCompatibility(plain) };
66
+ }
67
+ }
@@ -0,0 +1,7 @@
1
+ import type { CompatibilityReason, ContractCompatibilityStatus, ObligationDeclaration } from '@weftlet/core';
2
+ export interface ConstraintComparison {
3
+ readonly status: ContractCompatibilityStatus;
4
+ readonly reasons: readonly CompatibilityReason[];
5
+ }
6
+ /** Arithmetic on declared numeric sets only. No unit conversion, policy approval or implementation inference. */
7
+ export declare function compareDeclaredConstraints(producer: ObligationDeclaration, consumer: ObligationDeclaration): ConstraintComparison;
@@ -0,0 +1,49 @@
1
+ const unknown = (...reasons) => ({ status: 'unknown', reasons });
2
+ function interval(constraint) {
3
+ if (constraint.operator === 'at-most')
4
+ return [-Infinity, constraint.value];
5
+ if (constraint.operator === 'at-least')
6
+ return [constraint.value, Infinity];
7
+ return [constraint.value, constraint.value];
8
+ }
9
+ function contextProblems(producer, consumer) {
10
+ const problems = [];
11
+ if (!producer.contract.version || !consumer.contract.version)
12
+ problems.push('unknown-version');
13
+ if (!producer.domain || !consumer.domain || !producer.stage || !consumer.stage ||
14
+ !producer.constraint || !consumer.constraint || !producer.constraint.unit || !consumer.constraint.unit)
15
+ return [...problems, 'missing-context'];
16
+ if (producer.domain !== consumer.domain)
17
+ problems.push('different-domain');
18
+ if (producer.constraint.metric !== consumer.constraint.metric)
19
+ problems.push('different-metric');
20
+ if (producer.constraint.unit !== consumer.constraint.unit)
21
+ problems.push('different-unit');
22
+ for (const { value } of [producer.constraint, consumer.constraint]) {
23
+ if (!Number.isFinite(value) || Math.abs(value) > Number.MAX_SAFE_INTEGER)
24
+ problems.push('unsupported-number');
25
+ }
26
+ return [...new Set(problems)];
27
+ }
28
+ /** Arithmetic on declared numeric sets only. No unit conversion, policy approval or implementation inference. */
29
+ export function compareDeclaredConstraints(producer, consumer) {
30
+ if (producer.role !== 'guarantee' || consumer.role !== 'requirement')
31
+ return unknown('wrong-roles');
32
+ const keys = ['scope', 'id', 'kind', 'version'];
33
+ if (keys.some(key => producer.contract[key] !== consumer.contract[key]))
34
+ return unknown('different-contracts');
35
+ const problems = contextProblems(producer, consumer);
36
+ if (problems.length)
37
+ return unknown(...problems);
38
+ if (producer.stage !== consumer.stage)
39
+ return consumer.revalidation === 'required'
40
+ ? { status: 'revalidation-required', reasons: ['different-stage', 'consumer-revalidation'] } : unknown('different-stage');
41
+ const [pl, pu] = interval(producer.constraint), [cl, cu] = interval(consumer.constraint);
42
+ if (pu < cl || cu < pl)
43
+ return { status: 'disjoint-constraints', reasons: ['disjoint'] };
44
+ if (cl <= pl && pu <= cu)
45
+ return { status: 'compatible-constraints', reasons: ['subset'] };
46
+ if (consumer.revalidation === 'required')
47
+ return { status: 'revalidation-required', reasons: ['overlap-only', 'consumer-revalidation'] };
48
+ return { status: 'insufficient-guarantee', reasons: ['overlap-only'] };
49
+ }
@@ -1,8 +1,8 @@
1
- import { type ContextPackCoverage, type ContextPackLane, type GraphFact } from '@weftlet/core';
1
+ import { type ContextPackCoverage, type ContextPackLane, type GraphFact, type GraphSnapshot } from '@weftlet/core';
2
2
  export declare const contextPackLanes: readonly ContextPackLane[];
3
3
  export declare function isDeclaredFact(fact: GraphFact): boolean;
4
4
  export declare function contextPackLaneForFact(fact: GraphFact): ContextPackLane | undefined;
5
5
  export declare function coverageDescription(coverage: ContextPackCoverage): string;
6
6
  /** Reporting only: never changes a fact's authority, identity, primary lane or stored bytes. */
7
7
  export declare function isImplementationEvidence(fact: GraphFact): boolean;
8
- export declare function coverageForFacts(facts: readonly GraphFact[]): ContextPackCoverage;
8
+ export declare function coverageForFacts(facts: readonly GraphFact[], snapshot?: GraphSnapshot): ContextPackCoverage;
@@ -1,5 +1,6 @@
1
1
  import { repositoryFileNode } from '@weftlet/core';
2
2
  import { contextPackEvidenceAuthority } from './authority.js';
3
+ import { projectCodeCoverage, codeCoverageDescription } from './code-coverage-projection.js';
3
4
  export const contextPackLanes = [
4
5
  'productIntent',
5
6
  'contractsAndInvariants',
@@ -34,6 +35,8 @@ export function contextPackLaneForFact(fact) {
34
35
  if (isValidationFact(fact)) {
35
36
  return 'validationEvidence';
36
37
  }
38
+ if (kinds.has('CodeSymbol'))
39
+ return 'implementationAndConsumers';
37
40
  if (kinds.has('Contract') ||
38
41
  kinds.has('Invariant') ||
39
42
  kinds.has('PersistenceModel') ||
@@ -51,9 +54,10 @@ export function contextPackLaneForFact(fact) {
51
54
  return 'implementationAndConsumers';
52
55
  }
53
56
  export function coverageDescription(coverage) {
54
- return coverage.scope === 'returned-page' && coverage.countsAreNonExclusive
57
+ const page = coverage.scope === 'returned-page' && coverage.countsAreNonExclusive
55
58
  ? '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
59
  : 'Coverage scope and overlap metadata are unavailable for this Context Pack; re-query saved evidence for an explicit report.';
60
+ return `${page} ${codeCoverageDescription(coverage.code)}`;
57
61
  }
58
62
  /** Reporting only: never changes a fact's authority, identity, primary lane or stored bytes. */
59
63
  export function isImplementationEvidence(fact) {
@@ -69,12 +73,12 @@ export function isImplementationEvidence(fact) {
69
73
  const nodes = [fact.subject, fact.object];
70
74
  // Explicit adapter authority also supports logical component/provider identities.
71
75
  if (fact.authority === 'implemented')
72
- return nodes.some(node => ['Component', 'Provider'].includes(node.kind));
76
+ return nodes.some(node => ['Component', 'Provider', 'CodeSymbol'].includes(node.kind));
73
77
  // Legacy deterministic adapters omitted authority. Require their cited physical file,
74
78
  // rather than treating every fact in the implementation display group as code evidence.
75
79
  return nodes.some(node => node.kind === 'Component' && node.id === file.id);
76
80
  }
77
- export function coverageForFacts(facts) {
81
+ export function coverageForFacts(facts, snapshot) {
78
82
  const counts = {
79
83
  productIntent: 0, contractsAndInvariants: 0, implementationAndConsumers: 0, validationEvidence: 0,
80
84
  };
@@ -86,6 +90,7 @@ export function coverageForFacts(facts) {
86
90
  counts.implementationAndConsumers++;
87
91
  }
88
92
  return { scope: 'returned-page', countsAreNonExclusive: true, counts,
93
+ ...(snapshot ? { code: projectCodeCoverage(snapshot).overview } : {}),
89
94
  gaps: [
90
95
  ...contextPackLanes.filter(lane => counts[lane] === 0)
91
96
  .map(lane => `No ${lane} evidence appears on this returned page.`),
@@ -1,5 +1,5 @@
1
1
  import type { ContextPack, DiagnosticKind, DiagnosticReport, GraphDiagnostic, GraphFact, GraphSnapshot, ImpactScanRequest } from "@weftlet/core";
2
2
  export declare const diagnosticKinds: readonly DiagnosticKind[];
3
3
  export declare const MAX_DIAGNOSTICS_PER_KIND = 5;
4
- export declare function selectDiagnostics(snapshot: Pick<GraphSnapshot, 'unresolved' | 'diagnostics'>, request: ImpactScanRequest, facts: readonly GraphFact[], focus: readonly string[], queryDiagnostics: readonly GraphDiagnostic[]): DiagnosticReport;
4
+ export declare function selectDiagnostics(snapshot: Pick<GraphSnapshot, 'unresolved' | 'diagnostics'>, request: ImpactScanRequest, facts: readonly GraphFact[], focus: readonly string[], queryDiagnostics: readonly GraphDiagnostic[], exactNodeIds?: ReadonlySet<string>): DiagnosticReport;
5
5
  export declare function diagnosticSummary(pack: Pick<ContextPack, 'diagnostics'>): readonly string[];
@@ -6,7 +6,13 @@ export const diagnosticKinds = [
6
6
  'drift',
7
7
  ];
8
8
  export const MAX_DIAGNOSTICS_PER_KIND = 5;
9
- function relevantDiagnostic(item, request, facts, focus) {
9
+ function overlapsReturnedEvidence(item, facts) {
10
+ return item.provenance.some(span => facts.some(fact => fact.provenance.path === span.path && fact.provenance.startLine <= span.endLine &&
11
+ fact.provenance.endLine >= span.startLine));
12
+ }
13
+ function relevantDiagnostic(item, request, facts, focus, exactNodeIds) {
14
+ if (exactNodeIds)
15
+ return item.relatedNodeIds.some(id => exactNodeIds.has(id)) || overlapsReturnedEvidence(item, facts);
10
16
  const hasSelectors = request.ownerDocument ||
11
17
  request.changedFiles?.length ||
12
18
  request.symbols?.length ||
@@ -23,19 +29,17 @@ function relevantDiagnostic(item, request, facts, focus) {
23
29
  return true;
24
30
  // A source document can contain many independent capabilities. Match the cited passage,
25
31
  // not every diagnostic from a philosophy document that contributed one selected fact.
26
- if (item.provenance.some((span) => facts.some((fact) => fact.provenance.path === span.path &&
27
- fact.provenance.startLine <= span.endLine &&
28
- fact.provenance.endLine >= span.startLine)))
32
+ if (overlapsReturnedEvidence(item, facts))
29
33
  return true;
30
34
  return (item.provenance.some((span) => span.path === request.ownerDocument) &&
31
35
  (focus.length === 0 || focus.some((term) => item.reason.toLowerCase().includes(term))));
32
36
  }
33
- export function selectDiagnostics(snapshot, request, facts, focus, queryDiagnostics) {
37
+ export function selectDiagnostics(snapshot, request, facts, focus, queryDiagnostics, exactNodeIds) {
34
38
  const report = { unclassifiedLegacyCount: snapshot.unresolved.length };
35
39
  const unique = [...new Map((snapshot.diagnostics ?? []).map((item) => [item.id, item])).values()];
36
40
  for (const kind of diagnosticKinds) {
37
41
  const candidates = unique.filter((item) => item.kind === kind);
38
- const relevant = candidates.filter((item) => relevantDiagnostic(item, request, facts, focus));
42
+ const relevant = candidates.filter((item) => relevantDiagnostic(item, request, facts, focus, exactNodeIds));
39
43
  const ordered = [
40
44
  ...queryDiagnostics.filter((item) => item.kind === kind),
41
45
  ...relevant.sort((a, b) => a.id.localeCompare(b.id)),
@@ -0,0 +1,3 @@
1
+ import type { GraphFact } from '@weftlet/core';
2
+ /** Display the same structured assertion returned by queries; unknown values are never inferred. */
3
+ export declare function factEvidenceDescription(fact: GraphFact): string | undefined;
@@ -0,0 +1,13 @@
1
+ import { codeEvidenceDescription } from './code-description.js';
2
+ /** Display the same structured assertion returned by queries; unknown values are never inferred. */
3
+ export function factEvidenceDescription(fact) {
4
+ const obligation = fact.contractObligation?.declaration;
5
+ if (!obligation)
6
+ return codeEvidenceDescription(fact);
7
+ const constraint = obligation.constraint;
8
+ return `declared ${obligation.role}: ${obligation.statement}; ${obligation.contract.kind} contract ` +
9
+ `${obligation.contract.scope}/${obligation.contract.id}; version ${obligation.contract.version ?? 'unknown'}; ` +
10
+ `domain ${obligation.domain ?? 'unknown'}; stage ${obligation.stage ?? 'unknown'}; ` +
11
+ (constraint ? `${constraint.metric} ${constraint.operator} ${constraint.value}; unit ${constraint.unit ?? 'unknown'}; ` : 'constraint unknown; ') +
12
+ `consumer revalidation ${obligation.revalidation ?? 'unknown'}; fulfillment, delivery and compatibility not evaluated`;
13
+ }
@@ -1,7 +1,7 @@
1
1
  import { sha256, stableJson } from '@weftlet/core';
2
2
  const publicKinds = new Set([
3
3
  'Project', 'Document', 'Capability', 'Workflow', 'Component', 'Contract', 'Evidence',
4
- 'Invariant', 'Snapshot', 'Provider', 'PersistenceModel', 'Scope',
4
+ 'Invariant', 'Snapshot', 'Provider', 'PersistenceModel', 'Scope', 'CodeSymbol',
5
5
  ]);
6
6
  /** Find the single-kind constraint's conflicts without constructing RDF or changing any fact. */
7
7
  export function inspectGraphIdentities(facts, options) {
@@ -0,0 +1,2 @@
1
+ import type { GraphNeighborhood } from '@weftlet/core';
2
+ export declare function renderGraphNeighborhood(result: Omit<GraphNeighborhood, 'markdown'>): string;
@@ -0,0 +1,38 @@
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 { documentSelectionDescription } from './document-role-description.js';
6
+ import { diagnosticSummary } from './diagnostics.js';
7
+ const quoted = (value) => JSON.stringify(value).replace(/[\\`*_{}\[\]()<>!|]/g, '\\$&');
8
+ export function renderGraphNeighborhood(result) {
9
+ const { snapshot, focus, selection, frontier, evidenceState } = result;
10
+ return [
11
+ '# Exact graph neighborhood',
12
+ `Snapshot: ${quoted(snapshot.snapshotId)} (${snapshot.commitSha})`,
13
+ `Configuration: ${snapshot.configurationDigest ?? 'legacy/unknown'}; scope: ${snapshot.scope?.map(quoted).join(', ') ?? 'legacy/unknown'}`,
14
+ ...[documentSelectionDescription(snapshot.documentSelection)].filter(Boolean),
15
+ `Node: ${quoted(focus.nodeId)}; ${focus.status}`,
16
+ `Direction: ${selection.direction}; predicates: ${selection.predicates.join(', ') || 'all'}`,
17
+ `Incident assertions: ${selection.incidentFacts}; matching: ${selection.matchingFacts}; filtered out: ${selection.filteredFacts}`,
18
+ `Page: ${selection.returnedFacts} assertions at offset ${selection.offset}; remaining: ${selection.remainingFacts}; truncated: ${selection.truncated}`,
19
+ ...(selection.continuation ? [`Continuation: ${selection.continuation}`] : []),
20
+ ...(evidenceState ? [
21
+ `Stale relative to checkout: ${evidenceState.stale}; configuration matches: ${evidenceState.configurationMatches}; extraction compatible: ${evidenceState.extractionCompatible ?? 'unknown'}`,
22
+ `Excluded working changes: ${evidenceState.excludedWorkingChanges.map(quoted).join(', ') || '(none)'}`,
23
+ ...(evidenceState.overlayFreshness ? [`Overlay freshness: ${evidenceState.overlayFreshness}`] : []),
24
+ ] : []),
25
+ 'This is one-hop saved evidence. It does not validate the current diff or establish complete repository coverage.',
26
+ coverageDescription(result.coverage),
27
+ ...result.coverage.gaps.map(gap => `GAP: ${gap}`),
28
+ '', '## Cited assertions',
29
+ ...result.facts.map(fact => `- [${contextPackEvidenceAuthority(fact)}] ${quoted(fact.subject.id)} —${fact.predicate}→ ${quoted(fact.object.id)} ` +
30
+ `(${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
+ '', '## Scoped diagnostics',
32
+ ...diagnosticSummary(result).map(item => `- ${quoted(item)}`),
33
+ '', '## Further exploration',
34
+ `Selected neighbors: ${frontier.totalNeighbors}; not yet returned across these pages: ${frontier.remainingNeighbors}.`,
35
+ ...frontier.neighbors.map(node => `- ${quoted(node.nodeId)}: ${node.otherIncidentFacts} incident assertions outside this focus neighborhood; use graph.neighbors with the same snapshot to inspect them.`),
36
+ 'Counts concern the saved scope. Absence or an exhausted page is not proof that a product dependency does not exist.',
37
+ ].join('\n');
38
+ }
@@ -0,0 +1,9 @@
1
+ /** Shared validation/defaults for public API, CLI and MCP. Never interpret IDs as text search. */
2
+ export declare function parseGraphNeighborsRequest(value: unknown): {
3
+ continuation?: string | undefined;
4
+ snapshotId: string;
5
+ nodeId: 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
+ maxFacts: number;
9
+ };
@@ -0,0 +1,37 @@
1
+ import { graphPredicates } from '@weftlet/core';
2
+ const fields = new Set(['snapshotId', 'nodeId', 'direction', 'predicates', 'maxFacts', 'continuation']);
3
+ function identifier(value, name, maximum) {
4
+ if (typeof value !== 'string' || !value.trim() || value.length > maximum) {
5
+ throw new Error(`${name} must be a non-empty string of at most ${maximum} characters`);
6
+ }
7
+ return value;
8
+ }
9
+ /** Shared validation/defaults for public API, CLI and MCP. Never interpret IDs as text search. */
10
+ export function parseGraphNeighborsRequest(value) {
11
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
12
+ throw new Error('graph.neighbors arguments must be an object');
13
+ }
14
+ const record = value;
15
+ const unexpected = Object.keys(record).filter(field => !fields.has(field));
16
+ if (unexpected.length)
17
+ throw new Error(`Unknown graph.neighbors fields: ${unexpected.join(', ')}`);
18
+ const snapshotId = identifier(record.snapshotId, 'snapshotId', 256);
19
+ const nodeId = identifier(record.nodeId, 'nodeId', 8192);
20
+ const direction = record.direction === undefined ? 'both' : record.direction;
21
+ if (direction !== 'incoming' && direction !== 'outgoing' && direction !== 'both') {
22
+ throw new Error('direction must be incoming, outgoing or both');
23
+ }
24
+ const predicates = record.predicates === undefined ? [] : record.predicates;
25
+ if (!Array.isArray(predicates) || predicates.length > graphPredicates.length ||
26
+ predicates.some(item => !graphPredicates.includes(item))) {
27
+ throw new Error('predicates must be an array of supported graph predicates');
28
+ }
29
+ const maxFacts = record.maxFacts === undefined ? 60 : record.maxFacts;
30
+ if (!Number.isInteger(maxFacts) || Number(maxFacts) < 1 || Number(maxFacts) > 200) {
31
+ throw new Error('maxFacts must be an integer between 1 and 200');
32
+ }
33
+ const continuation = record.continuation === undefined ? undefined
34
+ : identifier(record.continuation, 'continuation', 2048);
35
+ return { snapshotId, nodeId, direction, predicates: [...new Set(predicates)].sort(),
36
+ maxFacts: Number(maxFacts), ...(continuation === undefined ? {} : { continuation }) };
37
+ }
@@ -0,0 +1,8 @@
1
+ import type { GraphExplorationService, GraphNeighborhood, GraphNeighborsRequest, SnapshotStore } from '@weftlet/core';
2
+ export declare const GRAPH_NEIGHBORHOOD_VERSION = "exact-neighborhood-v1";
3
+ /** One-hop, exact-ID exploration over the same lossless RDF projection as impact.scan. */
4
+ export declare class SnapshotGraphExplorationService implements GraphExplorationService {
5
+ private readonly store;
6
+ constructor(store: Pick<SnapshotStore, 'read'>);
7
+ neighbors(input: GraphNeighborsRequest): Promise<GraphNeighborhood>;
8
+ }
@@ -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
+ }