@odla-ai/chapter 0.47.10 → 0.48.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.
@@ -14,6 +14,27 @@ interface ChapterAcceptanceSku {
14
14
  /** How long the difference from compareAtPriceCents is retained. */
15
15
  discountDuration?: "first_term" | "while_active";
16
16
  }
17
+ /** Explicit expected terms at a test-clock instant, independent of the SKU price.
18
+ * These are acceptance assertions, never permission to issue an offer or change Stripe.
19
+ */
20
+ interface ChapterAcceptanceCommercialCase {
21
+ id: string;
22
+ skuId: string;
23
+ purchasedAt: number;
24
+ offeringVersionId: string;
25
+ founding: boolean;
26
+ /** Cohort policy may apply to a Steward without granting a price discount. */
27
+ cohortPolicyVersionId?: string;
28
+ dueTodayCents: number;
29
+ initialTermEndsAt: number;
30
+ renewalCents: number;
31
+ discount?: {
32
+ policyVersionId: string;
33
+ percentOffBps: number;
34
+ duration: "first_term" | "while_active" | "fixed_end";
35
+ endsAt?: number;
36
+ };
37
+ }
17
38
  /** Named-seat behavior that must be exercised as a membership add-on. */
18
39
  interface ChapterAcceptanceSeatOption {
19
40
  skuId: string;
@@ -45,6 +66,7 @@ interface ChapterAcceptanceTarget {
45
66
  stripeMode: "test" | "live";
46
67
  skus: readonly ChapterAcceptanceSku[];
47
68
  options: ChapterAcceptanceOptions;
69
+ commercialCases?: readonly ChapterAcceptanceCommercialCase[];
48
70
  }
49
71
  /** Complete multi-site contract consumed by hermetic and deployed-dev tests. */
50
72
  interface ChapterAcceptanceManifest {
@@ -60,7 +82,7 @@ type ChapterAcceptanceValidation = {
60
82
  errors: readonly string[];
61
83
  };
62
84
  /** 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";
85
+ type ChapterAcceptanceScenarioKind = "catalog_contract" | "commercial_quote" | "commercial_renewal" | "commercial_policy_replay" | "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" | "seat_declined_refund" | "unaccepted_seat_refund" | "seat_membership_alignment" | "canceled_membership" | "renewal" | "refund_cancel";
64
86
  /** One stable generated case. Provider cases are opt-in and test-mode only. */
65
87
  interface ChapterAcceptanceScenario {
66
88
  id: string;
@@ -69,6 +91,9 @@ interface ChapterAcceptanceScenario {
69
91
  targetId: string;
70
92
  siteId: string;
71
93
  skuId?: string;
94
+ /** Membership whose add-on path is exercised; never assume the first paid SKU. */
95
+ parentSkuId?: string;
96
+ commercialCaseId?: string;
72
97
  siblingTargetId?: string;
73
98
  }
74
99
  /** Public response shape needed by the non-mutating deployed contract probe. */
@@ -95,6 +120,8 @@ interface ChapterAcceptanceExecutionContext {
95
120
  scenario: ChapterAcceptanceScenario;
96
121
  target: ChapterAcceptanceTarget;
97
122
  sku?: ChapterAcceptanceSku;
123
+ parentSku?: ChapterAcceptanceSku;
124
+ commercialCase?: ChapterAcceptanceCommercialCase;
98
125
  siblingTarget?: ChapterAcceptanceTarget;
99
126
  }
100
127
  /** Provider references permitted in durable evidence. Values must be opaque ids or digests, never PII. */
@@ -137,6 +164,9 @@ interface ChapterAcceptanceRunResult {
137
164
  cases: ChapterAcceptanceCaseResult[];
138
165
  }
139
166
 
167
+ /** Compare real observed checkout terms against a separately authored acceptance vector. */
168
+ declare function assertCommercialQuote(expected: ChapterAcceptanceCommercialCase, sku: ChapterAcceptanceSku, actual: unknown): boolean;
169
+
140
170
  /**
141
171
  * Probe public deployed join contracts without creating applications or provider objects.
142
172
  * Each request, including its body, is limited to ten seconds and 64 KiB. Redirects
@@ -157,6 +187,66 @@ declare function runChapterAcceptanceSuite(manifest: ChapterAcceptanceManifest,
157
187
  /** PII-free line report suitable for CI logs and PM evidence. */
158
188
  declare function formatChapterAcceptanceRun(result: ChapterAcceptanceRunResult): string;
159
189
 
190
+ /** A release proof is tied to a reviewed source commit and one exact dev deployment. */
191
+ interface ChapterReleaseTarget {
192
+ targetId: string;
193
+ appId: string;
194
+ chapterId: string;
195
+ runtime: string;
196
+ environment: "dev";
197
+ dataEnvironment: "dev";
198
+ stripeMode: "test";
199
+ sourceCommit: string;
200
+ deploymentVersion: string;
201
+ }
202
+ /** Required behavior is defined independently of currently enabled feature flags. */
203
+ interface ChapterReleaseRequirement {
204
+ id: string;
205
+ targetId: string;
206
+ phase: "deterministic" | "deployed_contract" | "provider_dev" | "browser_dev";
207
+ assertions: readonly string[];
208
+ /** Each provider kind needs at least one opaque id/digest from the real attempt. */
209
+ providers: readonly ("stripe" | "clerk" | "calendar" | "email")[];
210
+ cleanup: readonly string[];
211
+ }
212
+ /** An independent release contract, not a list inferred from whichever tests happened to run. */
213
+ interface ChapterReleasePlan {
214
+ schemaVersion: 1;
215
+ targets: readonly ChapterReleaseTarget[];
216
+ requirements: readonly ChapterReleaseRequirement[];
217
+ }
218
+ /** Structured result from an actual driver; this completeness check does not execute or authenticate a driver. */
219
+ interface ChapterReleaseProof {
220
+ requirementId: string;
221
+ target: ChapterReleaseTarget;
222
+ phase: ChapterReleaseRequirement["phase"];
223
+ outcome: "passed" | "failed" | "blocked";
224
+ assertions: Readonly<Record<string, boolean>>;
225
+ references: readonly {
226
+ provider: ChapterReleaseRequirement["providers"][number];
227
+ id: string;
228
+ }[];
229
+ cleanup: Readonly<Record<string, boolean>>;
230
+ }
231
+ /** Bounded release blockers. Passing requests a human review, never permission to deploy. */
232
+ interface ChapterReleaseAssessment {
233
+ readyForReview: boolean;
234
+ productionAuthorized: false;
235
+ required: number;
236
+ passed: number;
237
+ blockers: readonly {
238
+ requirementId: string;
239
+ code: string;
240
+ }[];
241
+ }
242
+ /**
243
+ * Require a complete, exact-candidate release evidence set. Missing, duplicate,
244
+ * failed, stale, wrong-runtime, skipped, or incompletely cleaned attempts block
245
+ * review. This is evidence validation, not provider execution: callers must use
246
+ * real drivers and retain their independently inspectable run artifacts.
247
+ */
248
+ declare function assessChapterReleaseEvidence(plan: ChapterReleasePlan, proofs: readonly ChapterReleaseProof[]): ChapterReleaseAssessment;
249
+
160
250
  /** Validate the exact facts a multi-site acceptance run needs before it writes anywhere. */
161
251
  declare function validateChapterAcceptanceManifest(value: unknown): ChapterAcceptanceValidation;
162
252
  /**
@@ -165,4 +255,4 @@ declare function validateChapterAcceptanceManifest(value: unknown): ChapterAccep
165
255
  */
166
256
  declare function planChapterAcceptanceMatrix(manifest: ChapterAcceptanceManifest): ChapterAcceptanceScenario[];
167
257
 
168
- export { type ChapterAcceptanceCaseResult, type ChapterAcceptanceCleanupEvidence, type ChapterAcceptanceEvidence, type ChapterAcceptanceExecutionContext, type ChapterAcceptanceExecutor, type ChapterAcceptanceManifest, type ChapterAcceptanceOptions, type ChapterAcceptanceProviderReference, type ChapterAcceptanceRunResult, type ChapterAcceptanceScenario, type ChapterAcceptanceScenarioKind, type ChapterAcceptanceSeatOption, type ChapterAcceptanceSku, type ChapterAcceptanceTarget, type ChapterAcceptanceValidation, type ChapterJoinConfigProbe, type ChapterJoinContractFinding, type ChapterJoinContractProbeResult, formatChapterAcceptanceRun, planChapterAcceptanceMatrix, probeChapterJoinContracts, runChapterAcceptanceSuite, validateChapterAcceptanceManifest };
258
+ export { type ChapterAcceptanceCaseResult, type ChapterAcceptanceCleanupEvidence, type ChapterAcceptanceCommercialCase, type ChapterAcceptanceEvidence, type ChapterAcceptanceExecutionContext, type ChapterAcceptanceExecutor, type ChapterAcceptanceManifest, type ChapterAcceptanceOptions, type ChapterAcceptanceProviderReference, type ChapterAcceptanceRunResult, type ChapterAcceptanceScenario, type ChapterAcceptanceScenarioKind, type ChapterAcceptanceSeatOption, type ChapterAcceptanceSku, type ChapterAcceptanceTarget, type ChapterAcceptanceValidation, type ChapterJoinConfigProbe, type ChapterJoinContractFinding, type ChapterJoinContractProbeResult, type ChapterReleaseAssessment, type ChapterReleasePlan, type ChapterReleaseProof, type ChapterReleaseRequirement, type ChapterReleaseTarget, assertCommercialQuote, assessChapterReleaseEvidence, formatChapterAcceptanceRun, planChapterAcceptanceMatrix, probeChapterJoinContracts, runChapterAcceptanceSuite, validateChapterAcceptanceManifest };
package/dist/testing.d.ts CHANGED
@@ -14,6 +14,27 @@ interface ChapterAcceptanceSku {
14
14
  /** How long the difference from compareAtPriceCents is retained. */
15
15
  discountDuration?: "first_term" | "while_active";
16
16
  }
17
+ /** Explicit expected terms at a test-clock instant, independent of the SKU price.
18
+ * These are acceptance assertions, never permission to issue an offer or change Stripe.
19
+ */
20
+ interface ChapterAcceptanceCommercialCase {
21
+ id: string;
22
+ skuId: string;
23
+ purchasedAt: number;
24
+ offeringVersionId: string;
25
+ founding: boolean;
26
+ /** Cohort policy may apply to a Steward without granting a price discount. */
27
+ cohortPolicyVersionId?: string;
28
+ dueTodayCents: number;
29
+ initialTermEndsAt: number;
30
+ renewalCents: number;
31
+ discount?: {
32
+ policyVersionId: string;
33
+ percentOffBps: number;
34
+ duration: "first_term" | "while_active" | "fixed_end";
35
+ endsAt?: number;
36
+ };
37
+ }
17
38
  /** Named-seat behavior that must be exercised as a membership add-on. */
18
39
  interface ChapterAcceptanceSeatOption {
19
40
  skuId: string;
@@ -45,6 +66,7 @@ interface ChapterAcceptanceTarget {
45
66
  stripeMode: "test" | "live";
46
67
  skus: readonly ChapterAcceptanceSku[];
47
68
  options: ChapterAcceptanceOptions;
69
+ commercialCases?: readonly ChapterAcceptanceCommercialCase[];
48
70
  }
49
71
  /** Complete multi-site contract consumed by hermetic and deployed-dev tests. */
50
72
  interface ChapterAcceptanceManifest {
@@ -60,7 +82,7 @@ type ChapterAcceptanceValidation = {
60
82
  errors: readonly string[];
61
83
  };
62
84
  /** 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";
85
+ type ChapterAcceptanceScenarioKind = "catalog_contract" | "commercial_quote" | "commercial_renewal" | "commercial_policy_replay" | "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" | "seat_declined_refund" | "unaccepted_seat_refund" | "seat_membership_alignment" | "canceled_membership" | "renewal" | "refund_cancel";
64
86
  /** One stable generated case. Provider cases are opt-in and test-mode only. */
65
87
  interface ChapterAcceptanceScenario {
66
88
  id: string;
@@ -69,6 +91,9 @@ interface ChapterAcceptanceScenario {
69
91
  targetId: string;
70
92
  siteId: string;
71
93
  skuId?: string;
94
+ /** Membership whose add-on path is exercised; never assume the first paid SKU. */
95
+ parentSkuId?: string;
96
+ commercialCaseId?: string;
72
97
  siblingTargetId?: string;
73
98
  }
74
99
  /** Public response shape needed by the non-mutating deployed contract probe. */
@@ -95,6 +120,8 @@ interface ChapterAcceptanceExecutionContext {
95
120
  scenario: ChapterAcceptanceScenario;
96
121
  target: ChapterAcceptanceTarget;
97
122
  sku?: ChapterAcceptanceSku;
123
+ parentSku?: ChapterAcceptanceSku;
124
+ commercialCase?: ChapterAcceptanceCommercialCase;
98
125
  siblingTarget?: ChapterAcceptanceTarget;
99
126
  }
100
127
  /** Provider references permitted in durable evidence. Values must be opaque ids or digests, never PII. */
@@ -137,6 +164,9 @@ interface ChapterAcceptanceRunResult {
137
164
  cases: ChapterAcceptanceCaseResult[];
138
165
  }
139
166
 
167
+ /** Compare real observed checkout terms against a separately authored acceptance vector. */
168
+ declare function assertCommercialQuote(expected: ChapterAcceptanceCommercialCase, sku: ChapterAcceptanceSku, actual: unknown): boolean;
169
+
140
170
  /**
141
171
  * Probe public deployed join contracts without creating applications or provider objects.
142
172
  * Each request, including its body, is limited to ten seconds and 64 KiB. Redirects
@@ -157,6 +187,66 @@ declare function runChapterAcceptanceSuite(manifest: ChapterAcceptanceManifest,
157
187
  /** PII-free line report suitable for CI logs and PM evidence. */
158
188
  declare function formatChapterAcceptanceRun(result: ChapterAcceptanceRunResult): string;
159
189
 
190
+ /** A release proof is tied to a reviewed source commit and one exact dev deployment. */
191
+ interface ChapterReleaseTarget {
192
+ targetId: string;
193
+ appId: string;
194
+ chapterId: string;
195
+ runtime: string;
196
+ environment: "dev";
197
+ dataEnvironment: "dev";
198
+ stripeMode: "test";
199
+ sourceCommit: string;
200
+ deploymentVersion: string;
201
+ }
202
+ /** Required behavior is defined independently of currently enabled feature flags. */
203
+ interface ChapterReleaseRequirement {
204
+ id: string;
205
+ targetId: string;
206
+ phase: "deterministic" | "deployed_contract" | "provider_dev" | "browser_dev";
207
+ assertions: readonly string[];
208
+ /** Each provider kind needs at least one opaque id/digest from the real attempt. */
209
+ providers: readonly ("stripe" | "clerk" | "calendar" | "email")[];
210
+ cleanup: readonly string[];
211
+ }
212
+ /** An independent release contract, not a list inferred from whichever tests happened to run. */
213
+ interface ChapterReleasePlan {
214
+ schemaVersion: 1;
215
+ targets: readonly ChapterReleaseTarget[];
216
+ requirements: readonly ChapterReleaseRequirement[];
217
+ }
218
+ /** Structured result from an actual driver; this completeness check does not execute or authenticate a driver. */
219
+ interface ChapterReleaseProof {
220
+ requirementId: string;
221
+ target: ChapterReleaseTarget;
222
+ phase: ChapterReleaseRequirement["phase"];
223
+ outcome: "passed" | "failed" | "blocked";
224
+ assertions: Readonly<Record<string, boolean>>;
225
+ references: readonly {
226
+ provider: ChapterReleaseRequirement["providers"][number];
227
+ id: string;
228
+ }[];
229
+ cleanup: Readonly<Record<string, boolean>>;
230
+ }
231
+ /** Bounded release blockers. Passing requests a human review, never permission to deploy. */
232
+ interface ChapterReleaseAssessment {
233
+ readyForReview: boolean;
234
+ productionAuthorized: false;
235
+ required: number;
236
+ passed: number;
237
+ blockers: readonly {
238
+ requirementId: string;
239
+ code: string;
240
+ }[];
241
+ }
242
+ /**
243
+ * Require a complete, exact-candidate release evidence set. Missing, duplicate,
244
+ * failed, stale, wrong-runtime, skipped, or incompletely cleaned attempts block
245
+ * review. This is evidence validation, not provider execution: callers must use
246
+ * real drivers and retain their independently inspectable run artifacts.
247
+ */
248
+ declare function assessChapterReleaseEvidence(plan: ChapterReleasePlan, proofs: readonly ChapterReleaseProof[]): ChapterReleaseAssessment;
249
+
160
250
  /** Validate the exact facts a multi-site acceptance run needs before it writes anywhere. */
161
251
  declare function validateChapterAcceptanceManifest(value: unknown): ChapterAcceptanceValidation;
162
252
  /**
@@ -165,4 +255,4 @@ declare function validateChapterAcceptanceManifest(value: unknown): ChapterAccep
165
255
  */
166
256
  declare function planChapterAcceptanceMatrix(manifest: ChapterAcceptanceManifest): ChapterAcceptanceScenario[];
167
257
 
168
- export { type ChapterAcceptanceCaseResult, type ChapterAcceptanceCleanupEvidence, type ChapterAcceptanceEvidence, type ChapterAcceptanceExecutionContext, type ChapterAcceptanceExecutor, type ChapterAcceptanceManifest, type ChapterAcceptanceOptions, type ChapterAcceptanceProviderReference, type ChapterAcceptanceRunResult, type ChapterAcceptanceScenario, type ChapterAcceptanceScenarioKind, type ChapterAcceptanceSeatOption, type ChapterAcceptanceSku, type ChapterAcceptanceTarget, type ChapterAcceptanceValidation, type ChapterJoinConfigProbe, type ChapterJoinContractFinding, type ChapterJoinContractProbeResult, formatChapterAcceptanceRun, planChapterAcceptanceMatrix, probeChapterJoinContracts, runChapterAcceptanceSuite, validateChapterAcceptanceManifest };
258
+ export { type ChapterAcceptanceCaseResult, type ChapterAcceptanceCleanupEvidence, type ChapterAcceptanceCommercialCase, type ChapterAcceptanceEvidence, type ChapterAcceptanceExecutionContext, type ChapterAcceptanceExecutor, type ChapterAcceptanceManifest, type ChapterAcceptanceOptions, type ChapterAcceptanceProviderReference, type ChapterAcceptanceRunResult, type ChapterAcceptanceScenario, type ChapterAcceptanceScenarioKind, type ChapterAcceptanceSeatOption, type ChapterAcceptanceSku, type ChapterAcceptanceTarget, type ChapterAcceptanceValidation, type ChapterJoinConfigProbe, type ChapterJoinContractFinding, type ChapterJoinContractProbeResult, type ChapterReleaseAssessment, type ChapterReleasePlan, type ChapterReleaseProof, type ChapterReleaseRequirement, type ChapterReleaseTarget, assertCommercialQuote, assessChapterReleaseEvidence, formatChapterAcceptanceRun, planChapterAcceptanceMatrix, probeChapterJoinContracts, runChapterAcceptanceSuite, validateChapterAcceptanceManifest };
package/dist/testing.js CHANGED
@@ -1,3 +1,50 @@
1
+ // src/testing-commercial.ts
2
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3
+ var id = (value) => typeof value === "string" && /^[a-z0-9][a-z0-9_.:-]{1,159}$/.test(value);
4
+ var time = (value) => Number.isSafeInteger(value) && Number(value) > 0;
5
+ function validateCommercialCases(value, skus, path) {
6
+ if (value === void 0) return [];
7
+ if (!Array.isArray(value) || !value.length || value.length > 32) return [`${path} must be a non-empty array of at most 32 cases`];
8
+ const errors = [];
9
+ const ids = /* @__PURE__ */ new Set();
10
+ for (const [index, raw] of value.entries()) {
11
+ const item = object(raw);
12
+ const at = `${path}[${index}]`;
13
+ if (!item) {
14
+ errors.push(`${at} must be an object`);
15
+ continue;
16
+ }
17
+ if (!id(item.id) || ids.has(String(item.id))) errors.push(`${at}.id must be a unique stable id`);
18
+ ids.add(String(item.id));
19
+ const sku = skus.find((candidate) => candidate.id === item.skuId && candidate.active && candidate.kind === "membership" && candidate.priceCents > 0);
20
+ if (!sku) errors.push(`${at}.skuId must name an active paid base membership SKU`);
21
+ if (sku?.compareAtPriceCents !== void 0 || sku?.discountDuration !== void 0) {
22
+ errors.push(`${at} must not combine a discounted SKU with a separate commercial policy`);
23
+ }
24
+ if (!id(item.offeringVersionId)) errors.push(`${at}.offeringVersionId is required`);
25
+ if (typeof item.founding !== "boolean" || item.founding && !id(item.cohortPolicyVersionId) || item.cohortPolicyVersionId !== void 0 && !id(item.cohortPolicyVersionId)) errors.push(`${at}.founding requires a stable cohort policy identity`);
26
+ if (!time(item.purchasedAt) || !time(item.initialTermEndsAt) || item.initialTermEndsAt <= item.purchasedAt) {
27
+ errors.push(`${at} must specify a purchase instant and a later initial term end`);
28
+ }
29
+ const discount = object(item.discount);
30
+ if (item.discount !== void 0 && (!discount || !id(discount.policyVersionId) || !Number.isInteger(discount.percentOffBps) || Number(discount.percentOffBps) <= 0 || Number(discount.percentOffBps) >= 1e4 || !["first_term", "while_active", "fixed_end"].includes(String(discount.duration)) || (discount.duration === "fixed_end" ? !time(discount.endsAt) || Number(discount.endsAt) <= Number(item.purchasedAt) : discount.endsAt !== void 0))) {
31
+ errors.push(`${at}.discount must specify a version, percentage, and explicit expiry policy`);
32
+ }
33
+ if (sku) {
34
+ const due = Math.round(sku.priceCents * (1e4 - Number(discount?.percentOffBps ?? 0)) / 1e4);
35
+ const ongoing = discount?.duration === "while_active" || discount?.duration === "fixed_end" && Number(discount.endsAt) > Number(item.initialTermEndsAt);
36
+ if (item.dueTodayCents !== due || item.renewalCents !== (ongoing ? due : sku.priceCents)) {
37
+ errors.push(`${at} expected initial and renewal amounts must match the separate policy on the base price`);
38
+ }
39
+ }
40
+ }
41
+ return errors;
42
+ }
43
+ function assertCommercialQuote(expected, sku, actual) {
44
+ const quote = object(actual), lines = object(quote?.lineItems), terms = object(quote?.commercial);
45
+ return Boolean(quote && terms && sku.id === expected.skuId && quote.tierId === expected.skuId && quote.currency === sku.currency && quote.interval === sku.billing && lines?.standardCents === sku.priceCents && lines.discountCents === sku.priceCents - expected.dueTodayCents && lines.dueTodayCents === expected.dueTodayCents && terms.offeringVersionId === expected.offeringVersionId && terms.initialTermEndsAt === expected.initialTermEndsAt && terms.renewalCents === expected.renewalCents && terms.autoRenew === true && terms.discountPolicyVersionId === expected.discount?.policyVersionId && terms.discountDuration === expected.discount?.duration);
46
+ }
47
+
1
48
  // src/testing-probe.ts
2
49
  var MAX_BODY_BYTES = 64 * 1024;
3
50
  var TIMEOUT_MS = 1e4;
@@ -206,14 +253,16 @@ function validateCleanup(cleanup) {
206
253
  validateLabels(cleanup.checks, "cleanup.checks");
207
254
  return { status: cleanup.status, checks: [...cleanup.checks] };
208
255
  }
209
- function contextFor(manifest, id) {
210
- const scenario2 = planChapterAcceptanceMatrix(manifest).find((item) => item.id === id);
211
- if (!scenario2) throw new TypeError(`unknown acceptance scenario ${id}`);
256
+ function contextFor(manifest, id2) {
257
+ const scenario2 = planChapterAcceptanceMatrix(manifest).find((item) => item.id === id2);
258
+ if (!scenario2) throw new TypeError(`unknown acceptance scenario ${id2}`);
212
259
  const target = manifest.targets.find((item) => item.targetId === scenario2.targetId);
213
260
  return {
214
261
  scenario: scenario2,
215
262
  target,
216
263
  ...scenario2.skuId ? { sku: target.skus.find((item) => item.id === scenario2.skuId) } : {},
264
+ ...scenario2.parentSkuId ? { parentSku: target.skus.find((item) => item.id === scenario2.parentSkuId) } : {},
265
+ ...scenario2.commercialCaseId ? { commercialCase: target.commercialCases?.find((item) => item.id === scenario2.commercialCaseId) } : {},
217
266
  ...scenario2.siblingTargetId ? {
218
267
  siblingTarget: manifest.targets.find((item) => item.targetId === scenario2.siblingTargetId)
219
268
  } : {}
@@ -274,6 +323,66 @@ function formatChapterAcceptanceRun(result) {
274
323
  return lines.join("\n");
275
324
  }
276
325
 
326
+ // src/testing-release.ts
327
+ var ID = /^[a-z0-9][a-z0-9_.:-]{1,199}$/;
328
+ var validId = (value) => typeof value === "string" && ID.test(value);
329
+ var opaque = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_:-]{2,159}$/.test(value) && !/(?:sk|rk|pk|whsec)_(?:test_|live_)?|_secret_|bearer|password|token/i.test(value);
330
+ var keys = ["targetId", "appId", "chapterId", "runtime", "environment", "dataEnvironment", "stripeMode", "sourceCommit", "deploymentVersion"];
331
+ function assessChapterReleaseEvidence(plan, proofs) {
332
+ const blockers = [];
333
+ const block = (requirementId, code) => blockers.push({ requirementId, code });
334
+ const targets = /* @__PURE__ */ new Map();
335
+ if (!plan || plan.schemaVersion !== 1 || !Array.isArray(plan.targets) || !plan.targets.length || plan.targets.length > 64 || !Array.isArray(plan.requirements) || !plan.requirements.length || plan.requirements.length > 4096 || !Array.isArray(proofs) || proofs.length > 8192) {
336
+ return {
337
+ readyForReview: false,
338
+ productionAuthorized: false,
339
+ required: 0,
340
+ passed: 0,
341
+ blockers: [{ requirementId: "plan", code: "invalid_release_plan" }]
342
+ };
343
+ }
344
+ for (const target of plan.targets) {
345
+ if (!target || !validId(target.targetId) || targets.has(target.targetId) || ![target.appId, target.chapterId, target.runtime].every(validId) || target.environment !== "dev" || target.dataEnvironment !== "dev" || target.stripeMode !== "test" || typeof target.sourceCommit !== "string" || !/^[0-9a-f]{40}$/.test(target.sourceCommit) || !opaque(target.deploymentVersion)) {
346
+ block("plan", "invalid_or_undeployed_target");
347
+ continue;
348
+ }
349
+ targets.set(target.targetId, target);
350
+ }
351
+ const requirementIds = /* @__PURE__ */ new Set();
352
+ let passed = 0;
353
+ for (const requirement of plan.requirements) {
354
+ if (!requirement || !validId(requirement.id) || requirementIds.has(requirement.id) || !["deterministic", "deployed_contract", "provider_dev", "browser_dev"].includes(requirement.phase) || !Array.isArray(requirement.assertions) || !requirement.assertions.length || !requirement.assertions.every(validId) || new Set(requirement.assertions).size !== requirement.assertions.length || !Array.isArray(requirement.providers) || requirement.providers.some((kind) => !["stripe", "clerk", "calendar", "email"].includes(kind)) || !Array.isArray(requirement.cleanup) || !requirement.cleanup.every(validId) || requirement.phase === "provider_dev" && (!requirement.providers.length || !requirement.cleanup.length)) {
355
+ block("plan", "invalid_requirement");
356
+ continue;
357
+ }
358
+ requirementIds.add(requirement.id);
359
+ const target = targets.get(requirement.targetId);
360
+ if (!target) {
361
+ block(requirement.id, "missing_candidate_target");
362
+ continue;
363
+ }
364
+ const matching = proofs.filter((proof2) => proof2?.requirementId === requirement.id);
365
+ if (matching.length !== 1) {
366
+ block(requirement.id, matching.length ? "duplicate_proof" : "missing_proof");
367
+ continue;
368
+ }
369
+ const proof = matching[0];
370
+ const before = blockers.length;
371
+ if (!proof.target || keys.some((key) => proof.target[key] !== target[key])) block(requirement.id, "candidate_mismatch");
372
+ if (proof.phase !== requirement.phase) block(requirement.id, "wrong_execution_phase");
373
+ if (proof.outcome !== "passed") block(requirement.id, "attempt_not_passed");
374
+ if (!proof.assertions || requirement.assertions.some((id2) => proof.assertions[id2] !== true)) block(requirement.id, "assertions_incomplete");
375
+ if (!Array.isArray(proof.references) || proof.references.some((reference) => !reference || !opaque(reference.id) || !["stripe", "clerk", "calendar", "email"].includes(reference.provider)) || requirement.providers.some((provider) => !proof.references.some((reference) => reference.provider === provider))) block(requirement.id, "provider_evidence_incomplete");
376
+ if (!proof.cleanup || requirement.cleanup.some((id2) => proof.cleanup[id2] !== true)) block(requirement.id, "cleanup_incomplete");
377
+ if (blockers.length === before) passed++;
378
+ }
379
+ if (proofs.some((proof) => !proof || !requirementIds.has(proof.requirementId))) block("plan", "unexpected_proof");
380
+ for (const target of targets.values()) {
381
+ if (!plan.requirements.some((requirement) => requirement?.targetId === target.targetId)) block("plan", "uncovered_target");
382
+ }
383
+ return { readyForReview: blockers.length === 0, productionAuthorized: false, required: plan.requirements.length, passed, blockers };
384
+ }
385
+
277
386
  // src/testing.ts
278
387
  var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
279
388
  var CURRENCY = /^[a-z]{3}$/;
@@ -281,9 +390,6 @@ var STRIPE_PRICE = /^price_[A-Za-z0-9]{8,}$/;
281
390
  function activeMemberships2(site) {
282
391
  return site.skus.filter((sku) => sku.active && sku.kind === "membership");
283
392
  }
284
- function representativePaidMembership(site) {
285
- return activeMemberships2(site).find((sku) => sku.priceCents > 0);
286
- }
287
393
  function record2(value) {
288
394
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
289
395
  }
@@ -347,10 +453,10 @@ function validateChapterAcceptanceManifest(value) {
347
453
  errors.push(`${skuPath} must be an object`);
348
454
  continue;
349
455
  }
350
- const id = typeof sku.id === "string" ? sku.id : "";
351
- if (!SLUG.test(id)) errors.push(`${skuPath}.id must be a stable lowercase slug`);
352
- if (skuIds.has(id)) errors.push(`${skuPath}.id duplicates ${JSON.stringify(id)}`);
353
- if (id) skuIds.add(id);
456
+ const id2 = typeof sku.id === "string" ? sku.id : "";
457
+ if (!SLUG.test(id2)) errors.push(`${skuPath}.id must be a stable lowercase slug`);
458
+ if (skuIds.has(id2)) errors.push(`${skuPath}.id duplicates ${JSON.stringify(id2)}`);
459
+ if (id2) skuIds.add(id2);
354
460
  if (sku.kind !== "membership" && sku.kind !== "add_on") errors.push(`${skuPath}.kind must be membership or add_on`);
355
461
  if (typeof sku.active !== "boolean") errors.push(`${skuPath}.active must be boolean`);
356
462
  const priceCents = typeof sku.priceCents === "number" ? sku.priceCents : Number.NaN;
@@ -367,7 +473,7 @@ function validateChapterAcceptanceManifest(value) {
367
473
  }
368
474
  if (priceCents === 0 && sku.stripePriceId) errors.push(`${skuPath}.stripePriceId must be absent for a free SKU`);
369
475
  if (typeof sku.stripePriceId === "string" && sku.stripePriceId) {
370
- const owner = `${siteId}/${id}`;
476
+ const owner = `${siteId}/${id2}`;
371
477
  const previous = priceOwners.get(sku.stripePriceId);
372
478
  if (previous && previous !== owner) errors.push(`${skuPath}.stripePriceId is already owned by ${previous}`);
373
479
  else priceOwners.set(sku.stripePriceId, owner);
@@ -385,6 +491,7 @@ function validateChapterAcceptanceManifest(value) {
385
491
  skus.push(sku);
386
492
  }
387
493
  if (!skus.some((sku) => sku.active && sku.kind === "membership")) errors.push(`${path}.skus must contain an active membership SKU`);
494
+ errors.push(...validateCommercialCases(site.commercialCases, skus, `${path}.commercialCases`));
388
495
  const options = record2(site.options);
389
496
  if (!options) errors.push(`${path}.options must be an object`);
390
497
  if (options?.ordinaryInterview !== true) errors.push(`${path}.options.ordinaryInterview must be true`);
@@ -413,7 +520,7 @@ function validateChapterAcceptanceManifest(value) {
413
520
  function scenario(input) {
414
521
  return {
415
522
  ...input,
416
- id: [input.suite, input.targetId, input.skuId, input.siblingTargetId, input.kind].filter(Boolean).join(":")
523
+ id: [input.suite, input.targetId, input.skuId, input.parentSkuId, input.commercialCaseId, input.siblingTargetId, input.kind].filter(Boolean).join(":")
417
524
  };
418
525
  }
419
526
  function planChapterAcceptanceMatrix(manifest) {
@@ -424,6 +531,12 @@ function planChapterAcceptanceMatrix(manifest) {
424
531
  }
425
532
  const scenarios = [];
426
533
  for (const site of manifest.targets) {
534
+ for (const vector of site.commercialCases ?? []) {
535
+ const base = { targetId: site.targetId, siteId: site.siteId, skuId: vector.skuId, commercialCaseId: vector.id };
536
+ scenarios.push(scenario({ ...base, suite: "hermetic", kind: "commercial_policy_replay" }));
537
+ scenarios.push(scenario({ ...base, suite: "provider_dev", kind: "commercial_quote" }));
538
+ scenarios.push(scenario({ ...base, suite: "provider_dev", kind: "commercial_renewal" }));
539
+ }
427
540
  for (const sku of site.skus.filter((candidate) => candidate.active)) {
428
541
  scenarios.push(scenario({ suite: "hermetic", kind: "catalog_contract", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));
429
542
  if (sku.priceCents > 0) {
@@ -452,45 +565,34 @@ function planChapterAcceptanceMatrix(manifest) {
452
565
  skuId: sku.id
453
566
  }));
454
567
  }
455
- const paid = representativePaidMembership(site);
456
- if (paid) {
457
- scenarios.push(scenario({ suite: "provider_dev", kind: "ordinary_interview", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
458
- scenarios.push(scenario({ suite: "provider_dev", kind: "renewal", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
459
- scenarios.push(scenario({ suite: "provider_dev", kind: "refund_cancel", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
568
+ for (const membership of activeMemberships2(site)) {
569
+ const base = { targetId: site.targetId, siteId: site.siteId, skuId: membership.id };
570
+ scenarios.push(scenario({ ...base, suite: "provider_dev", kind: "ordinary_interview" }));
460
571
  if (site.options.invitationInterviewWaiver) {
461
- scenarios.push(scenario({ suite: "provider_dev", kind: "invitation_interview_waiver", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
462
- scenarios.push(scenario({ suite: "hermetic", kind: "expired_interview_waiver", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
463
- scenarios.push(scenario({ suite: "hermetic", kind: "replayed_interview_waiver", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
572
+ scenarios.push(scenario({ ...base, suite: "provider_dev", kind: "invitation_interview_waiver" }));
573
+ scenarios.push(scenario({ ...base, suite: "hermetic", kind: "expired_interview_waiver" }));
574
+ scenarios.push(scenario({ ...base, suite: "hermetic", kind: "replayed_interview_waiver" }));
464
575
  }
576
+ if (membership.priceCents === 0) continue;
577
+ scenarios.push(scenario({ ...base, suite: "provider_dev", kind: "renewal" }));
578
+ scenarios.push(scenario({ ...base, suite: "provider_dev", kind: "refund_cancel" }));
465
579
  if (site.options.additionalSeat) {
466
- scenarios.push(scenario({
467
- suite: "provider_dev",
468
- kind: "named_seat",
469
- targetId: site.targetId,
470
- siteId: site.siteId,
471
- skuId: site.options.additionalSeat.skuId
472
- }));
473
- scenarios.push(scenario({
474
- suite: "hermetic",
475
- kind: "duplicate_seat_email",
476
- targetId: site.targetId,
477
- siteId: site.siteId,
478
- skuId: site.options.additionalSeat.skuId
479
- }));
480
- scenarios.push(scenario({
481
- suite: "hermetic",
482
- kind: "expired_seat_window",
483
- targetId: site.targetId,
484
- siteId: site.siteId,
485
- skuId: site.options.additionalSeat.skuId
486
- }));
580
+ const seat = { ...base, skuId: site.options.additionalSeat.skuId, parentSkuId: membership.id };
581
+ for (const kind of ["named_seat", "seat_declined_refund", "unaccepted_seat_refund", "seat_membership_alignment"]) {
582
+ scenarios.push(scenario({ ...seat, suite: "provider_dev", kind }));
583
+ }
584
+ for (const kind of ["duplicate_seat_email", "expired_seat_window"]) {
585
+ scenarios.push(scenario({ ...seat, suite: "hermetic", kind }));
586
+ }
487
587
  }
488
- scenarios.push(scenario({ suite: "hermetic", kind: "canceled_membership", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));
588
+ scenarios.push(scenario({ ...base, suite: "hermetic", kind: "canceled_membership" }));
489
589
  }
490
590
  }
491
591
  return scenarios;
492
592
  }
493
593
  export {
594
+ assertCommercialQuote,
595
+ assessChapterReleaseEvidence,
494
596
  formatChapterAcceptanceRun,
495
597
  planChapterAcceptanceMatrix,
496
598
  probeChapterJoinContracts,