@descryy/ir 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/capabilities.d.ts +83 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +91 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/conformance/bind.d.ts +82 -0
- package/dist/conformance/bind.d.ts.map +1 -0
- package/dist/conformance/bind.js +157 -0
- package/dist/conformance/bind.js.map +1 -0
- package/dist/conformance/cli.d.ts +25 -0
- package/dist/conformance/cli.d.ts.map +1 -0
- package/dist/conformance/cli.js +137 -0
- package/dist/conformance/cli.js.map +1 -0
- package/dist/conformance/compare.d.ts +74 -0
- package/dist/conformance/compare.d.ts.map +1 -0
- package/dist/conformance/compare.js +140 -0
- package/dist/conformance/compare.js.map +1 -0
- package/dist/conformance/compose.d.ts +79 -0
- package/dist/conformance/compose.d.ts.map +1 -0
- package/dist/conformance/compose.js +167 -0
- package/dist/conformance/compose.js.map +1 -0
- package/dist/conformance/golden.d.ts +110 -0
- package/dist/conformance/golden.d.ts.map +1 -0
- package/dist/conformance/golden.js +252 -0
- package/dist/conformance/golden.js.map +1 -0
- package/dist/conformance/index.d.ts +26 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +18 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/manifest-check.d.ts +78 -0
- package/dist/conformance/manifest-check.d.ts.map +1 -0
- package/dist/conformance/manifest-check.js +149 -0
- package/dist/conformance/manifest-check.js.map +1 -0
- package/dist/conformance/manifest.d.ts +83 -0
- package/dist/conformance/manifest.d.ts.map +1 -0
- package/dist/conformance/manifest.js +158 -0
- package/dist/conformance/manifest.js.map +1 -0
- package/dist/conformance/report.d.ts +14 -0
- package/dist/conformance/report.d.ts.map +1 -0
- package/dist/conformance/report.js +87 -0
- package/dist/conformance/report.js.map +1 -0
- package/dist/conformance/run.d.ts +172 -0
- package/dist/conformance/run.d.ts.map +1 -0
- package/dist/conformance/run.js +626 -0
- package/dist/conformance/run.js.map +1 -0
- package/dist/contracts.d.ts +198 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +10 -0
- package/dist/contracts.js.map +1 -0
- package/dist/contradiction.d.ts +112 -0
- package/dist/contradiction.d.ts.map +1 -0
- package/dist/contradiction.js +97 -0
- package/dist/contradiction.js.map +1 -0
- package/dist/finding.d.ts +567 -0
- package/dist/finding.d.ts.map +1 -0
- package/dist/finding.js +491 -0
- package/dist/finding.js.map +1 -0
- package/dist/hypothesis.d.ts +130 -0
- package/dist/hypothesis.d.ts.map +1 -0
- package/dist/hypothesis.js +116 -0
- package/dist/hypothesis.js.map +1 -0
- package/dist/identity.d.ts +295 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +404 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/ir.d.ts +360 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +76 -0
- package/dist/ir.js.map +1 -0
- package/dist/model-tables.d.ts +111 -0
- package/dist/model-tables.d.ts.map +1 -0
- package/dist/model-tables.js +103 -0
- package/dist/model-tables.js.map +1 -0
- package/dist/normalise.d.ts +114 -0
- package/dist/normalise.d.ts.map +1 -0
- package/dist/normalise.js +603 -0
- package/dist/normalise.js.map +1 -0
- package/dist/reliability.d.ts +89 -0
- package/dist/reliability.d.ts.map +1 -0
- package/dist/reliability.js +181 -0
- package/dist/reliability.js.map +1 -0
- package/dist/tool-surface.d.ts +131 -0
- package/dist/tool-surface.d.ts.map +1 -0
- package/dist/tool-surface.js +133 -0
- package/dist/tool-surface.js.map +1 -0
- package/dist/vocabulary.d.ts +73 -0
- package/dist/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary.js +150 -0
- package/dist/vocabulary.js.map +1 -0
- package/package.json +27 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The comparison primitives: attribute matching and edge-type equivalence.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions over plain values. Nothing here reads a batch, constructs a
|
|
5
|
+
* finding or knows what a pattern is — which is what makes the two rules below
|
|
6
|
+
* arguable on their own terms rather than buried inside the runner.
|
|
7
|
+
*/
|
|
8
|
+
import type { EdgeType, NodeType } from "../vocabulary.ts";
|
|
9
|
+
/**
|
|
10
|
+
* Node types whose attributes are **not** passed through `fieldNameMap`.
|
|
11
|
+
*
|
|
12
|
+
* The map translates one identifier in source code into another corpus's
|
|
13
|
+
* spelling of the same idea — `total_amount` and `totalAmount` are the same
|
|
14
|
+
* field written the way each language writes fields. A database column is not
|
|
15
|
+
* that: `discount_code` is the same string in every language, because it is the
|
|
16
|
+
* name in the schema and not the name in the code. Mapping it would make golden
|
|
17
|
+
* pattern 10 demand `discountCode` from one adapter and `discount_code` from
|
|
18
|
+
* another for the identical migration, and pattern 10 exists precisely because
|
|
19
|
+
* that migration is language-independent (§14.6).
|
|
20
|
+
*
|
|
21
|
+
* Found by writing the map, not by reasoning about it: the corpus binds
|
|
22
|
+
* `discount_code -> discountCode` for one language and also asserts a column
|
|
23
|
+
* literally named `discount_code`. See DEC-040.
|
|
24
|
+
*/
|
|
25
|
+
export declare const UNMAPPED_ATTR_NODE_TYPES: ReadonlySet<NodeType>;
|
|
26
|
+
/**
|
|
27
|
+
* Rewrite every string leaf of an expected value through the manifest's map.
|
|
28
|
+
*
|
|
29
|
+
* Applied to the *expectation*, never to what the adapter emitted. An adapter
|
|
30
|
+
* reports what the source says (`fixtures/languages/<name>/manifest.json`,
|
|
31
|
+
* `fieldNameMapNote`); rewriting its output would let the harness invent
|
|
32
|
+
* agreement that is not there.
|
|
33
|
+
*/
|
|
34
|
+
export declare function mapFieldNames(value: unknown, map: Readonly<Record<string, string>>): unknown;
|
|
35
|
+
export interface AttrMismatch {
|
|
36
|
+
/** Dotted path into the attrs, for a message the adapter author can act on. */
|
|
37
|
+
readonly path: string;
|
|
38
|
+
readonly detail: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Does `actual` contain everything `expected` asserts?
|
|
42
|
+
*
|
|
43
|
+
* **Containment, not equality**, in three places, and each is deliberate:
|
|
44
|
+
*
|
|
45
|
+
* - extra keys in an actual object are fine — `attrs` is the pressure valve that
|
|
46
|
+
* keeps the vocabulary at 15 types, so a framework-specific key is expected;
|
|
47
|
+
* - extra elements in an actual array are fine, for the same reason;
|
|
48
|
+
* - array order is not compared, because declaration order of fields carries no
|
|
49
|
+
* meaning and forcing one would make the golden a style guide.
|
|
50
|
+
*
|
|
51
|
+
* Precision violations are not expressed by "the adapter emitted more than
|
|
52
|
+
* this"; they are expressed by `forbiddenEdges` and `mustNotResolveTo`, which
|
|
53
|
+
* say exactly what must be absent. Two mechanisms, not three.
|
|
54
|
+
*/
|
|
55
|
+
export declare function attrsSubset(actual: unknown, expected: unknown, path?: string): AttrMismatch | null;
|
|
56
|
+
/**
|
|
57
|
+
* Edge types a golden expectation will also accept, and from whom.
|
|
58
|
+
*
|
|
59
|
+
* Only one pair, and it comes from the corpus rather than from convenience:
|
|
60
|
+
* golden pattern 11's note says a language that does not distinguish an
|
|
61
|
+
* interface from a base class may emit `INHERITS` where the golden says
|
|
62
|
+
* `IMPLEMENTS`, "and the adapter declares which via `capabilities()`".
|
|
63
|
+
*
|
|
64
|
+
* The declaration requirement is what keeps this from being a hole. Without it,
|
|
65
|
+
* any adapter could satisfy `IMPLEMENTS` by emitting `INHERITS` at random and
|
|
66
|
+
* the pattern would stop asserting anything.
|
|
67
|
+
*/
|
|
68
|
+
export declare const EDGE_TYPE_ALTERNATIVES: Readonly<Partial<Record<EdgeType, readonly EdgeType[]>>>;
|
|
69
|
+
/**
|
|
70
|
+
* Which edge types satisfy an expectation for `expected`, given what the adapter
|
|
71
|
+
* declared it can produce. An adapter that declares nothing gets no substitutes.
|
|
72
|
+
*/
|
|
73
|
+
export declare function acceptedEdgeTypes(expected: EdgeType, declared: readonly EdgeType[] | undefined): readonly EdgeType[];
|
|
74
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/conformance/compare.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM3D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,QAAQ,CAGzD,CAAC;AAEH;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAS5F;AAMD,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,OAAO,EACjB,IAAI,SAAU,GACb,YAAY,GAAG,IAAI,CAwCrB;AAMD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC,CAAC,CAE3F,CAAC;AAEF;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,GACxC,SAAS,QAAQ,EAAE,CAIrB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The comparison primitives: attribute matching and edge-type equivalence.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions over plain values. Nothing here reads a batch, constructs a
|
|
5
|
+
* finding or knows what a pattern is — which is what makes the two rules below
|
|
6
|
+
* arguable on their own terms rather than buried inside the runner.
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Field-name mapping
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Node types whose attributes are **not** passed through `fieldNameMap`.
|
|
13
|
+
*
|
|
14
|
+
* The map translates one identifier in source code into another corpus's
|
|
15
|
+
* spelling of the same idea — `total_amount` and `totalAmount` are the same
|
|
16
|
+
* field written the way each language writes fields. A database column is not
|
|
17
|
+
* that: `discount_code` is the same string in every language, because it is the
|
|
18
|
+
* name in the schema and not the name in the code. Mapping it would make golden
|
|
19
|
+
* pattern 10 demand `discountCode` from one adapter and `discount_code` from
|
|
20
|
+
* another for the identical migration, and pattern 10 exists precisely because
|
|
21
|
+
* that migration is language-independent (§14.6).
|
|
22
|
+
*
|
|
23
|
+
* Found by writing the map, not by reasoning about it: the corpus binds
|
|
24
|
+
* `discount_code -> discountCode` for one language and also asserts a column
|
|
25
|
+
* literally named `discount_code`. See DEC-040.
|
|
26
|
+
*/
|
|
27
|
+
export const UNMAPPED_ATTR_NODE_TYPES = new Set([
|
|
28
|
+
"DATABASE_TABLE",
|
|
29
|
+
"DATABASE_COLUMN",
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Rewrite every string leaf of an expected value through the manifest's map.
|
|
33
|
+
*
|
|
34
|
+
* Applied to the *expectation*, never to what the adapter emitted. An adapter
|
|
35
|
+
* reports what the source says (`fixtures/languages/<name>/manifest.json`,
|
|
36
|
+
* `fieldNameMapNote`); rewriting its output would let the harness invent
|
|
37
|
+
* agreement that is not there.
|
|
38
|
+
*/
|
|
39
|
+
export function mapFieldNames(value, map) {
|
|
40
|
+
if (typeof value === "string")
|
|
41
|
+
return map[value] ?? value;
|
|
42
|
+
if (Array.isArray(value))
|
|
43
|
+
return value.map((item) => mapFieldNames(item, map));
|
|
44
|
+
if (typeof value === "object" && value !== null) {
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const [key, entry] of Object.entries(value))
|
|
47
|
+
out[key] = mapFieldNames(entry, map);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function describe(value) {
|
|
53
|
+
return JSON.stringify(value) ?? String(value);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Does `actual` contain everything `expected` asserts?
|
|
57
|
+
*
|
|
58
|
+
* **Containment, not equality**, in three places, and each is deliberate:
|
|
59
|
+
*
|
|
60
|
+
* - extra keys in an actual object are fine — `attrs` is the pressure valve that
|
|
61
|
+
* keeps the vocabulary at 15 types, so a framework-specific key is expected;
|
|
62
|
+
* - extra elements in an actual array are fine, for the same reason;
|
|
63
|
+
* - array order is not compared, because declaration order of fields carries no
|
|
64
|
+
* meaning and forcing one would make the golden a style guide.
|
|
65
|
+
*
|
|
66
|
+
* Precision violations are not expressed by "the adapter emitted more than
|
|
67
|
+
* this"; they are expressed by `forbiddenEdges` and `mustNotResolveTo`, which
|
|
68
|
+
* say exactly what must be absent. Two mechanisms, not three.
|
|
69
|
+
*/
|
|
70
|
+
export function attrsSubset(actual, expected, path = "attrs") {
|
|
71
|
+
if (Array.isArray(expected)) {
|
|
72
|
+
if (!Array.isArray(actual)) {
|
|
73
|
+
return { path, detail: `expected an array, got ${describe(actual)}` };
|
|
74
|
+
}
|
|
75
|
+
const taken = new Set();
|
|
76
|
+
for (const want of expected) {
|
|
77
|
+
let found = false;
|
|
78
|
+
for (let i = 0; i < actual.length; i += 1) {
|
|
79
|
+
if (taken.has(i))
|
|
80
|
+
continue;
|
|
81
|
+
if (attrsSubset(actual[i], want, `${path}[]`) === null) {
|
|
82
|
+
taken.add(i);
|
|
83
|
+
found = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!found)
|
|
88
|
+
return { path, detail: `no entry matching ${describe(want)}` };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (typeof expected === "object" && expected !== null) {
|
|
93
|
+
if (typeof actual !== "object" || actual === null || Array.isArray(actual)) {
|
|
94
|
+
return { path, detail: `expected an object, got ${describe(actual)}` };
|
|
95
|
+
}
|
|
96
|
+
const actualRecord = actual;
|
|
97
|
+
for (const [key, want] of Object.entries(expected)) {
|
|
98
|
+
if (!(key in actualRecord)) {
|
|
99
|
+
return { path: `${path}.${key}`, detail: `absent; expected ${describe(want)}` };
|
|
100
|
+
}
|
|
101
|
+
const mismatch = attrsSubset(actualRecord[key], want, `${path}.${key}`);
|
|
102
|
+
if (mismatch !== null)
|
|
103
|
+
return mismatch;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (actual !== expected) {
|
|
108
|
+
return { path, detail: `expected ${describe(expected)}, got ${describe(actual)}` };
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Edge-type equivalence
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* Edge types a golden expectation will also accept, and from whom.
|
|
117
|
+
*
|
|
118
|
+
* Only one pair, and it comes from the corpus rather than from convenience:
|
|
119
|
+
* golden pattern 11's note says a language that does not distinguish an
|
|
120
|
+
* interface from a base class may emit `INHERITS` where the golden says
|
|
121
|
+
* `IMPLEMENTS`, "and the adapter declares which via `capabilities()`".
|
|
122
|
+
*
|
|
123
|
+
* The declaration requirement is what keeps this from being a hole. Without it,
|
|
124
|
+
* any adapter could satisfy `IMPLEMENTS` by emitting `INHERITS` at random and
|
|
125
|
+
* the pattern would stop asserting anything.
|
|
126
|
+
*/
|
|
127
|
+
export const EDGE_TYPE_ALTERNATIVES = {
|
|
128
|
+
IMPLEMENTS: ["INHERITS"],
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Which edge types satisfy an expectation for `expected`, given what the adapter
|
|
132
|
+
* declared it can produce. An adapter that declares nothing gets no substitutes.
|
|
133
|
+
*/
|
|
134
|
+
export function acceptedEdgeTypes(expected, declared) {
|
|
135
|
+
const alternatives = EDGE_TYPE_ALTERNATIVES[expected] ?? [];
|
|
136
|
+
if (declared === undefined)
|
|
137
|
+
return [expected];
|
|
138
|
+
return [expected, ...alternatives.filter((type) => declared.includes(type))];
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/conformance/compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Several producers, graded as the graph will actually see them.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a per-adapter gate is not enough on its own
|
|
5
|
+
*
|
|
6
|
+
* `runConformance` grades **one** adapter, and that is right: a release gate
|
|
7
|
+
* certifies a thing that can be released. But four of the fourteen golden
|
|
8
|
+
* patterns assert nodes from **two different producers at once**, and no
|
|
9
|
+
* single-adapter run can ever satisfy one of those. Pattern 04 is the clearest
|
|
10
|
+
* case:
|
|
11
|
+
*
|
|
12
|
+
* API_ROUTE ──SERVES_API──▶ API_ENDPOINT and a FUNCTION handler
|
|
13
|
+
* (a contract, or a framework extractor) (the language adapter)
|
|
14
|
+
*
|
|
15
|
+
* `adapter-openapi` produces the route and the endpoint and genuinely cannot
|
|
16
|
+
* produce the handler; the language adapter produces the handler and genuinely
|
|
17
|
+
* cannot produce the route. Run apart, each correctly reports a skip for a type
|
|
18
|
+
* it does not declare — and the pattern is then **permanently unmeasured**,
|
|
19
|
+
* which reads exactly like a gap in the product when it is a gap in the harness.
|
|
20
|
+
*
|
|
21
|
+
* Composing them is not a loosening. It is the arrangement the product ships in:
|
|
22
|
+
* the Graph Builder merges every `IRSource` over one repository, and a question
|
|
23
|
+
* like *which handler serves this endpoint* is only ever answerable in the
|
|
24
|
+
* merged graph. What the composed run certifies is a **pair**, and it is
|
|
25
|
+
* reported separately from each adapter's own gate so neither number is diluted.
|
|
26
|
+
*
|
|
27
|
+
* ## What this is not
|
|
28
|
+
*
|
|
29
|
+
* It is not the Graph Builder. It concatenates batches and drops a node whose id
|
|
30
|
+
* a previous batch already emitted, which is the merge rule stated at its
|
|
31
|
+
* simplest (DEC-012); the builder additionally reconciles attributes, resolution
|
|
32
|
+
* and confidence across producers, and that reconciliation has its own tests.
|
|
33
|
+
* Using the real builder here would make a conformance failure ambiguous between
|
|
34
|
+
* the adapters and the merge — so this stays deliberately thin, and a
|
|
35
|
+
* disagreement between two producers over one id is **reported** rather than
|
|
36
|
+
* silently resolved.
|
|
37
|
+
*
|
|
38
|
+
* ## The one thing it gives up, and why that is allowed here
|
|
39
|
+
*
|
|
40
|
+
* The Normaliser requires every node in a batch to carry the batch own
|
|
41
|
+
* `producedBy`, because the writer invalidates by `(producedBy, sourceFiles)`
|
|
42
|
+
* and a node stamped differently would be inserted by a batch and then never
|
|
43
|
+
* deleted by it. That rule is load-bearing for storage and is not weakened: the
|
|
44
|
+
* composed batch **restamps** every node and edge with the composition id, which
|
|
45
|
+
* costs per-node provenance *inside this run*.
|
|
46
|
+
*
|
|
47
|
+
* That is acceptable only because a composed run stores nothing and certifies
|
|
48
|
+
* nothing shippable. The per-adapter gates above it keep full provenance and are
|
|
49
|
+
* what release depends on; this run answers one question — can the pair satisfy
|
|
50
|
+
* a pattern neither can satisfy alone — and is reported separately so no
|
|
51
|
+
* adapter number is diluted. The product itself never does this: the Graph
|
|
52
|
+
* Builder normalises each batch on its own and merges the results, which is why
|
|
53
|
+
* it keeps the provenance this does not.
|
|
54
|
+
*/
|
|
55
|
+
import type { CapabilityMatrix, IRSource } from "../contracts.ts";
|
|
56
|
+
import type { NodeType } from "../vocabulary.ts";
|
|
57
|
+
/** A node two producers both claimed, kept for reporting rather than hidden. */
|
|
58
|
+
export interface CompositionConflict {
|
|
59
|
+
readonly nodeId: string;
|
|
60
|
+
readonly keptFrom: string;
|
|
61
|
+
readonly alsoFrom: string;
|
|
62
|
+
readonly type: NodeType;
|
|
63
|
+
readonly name: string;
|
|
64
|
+
}
|
|
65
|
+
export interface ComposedSource extends IRSource {
|
|
66
|
+
capabilities(): CapabilityMatrix;
|
|
67
|
+
/** Populated by `emit`. Empty when no two producers claimed the same id. */
|
|
68
|
+
readonly conflicts: readonly CompositionConflict[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Compose several sources into one, in the given order.
|
|
72
|
+
*
|
|
73
|
+
* Order is significant and deliberate: the first producer to claim a node id
|
|
74
|
+
* keeps it. Passing the language adapter first therefore means a route it
|
|
75
|
+
* extracted from source outranks the same route read from a contract, which is
|
|
76
|
+
* the precedence a framework extractor should have — it saw the code.
|
|
77
|
+
*/
|
|
78
|
+
export declare function composeSources(sources: readonly IRSource[], composedId?: string): ComposedSource;
|
|
79
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/conformance/compose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAIH,OAAO,KAAK,EAEV,gBAAgB,EAChB,QAAQ,EAET,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE3D,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ;IAC9C,YAAY,IAAI,gBAAgB,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACpD;AAKD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,cAAc,CAyGhG"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Several producers, graded as the graph will actually see them.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a per-adapter gate is not enough on its own
|
|
5
|
+
*
|
|
6
|
+
* `runConformance` grades **one** adapter, and that is right: a release gate
|
|
7
|
+
* certifies a thing that can be released. But four of the fourteen golden
|
|
8
|
+
* patterns assert nodes from **two different producers at once**, and no
|
|
9
|
+
* single-adapter run can ever satisfy one of those. Pattern 04 is the clearest
|
|
10
|
+
* case:
|
|
11
|
+
*
|
|
12
|
+
* API_ROUTE ──SERVES_API──▶ API_ENDPOINT and a FUNCTION handler
|
|
13
|
+
* (a contract, or a framework extractor) (the language adapter)
|
|
14
|
+
*
|
|
15
|
+
* `adapter-openapi` produces the route and the endpoint and genuinely cannot
|
|
16
|
+
* produce the handler; the language adapter produces the handler and genuinely
|
|
17
|
+
* cannot produce the route. Run apart, each correctly reports a skip for a type
|
|
18
|
+
* it does not declare — and the pattern is then **permanently unmeasured**,
|
|
19
|
+
* which reads exactly like a gap in the product when it is a gap in the harness.
|
|
20
|
+
*
|
|
21
|
+
* Composing them is not a loosening. It is the arrangement the product ships in:
|
|
22
|
+
* the Graph Builder merges every `IRSource` over one repository, and a question
|
|
23
|
+
* like *which handler serves this endpoint* is only ever answerable in the
|
|
24
|
+
* merged graph. What the composed run certifies is a **pair**, and it is
|
|
25
|
+
* reported separately from each adapter's own gate so neither number is diluted.
|
|
26
|
+
*
|
|
27
|
+
* ## What this is not
|
|
28
|
+
*
|
|
29
|
+
* It is not the Graph Builder. It concatenates batches and drops a node whose id
|
|
30
|
+
* a previous batch already emitted, which is the merge rule stated at its
|
|
31
|
+
* simplest (DEC-012); the builder additionally reconciles attributes, resolution
|
|
32
|
+
* and confidence across producers, and that reconciliation has its own tests.
|
|
33
|
+
* Using the real builder here would make a conformance failure ambiguous between
|
|
34
|
+
* the adapters and the merge — so this stays deliberately thin, and a
|
|
35
|
+
* disagreement between two producers over one id is **reported** rather than
|
|
36
|
+
* silently resolved.
|
|
37
|
+
*
|
|
38
|
+
* ## The one thing it gives up, and why that is allowed here
|
|
39
|
+
*
|
|
40
|
+
* The Normaliser requires every node in a batch to carry the batch own
|
|
41
|
+
* `producedBy`, because the writer invalidates by `(producedBy, sourceFiles)`
|
|
42
|
+
* and a node stamped differently would be inserted by a batch and then never
|
|
43
|
+
* deleted by it. That rule is load-bearing for storage and is not weakened: the
|
|
44
|
+
* composed batch **restamps** every node and edge with the composition id, which
|
|
45
|
+
* costs per-node provenance *inside this run*.
|
|
46
|
+
*
|
|
47
|
+
* That is acceptable only because a composed run stores nothing and certifies
|
|
48
|
+
* nothing shippable. The per-adapter gates above it keep full provenance and are
|
|
49
|
+
* what release depends on; this run answers one question — can the pair satisfy
|
|
50
|
+
* a pattern neither can satisfy alone — and is reported separately so no
|
|
51
|
+
* adapter number is diluted. The product itself never does this: the Graph
|
|
52
|
+
* Builder normalises each batch on its own and merges the results, which is why
|
|
53
|
+
* it keeps the provenance this does not.
|
|
54
|
+
*/
|
|
55
|
+
const hasCapabilities = (source) => typeof source.capabilities === "function";
|
|
56
|
+
/**
|
|
57
|
+
* Compose several sources into one, in the given order.
|
|
58
|
+
*
|
|
59
|
+
* Order is significant and deliberate: the first producer to claim a node id
|
|
60
|
+
* keeps it. Passing the language adapter first therefore means a route it
|
|
61
|
+
* extracted from source outranks the same route read from a contract, which is
|
|
62
|
+
* the precedence a framework extractor should have — it saw the code.
|
|
63
|
+
*/
|
|
64
|
+
export function composeSources(sources, composedId) {
|
|
65
|
+
const conflicts = [];
|
|
66
|
+
// The id half of `producedBy` admits no `+`; the version half does. Both
|
|
67
|
+
// halves are checked by the Normaliser and a malformed one fails the whole
|
|
68
|
+
// batch, which is the boundary working rather than an obstacle.
|
|
69
|
+
const id = (composedId ?? sources.map((source) => source.id).join("-")).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
70
|
+
const version = sources.map((source) => source.version).join("+");
|
|
71
|
+
const stamp = `${id}@${version}`;
|
|
72
|
+
return {
|
|
73
|
+
id,
|
|
74
|
+
version,
|
|
75
|
+
conflicts,
|
|
76
|
+
capabilities() {
|
|
77
|
+
const matrices = sources.filter(hasCapabilities).map((source) => source.capabilities());
|
|
78
|
+
const nodeTypes = new Set();
|
|
79
|
+
const edgeTypes = new Set();
|
|
80
|
+
const extractors = new Set();
|
|
81
|
+
const supplied = new Set();
|
|
82
|
+
let maxResolution = 0;
|
|
83
|
+
for (const matrix of matrices) {
|
|
84
|
+
for (const type of matrix.nodeTypes)
|
|
85
|
+
nodeTypes.add(type);
|
|
86
|
+
for (const type of matrix.edgeTypes)
|
|
87
|
+
edgeTypes.add(type);
|
|
88
|
+
for (const extractor of matrix.frameworkExtractors)
|
|
89
|
+
extractors.add(extractor);
|
|
90
|
+
for (const capability of matrix.frameworkCapabilities ?? [])
|
|
91
|
+
supplied.add(capability);
|
|
92
|
+
// The union's ceiling is the best any member reaches. A pattern is then
|
|
93
|
+
// graded against the producer that can actually satisfy it rather than
|
|
94
|
+
// against the weakest member of the pair.
|
|
95
|
+
if (matrix.maxResolution > maxResolution)
|
|
96
|
+
maxResolution = matrix.maxResolution;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
maxResolution,
|
|
100
|
+
nodeTypes: [...nodeTypes],
|
|
101
|
+
edgeTypes: [...edgeTypes],
|
|
102
|
+
hasCallHierarchy: matrices.some((matrix) => matrix.hasCallHierarchy),
|
|
103
|
+
hasTypeHierarchy: matrices.some((matrix) => matrix.hasTypeHierarchy),
|
|
104
|
+
frameworkExtractors: [...extractors],
|
|
105
|
+
// The union of what the members supply, and only declared when
|
|
106
|
+
// something is bound. A composed source binding extractors and
|
|
107
|
+
// declaring no capability is the same adapter failure as a single one:
|
|
108
|
+
// the check lives in manifest-check.ts and applies here unchanged.
|
|
109
|
+
...(extractors.size === 0 ? {} : { frameworkCapabilities: [...supplied] }),
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
async emit(ctx) {
|
|
113
|
+
conflicts.length = 0;
|
|
114
|
+
const nodes = new Map();
|
|
115
|
+
const owner = new Map();
|
|
116
|
+
const edges = new Map();
|
|
117
|
+
const unresolved = [];
|
|
118
|
+
const sourceFiles = [];
|
|
119
|
+
const skippedFiles = [];
|
|
120
|
+
let reached = 0;
|
|
121
|
+
let first;
|
|
122
|
+
for (const source of sources) {
|
|
123
|
+
const batch = await source.emit(ctx);
|
|
124
|
+
first ??= batch;
|
|
125
|
+
sourceFiles.push(...batch.sourceFiles);
|
|
126
|
+
skippedFiles.push(...batch.skippedFiles);
|
|
127
|
+
if (batch.reachedResolution > reached)
|
|
128
|
+
reached = batch.reachedResolution;
|
|
129
|
+
for (const node of batch.nodes) {
|
|
130
|
+
const held = nodes.get(node.id);
|
|
131
|
+
if (held !== undefined) {
|
|
132
|
+
conflicts.push({
|
|
133
|
+
nodeId: node.id,
|
|
134
|
+
keptFrom: owner.get(node.id) ?? "?",
|
|
135
|
+
alsoFrom: batch.producedBy,
|
|
136
|
+
type: held.type,
|
|
137
|
+
name: held.name,
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
nodes.set(node.id, { ...node, producedBy: stamp });
|
|
142
|
+
owner.set(node.id, batch.producedBy);
|
|
143
|
+
}
|
|
144
|
+
for (const edge of batch.edges) {
|
|
145
|
+
edges.set(`${edge.from} ${edge.to} ${edge.type}`, { ...edge, producedBy: stamp });
|
|
146
|
+
}
|
|
147
|
+
unresolved.push(...batch.unresolved.map((ref) => ({ ...ref, producedBy: stamp })));
|
|
148
|
+
}
|
|
149
|
+
if (first === undefined) {
|
|
150
|
+
throw new TypeError("composeSources: at least one source is required");
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
repo: first.repo,
|
|
154
|
+
...(first.workspace === undefined ? {} : { workspace: first.workspace }),
|
|
155
|
+
commitSha: first.commitSha,
|
|
156
|
+
producedBy: stamp,
|
|
157
|
+
sourceFiles: [...new Set(sourceFiles)].sort(),
|
|
158
|
+
skippedFiles,
|
|
159
|
+
reachedResolution: reached,
|
|
160
|
+
nodes: [...nodes.values()],
|
|
161
|
+
edges: [...edges.values()],
|
|
162
|
+
unresolved,
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/conformance/compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The golden corpus, parsed and validated — `fixtures/golden/patterns.json`.
|
|
3
|
+
*
|
|
4
|
+
* The golden is hand-written JSON that outlives every adapter, so it is parsed
|
|
5
|
+
* **strictly**: an unknown key is an error, not something to ignore. A typo in
|
|
6
|
+
* `requiredAtResolution` that silently parsed as "no requirement" would turn a
|
|
7
|
+
* recall gate into a no-op, and nothing downstream would ever notice — the suite
|
|
8
|
+
* would go green and stay green.
|
|
9
|
+
*
|
|
10
|
+
* One golden, N language bindings (`fixtures/README.md`). Nothing here is
|
|
11
|
+
* per-language, and nothing here may become per-language: the moment a pattern
|
|
12
|
+
* needs a different expectation per adapter it has stopped being a conformance
|
|
13
|
+
* pattern and become a language quirk, which belongs in `attrs`.
|
|
14
|
+
*/
|
|
15
|
+
import { type FrameworkCapability } from "../capabilities.ts";
|
|
16
|
+
import type { ResolutionLevel } from "../ir.ts";
|
|
17
|
+
import { type EdgeType, type NodeType } from "../vocabulary.ts";
|
|
18
|
+
/** The wildcard target in a `forbiddenEdges` entry. See `compare.ts` for why it matters. */
|
|
19
|
+
export declare const ANY_TARGET = "*";
|
|
20
|
+
export interface GoldenNode {
|
|
21
|
+
readonly role: string;
|
|
22
|
+
readonly type: NodeType;
|
|
23
|
+
/** Asserts two roles resolve to nodes in the same file. Compares `node.file`. */
|
|
24
|
+
readonly sameFileAs?: string;
|
|
25
|
+
readonly attrs?: Readonly<Record<string, unknown>>;
|
|
26
|
+
}
|
|
27
|
+
export interface GoldenEdge {
|
|
28
|
+
readonly from: string;
|
|
29
|
+
readonly to: string;
|
|
30
|
+
readonly type: EdgeType;
|
|
31
|
+
/** Lowest level at which the edge MUST be present. Absence below it is correct. */
|
|
32
|
+
readonly requiredAtResolution?: ResolutionLevel;
|
|
33
|
+
readonly attrs?: Readonly<Record<string, unknown>>;
|
|
34
|
+
}
|
|
35
|
+
export interface GoldenForbiddenEdge {
|
|
36
|
+
readonly from: string;
|
|
37
|
+
/** A role, or `ANY_TARGET` — the licence to look outside the role-bound set. */
|
|
38
|
+
readonly to: string;
|
|
39
|
+
readonly type: EdgeType;
|
|
40
|
+
readonly atAnyResolution?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A role the corpus declares and the adapter must NOT produce a node for.
|
|
44
|
+
*
|
|
45
|
+
* The mirror of every other role: a binding is normally an assertion that
|
|
46
|
+
* something exists, and this one asserts that nothing does. It is how the corpus
|
|
47
|
+
* states an OVER-ADMISSION trap — a call site spelled exactly like a
|
|
48
|
+
* registration whose receiver is not a router, which a name-based rule admits
|
|
49
|
+
* and a provenance-based rule refuses.
|
|
50
|
+
*
|
|
51
|
+
* Why a role rather than a literal in the golden: the trap's spelling is
|
|
52
|
+
* per-language (`mux.HandleFunc("/health", ...)`, `Preferences::get(...)`,
|
|
53
|
+
* `@mock.patch(...)`) and per-language detail belongs in the manifest. The
|
|
54
|
+
* golden names the rule; the manifest names the trap. `alternates` on the
|
|
55
|
+
* binding are every spelling a wrong adapter might give it, and any of them
|
|
56
|
+
* binding is the failure.
|
|
57
|
+
*
|
|
58
|
+
* **A corpus that declares no such role is SKIPPED, never passed.** A negative
|
|
59
|
+
* nothing can violate is decorative, and reporting it green is exactly the
|
|
60
|
+
* "green means covered" failure this corpus exists to prevent.
|
|
61
|
+
*/
|
|
62
|
+
export interface GoldenMustNotBind {
|
|
63
|
+
readonly role: string;
|
|
64
|
+
readonly why: string;
|
|
65
|
+
}
|
|
66
|
+
export interface GoldenMustNotResolveTo {
|
|
67
|
+
/** `"from_role->to_role"`, naming one of the pattern's expected edges. */
|
|
68
|
+
readonly edge: string;
|
|
69
|
+
/** The role the edge must NOT terminate at. */
|
|
70
|
+
readonly target: string;
|
|
71
|
+
readonly why: string;
|
|
72
|
+
}
|
|
73
|
+
export interface GoldenPattern {
|
|
74
|
+
readonly description: string;
|
|
75
|
+
readonly nodes: readonly GoldenNode[];
|
|
76
|
+
readonly edges: readonly GoldenEdge[];
|
|
77
|
+
/** Applies to this pattern's node assertions, and to edges that state no level. */
|
|
78
|
+
readonly requiredAtResolution?: ResolutionLevel;
|
|
79
|
+
/**
|
|
80
|
+
* The framework capability a language adapter alone cannot supply (§11A.8).
|
|
81
|
+
*
|
|
82
|
+
* Named rather than boolean, so an adapter that reads routes and no ORM is
|
|
83
|
+
* held to the route pattern and excused the ORM ones. See `capabilities.ts`.
|
|
84
|
+
*/
|
|
85
|
+
readonly requires?: FrameworkCapability;
|
|
86
|
+
readonly forbiddenEdges?: readonly GoldenForbiddenEdge[];
|
|
87
|
+
readonly mustNotResolveTo?: GoldenMustNotResolveTo;
|
|
88
|
+
/** Roles that must bind to nothing. See `GoldenMustNotBind`. */
|
|
89
|
+
readonly mustNotBind?: readonly GoldenMustNotBind[];
|
|
90
|
+
readonly note?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface Golden {
|
|
93
|
+
readonly version: number;
|
|
94
|
+
readonly patterns: Readonly<Record<string, GoldenPattern>>;
|
|
95
|
+
}
|
|
96
|
+
export declare class GoldenError extends Error {
|
|
97
|
+
readonly name = "GoldenError";
|
|
98
|
+
}
|
|
99
|
+
export declare function parseGolden(raw: unknown): Golden;
|
|
100
|
+
export declare function loadGolden(path: string): Golden;
|
|
101
|
+
/** Every role any pattern names. Used to check a manifest binds the whole corpus. */
|
|
102
|
+
export declare function goldenRoles(golden: Golden): readonly string[];
|
|
103
|
+
/**
|
|
104
|
+
* The roles a single pattern needs bound before it can be checked.
|
|
105
|
+
*
|
|
106
|
+
* `forbiddenEdges[].to === ANY_TARGET` contributes nothing here, which is the
|
|
107
|
+
* point of the wildcard: it names no role, so it needs none bound.
|
|
108
|
+
*/
|
|
109
|
+
export declare function patternRoles(pattern: GoldenPattern): readonly string[];
|
|
110
|
+
//# sourceMappingURL=golden.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"golden.d.ts","sourceRoot":"","sources":["../../src/conformance/golden.ts"],"names":[],"mappings":"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"}
|