@oxygen-agent/cli 1.750.4 → 1.766.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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +11 -1
  3. package/dist/help.js +8 -0
  4. package/dist/index.js +481 -94
  5. package/node_modules/@oxygen/shared/dist/billing.d.ts +88 -46
  6. package/node_modules/@oxygen/shared/dist/billing.js +134 -74
  7. package/node_modules/@oxygen/shared/dist/capability-discovery.js +12 -4
  8. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  9. package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +8 -0
  10. package/node_modules/@oxygen/shared/dist/copilot-journeys.js +23 -5
  11. package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +13 -2
  12. package/node_modules/@oxygen/shared/dist/future-signup-events.js +17 -2
  13. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +106 -1
  14. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +156 -45
  15. package/node_modules/@oxygen/shared/dist/index.d.ts +4 -0
  16. package/node_modules/@oxygen/shared/dist/index.js +4 -0
  17. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +33 -0
  18. package/node_modules/@oxygen/shared/dist/object-storage.js +69 -4
  19. package/node_modules/@oxygen/shared/dist/person-name.d.ts +40 -0
  20. package/node_modules/@oxygen/shared/dist/person-name.js +23 -0
  21. package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +82 -0
  22. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +130 -0
  23. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +2 -2
  24. package/node_modules/@oxygen/shared/dist/plan-limits.js +18 -2
  25. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +50 -56
  26. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +77 -90
  27. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +62 -0
  28. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +91 -0
  29. package/node_modules/@oxygen/shared/dist/provider-funding-errors.d.ts +44 -0
  30. package/node_modules/@oxygen/shared/dist/provider-funding-errors.js +81 -0
  31. package/node_modules/@oxygen/shared/dist/publishing-limits.d.ts +24 -0
  32. package/node_modules/@oxygen/shared/dist/publishing-limits.js +24 -0
  33. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +27 -6
  34. package/node_modules/@oxygen/shared/dist/spend-safety.js +34 -6
  35. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  36. package/node_modules/@oxygen/shared/dist/version.js +6 -3
  37. package/node_modules/@oxygen/shared/package.json +10 -0
  38. package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +27 -0
  39. package/node_modules/@oxygen/workflows/dist/graph/topology.js +95 -0
  40. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +20 -0
  41. package/node_modules/@oxygen/workflows/dist/graph/types.js +2 -0
  42. package/package.json +1 -1
@@ -1,7 +1,15 @@
1
1
  export declare const FUTURE_SIGNUP_CONTRACT_VERSION = "future_signup_crm_activation.v1";
2
2
  export declare const FUTURE_SIGNUP_ACTIVATION_TAXONOMY_VERSION = "activation_commands.v1";
3
- export declare const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION = "future_signup_lifecycle.v1";
4
- export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION = "future_signup_lifecycle_projection.v1";
3
+ /** Legacy Trial-shaped lifecycle contract retained for exact replay. */
4
+ export declare const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V1: "future_signup_lifecycle.v1";
5
+ /** Durable-Free entry-edge contract. Runtime production remains in a later slice. */
6
+ export declare const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2: "future_signup_lifecycle.v2";
7
+ export declare const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION: "future_signup_lifecycle.v1";
8
+ /** Exact legacy Trial-shaped projection contract retained for immutable replay. */
9
+ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V1: "future_signup_lifecycle_projection.v1";
10
+ /** Durable-Free projection contract. No producer or drain is armed by this constant. */
11
+ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2: "future_signup_lifecycle_projection.v2";
12
+ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION: "future_signup_lifecycle_projection.v1";
5
13
  export declare const FUTURE_SIGNUP_CRM_DESTINATION = "oxygen_company_crm";
6
14
  export declare const FUTURE_SIGNUP_ACTIVATION_COMMANDS: readonly ["agent create", "crm activity log", "crm assert", "crm object add-attr", "crm object create", "crm relationships define", "crm relationships upsert", "knowledge page upsert", "publishing posts create", "sequences create", "tables create", "tables duplicate", "tables import-csv", "tables import-file", "tables import-staged", "tables import-url", "tables insert", "tables relate", "tables update", "tables upsert", "workflows apply"];
7
15
  export declare function isFutureSignupActivationCommand(command: string): boolean;
@@ -10,6 +18,8 @@ export declare function futureSignupFactId(clerkUserId: string): string;
10
18
  export declare function futureSignupOperationFactId(operationEventId: string): string;
11
19
  export declare function futureSignupActivationFactId(clerkUserId: string): string;
12
20
  export declare function futureSignupTrialStartedFactId(stripeSubscriptionId: string): string;
21
+ /** One immutable durable-Free entry edge per exact Clerk organization. */
22
+ export declare function futureSignupFreeStartedFactId(clerkOrgId: string): string;
13
23
  export declare function futureSignupPaidFactId(stripeInvoiceId: string): string;
14
24
  export declare function futureSignupLostFactId(stripeSubscriptionId: string, lossKind: "paid_churn" | "trial_non_conversion"): string;
15
25
  export declare function futureSignupSubscriptionEndedFactId(stripeSubscriptionId: string, terminationKind: "replaced" | "duplicate" | "unqualified"): string;
@@ -17,6 +27,7 @@ export declare function futureSignupWorkspaceDeletedFactId(clerkOrgId: string):
17
27
  /** Immutable cause for membership, completeness, or Company-identity projection changes. */
18
28
  export declare function futureSignupLifecycleProjectionRefreshFactId(clerkOrgId: string, sourceEventId: string): string;
19
29
  export declare function futureSignupLifecycleProjectionFactId(sourceFactId: string): string;
30
+ export declare function futureSignupLifecycleProjectionV2FactId(sourceFactId: string): string;
20
31
  export declare function futureSignupLifecycleAggregateId(clerkOrgId: string): string;
21
32
  export declare function futureSignupLifecycleEventId(factId: string, factRevision: number): string;
22
33
  export declare function isAtOrAfterFutureSignupCutover(input: {
@@ -1,7 +1,15 @@
1
1
  export const FUTURE_SIGNUP_CONTRACT_VERSION = "future_signup_crm_activation.v1";
2
2
  export const FUTURE_SIGNUP_ACTIVATION_TAXONOMY_VERSION = "activation_commands.v1";
3
- export const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION = "future_signup_lifecycle.v1";
4
- export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION = "future_signup_lifecycle_projection.v1";
3
+ /** Legacy Trial-shaped lifecycle contract retained for exact replay. */
4
+ export const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V1 = "future_signup_lifecycle.v1";
5
+ /** Durable-Free entry-edge contract. Runtime production remains in a later slice. */
6
+ export const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2 = "future_signup_lifecycle.v2";
7
+ export const FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION = FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V1;
8
+ /** Exact legacy Trial-shaped projection contract retained for immutable replay. */
9
+ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V1 = "future_signup_lifecycle_projection.v1";
10
+ /** Durable-Free projection contract. No producer or drain is armed by this constant. */
11
+ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2 = "future_signup_lifecycle_projection.v2";
12
+ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION = FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V1;
5
13
  export const FUTURE_SIGNUP_CRM_DESTINATION = "oxygen_company_crm";
6
14
  export const FUTURE_SIGNUP_ACTIVATION_COMMANDS = [
7
15
  "agent create",
@@ -45,6 +53,10 @@ export function futureSignupActivationFactId(clerkUserId) {
45
53
  export function futureSignupTrialStartedFactId(stripeSubscriptionId) {
46
54
  return `future_signup_lifecycle:trial_started:v1:${requiredId(stripeSubscriptionId, "stripeSubscriptionId")}`;
47
55
  }
56
+ /** One immutable durable-Free entry edge per exact Clerk organization. */
57
+ export function futureSignupFreeStartedFactId(clerkOrgId) {
58
+ return `future_signup_lifecycle:free_started:v2:${requiredId(clerkOrgId, "clerkOrgId")}`;
59
+ }
48
60
  export function futureSignupPaidFactId(stripeInvoiceId) {
49
61
  return `future_signup_lifecycle:paid:v1:${requiredId(stripeInvoiceId, "stripeInvoiceId")}`;
50
62
  }
@@ -64,6 +76,9 @@ export function futureSignupLifecycleProjectionRefreshFactId(clerkOrgId, sourceE
64
76
  export function futureSignupLifecycleProjectionFactId(sourceFactId) {
65
77
  return `lifecycle_projection:v1:${requiredId(sourceFactId, "sourceFactId")}`;
66
78
  }
79
+ export function futureSignupLifecycleProjectionV2FactId(sourceFactId) {
80
+ return `lifecycle_projection:v2:${requiredId(sourceFactId, "sourceFactId")}`;
81
+ }
67
82
  export function futureSignupLifecycleAggregateId(clerkOrgId) {
68
83
  return `lifecycle_aggregate:v1:clerk_org:${requiredId(clerkOrgId, "clerkOrgId")}`;
69
84
  }
@@ -1,4 +1,4 @@
1
- import { FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION } from "./future-signup-events.js";
1
+ import { FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION, FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2 } from "./future-signup-events.js";
2
2
  /** Fixed destination-head authority. Contract upgrades cannot reset this stream. */
3
3
  export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_AUTHORITY: "oxygen_product_lifecycle";
4
4
  export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_FACT_TYPES: readonly ["trial_started", "paid", "lost", "subscription_ended", "workspace_deleted", "projection_refreshed"];
@@ -7,12 +7,20 @@ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_PREBILLING_STAGES: reado
7
7
  export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_SEMANTIC_STAGES: readonly ["lead", "interested", "demo_booked", "free_trial", "paid", "lost"];
8
8
  export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_ALIAS_TYPES: readonly ["clerk_organization", "clerk_user", "domain", "email", "stripe_customer"];
9
9
  export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_ACTIVITY_TYPES: readonly ["trial_started", "plan_paid", "customer_lost", "workspace_deleted"];
10
+ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_FACT_TYPES: readonly ["trial_started", "paid", "lost", "subscription_ended", "workspace_deleted", "projection_refreshed", "free_started"];
11
+ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_BILLING_STAGES: readonly ["free", "paid", "lost"];
12
+ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_SEMANTIC_STAGES: readonly ["lead", "interested", "demo_booked", "free", "paid", "lost"];
13
+ export declare const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_ACTIVITY_TYPES: readonly ["plan_paid", "customer_lost", "workspace_deleted"];
10
14
  export type FutureSignupLifecycleProjectionFactType = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_FACT_TYPES)[number];
11
15
  export type FutureSignupLifecycleProjectionBillingStage = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_BILLING_STAGES)[number];
12
16
  export type FutureSignupLifecycleProjectionPrebillingStage = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_PREBILLING_STAGES)[number];
13
17
  export type FutureSignupLifecycleProjectionSemanticStage = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_SEMANTIC_STAGES)[number];
14
18
  export type FutureSignupLifecycleProjectionAliasType = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_ALIAS_TYPES)[number];
15
19
  export type FutureSignupLifecycleProjectionActivityType = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_ACTIVITY_TYPES)[number];
20
+ export type FutureSignupLifecycleProjectionV2FactType = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_FACT_TYPES)[number];
21
+ export type FutureSignupLifecycleProjectionV2BillingStage = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_BILLING_STAGES)[number];
22
+ export type FutureSignupLifecycleProjectionV2SemanticStage = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_SEMANTIC_STAGES)[number];
23
+ export type FutureSignupLifecycleProjectionV2ActivityType = (typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_ACTIVITY_TYPES)[number];
16
24
  export type FutureSignupLifecycleProjectionSubjectKind = "company" | "person";
17
25
  export type FutureSignupLifecycleProjectionAction = "upsert" | "neutralize";
18
26
  export type FutureSignupLifecycleProjectionLossKind = "paid_churn" | "trial_non_conversion";
@@ -173,6 +181,46 @@ export type FutureSignupLifecycleProjectionEnvelope = {
173
181
  no_send: true;
174
182
  };
175
183
  };
184
+ export type FutureSignupLifecycleProjectionV2FreeSourceFact = Omit<FutureSignupLifecycleProjectionSourceFact, "contract_version" | "fact_type" | "source" | "stripe_customer_id" | "stripe_subscription_id" | "stripe_invoice_id" | "stripe_period_start" | "stripe_period_end" | "amount_paid_cents" | "currency" | "loss_kind" | "termination_kind"> & {
185
+ contract_version: typeof FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2;
186
+ fact_type: "free_started";
187
+ source: "clerk";
188
+ stripe_customer_id: null;
189
+ stripe_subscription_id: null;
190
+ stripe_invoice_id: null;
191
+ stripe_period_start: null;
192
+ stripe_period_end: null;
193
+ amount_paid_cents: null;
194
+ currency: null;
195
+ loss_kind: null;
196
+ termination_kind: null;
197
+ };
198
+ /** Projection v2 preserves immutable v1 causes while adding one durable-Free cause. */
199
+ export type FutureSignupLifecycleProjectionV2SourceFact = FutureSignupLifecycleProjectionSourceFact | FutureSignupLifecycleProjectionV2FreeSourceFact;
200
+ export type FutureSignupLifecycleProjectionV2Subject = Omit<FutureSignupLifecycleProjectionSubject, "billing_stage"> & {
201
+ billing_stage: FutureSignupLifecycleProjectionV2BillingStage | null;
202
+ };
203
+ export type FutureSignupLifecycleProjectionV2OrganizationState = Omit<FutureSignupLifecycleProjectionOrganizationState, "billing_stage" | "loss_kind"> & {
204
+ billing_stage: FutureSignupLifecycleProjectionV2BillingStage | null;
205
+ /** Trial non-conversion remains v1 history and can never be current v2 Lost truth. */
206
+ loss_kind: "paid_churn" | null;
207
+ };
208
+ export type FutureSignupLifecycleProjectionV2Snapshot = Omit<FutureSignupLifecycleProjectionSnapshot, "organization_states" | "subjects"> & {
209
+ organization_states: FutureSignupLifecycleProjectionV2OrganizationState[];
210
+ subjects: FutureSignupLifecycleProjectionV2Subject[];
211
+ };
212
+ export type FutureSignupLifecycleProjectionV2Activity = Omit<FutureSignupLifecycleProjectionActivity, "type"> & {
213
+ type: FutureSignupLifecycleProjectionV2ActivityType | "note";
214
+ };
215
+ export type FutureSignupLifecycleProjectionV2Envelope = Omit<FutureSignupLifecycleProjectionEnvelope, "contract_version" | "upstream" | "source_fact" | "snapshot" | "activity"> & {
216
+ contract_version: typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2;
217
+ upstream: Omit<FutureSignupLifecycleProjectionEnvelope["upstream"], "provider"> & {
218
+ provider: FutureSignupLifecycleProjectionV2SourceFact["source"];
219
+ };
220
+ source_fact: FutureSignupLifecycleProjectionV2SourceFact;
221
+ snapshot: FutureSignupLifecycleProjectionV2Snapshot;
222
+ activity: FutureSignupLifecycleProjectionV2Activity;
223
+ };
176
224
  export type FutureSignupLifecycleProjectionAlias = {
177
225
  type: FutureSignupLifecycleProjectionAliasType;
178
226
  value: string;
@@ -213,6 +261,10 @@ export type FutureSignupLifecycleProjectionNormalizedSubject = {
213
261
  prebilling_candidate: FutureSignupLifecycleProjectionPrebillingCandidate | null;
214
262
  membership_contributors: FutureSignupLifecycleProjectionMembershipContributor[];
215
263
  };
264
+ export type FutureSignupLifecycleProjectionV2NormalizedSubject = Omit<FutureSignupLifecycleProjectionNormalizedSubject, "billing_stage" | "loss_kind"> & {
265
+ billing_stage: FutureSignupLifecycleProjectionV2BillingStage | null;
266
+ loss_kind: "paid_churn" | null;
267
+ };
216
268
  export type FutureSignupLifecycleProjectionParticipant = {
217
269
  kind: FutureSignupLifecycleProjectionSubjectKind;
218
270
  key: string;
@@ -235,6 +287,23 @@ export type FutureSignupLifecycleProjectionNormalizedActivity = {
235
287
  summary: null;
236
288
  participants: [];
237
289
  };
290
+ export type FutureSignupLifecycleProjectionV2NormalizedActivity = {
291
+ emit: true;
292
+ type: FutureSignupLifecycleProjectionV2ActivityType;
293
+ source_provider: typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_AUTHORITY;
294
+ provider_event_id: string;
295
+ occurred_at: string;
296
+ summary: string;
297
+ participants: FutureSignupLifecycleProjectionParticipant[];
298
+ } | {
299
+ emit: false;
300
+ type: null;
301
+ source_provider: null;
302
+ provider_event_id: null;
303
+ occurred_at: null;
304
+ summary: null;
305
+ participants: [];
306
+ };
238
307
  export type FutureSignupLifecycleProjectionFingerprintSet = {
239
308
  source_fact_fingerprint: string;
240
309
  subject_set_fingerprint: string;
@@ -275,6 +344,22 @@ export type NormalizedFutureSignupLifecycleProjectionEnvelope = {
275
344
  fingerprints: FutureSignupLifecycleProjectionFingerprintSet;
276
345
  idempotency_keys: FutureSignupLifecycleProjectionIdempotencyKeys;
277
346
  };
347
+ export type FutureSignupLifecycleProjectionV2NormalizedProjection = Omit<FutureSignupLifecycleProjectionNormalizedProjection, "contract_version" | "source_fact" | "snapshot" | "activity"> & {
348
+ contract_version: typeof FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2;
349
+ source_fact: FutureSignupLifecycleProjectionV2SourceFact;
350
+ snapshot: {
351
+ complete: true;
352
+ causal_clerk_org_id: string;
353
+ subjects: FutureSignupLifecycleProjectionV2NormalizedSubject[];
354
+ };
355
+ activity: FutureSignupLifecycleProjectionV2NormalizedActivity;
356
+ };
357
+ export type NormalizedFutureSignupLifecycleProjectionV2Envelope = {
358
+ envelope: FutureSignupLifecycleProjectionV2Envelope;
359
+ projection: FutureSignupLifecycleProjectionV2NormalizedProjection;
360
+ fingerprints: FutureSignupLifecycleProjectionFingerprintSet;
361
+ idempotency_keys: FutureSignupLifecycleProjectionIdempotencyKeys;
362
+ };
278
363
  export type FutureSignupLifecycleProjectionStageMapping = Record<FutureSignupLifecycleProjectionSemanticStage, string>;
279
364
  export type FutureSignupLifecycleProjectionDestinationApplyOptions = {
280
365
  objects: {
@@ -297,10 +382,21 @@ export type FutureSignupLifecycleProjectionDestinationApplyOptions = {
297
382
  idempotency: "event_id";
298
383
  };
299
384
  };
385
+ export type FutureSignupLifecycleProjectionV2StageMapping = Record<FutureSignupLifecycleProjectionV2SemanticStage, string>;
386
+ export type FutureSignupLifecycleProjectionV2DestinationApplyOptions = Omit<FutureSignupLifecycleProjectionDestinationApplyOptions, "stage_tokens"> & {
387
+ stage_tokens: {
388
+ people: FutureSignupLifecycleProjectionV2StageMapping;
389
+ companies: FutureSignupLifecycleProjectionV2StageMapping;
390
+ };
391
+ };
300
392
  export type NormalizedFutureSignupLifecycleProjectionDestinationApplyOptions = {
301
393
  apply_options: FutureSignupLifecycleProjectionDestinationApplyOptions;
302
394
  apply_options_fingerprint: string;
303
395
  };
396
+ export type NormalizedFutureSignupLifecycleProjectionV2DestinationApplyOptions = {
397
+ apply_options: FutureSignupLifecycleProjectionV2DestinationApplyOptions;
398
+ apply_options_fingerprint: string;
399
+ };
304
400
  /** Typed, PII-free parse failure. Raw values are intentionally never reflected. */
305
401
  export declare class FutureSignupLifecycleProjectionContractError extends TypeError {
306
402
  readonly path: string;
@@ -314,7 +410,16 @@ export declare function futureSignupLifecycleProjectionFingerprints(input: {
314
410
  sourceFact: FutureSignupLifecycleProjectionSourceFact;
315
411
  snapshot: Omit<FutureSignupLifecycleProjectionSnapshot, "subject_set_fingerprint" | "snapshot_fingerprint">;
316
412
  }): FutureSignupLifecycleProjectionFingerprintSet;
413
+ export declare function futureSignupLifecycleProjectionV2Fingerprints(input: {
414
+ eventId: string;
415
+ sourceFact: FutureSignupLifecycleProjectionV2SourceFact;
416
+ snapshot: Omit<FutureSignupLifecycleProjectionV2Snapshot, "subject_set_fingerprint" | "snapshot_fingerprint">;
417
+ }): FutureSignupLifecycleProjectionFingerprintSet;
317
418
  export declare function parseFutureSignupLifecycleProjectionJson(body: string): NormalizedFutureSignupLifecycleProjectionEnvelope;
419
+ export declare function parseFutureSignupLifecycleProjectionV2Json(body: string): NormalizedFutureSignupLifecycleProjectionV2Envelope;
318
420
  /** Validate shipped v1 and derive the closed destination view. No writes or sends. */
319
421
  export declare function normalizeFutureSignupLifecycleProjectionEnvelope(value: unknown): NormalizedFutureSignupLifecycleProjectionEnvelope;
422
+ /** Validate the exact durable-Free v2 plaintext and derive its closed destination view. */
423
+ export declare function normalizeFutureSignupLifecycleProjectionV2Envelope(value: unknown): NormalizedFutureSignupLifecycleProjectionV2Envelope;
320
424
  export declare function normalizeFutureSignupLifecycleProjectionDestinationApplyOptions(value: unknown): NormalizedFutureSignupLifecycleProjectionDestinationApplyOptions;
425
+ export declare function normalizeFutureSignupLifecycleProjectionV2DestinationApplyOptions(value: unknown): NormalizedFutureSignupLifecycleProjectionV2DestinationApplyOptions;
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION, futureSignupJourneyId, futureSignupLifecycleEventId, futureSignupLifecycleProjectionFactId, futureSignupLifecycleProjectionRefreshFactId, futureSignupLostFactId, futureSignupPaidFactId, futureSignupSubscriptionEndedFactId, futureSignupTrialStartedFactId, futureSignupWorkspaceDeletedFactId, } from "./future-signup-events.js";
2
+ import { FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION, FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2, futureSignupFreeStartedFactId, futureSignupJourneyId, futureSignupLifecycleEventId, futureSignupLifecycleProjectionFactId, futureSignupLifecycleProjectionV2FactId, futureSignupLifecycleProjectionRefreshFactId, futureSignupLostFactId, futureSignupPaidFactId, futureSignupSubscriptionEndedFactId, futureSignupTrialStartedFactId, futureSignupWorkspaceDeletedFactId, } from "./future-signup-events.js";
3
3
  /** Fixed destination-head authority. Contract upgrades cannot reset this stream. */
4
4
  export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_AUTHORITY = "oxygen_product_lifecycle";
5
5
  export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_FACT_TYPES = [
@@ -37,6 +37,45 @@ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_ACTIVITY_TYPES = [
37
37
  "customer_lost",
38
38
  "workspace_deleted",
39
39
  ];
40
+ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_FACT_TYPES = [
41
+ ...FUTURE_SIGNUP_LIFECYCLE_PROJECTION_FACT_TYPES,
42
+ "free_started",
43
+ ];
44
+ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_BILLING_STAGES = [
45
+ "free",
46
+ "paid",
47
+ "lost",
48
+ ];
49
+ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_SEMANTIC_STAGES = [
50
+ ...FUTURE_SIGNUP_LIFECYCLE_PROJECTION_PREBILLING_STAGES,
51
+ ...FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_BILLING_STAGES,
52
+ ];
53
+ export const FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_ACTIVITY_TYPES = [
54
+ "plan_paid",
55
+ "customer_lost",
56
+ "workspace_deleted",
57
+ ];
58
+ const PROJECTION_V1_SPEC = {
59
+ contractVersion: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION,
60
+ sourceContractVersions: [FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION],
61
+ billingStages: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_BILLING_STAGES,
62
+ lossKinds: ["paid_churn", "trial_non_conversion"],
63
+ activityTypes: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_ACTIVITY_TYPES,
64
+ projectionFactId: futureSignupLifecycleProjectionFactId,
65
+ activityType: legacyProjectionActivityType,
66
+ };
67
+ const PROJECTION_V2_SPEC = {
68
+ contractVersion: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION_V2,
69
+ sourceContractVersions: [
70
+ FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION,
71
+ FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2,
72
+ ],
73
+ billingStages: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_BILLING_STAGES,
74
+ lossKinds: ["paid_churn"],
75
+ activityTypes: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_ACTIVITY_TYPES,
76
+ projectionFactId: futureSignupLifecycleProjectionV2FactId,
77
+ activityType: durableFreeProjectionActivityType,
78
+ };
40
79
  /** Typed, PII-free parse failure. Raw values are intentionally never reflected. */
41
80
  export class FutureSignupLifecycleProjectionContractError extends TypeError {
42
81
  path;
@@ -51,6 +90,12 @@ export class FutureSignupLifecycleProjectionContractError extends TypeError {
51
90
  }
52
91
  /** Exact fingerprint algorithm used by the shipped v1 control builder. */
53
92
  export function futureSignupLifecycleProjectionFingerprints(input) {
93
+ return futureSignupLifecycleProjectionFingerprintsForContract(input, PROJECTION_V1_SPEC);
94
+ }
95
+ export function futureSignupLifecycleProjectionV2Fingerprints(input) {
96
+ return futureSignupLifecycleProjectionFingerprintsForContract(input, PROJECTION_V2_SPEC);
97
+ }
98
+ function futureSignupLifecycleProjectionFingerprintsForContract(input, spec) {
54
99
  const sourceFactFingerprint = sha256Canonical(input.sourceFact);
55
100
  const subjectSetFingerprint = sha256Canonical(input.snapshot.subjects.map((subject) => ({
56
101
  kind: subject.kind,
@@ -60,7 +105,7 @@ export function futureSignupLifecycleProjectionFingerprints(input) {
60
105
  })));
61
106
  const snapshotFingerprint = sha256Canonical(input.snapshot);
62
107
  const projectionFingerprint = sha256Canonical({
63
- contract_version: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION,
108
+ contract_version: spec.contractVersion,
64
109
  event_id: input.eventId,
65
110
  source_fact_fingerprint: sourceFactFingerprint,
66
111
  snapshot_fingerprint: snapshotFingerprint,
@@ -83,8 +128,25 @@ export function parseFutureSignupLifecycleProjectionJson(body) {
83
128
  }
84
129
  return normalizeFutureSignupLifecycleProjectionEnvelope(value);
85
130
  }
131
+ export function parseFutureSignupLifecycleProjectionV2Json(body) {
132
+ let value;
133
+ try {
134
+ value = JSON.parse(body);
135
+ }
136
+ catch {
137
+ invalid("$", "must be valid JSON");
138
+ }
139
+ return normalizeFutureSignupLifecycleProjectionV2Envelope(value);
140
+ }
86
141
  /** Validate shipped v1 and derive the closed destination view. No writes or sends. */
87
142
  export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
143
+ return normalizeFutureSignupLifecycleProjectionEnvelopeForContract(value, PROJECTION_V1_SPEC);
144
+ }
145
+ /** Validate the exact durable-Free v2 plaintext and derive its closed destination view. */
146
+ export function normalizeFutureSignupLifecycleProjectionV2Envelope(value) {
147
+ return normalizeFutureSignupLifecycleProjectionEnvelopeForContract(value, PROJECTION_V2_SPEC);
148
+ }
149
+ function normalizeFutureSignupLifecycleProjectionEnvelopeForContract(value, spec) {
88
150
  const root = exactRecord(value, "$", [
89
151
  "contract_version",
90
152
  "event_id",
@@ -101,7 +163,7 @@ export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
101
163
  "producer",
102
164
  "validation",
103
165
  ]);
104
- literal(root.contract_version, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION, "$.contract_version");
166
+ literal(root.contract_version, spec.contractVersion, "$.contract_version");
105
167
  literal(root.event_type, "lifecycle_projection", "$.event_type");
106
168
  literal(root.provider, "oxygen", "$.provider");
107
169
  const eventId = canonicalText(root.event_id, "$.event_id");
@@ -119,9 +181,9 @@ export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
119
181
  provider_event_id: canonicalText(upstreamRaw.provider_event_id, "$.upstream.provider_event_id"),
120
182
  source_revision: positiveInteger(upstreamRaw.source_revision, "$.upstream.source_revision"),
121
183
  };
122
- const sourceFact = parseSourceFact(root.source_fact);
123
- const snapshot = parseSnapshot(root.snapshot);
124
- const activity = parseShippedActivity(root.activity);
184
+ const sourceFact = parseSourceFact(root.source_fact, spec);
185
+ const snapshot = parseSnapshot(root.snapshot, spec);
186
+ const activity = parseShippedActivity(root.activity, spec);
125
187
  const producerRaw = exactRecord(root.producer, "$.producer", [
126
188
  "environment",
127
189
  "version",
@@ -135,7 +197,7 @@ export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
135
197
  const validationRaw = exactRecord(root.validation, "$.validation", ["synthetic", "no_send"]);
136
198
  literal(validationRaw.synthetic, false, "$.validation.synthetic");
137
199
  literal(validationRaw.no_send, true, "$.validation.no_send");
138
- const expectedProjectionFactId = futureSignupLifecycleProjectionFactId(sourceFact.fact_id);
200
+ const expectedProjectionFactId = spec.projectionFactId(sourceFact.fact_id);
139
201
  const expectedEventId = futureSignupLifecycleEventId(expectedProjectionFactId, sourceFact.fact_revision);
140
202
  same(factId, expectedProjectionFactId, "$.fact_id", "must match the source fact identity");
141
203
  same(factRevision, sourceFact.fact_revision, "$.fact_revision", "must match source_fact.fact_revision");
@@ -147,7 +209,7 @@ export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
147
209
  same(upstream.source_revision, sourceFact.source_revision, "$.upstream.source_revision", "must match source_fact.source_revision");
148
210
  same(snapshot.causal_clerk_org_id, sourceFact.clerk_org_id, "$.snapshot.causal_clerk_org_id", "must match source_fact.clerk_org_id");
149
211
  assertProjectionClosure(snapshot);
150
- assertShippedActivity(activity, sourceFact, snapshot);
212
+ assertShippedActivity(activity, sourceFact, snapshot, spec);
151
213
  const snapshotCore = {
152
214
  complete: true,
153
215
  causal_clerk_org_id: snapshot.causal_clerk_org_id,
@@ -155,15 +217,15 @@ export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
155
217
  membership_states: snapshot.membership_states,
156
218
  subjects: snapshot.subjects,
157
219
  };
158
- const fingerprints = futureSignupLifecycleProjectionFingerprints({
220
+ const fingerprints = futureSignupLifecycleProjectionFingerprintsForContract({
159
221
  eventId,
160
222
  sourceFact,
161
223
  snapshot: snapshotCore,
162
- });
224
+ }, spec);
163
225
  same(snapshot.subject_set_fingerprint, fingerprints.subject_set_fingerprint, "$.snapshot.subject_set_fingerprint", "does not match the canonical subject set");
164
- same(snapshot.snapshot_fingerprint, fingerprints.snapshot_fingerprint, "$.snapshot.snapshot_fingerprint", "does not match the canonical shipped-v1 snapshot");
226
+ same(snapshot.snapshot_fingerprint, fingerprints.snapshot_fingerprint, "$.snapshot.snapshot_fingerprint", `does not match the canonical shipped-${spec.contractVersion.endsWith(".v1") ? "v1" : "v2"} snapshot`);
165
227
  const envelope = {
166
- contract_version: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION,
228
+ contract_version: spec.contractVersion,
167
229
  event_id: eventId,
168
230
  event_type: "lifecycle_projection",
169
231
  fact_id: factId,
@@ -183,7 +245,7 @@ export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
183
245
  return {
184
246
  envelope,
185
247
  projection: {
186
- contract_version: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_CONTRACT_VERSION,
248
+ contract_version: spec.contractVersion,
187
249
  authority: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_AUTHORITY,
188
250
  event_id: eventId,
189
251
  occurred_at: occurredAt,
@@ -209,6 +271,12 @@ export function normalizeFutureSignupLifecycleProjectionEnvelope(value) {
209
271
  };
210
272
  }
211
273
  export function normalizeFutureSignupLifecycleProjectionDestinationApplyOptions(value) {
274
+ return normalizeFutureSignupLifecycleProjectionDestinationApplyOptionsForStages(value, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_SEMANTIC_STAGES);
275
+ }
276
+ export function normalizeFutureSignupLifecycleProjectionV2DestinationApplyOptions(value) {
277
+ return normalizeFutureSignupLifecycleProjectionDestinationApplyOptionsForStages(value, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_V2_SEMANTIC_STAGES);
278
+ }
279
+ function normalizeFutureSignupLifecycleProjectionDestinationApplyOptionsForStages(value, semanticStages) {
212
280
  const root = exactRecord(value, "$", [
213
281
  "objects",
214
282
  "stage_tokens",
@@ -219,8 +287,8 @@ export function normalizeFutureSignupLifecycleProjectionDestinationApplyOptions(
219
287
  literal(objectsRaw.people, "people", "$.objects.people");
220
288
  literal(objectsRaw.companies, "companies", "$.objects.companies");
221
289
  const stagesRaw = exactRecord(root.stage_tokens, "$.stage_tokens", ["people", "companies"]);
222
- const peopleStages = parseStageMapping(stagesRaw.people, "$.stage_tokens.people");
223
- const companyStages = parseStageMapping(stagesRaw.companies, "$.stage_tokens.companies");
290
+ const peopleStages = parseStageMapping(stagesRaw.people, "$.stage_tokens.people", semanticStages);
291
+ const companyStages = parseStageMapping(stagesRaw.companies, "$.stage_tokens.companies", semanticStages);
224
292
  let owner = null;
225
293
  if (root.owner_if_blank !== null) {
226
294
  const ownerRaw = exactRecord(root.owner_if_blank, "$.owner_if_blank", ["workspace_user_id"]);
@@ -257,7 +325,7 @@ export function normalizeFutureSignupLifecycleProjectionDestinationApplyOptions(
257
325
  apply_options_fingerprint: sha256Canonical(applyOptions),
258
326
  };
259
327
  }
260
- function parseSourceFact(value) {
328
+ function parseSourceFact(value, spec) {
261
329
  const path = "$.source_fact";
262
330
  const row = exactRecord(value, path, [
263
331
  "fact_id",
@@ -279,12 +347,25 @@ function parseSourceFact(value) {
279
347
  "loss_kind",
280
348
  "termination_kind",
281
349
  ]);
282
- literal(row.contract_version, FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION, `${path}.contract_version`);
350
+ let contractVersion;
351
+ if (spec.sourceContractVersions.length === 1) {
352
+ const exactContractVersion = spec.sourceContractVersions[0];
353
+ if (!exactContractVersion)
354
+ invalid(`${path}.contract_version`, "has no configured contract");
355
+ literal(row.contract_version, exactContractVersion, `${path}.contract_version`);
356
+ contractVersion = exactContractVersion;
357
+ }
358
+ else {
359
+ contractVersion = enumValue(row.contract_version, spec.sourceContractVersions, `${path}.contract_version`);
360
+ }
361
+ const factTypes = contractVersion === FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2
362
+ ? ["free_started"]
363
+ : FUTURE_SIGNUP_LIFECYCLE_PROJECTION_FACT_TYPES;
283
364
  const fact = {
284
365
  fact_id: canonicalText(row.fact_id, `${path}.fact_id`),
285
366
  fact_revision: positiveInteger(row.fact_revision, `${path}.fact_revision`),
286
- contract_version: FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION,
287
- fact_type: enumValue(row.fact_type, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_FACT_TYPES, `${path}.fact_type`),
367
+ contract_version: contractVersion,
368
+ fact_type: enumValue(row.fact_type, factTypes, `${path}.fact_type`),
288
369
  clerk_org_id: canonicalText(row.clerk_org_id, `${path}.clerk_org_id`),
289
370
  occurred_at: timestamp(row.occurred_at, `${path}.occurred_at`),
290
371
  source: enumValue(row.source, ["clerk", "clerk_org_deletion", "stripe"], `${path}.source`),
@@ -305,6 +386,23 @@ function parseSourceFact(value) {
305
386
  }
306
387
  function assertSourceFactShape(fact) {
307
388
  const path = "$.source_fact";
389
+ if (fact.contract_version === FUTURE_SIGNUP_LIFECYCLE_CONTRACT_VERSION_V2) {
390
+ same(fact.fact_type, "free_started", `${path}.fact_type`, "must be free_started for v2");
391
+ same(fact.source, "clerk", `${path}.source`, "must be clerk for free_started");
392
+ allNull(fact, [
393
+ "stripe_customer_id",
394
+ "stripe_subscription_id",
395
+ "stripe_invoice_id",
396
+ "stripe_period_start",
397
+ "stripe_period_end",
398
+ "amount_paid_cents",
399
+ "currency",
400
+ "loss_kind",
401
+ "termination_kind",
402
+ ], path);
403
+ same(fact.fact_id, futureSignupFreeStartedFactId(fact.clerk_org_id), `${path}.fact_id`, "must match the immutable durable-Free entry edge");
404
+ return;
405
+ }
308
406
  if (fact.fact_type === "trial_started") {
309
407
  same(fact.source, "stripe", `${path}.source`, "must be stripe for trial_started");
310
408
  present(fact.stripe_customer_id, `${path}.stripe_customer_id`);
@@ -390,7 +488,7 @@ function assertSourceFactShape(fact) {
390
488
  : futureSignupLifecycleProjectionRefreshFactId(fact.clerk_org_id, fact.source_event_id);
391
489
  same(fact.fact_id, expected, `${path}.fact_id`, "must match the immutable Clerk cause");
392
490
  }
393
- function parseSnapshot(value) {
491
+ function parseSnapshot(value, spec) {
394
492
  const path = "$.snapshot";
395
493
  const row = exactRecord(value, path, [
396
494
  "complete",
@@ -408,11 +506,11 @@ function parseSnapshot(value) {
408
506
  invalid(`${path}.membership_states`, "must be an array");
409
507
  if (!Array.isArray(row.subjects))
410
508
  invalid(`${path}.subjects`, "must be an array");
411
- const organizations = row.organization_states.map((item, index) => parseOrganizationState(item, `${path}.organization_states[${index}]`));
509
+ const organizations = row.organization_states.map((item, index) => parseOrganizationState(item, `${path}.organization_states[${index}]`, spec));
412
510
  assertSortedUnique(organizations, (item) => item.clerk_org_id, `${path}.organization_states`);
413
511
  const memberships = row.membership_states.map((item, index) => parseMembershipState(item, `${path}.membership_states[${index}]`));
414
512
  assertSortedUnique(memberships, (item) => `${item.clerk_user_id}\u0000${item.clerk_org_id}\u0000${item.clerk_membership_id}`, `${path}.membership_states`);
415
- const subjects = row.subjects.map((item, index) => parseSubject(item, `${path}.subjects[${index}]`));
513
+ const subjects = row.subjects.map((item, index) => parseSubject(item, `${path}.subjects[${index}]`, spec));
416
514
  assertSortedUnique(subjects, (item) => `${item.kind}\u0000${item.key}`, `${path}.subjects`);
417
515
  const subjectKeys = subjects.map((subject) => subject.key);
418
516
  if (new Set(subjectKeys).size !== subjectKeys.length) {
@@ -428,7 +526,7 @@ function parseSnapshot(value) {
428
526
  snapshot_fingerprint: fingerprint(row.snapshot_fingerprint, `${path}.snapshot_fingerprint`),
429
527
  };
430
528
  }
431
- function parseOrganizationState(value, path) {
529
+ function parseOrganizationState(value, path, spec) {
432
530
  const row = exactRecord(value, path, [
433
531
  "clerk_org_id",
434
532
  "company_key",
@@ -461,9 +559,9 @@ function parseOrganizationState(value, path) {
461
559
  company_key: canonicalText(row.company_key, `${path}.company_key`),
462
560
  company_key_history: history,
463
561
  normalized_domain: nullableDomain(row.normalized_domain, `${path}.normalized_domain`),
464
- billing_stage: nullableEnum(row.billing_stage, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_BILLING_STAGES, `${path}.billing_stage`),
562
+ billing_stage: nullableEnum(row.billing_stage, spec.billingStages, `${path}.billing_stage`),
465
563
  stage_since: nullableTimestamp(row.stage_since, `${path}.stage_since`),
466
- loss_kind: nullableEnum(row.loss_kind, ["paid_churn", "trial_non_conversion"], `${path}.loss_kind`),
564
+ loss_kind: nullableEnum(row.loss_kind, spec.lossKinds, `${path}.loss_kind`),
467
565
  winning_fact_id: nullableText(row.winning_fact_id, `${path}.winning_fact_id`),
468
566
  winning_fact_revision: nullablePositiveInteger(row.winning_fact_revision, `${path}.winning_fact_revision`),
469
567
  organization_revision: positiveInteger(row.organization_revision, `${path}.organization_revision`),
@@ -538,7 +636,7 @@ function parseMembershipState(value, path) {
538
636
  }
539
637
  return state;
540
638
  }
541
- function parseSubject(value, path) {
639
+ function parseSubject(value, path, spec) {
542
640
  const row = exactRecord(value, path, [
543
641
  "kind",
544
642
  "key",
@@ -625,7 +723,7 @@ function parseSubject(value, path) {
625
723
  const subject = {
626
724
  kind: enumValue(row.kind, ["company", "person"], `${path}.kind`),
627
725
  key: canonicalText(row.key, `${path}.key`),
628
- billing_stage: nullableEnum(row.billing_stage, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_BILLING_STAGES, `${path}.billing_stage`),
726
+ billing_stage: nullableEnum(row.billing_stage, spec.billingStages, `${path}.billing_stage`),
629
727
  stage_since: nullableTimestamp(row.stage_since, `${path}.stage_since`),
630
728
  aggregate_revision: aggregateRevision,
631
729
  aggregate_fingerprint: aggregateFingerprint,
@@ -849,7 +947,28 @@ snapshot) {
849
947
  }
850
948
  }
851
949
  }
852
- function parseShippedActivity(value) {
950
+ function legacyProjectionActivityType(sourceFact) {
951
+ if (sourceFact.fact_type === "trial_started")
952
+ return "trial_started";
953
+ if (sourceFact.fact_type === "paid")
954
+ return "plan_paid";
955
+ if (sourceFact.fact_type === "lost")
956
+ return "customer_lost";
957
+ if (sourceFact.fact_type === "workspace_deleted")
958
+ return "workspace_deleted";
959
+ return "note";
960
+ }
961
+ function durableFreeProjectionActivityType(sourceFact) {
962
+ if (sourceFact.fact_type === "paid")
963
+ return "plan_paid";
964
+ if (sourceFact.fact_type === "lost" && sourceFact.loss_kind === "paid_churn") {
965
+ return "customer_lost";
966
+ }
967
+ if (sourceFact.fact_type === "workspace_deleted")
968
+ return "workspace_deleted";
969
+ return "note";
970
+ }
971
+ function parseShippedActivity(value, spec) {
853
972
  const path = "$.activity";
854
973
  const row = exactRecord(value, path, [
855
974
  "type",
@@ -873,7 +992,7 @@ function parseShippedActivity(value) {
873
992
  "termination_kind",
874
993
  ]);
875
994
  return {
876
- type: enumValue(row.type, [...FUTURE_SIGNUP_LIFECYCLE_PROJECTION_ACTIVITY_TYPES, "note"], `${path}.type`),
995
+ type: enumValue(row.type, [...spec.activityTypes, "note"], `${path}.type`),
877
996
  source_provider: FUTURE_SIGNUP_LIFECYCLE_PROJECTION_AUTHORITY,
878
997
  provider_event_id: canonicalText(row.provider_event_id, `${path}.provider_event_id`),
879
998
  occurred_at: timestamp(row.occurred_at, `${path}.occurred_at`),
@@ -888,18 +1007,10 @@ function parseShippedActivity(value) {
888
1007
  },
889
1008
  };
890
1009
  }
891
- function assertShippedActivity(activity, sourceFact, snapshot) {
892
- const expectedType = sourceFact.fact_type === "trial_started"
893
- ? "trial_started"
894
- : sourceFact.fact_type === "paid"
895
- ? "plan_paid"
896
- : sourceFact.fact_type === "lost"
897
- ? "customer_lost"
898
- : sourceFact.fact_type === "workspace_deleted"
899
- ? "workspace_deleted"
900
- : "note";
1010
+ function assertShippedActivity(activity, sourceFact, snapshot, spec) {
1011
+ const expectedType = spec.activityType(sourceFact);
901
1012
  same(activity.type, expectedType, "$.activity.type", "does not match source_fact.fact_type");
902
- same(activity.provider_event_id, futureSignupLifecycleEventId(futureSignupLifecycleProjectionFactId(sourceFact.fact_id), sourceFact.fact_revision), "$.activity.provider_event_id", "must equal the projection event identity");
1013
+ same(activity.provider_event_id, futureSignupLifecycleEventId(spec.projectionFactId(sourceFact.fact_id), sourceFact.fact_revision), "$.activity.provider_event_id", "must equal the projection event identity");
903
1014
  same(activity.occurred_at, sourceFact.occurred_at, "$.activity.occurred_at", "must match source_fact.occurred_at");
904
1015
  same(activity.metadata.source_fact_id, sourceFact.fact_id, "$.activity.metadata.source_fact_id", "must match source_fact.fact_id");
905
1016
  same(activity.metadata.source_fact_revision, sourceFact.fact_revision, "$.activity.metadata.source_fact_revision", "must match source_fact.fact_revision");
@@ -973,7 +1084,7 @@ function normalizeSubjects(snapshot, sourceFact) {
973
1084
  });
974
1085
  }
975
1086
  function normalizeActivity(activity, sourceFact, subjects) {
976
- if (sourceFact.fact_type === "projection_refreshed" || sourceFact.fact_type === "subscription_ended") {
1087
+ if (activity.type === "note") {
977
1088
  return {
978
1089
  emit: false,
979
1090
  type: null,
@@ -1033,13 +1144,13 @@ function assertBillingShape(state, path) {
1033
1144
  invalid(`${path}.loss_kind`, "must be null unless billing_stage is lost");
1034
1145
  }
1035
1146
  }
1036
- function parseStageMapping(value, path) {
1037
- const row = exactRecord(value, path, FUTURE_SIGNUP_LIFECYCLE_PROJECTION_SEMANTIC_STAGES);
1038
- const mapping = Object.fromEntries(FUTURE_SIGNUP_LIFECYCLE_PROJECTION_SEMANTIC_STAGES.map((stage) => [
1147
+ function parseStageMapping(value, path, semanticStages) {
1148
+ const row = exactRecord(value, path, semanticStages);
1149
+ const mapping = Object.fromEntries(semanticStages.map((stage) => [
1039
1150
  stage,
1040
1151
  stageToken(row[stage], `${path}.${stage}`),
1041
1152
  ]));
1042
- if (new Set(Object.values(mapping)).size !== FUTURE_SIGNUP_LIFECYCLE_PROJECTION_SEMANTIC_STAGES.length) {
1153
+ if (new Set(Object.values(mapping)).size !== semanticStages.length) {
1043
1154
  invalid(path, "must map the six semantic stages to six distinct physical tokens");
1044
1155
  }
1045
1156
  return mapping;