@fabricorg/gen-conformance 0.1.1 → 0.2.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/CHANGELOG.md +30 -0
- package/dist/index.cjs +289 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -2
- package/dist/index.d.ts +127 -2
- package/dist/index.js +281 -6
- package/dist/index.js.map +1 -1
- package/dist/memory.cjs +114 -0
- package/dist/memory.cjs.map +1 -0
- package/dist/memory.d.cts +19 -0
- package/dist/memory.d.ts +19 -0
- package/dist/memory.js +89 -0
- package/dist/memory.js.map +1 -0
- package/package.json +19 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { GeneratorTarget } from '@fabricorg/compiler';
|
|
2
|
+
import { ModuleManifest } from '@fabricorg/platform';
|
|
2
3
|
|
|
4
|
+
/** Checks a single module's own behaviour, verifiable against a governed host. */
|
|
5
|
+
declare const MODULE_CONFORMANCE_CHECKS: readonly ["fabric.idempotency.same-command-replay.v1", "fabric.idempotency.conflicting-payload.v1", "fabric.idempotency.concurrent-submission.v1", "fabric.authorization.governing-moment.v1", "fabric.authorization.offline-replay.v1", "fabric.events.compatibility.v1", "fabric.provenance.propagation.v1", "fabric.consumers.tolerant-envelope.v1"];
|
|
6
|
+
/**
|
|
7
|
+
* Checks that belong to a repository rather than a module. They are certification
|
|
8
|
+
* evidence like any other check, but no per-module suite can produce them.
|
|
9
|
+
*/
|
|
10
|
+
declare const PLATFORM_CONFORMANCE_CHECKS: readonly ["fabric.documentation.parity.v1", "fabric.documentation.llms-contract.v1"];
|
|
3
11
|
declare const PHASE_1_CONFORMANCE_CHECKS: readonly ["fabric.idempotency.same-command-replay.v1", "fabric.idempotency.conflicting-payload.v1", "fabric.idempotency.concurrent-submission.v1", "fabric.authorization.governing-moment.v1", "fabric.authorization.offline-replay.v1", "fabric.events.compatibility.v1", "fabric.provenance.propagation.v1", "fabric.consumers.tolerant-envelope.v1", "fabric.documentation.parity.v1", "fabric.documentation.llms-contract.v1"];
|
|
4
|
-
type
|
|
12
|
+
type ModuleConformanceCheckId = typeof MODULE_CONFORMANCE_CHECKS[number];
|
|
13
|
+
type PlatformConformanceCheckId = typeof PLATFORM_CONFORMANCE_CHECKS[number];
|
|
14
|
+
type Phase1ConformanceCheckId = ModuleConformanceCheckId | PlatformConformanceCheckId;
|
|
5
15
|
interface ConformanceEvidence {
|
|
6
16
|
id: Phase1ConformanceCheckId;
|
|
7
17
|
status: "passed" | "failed";
|
|
@@ -15,7 +25,122 @@ interface CertificationResult {
|
|
|
15
25
|
checks: ConformanceEvidence[];
|
|
16
26
|
passed: boolean;
|
|
17
27
|
}
|
|
28
|
+
interface PlannedAction {
|
|
29
|
+
actionId: string;
|
|
30
|
+
version: number;
|
|
31
|
+
mutatesDomain: boolean;
|
|
32
|
+
idempotent: boolean;
|
|
33
|
+
idempotency?: "required" | "supported" | "prohibited";
|
|
34
|
+
authorityMoment?: "capture" | "execution" | "both";
|
|
35
|
+
connectivity?: "online-required" | "offline-capturable";
|
|
36
|
+
consistency?: "authoritative-now" | "provisional-until-reconciled";
|
|
37
|
+
}
|
|
38
|
+
interface PlannedView {
|
|
39
|
+
name: string;
|
|
40
|
+
version: string;
|
|
41
|
+
consumes: Array<{
|
|
42
|
+
eventType: string;
|
|
43
|
+
schemaVersions: number[];
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
interface ModuleConformancePlan {
|
|
47
|
+
formatVersion: 1;
|
|
48
|
+
module: string;
|
|
49
|
+
manifestDigest: string;
|
|
50
|
+
requiredChecks: readonly ModuleConformanceCheckId[];
|
|
51
|
+
platformChecks: readonly PlatformConformanceCheckId[];
|
|
52
|
+
actions: PlannedAction[];
|
|
53
|
+
views: PlannedView[];
|
|
54
|
+
eventTypes: Array<{
|
|
55
|
+
eventType: string;
|
|
56
|
+
schemaVersion: number;
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
interface ConformanceScope {
|
|
60
|
+
tenantId: string;
|
|
61
|
+
spaceId: string;
|
|
62
|
+
actorId: string;
|
|
63
|
+
actorType: string;
|
|
64
|
+
}
|
|
65
|
+
interface ConformanceSubmitInput {
|
|
66
|
+
actionId: string;
|
|
67
|
+
parameters: Record<string, unknown>;
|
|
68
|
+
idempotencyKey?: string;
|
|
69
|
+
correlationId?: string;
|
|
70
|
+
provenance?: {
|
|
71
|
+
source: string;
|
|
72
|
+
correlationId: string;
|
|
73
|
+
auditAttributes?: Record<string, string>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Declares only the intent of the binding. The harness completes tenant, actor,
|
|
77
|
+
* action and parameter-digest identity, because only it knows the command shape.
|
|
78
|
+
*/
|
|
79
|
+
authorizationBinding?: {
|
|
80
|
+
governingMoment: "capture" | "execution" | "both";
|
|
81
|
+
capturedAt?: string;
|
|
82
|
+
expiresAt?: string;
|
|
83
|
+
};
|
|
84
|
+
executionReason?: "initial" | "offline_replay";
|
|
85
|
+
}
|
|
86
|
+
interface ConformanceSubmitResult {
|
|
87
|
+
actionInvocationId: string;
|
|
88
|
+
status: string;
|
|
89
|
+
reconciliation?: {
|
|
90
|
+
kind: string;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
interface ConformanceEventEnvelope {
|
|
94
|
+
eventType: string;
|
|
95
|
+
correlationId?: string;
|
|
96
|
+
actionInvocationId?: string;
|
|
97
|
+
tenantId?: string;
|
|
98
|
+
spaceId?: string;
|
|
99
|
+
subjectType?: string;
|
|
100
|
+
subjectId?: string;
|
|
101
|
+
sequence?: number;
|
|
102
|
+
[key: string]: unknown;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* What an application supplies so its module's generated suite can run. Structural
|
|
106
|
+
* by design: any store or host that satisfies it is a valid subject.
|
|
107
|
+
*/
|
|
108
|
+
interface ConformanceHarness {
|
|
109
|
+
scope: ConformanceScope;
|
|
110
|
+
/** Representative parameters for an action; the module owns its own fixtures. */
|
|
111
|
+
parameters(actionId: string): Record<string, unknown>;
|
|
112
|
+
submit(input: ConformanceSubmitInput): Promise<ConformanceSubmitResult>;
|
|
113
|
+
events(): Promise<ConformanceEventEnvelope[]>;
|
|
114
|
+
/** Withdraw execution-time authority so a replay must be refused. */
|
|
115
|
+
revokeAuthority(actionId: string): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Observable effects an action has produced so far — events appended, rows written,
|
|
118
|
+
* whatever the application counts. Supply it and the idempotency checks prove the
|
|
119
|
+
* handler did not run twice, rather than only that the same identifier came back.
|
|
120
|
+
*/
|
|
121
|
+
sideEffects?(actionId: string): Promise<number>;
|
|
122
|
+
/**
|
|
123
|
+
* Hand a real emitted envelope to a real consumer. Supply it and the
|
|
124
|
+
* tolerant-envelope check stops being producer-side only: it proves a consumer
|
|
125
|
+
* survives fields it does not recognise, which is what tolerance means.
|
|
126
|
+
*/
|
|
127
|
+
acceptsEnvelope?(envelope: ConformanceEventEnvelope): Promise<boolean> | boolean;
|
|
128
|
+
}
|
|
129
|
+
type ConformanceOutcome = {
|
|
130
|
+
status: "passed";
|
|
131
|
+
evidence: string[];
|
|
132
|
+
} | {
|
|
133
|
+
status: "not-applicable";
|
|
134
|
+
reason: string;
|
|
135
|
+
};
|
|
136
|
+
interface ConformanceCheck {
|
|
137
|
+
id: ModuleConformanceCheckId;
|
|
138
|
+
title: string;
|
|
139
|
+
run(harness: ConformanceHarness, plan: ModuleConformancePlan): Promise<ConformanceOutcome>;
|
|
140
|
+
}
|
|
141
|
+
declare function moduleConformanceChecks(): ConformanceCheck[];
|
|
18
142
|
declare function createCertificationResult(input: Omit<CertificationResult, "formatVersion" | "passed">): CertificationResult;
|
|
143
|
+
declare function buildModuleConformancePlan(module: ModuleManifest, manifestDigest: string): ModuleConformancePlan;
|
|
19
144
|
declare const conformanceGenerator: GeneratorTarget;
|
|
20
145
|
|
|
21
|
-
export { type CertificationResult, type ConformanceEvidence, PHASE_1_CONFORMANCE_CHECKS, type Phase1ConformanceCheckId, conformanceGenerator, createCertificationResult };
|
|
146
|
+
export { type CertificationResult, type ConformanceCheck, type ConformanceEventEnvelope, type ConformanceEvidence, type ConformanceHarness, type ConformanceOutcome, type ConformanceScope, type ConformanceSubmitInput, type ConformanceSubmitResult, MODULE_CONFORMANCE_CHECKS, type ModuleConformanceCheckId, type ModuleConformancePlan, PHASE_1_CONFORMANCE_CHECKS, PLATFORM_CONFORMANCE_CHECKS, type Phase1ConformanceCheckId, type PlannedAction, type PlannedView, type PlatformConformanceCheckId, buildModuleConformancePlan, conformanceGenerator, createCertificationResult, moduleConformanceChecks };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// index.ts
|
|
2
2
|
import { stableStringify } from "@fabricorg/compiler";
|
|
3
|
-
var
|
|
3
|
+
var MODULE_CONFORMANCE_CHECKS = [
|
|
4
4
|
"fabric.idempotency.same-command-replay.v1",
|
|
5
5
|
"fabric.idempotency.conflicting-payload.v1",
|
|
6
6
|
"fabric.idempotency.concurrent-submission.v1",
|
|
@@ -8,10 +8,216 @@ var PHASE_1_CONFORMANCE_CHECKS = [
|
|
|
8
8
|
"fabric.authorization.offline-replay.v1",
|
|
9
9
|
"fabric.events.compatibility.v1",
|
|
10
10
|
"fabric.provenance.propagation.v1",
|
|
11
|
-
"fabric.consumers.tolerant-envelope.v1"
|
|
11
|
+
"fabric.consumers.tolerant-envelope.v1"
|
|
12
|
+
];
|
|
13
|
+
var PLATFORM_CONFORMANCE_CHECKS = [
|
|
12
14
|
"fabric.documentation.parity.v1",
|
|
13
15
|
"fabric.documentation.llms-contract.v1"
|
|
14
16
|
];
|
|
17
|
+
var PHASE_1_CONFORMANCE_CHECKS = [...MODULE_CONFORMANCE_CHECKS, ...PLATFORM_CONFORMANCE_CHECKS];
|
|
18
|
+
var ConformanceFailure = class extends Error {
|
|
19
|
+
name = "ConformanceFailure";
|
|
20
|
+
};
|
|
21
|
+
function expect(condition, message) {
|
|
22
|
+
if (!condition) throw new ConformanceFailure(message);
|
|
23
|
+
}
|
|
24
|
+
var idempotentActions = (plan) => plan.actions.filter((action) => action.idempotency !== "prohibited" && action.idempotent);
|
|
25
|
+
var key = (label, action) => `conformance:${label}:${action.actionId}`;
|
|
26
|
+
var conflictReason = (conflict) => conflict.code ?? conflict.message;
|
|
27
|
+
async function conflictOf(run) {
|
|
28
|
+
try {
|
|
29
|
+
await run();
|
|
30
|
+
return { thrown: false, message: "" };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
const code = error.code;
|
|
33
|
+
return { thrown: true, ...code ? { code } : {}, message: error instanceof Error ? error.message : String(error) };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function forEachAction(actions, reason, body) {
|
|
37
|
+
if (actions.length === 0) return { status: "not-applicable", reason };
|
|
38
|
+
const evidence = [];
|
|
39
|
+
for (const action of actions) evidence.push(...await body(action));
|
|
40
|
+
return { status: "passed", evidence };
|
|
41
|
+
}
|
|
42
|
+
function moduleConformanceChecks() {
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
id: "fabric.idempotency.same-command-replay.v1",
|
|
46
|
+
title: "replaying one command key with the same payload does not re-execute",
|
|
47
|
+
run: (harness, plan) => forEachAction(idempotentActions(plan), "module declares no idempotent action", async (action) => {
|
|
48
|
+
const input = { actionId: action.actionId, parameters: harness.parameters(action.actionId), idempotencyKey: key("replay", action) };
|
|
49
|
+
const first = await harness.submit(input);
|
|
50
|
+
const before = await harness.sideEffects?.(action.actionId);
|
|
51
|
+
const second = await harness.submit(input);
|
|
52
|
+
expect(first.actionInvocationId === second.actionInvocationId, `replay of ${action.actionId} produced a second invocation ${second.actionInvocationId} for key ${input.idempotencyKey}`);
|
|
53
|
+
const after = await harness.sideEffects?.(action.actionId);
|
|
54
|
+
if (before !== void 0 && after !== void 0) {
|
|
55
|
+
expect(after === before, `replay of ${action.actionId} returned the original invocation but produced ${after - before} further effects; the handler ran again`);
|
|
56
|
+
}
|
|
57
|
+
return [`invocation:${first.actionInvocationId}`, `key:${input.idempotencyKey}`, `effects:${after ?? "unobserved"}`];
|
|
58
|
+
})
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "fabric.idempotency.conflicting-payload.v1",
|
|
62
|
+
title: "reusing a command key with an altered payload is refused",
|
|
63
|
+
run: (harness, plan) => forEachAction(idempotentActions(plan), "module declares no idempotent action", async (action) => {
|
|
64
|
+
const idempotencyKey = key("conflict", action);
|
|
65
|
+
const parameters = harness.parameters(action.actionId);
|
|
66
|
+
await harness.submit({ actionId: action.actionId, parameters, idempotencyKey });
|
|
67
|
+
const conflict = await conflictOf(() => harness.submit({
|
|
68
|
+
actionId: action.actionId,
|
|
69
|
+
parameters: { ...parameters, "x-conformance-altered": true },
|
|
70
|
+
idempotencyKey
|
|
71
|
+
}));
|
|
72
|
+
expect(conflict.thrown, `altered payload reusing key ${idempotencyKey} was accepted instead of refused`);
|
|
73
|
+
expect(
|
|
74
|
+
conflict.code === "IDEMPOTENCY_CONFLICT" || /idempotenc/i.test(conflict.message),
|
|
75
|
+
`altered payload for ${action.actionId} was refused, but not as an idempotency conflict: ${conflict.message}`
|
|
76
|
+
);
|
|
77
|
+
return [`key:${idempotencyKey}`, `refusal:${conflictReason(conflict)}`];
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "fabric.idempotency.concurrent-submission.v1",
|
|
82
|
+
title: "concurrent identical submissions collapse to one invocation",
|
|
83
|
+
run: (harness, plan) => forEachAction(idempotentActions(plan), "module declares no idempotent action", async (action) => {
|
|
84
|
+
const input = { actionId: action.actionId, parameters: harness.parameters(action.actionId), idempotencyKey: key("concurrent", action) };
|
|
85
|
+
const before = await harness.sideEffects?.(action.actionId);
|
|
86
|
+
const results = await Promise.all([harness.submit(input), harness.submit(input), harness.submit(input)]);
|
|
87
|
+
const ids = new Set(results.map((result) => result.actionInvocationId));
|
|
88
|
+
expect(ids.size === 1, `concurrent submissions of ${action.actionId} produced ${ids.size} invocations: ${[...ids].join(", ")}`);
|
|
89
|
+
const after = await harness.sideEffects?.(action.actionId);
|
|
90
|
+
if (before !== void 0 && after !== void 0) {
|
|
91
|
+
const executions = after - before;
|
|
92
|
+
expect(executions <= 1, `${results.length} concurrent submissions of ${action.actionId} collapsed to one invocation but produced ${executions} executions' worth of effects`);
|
|
93
|
+
}
|
|
94
|
+
return [`invocation:${[...ids][0]}`, `attempts:${results.length}`, `effects:${after ?? "unobserved"}`];
|
|
95
|
+
})
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "fabric.authorization.governing-moment.v1",
|
|
99
|
+
title: "an authorization binding must match the action's declared governing moment",
|
|
100
|
+
run: (harness, plan) => forEachAction(
|
|
101
|
+
plan.actions.filter((action) => action.authorityMoment !== void 0),
|
|
102
|
+
"module declares no action with an authority moment",
|
|
103
|
+
async (action) => {
|
|
104
|
+
const mismatched = action.authorityMoment === "capture" ? "execution" : "capture";
|
|
105
|
+
const rejection = await conflictOf(() => harness.submit({
|
|
106
|
+
actionId: action.actionId,
|
|
107
|
+
parameters: harness.parameters(action.actionId),
|
|
108
|
+
authorizationBinding: { governingMoment: mismatched, capturedAt: new Date(Date.now() - 6e4).toISOString() }
|
|
109
|
+
}));
|
|
110
|
+
expect(rejection.thrown, `a binding declaring governingMoment "${mismatched}" was accepted for ${action.actionId}, governed at "${action.authorityMoment}"`);
|
|
111
|
+
expect(
|
|
112
|
+
/governingmoment|governing moment/i.test(conflictReason(rejection)),
|
|
113
|
+
`the binding for ${action.actionId} was refused, but not for its governing moment: ${conflictReason(rejection)}`
|
|
114
|
+
);
|
|
115
|
+
return [`action:${action.actionId}`, `declared:${action.authorityMoment}`, `refused:${mismatched}`];
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "fabric.authorization.offline-replay.v1",
|
|
121
|
+
title: "an offline capture replayed after authority is withdrawn is refused into reconciliation",
|
|
122
|
+
run: (harness, plan) => forEachAction(
|
|
123
|
+
plan.actions.filter((action) => action.connectivity === "offline-capturable" && action.authorityMoment !== void 0 && action.authorityMoment !== "capture"),
|
|
124
|
+
"module declares no offline-capturable action governed at execution",
|
|
125
|
+
async (action) => {
|
|
126
|
+
await harness.revokeAuthority(action.actionId);
|
|
127
|
+
const replay = await harness.submit({
|
|
128
|
+
actionId: action.actionId,
|
|
129
|
+
parameters: harness.parameters(action.actionId),
|
|
130
|
+
idempotencyKey: key("offline-replay", action),
|
|
131
|
+
executionReason: "offline_replay"
|
|
132
|
+
});
|
|
133
|
+
expect(
|
|
134
|
+
replay.status === "reconciliation_required",
|
|
135
|
+
`replay of ${action.actionId} after authority withdrawal returned status "${replay.status}" instead of reconciliation_required`
|
|
136
|
+
);
|
|
137
|
+
expect(replay.reconciliation !== void 0, `reconciliation_required was returned for ${action.actionId} without a reconciliation outcome`);
|
|
138
|
+
return [`action:${action.actionId}`, `reconciliation:${replay.reconciliation?.kind}`];
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "fabric.events.compatibility.v1",
|
|
144
|
+
title: "every consumed event version is one the module still declares",
|
|
145
|
+
async run(_harness, plan) {
|
|
146
|
+
if (plan.views.length === 0) return { status: "not-applicable", reason: "module declares no views" };
|
|
147
|
+
const declared = new Map(plan.eventTypes.map((event) => [event.eventType, event.schemaVersion]));
|
|
148
|
+
const evidence = [];
|
|
149
|
+
for (const view of plan.views) {
|
|
150
|
+
for (const consumption of view.consumes) {
|
|
151
|
+
const current = declared.get(consumption.eventType);
|
|
152
|
+
if (current === void 0) continue;
|
|
153
|
+
expect(
|
|
154
|
+
consumption.schemaVersions.includes(current),
|
|
155
|
+
`view ${view.name}@${view.version} consumes ${consumption.eventType} at [${consumption.schemaVersions.join(", ")}] but the module now emits version ${current}`
|
|
156
|
+
);
|
|
157
|
+
evidence.push(`${view.name}@${view.version}:${consumption.eventType}=${current}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (evidence.length === 0) return { status: "not-applicable", reason: "no view consumes an event this module declares" };
|
|
161
|
+
return { status: "passed", evidence };
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "fabric.provenance.propagation.v1",
|
|
166
|
+
title: "submission provenance reaches the events the action emits",
|
|
167
|
+
run: (harness, plan) => forEachAction(
|
|
168
|
+
plan.actions.filter((action) => action.mutatesDomain),
|
|
169
|
+
"module declares no domain-mutating action",
|
|
170
|
+
async (action) => {
|
|
171
|
+
const correlationId = `conformance-corr-${action.actionId}`;
|
|
172
|
+
const submitted = await harness.submit({
|
|
173
|
+
actionId: action.actionId,
|
|
174
|
+
parameters: harness.parameters(action.actionId),
|
|
175
|
+
correlationId,
|
|
176
|
+
// Audit attributes are allowlisted per application, so provenance identity is asserted on correlation alone.
|
|
177
|
+
provenance: { source: "system", correlationId }
|
|
178
|
+
});
|
|
179
|
+
const emitted = (await harness.events()).filter((event) => event.actionInvocationId === submitted.actionInvocationId);
|
|
180
|
+
expect(emitted.length > 0, `action ${action.actionId} mutates domain but emitted no events`);
|
|
181
|
+
for (const event of emitted) {
|
|
182
|
+
expect(event.correlationId === correlationId, `event ${event.eventType} carried correlationId ${String(event.correlationId)} instead of ${correlationId}`);
|
|
183
|
+
}
|
|
184
|
+
return [`invocation:${submitted.actionInvocationId}`, `events:${emitted.length}`, `correlationId:${correlationId}`];
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: "fabric.consumers.tolerant-envelope.v1",
|
|
190
|
+
title: "emitted envelopes are complete, and a consumer tolerates fields it does not know",
|
|
191
|
+
run: (harness, plan) => forEachAction(
|
|
192
|
+
plan.actions.filter((action) => action.mutatesDomain),
|
|
193
|
+
"module declares no domain-mutating action",
|
|
194
|
+
async (action) => {
|
|
195
|
+
const submitted = await harness.submit({
|
|
196
|
+
actionId: action.actionId,
|
|
197
|
+
parameters: harness.parameters(action.actionId),
|
|
198
|
+
idempotencyKey: key("envelope", action)
|
|
199
|
+
});
|
|
200
|
+
const emitted = (await harness.events()).filter((event) => event.actionInvocationId === submitted.actionInvocationId);
|
|
201
|
+
expect(emitted.length > 0, `action ${action.actionId} mutates domain but emitted no events`);
|
|
202
|
+
const required = ["eventType", "tenantId", "spaceId", "subjectType", "subjectId", "sequence", "correlationId"];
|
|
203
|
+
for (const event of emitted) {
|
|
204
|
+
for (const field of required) {
|
|
205
|
+
expect(event[field] !== void 0 && event[field] !== null, `event ${event.eventType} omits the canonical envelope field "${field}"`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const evidence = emitted.map((event) => `${event.eventType}@${event.sequence}`);
|
|
209
|
+
if (!harness.acceptsEnvelope) return [...evidence, "consumer:unobserved"];
|
|
210
|
+
for (const event of emitted) {
|
|
211
|
+
const extended = { ...event, "x-conformance-unknown": true, consistency: "provisional-until-reconciled" };
|
|
212
|
+
const accepted = await harness.acceptsEnvelope(extended);
|
|
213
|
+
expect(accepted, `a consumer rejected ${event.eventType} once it carried a field it did not recognise; additive event changes would break it`);
|
|
214
|
+
}
|
|
215
|
+
return [...evidence, `consumer:tolerated:${emitted.length}`];
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
];
|
|
220
|
+
}
|
|
15
221
|
function createCertificationResult(input) {
|
|
16
222
|
const results = new Map(input.checks.map((check) => [check.id, check]));
|
|
17
223
|
if (results.size !== input.checks.length) throw new Error("Certification evidence contains duplicate check identifiers.");
|
|
@@ -23,12 +229,81 @@ function createCertificationResult(input) {
|
|
|
23
229
|
if (input.checks.some((check) => check.status === "passed" && check.evidence.length === 0)) throw new Error("Passed certification checks require evidence references.");
|
|
24
230
|
return { ...input, formatVersion: 1, checks: [...input.checks].sort((a, b) => a.id.localeCompare(b.id)), passed: input.checks.every((check) => check.status === "passed") };
|
|
25
231
|
}
|
|
26
|
-
|
|
27
|
-
return
|
|
28
|
-
|
|
232
|
+
function buildModuleConformancePlan(module, manifestDigest) {
|
|
233
|
+
return {
|
|
234
|
+
formatVersion: 1,
|
|
235
|
+
module: module.namespace,
|
|
236
|
+
manifestDigest,
|
|
237
|
+
requiredChecks: MODULE_CONFORMANCE_CHECKS,
|
|
238
|
+
platformChecks: PLATFORM_CONFORMANCE_CHECKS,
|
|
239
|
+
actions: module.actions.map((action) => ({
|
|
240
|
+
actionId: action.actionId,
|
|
241
|
+
version: action.version,
|
|
242
|
+
mutatesDomain: action.mutatesDomain,
|
|
243
|
+
idempotent: action.idempotent,
|
|
244
|
+
...action.execution?.idempotency ? { idempotency: action.execution.idempotency } : {},
|
|
245
|
+
...action.execution?.authorityMoment ? { authorityMoment: action.execution.authorityMoment } : {},
|
|
246
|
+
...action.execution?.connectivity ? { connectivity: action.execution.connectivity } : {},
|
|
247
|
+
...action.execution?.consistency ? { consistency: action.execution.consistency } : {}
|
|
248
|
+
})),
|
|
249
|
+
views: module.views.map((view) => ({
|
|
250
|
+
name: view.name,
|
|
251
|
+
version: view.version,
|
|
252
|
+
consumes: view.consumes.map((consumption) => ({ eventType: consumption.eventType, schemaVersions: [...consumption.schemaVersions] }))
|
|
253
|
+
})),
|
|
254
|
+
eventTypes: module.eventTypes.map((event) => ({ eventType: event.eventType, schemaVersion: event.schemaVersion }))
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function suiteSource(plan, generator) {
|
|
258
|
+
return [
|
|
259
|
+
`// Generated by ${generator.name}@${generator.version}. Do not edit.`,
|
|
260
|
+
`// Manifest digest: ${plan.manifestDigest}`,
|
|
261
|
+
`// Supply ./conformance.harness with a createConformanceHarness() factory returning a fresh, isolated harness.`,
|
|
262
|
+
'import { expect, it } from "vitest";',
|
|
263
|
+
'import { moduleConformanceChecks, type ModuleConformancePlan } from "@fabricorg/gen-conformance";',
|
|
264
|
+
'import { createConformanceHarness } from "./conformance.harness";',
|
|
265
|
+
"",
|
|
266
|
+
`export const plan: ModuleConformancePlan = ${JSON.stringify(plan, null, 2)};`,
|
|
267
|
+
"",
|
|
268
|
+
"for (const check of moduleConformanceChecks()) {",
|
|
269
|
+
" it(`${check.id} \u2014 ${check.title}`, async () => {",
|
|
270
|
+
" const outcome = await check.run(await createConformanceHarness(), plan);",
|
|
271
|
+
' expect(["passed", "not-applicable"]).toContain(outcome.status);',
|
|
272
|
+
" });",
|
|
273
|
+
"}",
|
|
274
|
+
""
|
|
275
|
+
].join("\n");
|
|
276
|
+
}
|
|
277
|
+
var conformanceGenerator = {
|
|
278
|
+
name: "@fabricorg/gen-conformance",
|
|
279
|
+
version: "0.2.0",
|
|
280
|
+
generate(ir) {
|
|
281
|
+
return ir.modules.flatMap((module) => {
|
|
282
|
+
const plan = buildModuleConformancePlan(module, ir.manifestDigest);
|
|
283
|
+
return [
|
|
284
|
+
{
|
|
285
|
+
path: `${module.namespace}/conformance-plan.json`,
|
|
286
|
+
content: stableStringify({
|
|
287
|
+
...plan,
|
|
288
|
+
generator: { name: conformanceGenerator.name, version: conformanceGenerator.version },
|
|
289
|
+
compilerVersion: ir.compilerVersion
|
|
290
|
+
})
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
path: `${module.namespace}/conformance.test.ts`,
|
|
294
|
+
content: suiteSource(plan, { name: conformanceGenerator.name, version: conformanceGenerator.version })
|
|
295
|
+
}
|
|
296
|
+
];
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
};
|
|
29
300
|
export {
|
|
301
|
+
MODULE_CONFORMANCE_CHECKS,
|
|
30
302
|
PHASE_1_CONFORMANCE_CHECKS,
|
|
303
|
+
PLATFORM_CONFORMANCE_CHECKS,
|
|
304
|
+
buildModuleConformancePlan,
|
|
31
305
|
conformanceGenerator,
|
|
32
|
-
createCertificationResult
|
|
306
|
+
createCertificationResult,
|
|
307
|
+
moduleConformanceChecks
|
|
33
308
|
};
|
|
34
309
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["import { stableStringify, type FabricCompilerIr, type GeneratorTarget } from \"@fabricorg/compiler\";\n\nexport const PHASE_1_CONFORMANCE_CHECKS = [\n\t\"fabric.idempotency.same-command-replay.v1\", \"fabric.idempotency.conflicting-payload.v1\",\n\t\"fabric.idempotency.concurrent-submission.v1\", \"fabric.authorization.governing-moment.v1\",\n\t\"fabric.authorization.offline-replay.v1\", \"fabric.events.compatibility.v1\",\n\t\"fabric.provenance.propagation.v1\", \"fabric.consumers.tolerant-envelope.v1\",\n\t\"fabric.documentation.parity.v1\", \"fabric.documentation.llms-contract.v1\",\n] as const;\nexport type Phase1ConformanceCheckId = typeof PHASE_1_CONFORMANCE_CHECKS[number];\nexport interface ConformanceEvidence { id: Phase1ConformanceCheckId; status: \"passed\" | \"failed\"; evidence: string[]; }\nexport interface CertificationResult { formatVersion: 1; manifestDigest: string; compilerVersion: string; generatorDigests: Record<string, string>; checks: ConformanceEvidence[]; passed: boolean; }\n\nexport function createCertificationResult(input: Omit<CertificationResult, \"formatVersion\" | \"passed\">): CertificationResult {\n\tconst results = new Map(input.checks.map((check) => [check.id, check]));\n\tif (results.size !== input.checks.length) throw new Error(\"Certification evidence contains duplicate check identifiers.\");\n\tconst missing = PHASE_1_CONFORMANCE_CHECKS.filter((id) => !results.has(id));\n\tif (missing.length) throw new Error(`Certification evidence is missing mandatory checks: ${missing.join(\", \")}`);\n\tif (!/^[0-9a-f]{64}$/.test(input.manifestDigest)) throw new Error(\"Certification manifestDigest must be a lowercase SHA-256 digest.\");\n\tconst generatorDigests = Object.entries(input.generatorDigests);\n\tif (!generatorDigests.length || generatorDigests.some(([, digest]) => !/^[0-9a-f]{64}$/.test(digest))) throw new Error(\"Certification generatorDigests must contain SHA-256 evidence.\");\n\tif (input.checks.some((check) => check.status === \"passed\" && check.evidence.length === 0)) throw new Error(\"Passed certification checks require evidence references.\");\n\treturn { ...input, formatVersion: 1, checks: [...input.checks].sort((a, b) => a.id.localeCompare(b.id)), passed: input.checks.every((check) => check.status === \"passed\") };\n}\n\nexport const conformanceGenerator: GeneratorTarget = { name: \"@fabricorg/gen-conformance\", version: \"0.1.1\", generate(ir: FabricCompilerIr) { return ir.modules.map((module) => ({ path: `${module.namespace}/conformance-plan.json`, content: stableStringify({ formatVersion: 1, generator: { name: conformanceGenerator.name, version: conformanceGenerator.version }, compilerVersion: ir.compilerVersion, manifestDigest: ir.manifestDigest, module: module.namespace, requiredChecks: PHASE_1_CONFORMANCE_CHECKS }) })); } };\n"],"mappings":";AAAA,SAAS,uBAAoE;AAEtE,IAAM,6BAA6B;AAAA,EACzC;AAAA,EAA6C;AAAA,EAC7C;AAAA,EAA+C;AAAA,EAC/C;AAAA,EAA0C;AAAA,EAC1C;AAAA,EAAoC;AAAA,EACpC;AAAA,EAAkC;AACnC;AAKO,SAAS,0BAA0B,OAAmF;AAC5H,QAAM,UAAU,IAAI,IAAI,MAAM,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AACtE,MAAI,QAAQ,SAAS,MAAM,OAAO,OAAQ,OAAM,IAAI,MAAM,8DAA8D;AACxH,QAAM,UAAU,2BAA2B,OAAO,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC1E,MAAI,QAAQ,OAAQ,OAAM,IAAI,MAAM,uDAAuD,QAAQ,KAAK,IAAI,CAAC,EAAE;AAC/G,MAAI,CAAC,iBAAiB,KAAK,MAAM,cAAc,EAAG,OAAM,IAAI,MAAM,kEAAkE;AACpI,QAAM,mBAAmB,OAAO,QAAQ,MAAM,gBAAgB;AAC9D,MAAI,CAAC,iBAAiB,UAAU,iBAAiB,KAAK,CAAC,CAAC,EAAE,MAAM,MAAM,CAAC,iBAAiB,KAAK,MAAM,CAAC,EAAG,OAAM,IAAI,MAAM,+DAA+D;AACtL,MAAI,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW,YAAY,MAAM,SAAS,WAAW,CAAC,EAAG,OAAM,IAAI,MAAM,0DAA0D;AACtK,SAAO,EAAE,GAAG,OAAO,eAAe,GAAG,QAAQ,CAAC,GAAG,MAAM,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC,GAAG,QAAQ,MAAM,OAAO,MAAM,CAAC,UAAU,MAAM,WAAW,QAAQ,EAAE;AAC3K;AAEO,IAAM,uBAAwC,EAAE,MAAM,8BAA8B,SAAS,SAAS,SAAS,IAAsB;AAAE,SAAO,GAAG,QAAQ,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,SAAS,0BAA0B,SAAS,gBAAgB,EAAE,eAAe,GAAG,WAAW,EAAE,MAAM,qBAAqB,MAAM,SAAS,qBAAqB,QAAQ,GAAG,iBAAiB,GAAG,iBAAiB,gBAAgB,GAAG,gBAAgB,QAAQ,OAAO,WAAW,gBAAgB,2BAA2B,CAAC,EAAE,EAAE;AAAG,EAAE;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["import { stableStringify, type FabricCompilerIr, type GeneratorTarget } from \"@fabricorg/compiler\";\nimport type { ModuleManifest } from \"@fabricorg/platform\";\n\n/** Checks a single module's own behaviour, verifiable against a governed host. */\nexport const MODULE_CONFORMANCE_CHECKS = [\n\t\"fabric.idempotency.same-command-replay.v1\",\n\t\"fabric.idempotency.conflicting-payload.v1\",\n\t\"fabric.idempotency.concurrent-submission.v1\",\n\t\"fabric.authorization.governing-moment.v1\",\n\t\"fabric.authorization.offline-replay.v1\",\n\t\"fabric.events.compatibility.v1\",\n\t\"fabric.provenance.propagation.v1\",\n\t\"fabric.consumers.tolerant-envelope.v1\",\n] as const;\n\n/**\n * Checks that belong to a repository rather than a module. They are certification\n * evidence like any other check, but no per-module suite can produce them.\n */\nexport const PLATFORM_CONFORMANCE_CHECKS = [\n\t\"fabric.documentation.parity.v1\",\n\t\"fabric.documentation.llms-contract.v1\",\n] as const;\n\nexport const PHASE_1_CONFORMANCE_CHECKS = [...MODULE_CONFORMANCE_CHECKS, ...PLATFORM_CONFORMANCE_CHECKS] as const;\n\nexport type ModuleConformanceCheckId = typeof MODULE_CONFORMANCE_CHECKS[number];\nexport type PlatformConformanceCheckId = typeof PLATFORM_CONFORMANCE_CHECKS[number];\nexport type Phase1ConformanceCheckId = ModuleConformanceCheckId | PlatformConformanceCheckId;\n\nexport interface ConformanceEvidence { id: Phase1ConformanceCheckId; status: \"passed\" | \"failed\"; evidence: string[] }\n\nexport interface CertificationResult {\n\tformatVersion: 1;\n\tmanifestDigest: string;\n\tcompilerVersion: string;\n\tgeneratorDigests: Record<string, string>;\n\tchecks: ConformanceEvidence[];\n\tpassed: boolean;\n}\n\n// ── Plan ────────────────────────────────────────────────────────────────────\n\nexport interface PlannedAction {\n\tactionId: string;\n\tversion: number;\n\tmutatesDomain: boolean;\n\tidempotent: boolean;\n\tidempotency?: \"required\" | \"supported\" | \"prohibited\";\n\tauthorityMoment?: \"capture\" | \"execution\" | \"both\";\n\tconnectivity?: \"online-required\" | \"offline-capturable\";\n\tconsistency?: \"authoritative-now\" | \"provisional-until-reconciled\";\n}\n\nexport interface PlannedView {\n\tname: string;\n\tversion: string;\n\tconsumes: Array<{ eventType: string; schemaVersions: number[] }>;\n}\n\nexport interface ModuleConformancePlan {\n\tformatVersion: 1;\n\tmodule: string;\n\tmanifestDigest: string;\n\trequiredChecks: readonly ModuleConformanceCheckId[];\n\tplatformChecks: readonly PlatformConformanceCheckId[];\n\tactions: PlannedAction[];\n\tviews: PlannedView[];\n\teventTypes: Array<{ eventType: string; schemaVersion: number }>;\n}\n\n// ── Harness contract ────────────────────────────────────────────────────────\n\nexport interface ConformanceScope { tenantId: string; spaceId: string; actorId: string; actorType: string }\n\nexport interface ConformanceSubmitInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\tidempotencyKey?: string;\n\tcorrelationId?: string;\n\tprovenance?: { source: string; correlationId: string; auditAttributes?: Record<string, string> };\n\t/**\n\t * Declares only the intent of the binding. The harness completes tenant, actor,\n\t * action and parameter-digest identity, because only it knows the command shape.\n\t */\n\tauthorizationBinding?: { governingMoment: \"capture\" | \"execution\" | \"both\"; capturedAt?: string; expiresAt?: string };\n\texecutionReason?: \"initial\" | \"offline_replay\";\n}\n\nexport interface ConformanceSubmitResult {\n\tactionInvocationId: string;\n\tstatus: string;\n\treconciliation?: { kind: string };\n}\n\nexport interface ConformanceEventEnvelope {\n\teventType: string;\n\tcorrelationId?: string;\n\tactionInvocationId?: string;\n\ttenantId?: string;\n\tspaceId?: string;\n\tsubjectType?: string;\n\tsubjectId?: string;\n\tsequence?: number;\n\t[key: string]: unknown;\n}\n\n/**\n * What an application supplies so its module's generated suite can run. Structural\n * by design: any store or host that satisfies it is a valid subject.\n */\nexport interface ConformanceHarness {\n\tscope: ConformanceScope;\n\t/** Representative parameters for an action; the module owns its own fixtures. */\n\tparameters(actionId: string): Record<string, unknown>;\n\tsubmit(input: ConformanceSubmitInput): Promise<ConformanceSubmitResult>;\n\tevents(): Promise<ConformanceEventEnvelope[]>;\n\t/** Withdraw execution-time authority so a replay must be refused. */\n\trevokeAuthority(actionId: string): Promise<void>;\n\t/**\n\t * Observable effects an action has produced so far — events appended, rows written,\n\t * whatever the application counts. Supply it and the idempotency checks prove the\n\t * handler did not run twice, rather than only that the same identifier came back.\n\t */\n\tsideEffects?(actionId: string): Promise<number>;\n\t/**\n\t * Hand a real emitted envelope to a real consumer. Supply it and the\n\t * tolerant-envelope check stops being producer-side only: it proves a consumer\n\t * survives fields it does not recognise, which is what tolerance means.\n\t */\n\tacceptsEnvelope?(envelope: ConformanceEventEnvelope): Promise<boolean> | boolean;\n}\n\nexport type ConformanceOutcome =\n\t| { status: \"passed\"; evidence: string[] }\n\t| { status: \"not-applicable\"; reason: string };\n\nexport interface ConformanceCheck {\n\tid: ModuleConformanceCheckId;\n\ttitle: string;\n\trun(harness: ConformanceHarness, plan: ModuleConformancePlan): Promise<ConformanceOutcome>;\n}\n\nclass ConformanceFailure extends Error {\n\toverride readonly name = \"ConformanceFailure\";\n}\n\nfunction expect(condition: unknown, message: string): asserts condition {\n\tif (!condition) throw new ConformanceFailure(message);\n}\n\nconst idempotentActions = (plan: ModuleConformancePlan): PlannedAction[] =>\n\tplan.actions.filter((action) => action.idempotency !== \"prohibited\" && action.idempotent);\n\nconst key = (label: string, action: PlannedAction): string => `conformance:${label}:${action.actionId}`;\n\nconst conflictReason = (conflict: { code?: string; message: string }): string => conflict.code ?? conflict.message;\n\nasync function conflictOf(run: () => Promise<unknown>): Promise<{ thrown: boolean; code?: string; message: string }> {\n\ttry {\n\t\tawait run();\n\t\treturn { thrown: false, message: \"\" };\n\t} catch (error) {\n\t\tconst code = (error as { code?: string }).code;\n\t\treturn { thrown: true, ...(code ? { code } : {}), message: error instanceof Error ? error.message : String(error) };\n\t}\n}\n\n/**\n * Runs a check against every eligible action, not just the first. A module whose\n * first action conforms and whose second does not must fail, or certification\n * means nothing for the rest of the module.\n */\nasync function forEachAction(\n\tactions: PlannedAction[],\n\treason: string,\n\tbody: (action: PlannedAction) => Promise<string[]>,\n): Promise<ConformanceOutcome> {\n\tif (actions.length === 0) return { status: \"not-applicable\", reason };\n\tconst evidence: string[] = [];\n\tfor (const action of actions) evidence.push(...await body(action));\n\treturn { status: \"passed\", evidence };\n}\n\nexport function moduleConformanceChecks(): ConformanceCheck[] {\n\treturn [\n\t\t{\n\t\t\tid: \"fabric.idempotency.same-command-replay.v1\",\n\t\t\ttitle: \"replaying one command key with the same payload does not re-execute\",\n\t\t\trun: (harness, plan) => forEachAction(idempotentActions(plan), \"module declares no idempotent action\", async (action) => {\n\t\t\t\tconst input = { actionId: action.actionId, parameters: harness.parameters(action.actionId), idempotencyKey: key(\"replay\", action) };\n\t\t\t\tconst first = await harness.submit(input);\n\t\t\t\tconst before = await harness.sideEffects?.(action.actionId);\n\t\t\t\tconst second = await harness.submit(input);\n\t\t\t\texpect(first.actionInvocationId === second.actionInvocationId, `replay of ${action.actionId} produced a second invocation ${second.actionInvocationId} for key ${input.idempotencyKey}`);\n\t\t\t\tconst after = await harness.sideEffects?.(action.actionId);\n\t\t\t\tif (before !== undefined && after !== undefined) {\n\t\t\t\t\texpect(after === before, `replay of ${action.actionId} returned the original invocation but produced ${after - before} further effects; the handler ran again`);\n\t\t\t\t}\n\t\t\t\treturn [`invocation:${first.actionInvocationId}`, `key:${input.idempotencyKey}`, `effects:${after ?? \"unobserved\"}`];\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.idempotency.conflicting-payload.v1\",\n\t\t\ttitle: \"reusing a command key with an altered payload is refused\",\n\t\t\trun: (harness, plan) => forEachAction(idempotentActions(plan), \"module declares no idempotent action\", async (action) => {\n\t\t\t\tconst idempotencyKey = key(\"conflict\", action);\n\t\t\t\tconst parameters = harness.parameters(action.actionId);\n\t\t\t\tawait harness.submit({ actionId: action.actionId, parameters, idempotencyKey });\n\t\t\t\tconst conflict = await conflictOf(() => harness.submit({\n\t\t\t\t\tactionId: action.actionId,\n\t\t\t\t\tparameters: { ...parameters, \"x-conformance-altered\": true },\n\t\t\t\t\tidempotencyKey,\n\t\t\t\t}));\n\t\t\t\texpect(conflict.thrown, `altered payload reusing key ${idempotencyKey} was accepted instead of refused`);\n\t\t\t\texpect(\n\t\t\t\t\tconflict.code === \"IDEMPOTENCY_CONFLICT\" || /idempotenc/i.test(conflict.message),\n\t\t\t\t\t`altered payload for ${action.actionId} was refused, but not as an idempotency conflict: ${conflict.message}`,\n\t\t\t\t);\n\t\t\t\treturn [`key:${idempotencyKey}`, `refusal:${conflictReason(conflict)}`];\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.idempotency.concurrent-submission.v1\",\n\t\t\ttitle: \"concurrent identical submissions collapse to one invocation\",\n\t\t\trun: (harness, plan) => forEachAction(idempotentActions(plan), \"module declares no idempotent action\", async (action) => {\n\t\t\t\tconst input = { actionId: action.actionId, parameters: harness.parameters(action.actionId), idempotencyKey: key(\"concurrent\", action) };\n\t\t\t\tconst before = await harness.sideEffects?.(action.actionId);\n\t\t\t\tconst results = await Promise.all([harness.submit(input), harness.submit(input), harness.submit(input)]);\n\t\t\t\tconst ids = new Set(results.map((result) => result.actionInvocationId));\n\t\t\t\texpect(ids.size === 1, `concurrent submissions of ${action.actionId} produced ${ids.size} invocations: ${[...ids].join(\", \")}`);\n\t\t\t\tconst after = await harness.sideEffects?.(action.actionId);\n\t\t\t\tif (before !== undefined && after !== undefined) {\n\t\t\t\t\tconst executions = after - before;\n\t\t\t\t\texpect(executions <= 1, `${results.length} concurrent submissions of ${action.actionId} collapsed to one invocation but produced ${executions} executions' worth of effects`);\n\t\t\t\t}\n\t\t\t\treturn [`invocation:${[...ids][0]}`, `attempts:${results.length}`, `effects:${after ?? \"unobserved\"}`];\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.authorization.governing-moment.v1\",\n\t\t\ttitle: \"an authorization binding must match the action's declared governing moment\",\n\t\t\trun: (harness, plan) => forEachAction(\n\t\t\t\tplan.actions.filter((action) => action.authorityMoment !== undefined),\n\t\t\t\t\"module declares no action with an authority moment\",\n\t\t\t\tasync (action) => {\n\t\t\t\t\tconst mismatched = action.authorityMoment === \"capture\" ? \"execution\" : \"capture\";\n\t\t\t\t\tconst rejection = await conflictOf(() => harness.submit({\n\t\t\t\t\t\tactionId: action.actionId,\n\t\t\t\t\t\tparameters: harness.parameters(action.actionId),\n\t\t\t\t\t\tauthorizationBinding: { governingMoment: mismatched, capturedAt: new Date(Date.now() - 60_000).toISOString() },\n\t\t\t\t\t}));\n\t\t\t\t\texpect(rejection.thrown, `a binding declaring governingMoment \"${mismatched}\" was accepted for ${action.actionId}, governed at \"${action.authorityMoment}\"`);\n\t\t\t\t\texpect(\n\t\t\t\t\t\t/governingmoment|governing moment/i.test(conflictReason(rejection)),\n\t\t\t\t\t\t`the binding for ${action.actionId} was refused, but not for its governing moment: ${conflictReason(rejection)}`,\n\t\t\t\t\t);\n\t\t\t\t\treturn [`action:${action.actionId}`, `declared:${action.authorityMoment}`, `refused:${mismatched}`];\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.authorization.offline-replay.v1\",\n\t\t\ttitle: \"an offline capture replayed after authority is withdrawn is refused into reconciliation\",\n\t\t\trun: (harness, plan) => forEachAction(\n\t\t\t\tplan.actions.filter((action) =>\n\t\t\t\t\taction.connectivity === \"offline-capturable\" && action.authorityMoment !== undefined && action.authorityMoment !== \"capture\"),\n\t\t\t\t\"module declares no offline-capturable action governed at execution\",\n\t\t\t\tasync (action) => {\n\t\t\t\t\tawait harness.revokeAuthority(action.actionId);\n\t\t\t\t\tconst replay = await harness.submit({\n\t\t\t\t\t\tactionId: action.actionId,\n\t\t\t\t\t\tparameters: harness.parameters(action.actionId),\n\t\t\t\t\t\tidempotencyKey: key(\"offline-replay\", action),\n\t\t\t\t\t\texecutionReason: \"offline_replay\",\n\t\t\t\t\t});\n\t\t\t\t\texpect(\n\t\t\t\t\t\treplay.status === \"reconciliation_required\",\n\t\t\t\t\t\t`replay of ${action.actionId} after authority withdrawal returned status \"${replay.status}\" instead of reconciliation_required`,\n\t\t\t\t\t);\n\t\t\t\t\texpect(replay.reconciliation !== undefined, `reconciliation_required was returned for ${action.actionId} without a reconciliation outcome`);\n\t\t\t\t\treturn [`action:${action.actionId}`, `reconciliation:${replay.reconciliation?.kind}`];\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.events.compatibility.v1\",\n\t\t\ttitle: \"every consumed event version is one the module still declares\",\n\t\t\tasync run(_harness, plan) {\n\t\t\t\tif (plan.views.length === 0) return { status: \"not-applicable\", reason: \"module declares no views\" };\n\t\t\t\tconst declared = new Map(plan.eventTypes.map((event) => [event.eventType, event.schemaVersion]));\n\t\t\t\tconst evidence: string[] = [];\n\t\t\t\tfor (const view of plan.views) {\n\t\t\t\t\tfor (const consumption of view.consumes) {\n\t\t\t\t\t\tconst current = declared.get(consumption.eventType);\n\t\t\t\t\t\tif (current === undefined) continue;\n\t\t\t\t\t\texpect(\n\t\t\t\t\t\t\tconsumption.schemaVersions.includes(current),\n\t\t\t\t\t\t\t`view ${view.name}@${view.version} consumes ${consumption.eventType} at [${consumption.schemaVersions.join(\", \")}] but the module now emits version ${current}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tevidence.push(`${view.name}@${view.version}:${consumption.eventType}=${current}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (evidence.length === 0) return { status: \"not-applicable\", reason: \"no view consumes an event this module declares\" };\n\t\t\t\treturn { status: \"passed\", evidence };\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.provenance.propagation.v1\",\n\t\t\ttitle: \"submission provenance reaches the events the action emits\",\n\t\t\trun: (harness, plan) => forEachAction(\n\t\t\t\tplan.actions.filter((action) => action.mutatesDomain),\n\t\t\t\t\"module declares no domain-mutating action\",\n\t\t\t\tasync (action) => {\n\t\t\t\t\tconst correlationId = `conformance-corr-${action.actionId}`;\n\t\t\t\t\tconst submitted = await harness.submit({\n\t\t\t\t\t\tactionId: action.actionId,\n\t\t\t\t\t\tparameters: harness.parameters(action.actionId),\n\t\t\t\t\t\tcorrelationId,\n\t\t\t\t\t\t// Audit attributes are allowlisted per application, so provenance identity is asserted on correlation alone.\n\t\t\t\t\t\tprovenance: { source: \"system\", correlationId },\n\t\t\t\t\t});\n\t\t\t\t\tconst emitted = (await harness.events()).filter((event) => event.actionInvocationId === submitted.actionInvocationId);\n\t\t\t\t\texpect(emitted.length > 0, `action ${action.actionId} mutates domain but emitted no events`);\n\t\t\t\t\tfor (const event of emitted) {\n\t\t\t\t\t\texpect(event.correlationId === correlationId, `event ${event.eventType} carried correlationId ${String(event.correlationId)} instead of ${correlationId}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn [`invocation:${submitted.actionInvocationId}`, `events:${emitted.length}`, `correlationId:${correlationId}`];\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.consumers.tolerant-envelope.v1\",\n\t\t\ttitle: \"emitted envelopes are complete, and a consumer tolerates fields it does not know\",\n\t\t\trun: (harness, plan) => forEachAction(\n\t\t\t\tplan.actions.filter((action) => action.mutatesDomain),\n\t\t\t\t\"module declares no domain-mutating action\",\n\t\t\t\tasync (action) => {\n\t\t\t\t\tconst submitted = await harness.submit({\n\t\t\t\t\t\tactionId: action.actionId,\n\t\t\t\t\t\tparameters: harness.parameters(action.actionId),\n\t\t\t\t\t\tidempotencyKey: key(\"envelope\", action),\n\t\t\t\t\t});\n\t\t\t\t\tconst emitted = (await harness.events()).filter((event) => event.actionInvocationId === submitted.actionInvocationId);\n\t\t\t\t\texpect(emitted.length > 0, `action ${action.actionId} mutates domain but emitted no events`);\n\t\t\t\t\tconst required = [\"eventType\", \"tenantId\", \"spaceId\", \"subjectType\", \"subjectId\", \"sequence\", \"correlationId\"] as const;\n\t\t\t\t\tfor (const event of emitted) {\n\t\t\t\t\t\tfor (const field of required) {\n\t\t\t\t\t\t\texpect(event[field] !== undefined && event[field] !== null, `event ${event.eventType} omits the canonical envelope field \"${field}\"`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst evidence = emitted.map((event) => `${event.eventType}@${event.sequence}`);\n\t\t\t\t\tif (!harness.acceptsEnvelope) return [...evidence, \"consumer:unobserved\"];\n\t\t\t\t\tfor (const event of emitted) {\n\t\t\t\t\t\t// Additive fields are exactly what a future schema version introduces.\n\t\t\t\t\t\tconst extended = { ...event, \"x-conformance-unknown\": true, consistency: \"provisional-until-reconciled\" };\n\t\t\t\t\t\tconst accepted = await harness.acceptsEnvelope(extended);\n\t\t\t\t\t\texpect(accepted, `a consumer rejected ${event.eventType} once it carried a field it did not recognise; additive event changes would break it`);\n\t\t\t\t\t}\n\t\t\t\t\treturn [...evidence, `consumer:tolerated:${emitted.length}`];\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t];\n}\n\n// ── Certification evidence ──────────────────────────────────────────────────\n\nexport function createCertificationResult(input: Omit<CertificationResult, \"formatVersion\" | \"passed\">): CertificationResult {\n\tconst results = new Map(input.checks.map((check) => [check.id, check]));\n\tif (results.size !== input.checks.length) throw new Error(\"Certification evidence contains duplicate check identifiers.\");\n\tconst missing = PHASE_1_CONFORMANCE_CHECKS.filter((id) => !results.has(id));\n\tif (missing.length) throw new Error(`Certification evidence is missing mandatory checks: ${missing.join(\", \")}`);\n\tif (!/^[0-9a-f]{64}$/.test(input.manifestDigest)) throw new Error(\"Certification manifestDigest must be a lowercase SHA-256 digest.\");\n\tconst generatorDigests = Object.entries(input.generatorDigests);\n\tif (!generatorDigests.length || generatorDigests.some(([, digest]) => !/^[0-9a-f]{64}$/.test(digest))) throw new Error(\"Certification generatorDigests must contain SHA-256 evidence.\");\n\tif (input.checks.some((check) => check.status === \"passed\" && check.evidence.length === 0)) throw new Error(\"Passed certification checks require evidence references.\");\n\treturn { ...input, formatVersion: 1, checks: [...input.checks].sort((a, b) => a.id.localeCompare(b.id)), passed: input.checks.every((check) => check.status === \"passed\") };\n}\n\n// ── Generator ───────────────────────────────────────────────────────────────\n\nexport function buildModuleConformancePlan(module: ModuleManifest, manifestDigest: string): ModuleConformancePlan {\n\treturn {\n\t\tformatVersion: 1,\n\t\tmodule: module.namespace,\n\t\tmanifestDigest,\n\t\trequiredChecks: MODULE_CONFORMANCE_CHECKS,\n\t\tplatformChecks: PLATFORM_CONFORMANCE_CHECKS,\n\t\tactions: module.actions.map((action) => ({\n\t\t\tactionId: action.actionId,\n\t\t\tversion: action.version,\n\t\t\tmutatesDomain: action.mutatesDomain,\n\t\t\tidempotent: action.idempotent,\n\t\t\t...(action.execution?.idempotency ? { idempotency: action.execution.idempotency } : {}),\n\t\t\t...(action.execution?.authorityMoment ? { authorityMoment: action.execution.authorityMoment } : {}),\n\t\t\t...(action.execution?.connectivity ? { connectivity: action.execution.connectivity } : {}),\n\t\t\t...(action.execution?.consistency ? { consistency: action.execution.consistency } : {}),\n\t\t})),\n\t\tviews: module.views.map((view) => ({\n\t\t\tname: view.name,\n\t\t\tversion: view.version,\n\t\t\tconsumes: view.consumes.map((consumption) => ({ eventType: consumption.eventType, schemaVersions: [...consumption.schemaVersions] })),\n\t\t})),\n\t\teventTypes: module.eventTypes.map((event) => ({ eventType: event.eventType, schemaVersion: event.schemaVersion })),\n\t};\n}\n\nfunction suiteSource(plan: ModuleConformancePlan, generator: { name: string; version: string }): string {\n\treturn [\n\t\t`// Generated by ${generator.name}@${generator.version}. Do not edit.`,\n\t\t`// Manifest digest: ${plan.manifestDigest}`,\n\t\t`// Supply ./conformance.harness with a createConformanceHarness() factory returning a fresh, isolated harness.`,\n\t\t'import { expect, it } from \"vitest\";',\n\t\t'import { moduleConformanceChecks, type ModuleConformancePlan } from \"@fabricorg/gen-conformance\";',\n\t\t'import { createConformanceHarness } from \"./conformance.harness\";',\n\t\t\"\",\n\t\t`export const plan: ModuleConformancePlan = ${JSON.stringify(plan, null, 2)};`,\n\t\t\"\",\n\t\t\"for (const check of moduleConformanceChecks()) {\",\n\t\t\" it(`${check.id} — ${check.title}`, async () => {\",\n\t\t\" const outcome = await check.run(await createConformanceHarness(), plan);\",\n\t\t' expect([\"passed\", \"not-applicable\"]).toContain(outcome.status);',\n\t\t\" });\",\n\t\t\"}\",\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nexport const conformanceGenerator: GeneratorTarget = {\n\tname: \"@fabricorg/gen-conformance\",\n\tversion: \"0.2.0\",\n\tgenerate(ir: FabricCompilerIr) {\n\t\treturn ir.modules.flatMap((module) => {\n\t\t\tconst plan = buildModuleConformancePlan(module, ir.manifestDigest);\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: `${module.namespace}/conformance-plan.json`,\n\t\t\t\t\tcontent: stableStringify({\n\t\t\t\t\t\t...plan,\n\t\t\t\t\t\tgenerator: { name: conformanceGenerator.name, version: conformanceGenerator.version },\n\t\t\t\t\t\tcompilerVersion: ir.compilerVersion,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tpath: `${module.namespace}/conformance.test.ts`,\n\t\t\t\t\tcontent: suiteSource(plan, { name: conformanceGenerator.name, version: conformanceGenerator.version }),\n\t\t\t\t},\n\t\t\t];\n\t\t});\n\t},\n};\n"],"mappings":"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","names":[]}
|
package/dist/memory.cjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// memory.ts
|
|
21
|
+
var memory_exports = {};
|
|
22
|
+
__export(memory_exports, {
|
|
23
|
+
createMemoryConformanceHarness: () => createMemoryConformanceHarness
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(memory_exports);
|
|
26
|
+
var import_platform = require("@fabricorg/platform");
|
|
27
|
+
var import_platform_host = require("@fabricorg/platform-host");
|
|
28
|
+
var DEFAULT_SCOPE = {
|
|
29
|
+
tenantId: "conformance-tenant",
|
|
30
|
+
spaceId: "conformance-space",
|
|
31
|
+
actorId: "conformance-actor",
|
|
32
|
+
actorType: "natural_person"
|
|
33
|
+
};
|
|
34
|
+
function createMemoryConformanceHarness(options) {
|
|
35
|
+
const scope = { ...DEFAULT_SCOPE, ...options.scope };
|
|
36
|
+
const registry = (0, import_platform.createModuleRegistry)(options.modules);
|
|
37
|
+
const store = new import_platform_host.MemoryPlatformHostStore(options.db);
|
|
38
|
+
const revoked = /* @__PURE__ */ new Set();
|
|
39
|
+
const host = (0, import_platform_host.createGovernedActionHost)({
|
|
40
|
+
store,
|
|
41
|
+
registry,
|
|
42
|
+
authorization: {
|
|
43
|
+
checkEntitlement: async () => true,
|
|
44
|
+
// Capture-time authority always holds; only execution-time authority is withdrawable.
|
|
45
|
+
authorize: async () => true,
|
|
46
|
+
authorizeExecution: async (input) => !revoked.has(input.actionId)
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const parameters = (actionId) => options.parameters?.[actionId] ?? {};
|
|
50
|
+
return {
|
|
51
|
+
scope,
|
|
52
|
+
parameters,
|
|
53
|
+
async submit(input) {
|
|
54
|
+
const binding = input.authorizationBinding;
|
|
55
|
+
const result = await host.submitAction({
|
|
56
|
+
actionId: input.actionId,
|
|
57
|
+
parameters: input.parameters,
|
|
58
|
+
tenantId: scope.tenantId,
|
|
59
|
+
spaceId: scope.spaceId,
|
|
60
|
+
actorId: scope.actorId,
|
|
61
|
+
actorType: scope.actorType,
|
|
62
|
+
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
|
|
63
|
+
...input.correlationId ? { correlationId: input.correlationId } : {},
|
|
64
|
+
...input.provenance ? { provenance: input.provenance } : {},
|
|
65
|
+
...input.executionReason ? { executionReason: input.executionReason } : {},
|
|
66
|
+
...binding ? {
|
|
67
|
+
authorizationBinding: {
|
|
68
|
+
id: `conformance-binding-${input.actionId}`,
|
|
69
|
+
tenantId: scope.tenantId,
|
|
70
|
+
actorId: scope.actorId,
|
|
71
|
+
actionId: input.actionId,
|
|
72
|
+
parameterDigest: (0, import_platform_host.digestParameters)(input.parameters),
|
|
73
|
+
capturedAt: binding.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
74
|
+
...binding.expiresAt ? { expiresAt: binding.expiresAt } : {},
|
|
75
|
+
governingMoment: binding.governingMoment
|
|
76
|
+
}
|
|
77
|
+
} : {}
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
actionInvocationId: result.actionInvocationId,
|
|
81
|
+
status: result.status,
|
|
82
|
+
...result.reconciliation ? { reconciliation: { kind: result.reconciliation.kind } } : {}
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
async events() {
|
|
86
|
+
return (await store.listEvents(scope.tenantId, scope.spaceId)).map((event) => ({ ...event }));
|
|
87
|
+
},
|
|
88
|
+
async revokeAuthority(actionId) {
|
|
89
|
+
revoked.add(actionId);
|
|
90
|
+
},
|
|
91
|
+
async acceptsEnvelope(envelope) {
|
|
92
|
+
const consumers = options.modules.flatMap((module2) => module2.views ?? []).filter((view) => view.consumes.some((consumption) => consumption.eventType === envelope.eventType));
|
|
93
|
+
for (const view of consumers) {
|
|
94
|
+
try {
|
|
95
|
+
await view.applyEvent(view.initialState, envelope);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
async sideEffects(actionId) {
|
|
103
|
+
const invocations = await store.listActionInvocations({ tenantId: scope.tenantId, spaceId: scope.spaceId });
|
|
104
|
+
const ours = new Set(invocations.filter((invocation) => invocation.actionId === actionId).map((invocation) => invocation.id));
|
|
105
|
+
const events = await store.listEvents(scope.tenantId, scope.spaceId);
|
|
106
|
+
return events.filter((event) => event.actionInvocationId !== void 0 && ours.has(event.actionInvocationId)).length;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
111
|
+
0 && (module.exports = {
|
|
112
|
+
createMemoryConformanceHarness
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=memory.cjs.map
|