@sdd-method/author-cli 0.2.0 → 0.4.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/dist/lib/api-surface/__fixtures__/serverless-repo/serverless/serverless.yml +4 -4
- package/dist/lib/api-surface/analyzers/serverless.js +1 -1
- package/dist/lib/api-surface/analyzers/spring.js +1 -1
- package/dist/lib/briefing/render.js +1 -2
- package/dist/lib/briefing/variables.js +1 -2
- package/dist/lib/catalogue/archetype/wireframe.js +1 -1
- package/dist/lib/catalogue/build.js +17 -23
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +1 -1
- package/dist/lib/catalogue/builders/c4-components.js +4 -5
- package/dist/lib/catalogue/builders/c4-containers.js +3 -3
- package/dist/lib/catalogue/builders/c4-entity-rollups.js +1 -1
- package/dist/lib/catalogue/builders/c4-external-systems.js +1 -1
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +1 -1
- package/dist/lib/catalogue/builders/capabilities.js +2 -2
- package/dist/lib/catalogue/builders/capability-edges.js +4 -4
- package/dist/lib/catalogue/builders/capability-spec-walker.js +3 -3
- package/dist/lib/catalogue/builders/container-dependency.js +18 -19
- package/dist/lib/catalogue/builders/contracts.js +5 -6
- package/dist/lib/catalogue/builders/data-store-mapping.js +3 -3
- package/dist/lib/catalogue/builders/edge-label-merge.js +2 -2
- package/dist/lib/catalogue/builders/entities-manifest.js +1 -1
- package/dist/lib/catalogue/builders/entities-mermaid.js +3 -3
- package/dist/lib/catalogue/builders/entity-slug.js +11 -12
- package/dist/lib/catalogue/builders/event-catalog-walker.js +3 -3
- package/dist/lib/catalogue/builders/event-edges.js +1 -1
- package/dist/lib/catalogue/builders/evidence-class.js +2 -2
- package/dist/lib/catalogue/builders/external-system-exemption.js +1 -1
- package/dist/lib/catalogue/builders/feature-contract.js +1 -3
- package/dist/lib/catalogue/builders/feature-grounding.js +1 -2
- package/dist/lib/catalogue/builders/feature-groups.js +0 -1
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +2 -3
- package/dist/lib/catalogue/builders/feature-tree-walker.js +1 -1
- package/dist/lib/catalogue/builders/features.js +3 -4
- package/dist/lib/catalogue/builders/glossary-xref.js +1 -1
- package/dist/lib/catalogue/builders/persona-c4system.js +7 -7
- package/dist/lib/catalogue/builders/persona-container.js +14 -14
- package/dist/lib/catalogue/builders/personas.js +1 -2
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +2 -2
- package/dist/lib/catalogue/builders/quality-scores.js +1 -1
- package/dist/lib/catalogue/builders/repositories.js +2 -2
- package/dist/lib/catalogue/builders/screen-tree.js +1 -1
- package/dist/lib/catalogue/builders/screens.js +3 -4
- package/dist/lib/catalogue/builders/system-external-dependency.js +7 -7
- package/dist/lib/catalogue/canonical-schema.yaml +23 -23
- package/dist/lib/catalogue/coverage-predicates.js +4 -4
- package/dist/lib/catalogue/coverage-run.js +1 -1
- package/dist/lib/catalogue/dedupe-keyed-tables.js +2 -3
- package/dist/lib/catalogue/ext-id.js +4 -4
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +1 -1
- package/dist/lib/catalogue/finding-identity.js +3 -3
- package/dist/lib/catalogue/freshness.js +1 -1
- package/dist/lib/catalogue/manifest-loader.js +2 -2
- package/dist/lib/catalogue/scaffold-delta.js +1 -1
- package/dist/lib/catalogue/schema-compat.js +1 -2
- package/dist/lib/catalogue/screen-validation-loader.js +1 -1
- package/dist/lib/catalogue/staleness-verdicts.js +2 -2
- package/dist/lib/catalogue/traceability-loader.js +2 -3
- package/dist/lib/conformance/index.js +29 -8
- package/dist/lib/lock-file.js +1 -1
- package/dist/lib/package-version.js +1 -1
- package/dist/lib/snapshot/manifest.js +1 -1
- package/dist/lib/snapshot/targets.js +1 -1
- package/dist/lib/validate/behaviour-store.js +351 -0
- package/dist/lib/validate/dispatch.js +18 -0
- package/dist/lib/validate/index.js +1 -1
- package/dist/lib/validate/plan.js +3 -1
- package/dist/lib/validate/yaml-parse.js +1 -1
- package/dist/maintain-version.json +2 -2
- package/dist/verbs/plan.js +3 -3
- package/dist/verify-version.json +2 -2
- package/package.json +4 -6
- package/scripts/validate-app-dependencies.sh +5 -5
- package/scripts/validate-integration-docs.sh +1 -1
- package/LICENSE +0 -201
- package/NOTICE +0 -17
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{{- $vpc := .Values.Vpc | default "true" -}}
|
|
2
|
-
service:
|
|
2
|
+
service: northwind-receipts
|
|
3
3
|
provider:
|
|
4
4
|
name: aws
|
|
5
5
|
runtime: nodejs18.x
|
|
6
6
|
functions:
|
|
7
|
-
|
|
8
|
-
handler: src/
|
|
7
|
+
sendReceipt:
|
|
8
|
+
handler: src/sendReceipt.handler
|
|
9
9
|
events:
|
|
10
10
|
- http:
|
|
11
11
|
method: POST
|
|
12
|
-
path: /
|
|
12
|
+
path: /receipts/
|
|
13
13
|
resources:
|
|
14
14
|
Resources:
|
|
15
15
|
OutboundQueue:
|
|
@@ -2,7 +2,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
2
2
|
import { allBySuffix } from "../file-set.js";
|
|
3
3
|
// node-serverless analyzer.
|
|
4
4
|
//
|
|
5
|
-
// The serverless.yml in
|
|
5
|
+
// The serverless.yml in some repos is Go/Helm-templated (`{{ ... }}`, `${self:...}`, `${opt:...}`).
|
|
6
6
|
// Strategy: sanitise the template directives so the YAML parser succeeds, then read functions/events
|
|
7
7
|
// /provider/resources structurally. If the sanitised doc still won't parse, fall back to a line-based
|
|
8
8
|
// regex sweep so we never come back empty on a real (messy) file.
|
|
@@ -182,7 +182,7 @@ async function collectConfigInfra(fileSet, sharedInfra) {
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
function stripVar(v) {
|
|
185
|
-
// Keep env-var placeholders readable: `${
|
|
185
|
+
// Keep env-var placeholders readable: `${KAFKA_TOPIC}` → KAFKA_TOPIC.
|
|
186
186
|
const m = v.match(/\$\{([^:}]+)/);
|
|
187
187
|
if (m)
|
|
188
188
|
return m[1];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Renderer for the briefing-layer templates per ADR 0148
|
|
3
|
-
* `adopt-agent-instructions-briefing-layer.md`.
|
|
2
|
+
* Renderer for the briefing-layer templates per ADR 0148.
|
|
4
3
|
*
|
|
5
4
|
* Templates ship via the method baseline bundle and land at
|
|
6
5
|
* `<target>/orchestration/templates/briefing/...` after sync's
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Variable resolution for the briefing layer per ADR 0148 §4.1
|
|
3
|
-
* Phase 4 setup notes in `adopt-agent-instructions-briefing-layer.md`.
|
|
2
|
+
* Variable resolution for the briefing layer per ADR 0148 §4.1.
|
|
4
3
|
*
|
|
5
4
|
* The four variables are sourced from target-repo state:
|
|
6
5
|
*
|
|
@@ -343,7 +343,7 @@ function humaniseName(name, prefixes) {
|
|
|
343
343
|
let s = name;
|
|
344
344
|
for (const p of prefixes) {
|
|
345
345
|
// Strip only at a PascalCase boundary (next char uppercase) so a short prefix
|
|
346
|
-
// like "
|
|
346
|
+
// like "Ui" peels "UiLayout" → "Layout" but never "Uint" → "nt".
|
|
347
347
|
if (s.length > p.length && s.startsWith(p) && /[A-Z]/.test(s.charAt(p.length))) {
|
|
348
348
|
s = s.slice(p.length);
|
|
349
349
|
break;
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* Per ADR 0134 §4.6, the canonical builder reads method-defined declarative
|
|
5
5
|
* inputs and emits `catalogues/*.csv` matching the canonical schema.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* adopt-canonical-catalogue-builder.md Step F. Phase 1 ships:
|
|
7
|
+
* The orchestrator provides:
|
|
9
8
|
* - manifest loading + schema-version compat check,
|
|
10
9
|
* - schema loading,
|
|
11
10
|
* - headers-only emission for every catalogue declared in the schema,
|
|
@@ -13,9 +12,9 @@
|
|
|
13
12
|
* - findings + row-count reporting.
|
|
14
13
|
*
|
|
15
14
|
* Per-source discoverers (domains, repos, services, capabilities, features,
|
|
16
|
-
* etc.)
|
|
17
|
-
* but empty `catalogues/` directory — useful as a
|
|
18
|
-
* infrastructure works end-to-end on any SDD repo.
|
|
15
|
+
* etc.) live in `builders/`. With none of their inputs present the builder
|
|
16
|
+
* still produces a valid but empty `catalogues/` directory — useful as a
|
|
17
|
+
* smoke test that the infrastructure works end-to-end on any SDD repo.
|
|
19
18
|
*/
|
|
20
19
|
import { checkCapabilityServicePopulation } from "./population-checks.js";
|
|
21
20
|
import { basename, join, relative, resolve } from "node:path";
|
|
@@ -73,8 +72,7 @@ export async function runCatalogueBuild(inputs, write) {
|
|
|
73
72
|
// more than one walker; each walker's own seen-set only dedupes intra-walker.
|
|
74
73
|
// This union-dedupes identical rows across sources and hard-fails the build
|
|
75
74
|
// on same-PK/different-payload, so the per-SDD catalogue is set-correct over
|
|
76
|
-
// its declared PK before aggregation ever sees it.
|
|
77
|
-
// `coordination/feature-edge-cross-source-duplicate-coordination.md`.
|
|
75
|
+
// its declared PK before aggregation ever sees it.
|
|
78
76
|
const dedupe = dedupeKeyedTables(schema, rowsByCatalogue, findings);
|
|
79
77
|
// 3b. Population collapse, before anything is overwritten. A whole score family can
|
|
80
78
|
// vanish while its catalogue keeps other rows: one estate's 71 traceability-manifest
|
|
@@ -293,7 +291,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
293
291
|
// is the mistake M-ADR 0191 §4.1 / the 0.92.1 membership move corrected.
|
|
294
292
|
out.set("application_data_plane_bindings", buildApplicationDataPlaneBindings({ manifest, findings }));
|
|
295
293
|
out.set("application_data_plane_deviations", buildApplicationDataPlaneDeviations({ manifest, findings }));
|
|
296
|
-
// Conformance coverage (M-ADR 0191 §4.3
|
|
294
|
+
// Conformance coverage (M-ADR 0191 §4.3) — the product's declared coverage
|
|
297
295
|
// of / deviations from its archetype's conformance expectations. The
|
|
298
296
|
// conformance GATE that reads these runs at aggregation tier (like the binding
|
|
299
297
|
// overlay), where the reference-architecture `conformance` tags resolve.
|
|
@@ -389,8 +387,8 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
389
387
|
// Slug → capability-id aliases for feature-grounding path resolution.
|
|
390
388
|
// The grounding walker derives a capability slug from the design dir
|
|
391
389
|
// name (docs/domains/<dom>/design/<slug>/), but a capability's declared
|
|
392
|
-
// id may legitimately differ from its path slug — e.g. design/
|
|
393
|
-
// maps to id "
|
|
390
|
+
// id may legitimately differ from its path slug — e.g. design/gift-card-rules
|
|
391
|
+
// maps to id "gift-card-rules-engine". Map each capability's spec +
|
|
394
392
|
// feature-directory basename to its id so the walker resolves these
|
|
395
393
|
// without forcing a directory rename. Only non-identity, non-colliding
|
|
396
394
|
// slugs are aliased (first manifest wins).
|
|
@@ -412,7 +410,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
412
410
|
const knownServiceIds = new Set(services.map((s) => String(s["id"])));
|
|
413
411
|
// C4 L1 external systems are built here (rather than at step 15b) so the
|
|
414
412
|
// capability_service builder can categorise unresolved `services[].id`
|
|
415
|
-
// findings — an id naming a known external system (
|
|
413
|
+
// findings — an id naming a known external system (an auth provider, an identity SaaS, …) is
|
|
416
414
|
// a convention finding, not an orphan. The rows are emitted at their usual
|
|
417
415
|
// place in step 15b; only the computation is hoisted.
|
|
418
416
|
const c4External = await buildC4ExternalSystems({
|
|
@@ -593,8 +591,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
593
591
|
// emit even when every grounding row collides on feature id with an
|
|
594
592
|
// earlier source (`feature-tree` / `feature-spec`). Pre-0.30.2 the
|
|
595
593
|
// conditional above gated feature_repo on the row emission, dropping
|
|
596
|
-
// the edges in the all-collide case. Fixed
|
|
597
|
-
// `coordination/feature-grounding-collision-edges-coordination.md`.
|
|
594
|
+
// the edges in the all-collide case. Fixed.
|
|
598
595
|
out.set("feature_repo", [
|
|
599
596
|
...(out.get("feature_repo") ?? []),
|
|
600
597
|
...grounding.featureRepoRows,
|
|
@@ -635,8 +632,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
635
632
|
// emit even when every capability-spec row collides on feature id with an
|
|
636
633
|
// earlier source. Pre-fix the conditional above gated these edges on the
|
|
637
634
|
// row emission, dropping them in the all-collide case (same two-level bug
|
|
638
|
-
// feature-grounding had pre-0.30.2). Fixed
|
|
639
|
-
// `coordination/capability-spec-collision-edges-coordination.md`.
|
|
635
|
+
// feature-grounding had pre-0.30.2). Fixed.
|
|
640
636
|
out.set("feature_repo", [
|
|
641
637
|
...(out.get("feature_repo") ?? []),
|
|
642
638
|
...capabilitySpec.featureRepoRows,
|
|
@@ -646,10 +642,9 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
646
642
|
...capabilitySpec.featureServiceRows,
|
|
647
643
|
]);
|
|
648
644
|
// 10b-ter. feature_service — application-product Feature Services table.
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
//
|
|
652
|
-
// emitted, because the two extractors above only ever walk
|
|
645
|
+
// A coverage audit found this table authored and unread: 12 SDDs carry a
|
|
646
|
+
// populated `## Feature Services` table in feature-tree.md, 155 declared
|
|
647
|
+
// rows / 385 feature×service pairs, 0 emitted, because the two extractors above only ever walk
|
|
653
648
|
// feature-grounding.md / capability-spec paths (platform / integration
|
|
654
649
|
// profiles). Runs last so knownFeatureIds reflects every prior source.
|
|
655
650
|
// No `source` column exists on feature_service (schema has none), so rows
|
|
@@ -1157,8 +1152,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
1157
1152
|
// declared (repo-config depends_on) and derived (per-component sharedLibraries).
|
|
1158
1153
|
out.set("repo_dependency", await buildRepoDependencies({ sddPath, configs: reposResult.configs, knownRepoIds: knownRepoIdsFinal, findings }));
|
|
1159
1154
|
// 15. C4 systems + containers + components (L1 + L2 + L3). The L3 walker
|
|
1160
|
-
//
|
|
1161
|
-
// (method governance, 2026-05-29) — forward-method authoring path reading
|
|
1155
|
+
// is the forward-method authoring path reading
|
|
1162
1156
|
// `c4-l3-*.mermaid.md` files alongside the L2 layout. The earlier Path-B
|
|
1163
1157
|
// deferral assumed reverse-method code-walking; this walker is the
|
|
1164
1158
|
// forward-method analogue, granularity-agnostic (per-container or
|
|
@@ -1218,7 +1212,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
1218
1212
|
externalIdByEntryId: c4External.idByEntryId,
|
|
1219
1213
|
containerIdByEntryId,
|
|
1220
1214
|
}));
|
|
1221
|
-
// container_dependency member tier
|
|
1215
|
+
// container_dependency member tier: the C4-correct authoring —
|
|
1222
1216
|
// Rel(container_a, container_b) — emits its edge here; the aggregator's
|
|
1223
1217
|
// adjudication leg merges rather than being the only source.
|
|
1224
1218
|
out.set("container_dependency", await buildContainerDependency({
|
|
@@ -1228,7 +1222,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
1228
1222
|
containerIdByEntryId,
|
|
1229
1223
|
externalIdByEntryId: c4External.idByEntryId,
|
|
1230
1224
|
}));
|
|
1231
|
-
// persona_container per M-ADR 0189
|
|
1225
|
+
// persona_container per M-ADR 0189 (schema 2.34.0) — the
|
|
1232
1226
|
// L2-granularity companion to persona_c4system.
|
|
1233
1227
|
out.set("persona_container", await buildPersonaContainer({
|
|
1234
1228
|
sddPath,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// application_conformance_coverage builder (M-ADR 0191 §4.3 overlay
|
|
1
|
+
// application_conformance_coverage builder (M-ADR 0191 §4.3 overlay).
|
|
2
2
|
//
|
|
3
3
|
// Emits one row per conformance tag the product declares about its archetype's
|
|
4
4
|
// conformance expectations: `status=covered` (its suite exercises the tag) or
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// - docs/platform/diagrams/c4-l3-*.mermaid.md
|
|
5
5
|
// - docs/c4-l3-*.mermaid.md (per-product, application/integration SDDs)
|
|
6
6
|
//
|
|
7
|
-
// Mirrors c4-containers.ts at one level down
|
|
8
|
-
// `
|
|
9
|
-
// (method governance, 2026-05-29). The walker is granularity-agnostic — both
|
|
7
|
+
// Mirrors c4-containers.ts at one level down, reading the method's
|
|
8
|
+
// `c4-l3-*.mermaid.md` source convention. The walker is granularity-agnostic — both
|
|
10
9
|
// per-container files (one `Container_Boundary` block per file) and
|
|
11
10
|
// per-domain rollup files (multiple `Container_Boundary` blocks per file)
|
|
12
11
|
// produce identical row output. Adopters pick the file shape that suits
|
|
@@ -44,8 +43,8 @@ export async function buildC4Components(inputs) {
|
|
|
44
43
|
return [];
|
|
45
44
|
// Index containers by suffix-after-system-prefix for slug-of-name matching.
|
|
46
45
|
// c4_containers.id shape per c4-containers.ts:93 is `{systemId}-{slug}`.
|
|
47
|
-
// The suffix-match lets us resolve `Container_Boundary(_, "Asset
|
|
48
|
-
// (slug = "asset-
|
|
46
|
+
// The suffix-match lets us resolve `Container_Boundary(_, "Asset Register")`
|
|
47
|
+
// (slug = "asset-register") to `{anything}-asset-register`.
|
|
49
48
|
const containerBySuffix = new Map();
|
|
50
49
|
for (const row of inputs.c4ContainerRows) {
|
|
51
50
|
const id = String(row["id"] ?? "");
|
|
@@ -76,7 +76,7 @@ export async function buildC4Containers(inputs) {
|
|
|
76
76
|
}
|
|
77
77
|
const collapsed = rowById.get(id);
|
|
78
78
|
if (collapsed !== undefined) {
|
|
79
|
-
//
|
|
79
|
+
// Dedup-by-id is first-seen and was SILENT — two containers
|
|
80
80
|
// sharing a display name collapse into one row, and every edge
|
|
81
81
|
// referencing either binds to the merge. When their technology
|
|
82
82
|
// strings flatly contradict, that is two different things wearing
|
|
@@ -85,7 +85,7 @@ export async function buildC4Containers(inputs) {
|
|
|
85
85
|
inputs.findings.add("convention", `c4_containers: two containers share the display name "${entry.name}" with contradictory ` +
|
|
86
86
|
`technologies ("${String(collapsed["technology"])}" vs "${entry.technology}") — collapsed ` +
|
|
87
87
|
`into one row "${id}" — either different referents wearing one name (rename one) ` +
|
|
88
|
-
`or a misused technology field on a shared reference (correct it)
|
|
88
|
+
`or a misused technology field on a shared reference (correct it)`);
|
|
89
89
|
}
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
@@ -218,7 +218,7 @@ function slugify(s) {
|
|
|
218
218
|
.replace(/^-+|-+$/g, "");
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* Two technology strings flatly contradict when both are present and
|
|
222
222
|
* share no token — ".NET 6 (in Kestrel)" vs "Java" needs no NLP. Absent or
|
|
223
223
|
* overlapping strings are NOT a contradiction (silence, not a guess).
|
|
224
224
|
*/
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// c4_container_entity ⋈ c4_containers.system_id
|
|
11
11
|
// → aggregate (system_id, entity_id) with container_count.
|
|
12
12
|
//
|
|
13
|
-
// Both handle empty inputs gracefully (an adopter's
|
|
13
|
+
// Both handle empty inputs gracefully (an adopter's state before agreement has zero
|
|
14
14
|
// service_entity edges → both roll-up outputs are empty; no errors).
|
|
15
15
|
export function buildC4ContainerEntity(inputs) {
|
|
16
16
|
// Index containers by service_id.
|
|
@@ -134,7 +134,7 @@ function parseExternals(content) {
|
|
|
134
134
|
/**
|
|
135
135
|
* Every C4 diagram file an SDD can carry (all L1 candidates incl.
|
|
136
136
|
* domain-scoped, plus every L2), scope-tagged. Shared with
|
|
137
|
-
* system-external-dependency (
|
|
137
|
+
* system-external-dependency (that builder kept a private,
|
|
138
138
|
* pre-0.30.1-ordered copy MISSING the domain-scoped path — the fourth
|
|
139
139
|
* divergent copy of this list, and a silent drop inside adjudication:
|
|
140
140
|
* a Rel in a domain-scoped L1 was never harvested, so a converted
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Under federation a sibling SDD typically carries a syndicated copy of the
|
|
6
6
|
// platform-root's L1 at `docs/platform/diagrams/c4-l1-context.mermaid.md`
|
|
7
7
|
// for cross-reference, AND may author its own L1. The sibling's own L1 must
|
|
8
|
-
// outrank the syndicated copy — and
|
|
8
|
+
// outrank the syndicated copy — and the syndicated copy must
|
|
9
9
|
// not source a sibling's identity AT ALL: on two measured estates roughly
|
|
10
10
|
// half of all systems carried the platform's name and description because
|
|
11
11
|
// the fallback fired for every sibling that authored no own L1. A sibling
|
|
@@ -146,12 +146,12 @@ export async function buildCapabilities(inputs) {
|
|
|
146
146
|
if (specRel) {
|
|
147
147
|
usedSpecPaths.add(specRel);
|
|
148
148
|
const specAbs = join(inputs.sddPath, specRel);
|
|
149
|
-
//
|
|
149
|
+
// An emitted spec_path must exist — a dangling path silently
|
|
150
150
|
// produced an empty name/description while the column pointed at
|
|
151
151
|
// nothing. The row still emits (the capability is declared); the
|
|
152
152
|
// dangle is loud.
|
|
153
153
|
if (!(await fileExists(specAbs))) {
|
|
154
|
-
inputs.findings.add("coverage", `${m.relPath}: capability "${cap.id}" declares capability_spec "${specRel}" which does not exist — name/description left empty, spec_path emitted dangling
|
|
154
|
+
inputs.findings.add("coverage", `${m.relPath}: capability "${cap.id}" declares capability_spec "${specRel}" which does not exist — name/description left empty, spec_path emitted dangling`);
|
|
155
155
|
}
|
|
156
156
|
// M-ADR 0211 §4.2 — the manifest is a declaration and its row is correct. The finding
|
|
157
157
|
// fires ONLY on a CONTRADICTION between two declaration surfaces: this manifest
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// canonical schema declares capability_id and the target column as
|
|
6
6
|
// required FKs — so each builder skips entries whose target doesn't
|
|
7
7
|
// resolve in the known-target set passed in.
|
|
8
|
-
// capability_service is the exception (
|
|
8
|
+
// capability_service is the exception (schema 2.29.0): its service_id
|
|
9
9
|
// FK is external, and an entry marked `owner_sdd:` declares a cross-SDD
|
|
10
10
|
// reference — the edge is emitted despite not resolving locally and is
|
|
11
11
|
// validated hard at aggregation against the owning sibling's rows.
|
|
@@ -40,7 +40,7 @@ export function buildCapabilityService(inputs) {
|
|
|
40
40
|
if (!inputs.knownServiceIds.has(sid)) {
|
|
41
41
|
// Never drop silently (61 missing edges sat invisible in the field
|
|
42
42
|
// until a manual sweep). Resolution order:
|
|
43
|
-
// - `owner_sdd:` marked (
|
|
43
|
+
// - `owner_sdd:` marked (schema 2.29.0) — a DECLARED cross-SDD
|
|
44
44
|
// reference (e.g. the service moved to a sibling at a carve).
|
|
45
45
|
// Emit the edge; the external FK logs it and aggregation
|
|
46
46
|
// validates it hard against the sibling's rows.
|
|
@@ -174,7 +174,7 @@ export function buildCapabilityContract(inputs) {
|
|
|
174
174
|
const contractId = resolveContractPath(path, inputs.contractIdByPath);
|
|
175
175
|
if (contractId === "") {
|
|
176
176
|
// Never drop silently. This guard dropped every unresolvable entry with no
|
|
177
|
-
// trace until
|
|
177
|
+
// trace until it was measured: 62 of 188
|
|
178
178
|
// declared entries across two estates, and NOT ONE of them dangling — every
|
|
179
179
|
// one named a real artefact. So the resolution failure is the interesting
|
|
180
180
|
// fact, and the reader needs it.
|
|
@@ -236,7 +236,7 @@ function resolveContractPath(path, index) {
|
|
|
236
236
|
const direct = index.get(cleaned);
|
|
237
237
|
if (direct !== undefined)
|
|
238
238
|
return direct;
|
|
239
|
-
// Suffix: last two parts (e.g. "asset/asset-
|
|
239
|
+
// Suffix: last two parts (e.g. "asset/asset-register.yaml")
|
|
240
240
|
const parts = cleaned.split(sep);
|
|
241
241
|
if (parts.length >= 2) {
|
|
242
242
|
const suffix = parts.slice(-2).join("/");
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* | Feature | Implementation | Grounding Doc |
|
|
8
8
|
* |---|---|---|
|
|
9
|
-
* | `
|
|
9
|
+
* | `banners-module` | `banners` store module + `src/components/banners` | docs/.../feature-grounding.md |
|
|
10
10
|
*
|
|
11
11
|
* Path convention (per ADR 0145 §4.3):
|
|
12
12
|
* - platform-profile: `docs/domains/<dom>/product/<capability>.md`
|
|
@@ -67,8 +67,8 @@ export async function findCapabilitySpecFiles(sddPath) {
|
|
|
67
67
|
* Resolve the capability id from a capability spec file's absolute
|
|
68
68
|
* path. Returns the filename stem (`.md` stripped).
|
|
69
69
|
*
|
|
70
|
-
* docs/domains/
|
|
71
|
-
* → "
|
|
70
|
+
* docs/domains/storefront/product/banners-and-toasts.md
|
|
71
|
+
* → "banners-and-toasts"
|
|
72
72
|
*/
|
|
73
73
|
export function capabilityIdFromSpecPath(absPath, sddPath) {
|
|
74
74
|
const rel = relative(sddPath, absPath);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// container_dependency member-tier builder
|
|
2
|
-
//
|
|
1
|
+
// container_dependency member-tier builder (M-ADR 0189 §4.4 follow-on,
|
|
2
|
+
// schema 2.33.1).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Container conversion made the aggregator's adjudication leg the ONLY source of
|
|
5
5
|
// container_dependency rows — populated exactly when an author
|
|
6
6
|
// mis-declared an internal container as `System_Ext`. An author who wrote
|
|
7
7
|
// the C4-correct thing instead — `Rel(container_a, container_b)` between
|
|
8
8
|
// two `Container(...)` nodes — got no row and no finding: the metamodel
|
|
9
9
|
// rewarded the wrong authoring and silently discarded the right one,
|
|
10
|
-
// inverting the incentive
|
|
10
|
+
// inverting the incentive container conversion was built to create.
|
|
11
11
|
//
|
|
12
12
|
// This builder harvests intra-SDD container→container `Rel(...)` lines:
|
|
13
13
|
// both endpoints must resolve to local containers (via the entry-id map
|
|
14
14
|
// the c4-containers builder fills), neither may be external, and the
|
|
15
|
-
// syndicated platform L1 is skipped for non-root SDDs (
|
|
15
|
+
// syndicated platform L1 is skipped for non-root SDDs (its Rel
|
|
16
16
|
// lines are the platform's statements). The adjudication leg then MERGES
|
|
17
17
|
// into a catalogue that already has content, rather than being its only
|
|
18
18
|
// source; dedup is by id, member rows first.
|
|
@@ -34,7 +34,7 @@ export async function buildContainerDependency(inputs) {
|
|
|
34
34
|
if (syndicated && !isRoot)
|
|
35
35
|
continue;
|
|
36
36
|
const content = await readFile(abs, "utf-8");
|
|
37
|
-
//
|
|
37
|
+
// Per-file Person entries — the endpoint class that has no edge
|
|
38
38
|
// catalogue to a container.
|
|
39
39
|
const personEntries = new Set();
|
|
40
40
|
for (const raw of content.split(/\r?\n/)) {
|
|
@@ -47,7 +47,7 @@ export async function buildContainerDependency(inputs) {
|
|
|
47
47
|
if (!m)
|
|
48
48
|
continue;
|
|
49
49
|
const [, fromEntry, toEntry, label] = m;
|
|
50
|
-
//
|
|
50
|
+
// Resolve the container FIRST. Both maps are SDD-wide, and an
|
|
51
51
|
// entry-id that resolves to a local container IS a container —
|
|
52
52
|
// whatever a sibling diagram in the same SDD called it. The old
|
|
53
53
|
// external-guard-first order silently discarded a true edge whenever
|
|
@@ -60,10 +60,10 @@ export async function buildContainerDependency(inputs) {
|
|
|
60
60
|
const from = inputs.containerIdByEntryId.get(fromEntry ?? "");
|
|
61
61
|
const to = inputs.containerIdByEntryId.get(toEntry ?? "");
|
|
62
62
|
if (from === undefined || to === undefined) {
|
|
63
|
-
//
|
|
64
|
-
// — following
|
|
63
|
+
// A Rel the builder RESOLVES but cannot catalogue must say so
|
|
64
|
+
// — following the rename prescription turned exactly this
|
|
65
65
|
// silence into a two-edge delete. Classification is
|
|
66
|
-
// container-first
|
|
66
|
+
// container-first, then external, then Person (file-local);
|
|
67
67
|
// anything unknown stays silent (no confident classification, no
|
|
68
68
|
// guess). Pairs with a home elsewhere (one-external →
|
|
69
69
|
// system_external_dependency; person→system → persona_c4system)
|
|
@@ -83,7 +83,7 @@ export async function buildContainerDependency(inputs) {
|
|
|
83
83
|
// resolve) — only the pairs with NO home remain findings here.
|
|
84
84
|
if (pair === "external→external") {
|
|
85
85
|
inputs.findings.add("convention", `container_dependency: Rel(${fromEntry}, ${toEntry}${label ? `, "${label}"` : ""}) resolves as ` +
|
|
86
|
-
`${pair} — no catalogue holds that pair; the relationship is recorded nowhere
|
|
86
|
+
`${pair} — no catalogue holds that pair; the relationship is recorded nowhere`);
|
|
87
87
|
}
|
|
88
88
|
continue;
|
|
89
89
|
}
|
|
@@ -110,8 +110,8 @@ export async function buildContainerDependency(inputs) {
|
|
|
110
110
|
return rows;
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
* channel, never the one under guard.
|
|
113
|
+
* Referent-collision check — corroborate on an INDEPENDENT evidence
|
|
114
|
+
* channel, never the one under guard. An earlier version decided referent
|
|
115
115
|
* identity by comparing display names — the same evidence class the guard
|
|
116
116
|
* existed to distrust — so it was circular: the estate sweep that declared
|
|
117
117
|
* different-referent collisions absent USED this predicate and could not
|
|
@@ -144,18 +144,17 @@ export function reportEntryIdReferentCollisions(inputs) {
|
|
|
144
144
|
if (externalTech !== "" && techContradicts(containerTech, externalTech)) {
|
|
145
145
|
inputs.findings.add("convention", `c4 diagrams: entry-id "${entryId}" is declared Container ("${containerName}", "${containerTech}") and ` +
|
|
146
146
|
`System_Ext ("${externalName}", "${externalTech}") with contradictory technologies — ` +
|
|
147
|
-
`different referents wearing one id; the container interpretation wins for Rel edges — rename one entry-id to split them
|
|
147
|
+
`different referents wearing one id; the container interpretation wins for Rel edges — rename one entry-id to split them`);
|
|
148
148
|
continue;
|
|
149
149
|
}
|
|
150
150
|
if (externalTech !== "")
|
|
151
151
|
continue; // corroborating tech — same referent, silent
|
|
152
|
-
//
|
|
152
|
+
// System_Ext carries no technology argument, so the primary
|
|
153
153
|
// dual-use case has NO independent evidence channel — measured: 0 of
|
|
154
154
|
// 109 external rows carry a technology. Weak corroboration is
|
|
155
155
|
// accepted (the external's description mentioning a container-tech
|
|
156
156
|
// token); otherwise the guard says what it cannot do, loudly. A
|
|
157
|
-
// dual-use entry-id buys nothing
|
|
158
|
-
// for Rel edges) and risks a silent different-referent bind — the
|
|
157
|
+
// dual-use entry-id buys nothing once the container is resolved first for Rel edges and risks a silent different-referent bind — the
|
|
159
158
|
// retirement is a rename, cheap because entry-ids are diagram-local
|
|
160
159
|
// and row identity derives from the display name.
|
|
161
160
|
const externalDesc = inputs.externalDescByEntryId.get(entryId) ?? "";
|
|
@@ -175,9 +174,9 @@ export function reportEntryIdReferentCollisions(inputs) {
|
|
|
175
174
|
`("${externalName}") with no independent evidence to adjudicate referent identity — System_Ext ` +
|
|
176
175
|
`carries no technology field; cannot corroborate, cannot contradict. Rename one entry-id to split ` +
|
|
177
176
|
`the namespaces — but check first whether the dual use is load-bearing: if Rel edges exist only via ` +
|
|
178
|
-
`the Container reading, a rename orphans them (they will surface as
|
|
177
|
+
`the Container reading, a rename orphans them (they will surface as unroutable-Rel findings); in that case ` +
|
|
179
178
|
`annotate the System_Ext description with the technology instead, so weak corroboration can silence ` +
|
|
180
|
-
`this honestly
|
|
179
|
+
`this honestly`);
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
//# sourceMappingURL=container-dependency.js.map
|
|
@@ -27,12 +27,11 @@ const KIND_SPECS = [
|
|
|
27
27
|
{ subdir: "avro", extensions: [".avsc"], kind: "event" },
|
|
28
28
|
// `authz` — authorization/policy schemas (SpiceDB `.zed` and equivalents).
|
|
29
29
|
//
|
|
30
|
-
// Added because
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// layer down.
|
|
30
|
+
// Added because a contract agreement gate can approve `.zed` schemas as
|
|
31
|
+
// contracts while `kind` had no member able to represent them — so the
|
|
32
|
+
// catalogue could not hold an artefact the method's own gate had certified.
|
|
33
|
+
// Such a gate records the tension itself, passing the schema with a waiver on
|
|
34
|
+
// rubric-fit grounds; this closes the same gap one layer down.
|
|
36
35
|
//
|
|
37
36
|
// Zero-delta when added: no estate holds an in-repo `contracts/spicedb/` tree,
|
|
38
37
|
// so this discovers nothing today and exists so that a declared or migrated
|
|
@@ -90,8 +90,8 @@ const PERSISTENCE_KIND_RULES = [
|
|
|
90
90
|
"event-payload",
|
|
91
91
|
],
|
|
92
92
|
// Transient / in-memory state (not a system of record) — the schema's
|
|
93
|
-
// `dto` classification.
|
|
94
|
-
// "Transient (API response)", "Runtime (
|
|
93
|
+
// `dto` classification. Typical values: "In-memory (client store)",
|
|
94
|
+
// "Transient (API response)", "Runtime (framework plugin)".
|
|
95
95
|
[/\b(in-memory|transient|ephemeral|runtime)\b/, "dto"],
|
|
96
96
|
];
|
|
97
97
|
/** Cells that mean "no store", treated as empty. */
|
|
@@ -104,7 +104,7 @@ export function derivePersistenceKind(store) {
|
|
|
104
104
|
if (re.test(s))
|
|
105
105
|
return { kind, recognised: true };
|
|
106
106
|
}
|
|
107
|
-
// Present but unmapped (e.g. Elasticsearch, in-memory
|
|
107
|
+
// Present but unmapped (e.g. Elasticsearch, in-memory client store) → a valid
|
|
108
108
|
// recommended value, but flagged so the rule set can grow.
|
|
109
109
|
return { kind: "other", recognised: false };
|
|
110
110
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// One edge per node pair is the metamodel's rule (schema-stated since
|
|
2
2
|
// 2.30.0) and it stands. The defect was the SILENCE: distinct parallel
|
|
3
3
|
// labels merged first-seen-wins at member and aggregate tier, discarding
|
|
4
4
|
// authored facts without a word (measured: 20 labels already lost before
|
|
@@ -14,6 +14,6 @@ export function mergeEdgeLabel(row, label, catalogue, findings) {
|
|
|
14
14
|
row["interaction"] =
|
|
15
15
|
existing === "" ? label : `${existing}; ${label}`;
|
|
16
16
|
findings.add("convention", `${catalogue}: edge "${String(row["id"])}" is declared with multiple distinct labels — ` +
|
|
17
|
-
`one edge per node pair (schema rule); labels concatenated, nothing dropped
|
|
17
|
+
`one edge per node pair (schema rule); labels concatenated, nothing dropped`);
|
|
18
18
|
}
|
|
19
19
|
//# sourceMappingURL=edge-label-merge.js.map
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// at aggregation. Profile-gating mirrors the precedent set by
|
|
14
14
|
// domain-models.ts per ADR 0142 (same scope, same syndication shape).
|
|
15
15
|
//
|
|
16
|
-
// Behaviour (
|
|
16
|
+
// Behaviour (ADR 0159 acceptance):
|
|
17
17
|
// - For each models[] entry, derive domain_id from its `domain` field.
|
|
18
18
|
// `platform-wide` and other non-canonical hints map to empty domain_id
|
|
19
19
|
// and the entity PK uses the `_platform-wide-{slug}` sentinel prefix
|
|
@@ -47,7 +47,7 @@ export async function buildEntitiesFromMermaid(inputs) {
|
|
|
47
47
|
// entity extracted from this file inherits it; absence → "".
|
|
48
48
|
const fileEvidenceClass = extractFileEvidenceClass(text);
|
|
49
49
|
// Within-file dedup by id is enforced inside parseFile via seen-set;
|
|
50
|
-
// cross-file dedup is enforced here (
|
|
50
|
+
// cross-file dedup is enforced here (not expected to fire in a one-file-per-domain estate,
|
|
51
51
|
// defensive against future authoring that splits a domain across files).
|
|
52
52
|
for (const p of parsed) {
|
|
53
53
|
const id = `${p.domainId}-${entitySlug(p.name)}`;
|
|
@@ -169,8 +169,8 @@ function resolveGlossaryTermBySlug(entityName, domainId, knownGlossaryIds) {
|
|
|
169
169
|
return id;
|
|
170
170
|
}
|
|
171
171
|
// ADR 0165 follow-on: the glossary builder collapses multi-word term slugs
|
|
172
|
-
// (
|
|
173
|
-
// hyphenates ("
|
|
172
|
+
// (BASKET_MESSAGE → "...-basketmessage"), while entitySlug
|
|
173
|
+
// hyphenates ("basket-message"), so multi-word terms miss the suffix
|
|
174
174
|
// match above. Reconcile boundary-insensitively (compare hyphen-stripped
|
|
175
175
|
// forms), scoped to the entity's OWN domain to avoid cross-domain
|
|
176
176
|
// false positives.
|
|
@@ -7,18 +7,17 @@
|
|
|
7
7
|
// authoring resolves to a single canonical slug.
|
|
8
8
|
//
|
|
9
9
|
// Examples:
|
|
10
|
-
// entitySlug("
|
|
11
|
-
// entitySlug("
|
|
10
|
+
// entitySlug("POSTerminalConfig") → "pos-terminal-config"
|
|
11
|
+
// entitySlug("POS_TERMINAL_CONFIG") → "pos-terminal-config"
|
|
12
12
|
// entitySlug("Vehicle") → "vehicle"
|
|
13
|
-
// entitySlug("
|
|
13
|
+
// entitySlug("GTINBarcodeRecord") → "gtin-barcode-record"
|
|
14
14
|
// entitySlug("customer") → "customer" (lowercase Mermaid)
|
|
15
|
-
// entitySlug("
|
|
15
|
+
// entitySlug("basketItems") → "basket-items"
|
|
16
16
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// (`
|
|
20
|
-
//
|
|
21
|
-
// edges in Adopter A.
|
|
17
|
+
// Measured finding: without the ALLCAPS-then-Pascal rule, the Data Store
|
|
18
|
+
// Mapping table form (`POSTerminalConfig`) never resolves to the Mermaid form
|
|
19
|
+
// (`POS_TERMINAL_CONFIG`), breaking ~50% of expected service_entity
|
|
20
|
+
// edges in one reference estate.
|
|
22
21
|
//
|
|
23
22
|
// Acronym-run rule (ADR 0164 wave-2 follow-on): the first replace requires the
|
|
24
23
|
// leading uppercase run to be >=2 chars (`[A-Z]{2,}`). A *single* leading
|
|
@@ -29,9 +28,9 @@
|
|
|
29
28
|
// Before the >=2 rule these produced "o-auth-client" vs "oauth-client".
|
|
30
29
|
// Verified zero PK churn across both reference adopters (263 + 414 entities).
|
|
31
30
|
// This cannot reconcile names whose PascalCase and UPPER_SNAKE forms genuinely
|
|
32
|
-
// disagree on word boundaries — e.g. "
|
|
33
|
-
// -> "
|
|
34
|
-
// aligning the spellings (author `
|
|
31
|
+
// disagree on word boundaries — e.g. "KeyHub" (two words) vs "KEYHUB" (one)
|
|
32
|
+
// -> "key-hub" vs "keyhub". That is adopter authoring drift, resolved by
|
|
33
|
+
// aligning the spellings (author `KEY_HUB`), not by this normaliser.
|
|
35
34
|
//
|
|
36
35
|
// Kept local to the entity walkers to avoid affecting events / contracts /
|
|
37
36
|
// capability id generation. Promote to a shared module only if a fourth
|
|
@@ -80,8 +80,8 @@ export async function walkEventCatalogs(inputs) {
|
|
|
80
80
|
const explicitId = String(e["id"] ?? "").trim();
|
|
81
81
|
const name = String(e["name"] ?? "").trim();
|
|
82
82
|
// Derive id: explicit id wins when present (allows future override);
|
|
83
|
-
// otherwise slug from name
|
|
84
|
-
//
|
|
83
|
+
// otherwise slug from name, per ADR 0156: "derive `id` from `name`
|
|
84
|
+
// (slugged)". Some adopters' authoring uses
|
|
85
85
|
// `name:` only (no `id:` field).
|
|
86
86
|
let id;
|
|
87
87
|
if (explicitId !== "") {
|
|
@@ -141,7 +141,7 @@ export async function walkEventCatalogs(inputs) {
|
|
|
141
141
|
/**
|
|
142
142
|
* Slugify an event name into an id.
|
|
143
143
|
*
|
|
144
|
-
* - Inserts a hyphen at camelCase boundaries (`
|
|
144
|
+
* - Inserts a hyphen at camelCase boundaries (`AssetRegister` → `Asset-Register`).
|
|
145
145
|
* - Lowercases.
|
|
146
146
|
* - Replaces any run of non-alphanumeric characters with a single hyphen.
|
|
147
147
|
* - Trims leading/trailing hyphens.
|