@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.
- package/README.md +1 -1
- package/dist/command-manifest.js +11 -1
- package/dist/help.js +8 -0
- package/dist/index.js +481 -94
- package/node_modules/@oxygen/shared/dist/billing.d.ts +88 -46
- package/node_modules/@oxygen/shared/dist/billing.js +134 -74
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +12 -4
- package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.js +23 -5
- package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +13 -2
- package/node_modules/@oxygen/shared/dist/future-signup-events.js +17 -2
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +106 -1
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +156 -45
- package/node_modules/@oxygen/shared/dist/index.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/index.js +4 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +69 -4
- package/node_modules/@oxygen/shared/dist/person-name.d.ts +40 -0
- package/node_modules/@oxygen/shared/dist/person-name.js +23 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +82 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +130 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/plan-limits.js +18 -2
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +50 -56
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +77 -90
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +62 -0
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +91 -0
- package/node_modules/@oxygen/shared/dist/provider-funding-errors.d.ts +44 -0
- package/node_modules/@oxygen/shared/dist/provider-funding-errors.js +81 -0
- package/node_modules/@oxygen/shared/dist/publishing-limits.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/publishing-limits.js +24 -0
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +27 -6
- package/node_modules/@oxygen/shared/dist/spend-safety.js +34 -6
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +6 -3
- package/node_modules/@oxygen/shared/package.json +10 -0
- package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +27 -0
- package/node_modules/@oxygen/workflows/dist/graph/topology.js +95 -0
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +20 -0
- package/node_modules/@oxygen/workflows/dist/graph/types.js +2 -0
- 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
|
-
|
|
4
|
-
export declare const
|
|
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
|
-
|
|
4
|
-
export const
|
|
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:
|
|
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,
|
|
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 =
|
|
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 =
|
|
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",
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
287
|
-
fact_type: enumValue(row.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,
|
|
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,
|
|
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,
|
|
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
|
|
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, [...
|
|
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
|
|
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(
|
|
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 (
|
|
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,
|
|
1038
|
-
const mapping = Object.fromEntries(
|
|
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 !==
|
|
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;
|