@sdd-method/sdd-cli 1.20.0 → 1.21.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/catalogue/aggregate.d.ts +40 -0
- package/dist/lib/catalogue/aggregate.d.ts.map +1 -1
- package/dist/lib/catalogue/aggregate.js +184 -4
- package/dist/lib/catalogue/aggregate.js.map +1 -1
- package/dist/lib/catalogue/build.d.ts.map +1 -1
- package/dist/lib/catalogue/build.js +17 -0
- package/dist/lib/catalogue/build.js.map +1 -1
- package/dist/lib/catalogue/builders/capability-spec.d.ts +14 -0
- package/dist/lib/catalogue/builders/capability-spec.d.ts.map +1 -1
- package/dist/lib/catalogue/builders/capability-spec.js +8 -5
- package/dist/lib/catalogue/builders/capability-spec.js.map +1 -1
- package/dist/lib/catalogue/builders/feature-grounding.d.ts +8 -0
- package/dist/lib/catalogue/builders/feature-grounding.d.ts.map +1 -1
- package/dist/lib/catalogue/builders/feature-grounding.js +6 -5
- package/dist/lib/catalogue/builders/feature-grounding.js.map +1 -1
- package/dist/lib/catalogue/ddl-generator.d.ts.map +1 -1
- package/dist/lib/catalogue/ddl-generator.js +22 -4
- package/dist/lib/catalogue/ddl-generator.js.map +1 -1
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.d.ts +20 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.d.ts.map +1 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js.map +1 -0
- package/dist/lib/catalogue/fk-validator.d.ts +20 -1
- package/dist/lib/catalogue/fk-validator.d.ts.map +1 -1
- package/dist/lib/catalogue/fk-validator.js +33 -3
- package/dist/lib/catalogue/fk-validator.js.map +1 -1
- package/package.json +1 -1
|
@@ -31,7 +31,31 @@ export interface AggregateResult {
|
|
|
31
31
|
}
|
|
32
32
|
export type Writer = (s: string) => void;
|
|
33
33
|
export declare function runCatalogueAggregate(inputs: AggregateInputs, write: Writer): Promise<AggregateResult>;
|
|
34
|
+
interface DuplicatePk {
|
|
35
|
+
readonly catalogue: string;
|
|
36
|
+
readonly primaryKey: string;
|
|
37
|
+
readonly value: string;
|
|
38
|
+
readonly sources: readonly string[];
|
|
39
|
+
}
|
|
34
40
|
export declare function dedupExternalSystems(rowsByCatalogue: Map<string, CatalogueRow[]>, findings: FindingsLog): void;
|
|
41
|
+
/**
|
|
42
|
+
* M-ADR 0208 §4.4: the catalogues whose organisation-tier key is
|
|
43
|
+
* `(platform_sdd_id, <pk>)`. Only `domains` — a domain is a bounded context
|
|
44
|
+
* OF a platform, so the pair is its natural key; every other catalogue's ids
|
|
45
|
+
* are already platform-qualified by construction or are method-scoped.
|
|
46
|
+
*/
|
|
47
|
+
export declare const PLATFORM_SCOPED_AT_ORGANISATION: readonly string[];
|
|
48
|
+
export declare function findDuplicatePrimaryKeys(schema: CanonicalSchema, rowsByCatalogue: ReadonlyMap<string, readonly CatalogueRow[]>, platformScopedTargets?: readonly string[]): DuplicatePk[];
|
|
49
|
+
/**
|
|
50
|
+
* M-ADR 0208 §4.4 (F-D), organisation tier. For each platform-scoped
|
|
51
|
+
* catalogue: which pks are owned by more than one platform, and how many
|
|
52
|
+
* referencing rows carry such a pk with NO stamp of their own — those
|
|
53
|
+
* references resolve by scalar fallback (the FK check cannot fail them
|
|
54
|
+
* without inventing a platform) but they are AMBIGUOUS, and this says so.
|
|
55
|
+
* One line per scoped catalogue, always written; a coverage finding only
|
|
56
|
+
* when ambiguity exists. Fires zero on an estate where no id is shared.
|
|
57
|
+
*/
|
|
58
|
+
export declare function checkPlatformScopedKeyAmbiguity(schema: CanonicalSchema, rowsByCatalogue: ReadonlyMap<string, readonly CatalogueRow[]>, platformScopedTargets: readonly string[], findings: FindingsLog, write: (s: string) => void): void;
|
|
35
59
|
/**
|
|
36
60
|
* Stamp `column` = `value` on every row that does not already carry one
|
|
37
61
|
* (preserve-if-set — chained aggregation, M-ADR 0134 §4.5 step 4). Used for
|
|
@@ -150,6 +174,22 @@ export interface ContractRekeySummary {
|
|
|
150
174
|
* guess). Exported for testing.
|
|
151
175
|
*/
|
|
152
176
|
export declare function rekeyLegacyContractRefs(schema: CanonicalSchema, rowsByCatalogue: Map<string, CatalogueRow[]>, findings: FindingsLog): ContractRekeySummary;
|
|
177
|
+
/**
|
|
178
|
+
* Fill `platform_sdd_id` on rows the aggregator constructed after the node
|
|
179
|
+
* loader stamped its inputs (M-ADR 0208 §4.4). The node loader stamps every
|
|
180
|
+
* row it MERGES; rows built at this tier from those inputs — 0189 §4.4
|
|
181
|
+
* adjudication conversions into system_system_dependency / container_dependency,
|
|
182
|
+
* re-derived edges — set source_sdd_id explicitly and were emitted with an empty
|
|
183
|
+
* platform stamp under FK PASS (measured: 38 of 38 system_system_dependency rows
|
|
184
|
+
* and 2 of 208 container_dependency rows on the forward reference estate, the
|
|
185
|
+
* other 206 stamped by the loader; 26 of 26 and 13 of 194 on the reverse estate's
|
|
186
|
+
* platform tier). Resolution order per row: the §4.4 fill-rule platform recorded
|
|
187
|
+
* for its source_sdd_id; else the single stamp already observed on that source's
|
|
188
|
+
* rows anywhere in the aggregate (a source whose rows carry two different stamps
|
|
189
|
+
* is ambiguous and is NOT guessed). Whatever remains empty is reported as
|
|
190
|
+
* coverage — an empty stamp is unresolved, never a key value. Exported for testing.
|
|
191
|
+
*/
|
|
192
|
+
export declare function fillDerivedPlatformStamps(schema: CanonicalSchema, rowsByCatalogue: Map<string, CatalogueRow[]>, platformBySource: ReadonlyMap<string, string>, findings: FindingsLog): void;
|
|
153
193
|
/**
|
|
154
194
|
* M-ADR 0133 §4.2c open hygiene, shipped with M-ADR 0189 §4.4, TIER-GATED by
|
|
155
195
|
* M-ADR 0208 §4.2 — gated, not skipped: a skipped check fails toward success.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../../src/lib/catalogue/aggregate.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,EAAoB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../../src/lib/catalogue/aggregate.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,6 +29,7 @@ import { readCatalogueCsv, parseCatalogueCsv } from "./csv-reader.js";
|
|
|
29
29
|
import { writeCatalogue } from "./csv-writer.js";
|
|
30
30
|
import { buildLegacyContractIdMap } from "./contract-id.js";
|
|
31
31
|
import { validateWithEngineToLog } from "./engine-validator.js";
|
|
32
|
+
import { PLATFORM_STAMP_COLUMN, isPlatformScopedFk } from "./fk-validator.js";
|
|
32
33
|
import { FindingsLog } from "./findings.js";
|
|
33
34
|
import { readLockFile } from "../lock-file.js";
|
|
34
35
|
import { mergeEdgeLabel } from "./builders/edge-label-merge.js";
|
|
@@ -112,6 +113,14 @@ export async function runCatalogueAggregate(inputs, write) {
|
|
|
112
113
|
rowsByCatalogue.set(cat.name, []);
|
|
113
114
|
}
|
|
114
115
|
perSiblingCounts.set(rootManifest.sddId, await mergeCatalogues(schema, rowsByCatalogue, join(sddPath, "catalogues"), rootManifest.sddId, rootIsOrganisation ? null : rootManifest.sddId));
|
|
116
|
+
// source_sdd_id -> the platform the §4.4 fill rule assigned it. Kept for the final
|
|
117
|
+
// pass (fillDerivedPlatformStamps): rows the aggregator itself constructs AFTER the
|
|
118
|
+
// node loader (0189 adjudication conversions, re-derived edges) set source_sdd_id
|
|
119
|
+
// explicitly and would otherwise leave platform_sdd_id empty under FK PASS —
|
|
120
|
+
// the same silent shape as the empty platform leg on cross_sdd_platform_capability.
|
|
121
|
+
const platformBySource = new Map();
|
|
122
|
+
if (!rootIsOrganisation)
|
|
123
|
+
platformBySource.set(rootManifest.sddId, rootManifest.sddId);
|
|
115
124
|
for (const sib of aggManifest.siblings) {
|
|
116
125
|
// M-ADR 0208 §4.4 fill rule, by pull path. An empty stamp is UNRESOLVED and is
|
|
117
126
|
// reported below by checkPlatformKindUniqueness; it is never a key value.
|
|
@@ -141,6 +150,8 @@ export async function runCatalogueAggregate(inputs, write) {
|
|
|
141
150
|
if (crossCheck.deprecatedPlatformField.has(sib.sddId)) {
|
|
142
151
|
findings.add("convention", `sibling "${sib.sddId}" manifest uses deprecated key \`ecosystem_root\` — rename to \`platform_sdd_id\` (M-ADR 0208 §4.3; alias removed at 2.0.0)`);
|
|
143
152
|
}
|
|
153
|
+
if (platformSddId !== null)
|
|
154
|
+
platformBySource.set(sib.sddId, platformSddId);
|
|
144
155
|
perSiblingCounts.set(sib.sddId, await mergeCatalogues(schema, rowsByCatalogue, sib.cataloguesPath, sib.sddId, platformSddId));
|
|
145
156
|
}
|
|
146
157
|
// 5a. Derive cross_sdd_platform_capability — federated edge, schema 2.9.0.
|
|
@@ -424,6 +435,11 @@ export async function runCatalogueAggregate(inputs, write) {
|
|
|
424
435
|
`${adjudication.containerConverted} container conversions (${adjudication.containerEdgesRewritten} edges → container_dependency, ` +
|
|
425
436
|
`${adjudication.systemFallbackEdges} stuck edges → system_system_dependency at L1), ` +
|
|
426
437
|
`${adjudication.exempted} exempted, ${adjudication.unverified} unverified\n`);
|
|
438
|
+
// 5f-bis. Rows constructed at THIS tier after the node loader (adjudication
|
|
439
|
+
// conversions, re-derived edges) carry source_sdd_id but no platform stamp.
|
|
440
|
+
// Fill them now that every derivation has run (M-ADR 0208 §4.4 as applied to
|
|
441
|
+
// aggregator-constructed rows); whatever stays empty is reported, never keyed on.
|
|
442
|
+
fillDerivedPlatformStamps(schema, rowsByCatalogue, platformBySource, findings);
|
|
427
443
|
// 5g. Platform-kind uniqueness (M-ADR 0133 §4.2c open hygiene).
|
|
428
444
|
checkPlatformKindUniqueness(rowsByCatalogue.get("c4_systems") ?? [], findings, rootIsOrganisation ? "organisation" : "platform");
|
|
429
445
|
// 5h. M-ADR 0203 §4.4 cross-SDD re-key: the contract-id migration derives
|
|
@@ -439,8 +455,12 @@ export async function runCatalogueAggregate(inputs, write) {
|
|
|
439
455
|
// was never printed.
|
|
440
456
|
write(` contract-id cut window (M-ADR 0203 §4.4): ${contractRekey.rekeyed} cross-SDD reference(s) re-keyed, ` +
|
|
441
457
|
`${contractRekey.deduped} duplicate fact(s) dropped, ${contractRekey.ambiguous} ambiguous left dangling\n`);
|
|
442
|
-
// 6. Duplicate primary key check.
|
|
443
|
-
|
|
458
|
+
// 6. Duplicate primary key check. M-ADR 0208 §4.4 (F-D): at organisation
|
|
459
|
+
// tier the key of `domains` is (platform_sdd_id, id) — two platforms may
|
|
460
|
+
// each own `identity`. Everywhere else, and for every other catalogue,
|
|
461
|
+
// the scalar pk. An empty stamp is unresolved, never a key.
|
|
462
|
+
const platformScopedTargets = rootIsOrganisation ? PLATFORM_SCOPED_AT_ORGANISATION : [];
|
|
463
|
+
const duplicates = findDuplicatePrimaryKeys(schema, rowsByCatalogue, platformScopedTargets);
|
|
444
464
|
if (duplicates.length > 0) {
|
|
445
465
|
write(`\nDuplicate primary keys (${duplicates.length}) — aggregation fails:\n`);
|
|
446
466
|
for (const d of duplicates) {
|
|
@@ -463,10 +483,16 @@ export async function runCatalogueAggregate(inputs, write) {
|
|
|
463
483
|
});
|
|
464
484
|
}
|
|
465
485
|
// 7. Full FK validation, treating external as internal.
|
|
466
|
-
const { fk } = await validateWithEngineToLog(schema, rowsByCatalogue, findings, { treatExternalAsInternal: true },
|
|
486
|
+
const { fk } = await validateWithEngineToLog(schema, rowsByCatalogue, findings, { treatExternalAsInternal: true, platformScopedTargets },
|
|
467
487
|
// The aggregation tier has never emitted enum findings; the cutover
|
|
468
488
|
// changes no tier's finding surface (M-ADR 0205 §4.4).
|
|
469
489
|
{ logEnumFindings: false });
|
|
490
|
+
// 7b. M-ADR 0208 §4.4 (F-D): report the domain-key state at organisation
|
|
491
|
+
// tier. Always emitted there — an absent line is indistinguishable from a
|
|
492
|
+
// check that never ran (M-ADR 0200 §4.3).
|
|
493
|
+
if (rootIsOrganisation) {
|
|
494
|
+
checkPlatformScopedKeyAmbiguity(schema, rowsByCatalogue, platformScopedTargets, findings, write);
|
|
495
|
+
}
|
|
470
496
|
// 8. Emit aggregated catalogues + aggregation-manifest.json.
|
|
471
497
|
const outDir = resolveOutDir(sddPath, inputs.outDir);
|
|
472
498
|
const rowCounts = new Map();
|
|
@@ -815,11 +841,21 @@ async function enrichFederatedServiceMetadata(rowsByCatalogue, siblings, finding
|
|
|
815
841
|
rowsByCatalogue.set("services", enriched);
|
|
816
842
|
}
|
|
817
843
|
}
|
|
818
|
-
|
|
844
|
+
/**
|
|
845
|
+
* M-ADR 0208 §4.4: the catalogues whose organisation-tier key is
|
|
846
|
+
* `(platform_sdd_id, <pk>)`. Only `domains` — a domain is a bounded context
|
|
847
|
+
* OF a platform, so the pair is its natural key; every other catalogue's ids
|
|
848
|
+
* are already platform-qualified by construction or are method-scoped.
|
|
849
|
+
*/
|
|
850
|
+
export const PLATFORM_SCOPED_AT_ORGANISATION = ["domains"];
|
|
851
|
+
export function findDuplicatePrimaryKeys(schema, rowsByCatalogue, platformScopedTargets = []) {
|
|
819
852
|
const out = [];
|
|
820
853
|
for (const cat of schema.catalogues) {
|
|
821
854
|
const rows = rowsByCatalogue.get(cat.name) ?? [];
|
|
855
|
+
// Scoped iff asked for AND the catalogue carries the stamp column.
|
|
856
|
+
const scoped = isPlatformScopedFk(cat, cat, { platformScopedTargets });
|
|
822
857
|
const byKey = new Map();
|
|
858
|
+
const stampsByKey = new Map();
|
|
823
859
|
for (const row of rows) {
|
|
824
860
|
const pk = row[cat.primaryKey];
|
|
825
861
|
if (pk === undefined || pk === null || pk === "")
|
|
@@ -829,8 +865,31 @@ function findDuplicatePrimaryKeys(schema, rowsByCatalogue) {
|
|
|
829
865
|
const list = byKey.get(value) ?? [];
|
|
830
866
|
list.push(sourceSdd || "<unknown>");
|
|
831
867
|
byKey.set(value, list);
|
|
868
|
+
if (scoped) {
|
|
869
|
+
const stamps = stampsByKey.get(value) ?? [];
|
|
870
|
+
stamps.push(String(row[PLATFORM_STAMP_COLUMN] ?? "").trim());
|
|
871
|
+
stampsByKey.set(value, stamps);
|
|
872
|
+
}
|
|
832
873
|
}
|
|
833
874
|
for (const [value, sources] of byKey) {
|
|
875
|
+
if (sources.length > 1 && scoped) {
|
|
876
|
+
// n rows are distinct under (stamp, pk) iff every stamp is present
|
|
877
|
+
// and all differ. An EMPTY stamp is unresolved, never a key: it
|
|
878
|
+
// collides with everything (§4.4 — "two children contributing
|
|
879
|
+
// (∅, identity) must produce a finding, not a silent merge").
|
|
880
|
+
// Mirrors ddl-generator pkQuery's HAVING clause.
|
|
881
|
+
const stamps = stampsByKey.get(value) ?? [];
|
|
882
|
+
const distinctNonEmpty = new Set(stamps.filter((st) => st !== "")).size;
|
|
883
|
+
if (distinctNonEmpty === stamps.length)
|
|
884
|
+
continue;
|
|
885
|
+
out.push({
|
|
886
|
+
catalogue: cat.name,
|
|
887
|
+
primaryKey: `(${PLATFORM_STAMP_COLUMN}, ${cat.primaryKey})`,
|
|
888
|
+
value: `${value} [stamps: ${stamps.map((st) => st || "∅").join(", ")}] — an empty stamp is unresolved, never a key (M-ADR 0208 §4.4)`,
|
|
889
|
+
sources,
|
|
890
|
+
});
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
834
893
|
if (sources.length > 1) {
|
|
835
894
|
out.push({
|
|
836
895
|
catalogue: cat.name,
|
|
@@ -843,6 +902,58 @@ function findDuplicatePrimaryKeys(schema, rowsByCatalogue) {
|
|
|
843
902
|
}
|
|
844
903
|
return out;
|
|
845
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* M-ADR 0208 §4.4 (F-D), organisation tier. For each platform-scoped
|
|
907
|
+
* catalogue: which pks are owned by more than one platform, and how many
|
|
908
|
+
* referencing rows carry such a pk with NO stamp of their own — those
|
|
909
|
+
* references resolve by scalar fallback (the FK check cannot fail them
|
|
910
|
+
* without inventing a platform) but they are AMBIGUOUS, and this says so.
|
|
911
|
+
* One line per scoped catalogue, always written; a coverage finding only
|
|
912
|
+
* when ambiguity exists. Fires zero on an estate where no id is shared.
|
|
913
|
+
*/
|
|
914
|
+
export function checkPlatformScopedKeyAmbiguity(schema, rowsByCatalogue, platformScopedTargets, findings, write) {
|
|
915
|
+
for (const target of schema.catalogues) {
|
|
916
|
+
if (!isPlatformScopedFk(target, target, { platformScopedTargets }))
|
|
917
|
+
continue;
|
|
918
|
+
const owners = new Map();
|
|
919
|
+
for (const row of rowsByCatalogue.get(target.name) ?? []) {
|
|
920
|
+
const pk = String(row[target.primaryKey] ?? "");
|
|
921
|
+
if (pk === "")
|
|
922
|
+
continue;
|
|
923
|
+
const set = owners.get(pk) ?? new Set();
|
|
924
|
+
set.add(String(row[PLATFORM_STAMP_COLUMN] ?? "").trim() || "∅");
|
|
925
|
+
owners.set(pk, set);
|
|
926
|
+
}
|
|
927
|
+
const shared = new Set([...owners].filter(([, o]) => o.size > 1).map(([pk]) => pk));
|
|
928
|
+
let ambiguous = 0;
|
|
929
|
+
const where = [];
|
|
930
|
+
for (const cat of schema.catalogues) {
|
|
931
|
+
for (const fk of cat.foreignKeys) {
|
|
932
|
+
if (fk.references.split(".")[0] !== target.name)
|
|
933
|
+
continue;
|
|
934
|
+
if (!isPlatformScopedFk(cat, target, { platformScopedTargets }))
|
|
935
|
+
continue;
|
|
936
|
+
let n = 0;
|
|
937
|
+
for (const row of rowsByCatalogue.get(cat.name) ?? []) {
|
|
938
|
+
const v = String(row[fk.column] ?? "");
|
|
939
|
+
if (v === "" || !shared.has(v))
|
|
940
|
+
continue;
|
|
941
|
+
if (String(row[PLATFORM_STAMP_COLUMN] ?? "").trim() === "")
|
|
942
|
+
n++;
|
|
943
|
+
}
|
|
944
|
+
if (n > 0) {
|
|
945
|
+
ambiguous += n;
|
|
946
|
+
where.push(`${cat.name}.${fk.column}=${n}`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
write(` ${target.name} key at organisation tier is (${PLATFORM_STAMP_COLUMN}, ${target.primaryKey}) (M-ADR 0208 §4.4): ` +
|
|
951
|
+
`${shared.size} id(s) owned by more than one platform, ${ambiguous} unstamped referencing row(s) ambiguous\n`);
|
|
952
|
+
if (ambiguous > 0) {
|
|
953
|
+
findings.add("coverage", `${target.name}: ${ambiguous} referencing row(s) with EMPTY ${PLATFORM_STAMP_COLUMN} name an id owned by more than one platform [${[...shared].sort().join(", ")}] (${where.join(", ")}) — resolved by scalar fallback, AMBIGUOUS until the row is stamped (M-ADR 0208 §4.4)`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
846
957
|
async function mergeCatalogues(schema, rowsByCatalogue, cataloguesDir, sourceSddId,
|
|
847
958
|
/** M-ADR 0208 §4.4: value to fill into `platform_sdd_id` when empty; null = do not fill. */
|
|
848
959
|
platformSddId) {
|
|
@@ -1854,6 +1965,75 @@ export function rekeyLegacyContractRefs(schema, rowsByCatalogue, findings) {
|
|
|
1854
1965
|
}
|
|
1855
1966
|
return { rekeyed, deduped, ambiguous };
|
|
1856
1967
|
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Fill `platform_sdd_id` on rows the aggregator constructed after the node
|
|
1970
|
+
* loader stamped its inputs (M-ADR 0208 §4.4). The node loader stamps every
|
|
1971
|
+
* row it MERGES; rows built at this tier from those inputs — 0189 §4.4
|
|
1972
|
+
* adjudication conversions into system_system_dependency / container_dependency,
|
|
1973
|
+
* re-derived edges — set source_sdd_id explicitly and were emitted with an empty
|
|
1974
|
+
* platform stamp under FK PASS (measured: 38 of 38 system_system_dependency rows
|
|
1975
|
+
* and 2 of 208 container_dependency rows on the forward reference estate, the
|
|
1976
|
+
* other 206 stamped by the loader; 26 of 26 and 13 of 194 on the reverse estate's
|
|
1977
|
+
* platform tier). Resolution order per row: the §4.4 fill-rule platform recorded
|
|
1978
|
+
* for its source_sdd_id; else the single stamp already observed on that source's
|
|
1979
|
+
* rows anywhere in the aggregate (a source whose rows carry two different stamps
|
|
1980
|
+
* is ambiguous and is NOT guessed). Whatever remains empty is reported as
|
|
1981
|
+
* coverage — an empty stamp is unresolved, never a key value. Exported for testing.
|
|
1982
|
+
*/
|
|
1983
|
+
export function fillDerivedPlatformStamps(schema, rowsByCatalogue, platformBySource, findings) {
|
|
1984
|
+
const stamped = schema.catalogues.filter((c) => c.columns.some((col) => col.name === "platform_sdd_id") &&
|
|
1985
|
+
c.columns.some((col) => col.name === "source_sdd_id"));
|
|
1986
|
+
const observed = new Map();
|
|
1987
|
+
for (const cat of stamped) {
|
|
1988
|
+
for (const r of rowsByCatalogue.get(cat.name) ?? []) {
|
|
1989
|
+
const src = String(r["source_sdd_id"] ?? "").trim();
|
|
1990
|
+
const pf = String(r["platform_sdd_id"] ?? "").trim();
|
|
1991
|
+
if (src === "" || pf === "")
|
|
1992
|
+
continue;
|
|
1993
|
+
if (!observed.has(src))
|
|
1994
|
+
observed.set(src, new Set());
|
|
1995
|
+
observed.get(src).add(pf);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const resolve = (src) => {
|
|
1999
|
+
const ruled = platformBySource.get(src);
|
|
2000
|
+
if (ruled !== undefined)
|
|
2001
|
+
return ruled;
|
|
2002
|
+
const seen = observed.get(src);
|
|
2003
|
+
if (seen !== undefined && seen.size === 1)
|
|
2004
|
+
return [...seen][0];
|
|
2005
|
+
return null;
|
|
2006
|
+
};
|
|
2007
|
+
let filled = 0;
|
|
2008
|
+
const unresolved = [];
|
|
2009
|
+
for (const cat of stamped) {
|
|
2010
|
+
const rows = rowsByCatalogue.get(cat.name);
|
|
2011
|
+
if (rows === undefined || rows.length === 0)
|
|
2012
|
+
continue;
|
|
2013
|
+
let catEmpty = 0;
|
|
2014
|
+
const next = rows.map((r) => {
|
|
2015
|
+
if (String(r["platform_sdd_id"] ?? "").trim() !== "")
|
|
2016
|
+
return r;
|
|
2017
|
+
const src = String(r["source_sdd_id"] ?? "").trim();
|
|
2018
|
+
const value = src === "" ? null : resolve(src);
|
|
2019
|
+
if (value === null) {
|
|
2020
|
+
catEmpty++;
|
|
2021
|
+
return r;
|
|
2022
|
+
}
|
|
2023
|
+
filled++;
|
|
2024
|
+
return { ...r, platform_sdd_id: value };
|
|
2025
|
+
});
|
|
2026
|
+
rowsByCatalogue.set(cat.name, next);
|
|
2027
|
+
if (catEmpty > 0)
|
|
2028
|
+
unresolved.push(`${cat.name} ${catEmpty}`);
|
|
2029
|
+
}
|
|
2030
|
+
if (filled > 0) {
|
|
2031
|
+
findings.add("inferred", `platform_sdd_id: ${filled} aggregator-constructed row(s) stamped from their source_sdd_id after derivation (M-ADR 0208 §4.4) — the node loader stamps only the rows it merges`);
|
|
2032
|
+
}
|
|
2033
|
+
if (unresolved.length > 0) {
|
|
2034
|
+
findings.add("coverage", `platform_sdd_id: rows still EMPTY after the final pass [${unresolved.join(", ")}] — source unknown to the fill rule, or its rows carry more than one stamp; unresolved, not a key (M-ADR 0208 §4.4)`);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
1857
2037
|
/**
|
|
1858
2038
|
* M-ADR 0133 §4.2c open hygiene, shipped with M-ADR 0189 §4.4, TIER-GATED by
|
|
1859
2039
|
* M-ADR 0208 §4.2 — gated, not skipped: a skipped check fails toward success.
|