@sdd-method/author-cli 0.0.0-stage → 0.1.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/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +6 -2
- package/dist/author-bin.js +8 -0
- package/dist/author-program.js +99 -0
- package/dist/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +37 -0
- package/dist/lib/api-surface/__fixtures__/serverless-repo/serverless/serverless.yml +18 -0
- package/dist/lib/api-surface/analyzers/library.js +95 -0
- package/dist/lib/api-surface/analyzers/serverless.js +336 -0
- package/dist/lib/api-surface/analyzers/spring.js +237 -0
- package/dist/lib/api-surface/analyzers/terraform.js +109 -0
- package/dist/lib/api-surface/detect.js +25 -0
- package/dist/lib/api-surface/emit.js +109 -0
- package/dist/lib/api-surface/file-set.js +43 -0
- package/dist/lib/api-surface/index.js +134 -0
- package/dist/lib/api-surface/types.js +17 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +132 -0
- package/dist/lib/briefing/variables.js +92 -0
- package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
- package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
- package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
- package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
- package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
- package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
- package/dist/lib/catalogue/archetype/resolver.js +103 -0
- package/dist/lib/catalogue/archetype/wireframe.js +418 -0
- package/dist/lib/catalogue/baseline-check.js +151 -0
- package/dist/lib/catalogue/build.js +1502 -0
- package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
- package/dist/lib/catalogue/builders/adrs.js +222 -0
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
- package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
- package/dist/lib/catalogue/builders/c4-components.js +229 -0
- package/dist/lib/catalogue/builders/c4-containers.js +240 -0
- package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
- package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
- package/dist/lib/catalogue/builders/c4-systems.js +98 -0
- package/dist/lib/catalogue/builders/capabilities.js +488 -0
- package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
- package/dist/lib/catalogue/builders/capability-edges.js +256 -0
- package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
- package/dist/lib/catalogue/builders/capability-spec.js +348 -0
- package/dist/lib/catalogue/builders/container-dependency.js +183 -0
- package/dist/lib/catalogue/builders/contracts.js +402 -0
- package/dist/lib/catalogue/builders/data-items.js +344 -0
- package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
- package/dist/lib/catalogue/builders/domain-models.js +175 -0
- package/dist/lib/catalogue/builders/domains.js +119 -0
- package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
- package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
- package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
- package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
- package/dist/lib/catalogue/builders/entity-slug.js +47 -0
- package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
- package/dist/lib/catalogue/builders/event-edges.js +327 -0
- package/dist/lib/catalogue/builders/events.js +168 -0
- package/dist/lib/catalogue/builders/evidence-class.js +43 -0
- package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
- package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
- package/dist/lib/catalogue/builders/feature-contract.js +77 -0
- package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
- package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
- package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
- package/dist/lib/catalogue/builders/feature-groups.js +63 -0
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
- package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
- package/dist/lib/catalogue/builders/features.js +300 -0
- package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
- package/dist/lib/catalogue/builders/glossary.js +204 -0
- package/dist/lib/catalogue/builders/implementations.js +214 -0
- package/dist/lib/catalogue/builders/index.js +64 -0
- package/dist/lib/catalogue/builders/integration-components.js +344 -0
- package/dist/lib/catalogue/builders/integrations.js +181 -0
- package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
- package/dist/lib/catalogue/builders/persona-container.js +133 -0
- package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
- package/dist/lib/catalogue/builders/personas.js +152 -0
- package/dist/lib/catalogue/builders/product-edges.js +106 -0
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
- package/dist/lib/catalogue/builders/products.js +162 -0
- package/dist/lib/catalogue/builders/quality-scores.js +635 -0
- package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
- package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
- package/dist/lib/catalogue/builders/repo-edges.js +340 -0
- package/dist/lib/catalogue/builders/repositories.js +458 -0
- package/dist/lib/catalogue/builders/scaffolds.js +197 -0
- package/dist/lib/catalogue/builders/screen-tree.js +222 -0
- package/dist/lib/catalogue/builders/screens.js +396 -0
- package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
- package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
- package/dist/lib/catalogue/builders/service-contract.js +74 -0
- package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
- package/dist/lib/catalogue/builders/services.js +232 -0
- package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
- package/dist/lib/catalogue/builders/user-journeys.js +253 -0
- package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
- package/dist/lib/catalogue/cli-version.js +10 -0
- package/dist/lib/catalogue/contract-id.js +49 -0
- package/dist/lib/catalogue/coverage-predicates.js +603 -0
- package/dist/lib/catalogue/coverage-render.js +552 -0
- package/dist/lib/catalogue/coverage-run.js +115 -0
- package/dist/lib/catalogue/csv-reader.js +97 -0
- package/dist/lib/catalogue/csv-writer.js +96 -0
- package/dist/lib/catalogue/data-plane-coherence.js +77 -0
- package/dist/lib/catalogue/ddl-generator.js +233 -0
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/engine-validator.js +239 -0
- package/dist/lib/catalogue/ext-id.js +76 -0
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
- package/dist/lib/catalogue/finding-identity.js +183 -0
- package/dist/lib/catalogue/findings.js +46 -0
- package/dist/lib/catalogue/fk-validator.js +136 -0
- package/dist/lib/catalogue/freshness.js +70 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -0
- package/dist/lib/catalogue/impact-run.js +228 -0
- package/dist/lib/catalogue/manifest-loader.js +435 -0
- package/dist/lib/catalogue/platform-scope.js +7 -0
- package/dist/lib/catalogue/population-checks.js +87 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/reverse-product-quality.js +99 -0
- package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
- package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
- package/dist/lib/catalogue/scaffold-delta.js +261 -0
- package/dist/lib/catalogue/schema-compat.js +52 -0
- package/dist/lib/catalogue/schema-loader.js +87 -0
- package/dist/lib/catalogue/scores-coverage.js +573 -0
- package/dist/lib/catalogue/screen-validation-loader.js +89 -0
- package/dist/lib/catalogue/service-architecture-parser.js +193 -0
- package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
- package/dist/lib/catalogue/staleness-verdicts.js +345 -0
- package/dist/lib/catalogue/traceability-loader.js +350 -0
- package/dist/lib/catalogue/validate.js +200 -0
- package/dist/lib/catalogue/version.js +18 -0
- package/dist/lib/conformance/binding.js +70 -0
- package/dist/lib/conformance/finding.js +69 -0
- package/dist/lib/conformance/index.js +578 -0
- package/dist/lib/context-pull/index.js +191 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
- package/dist/lib/contracts-availability/index.js +267 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +437 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/schema.js +121 -0
- package/dist/lib/pack/seed.js +182 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/package-version.js +92 -0
- package/dist/lib/plan/index.js +69 -0
- package/dist/lib/plan/read-sdd.js +102 -0
- package/dist/lib/plan/render-template.js +84 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scaffold/domain.js +234 -0
- package/dist/lib/scaffold/registry.js +136 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/sdd-index/repo-config.js +245 -0
- package/dist/lib/sdd-reader.js +91 -0
- package/dist/lib/snapshot/advance.js +454 -0
- package/dist/lib/snapshot/diff.js +190 -0
- package/dist/lib/snapshot/fold.js +208 -0
- package/dist/lib/snapshot/land.js +178 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/snapshot/targets.js +169 -0
- package/dist/lib/snapshot/verify.js +91 -0
- package/dist/lib/sync/briefing-merge.js +106 -0
- package/dist/lib/sync/bundle-cache.js +109 -0
- package/dist/lib/sync/yaml-block-merge.js +120 -0
- package/dist/lib/validate/aggregate.js +51 -0
- package/dist/lib/validate/bundle-coherence.js +74 -0
- package/dist/lib/validate/dispatch.js +243 -0
- package/dist/lib/validate/index.js +110 -0
- package/dist/lib/validate/plan.js +54 -0
- package/dist/lib/validate/runner.js +85 -0
- package/dist/lib/validate/yaml-parse.js +112 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/api-surface.js +71 -0
- package/dist/verbs/catalogue-check.js +28 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/conformance.js +34 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/contracts.js +34 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/plan.js +42 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/scaffold.js +83 -0
- package/dist/verbs/snapshot.js +134 -0
- package/dist/verbs/validate.js +48 -0
- package/dist/verify-program.js +122 -0
- package/dist/verify-version.json +4 -0
- package/package.json +32 -4
- package/scripts/validate-app-dependencies.sh +664 -0
- package/scripts/validate-app-docs.sh +199 -0
- package/scripts/validate-integration-docs.sh +505 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sdd-cli version constant, used in error messages from the catalogue
|
|
3
|
+
* builder + aggregator + validate subcommands.
|
|
4
|
+
*
|
|
5
|
+
* Read from package.json via getSddCliVersion() (the ENGINE version, also in a persona package) to guarantee the
|
|
6
|
+
* constant cannot drift from the published version.
|
|
7
|
+
*/
|
|
8
|
+
import { getSddCliVersion } from "../package-version.js";
|
|
9
|
+
export const CLI_VERSION = getSddCliVersion();
|
|
10
|
+
//# sourceMappingURL=cli-version.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Contract row identity (schema 3.0.0) and the legacy-id migration map —
|
|
2
|
+
// M-ADR 0203 §4.4, the cut's acid test.
|
|
3
|
+
//
|
|
4
|
+
// From 3.0.0 a contract id carries its KIND component: `{domain}-{stem}-{kind}`
|
|
5
|
+
// (kind ∈ rest | graphql | rpc | event | authz). The kindless 2.x form made one
|
|
6
|
+
// measured collision class unrepresentable: a service published as two contract
|
|
7
|
+
// families (`calendar-service.yaml` + `calendar_service.proto`) minted one id,
|
|
8
|
+
// costing a live estate one row and two edges permanently, and mislabelling
|
|
9
|
+
// legitimate shared-contract citation as divergence.
|
|
10
|
+
//
|
|
11
|
+
// The map is mechanical in both directions: new id = `${old id}-${kind}`, from
|
|
12
|
+
// two columns every committed catalogue carries (`id`, `kind`). It ships via
|
|
13
|
+
// `sdd-cli catalogue migration-map`; the alias-file re-key (M-ADR 0147 targets
|
|
14
|
+
// are authored copies of tool-minted ids — regenerable, not drift, the same
|
|
15
|
+
// rule as external_system_exemption) consults the in-build form of it.
|
|
16
|
+
/** The 3.0.0 contract id: the kindless slug with its kind appended. */
|
|
17
|
+
export function contractIdWithKind(kindlessSlug, kind) {
|
|
18
|
+
return kindlessSlug === "" ? "" : `${kindlessSlug}-${kind}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* legacy (2.x, kindless) id → the 3.0.0 kind-suffixed ids it maps to, from
|
|
22
|
+
* built rows. More than one target means the id was a measured collision
|
|
23
|
+
* under 2.x (two families sharing one kindless slug) — the consumer decides
|
|
24
|
+
* which family is meant, loudly, never by guess.
|
|
25
|
+
*/
|
|
26
|
+
export function buildLegacyContractIdMap(rows) {
|
|
27
|
+
const map = new Map();
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
const id = String(row["id"] ?? "");
|
|
30
|
+
const kind = String(row["kind"] ?? "");
|
|
31
|
+
if (id === "" || kind === "" || !id.endsWith(`-${kind}`))
|
|
32
|
+
continue;
|
|
33
|
+
const legacy = id.slice(0, -(kind.length + 1));
|
|
34
|
+
if (legacy === "")
|
|
35
|
+
continue;
|
|
36
|
+
const ids = map.get(legacy);
|
|
37
|
+
if (ids) {
|
|
38
|
+
if (!ids.includes(id))
|
|
39
|
+
ids.push(id);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
map.set(legacy, [id]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const ids of map.values())
|
|
46
|
+
ids.sort((a, b) => a.localeCompare(b));
|
|
47
|
+
return map;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=contract-id.js.map
|
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage predicates as SQL — M-ADR 0205 §4.4 (engine-evolution Phase A3).
|
|
3
|
+
*
|
|
4
|
+
* Declarative re-expressions of the participation-constraint predicates —
|
|
5
|
+
* the four catalogue-classics:
|
|
6
|
+
*
|
|
7
|
+
* - repo_capability (M-ADR 0188): every catalogued repository resolves
|
|
8
|
+
* to a capability or declares an exemption; orphans classified by
|
|
9
|
+
* whether the capability_service ⋈ services.primary_repo_id derivation
|
|
10
|
+
* COULD have fired (shape a / claimed-but-unresolved / shape b).
|
|
11
|
+
* - service_capability (M-ADR 0133 §4.2b): every catalogued service is
|
|
12
|
+
* claimed or exempt; tier is explicit and asymmetric per M-ADR 0196
|
|
13
|
+
* §4.3 (`orphaned` is aggregate-only; member tier reports
|
|
14
|
+
* `unresolved-locally`, cause-agnostic); the Path-X leverage axis is
|
|
15
|
+
* reported separately and only when repo_capability.csv exists.
|
|
16
|
+
* - event_coverage (WS3): every catalogued event is attributed to a
|
|
17
|
+
* runtime — service_event edge or resolving publisher column — with
|
|
18
|
+
* the contract-twin alternative route, the column-vs-edge disagreement
|
|
19
|
+
* controls, and the chain-readout/consumer-enumerability metric lines
|
|
20
|
+
* that are never verdicts.
|
|
21
|
+
* - contract_coverage (WS2): every catalogued contract is reachable —
|
|
22
|
+
* capability_contract or service_contract — with the three computed
|
|
23
|
+
* exclusions (deprecated / provider-surface / not-an-interface, the
|
|
24
|
+
* last a positive READ of the spec file), the covered-as-event
|
|
25
|
+
* alternative route, and the M-ADR 0202 availability-manifest info
|
|
26
|
+
* surface. File and YAML ingestion stay host-side; set logic is SQL.
|
|
27
|
+
*
|
|
28
|
+
* The shell scripts in sdd-method's orchestration/scripts/ remain the
|
|
29
|
+
* REFERENCE implementations until A4 cutover: any semantic change lands
|
|
30
|
+
* there first and here second, and the dual-run driver compares the two
|
|
31
|
+
* as structured findings — (kind, subject, classification, leverage) plus
|
|
32
|
+
* every metric integer — never as prose.
|
|
33
|
+
*
|
|
34
|
+
* The relations live in SQL; the classification branching mirrors the
|
|
35
|
+
* reference implementations statement-for-statement, including their
|
|
36
|
+
* duplicate-row semantics (a duplicated id is counted and reported per
|
|
37
|
+
* ROW, never deduplicated away).
|
|
38
|
+
*/
|
|
39
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
40
|
+
import { join } from "node:path";
|
|
41
|
+
import { parse as parseYaml } from "yaml";
|
|
42
|
+
const REPO_FLAGS_SQL = `
|
|
43
|
+
SELECT
|
|
44
|
+
r."id" AS repo_id,
|
|
45
|
+
EXISTS (SELECT 1 FROM "repo_capability" c
|
|
46
|
+
WHERE c."repo_id" = r."id") AS has_cap,
|
|
47
|
+
EXISTS (SELECT 1 FROM "repo_capability_exemption" e
|
|
48
|
+
WHERE e."repo_id" = r."id") AS has_exempt,
|
|
49
|
+
EXISTS (SELECT 1 FROM "services" s
|
|
50
|
+
WHERE s."primary_repo_id" = r."id"
|
|
51
|
+
AND s."id" IS NOT NULL AND s."id" <> '') AS has_svc,
|
|
52
|
+
EXISTS (SELECT 1 FROM "services" s
|
|
53
|
+
WHERE s."primary_repo_id" = r."id"
|
|
54
|
+
AND s."id" IS NOT NULL AND s."id" <> ''
|
|
55
|
+
AND NOT EXISTS (SELECT 1 FROM "capability_service" cs
|
|
56
|
+
WHERE cs."service_id" = s."id")) AS has_unclaimed_svc,
|
|
57
|
+
(SELECT e2."reason" FROM "repo_capability_exemption" e2
|
|
58
|
+
WHERE e2."repo_id" = r."id"
|
|
59
|
+
AND e2.rowid = (SELECT MAX(e3.rowid) FROM "repo_capability_exemption" e3
|
|
60
|
+
WHERE e3."repo_id" = r."id")) AS exempt_reason,
|
|
61
|
+
(SELECT string_agg(s."id", chr(31) ORDER BY s."id") FROM "services" s
|
|
62
|
+
WHERE s."primary_repo_id" = r."id"
|
|
63
|
+
AND s."id" IS NOT NULL AND s."id" <> ''
|
|
64
|
+
AND NOT EXISTS (SELECT 1 FROM "capability_service" cs
|
|
65
|
+
WHERE cs."service_id" = s."id")) AS unclaimed_ids
|
|
66
|
+
FROM "repositories" r
|
|
67
|
+
WHERE r."id" IS NOT NULL AND r."id" <> ''
|
|
68
|
+
ORDER BY repo_id;`;
|
|
69
|
+
export async function runRepoCapabilityCoverage(session) {
|
|
70
|
+
const reader = await session.conn.runAndReadAll(REPO_FLAGS_SQL);
|
|
71
|
+
const rows = reader.getRows().map((r) => ({
|
|
72
|
+
repoId: String(r[0]),
|
|
73
|
+
hasCap: r[1] === true,
|
|
74
|
+
hasExempt: r[2] === true,
|
|
75
|
+
hasSvc: r[3] === true,
|
|
76
|
+
hasUnclaimedSvc: r[4] === true,
|
|
77
|
+
exemptReason: r[5] === null || r[5] === undefined ? "" : String(r[5]),
|
|
78
|
+
unclaimedIds: r[6] === null || r[6] === undefined
|
|
79
|
+
? []
|
|
80
|
+
: String(r[6]).split("\x1f"),
|
|
81
|
+
}));
|
|
82
|
+
const findings = [];
|
|
83
|
+
let byCapability = 0;
|
|
84
|
+
let byExemption = 0;
|
|
85
|
+
let conflicting = 0;
|
|
86
|
+
let unresolved = 0;
|
|
87
|
+
let shapeA = 0;
|
|
88
|
+
let shapeB = 0;
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
if (row.hasCap && row.hasExempt) {
|
|
91
|
+
conflicting++;
|
|
92
|
+
findings.push({
|
|
93
|
+
predicate: "repo_capability",
|
|
94
|
+
kind: "divergence",
|
|
95
|
+
subjectId: row.repoId,
|
|
96
|
+
classification: "conflict",
|
|
97
|
+
exemptionReason: row.exemptReason,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (row.hasCap) {
|
|
101
|
+
byCapability++;
|
|
102
|
+
}
|
|
103
|
+
else if (row.hasExempt) {
|
|
104
|
+
byExemption++;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
unresolved++;
|
|
108
|
+
if (row.hasUnclaimedSvc) {
|
|
109
|
+
shapeA++;
|
|
110
|
+
findings.push({
|
|
111
|
+
predicate: "repo_capability",
|
|
112
|
+
kind: "orphan",
|
|
113
|
+
subjectId: row.repoId,
|
|
114
|
+
classification: "shape_a",
|
|
115
|
+
unclaimedServices: row.unclaimedIds.slice(0, 3),
|
|
116
|
+
unclaimedCount: row.unclaimedIds.length,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else if (row.hasSvc) {
|
|
120
|
+
findings.push({
|
|
121
|
+
predicate: "repo_capability",
|
|
122
|
+
kind: "orphan",
|
|
123
|
+
subjectId: row.repoId,
|
|
124
|
+
classification: "claimed_unresolved",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
shapeB++;
|
|
129
|
+
findings.push({
|
|
130
|
+
predicate: "repo_capability",
|
|
131
|
+
kind: "orphan",
|
|
132
|
+
subjectId: row.repoId,
|
|
133
|
+
classification: "shape_b",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
findings,
|
|
140
|
+
metrics: {
|
|
141
|
+
total: rows.length,
|
|
142
|
+
byCapability,
|
|
143
|
+
byExemption,
|
|
144
|
+
conflicting,
|
|
145
|
+
unresolved,
|
|
146
|
+
shapeA,
|
|
147
|
+
shapeB,
|
|
148
|
+
other: unresolved - shapeA - shapeB,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** Statement-for-statement mirror of the reference classify(). */
|
|
153
|
+
function classify(hasCap, hasExempt) {
|
|
154
|
+
if (hasCap && hasExempt)
|
|
155
|
+
return "conflict";
|
|
156
|
+
if (hasCap)
|
|
157
|
+
return "claimed";
|
|
158
|
+
if (hasExempt)
|
|
159
|
+
return "exempt";
|
|
160
|
+
return "unresolved";
|
|
161
|
+
}
|
|
162
|
+
const SERVICE_FLAGS_SQL = `
|
|
163
|
+
SELECT
|
|
164
|
+
s."id" AS service_id,
|
|
165
|
+
COALESCE(s."primary_repo_id", '') AS primary_repo,
|
|
166
|
+
EXISTS (SELECT 1 FROM "capability_service" cs
|
|
167
|
+
WHERE cs."service_id" = s."id") AS has_cap,
|
|
168
|
+
EXISTS (SELECT 1 FROM "service_capability_exemption" e
|
|
169
|
+
WHERE e."service_id" = s."id") AS has_exempt,
|
|
170
|
+
EXISTS (SELECT 1 FROM "repo_capability" rc
|
|
171
|
+
WHERE rc."repo_id" = s."primary_repo_id"
|
|
172
|
+
AND rc."repo_id" IS NOT NULL AND rc."repo_id" <> '') AS repo_resolves,
|
|
173
|
+
(SELECT e2."reason" FROM "service_capability_exemption" e2
|
|
174
|
+
WHERE e2."service_id" = s."id"
|
|
175
|
+
AND e2.rowid = (SELECT MAX(e3.rowid) FROM "service_capability_exemption" e3
|
|
176
|
+
WHERE e3."service_id" = s."id")) AS exempt_reason
|
|
177
|
+
FROM "services" s
|
|
178
|
+
WHERE s."id" IS NOT NULL AND s."id" <> ''
|
|
179
|
+
ORDER BY service_id, primary_repo;`;
|
|
180
|
+
export async function runServiceCapabilityCoverage(session, options) {
|
|
181
|
+
// Known-bad probe, carried over from the reference implementation
|
|
182
|
+
// (WS11's third requirement): a synthetic claimed service must classify
|
|
183
|
+
// as claimed BEFORE any real data is reported — a run whose
|
|
184
|
+
// classification never executed must not be able to report a healthy
|
|
185
|
+
// estate.
|
|
186
|
+
if (classify(true, false) !== "claimed") {
|
|
187
|
+
throw new Error("coverage-predicates: known-bad probe failed — a synthetic claimed service did not classify as claimed; refusing to report");
|
|
188
|
+
}
|
|
189
|
+
const reader = await session.conn.runAndReadAll(SERVICE_FLAGS_SQL);
|
|
190
|
+
const findings = [];
|
|
191
|
+
let claimed = 0;
|
|
192
|
+
let exempt = 0;
|
|
193
|
+
let conflicting = 0;
|
|
194
|
+
let unresolved = 0;
|
|
195
|
+
let leverageYes = 0;
|
|
196
|
+
let leverageNo = 0;
|
|
197
|
+
let leverageNa = 0;
|
|
198
|
+
let total = 0;
|
|
199
|
+
for (const r of reader.getRows()) {
|
|
200
|
+
total++;
|
|
201
|
+
const serviceId = String(r[0]);
|
|
202
|
+
const primaryRepo = String(r[1] ?? "");
|
|
203
|
+
const verdict = classify(r[2] === true, r[3] === true);
|
|
204
|
+
if (verdict === "conflict") {
|
|
205
|
+
conflicting++;
|
|
206
|
+
findings.push({
|
|
207
|
+
predicate: "service_capability",
|
|
208
|
+
kind: "divergence",
|
|
209
|
+
subjectId: serviceId,
|
|
210
|
+
classification: "conflict",
|
|
211
|
+
exemptionReason: r[5] === null || r[5] === undefined ? "" : String(r[5]),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
else if (verdict === "claimed") {
|
|
215
|
+
claimed++;
|
|
216
|
+
}
|
|
217
|
+
else if (verdict === "exempt") {
|
|
218
|
+
exempt++;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
unresolved++;
|
|
222
|
+
let leverage;
|
|
223
|
+
if (options.repoCapabilityAvailable) {
|
|
224
|
+
if (primaryRepo === "") {
|
|
225
|
+
leverageNa++;
|
|
226
|
+
leverage = "na";
|
|
227
|
+
}
|
|
228
|
+
else if (r[4] !== true) {
|
|
229
|
+
leverageYes++;
|
|
230
|
+
leverage = "yes";
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
leverageNo++;
|
|
234
|
+
leverage = "no";
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
findings.push({
|
|
238
|
+
predicate: "service_capability",
|
|
239
|
+
kind: options.tier === "aggregate" ? "orphan" : "coverage",
|
|
240
|
+
subjectId: serviceId,
|
|
241
|
+
classification: options.tier === "aggregate" ? "orphaned" : "unresolved-locally",
|
|
242
|
+
...(leverage !== undefined ? { leverage } : {}),
|
|
243
|
+
primaryRepo,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
findings,
|
|
249
|
+
metrics: {
|
|
250
|
+
total,
|
|
251
|
+
claimed,
|
|
252
|
+
exempt,
|
|
253
|
+
conflicting,
|
|
254
|
+
unresolved,
|
|
255
|
+
...(options.repoCapabilityAvailable && unresolved > 0
|
|
256
|
+
? { leverage: { yes: leverageYes, no: leverageNo, na: leverageNa } }
|
|
257
|
+
: {}),
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/** Statement-for-statement mirror of the reference classify(). */
|
|
262
|
+
function classifyEvent(hasEdge, pub, pubResolves, twinCovered) {
|
|
263
|
+
if (hasEdge)
|
|
264
|
+
return "covered";
|
|
265
|
+
if (pub !== "" && pubResolves)
|
|
266
|
+
return "covered";
|
|
267
|
+
if (twinCovered)
|
|
268
|
+
return "covered-as-contract";
|
|
269
|
+
return "unresolved";
|
|
270
|
+
}
|
|
271
|
+
const EVENT_FLAGS_SQL = `
|
|
272
|
+
WITH ev AS (
|
|
273
|
+
SELECT e."id" AS id,
|
|
274
|
+
TRIM(COALESCE(e."publisher_service_id", '')) AS pub,
|
|
275
|
+
REPLACE(COALESCE(e."schema_path", ''), chr(92), '/') AS sp
|
|
276
|
+
FROM "events" e
|
|
277
|
+
WHERE e."id" IS NOT NULL AND e."id" <> ''
|
|
278
|
+
AND e.rowid = (SELECT MAX(e2.rowid) FROM "events" e2 WHERE e2."id" = e."id")
|
|
279
|
+
),
|
|
280
|
+
covered_twin_bases AS (
|
|
281
|
+
SELECT DISTINCT regexp_extract(REPLACE(c."spec_path", chr(92), '/'), '[^/]*$') AS base
|
|
282
|
+
FROM "contracts" c
|
|
283
|
+
WHERE COALESCE(c."kind", '') = 'event'
|
|
284
|
+
AND COALESCE(c."spec_path", '') <> ''
|
|
285
|
+
AND (EXISTS (SELECT 1 FROM "capability_contract" cc WHERE cc."contract_id" = c."id")
|
|
286
|
+
OR EXISTS (SELECT 1 FROM "service_contract" sc WHERE sc."contract_id" = c."id"))
|
|
287
|
+
)
|
|
288
|
+
SELECT ev.id, ev.pub,
|
|
289
|
+
EXISTS (SELECT 1 FROM "service_event" se WHERE se."event_id" = ev.id) AS has_edge,
|
|
290
|
+
EXISTS (SELECT 1 FROM "service_event" se WHERE se."event_id" = ev.id
|
|
291
|
+
AND COALESCE(se."role", '') IN ('publishes', 'produces')) AS has_pub_edge,
|
|
292
|
+
EXISTS (SELECT 1 FROM "services" s WHERE s."id" = ev.pub) AS pub_resolves,
|
|
293
|
+
(ev.sp <> '' AND EXISTS (SELECT 1 FROM covered_twin_bases b
|
|
294
|
+
WHERE b.base = regexp_extract(ev.sp, '[^/]*$'))) AS twin_covered,
|
|
295
|
+
(ev.pub <> '' AND EXISTS (SELECT 1 FROM "capability_service" cs
|
|
296
|
+
WHERE cs."service_id" = ev.pub)) AS pub_cap_claimed,
|
|
297
|
+
EXISTS (SELECT 1 FROM "service_event" se
|
|
298
|
+
JOIN "capability_service" cs ON cs."service_id" = se."service_id"
|
|
299
|
+
WHERE se."event_id" = ev.id
|
|
300
|
+
AND COALESCE(se."service_id", '') <> '') AS edge_cap_claimed,
|
|
301
|
+
EXISTS (SELECT 1 FROM "service_event" se WHERE se."event_id" = ev.id
|
|
302
|
+
AND COALESCE(se."role", '') = 'consumes') AS has_consume_edge
|
|
303
|
+
FROM ev
|
|
304
|
+
ORDER BY ev.id;`;
|
|
305
|
+
export async function runEventCoverage(session, options) {
|
|
306
|
+
// Classifier probe (M-ADR 0199 §4.6): known-good and known-bad synthetic
|
|
307
|
+
// rows through the real classifier, both directions, before any real data.
|
|
308
|
+
if (classifyEvent(false, "svc", true, false) !== "covered" ||
|
|
309
|
+
classifyEvent(false, "", false, false) !== "unresolved") {
|
|
310
|
+
throw new Error("coverage-predicates: event classifier probe misclassified a synthetic row — no verdict is trustworthy");
|
|
311
|
+
}
|
|
312
|
+
const reader = await session.conn.runAndReadAll(EVENT_FLAGS_SQL);
|
|
313
|
+
const rows = reader.getRows();
|
|
314
|
+
if (rows.length === 0) {
|
|
315
|
+
return { na: true, findings: [] };
|
|
316
|
+
}
|
|
317
|
+
const findings = [];
|
|
318
|
+
const colUnresolving = [];
|
|
319
|
+
const colNoEdge = [];
|
|
320
|
+
const edgeNoCol = [];
|
|
321
|
+
let covered = 0;
|
|
322
|
+
let viaContract = 0;
|
|
323
|
+
let unresolved = 0;
|
|
324
|
+
let capReach = 0;
|
|
325
|
+
let consumerEnum = 0;
|
|
326
|
+
for (const r of rows) {
|
|
327
|
+
const id = String(r[0]);
|
|
328
|
+
const pub = String(r[1] ?? "");
|
|
329
|
+
const hasEdge = r[2] === true;
|
|
330
|
+
const hasPubEdge = r[3] === true;
|
|
331
|
+
const pubResolves = r[4] === true;
|
|
332
|
+
const twinCovered = r[5] === true;
|
|
333
|
+
const pubCapClaimed = r[6] === true;
|
|
334
|
+
const edgeCapClaimed = r[7] === true;
|
|
335
|
+
const hasConsumeEdge = r[8] === true;
|
|
336
|
+
// Column-vs-edge disagreement controls — the two attribution
|
|
337
|
+
// representations checked against each other every run.
|
|
338
|
+
if (pub !== "" && !pubResolves)
|
|
339
|
+
colUnresolving.push({ eventId: id, publisher: pub });
|
|
340
|
+
if (pub !== "" && pubResolves && !hasPubEdge)
|
|
341
|
+
colNoEdge.push(id);
|
|
342
|
+
if (hasPubEdge && pub === "")
|
|
343
|
+
edgeNoCol.push(id);
|
|
344
|
+
if (pubCapClaimed || edgeCapClaimed)
|
|
345
|
+
capReach++;
|
|
346
|
+
if (hasConsumeEdge)
|
|
347
|
+
consumerEnum++;
|
|
348
|
+
const verdict = classifyEvent(hasEdge, pub, pubResolves, twinCovered);
|
|
349
|
+
if (verdict === "covered")
|
|
350
|
+
covered++;
|
|
351
|
+
else if (verdict === "covered-as-contract")
|
|
352
|
+
viaContract++;
|
|
353
|
+
else {
|
|
354
|
+
unresolved++;
|
|
355
|
+
findings.push({
|
|
356
|
+
predicate: "event_coverage",
|
|
357
|
+
kind: options.tier === "aggregate" ? "orphan" : "report",
|
|
358
|
+
subjectId: id,
|
|
359
|
+
classification: options.tier === "aggregate" ? "orphaned" : "unresolved-locally",
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
na: false,
|
|
365
|
+
findings,
|
|
366
|
+
metrics: {
|
|
367
|
+
total: rows.length,
|
|
368
|
+
covered,
|
|
369
|
+
viaContract,
|
|
370
|
+
unresolved,
|
|
371
|
+
capReach,
|
|
372
|
+
consumerEnum,
|
|
373
|
+
},
|
|
374
|
+
controls: { colUnresolving, colNoEdge, edgeNoCol },
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/** Statement-for-statement mirror of the reference classify(). */
|
|
378
|
+
function classifyContract(reachable, excluded, coveredAsEvent) {
|
|
379
|
+
if (excluded && reachable)
|
|
380
|
+
return "conflict";
|
|
381
|
+
if (reachable)
|
|
382
|
+
return "covered";
|
|
383
|
+
if (coveredAsEvent)
|
|
384
|
+
return "covered-as-event";
|
|
385
|
+
if (excluded)
|
|
386
|
+
return "excluded";
|
|
387
|
+
return "unresolved";
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* A positive property of the artefact: an OpenAPI document with no
|
|
391
|
+
* `paths`. Mirrors the reference's has_no_operations, including every
|
|
392
|
+
* refusal path (non-yaml extension, unreachable file, unparseable
|
|
393
|
+
* content, non-mapping document → False, never a guess).
|
|
394
|
+
*/
|
|
395
|
+
function hasNoOperations(sddRoot, specPath) {
|
|
396
|
+
if (!specPath || !(specPath.endsWith(".yaml") || specPath.endsWith(".yml"))) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
const full = join(sddRoot, specPath);
|
|
400
|
+
if (!existsSync(full))
|
|
401
|
+
return false;
|
|
402
|
+
let doc;
|
|
403
|
+
try {
|
|
404
|
+
doc = parseYaml(readFileSync(full, "utf-8"));
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
const d = doc;
|
|
413
|
+
if (!("openapi" in d) && !("swagger" in d) && !("paths" in d))
|
|
414
|
+
return true;
|
|
415
|
+
const paths = d["paths"];
|
|
416
|
+
if (!paths || typeof paths !== "object")
|
|
417
|
+
return true;
|
|
418
|
+
return Object.keys(paths).length === 0;
|
|
419
|
+
}
|
|
420
|
+
const CONTRACT_FLAGS_SQL = `
|
|
421
|
+
WITH ct AS (
|
|
422
|
+
SELECT c."id" AS id,
|
|
423
|
+
COALESCE(c."status", '') AS status,
|
|
424
|
+
COALESCE(c."evidence_class", '') AS evidence_class,
|
|
425
|
+
COALESCE(c."kind", '') AS kind,
|
|
426
|
+
REPLACE(COALESCE(c."spec_path", ''), chr(92), '/') AS sp
|
|
427
|
+
FROM "contracts" c
|
|
428
|
+
WHERE c."id" IS NOT NULL AND c."id" <> ''
|
|
429
|
+
AND c.rowid = (SELECT MAX(c2.rowid) FROM "contracts" c2 WHERE c2."id" = c."id")
|
|
430
|
+
),
|
|
431
|
+
events_with_edges AS (
|
|
432
|
+
SELECT DISTINCT event_id FROM (
|
|
433
|
+
SELECT "event_id" FROM "service_event"
|
|
434
|
+
UNION ALL SELECT "event_id" FROM "capability_event"
|
|
435
|
+
UNION ALL SELECT "event_id" FROM "feature_event"
|
|
436
|
+
) u
|
|
437
|
+
WHERE COALESCE(event_id, '') <> ''
|
|
438
|
+
),
|
|
439
|
+
twin_bases AS (
|
|
440
|
+
SELECT DISTINCT regexp_extract(REPLACE(e."schema_path", chr(92), '/'), '[^/]*$') AS base
|
|
441
|
+
FROM "events" e
|
|
442
|
+
WHERE COALESCE(e."schema_path", '') <> ''
|
|
443
|
+
AND COALESCE(e."id", '') <> ''
|
|
444
|
+
AND e."id" IN (SELECT event_id FROM events_with_edges)
|
|
445
|
+
)
|
|
446
|
+
SELECT ct.id, ct.status, ct.evidence_class, ct.kind, ct.sp,
|
|
447
|
+
EXISTS (SELECT 1 FROM "capability_contract" cc
|
|
448
|
+
WHERE cc."contract_id" = ct.id) AS owned,
|
|
449
|
+
EXISTS (SELECT 1 FROM "service_contract" sc
|
|
450
|
+
WHERE sc."contract_id" = ct.id) AS attributed,
|
|
451
|
+
(ct.sp <> '' AND EXISTS (SELECT 1 FROM twin_bases b
|
|
452
|
+
WHERE b.base = regexp_extract(ct.sp, '[^/]*$'))) AS twin_edge
|
|
453
|
+
FROM ct
|
|
454
|
+
ORDER BY ct.id;`;
|
|
455
|
+
const CONSUMERS_NAMED_SQL = `
|
|
456
|
+
SELECT COUNT(DISTINCT sc."contract_id")
|
|
457
|
+
FROM "service_contract" sc
|
|
458
|
+
WHERE COALESCE(sc."contract_id", '') <> ''
|
|
459
|
+
AND COALESCE(sc."role", '') = 'consumes';`;
|
|
460
|
+
const RAW_SPEC_PATHS_SQL = `
|
|
461
|
+
SELECT DISTINCT COALESCE(c."spec_path", '') FROM "contracts" c;`;
|
|
462
|
+
export async function runContractCoverage(session, options) {
|
|
463
|
+
// Known-good / known-bad probe (M-ADR 0199 §4.6), both directions,
|
|
464
|
+
// before any real data — mirror of the reference's five-way probe.
|
|
465
|
+
const probe = {
|
|
466
|
+
covered: classifyContract(true, false, false),
|
|
467
|
+
orphan: classifyContract(false, false, false),
|
|
468
|
+
excluded: classifyContract(false, true, false),
|
|
469
|
+
event: classifyContract(false, false, true),
|
|
470
|
+
conflict: classifyContract(true, true, false),
|
|
471
|
+
};
|
|
472
|
+
if (probe["covered"] !== "covered" ||
|
|
473
|
+
probe["orphan"] !== "unresolved" ||
|
|
474
|
+
probe["excluded"] !== "excluded" ||
|
|
475
|
+
probe["event"] !== "covered-as-event" ||
|
|
476
|
+
probe["conflict"] !== "conflict") {
|
|
477
|
+
throw new Error("coverage-predicates: contract classifier probe misclassified a synthetic row — no verdict is trustworthy");
|
|
478
|
+
}
|
|
479
|
+
const reader = await session.conn.runAndReadAll(CONTRACT_FLAGS_SQL);
|
|
480
|
+
const rows = reader.getRows();
|
|
481
|
+
if (rows.length === 0) {
|
|
482
|
+
return { na: true, findings: [] };
|
|
483
|
+
}
|
|
484
|
+
// Availability manifest (M-ADR 0202): informational, never
|
|
485
|
+
// verdict-changing; unreadable → a warning, mirror of the reference's
|
|
486
|
+
// try/except. Row paths are the RAW spec_path values, un-normalised.
|
|
487
|
+
let availability;
|
|
488
|
+
let availabilityWarning;
|
|
489
|
+
try {
|
|
490
|
+
const manifestPath = join(options.sddRoot, ".sdd", "contract-availability.yaml");
|
|
491
|
+
if (existsSync(manifestPath)) {
|
|
492
|
+
const text = readFileSync(manifestPath, "utf-8");
|
|
493
|
+
const attestedPaths = [...text.matchAll(/^\s*- path: "([^"]+)"/gm)].map((m) => m[1]);
|
|
494
|
+
const rawReader = await session.conn.runAndReadAll(RAW_SPEC_PATHS_SQL);
|
|
495
|
+
const rowPaths = new Set(rawReader.getRows().map((r) => String(r[0] ?? "")));
|
|
496
|
+
availability = {
|
|
497
|
+
attested: attestedPaths.length,
|
|
498
|
+
unmodelled: attestedPaths.filter((p) => !rowPaths.has(p)),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
catch (e) {
|
|
503
|
+
availabilityWarning = String(e instanceof Error ? e.message : e);
|
|
504
|
+
}
|
|
505
|
+
const findings = [];
|
|
506
|
+
const byExclusion = {};
|
|
507
|
+
let covered = 0;
|
|
508
|
+
let viaEvent = 0;
|
|
509
|
+
let excludedCount = 0;
|
|
510
|
+
let conflicting = 0;
|
|
511
|
+
let unresolved = 0;
|
|
512
|
+
let ownedOnly = 0;
|
|
513
|
+
let attrOnly = 0;
|
|
514
|
+
let both = 0;
|
|
515
|
+
for (const r of rows) {
|
|
516
|
+
const id = String(r[0]);
|
|
517
|
+
const status = String(r[1] ?? "");
|
|
518
|
+
const evidenceClass = String(r[2] ?? "");
|
|
519
|
+
const kind = String(r[3] ?? "");
|
|
520
|
+
const sp = String(r[4] ?? "");
|
|
521
|
+
const owned = r[5] === true;
|
|
522
|
+
const attributed = r[6] === true;
|
|
523
|
+
const twinEdge = r[7] === true;
|
|
524
|
+
if (owned && !attributed)
|
|
525
|
+
ownedOnly++;
|
|
526
|
+
else if (attributed && !owned)
|
|
527
|
+
attrOnly++;
|
|
528
|
+
else if (owned && attributed)
|
|
529
|
+
both++;
|
|
530
|
+
// The three exclusions + the one alternative route, in the
|
|
531
|
+
// reference's order (first match wins; a deprecated event-kind
|
|
532
|
+
// contract is excluded, never covered-as-event).
|
|
533
|
+
let excluded = false;
|
|
534
|
+
let exclusionKey = "";
|
|
535
|
+
let coveredAsEvent = false;
|
|
536
|
+
if (status === "deprecated") {
|
|
537
|
+
excluded = true;
|
|
538
|
+
exclusionKey = "deprecated";
|
|
539
|
+
}
|
|
540
|
+
else if (evidenceClass === "vendor-doc") {
|
|
541
|
+
excluded = true;
|
|
542
|
+
exclusionKey = "provider-surface";
|
|
543
|
+
}
|
|
544
|
+
else if (hasNoOperations(options.sddRoot, sp)) {
|
|
545
|
+
excluded = true;
|
|
546
|
+
exclusionKey = "not-an-interface";
|
|
547
|
+
}
|
|
548
|
+
else if (kind === "event" && twinEdge) {
|
|
549
|
+
coveredAsEvent = true;
|
|
550
|
+
}
|
|
551
|
+
const verdict = classifyContract(owned || attributed, excluded, coveredAsEvent);
|
|
552
|
+
if (verdict === "conflict") {
|
|
553
|
+
conflicting++;
|
|
554
|
+
findings.push({
|
|
555
|
+
predicate: "contract_coverage",
|
|
556
|
+
kind: "divergence",
|
|
557
|
+
subjectId: id,
|
|
558
|
+
classification: exclusionKey,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
else if (verdict === "covered") {
|
|
562
|
+
covered++;
|
|
563
|
+
}
|
|
564
|
+
else if (verdict === "covered-as-event") {
|
|
565
|
+
viaEvent++;
|
|
566
|
+
}
|
|
567
|
+
else if (verdict === "excluded") {
|
|
568
|
+
excludedCount++;
|
|
569
|
+
byExclusion[exclusionKey] = (byExclusion[exclusionKey] ?? 0) + 1;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
unresolved++;
|
|
573
|
+
findings.push({
|
|
574
|
+
predicate: "contract_coverage",
|
|
575
|
+
kind: options.tier === "aggregate" ? "orphan" : "coverage",
|
|
576
|
+
subjectId: id,
|
|
577
|
+
classification: options.tier === "aggregate" ? "orphaned" : "unresolved-locally",
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const consumersReader = await session.conn.runAndReadAll(CONSUMERS_NAMED_SQL);
|
|
582
|
+
const consumersNamed = Number(consumersReader.getRows()[0][0]);
|
|
583
|
+
return {
|
|
584
|
+
na: false,
|
|
585
|
+
findings,
|
|
586
|
+
metrics: {
|
|
587
|
+
total: rows.length,
|
|
588
|
+
covered,
|
|
589
|
+
viaEvent,
|
|
590
|
+
excluded: excludedCount,
|
|
591
|
+
conflicting,
|
|
592
|
+
unresolved,
|
|
593
|
+
byExclusion,
|
|
594
|
+
consumersNamed,
|
|
595
|
+
ownedOnly,
|
|
596
|
+
attrOnly,
|
|
597
|
+
both,
|
|
598
|
+
},
|
|
599
|
+
...(availability !== undefined ? { availability } : {}),
|
|
600
|
+
...(availabilityWarning !== undefined ? { availabilityWarning } : {}),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
//# sourceMappingURL=coverage-predicates.js.map
|