@sdd-method/maintain-cli 0.3.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/briefing/render.js +1 -2
- package/dist/lib/briefing/variables.js +1 -2
- package/dist/lib/catalogue/aggregate.js +35 -36
- package/dist/lib/catalogue/application-archetype/expectations.js +1 -1
- 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-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/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/contract-path-resolution.js +3 -3
- 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/ci-binding/index.js +106 -33
- package/dist/lib/ci-binding/module.js +1 -1
- package/dist/lib/lock-file.js +1 -1
- package/dist/lib/pack/reference-closure.js +3 -3
- package/dist/lib/package-version.js +1 -1
- package/dist/lib/snapshot/manifest.js +1 -1
- package/dist/lib/sync/adr-transitions.js +1 -1
- package/dist/lib/sync/diff.js +1 -1
- package/dist/lib/sync/index.js +8 -3
- package/dist/lib/validate/index.js +1 -1
- package/dist/lib/validate/plan.js +1 -1
- package/dist/lib/validate/yaml-parse.js +1 -1
- package/dist/maintain-version.json +2 -2
- package/dist/verbs/sync.js +1 -1
- package/dist/verify-version.json +2 -2
- package/package.json +2 -2
|
@@ -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 lock-in 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.
|
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
// pairs are deduplicated with a `[finding] inferred` log line recording that
|
|
8
8
|
// both paths produced the same edge.
|
|
9
9
|
//
|
|
10
|
-
// Added in schema 2.3.0 — see catalogue-metamodel.md §"Edge catalogue status"
|
|
11
|
-
// and the companion plan
|
|
12
|
-
// `content/plans/document-non-catalogued-artefacts-and-add-feature-contract.md`.
|
|
10
|
+
// Added in schema 2.3.0 — see catalogue-metamodel.md §"Edge catalogue status".
|
|
13
11
|
export function buildFeatureContract(inputs) {
|
|
14
12
|
const seen = new Map();
|
|
15
13
|
// Path 1 (forward): capability_feature ⋈ capability_contract on capability_id.
|
|
@@ -107,8 +107,7 @@ export async function buildFeatureGrounding(inputs) {
|
|
|
107
107
|
// feature_repo and feature_service edges: emit unconditionally per
|
|
108
108
|
// ADR 0143 §2 (these are the source's unique contribution; nothing
|
|
109
109
|
// else carries per-row code-mapping). Pre-0.30.2 the collision skip
|
|
110
|
-
// dropped these edges too — bug fixed
|
|
111
|
-
// `coordination/feature-grounding-collision-edges-coordination.md`.
|
|
110
|
+
// dropped these edges too — bug fixed.
|
|
112
111
|
if (featureRowCollides) {
|
|
113
112
|
// ONE CHECK, TWO MEANINGS — split them, because they are not the same
|
|
114
113
|
// event and only one of them is a defect.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// FeatureGroups discoverer — emits `feature_groups.csv` rows from
|
|
2
2
|
// hierarchical `feature-tree.md` files.
|
|
3
3
|
//
|
|
4
|
-
// Per Q7.2 resolution (adopt-canonical-catalogue-builder.md §7.2),
|
|
5
4
|
// FeatureGroups are first-class entities. This builder consumes the
|
|
6
5
|
// shared `feature-tree-walker` and emits one row per H2/H3 heading
|
|
7
6
|
// outside fenced code blocks.
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
// `subscription_channels:` entry in any `reverse-evidence/feature-grounding-evidence.yaml`
|
|
3
3
|
// file in the SDD. Added in schema 2.9.0.
|
|
4
4
|
//
|
|
5
|
-
// Per
|
|
6
|
-
//
|
|
7
|
-
// per-feature slot vocabulary): `subscription_channels:` is a peer of
|
|
5
|
+
// Per the per-feature slot vocabulary in
|
|
6
|
+
// `docs/method/methods/reverse/rubrics/reverse-application-evidence-quality.md`: `subscription_channels:` is a peer of
|
|
8
7
|
// `graphql_operations:` in the per-feature evidence shape — `graphql_operations:`
|
|
9
8
|
// carries the operation names declared in the GraphQL schema; this builder
|
|
10
9
|
// captures the channel/topic layer (AppSync WebSocket, Pekko WS, MQTT, SSE,
|
|
@@ -162,7 +162,7 @@ const SERVICE_ID_RE = /^[a-z0-9][a-z0-9_-]*$/;
|
|
|
162
162
|
/**
|
|
163
163
|
* Parse the `## Feature Services` section of a `feature-tree.md` body (the
|
|
164
164
|
* application-product authoring surface for `feature_service`, per M-ADR
|
|
165
|
-
* 0157
|
|
165
|
+
* 0157). Table shape:
|
|
166
166
|
*
|
|
167
167
|
* | Feature | Services |
|
|
168
168
|
* |---------|----------|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// Folder→capability mapping uses (a) the traceability manifest's
|
|
5
5
|
// `capability.feature_directory` field — authoritative when a
|
|
6
6
|
// capability spec uses a folder name that differs from the
|
|
7
|
-
// canonical capability_id (e.g. capability_id "
|
|
8
|
-
// with feature_directory pointing at "features/
|
|
7
|
+
// canonical capability_id (e.g. capability_id "gift-card-rules-engine"
|
|
8
|
+
// with feature_directory pointing at "features/gift-card-rules/") —
|
|
9
9
|
// then (b) the folder name itself when it equals a known capability_id.
|
|
10
10
|
// id format: `{capability_id}-{feature-slug}`.
|
|
11
11
|
//
|
|
@@ -26,8 +26,7 @@
|
|
|
26
26
|
// then walker (2) (standalone specs), then walker (3) (bullets). When
|
|
27
27
|
// a bullet's id is already in `seen` from a standalone spec the spec
|
|
28
28
|
// wins — divergence finding fires for the bullet. This matches the
|
|
29
|
-
// "standalone is more authoritative than bullet inventory" rule
|
|
30
|
-
// plan extract-feature-tree-bullets.md §1.4.
|
|
29
|
+
// "standalone is more authoritative than bullet inventory" rule.
|
|
31
30
|
import { readdir, readFile } from "node:fs/promises";
|
|
32
31
|
import { basename, join, relative, sep } from "node:path";
|
|
33
32
|
import { featureGroupId, findFeatureTreeFile, walkFeatureTree, } from "./feature-tree-walker.js";
|
|
@@ -9,19 +9,19 @@
|
|
|
9
9
|
// are skipped (no dangling FK). The optional interaction label comes from a
|
|
10
10
|
// `Rel(person, system, "label")` line.
|
|
11
11
|
//
|
|
12
|
-
// `Person_Ext(...)` is discriminated by EVIDENCE, not notation (
|
|
13
|
-
//
|
|
12
|
+
// `Person_Ext(...)` is discriminated by EVIDENCE, not notation (a
|
|
13
|
+
// discriminator keyed on notation breaks the external-human case): the
|
|
14
14
|
// notation is idiomatic both for a non-human external actor (a device) and
|
|
15
15
|
// for a human legitimately outside the boundary. Whether a persona row
|
|
16
16
|
// resolves is the declaration of humanity — a Person_Ext that resolves
|
|
17
17
|
// emits its edge; one that doesn't is silent (a device is not an authoring
|
|
18
|
-
// gap, and a typo'd human still surfaces through
|
|
18
|
+
// gap, and a typo'd human still surfaces through the reverse finding on
|
|
19
19
|
// the edgeless persona row).
|
|
20
20
|
//
|
|
21
21
|
// `Person(...)` asserts a human, so an unresolved one stays a forward
|
|
22
22
|
// coverage finding.
|
|
23
23
|
//
|
|
24
|
-
// Both directions report (
|
|
24
|
+
// Both directions report (M-ADR 0157 parity + M-ADR 0133 §4.2c
|
|
25
25
|
// no-silent-drop): a Person with no persona row is a coverage finding
|
|
26
26
|
// (forward), and a persona with no edge is a coverage finding stating WHY —
|
|
27
27
|
// no system row, no identity-sourcing L1, no Person actors, or no actor
|
|
@@ -31,7 +31,7 @@ import { readFile } from "node:fs/promises";
|
|
|
31
31
|
import { join } from "node:path";
|
|
32
32
|
import { resolveIdentityL1 } from "./c4-l1-candidates.js";
|
|
33
33
|
// `Person(id, "name", "desc")` / `Person_Ext(id, "name", "desc")` — the
|
|
34
|
-
// _Ext capture drives the evidence discriminator (
|
|
34
|
+
// _Ext capture drives the evidence discriminator (see header).
|
|
35
35
|
const PERSON_RE = /Person(_Ext)?\s*\(\s*(\w+)\s*,\s*"([^"]+)"\s*(?:,\s*"([^"]*)")?\s*\)/;
|
|
36
36
|
const REL_RE = /^(?:Bi)?Rel(?:_[A-Za-z]+)?\s*\(\s*(\w+)\s*,\s*(\w+)\s*(?:,\s*"([^"]*)")?/;
|
|
37
37
|
export async function buildPersonaC4System(inputs) {
|
|
@@ -65,7 +65,7 @@ export async function buildPersonaC4System(inputs) {
|
|
|
65
65
|
if (personaId === undefined) {
|
|
66
66
|
// Person_Ext with no persona row: a device or other non-human external
|
|
67
67
|
// actor — silence is correct, and a typo'd human surfaces through the
|
|
68
|
-
// reverse finding on its edgeless persona row
|
|
68
|
+
// reverse finding on its edgeless persona row.
|
|
69
69
|
if (person.external)
|
|
70
70
|
continue;
|
|
71
71
|
inputs.findings.add("coverage", `persona_c4system: Person "${person.name}" on the L1 diagram has no matching persona row — edge skipped`);
|
|
@@ -87,7 +87,7 @@ export async function buildPersonaC4System(inputs) {
|
|
|
87
87
|
rows.sort((a, b) => String(a["id"]).localeCompare(String(b["id"])));
|
|
88
88
|
return rows;
|
|
89
89
|
}
|
|
90
|
-
//
|
|
90
|
+
// The reverse direction (M-ADR 0157 parity): every persona that ends
|
|
91
91
|
// the build without an edge is reported, with the reason. A persona with
|
|
92
92
|
// neither an edge nor a finding is indistinguishable from one that was
|
|
93
93
|
// never considered.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
// persona_container edge builder — M-ADR 0189
|
|
1
|
+
// persona_container edge builder — M-ADR 0189 (schema 2.34.0).
|
|
2
2
|
//
|
|
3
3
|
// The L2-granularity companion to persona_c4system: a Rel(...) between a
|
|
4
|
-
// Person and a container had
|
|
4
|
+
// Person and a container had neither a catalogue nor a finding —
|
|
5
5
|
// eleven of one estate's L2s and the bulk of another's 81 unroutable Rels
|
|
6
|
-
// were exactly this class.
|
|
6
|
+
// were exactly this class. The unroutable-Rel finding made the silence loud; this gives the
|
|
7
7
|
// loudness a destination.
|
|
8
8
|
//
|
|
9
9
|
// Person entries are file-local (a Person declared in the same diagram as
|
|
10
|
-
// the Rel); containers resolve SDD-wide (
|
|
10
|
+
// the Rel); containers resolve SDD-wide (container-first). Either Rel direction
|
|
11
11
|
// normalises to persona→container. A Person that matches no persona row is
|
|
12
12
|
// a coverage finding, never a silent skip (the persona_c4system precedent).
|
|
13
|
-
// The syndicated platform L1 is skipped for non-root SDDs
|
|
14
|
-
// labels concatenate loudly
|
|
13
|
+
// The syndicated platform L1 is skipped for non-root SDDs; parallel
|
|
14
|
+
// labels concatenate loudly.
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
16
16
|
import { collectC4DiagramFiles } from "./c4-external-systems.js";
|
|
17
17
|
import { mergeEdgeLabel } from "./edge-label-merge.js";
|
|
@@ -37,11 +37,11 @@ export async function buildPersonaContainer(inputs) {
|
|
|
37
37
|
}
|
|
38
38
|
if (personNameByEntry.size === 0)
|
|
39
39
|
continue;
|
|
40
|
-
//
|
|
40
|
+
// Only the SDD's OWN system entry is persona_c4system's domain —
|
|
41
41
|
// that catalogue emits one edge per persona to THE system, nothing
|
|
42
42
|
// else. The 0.89.0 skip silenced person Rels to ANY System(...) entry,
|
|
43
43
|
// a silence delegated to a catalogue that never held those edges
|
|
44
|
-
// (
|
|
44
|
+
// (a delegated silence one tier over): four of an estate's best-authored
|
|
45
45
|
// persona edges vanished, while the same file's person→external edge
|
|
46
46
|
// was announced. Own entry = the first System(...) line, mirroring
|
|
47
47
|
// c4-systems' parseSystemLine; every other target takes exits 1/3/4.
|
|
@@ -58,17 +58,17 @@ export async function buildPersonaContainer(inputs) {
|
|
|
58
58
|
if (!m)
|
|
59
59
|
continue;
|
|
60
60
|
const [, fromEntry, toEntry, label] = m;
|
|
61
|
-
//
|
|
61
|
+
// The person tier's exits, designed once. Identify the
|
|
62
62
|
// person endpoint (file-local; container resolution wins on a
|
|
63
|
-
// dual-use id
|
|
63
|
+
// dual-use id), then route by the OTHER endpoint's kind:
|
|
64
64
|
// local container → persona_container row
|
|
65
65
|
// own System(...) → persona_c4system's domain; silent here
|
|
66
|
-
// external → announced skip
|
|
66
|
+
// external → announced skip: a human using an
|
|
67
67
|
// external system has no persona-tier
|
|
68
68
|
// catalogue YET — it must never transmute
|
|
69
69
|
// into sed/ssd, whose semantics are runtime
|
|
70
70
|
// calls between software
|
|
71
|
-
// anything else → announced skip
|
|
71
|
+
// anything else → announced skip: claimed by no
|
|
72
72
|
// catalogue is said, never silent
|
|
73
73
|
const fromIsContainer = inputs.containerIdByEntryId.has(fromEntry ?? "");
|
|
74
74
|
const toIsContainer = inputs.containerIdByEntryId.has(toEntry ?? "");
|
|
@@ -100,8 +100,8 @@ export async function buildPersonaContainer(inputs) {
|
|
|
100
100
|
if (targetContainer === undefined) {
|
|
101
101
|
const isExternal = inputs.externalIdByEntryId?.has(targetEntry ?? "") ?? false;
|
|
102
102
|
inputs.findings.add("coverage", isExternal
|
|
103
|
-
? `persona_container: persona "${personaId}" → external "${inputs.externalIdByEntryId.get(targetEntry ?? "")}"${label ? ` ("${label}")` : ""} — a human using an external system has no persona-tier catalogue; skipped, never transmuted into a system edge
|
|
104
|
-
: `persona_container: persona "${personaId}" → "${targetEntry}"${label ? ` ("${label}")` : ""} resolves to no container — claimed by no catalogue; skipped
|
|
103
|
+
? `persona_container: persona "${personaId}" → external "${inputs.externalIdByEntryId.get(targetEntry ?? "")}"${label ? ` ("${label}")` : ""} — a human using an external system has no persona-tier catalogue; skipped, never transmuted into a system edge`
|
|
104
|
+
: `persona_container: persona "${personaId}" → "${targetEntry}"${label ? ` ("${label}")` : ""} resolves to no container — claimed by no catalogue; skipped`);
|
|
105
105
|
continue;
|
|
106
106
|
}
|
|
107
107
|
const id = `${personaId}__${targetContainer}`;
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
// ## Summary
|
|
10
10
|
// {1-2 sentences}
|
|
11
11
|
//
|
|
12
|
-
// Cross-profile path
|
|
13
|
-
// coordination.md, 2026-05-29): the walker reads `personas/` at SDD root
|
|
12
|
+
// Cross-profile path: the walker reads `personas/` at SDD root
|
|
14
13
|
// for every profile. Each SDD's personas/ carries personas at that SDD's
|
|
15
14
|
// scope — for application-product that's app-specific personas, for
|
|
16
15
|
// platform-product that's platform-wide / cross-app personas surfaced as
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
// The aggregator subsequently joins synchronous rows against platform-side
|
|
33
33
|
// `capability_service` to derive `cross_sdd_platform_capability` edges (per
|
|
34
34
|
// ADR 0134, federation); async rows resolve via the capability leg (no
|
|
35
|
-
// realiser service to join on) and skip the
|
|
35
|
+
// realiser service to join on) and skip the realiser-join.
|
|
36
36
|
//
|
|
37
37
|
// Cross-SDD references (target_capability_id, target_service_id) are
|
|
38
38
|
// declared `external: true` in the schema — per-SDD FK validation
|
|
@@ -62,7 +62,7 @@ const FORWARD_REL_PATH = "app-platform-dependencies.yaml";
|
|
|
62
62
|
* Contract types that denote an ASYNC shared-stream dependency (M-ADR 0192).
|
|
63
63
|
* A dependency of one of these kinds binds to a stream contract published by a
|
|
64
64
|
* capability, not to a synchronous realiser service — so it characteristically
|
|
65
|
-
* has no `service_id`, and the
|
|
65
|
+
* has no `service_id`, and the realiser-join does not apply to it at
|
|
66
66
|
* aggregation. Kept lowercase; callers normalise before testing.
|
|
67
67
|
*/
|
|
68
68
|
export const ASYNC_CONTRACT_TYPES = new Set([
|
|
@@ -468,7 +468,7 @@ export async function buildQualityScores(inputs) {
|
|
|
468
468
|
}
|
|
469
469
|
// Flat headline keys beside a `current:` block are a compatibility
|
|
470
470
|
// shim, and a shim that DISAGREES with the effective record is the
|
|
471
|
-
// silent-failure shape found live
|
|
471
|
+
// silent-failure shape found live: an ingestion updated
|
|
472
472
|
// the flat score and left `current:` stale, and the catalogue published
|
|
473
473
|
// the stale value with the right one inches away in the same entry —
|
|
474
474
|
// no complaint anywhere. The disagreement is loud now. scored_at is
|
|
@@ -83,7 +83,7 @@ const ServiceCapabilityExemptionSchema = z
|
|
|
83
83
|
const ServicesBlockSchema = z.unknown().optional();
|
|
84
84
|
// `additional_context` accepts strings OR loosely-parsed key:value
|
|
85
85
|
// objects (which arise when adopters leave colons unquoted in their
|
|
86
|
-
// YAML — e.g. `- docs/.../ADR 0017: Use
|
|
86
|
+
// YAML — e.g. `- docs/.../ADR 0017: Use Acme IdP for Auth.md`). The
|
|
87
87
|
// canonical builder reconstructs the original string downstream.
|
|
88
88
|
const RepoConfigSchema = z.object({
|
|
89
89
|
repo: z.string(),
|
|
@@ -117,7 +117,7 @@ const RepoConfigSchema = z.object({
|
|
|
117
117
|
* Reconstruct a string from a possibly-mis-parsed YAML list entry.
|
|
118
118
|
*
|
|
119
119
|
* Adopters sometimes write list entries with unquoted colons like:
|
|
120
|
-
* - docs/.../ADR 0017: Use
|
|
120
|
+
* - docs/.../ADR 0017: Use Acme IdP for Auth.md
|
|
121
121
|
*
|
|
122
122
|
* The YAML parser reads each such entry as a single-key object whose key
|
|
123
123
|
* is the part before the first colon and whose value is the part after.
|
|
@@ -112,7 +112,7 @@ function parseStatements(cleanBlock) {
|
|
|
112
112
|
const id = m[1];
|
|
113
113
|
const rhs = m[2];
|
|
114
114
|
// A component statement's RHS is a call (`Text("Q4")`) or a reference — never a
|
|
115
|
-
// bare string literal. A line like `action="
|
|
115
|
+
// bare string literal. A line like `action="basket-updated"` (the block-level
|
|
116
116
|
// screen_action messaging contract, ADR 0166) matches the statement shape with
|
|
117
117
|
// id=`action`; skip it so it isn't lexed as a phantom component (and so a node
|
|
118
118
|
// carrying an `action=` attr doesn't pick it up as a spurious child).
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
// `action="..."` declarations -> screen_action. The frontmatter supplies the
|
|
24
24
|
// catalogue keys + FK lists. This is the forward / authored path; the reverse
|
|
25
25
|
// toolkit projects an app's router + component tree into the same shape (the
|
|
26
|
-
// upstream step that produces these artefacts
|
|
27
|
-
// research/adr-0166-prototypes/ for the Phase Pre validation).
|
|
26
|
+
// upstream step that produces these artefacts).
|
|
28
27
|
//
|
|
29
28
|
// Per ADR 0135 the rows are identical regardless of authoring direction; only
|
|
30
29
|
// the `source` discriminator differs (screen-spec vs screen-extraction).
|
|
@@ -305,8 +304,8 @@ function isTemplate(name) {
|
|
|
305
304
|
lower.endsWith("-spec-template.md"));
|
|
306
305
|
}
|
|
307
306
|
// entity-style slug: ALLCAPS-then-Pascal split so naming variation across
|
|
308
|
-
// `
|
|
309
|
-
// (
|
|
307
|
+
// `GiftCardHub` / `order_tracking_container` / `POS_checkout` resolves stably
|
|
308
|
+
// (a measured real-menu finding; mirrors entitySlug).
|
|
310
309
|
function screenSlug(s) {
|
|
311
310
|
return s
|
|
312
311
|
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|