@fabricorg/gen-conformance 1.0.6 → 1.0.7
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/CHANGELOG.md +26 -0
- package/dist/index.cjs +349 -323
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -125
- package/dist/index.d.ts +124 -125
- package/dist/index.js +340 -290
- package/dist/index.js.map +1 -1
- package/dist/memory.cjs +91 -110
- package/dist/memory.cjs.map +1 -1
- package/dist/memory.d.cts +11 -13
- package/dist/memory.d.ts +11 -13
- package/dist/memory.js +89 -84
- package/dist/memory.js.map +1 -1
- package/package.json +10 -9
package/dist/index.cjs
CHANGED
|
@@ -1,341 +1,367 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
MODULE_CONFORMANCE_CHECKS: () => MODULE_CONFORMANCE_CHECKS,
|
|
24
|
-
PHASE_1_CONFORMANCE_CHECKS: () => PHASE_1_CONFORMANCE_CHECKS,
|
|
25
|
-
PLATFORM_CONFORMANCE_CHECKS: () => PLATFORM_CONFORMANCE_CHECKS,
|
|
26
|
-
buildModuleConformancePlan: () => buildModuleConformancePlan,
|
|
27
|
-
conformanceGenerator: () => conformanceGenerator,
|
|
28
|
-
createCertificationResult: () => createCertificationResult,
|
|
29
|
-
moduleConformanceChecks: () => moduleConformanceChecks
|
|
30
|
-
});
|
|
31
|
-
module.exports = __toCommonJS(index_exports);
|
|
32
|
-
var import_compiler = require("@fabricorg/compiler");
|
|
33
|
-
var MODULE_CONFORMANCE_CHECKS = [
|
|
34
|
-
"fabric.idempotency.same-command-replay.v1",
|
|
35
|
-
"fabric.idempotency.conflicting-payload.v1",
|
|
36
|
-
"fabric.idempotency.concurrent-submission.v1",
|
|
37
|
-
"fabric.authorization.governing-moment.v1",
|
|
38
|
-
"fabric.authorization.offline-replay.v1",
|
|
39
|
-
"fabric.events.compatibility.v1",
|
|
40
|
-
"fabric.provenance.propagation.v1",
|
|
41
|
-
"fabric.consumers.tolerant-envelope.v1"
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _fabricorg_compiler = require("@fabricorg/compiler");
|
|
3
|
+
//#region index.ts
|
|
4
|
+
/** Checks a single module's own behaviour, verifiable against a governed host. */
|
|
5
|
+
const MODULE_CONFORMANCE_CHECKS = [
|
|
6
|
+
"fabric.idempotency.same-command-replay.v1",
|
|
7
|
+
"fabric.idempotency.conflicting-payload.v1",
|
|
8
|
+
"fabric.idempotency.concurrent-submission.v1",
|
|
9
|
+
"fabric.authorization.governing-moment.v1",
|
|
10
|
+
"fabric.authorization.offline-replay.v1",
|
|
11
|
+
"fabric.events.compatibility.v1",
|
|
12
|
+
"fabric.provenance.propagation.v1",
|
|
13
|
+
"fabric.consumers.tolerant-envelope.v1"
|
|
42
14
|
];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Checks that belong to a repository rather than a module. They are certification
|
|
17
|
+
* evidence like any other check, but no per-module suite can produce them.
|
|
18
|
+
*/
|
|
19
|
+
const PLATFORM_CONFORMANCE_CHECKS = ["fabric.documentation.parity.v1", "fabric.documentation.llms-contract.v1"];
|
|
20
|
+
const PHASE_1_CONFORMANCE_CHECKS = [...MODULE_CONFORMANCE_CHECKS, ...PLATFORM_CONFORMANCE_CHECKS];
|
|
48
21
|
var ConformanceFailure = class extends Error {
|
|
49
|
-
|
|
22
|
+
name = "ConformanceFailure";
|
|
50
23
|
};
|
|
51
24
|
function expect(condition, message) {
|
|
52
|
-
|
|
25
|
+
if (!condition) throw new ConformanceFailure(message);
|
|
53
26
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
27
|
+
const idempotentActions = (plan) => plan.actions.filter((action) => action.idempotency !== "prohibited" && action.idempotent);
|
|
28
|
+
const key = (label, action) => `conformance:${label}:${action.actionId}`;
|
|
29
|
+
const conflictReason = (conflict) => conflict.code ?? conflict.message;
|
|
57
30
|
async function conflictOf(run) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
31
|
+
try {
|
|
32
|
+
await run();
|
|
33
|
+
return {
|
|
34
|
+
thrown: false,
|
|
35
|
+
message: ""
|
|
36
|
+
};
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const code = error.code;
|
|
39
|
+
return {
|
|
40
|
+
thrown: true,
|
|
41
|
+
...code ? { code } : {},
|
|
42
|
+
message: error instanceof Error ? error.message : String(error)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
65
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Runs a check against every eligible action, not just the first. A module whose
|
|
48
|
+
* first action conforms and whose second does not must fail, or certification
|
|
49
|
+
* means nothing for the rest of the module.
|
|
50
|
+
*/
|
|
66
51
|
async function forEachAction(actions, reason, body) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
if (actions.length === 0) return {
|
|
53
|
+
status: "not-applicable",
|
|
54
|
+
reason
|
|
55
|
+
};
|
|
56
|
+
const evidence = [];
|
|
57
|
+
for (const action of actions) evidence.push(...await body(action));
|
|
58
|
+
return {
|
|
59
|
+
status: "passed",
|
|
60
|
+
evidence
|
|
61
|
+
};
|
|
71
62
|
}
|
|
72
63
|
function moduleConformanceChecks() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
id: "fabric.idempotency.same-command-replay.v1",
|
|
67
|
+
title: "replaying one command key with the same payload does not re-execute",
|
|
68
|
+
run: (harness, plan) => forEachAction(idempotentActions(plan), "module declares no idempotent action", async (action) => {
|
|
69
|
+
const input = {
|
|
70
|
+
actionId: action.actionId,
|
|
71
|
+
parameters: harness.parameters(action.actionId),
|
|
72
|
+
idempotencyKey: key("replay", action)
|
|
73
|
+
};
|
|
74
|
+
const first = await harness.submit(input);
|
|
75
|
+
const before = await harness.sideEffects?.(action.actionId);
|
|
76
|
+
const second = await harness.submit(input);
|
|
77
|
+
expect(first.actionInvocationId === second.actionInvocationId, `replay of ${action.actionId} produced a second invocation ${second.actionInvocationId} for key ${input.idempotencyKey}`);
|
|
78
|
+
const after = await harness.sideEffects?.(action.actionId);
|
|
79
|
+
if (before !== void 0 && after !== void 0) expect(after === before, `replay of ${action.actionId} returned the original invocation but produced ${after - before} further effects; the handler ran again`);
|
|
80
|
+
return [
|
|
81
|
+
`invocation:${first.actionInvocationId}`,
|
|
82
|
+
`key:${input.idempotencyKey}`,
|
|
83
|
+
`effects:${after ?? "unobserved"}`
|
|
84
|
+
];
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "fabric.idempotency.conflicting-payload.v1",
|
|
89
|
+
title: "reusing a command key with an altered payload is refused",
|
|
90
|
+
run: (harness, plan) => forEachAction(idempotentActions(plan), "module declares no idempotent action", async (action) => {
|
|
91
|
+
const idempotencyKey = key("conflict", action);
|
|
92
|
+
const parameters = harness.parameters(action.actionId);
|
|
93
|
+
await harness.submit({
|
|
94
|
+
actionId: action.actionId,
|
|
95
|
+
parameters,
|
|
96
|
+
idempotencyKey
|
|
97
|
+
});
|
|
98
|
+
const conflict = await conflictOf(() => harness.submit({
|
|
99
|
+
actionId: action.actionId,
|
|
100
|
+
parameters: {
|
|
101
|
+
...parameters,
|
|
102
|
+
"x-conformance-altered": true
|
|
103
|
+
},
|
|
104
|
+
idempotencyKey
|
|
105
|
+
}));
|
|
106
|
+
expect(conflict.thrown, `altered payload reusing key ${idempotencyKey} was accepted instead of refused`);
|
|
107
|
+
expect(conflict.code === "IDEMPOTENCY_CONFLICT" || /idempotenc/i.test(conflict.message), `altered payload for ${action.actionId} was refused, but not as an idempotency conflict: ${conflict.message}`);
|
|
108
|
+
return [`key:${idempotencyKey}`, `refusal:${conflictReason(conflict)}`];
|
|
109
|
+
})
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "fabric.idempotency.concurrent-submission.v1",
|
|
113
|
+
title: "concurrent identical submissions collapse to one invocation",
|
|
114
|
+
run: (harness, plan) => forEachAction(idempotentActions(plan), "module declares no idempotent action", async (action) => {
|
|
115
|
+
const input = {
|
|
116
|
+
actionId: action.actionId,
|
|
117
|
+
parameters: harness.parameters(action.actionId),
|
|
118
|
+
idempotencyKey: key("concurrent", action)
|
|
119
|
+
};
|
|
120
|
+
const before = await harness.sideEffects?.(action.actionId);
|
|
121
|
+
const results = await Promise.all([
|
|
122
|
+
harness.submit(input),
|
|
123
|
+
harness.submit(input),
|
|
124
|
+
harness.submit(input)
|
|
125
|
+
]);
|
|
126
|
+
const ids = new Set(results.map((result) => result.actionInvocationId));
|
|
127
|
+
expect(ids.size === 1, `concurrent submissions of ${action.actionId} produced ${ids.size} invocations: ${[...ids].join(", ")}`);
|
|
128
|
+
const after = await harness.sideEffects?.(action.actionId);
|
|
129
|
+
if (before !== void 0 && after !== void 0) {
|
|
130
|
+
const executions = after - before;
|
|
131
|
+
expect(executions <= 1, `${results.length} concurrent submissions of ${action.actionId} collapsed to one invocation but produced ${executions} executions' worth of effects`);
|
|
132
|
+
}
|
|
133
|
+
return [
|
|
134
|
+
`invocation:${[...ids][0]}`,
|
|
135
|
+
`attempts:${results.length}`,
|
|
136
|
+
`effects:${after ?? "unobserved"}`
|
|
137
|
+
];
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "fabric.authorization.governing-moment.v1",
|
|
142
|
+
title: "an authorization binding must match the action's declared governing moment",
|
|
143
|
+
run: (harness, plan) => forEachAction(plan.actions.filter((action) => action.authorityMoment !== void 0), "module declares no action with an authority moment", async (action) => {
|
|
144
|
+
const mismatched = action.authorityMoment === "capture" ? "execution" : "capture";
|
|
145
|
+
const rejection = await conflictOf(() => harness.submit({
|
|
146
|
+
actionId: action.actionId,
|
|
147
|
+
parameters: harness.parameters(action.actionId),
|
|
148
|
+
authorizationBinding: {
|
|
149
|
+
governingMoment: mismatched,
|
|
150
|
+
capturedAt: (/* @__PURE__ */ new Date(Date.now() - 6e4)).toISOString()
|
|
151
|
+
}
|
|
152
|
+
}));
|
|
153
|
+
expect(rejection.thrown, `a binding declaring governingMoment "${mismatched}" was accepted for ${action.actionId}, governed at "${action.authorityMoment}"`);
|
|
154
|
+
expect(/governingmoment|governing moment/i.test(conflictReason(rejection)), `the binding for ${action.actionId} was refused, but not for its governing moment: ${conflictReason(rejection)}`);
|
|
155
|
+
return [
|
|
156
|
+
`action:${action.actionId}`,
|
|
157
|
+
`declared:${action.authorityMoment}`,
|
|
158
|
+
`refused:${mismatched}`
|
|
159
|
+
];
|
|
160
|
+
})
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "fabric.authorization.offline-replay.v1",
|
|
164
|
+
title: "an offline capture replayed after authority is withdrawn is refused into reconciliation",
|
|
165
|
+
run: (harness, plan) => forEachAction(plan.actions.filter((action) => action.connectivity === "offline-capturable" && action.authorityMoment !== void 0 && action.authorityMoment !== "capture"), "module declares no offline-capturable action governed at execution", async (action) => {
|
|
166
|
+
await harness.revokeAuthority(action.actionId);
|
|
167
|
+
const replay = await harness.submit({
|
|
168
|
+
actionId: action.actionId,
|
|
169
|
+
parameters: harness.parameters(action.actionId),
|
|
170
|
+
idempotencyKey: key("offline-replay", action),
|
|
171
|
+
executionReason: "offline_replay"
|
|
172
|
+
});
|
|
173
|
+
expect(replay.status === "reconciliation_required", `replay of ${action.actionId} after authority withdrawal returned status "${replay.status}" instead of reconciliation_required`);
|
|
174
|
+
expect(replay.reconciliation !== void 0, `reconciliation_required was returned for ${action.actionId} without a reconciliation outcome`);
|
|
175
|
+
return [`action:${action.actionId}`, `reconciliation:${replay.reconciliation?.kind}`];
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "fabric.events.compatibility.v1",
|
|
180
|
+
title: "every consumed event version is one the module still declares",
|
|
181
|
+
async run(_harness, plan) {
|
|
182
|
+
if (plan.views.length === 0) return {
|
|
183
|
+
status: "not-applicable",
|
|
184
|
+
reason: "module declares no views"
|
|
185
|
+
};
|
|
186
|
+
const declared = new Map(plan.eventTypes.map((event) => [event.eventType, event.schemaVersion]));
|
|
187
|
+
const evidence = [];
|
|
188
|
+
for (const view of plan.views) for (const consumption of view.consumes) {
|
|
189
|
+
const current = declared.get(consumption.eventType);
|
|
190
|
+
if (current === void 0) continue;
|
|
191
|
+
expect(consumption.schemaVersions.includes(current), `view ${view.name}@${view.version} consumes ${consumption.eventType} at [${consumption.schemaVersions.join(", ")}] but the module now emits version ${current}`);
|
|
192
|
+
evidence.push(`${view.name}@${view.version}:${consumption.eventType}=${current}`);
|
|
193
|
+
}
|
|
194
|
+
if (evidence.length === 0) return {
|
|
195
|
+
status: "not-applicable",
|
|
196
|
+
reason: "no view consumes an event this module declares"
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
status: "passed",
|
|
200
|
+
evidence
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "fabric.provenance.propagation.v1",
|
|
206
|
+
title: "submission provenance reaches the events the action emits",
|
|
207
|
+
run: (harness, plan) => forEachAction(plan.actions.filter((action) => action.mutatesDomain), "module declares no domain-mutating action", async (action) => {
|
|
208
|
+
const correlationId = `conformance-corr-${action.actionId}`;
|
|
209
|
+
const submitted = await harness.submit({
|
|
210
|
+
actionId: action.actionId,
|
|
211
|
+
parameters: harness.parameters(action.actionId),
|
|
212
|
+
correlationId,
|
|
213
|
+
provenance: {
|
|
214
|
+
source: "system",
|
|
215
|
+
correlationId
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
const emitted = (await harness.events()).filter((event) => event.actionInvocationId === submitted.actionInvocationId);
|
|
219
|
+
expect(emitted.length > 0, `action ${action.actionId} mutates domain but emitted no events`);
|
|
220
|
+
for (const event of emitted) expect(event.correlationId === correlationId, `event ${event.eventType} carried correlationId ${String(event.correlationId)} instead of ${correlationId}`);
|
|
221
|
+
return [
|
|
222
|
+
`invocation:${submitted.actionInvocationId}`,
|
|
223
|
+
`events:${emitted.length}`,
|
|
224
|
+
`correlationId:${correlationId}`
|
|
225
|
+
];
|
|
226
|
+
})
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "fabric.consumers.tolerant-envelope.v1",
|
|
230
|
+
title: "emitted envelopes are complete, and a consumer tolerates fields it does not know",
|
|
231
|
+
run: (harness, plan) => forEachAction(plan.actions.filter((action) => action.mutatesDomain), "module declares no domain-mutating action", async (action) => {
|
|
232
|
+
const submitted = await harness.submit({
|
|
233
|
+
actionId: action.actionId,
|
|
234
|
+
parameters: harness.parameters(action.actionId),
|
|
235
|
+
idempotencyKey: key("envelope", action)
|
|
236
|
+
});
|
|
237
|
+
const emitted = (await harness.events()).filter((event) => event.actionInvocationId === submitted.actionInvocationId);
|
|
238
|
+
expect(emitted.length > 0, `action ${action.actionId} mutates domain but emitted no events`);
|
|
239
|
+
const required = [
|
|
240
|
+
"eventType",
|
|
241
|
+
"tenantId",
|
|
242
|
+
"spaceId",
|
|
243
|
+
"subjectType",
|
|
244
|
+
"subjectId",
|
|
245
|
+
"sequence",
|
|
246
|
+
"correlationId"
|
|
247
|
+
];
|
|
248
|
+
for (const event of emitted) for (const field of required) expect(event[field] !== void 0 && event[field] !== null, `event ${event.eventType} omits the canonical envelope field "${field}"`);
|
|
249
|
+
const evidence = emitted.map((event) => `${event.eventType}@${event.sequence}`);
|
|
250
|
+
if (!harness.acceptsEnvelope) return [...evidence, "consumer:unobserved"];
|
|
251
|
+
for (const event of emitted) {
|
|
252
|
+
const extended = {
|
|
253
|
+
...event,
|
|
254
|
+
"x-conformance-unknown": true,
|
|
255
|
+
consistency: "provisional-until-reconciled"
|
|
256
|
+
};
|
|
257
|
+
expect(await harness.acceptsEnvelope(extended), `a consumer rejected ${event.eventType} once it carried a field it did not recognise; additive event changes would break it`);
|
|
258
|
+
}
|
|
259
|
+
return [...evidence, `consumer:tolerated:${emitted.length}`];
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
];
|
|
250
263
|
}
|
|
251
264
|
function createCertificationResult(input) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
265
|
+
const results = new Map(input.checks.map((check) => [check.id, check]));
|
|
266
|
+
if (results.size !== input.checks.length) throw new Error("Certification evidence contains duplicate check identifiers.");
|
|
267
|
+
const missing = PHASE_1_CONFORMANCE_CHECKS.filter((id) => !results.has(id));
|
|
268
|
+
if (missing.length) throw new Error(`Certification evidence is missing mandatory checks: ${missing.join(", ")}`);
|
|
269
|
+
if (!/^[0-9a-f]{64}$/.test(input.manifestDigest)) throw new Error("Certification manifestDigest must be a lowercase SHA-256 digest.");
|
|
270
|
+
const generatorDigests = Object.entries(input.generatorDigests);
|
|
271
|
+
if (!generatorDigests.length || generatorDigests.some(([, digest]) => !/^[0-9a-f]{64}$/.test(digest))) throw new Error("Certification generatorDigests must contain SHA-256 evidence.");
|
|
272
|
+
if (input.checks.some((check) => check.status === "passed" && check.evidence.length === 0)) throw new Error("Passed certification checks require evidence references.");
|
|
273
|
+
return {
|
|
274
|
+
...input,
|
|
275
|
+
formatVersion: 1,
|
|
276
|
+
checks: [...input.checks].sort((a, b) => a.id.localeCompare(b.id)),
|
|
277
|
+
passed: input.checks.every((check) => check.status === "passed")
|
|
278
|
+
};
|
|
261
279
|
}
|
|
262
|
-
function buildModuleConformancePlan(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
280
|
+
function buildModuleConformancePlan(module, manifestDigest) {
|
|
281
|
+
return {
|
|
282
|
+
formatVersion: 1,
|
|
283
|
+
module: module.namespace,
|
|
284
|
+
manifestDigest,
|
|
285
|
+
requiredChecks: MODULE_CONFORMANCE_CHECKS,
|
|
286
|
+
platformChecks: PLATFORM_CONFORMANCE_CHECKS,
|
|
287
|
+
actions: module.actions.map((action) => ({
|
|
288
|
+
actionId: action.actionId,
|
|
289
|
+
version: action.version,
|
|
290
|
+
mutatesDomain: action.mutatesDomain,
|
|
291
|
+
idempotent: action.idempotent,
|
|
292
|
+
...action.execution?.idempotency ? { idempotency: action.execution.idempotency } : {},
|
|
293
|
+
...action.execution?.authorityMoment ? { authorityMoment: action.execution.authorityMoment } : {},
|
|
294
|
+
...action.execution?.connectivity ? { connectivity: action.execution.connectivity } : {},
|
|
295
|
+
...action.execution?.consistency ? { consistency: action.execution.consistency } : {}
|
|
296
|
+
})),
|
|
297
|
+
views: module.views.map((view) => ({
|
|
298
|
+
name: view.name,
|
|
299
|
+
version: view.version,
|
|
300
|
+
...view.parameterSchema ? { parameterSchema: view.parameterSchema } : {},
|
|
301
|
+
consumes: view.consumes.map((consumption) => ({
|
|
302
|
+
eventType: consumption.eventType,
|
|
303
|
+
schemaVersions: [...consumption.schemaVersions]
|
|
304
|
+
}))
|
|
305
|
+
})),
|
|
306
|
+
eventTypes: module.eventTypes.map((event) => ({
|
|
307
|
+
eventType: event.eventType,
|
|
308
|
+
schemaVersion: event.schemaVersion
|
|
309
|
+
}))
|
|
310
|
+
};
|
|
287
311
|
}
|
|
288
312
|
function suiteSource(plan, generator) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
313
|
+
return [
|
|
314
|
+
`// Generated by ${generator.name}@${generator.version}. Do not edit.`,
|
|
315
|
+
`// Manifest digest: ${plan.manifestDigest}`,
|
|
316
|
+
`// Supply ./conformance.harness with a createConformanceHarness() factory returning a fresh, isolated harness.`,
|
|
317
|
+
"import { expect, it } from \"vitest\";",
|
|
318
|
+
"import { moduleConformanceChecks, type ModuleConformancePlan } from \"@fabricorg/gen-conformance\";",
|
|
319
|
+
"import { createConformanceHarness } from \"./conformance.harness\";",
|
|
320
|
+
"",
|
|
321
|
+
`export const plan: ModuleConformancePlan = ${JSON.stringify(plan, null, 2)};`,
|
|
322
|
+
"",
|
|
323
|
+
"for (const check of moduleConformanceChecks()) {",
|
|
324
|
+
" it(`${check.id} — ${check.title}`, async () => {",
|
|
325
|
+
" const outcome = await check.run(await createConformanceHarness(), plan);",
|
|
326
|
+
" expect([\"passed\", \"not-applicable\"]).toContain(outcome.status);",
|
|
327
|
+
" });",
|
|
328
|
+
"}",
|
|
329
|
+
""
|
|
330
|
+
].join("\n");
|
|
307
331
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
332
|
+
const conformanceGenerator = {
|
|
333
|
+
name: "@fabricorg/gen-conformance",
|
|
334
|
+
version: "1.0.7",
|
|
335
|
+
generate(ir) {
|
|
336
|
+
return ir.modules.flatMap((module) => {
|
|
337
|
+
const plan = buildModuleConformancePlan(module, ir.manifestDigest);
|
|
338
|
+
return [{
|
|
339
|
+
path: `${module.namespace}/conformance-plan.json`,
|
|
340
|
+
content: (0, _fabricorg_compiler.stableStringify)({
|
|
341
|
+
...plan,
|
|
342
|
+
generator: {
|
|
343
|
+
name: conformanceGenerator.name,
|
|
344
|
+
version: conformanceGenerator.version
|
|
345
|
+
},
|
|
346
|
+
compilerVersion: ir.compilerVersion
|
|
347
|
+
})
|
|
348
|
+
}, {
|
|
349
|
+
path: `${module.namespace}/conformance.test.ts`,
|
|
350
|
+
content: suiteSource(plan, {
|
|
351
|
+
name: conformanceGenerator.name,
|
|
352
|
+
version: conformanceGenerator.version
|
|
353
|
+
})
|
|
354
|
+
}];
|
|
355
|
+
});
|
|
356
|
+
}
|
|
330
357
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
});
|
|
358
|
+
//#endregion
|
|
359
|
+
exports.MODULE_CONFORMANCE_CHECKS = MODULE_CONFORMANCE_CHECKS;
|
|
360
|
+
exports.PHASE_1_CONFORMANCE_CHECKS = PHASE_1_CONFORMANCE_CHECKS;
|
|
361
|
+
exports.PLATFORM_CONFORMANCE_CHECKS = PLATFORM_CONFORMANCE_CHECKS;
|
|
362
|
+
exports.buildModuleConformancePlan = buildModuleConformancePlan;
|
|
363
|
+
exports.conformanceGenerator = conformanceGenerator;
|
|
364
|
+
exports.createCertificationResult = createCertificationResult;
|
|
365
|
+
exports.moduleConformanceChecks = moduleConformanceChecks;
|
|
366
|
+
|
|
341
367
|
//# sourceMappingURL=index.cjs.map
|