@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,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader for the per-SDD `.sdd/sdd-manifest.yaml` per the schema at
|
|
3
|
+
* docs/method/methods/sdd-manifest-schema.yaml in sdd-method.
|
|
4
|
+
*
|
|
5
|
+
* Validates schema_version against SCHEMA_COMPAT and the
|
|
6
|
+
* profile↔category invariant from product-category-taxonomy.md.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { parse as parseYaml } from "yaml";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { SCHEMA_COMPAT, formatSchemaVersionMismatch } from "./schema-compat.js";
|
|
13
|
+
export const MANIFEST_REL_PATH = ".sdd/sdd-manifest.yaml";
|
|
14
|
+
export const PROFILES = [
|
|
15
|
+
"platform-profile",
|
|
16
|
+
"application-product",
|
|
17
|
+
"integration-product",
|
|
18
|
+
"support-repo",
|
|
19
|
+
"organisation-profile",
|
|
20
|
+
];
|
|
21
|
+
export const CATEGORIES = [
|
|
22
|
+
"platform-root",
|
|
23
|
+
"platform-subsystem",
|
|
24
|
+
"application-product",
|
|
25
|
+
"integration-provider",
|
|
26
|
+
"integration-umbrella",
|
|
27
|
+
"support-repo",
|
|
28
|
+
"organisation-root",
|
|
29
|
+
];
|
|
30
|
+
const PROFILE_CATEGORY_INVARIANT = {
|
|
31
|
+
"platform-profile": ["platform-root", "platform-subsystem"],
|
|
32
|
+
"application-product": ["application-product"],
|
|
33
|
+
"integration-product": ["integration-provider", "integration-umbrella"],
|
|
34
|
+
"support-repo": ["support-repo"],
|
|
35
|
+
"organisation-profile": ["organisation-root"],
|
|
36
|
+
};
|
|
37
|
+
// M-ADR 0193 — the data-plane axis. `pattern` and `locality` are CLOSED
|
|
38
|
+
// method-tier enums; `transport`/`target`/`runtime`/`notes` are free
|
|
39
|
+
// (adopter-tier). Malformed bindings FAIL at load, consistent with every other
|
|
40
|
+
// fixed manifest enum.
|
|
41
|
+
export const DATA_PLANE_PATTERNS = [
|
|
42
|
+
// Inbound (data-sourcing) sub-set.
|
|
43
|
+
"retrieve",
|
|
44
|
+
"own-store",
|
|
45
|
+
"stream-ingest",
|
|
46
|
+
"project",
|
|
47
|
+
// Output-side sub-set (M-ADR 0193 §4.7): how the service produces or exposes
|
|
48
|
+
// data. `generate` produces a scheduled/periodic artefact (compute-bearing,
|
|
49
|
+
// carries a runtime); `publish` exposes an outward contract/API (not
|
|
50
|
+
// compute-bearing).
|
|
51
|
+
"generate",
|
|
52
|
+
"publish",
|
|
53
|
+
];
|
|
54
|
+
// Inbound data-sourcing patterns MUST name where the other end sits. `own-store`
|
|
55
|
+
// is a self-binding (no other end); the output-side patterns (`generate`/
|
|
56
|
+
// `publish`) MAY carry a locality but do not require one (M-ADR 0193 §4.7).
|
|
57
|
+
const LOCALITY_REQUIRED_PATTERNS = new Set([
|
|
58
|
+
"retrieve",
|
|
59
|
+
"stream-ingest",
|
|
60
|
+
"project",
|
|
61
|
+
]);
|
|
62
|
+
export const DATA_PLANE_LOCALITIES = [
|
|
63
|
+
"internal-platform",
|
|
64
|
+
"internal-domain",
|
|
65
|
+
"external-vendor",
|
|
66
|
+
];
|
|
67
|
+
// The OUTPUT-SIDE sub-set (M-ADR 0193 §4.7). This is the single method-tier
|
|
68
|
+
// direction mapping: every consumer that needs to branch on direction reads it
|
|
69
|
+
// from here rather than re-deriving the pattern split, so the sub-set the ADR
|
|
70
|
+
// declares has exactly one definition in the codebase.
|
|
71
|
+
const OUTPUT_SIDE_PATTERNS = new Set(["generate", "publish"]);
|
|
72
|
+
/**
|
|
73
|
+
* Direction of a data-plane pattern. DERIVED, never authored — `direction` is not
|
|
74
|
+
* a manifest field, so it cannot disagree with `pattern`. Three §4.7 rules branch
|
|
75
|
+
* on it: platform-dep reconciliation is inbound-only, the archetype-default WARN
|
|
76
|
+
* compares like with like, and the M-ADR 0187 convergence reasoning differs by
|
|
77
|
+
* direction.
|
|
78
|
+
*/
|
|
79
|
+
export function dataPlaneDirection(pattern) {
|
|
80
|
+
return OUTPUT_SIDE_PATTERNS.has(pattern) ? "output" : "inbound";
|
|
81
|
+
}
|
|
82
|
+
/** True for the output-side sub-set (`generate`/`publish`). */
|
|
83
|
+
export function isOutputSidePattern(pattern) {
|
|
84
|
+
return OUTPUT_SIDE_PATTERNS.has(pattern);
|
|
85
|
+
}
|
|
86
|
+
// Patterns that carry a backend-compute role and therefore a runtime choice
|
|
87
|
+
// (M-ADR 0193 §4.1, extended by §4.7). `stream-ingest`/`project` (inbound
|
|
88
|
+
// compute) and `generate` (scheduled-generation) carry a runtime; `retrieve`
|
|
89
|
+
// (BFF/client), `own-store` (private-api), and `publish` (an exposed contract,
|
|
90
|
+
// not compute in the sourcing sense) do not, so a `runtime` on them is malformed.
|
|
91
|
+
const COMPUTE_BEARING_PATTERNS = new Set([
|
|
92
|
+
"stream-ingest",
|
|
93
|
+
"project",
|
|
94
|
+
"generate",
|
|
95
|
+
]);
|
|
96
|
+
const DataPlaneBindingSchema = z
|
|
97
|
+
.object({
|
|
98
|
+
pattern: z.enum(DATA_PLANE_PATTERNS),
|
|
99
|
+
locality: z.enum(DATA_PLANE_LOCALITIES).optional(),
|
|
100
|
+
transport: z.string().optional(),
|
|
101
|
+
runtime: z.string().optional(),
|
|
102
|
+
target: z.string().optional(),
|
|
103
|
+
notes: z.string().optional(),
|
|
104
|
+
})
|
|
105
|
+
.refine((b) => !LOCALITY_REQUIRED_PATTERNS.has(b.pattern) || b.locality !== undefined, {
|
|
106
|
+
message: "locality is required for a data-sourcing binding (retrieve/stream-ingest/project); own-store and the output-side patterns (generate/publish) may omit it",
|
|
107
|
+
path: ["locality"],
|
|
108
|
+
})
|
|
109
|
+
.refine((b) => b.runtime === undefined || COMPUTE_BEARING_PATTERNS.has(b.pattern), {
|
|
110
|
+
message: "runtime is only valid on a compute-bearing binding (stream-ingest/project/generate); retrieve/own-store/publish carry no runtime choice",
|
|
111
|
+
path: ["runtime"],
|
|
112
|
+
});
|
|
113
|
+
// A declared departure from the archetype's default composition (M-ADR 0191
|
|
114
|
+
// §4.3 overlay). `expectation` names the archetype-default pattern the product
|
|
115
|
+
// knowingly diverges from; the rationale makes it sanctioned variance.
|
|
116
|
+
const DataPlaneDeviationSchema = z.object({
|
|
117
|
+
expectation: z.string(),
|
|
118
|
+
rationale: z.string().min(1, {
|
|
119
|
+
message: "a data_plane deviation must carry a non-empty rationale",
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
const DataPlaneSchema = z.object({
|
|
123
|
+
bindings: z.array(DataPlaneBindingSchema),
|
|
124
|
+
deviations: z.array(DataPlaneDeviationSchema).optional(),
|
|
125
|
+
});
|
|
126
|
+
// Conformance coverage (M-ADR 0191 §4.3, second overlay element — WS4). An
|
|
127
|
+
// application-product asserts which of its archetype's conformance expectations
|
|
128
|
+
// its own conformance suite exercises (`coverage`), and declares a rationale for
|
|
129
|
+
// any expected tag it deliberately does not cover (`deviations`). The method
|
|
130
|
+
// gates the DECLARATION — it does not run the adopter's conformance tests.
|
|
131
|
+
const ConformanceSchema = z.object({
|
|
132
|
+
coverage: z.array(z.string()).optional(),
|
|
133
|
+
deviations: z
|
|
134
|
+
.array(z.object({
|
|
135
|
+
tag: z.string(),
|
|
136
|
+
rationale: z.string().min(1, {
|
|
137
|
+
message: "a conformance deviation must carry a non-empty rationale",
|
|
138
|
+
}),
|
|
139
|
+
}))
|
|
140
|
+
.optional(),
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* Owner-declared product lifecycle. Exported as a runtime array, not just a type,
|
|
144
|
+
* so the canonical-conformance test can compare the CANONICAL vocabulary against
|
|
145
|
+
* the IMPLEMENTATION rather than against a list duplicated in the test — a
|
|
146
|
+
* hardcoded expectation cannot detect the implementation drifting.
|
|
147
|
+
*
|
|
148
|
+
* `planned` = real and intended but does not exist yet (sdd-manifest-schema
|
|
149
|
+
* 1.9.0). Absent still reads as `active`.
|
|
150
|
+
*/
|
|
151
|
+
export const LIFECYCLES = ["planned", "active", "deprecated"];
|
|
152
|
+
const ManifestSchema = z.object({
|
|
153
|
+
schema_version: z.string(),
|
|
154
|
+
sdd_id: z.string().regex(/^[a-z][a-z0-9-]*$/, {
|
|
155
|
+
message: "sdd_id must match /^[a-z][a-z0-9-]*$/",
|
|
156
|
+
}),
|
|
157
|
+
profile: z.enum(PROFILES),
|
|
158
|
+
category: z.enum(CATEGORIES),
|
|
159
|
+
domain_id: z
|
|
160
|
+
.string()
|
|
161
|
+
.regex(/^[a-z][a-z0-9-]*$/)
|
|
162
|
+
.optional(),
|
|
163
|
+
ecosystem_root: z.string().optional(),
|
|
164
|
+
// M-ADR 0208 §4.3: the platform this member is contained by. Canonical name;
|
|
165
|
+
// `ecosystem_root` above is its deprecated alias (schema 1.10.0, removed at 2.0.0).
|
|
166
|
+
platform_sdd_id: z.string().optional(),
|
|
167
|
+
// M-ADR 0056 (amended 2026-09-29, manifest schema 1.11.0): the SDD whose
|
|
168
|
+
// feature-tree.md holds this SDD's runway classifications, when it has none.
|
|
169
|
+
feature_tree_anchor: z
|
|
170
|
+
.string()
|
|
171
|
+
.regex(/^[a-z][a-z0-9-]*$/)
|
|
172
|
+
.optional(),
|
|
173
|
+
aggregation_manifest_path: z.string().optional(),
|
|
174
|
+
description: z.string().optional(),
|
|
175
|
+
// Optional canonical C4 system id. Defaults to `{sdd_id}-system`. Set this
|
|
176
|
+
// when the system was already catalogued under an established id elsewhere
|
|
177
|
+
// (e.g. a reverse-extracted system pulled into its own SDD) so the id stays
|
|
178
|
+
// stable across SDDs and aggregation.
|
|
179
|
+
c4_system_id: z
|
|
180
|
+
.string()
|
|
181
|
+
.regex(/^[a-z][a-z0-9-]*$/, {
|
|
182
|
+
message: "c4_system_id must match /^[a-z][a-z0-9-]*$/",
|
|
183
|
+
})
|
|
184
|
+
.optional(),
|
|
185
|
+
// Owner-declared product lifecycle (sdd-manifest-schema 1.1.0). Absent
|
|
186
|
+
// means "active". Unknown values are a hard load failure, consistent with
|
|
187
|
+
// every other manifest enum — a typo'd lifecycle silently defaulting to
|
|
188
|
+
// active would recreate the exact false-active bug the field exists to fix.
|
|
189
|
+
// `planned` (sdd-manifest-schema 1.9.0): the product is real and intended but
|
|
190
|
+
// does not exist yet — the normal OPENING state of a forward-authored product,
|
|
191
|
+
// where the SDD is written before the code. The absent-means-"active" default
|
|
192
|
+
// was the one state a pre-realisation estate cannot be in, so it silently
|
|
193
|
+
// asserted that every unbuilt product was running. A fact about the PRODUCT,
|
|
194
|
+
// deliberately not about the document (that is `status`).
|
|
195
|
+
lifecycle: z.enum(LIFECYCLES).optional(),
|
|
196
|
+
// Application-product surface trait (M-ADR 0167, sdd-manifest-schema 1.2.0).
|
|
197
|
+
// Fixed enum: ui (human-facing, default) | api (headless). Unknown values
|
|
198
|
+
// are a hard load failure like every other fixed manifest enum. Ignored off
|
|
199
|
+
// application-product (cleared with a convention finding in the products
|
|
200
|
+
// builder). Absent reads as "ui".
|
|
201
|
+
surface: z.enum(["ui", "api"]).optional(),
|
|
202
|
+
// Application-product packaging trait (M-ADR 0175, sdd-manifest-schema
|
|
203
|
+
// 1.7.0). Fixed enum, deliberately narrow: `vendor-saas` marks a
|
|
204
|
+
// packaged-SaaS product (vendor-operated runtime, vendor-versioned base);
|
|
205
|
+
// absent reads as self-operated, the unmarked norm. Unknown values are a hard
|
|
206
|
+
// load failure like every other fixed manifest enum. Ignored off
|
|
207
|
+
// application-product (cleared with a convention finding in the products
|
|
208
|
+
// builder). Per M-ADR 0175 §4 no builder may branch on this beyond
|
|
209
|
+
// evidence/deployment handling.
|
|
210
|
+
packaging: z.enum(["vendor-saas"]).optional(),
|
|
211
|
+
// Reference-exemplar trait (sdd-manifest-schema 1.8.0). PROFILE-AGNOSTIC,
|
|
212
|
+
// unlike the three traits it sits between — a demonstration
|
|
213
|
+
// application-product is as conceivable as the integration-products the first
|
|
214
|
+
// cohort came from — so the products builder must NOT clear it off
|
|
215
|
+
// application-product. Absent reads as a real SDD, the unmarked norm, with no
|
|
216
|
+
// finding. Only `true` is meaningful; `false` is redundant but accepted.
|
|
217
|
+
exemplar: z.boolean().optional(),
|
|
218
|
+
// Shared-library trait (sdd-manifest-schema 1.12.0, M-ADR 0217 §4.4). Profile-agnostic,
|
|
219
|
+
// like `exemplar`; absent is the unmarked norm, no finding.
|
|
220
|
+
shared_library: z.boolean().optional(),
|
|
221
|
+
// Application-product archetype trait (M-ADR 0191, sdd-manifest-schema
|
|
222
|
+
// 1.2.0). RESOLVED enum, not fixed: the loader validates only the slug
|
|
223
|
+
// shape here. Whether the value resolves against the active vocabulary
|
|
224
|
+
// (method ∪ RA-bundle ∪ adopter) is checked in the build step, which has
|
|
225
|
+
// the bundle cache. Absent reads as "unclassified" (with a finding). Named
|
|
226
|
+
// application_archetype to stay distinct from the ADR 0173 domain archetype
|
|
227
|
+
// and the ADR 0169 openui archetype.
|
|
228
|
+
application_archetype: z
|
|
229
|
+
.string()
|
|
230
|
+
.regex(/^[a-z][a-z0-9-]*$/, {
|
|
231
|
+
message: "application_archetype must match /^[a-z][a-z0-9-]*$/",
|
|
232
|
+
})
|
|
233
|
+
.optional(),
|
|
234
|
+
deprecated_date: z
|
|
235
|
+
.string()
|
|
236
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, {
|
|
237
|
+
message: "deprecated_date must be a YYYY-MM-DD date",
|
|
238
|
+
})
|
|
239
|
+
.optional(),
|
|
240
|
+
successor_sdd_id: z
|
|
241
|
+
.string()
|
|
242
|
+
.regex(/^[a-z][a-z0-9-]*$/, {
|
|
243
|
+
message: "successor_sdd_id must match /^[a-z][a-z0-9-]*$/ (sdd_id vocabulary)",
|
|
244
|
+
})
|
|
245
|
+
.optional(),
|
|
246
|
+
// Application-product data-plane axis (M-ADR 0193, sdd-manifest-schema 1.3.0);
|
|
247
|
+
// `data_plane.deviations` added in 1.4.0 (M-ADR 0191 §4.3 overlay). A
|
|
248
|
+
// composition of typed bindings (malformed bindings FAIL at load) plus
|
|
249
|
+
// optional declared deviations from the archetype default, each carrying a
|
|
250
|
+
// rationale so the mismatch is sanctioned variance, not silent drift.
|
|
251
|
+
data_plane: DataPlaneSchema.optional(),
|
|
252
|
+
// Conformance coverage declaration (M-ADR 0191 §4.3 WS4, sdd-manifest-schema
|
|
253
|
+
// 1.5.0). Which archetype conformance expectations the product's suite
|
|
254
|
+
// exercises, plus rationales for any it deliberately does not.
|
|
255
|
+
conformance: ConformanceSchema.optional(),
|
|
256
|
+
});
|
|
257
|
+
/** What `products.packaging` carries when the manifest declares nothing. */
|
|
258
|
+
export const PACKAGING_DEFAULT = "self-operated";
|
|
259
|
+
/**
|
|
260
|
+
* Read and validate the `.sdd/sdd-manifest.yaml` at the given SDD repo root.
|
|
261
|
+
*
|
|
262
|
+
* Throws on:
|
|
263
|
+
* - file missing
|
|
264
|
+
* - YAML parse error
|
|
265
|
+
* - shape violation (missing fields, bad enum value, bad slug pattern)
|
|
266
|
+
* - profile↔category invariant violation
|
|
267
|
+
* - schema_version outside SCHEMA_COMPAT.sdd_manifest range
|
|
268
|
+
*/
|
|
269
|
+
/**
|
|
270
|
+
* Top-level keys this build understands. Derived from the zod shape so it cannot
|
|
271
|
+
* drift from what is actually parsed — a hand-maintained list would go stale and
|
|
272
|
+
* start reporting known fields as unknown, which is worse than saying nothing.
|
|
273
|
+
*/
|
|
274
|
+
const KNOWN_MANIFEST_KEYS = new Set(Object.keys(ManifestSchema.shape));
|
|
275
|
+
/**
|
|
276
|
+
* Top-level keys present in the file that this build does not know.
|
|
277
|
+
*
|
|
278
|
+
* Deliberately TOP-LEVEL only. Nested blocks (`data_plane`, `conformance`) are
|
|
279
|
+
* validated by their own object schemas and strip their own unknowns; walking
|
|
280
|
+
* them would need a per-schema recursion this does not yet warrant. Stated so the
|
|
281
|
+
* limit is known rather than assumed — an unknown key nested inside `data_plane`
|
|
282
|
+
* is still silently dropped today.
|
|
283
|
+
*/
|
|
284
|
+
function findUnknownTopLevelKeys(raw) {
|
|
285
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
286
|
+
return [];
|
|
287
|
+
return Object.keys(raw)
|
|
288
|
+
.filter((k) => !KNOWN_MANIFEST_KEYS.has(k))
|
|
289
|
+
.sort();
|
|
290
|
+
}
|
|
291
|
+
export async function loadSddManifest(sddPath, options) {
|
|
292
|
+
const sourcePath = join(sddPath, MANIFEST_REL_PATH);
|
|
293
|
+
let text;
|
|
294
|
+
try {
|
|
295
|
+
text = await readFile(sourcePath, "utf-8");
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
const code = err.code;
|
|
299
|
+
if (code === "ENOENT") {
|
|
300
|
+
throw new Error(`SDD manifest not found at ${sourcePath}\n\n` +
|
|
301
|
+
`Every SDD repo MUST declare an .sdd/sdd-manifest.yaml per ADR 0134 §4.6.\n` +
|
|
302
|
+
`Schema: docs/method/methods/sdd-manifest-schema.yaml in sdd-method.\n` +
|
|
303
|
+
`Bootstrap: \`sdd-cli init\` (when migrated to support manifest authoring).`);
|
|
304
|
+
}
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
let raw;
|
|
308
|
+
try {
|
|
309
|
+
raw = parseYaml(text);
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
throw new Error(`failed to parse YAML at ${sourcePath}: ${err.message}`);
|
|
313
|
+
}
|
|
314
|
+
// Unknown top-level keys are REPORTED, not silently dropped and not fatal.
|
|
315
|
+
//
|
|
316
|
+
// zod's default is `strip`, so a key this build does not know vanished without a
|
|
317
|
+
// trace: an adopter following a schema that documents a field their sdd-cli
|
|
318
|
+
// predates got a clean validation and a silent no-op. Not hypothetical —
|
|
319
|
+
// M-ADR 0175's own version history records exactly this happening to
|
|
320
|
+
// `packaging`, where the ADR instructed adopters to declare a field the CLI had
|
|
321
|
+
// never implemented.
|
|
322
|
+
//
|
|
323
|
+
// `.strict()` is the WRONG fix: it turns the same situation into a hard failure,
|
|
324
|
+
// so an older CLI could not build an SDD whose manifest declares anything newer
|
|
325
|
+
// than itself. Newer baselines ship newer schemas by design, so that inverts the
|
|
326
|
+
// problem instead of solving it. Reporting keeps the declaration visible while
|
|
327
|
+
// leaving the build working.
|
|
328
|
+
const unknownKeys = findUnknownTopLevelKeys(raw);
|
|
329
|
+
const parsed = ManifestSchema.safeParse(raw);
|
|
330
|
+
if (!parsed.success) {
|
|
331
|
+
const issues = parsed.error.issues
|
|
332
|
+
.map((i) => ` - ${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
333
|
+
.join("\n");
|
|
334
|
+
throw new Error(`invalid SDD manifest at ${sourcePath}:\n${issues}`);
|
|
335
|
+
}
|
|
336
|
+
// Compat range check.
|
|
337
|
+
if (!isInRange(parsed.data.schema_version, SCHEMA_COMPAT.sdd_manifest)) {
|
|
338
|
+
throw new Error(formatSchemaVersionMismatch({
|
|
339
|
+
schema: "sdd_manifest",
|
|
340
|
+
readVersion: parsed.data.schema_version,
|
|
341
|
+
cliVersion: options.cliVersion,
|
|
342
|
+
sourcePath,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
// Profile ↔ category invariant.
|
|
346
|
+
const permitted = PROFILE_CATEGORY_INVARIANT[parsed.data.profile];
|
|
347
|
+
if (!permitted.includes(parsed.data.category)) {
|
|
348
|
+
throw new Error(`invalid profile/category combination at ${sourcePath}:\n` +
|
|
349
|
+
` profile=${parsed.data.profile}, category=${parsed.data.category}\n` +
|
|
350
|
+
` permitted categories under profile=${parsed.data.profile}: ${permitted.join(", ")}\n` +
|
|
351
|
+
` per docs/method/methods/product-category-taxonomy.md`);
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
schemaVersion: parsed.data.schema_version,
|
|
355
|
+
sddId: parsed.data.sdd_id,
|
|
356
|
+
profile: parsed.data.profile,
|
|
357
|
+
category: parsed.data.category,
|
|
358
|
+
...(parsed.data.domain_id !== undefined ? { domainId: parsed.data.domain_id } : {}),
|
|
359
|
+
...(parsed.data.c4_system_id !== undefined ? { c4SystemId: parsed.data.c4_system_id } : {}),
|
|
360
|
+
...(parsed.data.ecosystem_root !== undefined
|
|
361
|
+
? { ecosystemRoot: parsed.data.ecosystem_root }
|
|
362
|
+
: {}),
|
|
363
|
+
...((parsed.data.platform_sdd_id ?? parsed.data.ecosystem_root) !== undefined
|
|
364
|
+
? {
|
|
365
|
+
platformSddId: (parsed.data.platform_sdd_id ?? parsed.data.ecosystem_root),
|
|
366
|
+
platformFieldDeprecated: parsed.data.platform_sdd_id === undefined && parsed.data.ecosystem_root !== undefined,
|
|
367
|
+
}
|
|
368
|
+
: {}),
|
|
369
|
+
...(parsed.data.aggregation_manifest_path !== undefined
|
|
370
|
+
? { aggregationManifestPath: parsed.data.aggregation_manifest_path }
|
|
371
|
+
: {}),
|
|
372
|
+
...(parsed.data.feature_tree_anchor !== undefined
|
|
373
|
+
? { featureTreeAnchor: parsed.data.feature_tree_anchor }
|
|
374
|
+
: {}),
|
|
375
|
+
...(parsed.data.description !== undefined ? { description: parsed.data.description } : {}),
|
|
376
|
+
...(parsed.data.lifecycle !== undefined ? { lifecycle: parsed.data.lifecycle } : {}),
|
|
377
|
+
...(parsed.data.surface !== undefined ? { surface: parsed.data.surface } : {}),
|
|
378
|
+
...(parsed.data.packaging !== undefined ? { packaging: parsed.data.packaging } : {}),
|
|
379
|
+
...(parsed.data.exemplar !== undefined ? { exemplar: parsed.data.exemplar } : {}),
|
|
380
|
+
...(parsed.data.shared_library !== undefined ? { sharedLibrary: parsed.data.shared_library } : {}),
|
|
381
|
+
...(parsed.data.application_archetype !== undefined
|
|
382
|
+
? { applicationArchetype: parsed.data.application_archetype }
|
|
383
|
+
: {}),
|
|
384
|
+
...(parsed.data.deprecated_date !== undefined
|
|
385
|
+
? { deprecatedDate: parsed.data.deprecated_date }
|
|
386
|
+
: {}),
|
|
387
|
+
...(parsed.data.successor_sdd_id !== undefined
|
|
388
|
+
? { successorSddId: parsed.data.successor_sdd_id }
|
|
389
|
+
: {}),
|
|
390
|
+
...(parsed.data.data_plane !== undefined ? { dataPlane: parsed.data.data_plane } : {}),
|
|
391
|
+
...(parsed.data.conformance !== undefined ? { conformance: parsed.data.conformance } : {}),
|
|
392
|
+
...(unknownKeys.length > 0 ? { unknownKeys } : {}),
|
|
393
|
+
sourcePath,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Caret-prefixed semver range check. Accepts X in `^1.2.3` if X.major === 1
|
|
398
|
+
* and X >= 1.2.3.
|
|
399
|
+
*
|
|
400
|
+
* Restricted to the `^X.Y.Z` shape — that's the only form SCHEMA_COMPAT uses.
|
|
401
|
+
*/
|
|
402
|
+
export function isInRange(version, range) {
|
|
403
|
+
if (!range.startsWith("^")) {
|
|
404
|
+
throw new Error(`unsupported range syntax: ${range}`);
|
|
405
|
+
}
|
|
406
|
+
const v = parseSemver(version);
|
|
407
|
+
const r = parseSemver(range.slice(1));
|
|
408
|
+
if (v === null || r === null)
|
|
409
|
+
return false;
|
|
410
|
+
if (v.major !== r.major)
|
|
411
|
+
return false;
|
|
412
|
+
if (v.major === 0) {
|
|
413
|
+
// Per semver, 0.x is unstable; ^0.x.y matches only 0.x.y itself.
|
|
414
|
+
return v.minor === r.minor && v.patch >= r.patch;
|
|
415
|
+
}
|
|
416
|
+
if (v.minor < r.minor)
|
|
417
|
+
return false;
|
|
418
|
+
if (v.minor === r.minor && v.patch < r.patch)
|
|
419
|
+
return false;
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
function parseSemver(s) {
|
|
423
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(s);
|
|
424
|
+
if (!m)
|
|
425
|
+
return null;
|
|
426
|
+
const [, maj, min, pat] = m;
|
|
427
|
+
if (maj === undefined || min === undefined || pat === undefined)
|
|
428
|
+
return null;
|
|
429
|
+
return {
|
|
430
|
+
major: parseInt(maj, 10),
|
|
431
|
+
minor: parseInt(min, 10),
|
|
432
|
+
patch: parseInt(pat, 10),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=manifest-loader.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Catalogues whose key is platform-scoped at organisation tier (M-ADR 0208 §4.4): two
|
|
2
|
+
// platforms may each own a row of one id, so the key is (platform_sdd_id, id). Its own
|
|
3
|
+
// module so a read-only verb (catalogue validate, reached by verify-cli) can use it
|
|
4
|
+
// without importing the aggregator.
|
|
5
|
+
// data_items (M-ADR 0216): each platform owns its vocabulary, as it owns its domains.
|
|
6
|
+
export const PLATFORM_SCOPED_AT_ORGANISATION = ["domains", "data_items"];
|
|
7
|
+
//# sourceMappingURL=platform-scope.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const norm = (s) => s.toLowerCase().replace(/^ext-/, "").replace(/[^a-z0-9]/g, "");
|
|
2
|
+
const slug = (s) => s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3
|
+
/** Does a services[] id name a declared C4 container — by id, by the system-anchored id, or by the slug of its name? */
|
|
4
|
+
export function matchesContainer(sid, containers) {
|
|
5
|
+
const target = sid.toLowerCase();
|
|
6
|
+
return containers.some((c) => {
|
|
7
|
+
const id = c.id.toLowerCase();
|
|
8
|
+
if (id === target || slug(c.name) === target)
|
|
9
|
+
return true;
|
|
10
|
+
const sys = (c.system_id ?? "").toLowerCase();
|
|
11
|
+
return sys !== "" && id === `${sys}-${target}`;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A near-miss is an external-system id that the unresolved id contains or is contained by after
|
|
16
|
+
* normalisation (a separator changed, a prefix or suffix added — the three measured shapes), with a
|
|
17
|
+
* length floor so a short common token does not fire on everything. It is NAMED, never applied.
|
|
18
|
+
*/
|
|
19
|
+
export function nearMiss(id, externalIds) {
|
|
20
|
+
const n = norm(id);
|
|
21
|
+
let best;
|
|
22
|
+
for (const ext of externalIds) {
|
|
23
|
+
const e = norm(ext);
|
|
24
|
+
if (ext === id || e === "" || Math.min(n.length, e.length) < 6)
|
|
25
|
+
continue;
|
|
26
|
+
if (n === e || n.includes(e) || e.includes(n)) {
|
|
27
|
+
if (best === undefined || e.length > norm(best).length)
|
|
28
|
+
best = ext; // prefer the most specific
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return best;
|
|
32
|
+
}
|
|
33
|
+
// @rule population-capability-service
|
|
34
|
+
export function checkCapabilityServicePopulation(inputs) {
|
|
35
|
+
let declared = 0, unresolved = 0, deduplicated = 0, capabilityUnknown = 0, crossSdd = 0;
|
|
36
|
+
let externalSystem = 0, declaredContainer = 0, nowhere = 0, expectedRows = 0;
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
for (const m of inputs.manifests) {
|
|
39
|
+
const capId = m.capability.id;
|
|
40
|
+
for (const entry of m.traceability.services ?? []) {
|
|
41
|
+
declared++;
|
|
42
|
+
if (!inputs.knownCapabilityIds.has(capId)) {
|
|
43
|
+
capabilityUnknown++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const sid = entry.id;
|
|
47
|
+
// Resolution order (M-ADR 0211 §4.3): owner_sdd FIRST — a declared cross-SDD
|
|
48
|
+
// reference is resolved by declaration, not by the local services catalogue.
|
|
49
|
+
const resolved = entry.owner_sdd !== undefined || inputs.knownServiceIds.has(sid);
|
|
50
|
+
if (entry.owner_sdd !== undefined)
|
|
51
|
+
crossSdd++;
|
|
52
|
+
if (!resolved) {
|
|
53
|
+
unresolved++;
|
|
54
|
+
if (inputs.knownExternalSystemIds.has(sid)) {
|
|
55
|
+
externalSystem++;
|
|
56
|
+
}
|
|
57
|
+
else if (matchesContainer(sid, inputs.knownContainers)) {
|
|
58
|
+
declaredContainer++;
|
|
59
|
+
inputs.findings.add("convention", `capability_service: ${capId} services[] id "${sid}" resolves as a DECLARED C4 CONTAINER, not a service — capability_service has no locus for it; the id is correct and there is nothing to fix here (M-ADR 0211 §4.3, class 2: a dependency on a container that is not a service, filed as its own item)`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
nowhere++;
|
|
63
|
+
const cand = nearMiss(sid, inputs.knownExternalSystemIds);
|
|
64
|
+
if (cand !== undefined) {
|
|
65
|
+
inputs.findings.add("convention", `capability_service: ${capId} services[] id "${sid}" resolves nowhere; nearest external-system id is "${cand}" — a CANDIDATE, not applied (M-ADR 0211 §4.3, class 3)`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const key = `${capId}__${sid}__${(entry.role ?? "") !== "" ? entry.role : "related"}`;
|
|
71
|
+
if (seen.has(key)) {
|
|
72
|
+
deduplicated++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
seen.add(key);
|
|
76
|
+
expectedRows++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const rows = inputs.capabilityServiceRows.length;
|
|
80
|
+
const balances = declared === rows + unresolved + deduplicated + capabilityUnknown;
|
|
81
|
+
inputs.findings.add("metric", `capability_service: declared=${declared} rows=${rows} unresolved=${unresolved} deduplicated=${deduplicated} capability-unknown=${capabilityUnknown} (cross-SDD via owner_sdd=${crossSdd}); unresolved by class: external-system=${externalSystem} declared-container=${declaredContainer} nowhere=${nowhere}`);
|
|
82
|
+
if (!balances) {
|
|
83
|
+
inputs.findings.add("coverage", `capability_service: the population does not balance — declared ${declared} ≠ rows ${rows} + unresolved ${unresolved} + deduplicated ${deduplicated} + capability-unknown ${capabilityUnknown} (expected rows from declarations: ${expectedRows}). Two instruments disagree; adjudicate, do not smooth (M-ADR 0211 §4.3)`);
|
|
84
|
+
}
|
|
85
|
+
return { declared, rows, unresolved, deduplicated, capabilityUnknown, crossSdd, externalSystem, declaredContainer, nowhere, balances };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=population-checks.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Relationship semantics and catalogue kinds, read from the vendored
|
|
2
|
+
// rubric-coverage-map.yaml — READ-ONLY. Split out of `graph-run.ts`, which also
|
|
3
|
+
// holds `runCatalogueGraph`, the verb that writes `<catalogues-dir>-graph.jsonl`
|
|
4
|
+
// by default. `catalogue impact` needs these loaders and nothing that writes,
|
|
5
|
+
// and the verify-only CLI ships `impact` (M-ADR 0212 §4.3b): while they lived in
|
|
6
|
+
// graph-run the writer came along with them, and the package build caught it.
|
|
7
|
+
// `graph-run.ts` re-exports everything here, so existing imports are unchanged.
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { parse as parseYaml } from "yaml";
|
|
12
|
+
/**
|
|
13
|
+
* Vendored copy of sdd-method's orchestration/rubric-coverage-map.yaml —
|
|
14
|
+
* byte-identical to the canonical (rubric-coverage-map-drift.test.ts guards
|
|
15
|
+
* it, same one-way contract as canonical-schema.yaml). The build copies it
|
|
16
|
+
* alongside the compiled module via scripts/copy-yaml-assets.mjs.
|
|
17
|
+
*/
|
|
18
|
+
export const VENDORED_COVERAGE_MAP_PATH = join(fileURLToPath(new URL(".", import.meta.url)), "rubric-coverage-map.yaml");
|
|
19
|
+
function toDecl(where, e) {
|
|
20
|
+
if (e.class === undefined) {
|
|
21
|
+
throw new Error(`rubric-coverage-map: ${where} has no class declaration (M-ADR 0206)`);
|
|
22
|
+
}
|
|
23
|
+
if ((e.impact_flow === undefined) === (e.impact_flow_by === undefined)) {
|
|
24
|
+
throw new Error(`rubric-coverage-map: ${where} must declare exactly one of impact_flow / impact_flow_by`);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
class: e.class,
|
|
28
|
+
...(e.impact_flow !== undefined
|
|
29
|
+
? { impactFlow: e.impact_flow }
|
|
30
|
+
: {}),
|
|
31
|
+
...(e.impact_flow_by !== undefined
|
|
32
|
+
? {
|
|
33
|
+
impactFlowBy: {
|
|
34
|
+
column: e.impact_flow_by.column,
|
|
35
|
+
values: e.impact_flow_by.values,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
: {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function loadRelationshipSemantics(mapPath = VENDORED_COVERAGE_MAP_PATH) {
|
|
42
|
+
const raw = parseYaml(await readFile(mapPath, "utf-8"));
|
|
43
|
+
const kinds = new Map();
|
|
44
|
+
const edges = new Map();
|
|
45
|
+
const entityFks = new Map();
|
|
46
|
+
for (const [name, entry] of Object.entries(raw.coverage)) {
|
|
47
|
+
kinds.set(name, entry.kind);
|
|
48
|
+
if (entry.kind === "edge" || entry.kind === "annotation") {
|
|
49
|
+
// an annotation classifies at top level exactly as an edge does (schema 3.9.0)
|
|
50
|
+
edges.set(name, toDecl(`edge ${name}`, entry));
|
|
51
|
+
}
|
|
52
|
+
for (const [col, decl] of Object.entries(entry.relationships ?? {})) {
|
|
53
|
+
entityFks.set(`${name}.${col}`, toDecl(`entity FK ${name}.${col}`, decl));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { kinds, edges, entityFks };
|
|
57
|
+
}
|
|
58
|
+
export async function loadCatalogueKinds(mapPath = VENDORED_COVERAGE_MAP_PATH) {
|
|
59
|
+
const raw = parseYaml(await readFile(mapPath, "utf-8"));
|
|
60
|
+
const kinds = new Map();
|
|
61
|
+
for (const [name, entry] of Object.entries(raw.coverage)) {
|
|
62
|
+
kinds.set(name, entry.kind);
|
|
63
|
+
}
|
|
64
|
+
return kinds;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=relationship-semantics.js.map
|