@odla-ai/chapter 0.47.5 → 0.47.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/dist/{copy-context-20WFI5TI.d.ts → copy-context-Dorxp-WK.d.ts} +2 -0
- package/dist/index.cjs +29 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +303 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +105 -0
- package/dist/testing.d.ts +105 -0
- package/dist/testing.js +280 -0
- package/dist/testing.js.map +1 -0
- package/dist/ui/admin/index.d.ts +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +287 -5
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +6 -0
- package/dist/worker/index.d.ts +6 -0
- package/dist/worker/index.js +287 -5
- package/dist/worker/index.js.map +1 -1
- package/package.json +6 -1
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
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
|
+
// src/testing.ts
|
|
21
|
+
var testing_exports = {};
|
|
22
|
+
__export(testing_exports, {
|
|
23
|
+
planChapterAcceptanceMatrix: () => planChapterAcceptanceMatrix,
|
|
24
|
+
probeChapterJoinContracts: () => probeChapterJoinContracts,
|
|
25
|
+
validateChapterAcceptanceManifest: () => validateChapterAcceptanceManifest
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(testing_exports);
|
|
28
|
+
|
|
29
|
+
// src/testing-probe.ts
|
|
30
|
+
function activeMemberships(site) {
|
|
31
|
+
return site.skus.filter((sku) => sku.active && sku.kind === "membership");
|
|
32
|
+
}
|
|
33
|
+
async function probeChapterJoinContracts(manifest, fetcher = fetch) {
|
|
34
|
+
const validation = validateChapterAcceptanceManifest(manifest);
|
|
35
|
+
if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join("; ")}`);
|
|
36
|
+
const findings = [];
|
|
37
|
+
for (const site of manifest.targets) {
|
|
38
|
+
let health;
|
|
39
|
+
let configResponse;
|
|
40
|
+
try {
|
|
41
|
+
[health, configResponse] = await Promise.all([
|
|
42
|
+
fetcher(`${site.origin}/api/health`, { headers: { "cache-control": "no-cache" } }),
|
|
43
|
+
fetcher(`${site.origin}/api/join-config`, { headers: { "cache-control": "no-cache" } })
|
|
44
|
+
]);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
findings.push({ targetId: site.targetId, siteId: site.siteId, path: "fetch", expected: "reachable", actual: error instanceof Error ? error.message : String(error) });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const targetNote = (path, expected, actual) => {
|
|
50
|
+
if (JSON.stringify(expected) !== JSON.stringify(actual)) findings.push({ targetId: site.targetId, siteId: site.siteId, path, expected, actual });
|
|
51
|
+
};
|
|
52
|
+
targetNote("/api/health status", 200, health.status);
|
|
53
|
+
targetNote("/api/join-config status", 200, configResponse.status);
|
|
54
|
+
if (!configResponse.ok) continue;
|
|
55
|
+
let config;
|
|
56
|
+
try {
|
|
57
|
+
config = await configResponse.json();
|
|
58
|
+
} catch {
|
|
59
|
+
targetNote("/api/join-config JSON", "object", "invalid JSON");
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
targetNote("id", site.target.chapterId, config.id);
|
|
63
|
+
const actualTiers = Array.isArray(config.tiers) ? config.tiers : [];
|
|
64
|
+
const expectedTiers = activeMemberships(site).map((sku) => ({
|
|
65
|
+
id: sku.id,
|
|
66
|
+
priceCents: sku.priceCents,
|
|
67
|
+
currency: sku.currency,
|
|
68
|
+
interval: sku.billing,
|
|
69
|
+
free: sku.priceCents === 0
|
|
70
|
+
}));
|
|
71
|
+
const projected = actualTiers.map((tier) => {
|
|
72
|
+
const value = tier;
|
|
73
|
+
return {
|
|
74
|
+
id: value.id,
|
|
75
|
+
priceCents: value.priceCents,
|
|
76
|
+
currency: value.currency,
|
|
77
|
+
interval: value.interval,
|
|
78
|
+
free: value.free
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
targetNote("tiers", expectedTiers, projected);
|
|
82
|
+
targetNote("paymentsReady", expectedTiers.some((tier) => !tier.free), config.paymentsReady);
|
|
83
|
+
}
|
|
84
|
+
return { ok: findings.length === 0, findings };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/testing.ts
|
|
88
|
+
var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
89
|
+
var CURRENCY = /^[a-z]{3}$/;
|
|
90
|
+
var STRIPE_PRICE = /^price_[A-Za-z0-9]{8,}$/;
|
|
91
|
+
function activeMemberships2(site) {
|
|
92
|
+
return site.skus.filter((sku) => sku.active && sku.kind === "membership");
|
|
93
|
+
}
|
|
94
|
+
function representativePaidMembership(site) {
|
|
95
|
+
return activeMemberships2(site).find((sku) => sku.priceCents > 0);
|
|
96
|
+
}
|
|
97
|
+
function record(value) {
|
|
98
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
99
|
+
}
|
|
100
|
+
function validateChapterAcceptanceManifest(value) {
|
|
101
|
+
const errors = [];
|
|
102
|
+
const input = record(value);
|
|
103
|
+
if (!input) return { ok: false, errors: ["manifest must be an object"] };
|
|
104
|
+
if (input.schemaVersion !== 1) errors.push("schemaVersion must be 1");
|
|
105
|
+
const inputTargets = Array.isArray(input.targets) ? input.targets : [];
|
|
106
|
+
if (inputTargets.length < 2) errors.push("targets must contain at least two deployed Chapter targets");
|
|
107
|
+
const targetIds = /* @__PURE__ */ new Set();
|
|
108
|
+
const siteIds = /* @__PURE__ */ new Set();
|
|
109
|
+
const targets = /* @__PURE__ */ new Set();
|
|
110
|
+
const priceOwners = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const [siteIndex, rawSite] of inputTargets.entries()) {
|
|
112
|
+
const path = `targets[${siteIndex}]`;
|
|
113
|
+
const site = record(rawSite);
|
|
114
|
+
if (!site) {
|
|
115
|
+
errors.push(`${path} must be an object`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const targetId = typeof site.targetId === "string" ? site.targetId : "";
|
|
119
|
+
const siteId = typeof site.siteId === "string" ? site.siteId : "";
|
|
120
|
+
if (!SLUG.test(targetId)) errors.push(`${path}.targetId must be a stable lowercase slug`);
|
|
121
|
+
if (targetIds.has(targetId)) errors.push(`${path}.targetId duplicates ${JSON.stringify(targetId)}`);
|
|
122
|
+
if (targetId) targetIds.add(targetId);
|
|
123
|
+
if (!SLUG.test(siteId)) errors.push(`${path}.siteId must be a stable lowercase slug`);
|
|
124
|
+
if (siteId) siteIds.add(siteId);
|
|
125
|
+
try {
|
|
126
|
+
if (typeof site.origin !== "string") throw new TypeError("origin");
|
|
127
|
+
const origin = new URL(site.origin);
|
|
128
|
+
if (origin.protocol !== "https:" || origin.pathname !== "/" || origin.search || origin.hash) {
|
|
129
|
+
errors.push(`${path}.origin must be an HTTPS origin without a path, query, or fragment`);
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
errors.push(`${path}.origin must be a valid HTTPS origin`);
|
|
133
|
+
}
|
|
134
|
+
const target = record(site.target);
|
|
135
|
+
const targetValues = {};
|
|
136
|
+
for (const key of ["appId", "environment", "dataEnvironment", "runtime", "chapterId"]) {
|
|
137
|
+
targetValues[key] = typeof target?.[key] === "string" ? String(target[key]) : "";
|
|
138
|
+
if (!targetValues[key]) errors.push(`${path}.target.${key} is required`);
|
|
139
|
+
}
|
|
140
|
+
const partition = Object.values(targetValues).join("\0");
|
|
141
|
+
if (targets.has(partition)) errors.push(`${path}.target duplicates another site partition`);
|
|
142
|
+
if (Object.values(targetValues).every(Boolean)) targets.add(partition);
|
|
143
|
+
if (site.stripeMode !== "test" && site.stripeMode !== "live") {
|
|
144
|
+
errors.push(`${path}.stripeMode must be test or live`);
|
|
145
|
+
}
|
|
146
|
+
if (site.stripeMode === "live" && targetValues.environment !== "prod" && targetValues.environment !== "production") {
|
|
147
|
+
errors.push(`${path}.stripeMode cannot be live outside a production environment`);
|
|
148
|
+
}
|
|
149
|
+
const skuIds = /* @__PURE__ */ new Set();
|
|
150
|
+
const inputSkus = Array.isArray(site.skus) ? site.skus : [];
|
|
151
|
+
if (!Array.isArray(site.skus) || inputSkus.length === 0) errors.push(`${path}.skus must be a non-empty array`);
|
|
152
|
+
const skus = [];
|
|
153
|
+
for (const [skuIndex, rawSku] of inputSkus.entries()) {
|
|
154
|
+
const skuPath = `${path}.skus[${skuIndex}]`;
|
|
155
|
+
const sku = record(rawSku);
|
|
156
|
+
if (!sku) {
|
|
157
|
+
errors.push(`${skuPath} must be an object`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const id = typeof sku.id === "string" ? sku.id : "";
|
|
161
|
+
if (!SLUG.test(id)) errors.push(`${skuPath}.id must be a stable lowercase slug`);
|
|
162
|
+
if (skuIds.has(id)) errors.push(`${skuPath}.id duplicates ${JSON.stringify(id)}`);
|
|
163
|
+
if (id) skuIds.add(id);
|
|
164
|
+
if (sku.kind !== "membership" && sku.kind !== "add_on") errors.push(`${skuPath}.kind must be membership or add_on`);
|
|
165
|
+
if (typeof sku.active !== "boolean") errors.push(`${skuPath}.active must be boolean`);
|
|
166
|
+
const priceCents = typeof sku.priceCents === "number" ? sku.priceCents : Number.NaN;
|
|
167
|
+
if (!Number.isSafeInteger(priceCents) || priceCents < 0) errors.push(`${skuPath}.priceCents must be a non-negative integer`);
|
|
168
|
+
if (typeof sku.currency !== "string" || !CURRENCY.test(sku.currency)) errors.push(`${skuPath}.currency must be a lowercase ISO currency code`);
|
|
169
|
+
if (sku.billing !== "month" && sku.billing !== "year" && sku.billing !== "once") {
|
|
170
|
+
errors.push(`${skuPath}.billing must be month, year, or once`);
|
|
171
|
+
}
|
|
172
|
+
if (sku.kind === "membership" && sku.billing === "once") {
|
|
173
|
+
errors.push(`${skuPath}.billing must be month or year for a membership SKU`);
|
|
174
|
+
}
|
|
175
|
+
if (sku.active === true && priceCents > 0 && (typeof sku.stripePriceId !== "string" || !STRIPE_PRICE.test(sku.stripePriceId))) {
|
|
176
|
+
errors.push(`${skuPath}.stripePriceId is required for an active paid SKU`);
|
|
177
|
+
}
|
|
178
|
+
if (priceCents === 0 && sku.stripePriceId) errors.push(`${skuPath}.stripePriceId must be absent for a free SKU`);
|
|
179
|
+
if (typeof sku.stripePriceId === "string" && sku.stripePriceId) {
|
|
180
|
+
const owner = `${siteId}/${id}`;
|
|
181
|
+
const previous = priceOwners.get(sku.stripePriceId);
|
|
182
|
+
if (previous && previous !== owner) errors.push(`${skuPath}.stripePriceId is already owned by ${previous}`);
|
|
183
|
+
else priceOwners.set(sku.stripePriceId, owner);
|
|
184
|
+
}
|
|
185
|
+
if (sku.compareAtPriceCents !== void 0) {
|
|
186
|
+
if (!Number.isSafeInteger(sku.compareAtPriceCents) || Number(sku.compareAtPriceCents) <= priceCents) {
|
|
187
|
+
errors.push(`${skuPath}.compareAtPriceCents must be an integer greater than priceCents`);
|
|
188
|
+
}
|
|
189
|
+
if (sku.discountDuration !== "first_term" && sku.discountDuration !== "while_active") {
|
|
190
|
+
errors.push(`${skuPath}.discountDuration is required with compareAtPriceCents`);
|
|
191
|
+
}
|
|
192
|
+
} else if (sku.discountDuration) {
|
|
193
|
+
errors.push(`${skuPath}.compareAtPriceCents is required with discountDuration`);
|
|
194
|
+
}
|
|
195
|
+
skus.push(sku);
|
|
196
|
+
}
|
|
197
|
+
if (!skus.some((sku) => sku.active && sku.kind === "membership")) errors.push(`${path}.skus must contain an active membership SKU`);
|
|
198
|
+
const options = record(site.options);
|
|
199
|
+
if (!options) errors.push(`${path}.options must be an object`);
|
|
200
|
+
if (options?.ordinaryInterview !== true) errors.push(`${path}.options.ordinaryInterview must be true`);
|
|
201
|
+
if (typeof options?.invitationInterviewWaiver !== "boolean") errors.push(`${path}.options.invitationInterviewWaiver must be boolean`);
|
|
202
|
+
if (options?.autoRenew !== true) errors.push(`${path}.options.autoRenew must be true`);
|
|
203
|
+
const seat = record(options?.additionalSeat);
|
|
204
|
+
if (options?.additionalSeat !== void 0 && !seat) errors.push(`${path}.options.additionalSeat must be an object`);
|
|
205
|
+
if (seat) {
|
|
206
|
+
const seatSku = skus.find((sku) => sku.id === seat.skuId);
|
|
207
|
+
if (!seatSku || !seatSku.active || seatSku.kind !== "add_on") {
|
|
208
|
+
errors.push(`${path}.options.additionalSeat.skuId must name an active add_on SKU`);
|
|
209
|
+
} else if (seatSku.billing === "once") {
|
|
210
|
+
errors.push(`${path}.options.additionalSeat.skuId must name a recurring add_on SKU`);
|
|
211
|
+
}
|
|
212
|
+
if (typeof seat.purchaseWindowCalendarMonths !== "number" || !Number.isInteger(seat.purchaseWindowCalendarMonths) || seat.purchaseWindowCalendarMonths < 1) {
|
|
213
|
+
errors.push(`${path}.options.additionalSeat.purchaseWindowCalendarMonths must be a positive integer`);
|
|
214
|
+
}
|
|
215
|
+
if (seat.named !== true || seat.uniqueEmail !== true || seat.renewal !== "membership_aligned" || seat.chargeTiming !== "start") {
|
|
216
|
+
errors.push(`${path}.options.additionalSeat must require a named unique-email seat, membership-aligned renewal, and payment at start`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (siteIds.size < 2) errors.push("targets must cover at least two distinct logical sites");
|
|
221
|
+
return errors.length ? { ok: false, errors } : { ok: true, value };
|
|
222
|
+
}
|
|
223
|
+
function scenario(input) {
|
|
224
|
+
return {
|
|
225
|
+
...input,
|
|
226
|
+
id: [input.suite, input.targetId, input.skuId, input.siblingTargetId, input.kind].filter(Boolean).join(":")
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function planChapterAcceptanceMatrix(manifest) {
|
|
230
|
+
const validation = validateChapterAcceptanceManifest(manifest);
|
|
231
|
+
if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join("; ")}`);
|
|
232
|
+
if (manifest.targets.some((site) => site.stripeMode !== "test")) {
|
|
233
|
+
throw new TypeError("provider acceptance runs are development-only and require Stripe test mode on every target");
|
|
234
|
+
}
|
|
235
|
+
const scenarios = [];
|
|
236
|
+
for (const site of manifest.targets) {
|
|
237
|
+
for (const sku of site.skus.filter((candidate) => candidate.active)) {
|
|
238
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "catalog_contract", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));
|
|
239
|
+
if (sku.priceCents > 0) {
|
|
240
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "amount_tamper", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));
|
|
241
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "wrong_environment_event", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));
|
|
242
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "post_stripe_compensation", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));
|
|
243
|
+
for (const sibling of manifest.targets.filter((candidate) => candidate.targetId !== site.targetId)) {
|
|
244
|
+
scenarios.push(scenario({
|
|
245
|
+
suite: "hermetic",
|
|
246
|
+
kind: "sibling_event_isolation",
|
|
247
|
+
targetId: site.targetId,
|
|
248
|
+
siteId: site.siteId,
|
|
249
|
+
skuId: sku.id,
|
|
250
|
+
siblingTargetId: sibling.targetId
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const sku of activeMemberships2(site)) {
|
|
256
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "submit_replay", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));
|
|
257
|
+
scenarios.push(scenario({
|
|
258
|
+
suite: "provider_dev",
|
|
259
|
+
kind: sku.priceCents > 0 ? "paid_signup" : "free_signup",
|
|
260
|
+
targetId: site.targetId,
|
|
261
|
+
siteId: site.siteId,
|
|
262
|
+
skuId: sku.id
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
const paid = representativePaidMembership(site);
|
|
266
|
+
if (paid) {
|
|
267
|
+
scenarios.push(scenario({ suite: "provider_dev", kind: "ordinary_interview", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
|
|
268
|
+
scenarios.push(scenario({ suite: "provider_dev", kind: "renewal", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
|
|
269
|
+
scenarios.push(scenario({ suite: "provider_dev", kind: "refund_cancel", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
|
|
270
|
+
if (site.options.invitationInterviewWaiver) {
|
|
271
|
+
scenarios.push(scenario({ suite: "provider_dev", kind: "invitation_interview_waiver", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
|
|
272
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "expired_interview_waiver", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
|
|
273
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "replayed_interview_waiver", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
|
|
274
|
+
}
|
|
275
|
+
if (site.options.additionalSeat) {
|
|
276
|
+
scenarios.push(scenario({
|
|
277
|
+
suite: "provider_dev",
|
|
278
|
+
kind: "named_seat",
|
|
279
|
+
targetId: site.targetId,
|
|
280
|
+
siteId: site.siteId,
|
|
281
|
+
skuId: site.options.additionalSeat.skuId
|
|
282
|
+
}));
|
|
283
|
+
scenarios.push(scenario({
|
|
284
|
+
suite: "hermetic",
|
|
285
|
+
kind: "duplicate_seat_email",
|
|
286
|
+
targetId: site.targetId,
|
|
287
|
+
siteId: site.siteId,
|
|
288
|
+
skuId: site.options.additionalSeat.skuId
|
|
289
|
+
}));
|
|
290
|
+
scenarios.push(scenario({
|
|
291
|
+
suite: "hermetic",
|
|
292
|
+
kind: "expired_seat_window",
|
|
293
|
+
targetId: site.targetId,
|
|
294
|
+
siteId: site.siteId,
|
|
295
|
+
skuId: site.options.additionalSeat.skuId
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
scenarios.push(scenario({ suite: "hermetic", kind: "canceled_membership", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return scenarios;
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=testing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts","../src/testing-probe.ts"],"sourcesContent":["import type {\n ChapterAcceptanceManifest, ChapterAcceptanceScenario, ChapterAcceptanceSku, ChapterAcceptanceTarget, ChapterAcceptanceValidation,\n} from \"./testing-contract\";\nexport type * from \"./testing-contract\";\nexport { probeChapterJoinContracts } from \"./testing-probe\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\nconst CURRENCY = /^[a-z]{3}$/;\nconst STRIPE_PRICE = /^price_[A-Za-z0-9]{8,}$/;\n\nfunction activeMemberships(site: ChapterAcceptanceTarget): ChapterAcceptanceSku[] {\n return site.skus.filter((sku) => sku.active && sku.kind === \"membership\");\n}\n\nfunction representativePaidMembership(site: ChapterAcceptanceTarget): ChapterAcceptanceSku | undefined {\n return activeMemberships(site).find((sku) => sku.priceCents > 0);\n}\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null;\n}\n\n/** Validate the exact facts a multi-site acceptance run needs before it writes anywhere. */\nexport function validateChapterAcceptanceManifest(value: unknown): ChapterAcceptanceValidation {\n const errors: string[] = [];\n const input = record(value);\n if (!input) return { ok: false, errors: [\"manifest must be an object\"] };\n if (input.schemaVersion !== 1) errors.push(\"schemaVersion must be 1\");\n const inputTargets = Array.isArray(input.targets) ? input.targets : [];\n if (inputTargets.length < 2) errors.push(\"targets must contain at least two deployed Chapter targets\");\n\n const targetIds = new Set<string>();\n const siteIds = new Set<string>();\n const targets = new Set<string>();\n const priceOwners = new Map<string, string>();\n for (const [siteIndex, rawSite] of inputTargets.entries()) {\n const path = `targets[${siteIndex}]`;\n const site = record(rawSite);\n if (!site) {\n errors.push(`${path} must be an object`);\n continue;\n }\n const targetId = typeof site.targetId === \"string\" ? site.targetId : \"\";\n const siteId = typeof site.siteId === \"string\" ? site.siteId : \"\";\n if (!SLUG.test(targetId)) errors.push(`${path}.targetId must be a stable lowercase slug`);\n if (targetIds.has(targetId)) errors.push(`${path}.targetId duplicates ${JSON.stringify(targetId)}`);\n if (targetId) targetIds.add(targetId);\n if (!SLUG.test(siteId)) errors.push(`${path}.siteId must be a stable lowercase slug`);\n if (siteId) siteIds.add(siteId);\n try {\n if (typeof site.origin !== \"string\") throw new TypeError(\"origin\");\n const origin = new URL(site.origin);\n if (origin.protocol !== \"https:\" || origin.pathname !== \"/\" || origin.search || origin.hash) {\n errors.push(`${path}.origin must be an HTTPS origin without a path, query, or fragment`);\n }\n } catch {\n errors.push(`${path}.origin must be a valid HTTPS origin`);\n }\n const target = record(site.target);\n const targetValues = {} as Record<\"appId\" | \"environment\" | \"dataEnvironment\" | \"runtime\" | \"chapterId\", string>;\n for (const key of [\"appId\", \"environment\", \"dataEnvironment\", \"runtime\", \"chapterId\"] as const) {\n targetValues[key] = typeof target?.[key] === \"string\" ? String(target[key]) : \"\";\n if (!targetValues[key]) errors.push(`${path}.target.${key} is required`);\n }\n const partition = Object.values(targetValues).join(\"\\u0000\");\n if (targets.has(partition)) errors.push(`${path}.target duplicates another site partition`);\n if (Object.values(targetValues).every(Boolean)) targets.add(partition);\n if (site.stripeMode !== \"test\" && site.stripeMode !== \"live\") {\n errors.push(`${path}.stripeMode must be test or live`);\n }\n if (site.stripeMode === \"live\" && targetValues.environment !== \"prod\" && targetValues.environment !== \"production\") {\n errors.push(`${path}.stripeMode cannot be live outside a production environment`);\n }\n\n const skuIds = new Set<string>();\n const inputSkus = Array.isArray(site.skus) ? site.skus : [];\n if (!Array.isArray(site.skus) || inputSkus.length === 0) errors.push(`${path}.skus must be a non-empty array`);\n const skus: ChapterAcceptanceSku[] = [];\n for (const [skuIndex, rawSku] of inputSkus.entries()) {\n const skuPath = `${path}.skus[${skuIndex}]`;\n const sku = record(rawSku);\n if (!sku) {\n errors.push(`${skuPath} must be an object`);\n continue;\n }\n const id = typeof sku.id === \"string\" ? sku.id : \"\";\n if (!SLUG.test(id)) errors.push(`${skuPath}.id must be a stable lowercase slug`);\n if (skuIds.has(id)) errors.push(`${skuPath}.id duplicates ${JSON.stringify(id)}`);\n if (id) skuIds.add(id);\n if (sku.kind !== \"membership\" && sku.kind !== \"add_on\") errors.push(`${skuPath}.kind must be membership or add_on`);\n if (typeof sku.active !== \"boolean\") errors.push(`${skuPath}.active must be boolean`);\n const priceCents = typeof sku.priceCents === \"number\" ? sku.priceCents : Number.NaN;\n if (!Number.isSafeInteger(priceCents) || priceCents < 0) errors.push(`${skuPath}.priceCents must be a non-negative integer`);\n if (typeof sku.currency !== \"string\" || !CURRENCY.test(sku.currency)) errors.push(`${skuPath}.currency must be a lowercase ISO currency code`);\n if (sku.billing !== \"month\" && sku.billing !== \"year\" && sku.billing !== \"once\") {\n errors.push(`${skuPath}.billing must be month, year, or once`);\n }\n if (sku.kind === \"membership\" && sku.billing === \"once\") {\n errors.push(`${skuPath}.billing must be month or year for a membership SKU`);\n }\n if (sku.active === true && priceCents > 0 && (typeof sku.stripePriceId !== \"string\" || !STRIPE_PRICE.test(sku.stripePriceId))) {\n errors.push(`${skuPath}.stripePriceId is required for an active paid SKU`);\n }\n if (priceCents === 0 && sku.stripePriceId) errors.push(`${skuPath}.stripePriceId must be absent for a free SKU`);\n if (typeof sku.stripePriceId === \"string\" && sku.stripePriceId) {\n const owner = `${siteId}/${id}`;\n const previous = priceOwners.get(sku.stripePriceId);\n if (previous && previous !== owner) errors.push(`${skuPath}.stripePriceId is already owned by ${previous}`);\n else priceOwners.set(sku.stripePriceId, owner);\n }\n if (sku.compareAtPriceCents !== undefined) {\n if (!Number.isSafeInteger(sku.compareAtPriceCents) || Number(sku.compareAtPriceCents) <= priceCents) {\n errors.push(`${skuPath}.compareAtPriceCents must be an integer greater than priceCents`);\n }\n if (sku.discountDuration !== \"first_term\" && sku.discountDuration !== \"while_active\") {\n errors.push(`${skuPath}.discountDuration is required with compareAtPriceCents`);\n }\n } else if (sku.discountDuration) {\n errors.push(`${skuPath}.compareAtPriceCents is required with discountDuration`);\n }\n skus.push(sku as unknown as ChapterAcceptanceSku);\n }\n if (!skus.some((sku) => sku.active && sku.kind === \"membership\")) errors.push(`${path}.skus must contain an active membership SKU`);\n const options = record(site.options);\n if (!options) errors.push(`${path}.options must be an object`);\n if (options?.ordinaryInterview !== true) errors.push(`${path}.options.ordinaryInterview must be true`);\n if (typeof options?.invitationInterviewWaiver !== \"boolean\") errors.push(`${path}.options.invitationInterviewWaiver must be boolean`);\n if (options?.autoRenew !== true) errors.push(`${path}.options.autoRenew must be true`);\n const seat = record(options?.additionalSeat);\n if (options?.additionalSeat !== undefined && !seat) errors.push(`${path}.options.additionalSeat must be an object`);\n if (seat) {\n const seatSku = skus.find((sku) => sku.id === seat.skuId);\n if (!seatSku || !seatSku.active || seatSku.kind !== \"add_on\") {\n errors.push(`${path}.options.additionalSeat.skuId must name an active add_on SKU`);\n } else if (seatSku.billing === \"once\") {\n errors.push(`${path}.options.additionalSeat.skuId must name a recurring add_on SKU`);\n }\n if (typeof seat.purchaseWindowCalendarMonths !== \"number\" ||\n !Number.isInteger(seat.purchaseWindowCalendarMonths) || seat.purchaseWindowCalendarMonths < 1) {\n errors.push(`${path}.options.additionalSeat.purchaseWindowCalendarMonths must be a positive integer`);\n }\n if (seat.named !== true || seat.uniqueEmail !== true || seat.renewal !== \"membership_aligned\" || seat.chargeTiming !== \"start\") {\n errors.push(`${path}.options.additionalSeat must require a named unique-email seat, membership-aligned renewal, and payment at start`);\n }\n }\n }\n if (siteIds.size < 2) errors.push(\"targets must cover at least two distinct logical sites\");\n return errors.length ? { ok: false, errors } : { ok: true, value: value as ChapterAcceptanceManifest };\n}\n\nfunction scenario(input: Omit<ChapterAcceptanceScenario, \"id\">): ChapterAcceptanceScenario {\n return {\n ...input,\n id: [input.suite, input.targetId, input.skuId, input.siblingTargetId, input.kind].filter(Boolean).join(\":\"),\n };\n}\n\n/**\n * Build exhaustive pure cases plus a bounded provider-backed development matrix.\n * Throws before returning provider work when the manifest is invalid or names live Stripe.\n */\nexport function planChapterAcceptanceMatrix(manifest: ChapterAcceptanceManifest): ChapterAcceptanceScenario[] {\n const validation = validateChapterAcceptanceManifest(manifest);\n if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join(\"; \")}`);\n if (manifest.targets.some((site) => site.stripeMode !== \"test\")) {\n throw new TypeError(\"provider acceptance runs are development-only and require Stripe test mode on every target\");\n }\n const scenarios: ChapterAcceptanceScenario[] = [];\n for (const site of manifest.targets) {\n for (const sku of site.skus.filter((candidate) => candidate.active)) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"catalog_contract\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n if (sku.priceCents > 0) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"amount_tamper\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"wrong_environment_event\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"post_stripe_compensation\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n for (const sibling of manifest.targets.filter((candidate) => candidate.targetId !== site.targetId)) {\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"sibling_event_isolation\", targetId: site.targetId, siteId: site.siteId,\n skuId: sku.id, siblingTargetId: sibling.targetId,\n }));\n }\n }\n }\n for (const sku of activeMemberships(site)) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"submit_replay\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({\n suite: \"provider_dev\", kind: sku.priceCents > 0 ? \"paid_signup\" : \"free_signup\",\n targetId: site.targetId, siteId: site.siteId, skuId: sku.id,\n }));\n }\n const paid = representativePaidMembership(site);\n if (paid) {\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"ordinary_interview\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"renewal\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"refund_cancel\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n if (site.options.invitationInterviewWaiver) {\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"invitation_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"expired_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"replayed_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n }\n if (site.options.additionalSeat) {\n scenarios.push(scenario({\n suite: \"provider_dev\", kind: \"named_seat\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"duplicate_seat_email\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"expired_seat_window\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n }\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"canceled_membership\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n }\n }\n return scenarios;\n}\n","import type {\n ChapterAcceptanceManifest,\n ChapterAcceptanceTarget,\n ChapterJoinConfigProbe,\n ChapterJoinContractFinding,\n ChapterJoinContractProbeResult,\n} from \"./testing-contract\";\nimport { validateChapterAcceptanceManifest } from \"./testing\";\n\nfunction activeMemberships(site: ChapterAcceptanceTarget) {\n return site.skus.filter((sku) => sku.active && sku.kind === \"membership\");\n}\n\n/** Probe public deployed join contracts without creating applications or provider objects. */\nexport async function probeChapterJoinContracts(\n manifest: ChapterAcceptanceManifest,\n fetcher: typeof fetch = fetch,\n): Promise<ChapterJoinContractProbeResult> {\n const validation = validateChapterAcceptanceManifest(manifest);\n if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join(\"; \")}`);\n const findings: ChapterJoinContractFinding[] = [];\n for (const site of manifest.targets) {\n let health: Response;\n let configResponse: Response;\n try {\n [health, configResponse] = await Promise.all([\n fetcher(`${site.origin}/api/health`, { headers: { \"cache-control\": \"no-cache\" } }),\n fetcher(`${site.origin}/api/join-config`, { headers: { \"cache-control\": \"no-cache\" } }),\n ]);\n } catch (error) {\n findings.push({ targetId: site.targetId, siteId: site.siteId, path: \"fetch\", expected: \"reachable\", actual: error instanceof Error ? error.message : String(error) });\n continue;\n }\n const targetNote = (path: string, expected: unknown, actual: unknown): void => {\n if (JSON.stringify(expected) !== JSON.stringify(actual)) findings.push({ targetId: site.targetId, siteId: site.siteId, path, expected, actual });\n };\n targetNote(\"/api/health status\", 200, health.status);\n targetNote(\"/api/join-config status\", 200, configResponse.status);\n if (!configResponse.ok) continue;\n let config: ChapterJoinConfigProbe;\n try {\n config = await configResponse.json() as ChapterJoinConfigProbe;\n } catch {\n targetNote(\"/api/join-config JSON\", \"object\", \"invalid JSON\");\n continue;\n }\n targetNote(\"id\", site.target.chapterId, config.id);\n const actualTiers = Array.isArray(config.tiers) ? config.tiers : [];\n const expectedTiers = activeMemberships(site).map((sku) => ({\n id: sku.id, priceCents: sku.priceCents, currency: sku.currency,\n interval: sku.billing, free: sku.priceCents === 0,\n }));\n const projected = actualTiers.map((tier) => {\n const value = tier as Record<string, unknown>;\n return {\n id: value.id, priceCents: value.priceCents, currency: value.currency,\n interval: value.interval, free: value.free,\n };\n });\n targetNote(\"tiers\", expectedTiers, projected);\n targetNote(\"paymentsReady\", expectedTiers.some((tier) => !tier.free), config.paymentsReady);\n }\n return { ok: findings.length === 0, findings };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSA,SAAS,kBAAkB,MAA+B;AACxD,SAAO,KAAK,KAAK,OAAO,CAAC,QAAQ,IAAI,UAAU,IAAI,SAAS,YAAY;AAC1E;AAGA,eAAsB,0BACpB,UACA,UAAwB,OACiB;AACzC,QAAM,aAAa,kCAAkC,QAAQ;AAC7D,MAAI,CAAC,WAAW,GAAI,OAAM,IAAI,UAAU,wCAAwC,WAAW,OAAO,KAAK,IAAI,CAAC,EAAE;AAC9G,QAAM,WAAyC,CAAC;AAChD,aAAW,QAAQ,SAAS,SAAS;AACnC,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,OAAC,QAAQ,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC3C,QAAQ,GAAG,KAAK,MAAM,eAAe,EAAE,SAAS,EAAE,iBAAiB,WAAW,EAAE,CAAC;AAAA,QACjF,QAAQ,GAAG,KAAK,MAAM,oBAAoB,EAAE,SAAS,EAAE,iBAAiB,WAAW,EAAE,CAAC;AAAA,MACxF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,eAAS,KAAK,EAAE,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,MAAM,SAAS,UAAU,aAAa,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AACpK;AAAA,IACF;AACA,UAAM,aAAa,CAAC,MAAc,UAAmB,WAA0B;AAC7E,UAAI,KAAK,UAAU,QAAQ,MAAM,KAAK,UAAU,MAAM,EAAG,UAAS,KAAK,EAAE,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,MAAM,UAAU,OAAO,CAAC;AAAA,IACjJ;AACA,eAAW,sBAAsB,KAAK,OAAO,MAAM;AACnD,eAAW,2BAA2B,KAAK,eAAe,MAAM;AAChE,QAAI,CAAC,eAAe,GAAI;AACxB,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,eAAe,KAAK;AAAA,IACrC,QAAQ;AACN,iBAAW,yBAAyB,UAAU,cAAc;AAC5D;AAAA,IACF;AACA,eAAW,MAAM,KAAK,OAAO,WAAW,OAAO,EAAE;AACjD,UAAM,cAAc,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC;AAClE,UAAM,gBAAgB,kBAAkB,IAAI,EAAE,IAAI,CAAC,SAAS;AAAA,MAC1D,IAAI,IAAI;AAAA,MAAI,YAAY,IAAI;AAAA,MAAY,UAAU,IAAI;AAAA,MACtD,UAAU,IAAI;AAAA,MAAS,MAAM,IAAI,eAAe;AAAA,IAClD,EAAE;AACF,UAAM,YAAY,YAAY,IAAI,CAAC,SAAS;AAC1C,YAAM,QAAQ;AACd,aAAO;AAAA,QACL,IAAI,MAAM;AAAA,QAAI,YAAY,MAAM;AAAA,QAAY,UAAU,MAAM;AAAA,QAC5D,UAAU,MAAM;AAAA,QAAU,MAAM,MAAM;AAAA,MACxC;AAAA,IACF,CAAC;AACD,eAAW,SAAS,eAAe,SAAS;AAC5C,eAAW,iBAAiB,cAAc,KAAK,CAAC,SAAS,CAAC,KAAK,IAAI,GAAG,OAAO,aAAa;AAAA,EAC5F;AACA,SAAO,EAAE,IAAI,SAAS,WAAW,GAAG,SAAS;AAC/C;;;ADzDA,IAAM,OAAO;AACb,IAAM,WAAW;AACjB,IAAM,eAAe;AAErB,SAASA,mBAAkB,MAAuD;AAChF,SAAO,KAAK,KAAK,OAAO,CAAC,QAAQ,IAAI,UAAU,IAAI,SAAS,YAAY;AAC1E;AAEA,SAAS,6BAA6B,MAAiE;AACrG,SAAOA,mBAAkB,IAAI,EAAE,KAAK,CAAC,QAAQ,IAAI,aAAa,CAAC;AACjE;AAEA,SAAS,OAAO,OAAgD;AAC9D,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IACtE,QACA;AACN;AAGO,SAAS,kCAAkC,OAA6C;AAC7F,QAAM,SAAmB,CAAC;AAC1B,QAAM,QAAQ,OAAO,KAAK;AAC1B,MAAI,CAAC,MAAO,QAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,4BAA4B,EAAE;AACvE,MAAI,MAAM,kBAAkB,EAAG,QAAO,KAAK,yBAAyB;AACpE,QAAM,eAAe,MAAM,QAAQ,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC;AACrE,MAAI,aAAa,SAAS,EAAG,QAAO,KAAK,4DAA4D;AAErG,QAAM,YAAY,oBAAI,IAAY;AAClC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,cAAc,oBAAI,IAAoB;AAC5C,aAAW,CAAC,WAAW,OAAO,KAAK,aAAa,QAAQ,GAAG;AACzD,UAAM,OAAO,WAAW,SAAS;AACjC,UAAM,OAAO,OAAO,OAAO;AAC3B,QAAI,CAAC,MAAM;AACT,aAAO,KAAK,GAAG,IAAI,oBAAoB;AACvC;AAAA,IACF;AACA,UAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,UAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;AAC/D,QAAI,CAAC,KAAK,KAAK,QAAQ,EAAG,QAAO,KAAK,GAAG,IAAI,2CAA2C;AACxF,QAAI,UAAU,IAAI,QAAQ,EAAG,QAAO,KAAK,GAAG,IAAI,wBAAwB,KAAK,UAAU,QAAQ,CAAC,EAAE;AAClG,QAAI,SAAU,WAAU,IAAI,QAAQ;AACpC,QAAI,CAAC,KAAK,KAAK,MAAM,EAAG,QAAO,KAAK,GAAG,IAAI,yCAAyC;AACpF,QAAI,OAAQ,SAAQ,IAAI,MAAM;AAC9B,QAAI;AACF,UAAI,OAAO,KAAK,WAAW,SAAU,OAAM,IAAI,UAAU,QAAQ;AACjE,YAAM,SAAS,IAAI,IAAI,KAAK,MAAM;AAClC,UAAI,OAAO,aAAa,YAAY,OAAO,aAAa,OAAO,OAAO,UAAU,OAAO,MAAM;AAC3F,eAAO,KAAK,GAAG,IAAI,oEAAoE;AAAA,MACzF;AAAA,IACF,QAAQ;AACN,aAAO,KAAK,GAAG,IAAI,sCAAsC;AAAA,IAC3D;AACA,UAAM,SAAS,OAAO,KAAK,MAAM;AACjC,UAAM,eAAe,CAAC;AACtB,eAAW,OAAO,CAAC,SAAS,eAAe,mBAAmB,WAAW,WAAW,GAAY;AAC9F,mBAAa,GAAG,IAAI,OAAO,SAAS,GAAG,MAAM,WAAW,OAAO,OAAO,GAAG,CAAC,IAAI;AAC9E,UAAI,CAAC,aAAa,GAAG,EAAG,QAAO,KAAK,GAAG,IAAI,WAAW,GAAG,cAAc;AAAA,IACzE;AACA,UAAM,YAAY,OAAO,OAAO,YAAY,EAAE,KAAK,IAAQ;AAC3D,QAAI,QAAQ,IAAI,SAAS,EAAG,QAAO,KAAK,GAAG,IAAI,2CAA2C;AAC1F,QAAI,OAAO,OAAO,YAAY,EAAE,MAAM,OAAO,EAAG,SAAQ,IAAI,SAAS;AACrE,QAAI,KAAK,eAAe,UAAU,KAAK,eAAe,QAAQ;AAC5D,aAAO,KAAK,GAAG,IAAI,kCAAkC;AAAA,IACvD;AACA,QAAI,KAAK,eAAe,UAAU,aAAa,gBAAgB,UAAU,aAAa,gBAAgB,cAAc;AAClH,aAAO,KAAK,GAAG,IAAI,6DAA6D;AAAA,IAClF;AAEA,UAAM,SAAS,oBAAI,IAAY;AAC/B,UAAM,YAAY,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC;AAC1D,QAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,UAAU,WAAW,EAAG,QAAO,KAAK,GAAG,IAAI,iCAAiC;AAC7G,UAAM,OAA+B,CAAC;AACtC,eAAW,CAAC,UAAU,MAAM,KAAK,UAAU,QAAQ,GAAG;AACpD,YAAM,UAAU,GAAG,IAAI,SAAS,QAAQ;AACxC,YAAM,MAAM,OAAO,MAAM;AACzB,UAAI,CAAC,KAAK;AACR,eAAO,KAAK,GAAG,OAAO,oBAAoB;AAC1C;AAAA,MACF;AACA,YAAM,KAAK,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK;AACjD,UAAI,CAAC,KAAK,KAAK,EAAE,EAAG,QAAO,KAAK,GAAG,OAAO,qCAAqC;AAC/E,UAAI,OAAO,IAAI,EAAE,EAAG,QAAO,KAAK,GAAG,OAAO,kBAAkB,KAAK,UAAU,EAAE,CAAC,EAAE;AAChF,UAAI,GAAI,QAAO,IAAI,EAAE;AACrB,UAAI,IAAI,SAAS,gBAAgB,IAAI,SAAS,SAAU,QAAO,KAAK,GAAG,OAAO,oCAAoC;AAClH,UAAI,OAAO,IAAI,WAAW,UAAW,QAAO,KAAK,GAAG,OAAO,yBAAyB;AACpF,YAAM,aAAa,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa,OAAO;AAChF,UAAI,CAAC,OAAO,cAAc,UAAU,KAAK,aAAa,EAAG,QAAO,KAAK,GAAG,OAAO,4CAA4C;AAC3H,UAAI,OAAO,IAAI,aAAa,YAAY,CAAC,SAAS,KAAK,IAAI,QAAQ,EAAG,QAAO,KAAK,GAAG,OAAO,iDAAiD;AAC7I,UAAI,IAAI,YAAY,WAAW,IAAI,YAAY,UAAU,IAAI,YAAY,QAAQ;AAC/E,eAAO,KAAK,GAAG,OAAO,uCAAuC;AAAA,MAC/D;AACA,UAAI,IAAI,SAAS,gBAAgB,IAAI,YAAY,QAAQ;AACvD,eAAO,KAAK,GAAG,OAAO,qDAAqD;AAAA,MAC7E;AACA,UAAI,IAAI,WAAW,QAAQ,aAAa,MAAM,OAAO,IAAI,kBAAkB,YAAY,CAAC,aAAa,KAAK,IAAI,aAAa,IAAI;AAC7H,eAAO,KAAK,GAAG,OAAO,mDAAmD;AAAA,MAC3E;AACA,UAAI,eAAe,KAAK,IAAI,cAAe,QAAO,KAAK,GAAG,OAAO,8CAA8C;AAC/G,UAAI,OAAO,IAAI,kBAAkB,YAAY,IAAI,eAAe;AAC9D,cAAM,QAAQ,GAAG,MAAM,IAAI,EAAE;AAC7B,cAAM,WAAW,YAAY,IAAI,IAAI,aAAa;AAClD,YAAI,YAAY,aAAa,MAAO,QAAO,KAAK,GAAG,OAAO,sCAAsC,QAAQ,EAAE;AAAA,YACrG,aAAY,IAAI,IAAI,eAAe,KAAK;AAAA,MAC/C;AACA,UAAI,IAAI,wBAAwB,QAAW;AACzC,YAAI,CAAC,OAAO,cAAc,IAAI,mBAAmB,KAAK,OAAO,IAAI,mBAAmB,KAAK,YAAY;AACnG,iBAAO,KAAK,GAAG,OAAO,iEAAiE;AAAA,QACzF;AACA,YAAI,IAAI,qBAAqB,gBAAgB,IAAI,qBAAqB,gBAAgB;AACpF,iBAAO,KAAK,GAAG,OAAO,wDAAwD;AAAA,QAChF;AAAA,MACF,WAAW,IAAI,kBAAkB;AAC/B,eAAO,KAAK,GAAG,OAAO,wDAAwD;AAAA,MAChF;AACA,WAAK,KAAK,GAAsC;AAAA,IAClD;AACA,QAAI,CAAC,KAAK,KAAK,CAAC,QAAQ,IAAI,UAAU,IAAI,SAAS,YAAY,EAAG,QAAO,KAAK,GAAG,IAAI,6CAA6C;AAClI,UAAM,UAAU,OAAO,KAAK,OAAO;AACnC,QAAI,CAAC,QAAS,QAAO,KAAK,GAAG,IAAI,4BAA4B;AAC7D,QAAI,SAAS,sBAAsB,KAAM,QAAO,KAAK,GAAG,IAAI,yCAAyC;AACrG,QAAI,OAAO,SAAS,8BAA8B,UAAW,QAAO,KAAK,GAAG,IAAI,oDAAoD;AACpI,QAAI,SAAS,cAAc,KAAM,QAAO,KAAK,GAAG,IAAI,iCAAiC;AACrF,UAAM,OAAO,OAAO,SAAS,cAAc;AAC3C,QAAI,SAAS,mBAAmB,UAAa,CAAC,KAAM,QAAO,KAAK,GAAG,IAAI,2CAA2C;AAClH,QAAI,MAAM;AACR,YAAM,UAAU,KAAK,KAAK,CAAC,QAAQ,IAAI,OAAO,KAAK,KAAK;AACxD,UAAI,CAAC,WAAW,CAAC,QAAQ,UAAU,QAAQ,SAAS,UAAU;AAC5D,eAAO,KAAK,GAAG,IAAI,8DAA8D;AAAA,MACnF,WAAW,QAAQ,YAAY,QAAQ;AACrC,eAAO,KAAK,GAAG,IAAI,gEAAgE;AAAA,MACrF;AACA,UAAI,OAAO,KAAK,iCAAiC,YAC7C,CAAC,OAAO,UAAU,KAAK,4BAA4B,KAAK,KAAK,+BAA+B,GAAG;AACjG,eAAO,KAAK,GAAG,IAAI,iFAAiF;AAAA,MACtG;AACA,UAAI,KAAK,UAAU,QAAQ,KAAK,gBAAgB,QAAQ,KAAK,YAAY,wBAAwB,KAAK,iBAAiB,SAAS;AAC9H,eAAO,KAAK,GAAG,IAAI,kHAAkH;AAAA,MACvI;AAAA,IACF;AAAA,EACF;AACA,MAAI,QAAQ,OAAO,EAAG,QAAO,KAAK,wDAAwD;AAC1F,SAAO,OAAO,SAAS,EAAE,IAAI,OAAO,OAAO,IAAI,EAAE,IAAI,MAAM,MAA0C;AACvG;AAEA,SAAS,SAAS,OAAyE;AACzF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,IAAI,CAAC,MAAM,OAAO,MAAM,UAAU,MAAM,OAAO,MAAM,iBAAiB,MAAM,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,EAC5G;AACF;AAMO,SAAS,4BAA4B,UAAkE;AAC5G,QAAM,aAAa,kCAAkC,QAAQ;AAC7D,MAAI,CAAC,WAAW,GAAI,OAAM,IAAI,UAAU,wCAAwC,WAAW,OAAO,KAAK,IAAI,CAAC,EAAE;AAC9G,MAAI,SAAS,QAAQ,KAAK,CAAC,SAAS,KAAK,eAAe,MAAM,GAAG;AAC/D,UAAM,IAAI,UAAU,4FAA4F;AAAA,EAClH;AACA,QAAM,YAAyC,CAAC;AAChD,aAAW,QAAQ,SAAS,SAAS;AACnC,eAAW,OAAO,KAAK,KAAK,OAAO,CAAC,cAAc,UAAU,MAAM,GAAG;AACnE,gBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,oBAAoB,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,IAAI,GAAG,CAAC,CAAC;AACrI,UAAI,IAAI,aAAa,GAAG;AACtB,kBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,iBAAiB,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,IAAI,GAAG,CAAC,CAAC;AAClI,kBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,2BAA2B,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,IAAI,GAAG,CAAC,CAAC;AAC5I,kBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,4BAA4B,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,IAAI,GAAG,CAAC,CAAC;AAC7I,mBAAW,WAAW,SAAS,QAAQ,OAAO,CAAC,cAAc,UAAU,aAAa,KAAK,QAAQ,GAAG;AAClG,oBAAU,KAAK,SAAS;AAAA,YACtB,OAAO;AAAA,YAAY,MAAM;AAAA,YAA2B,UAAU,KAAK;AAAA,YAAU,QAAQ,KAAK;AAAA,YAC1F,OAAO,IAAI;AAAA,YAAI,iBAAiB,QAAQ;AAAA,UAC1C,CAAC,CAAC;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AACA,eAAW,OAAOA,mBAAkB,IAAI,GAAG;AACzC,gBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,iBAAiB,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,IAAI,GAAG,CAAC,CAAC;AAClI,gBAAU,KAAK,SAAS;AAAA,QACtB,OAAO;AAAA,QAAgB,MAAM,IAAI,aAAa,IAAI,gBAAgB;AAAA,QAClE,UAAU,KAAK;AAAA,QAAU,QAAQ,KAAK;AAAA,QAAQ,OAAO,IAAI;AAAA,MAC3D,CAAC,CAAC;AAAA,IACJ;AACA,UAAM,OAAO,6BAA6B,IAAI;AAC9C,QAAI,MAAM;AACR,gBAAU,KAAK,SAAS,EAAE,OAAO,gBAAgB,MAAM,sBAAsB,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,CAAC;AAC5I,gBAAU,KAAK,SAAS,EAAE,OAAO,gBAAgB,MAAM,WAAW,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,CAAC;AACjI,gBAAU,KAAK,SAAS,EAAE,OAAO,gBAAgB,MAAM,iBAAiB,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,CAAC;AACvI,UAAI,KAAK,QAAQ,2BAA2B;AAC1C,kBAAU,KAAK,SAAS,EAAE,OAAO,gBAAgB,MAAM,+BAA+B,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,CAAC;AACrJ,kBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,4BAA4B,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,CAAC;AAC9I,kBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,6BAA6B,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,CAAC;AAAA,MACjJ;AACA,UAAI,KAAK,QAAQ,gBAAgB;AAC/B,kBAAU,KAAK,SAAS;AAAA,UACtB,OAAO;AAAA,UAAgB,MAAM;AAAA,UAAc,UAAU,KAAK;AAAA,UAAU,QAAQ,KAAK;AAAA,UACjF,OAAO,KAAK,QAAQ,eAAe;AAAA,QACrC,CAAC,CAAC;AACF,kBAAU,KAAK,SAAS;AAAA,UACtB,OAAO;AAAA,UAAY,MAAM;AAAA,UAAwB,UAAU,KAAK;AAAA,UAAU,QAAQ,KAAK;AAAA,UACvF,OAAO,KAAK,QAAQ,eAAe;AAAA,QACrC,CAAC,CAAC;AACF,kBAAU,KAAK,SAAS;AAAA,UACtB,OAAO;AAAA,UAAY,MAAM;AAAA,UAAuB,UAAU,KAAK;AAAA,UAAU,QAAQ,KAAK;AAAA,UACtF,OAAO,KAAK,QAAQ,eAAe;AAAA,QACrC,CAAC,CAAC;AAAA,MACJ;AACA,gBAAU,KAAK,SAAS,EAAE,OAAO,YAAY,MAAM,uBAAuB,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,CAAC;AAAA,IAC3I;AAAA,EACF;AACA,SAAO;AACT;","names":["activeMemberships"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Wire-independent test contract for one SKU offered by one Chapter site. */
|
|
2
|
+
interface ChapterAcceptanceSku {
|
|
3
|
+
/** Stable BNF SKU id. It is also the Chapter tier id for membership SKUs. */
|
|
4
|
+
id: string;
|
|
5
|
+
kind: "membership" | "add_on";
|
|
6
|
+
active: boolean;
|
|
7
|
+
priceCents: number;
|
|
8
|
+
currency: string;
|
|
9
|
+
billing: "month" | "year" | "once";
|
|
10
|
+
/** Required for an active paid SKU and absent for a free SKU. */
|
|
11
|
+
stripePriceId?: string;
|
|
12
|
+
/** Authoritative comparison price. Omit it when no real discount exists. */
|
|
13
|
+
compareAtPriceCents?: number;
|
|
14
|
+
/** How long the difference from compareAtPriceCents is retained. */
|
|
15
|
+
discountDuration?: "first_term" | "while_active";
|
|
16
|
+
}
|
|
17
|
+
/** Named-seat behavior that must be exercised as a membership add-on. */
|
|
18
|
+
interface ChapterAcceptanceSeatOption {
|
|
19
|
+
skuId: string;
|
|
20
|
+
purchaseWindowCalendarMonths: number;
|
|
21
|
+
named: true;
|
|
22
|
+
uniqueEmail: true;
|
|
23
|
+
renewal: "membership_aligned";
|
|
24
|
+
chargeTiming: "start";
|
|
25
|
+
}
|
|
26
|
+
/** Options that change the Chapter signup journey independently of SKU price. */
|
|
27
|
+
interface ChapterAcceptanceOptions {
|
|
28
|
+
ordinaryInterview: true;
|
|
29
|
+
invitationInterviewWaiver: boolean;
|
|
30
|
+
autoRenew: true;
|
|
31
|
+
additionalSeat?: ChapterAcceptanceSeatOption;
|
|
32
|
+
}
|
|
33
|
+
/** One deployed runtime target. A logical site may have multiple authoritative dev targets. */
|
|
34
|
+
interface ChapterAcceptanceTarget {
|
|
35
|
+
targetId: string;
|
|
36
|
+
siteId: string;
|
|
37
|
+
origin: string;
|
|
38
|
+
target: {
|
|
39
|
+
appId: string;
|
|
40
|
+
environment: string;
|
|
41
|
+
dataEnvironment: string;
|
|
42
|
+
runtime: string;
|
|
43
|
+
chapterId: string;
|
|
44
|
+
};
|
|
45
|
+
stripeMode: "test" | "live";
|
|
46
|
+
skus: readonly ChapterAcceptanceSku[];
|
|
47
|
+
options: ChapterAcceptanceOptions;
|
|
48
|
+
}
|
|
49
|
+
/** Complete multi-site contract consumed by hermetic and deployed-dev tests. */
|
|
50
|
+
interface ChapterAcceptanceManifest {
|
|
51
|
+
schemaVersion: 1;
|
|
52
|
+
targets: readonly ChapterAcceptanceTarget[];
|
|
53
|
+
}
|
|
54
|
+
/** Runtime-safe validation result for JSON-loaded acceptance manifests. */
|
|
55
|
+
type ChapterAcceptanceValidation = {
|
|
56
|
+
ok: true;
|
|
57
|
+
value: ChapterAcceptanceManifest;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
errors: readonly string[];
|
|
61
|
+
};
|
|
62
|
+
/** Closed set of deterministic and provider-backed acceptance behaviors. */
|
|
63
|
+
type ChapterAcceptanceScenarioKind = "catalog_contract" | "amount_tamper" | "wrong_environment_event" | "sibling_event_isolation" | "submit_replay" | "post_stripe_compensation" | "paid_signup" | "free_signup" | "ordinary_interview" | "invitation_interview_waiver" | "expired_interview_waiver" | "replayed_interview_waiver" | "named_seat" | "duplicate_seat_email" | "expired_seat_window" | "canceled_membership" | "renewal" | "refund_cancel";
|
|
64
|
+
/** One stable generated case. Provider cases are opt-in and test-mode only. */
|
|
65
|
+
interface ChapterAcceptanceScenario {
|
|
66
|
+
id: string;
|
|
67
|
+
suite: "hermetic" | "provider_dev";
|
|
68
|
+
kind: ChapterAcceptanceScenarioKind;
|
|
69
|
+
targetId: string;
|
|
70
|
+
siteId: string;
|
|
71
|
+
skuId?: string;
|
|
72
|
+
siblingTargetId?: string;
|
|
73
|
+
}
|
|
74
|
+
/** Public response shape needed by the non-mutating deployed contract probe. */
|
|
75
|
+
interface ChapterJoinConfigProbe {
|
|
76
|
+
id?: unknown;
|
|
77
|
+
paymentsReady?: unknown;
|
|
78
|
+
tiers?: unknown;
|
|
79
|
+
}
|
|
80
|
+
/** One exact expected-versus-observed difference from a deployed public contract. */
|
|
81
|
+
interface ChapterJoinContractFinding {
|
|
82
|
+
targetId: string;
|
|
83
|
+
siteId: string;
|
|
84
|
+
path: string;
|
|
85
|
+
expected: unknown;
|
|
86
|
+
actual: unknown;
|
|
87
|
+
}
|
|
88
|
+
/** Aggregate result of a non-mutating multi-target join-contract probe. */
|
|
89
|
+
interface ChapterJoinContractProbeResult {
|
|
90
|
+
ok: boolean;
|
|
91
|
+
findings: ChapterJoinContractFinding[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Probe public deployed join contracts without creating applications or provider objects. */
|
|
95
|
+
declare function probeChapterJoinContracts(manifest: ChapterAcceptanceManifest, fetcher?: typeof fetch): Promise<ChapterJoinContractProbeResult>;
|
|
96
|
+
|
|
97
|
+
/** Validate the exact facts a multi-site acceptance run needs before it writes anywhere. */
|
|
98
|
+
declare function validateChapterAcceptanceManifest(value: unknown): ChapterAcceptanceValidation;
|
|
99
|
+
/**
|
|
100
|
+
* Build exhaustive pure cases plus a bounded provider-backed development matrix.
|
|
101
|
+
* Throws before returning provider work when the manifest is invalid or names live Stripe.
|
|
102
|
+
*/
|
|
103
|
+
declare function planChapterAcceptanceMatrix(manifest: ChapterAcceptanceManifest): ChapterAcceptanceScenario[];
|
|
104
|
+
|
|
105
|
+
export { type ChapterAcceptanceManifest, type ChapterAcceptanceOptions, type ChapterAcceptanceScenario, type ChapterAcceptanceScenarioKind, type ChapterAcceptanceSeatOption, type ChapterAcceptanceSku, type ChapterAcceptanceTarget, type ChapterAcceptanceValidation, type ChapterJoinConfigProbe, type ChapterJoinContractFinding, type ChapterJoinContractProbeResult, planChapterAcceptanceMatrix, probeChapterJoinContracts, validateChapterAcceptanceManifest };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Wire-independent test contract for one SKU offered by one Chapter site. */
|
|
2
|
+
interface ChapterAcceptanceSku {
|
|
3
|
+
/** Stable BNF SKU id. It is also the Chapter tier id for membership SKUs. */
|
|
4
|
+
id: string;
|
|
5
|
+
kind: "membership" | "add_on";
|
|
6
|
+
active: boolean;
|
|
7
|
+
priceCents: number;
|
|
8
|
+
currency: string;
|
|
9
|
+
billing: "month" | "year" | "once";
|
|
10
|
+
/** Required for an active paid SKU and absent for a free SKU. */
|
|
11
|
+
stripePriceId?: string;
|
|
12
|
+
/** Authoritative comparison price. Omit it when no real discount exists. */
|
|
13
|
+
compareAtPriceCents?: number;
|
|
14
|
+
/** How long the difference from compareAtPriceCents is retained. */
|
|
15
|
+
discountDuration?: "first_term" | "while_active";
|
|
16
|
+
}
|
|
17
|
+
/** Named-seat behavior that must be exercised as a membership add-on. */
|
|
18
|
+
interface ChapterAcceptanceSeatOption {
|
|
19
|
+
skuId: string;
|
|
20
|
+
purchaseWindowCalendarMonths: number;
|
|
21
|
+
named: true;
|
|
22
|
+
uniqueEmail: true;
|
|
23
|
+
renewal: "membership_aligned";
|
|
24
|
+
chargeTiming: "start";
|
|
25
|
+
}
|
|
26
|
+
/** Options that change the Chapter signup journey independently of SKU price. */
|
|
27
|
+
interface ChapterAcceptanceOptions {
|
|
28
|
+
ordinaryInterview: true;
|
|
29
|
+
invitationInterviewWaiver: boolean;
|
|
30
|
+
autoRenew: true;
|
|
31
|
+
additionalSeat?: ChapterAcceptanceSeatOption;
|
|
32
|
+
}
|
|
33
|
+
/** One deployed runtime target. A logical site may have multiple authoritative dev targets. */
|
|
34
|
+
interface ChapterAcceptanceTarget {
|
|
35
|
+
targetId: string;
|
|
36
|
+
siteId: string;
|
|
37
|
+
origin: string;
|
|
38
|
+
target: {
|
|
39
|
+
appId: string;
|
|
40
|
+
environment: string;
|
|
41
|
+
dataEnvironment: string;
|
|
42
|
+
runtime: string;
|
|
43
|
+
chapterId: string;
|
|
44
|
+
};
|
|
45
|
+
stripeMode: "test" | "live";
|
|
46
|
+
skus: readonly ChapterAcceptanceSku[];
|
|
47
|
+
options: ChapterAcceptanceOptions;
|
|
48
|
+
}
|
|
49
|
+
/** Complete multi-site contract consumed by hermetic and deployed-dev tests. */
|
|
50
|
+
interface ChapterAcceptanceManifest {
|
|
51
|
+
schemaVersion: 1;
|
|
52
|
+
targets: readonly ChapterAcceptanceTarget[];
|
|
53
|
+
}
|
|
54
|
+
/** Runtime-safe validation result for JSON-loaded acceptance manifests. */
|
|
55
|
+
type ChapterAcceptanceValidation = {
|
|
56
|
+
ok: true;
|
|
57
|
+
value: ChapterAcceptanceManifest;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
errors: readonly string[];
|
|
61
|
+
};
|
|
62
|
+
/** Closed set of deterministic and provider-backed acceptance behaviors. */
|
|
63
|
+
type ChapterAcceptanceScenarioKind = "catalog_contract" | "amount_tamper" | "wrong_environment_event" | "sibling_event_isolation" | "submit_replay" | "post_stripe_compensation" | "paid_signup" | "free_signup" | "ordinary_interview" | "invitation_interview_waiver" | "expired_interview_waiver" | "replayed_interview_waiver" | "named_seat" | "duplicate_seat_email" | "expired_seat_window" | "canceled_membership" | "renewal" | "refund_cancel";
|
|
64
|
+
/** One stable generated case. Provider cases are opt-in and test-mode only. */
|
|
65
|
+
interface ChapterAcceptanceScenario {
|
|
66
|
+
id: string;
|
|
67
|
+
suite: "hermetic" | "provider_dev";
|
|
68
|
+
kind: ChapterAcceptanceScenarioKind;
|
|
69
|
+
targetId: string;
|
|
70
|
+
siteId: string;
|
|
71
|
+
skuId?: string;
|
|
72
|
+
siblingTargetId?: string;
|
|
73
|
+
}
|
|
74
|
+
/** Public response shape needed by the non-mutating deployed contract probe. */
|
|
75
|
+
interface ChapterJoinConfigProbe {
|
|
76
|
+
id?: unknown;
|
|
77
|
+
paymentsReady?: unknown;
|
|
78
|
+
tiers?: unknown;
|
|
79
|
+
}
|
|
80
|
+
/** One exact expected-versus-observed difference from a deployed public contract. */
|
|
81
|
+
interface ChapterJoinContractFinding {
|
|
82
|
+
targetId: string;
|
|
83
|
+
siteId: string;
|
|
84
|
+
path: string;
|
|
85
|
+
expected: unknown;
|
|
86
|
+
actual: unknown;
|
|
87
|
+
}
|
|
88
|
+
/** Aggregate result of a non-mutating multi-target join-contract probe. */
|
|
89
|
+
interface ChapterJoinContractProbeResult {
|
|
90
|
+
ok: boolean;
|
|
91
|
+
findings: ChapterJoinContractFinding[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Probe public deployed join contracts without creating applications or provider objects. */
|
|
95
|
+
declare function probeChapterJoinContracts(manifest: ChapterAcceptanceManifest, fetcher?: typeof fetch): Promise<ChapterJoinContractProbeResult>;
|
|
96
|
+
|
|
97
|
+
/** Validate the exact facts a multi-site acceptance run needs before it writes anywhere. */
|
|
98
|
+
declare function validateChapterAcceptanceManifest(value: unknown): ChapterAcceptanceValidation;
|
|
99
|
+
/**
|
|
100
|
+
* Build exhaustive pure cases plus a bounded provider-backed development matrix.
|
|
101
|
+
* Throws before returning provider work when the manifest is invalid or names live Stripe.
|
|
102
|
+
*/
|
|
103
|
+
declare function planChapterAcceptanceMatrix(manifest: ChapterAcceptanceManifest): ChapterAcceptanceScenario[];
|
|
104
|
+
|
|
105
|
+
export { type ChapterAcceptanceManifest, type ChapterAcceptanceOptions, type ChapterAcceptanceScenario, type ChapterAcceptanceScenarioKind, type ChapterAcceptanceSeatOption, type ChapterAcceptanceSku, type ChapterAcceptanceTarget, type ChapterAcceptanceValidation, type ChapterJoinConfigProbe, type ChapterJoinContractFinding, type ChapterJoinContractProbeResult, planChapterAcceptanceMatrix, probeChapterJoinContracts, validateChapterAcceptanceManifest };
|