From 4664e7e07c6ed15a0ea83951f1e84b42ee70a56f Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Wed, 29 Jul 2026 12:24:44 +0100 Subject: [PATCH 01/25] fix(billing): close phase 9b remediation blockers --- apps/api/cmd/worker/main.go | 1 + apps/api/internal/billing/repository.go | 24 +- apps/api/internal/billing/service_worker.go | 84 +++---- apps/api/internal/billingwebhook/errors.go | 4 + .../api/internal/billingwebhook/repository.go | 7 + apps/api/internal/billingwebhook/service.go | 56 +++-- .../billing_integration_test.go | 1 + .../identity_binding_integration_test.go | 237 ++++++++++++++++++ .../internal/platform/billingpostgres/jobs.go | 198 +++++++++++++-- .../platform/billingpostgres/queue_metrics.go | 9 +- .../delivery_integration_test.go | 154 ++++++++++++ .../billingwebhookpostgres/repository.go | 45 ++++ .../transport/billingwebhook/handler.go | 2 + .../00051_billing_identity_binding_jobs.sql | 59 +++++ docs/backend/operations/observability.md | 5 +- .../phase-9b-authoritative-entitlements.md | 8 + docs/reviews/phase-9b.md | 19 ++ 17 files changed, 810 insertions(+), 103 deletions(-) create mode 100644 apps/api/internal/platform/billingpostgres/identity_binding_integration_test.go create mode 100644 apps/api/migrations/00051_billing_identity_binding_jobs.sql diff --git a/apps/api/cmd/worker/main.go b/apps/api/cmd/worker/main.go index 5e433e23..f18fcf4f 100644 --- a/apps/api/cmd/worker/main.go +++ b/apps/api/cmd/worker/main.go @@ -277,6 +277,7 @@ func run() (runErr error) { // latencies are one user-visible number. families = append(families, jobFamily{"billing_validation", billingService.ProcessNextValidation}, + jobFamily{"billing_identity_binding", billingService.ProcessNextIdentityBinding}, jobFamily{"billing_projection", projectionService.ProcessNextProjection}, // A restore's outcome is only knowable once validation and // projection have moved, so it runs immediately after them: any diff --git a/apps/api/internal/billing/repository.go b/apps/api/internal/billing/repository.go index ae9e5612..c34177cf 100644 --- a/apps/api/internal/billing/repository.go +++ b/apps/api/internal/billing/repository.go @@ -136,6 +136,21 @@ type AssociationCorrelator struct { Digest []byte } +// IdentityBindingJob is the durable, digest-only identity decision produced by +// one fact-bearing validation attempt. Its unique validation-attempt identity +// preserves new evidence from a deduplicated revalidation without repeating +// the provider call or appending another Transaction Fact. +type IdentityBindingJob struct { + ID string + ProjectID string + EnvironmentID string + ValidationAttemptID string + LeaseOwner string + AttemptCount int + MaxAttempts int + Binding FactBinding +} + // ResolutionRecord is the persisted Resolution Snapshot. type ResolutionRecord struct { ID string @@ -244,11 +259,10 @@ type Repository interface { // and no other worker can claim the job underneath it. LeaseValidationJobFor(ctx context.Context, workerID string, input RawInput, now, leaseUntil time.Time) (ValidationJob, error) CompleteAttempt(ctx context.Context, job ValidationJob, outcome AttemptOutcome, now time.Time) error - // ChainRootDigest resolves the root of the purchase chain a fact belongs to - // by walking supersession edges backwards. The seam needs it because the - // Purchase Lineage is keyed on the root, and a fact whose provider handed the - // chain a new token carries a digest that is not it. - ChainRootDigest(ctx context.Context, fact TransactionFact) ([]byte, error) + LeaseIdentityBindingJob(ctx context.Context, workerID string, now, leaseUntil time.Time) (IdentityBindingJob, bool, error) + CompleteIdentityBindingJob(ctx context.Context, job IdentityBindingJob, now time.Time) error + ParkIdentityBindingJob(ctx context.Context, job IdentityBindingJob, reason string, now time.Time) error + RetryIdentityBindingJob(ctx context.Context, job IdentityBindingJob, reason string, availableAt, now time.Time) error // ParkValidationJob returns a job to the queue without consuming an attempt, // for conditions that are expected to resolve without operator action. ParkValidationJob(ctx context.Context, job ValidationJob, reason string, now time.Time) error diff --git a/apps/api/internal/billing/service_worker.go b/apps/api/internal/billing/service_worker.go index 6136e63a..0c9170bb 100644 --- a/apps/api/internal/billing/service_worker.go +++ b/apps/api/internal/billing/service_worker.go @@ -22,6 +22,10 @@ import ( // validationLease bounds how long one worker may hold a validation job. const validationLease = 2 * time.Minute +// identityBindingLease is short because binding performs only PostgreSQL-backed +// identity decisions and no store-provider call. +const identityBindingLease = 60 * time.Second + // ProcessNextValidation leases and runs one validation job. It matches the // (processed, error) contract every other Mosaic job family uses so the worker // loop treats billing exactly like analytics and Experiment scheduling. @@ -61,55 +65,47 @@ func (s *Service) ProcessNextValidation(ctx context.Context, workerID string) (b if err := s.repository.CompleteAttempt(ctx, job, outcome, s.now()); err != nil { return true, safeFailure(err, "billing_attempt_write_failed") } - if err := s.bindFactIdentity(ctx, outcome); err != nil { - // The fact and its lineage are committed; only the identity decision - // failed. The job is still `processed` — re-leasing it would re-run the - // provider call and re-record an attempt for work that succeeded — but - // the error is reported so the worker's failure signal and its metrics - // see it. The lineage is left unassociated, which is exactly what - // `unresolvedLineages` on the projection-health surface counts, and the - // next fact on the same chain retries the decision. - return true, safeFailure(err, "billing_lineage_bind_failed") - } return true, nil } -// bindFactIdentity runs the identity half of the Phase 9A→9B seam. -// -// It is outside CompleteAttempt's transaction on purpose. The structural half — -// the lineage row and its projection instance — is written inside that -// transaction because it is a deterministic function of the fact and must be -// exactly as durable as it. Deciding *who owns* the lineage reads alias -// resolutions and prior evidence and can open an operator conflict, which is -// application logic rather than a write, and holding the fact's transaction open -// across it would put the ledger's hot path behind the identity module. -func (s *Service) bindFactIdentity(ctx context.Context, outcome AttemptOutcome) error { - if s.lineages == nil || outcome.Fact == nil || len(outcome.Fact.PurchaseChainDigest) == 0 { - return nil - } - fact := *outcome.Fact - root, err := s.repository.ChainRootDigest(ctx, fact) +// ProcessNextIdentityBinding runs the durable identity half of the Phase +// 9A→9B seam. Validation has already committed; failures requeue this job and +// never repeat the provider request, validation attempt, or Transaction Fact. +func (s *Service) ProcessNextIdentityBinding(ctx context.Context, workerID string) (bool, error) { + now := s.now() + job, leased, err := s.repository.LeaseIdentityBindingJob(ctx, workerID, now, now.Add(identityBindingLease)) if err != nil { - return err - } - if len(root) == 0 { - root = fact.PurchaseChainDigest - } - acquiredAt := fact.OccurredAt - if fact.PeriodStartAt != nil && fact.PeriodStartAt.Before(acquiredAt) { - acquiredAt = *fact.PeriodStartAt - } - return s.lineages.BindFact(ctx, FactBinding{ - ProjectID: fact.ProjectID, - EnvironmentID: fact.EnvironmentID, - Provider: fact.Provider, - LineageKeyDigest: root, - FactChainDigest: fact.PurchaseChainDigest, - RawInputID: fact.SourceRawInputID, - ReferenceDigests: outcome.ReferenceDigests, - Correlators: outcome.Correlators, - AcquiredAt: acquiredAt, + return false, safeFailure(err, "billing_identity_binding_lease_failed") + } + if !leased { + return false, nil + } + jobtelemetry.Annotate(ctx, jobtelemetry.Identity{ + JobID: job.ID, JobKind: "billing_identity_binding", + ProjectID: job.ProjectID, EnvironmentID: job.EnvironmentID, ResourceID: job.Binding.RawInputID, }) + if s.lineages == nil { + return true, s.repository.ParkIdentityBindingJob(ctx, job, "identity_binding_unavailable", s.now()) + } + if !s.billingEnabled(ctx, job.ProjectID) { + return true, s.repository.ParkIdentityBindingJob(ctx, job, "billing_disabled", s.now()) + } + + ctx, span := s.tracer.Start(ctx, "billing.identity.bind") + defer span.End() + if err := s.lineages.BindFact(ctx, job.Binding); err != nil { + completed := s.now() + delay := time.Duration(1<= max_attempts + ORDER BY lease_expires_at, id + FOR UPDATE SKIP LOCKED LIMIT 100 + ) + UPDATE billing_identity_binding_jobs jobs + SET status='failed', lease_owner=NULL, lease_expires_at=NULL, + last_error_code=COALESCE(last_error_code,'identity_binding_lease_expired_exhausted'), + updated_at=$1 + FROM exhausted WHERE jobs.id=exhausted.id`, now); err != nil { + return billing.IdentityBindingJob{}, false, fmt.Errorf("terminalize exhausted identity-binding leases: %w", err) + } + + var job billing.IdentityBindingJob + var correlators []byte + err = tx.QueryRow(ctx, + `SELECT id, project_id, environment_id, validation_attempt_id, raw_input_id, provider, + lineage_key_digest, fact_chain_digest, reference_digests, correlators, + acquired_at, attempt_count, max_attempts + FROM billing_identity_binding_jobs candidate + WHERE (candidate.status='queued' OR (candidate.status='leased' AND candidate.lease_expires_at <= $1)) + AND candidate.available_at <= $1 AND candidate.attempt_count < candidate.max_attempts + AND NOT EXISTS ( + SELECT 1 FROM billing_identity_binding_jobs leased + WHERE leased.status='leased' AND leased.id <> candidate.id + AND leased.environment_id=candidate.environment_id AND leased.provider=candidate.provider + AND leased.lineage_key_digest=candidate.lineage_key_digest + ) + ORDER BY candidate.available_at, candidate.created_at, candidate.id + FOR UPDATE SKIP LOCKED LIMIT 1`, now).Scan( + &job.ID, &job.ProjectID, &job.EnvironmentID, &job.ValidationAttemptID, + &job.Binding.RawInputID, &job.Binding.Provider, &job.Binding.LineageKeyDigest, + &job.Binding.FactChainDigest, &job.Binding.ReferenceDigests, &correlators, + &job.Binding.AcquiredAt, &job.AttemptCount, &job.MaxAttempts) + if errors.Is(err, pgx.ErrNoRows) { + return billing.IdentityBindingJob{}, false, nil + } + if err != nil { + return billing.IdentityBindingJob{}, false, fmt.Errorf("select identity-binding job: %w", err) + } + if err := json.Unmarshal(correlators, &job.Binding.Correlators); err != nil { + return billing.IdentityBindingJob{}, false, fmt.Errorf("decode identity-binding correlators: %w", err) + } + job.Binding.ProjectID = job.ProjectID + job.Binding.EnvironmentID = job.EnvironmentID + job.LeaseOwner = workerID + if _, err := tx.Exec(ctx, + `UPDATE billing_identity_binding_jobs + SET status='leased', lease_owner=$2, lease_expires_at=$3, + attempt_count=attempt_count+1, updated_at=$4 + WHERE id=$1`, job.ID, workerID, leaseUntil, now); err != nil { + return billing.IdentityBindingJob{}, false, fmt.Errorf("lease identity-binding job: %w", err) + } + job.AttemptCount++ + if err := tx.Commit(ctx); err != nil { + return billing.IdentityBindingJob{}, false, fmt.Errorf("commit identity-binding lease: %w", err) + } + return job, true, nil +} + +func (r *Repository) CompleteIdentityBindingJob(ctx context.Context, job billing.IdentityBindingJob, now time.Time) error { + tag, err := r.pool.Exec(ctx, + `UPDATE billing_identity_binding_jobs + SET status='completed', lease_owner=NULL, lease_expires_at=NULL, + last_error_code=NULL, available_at=$2, updated_at=$2 + WHERE id=$1 AND status='leased' AND lease_owner=$3`, job.ID, now, job.LeaseOwner) + if err != nil { + return fmt.Errorf("complete identity-binding job: %w", err) + } + if tag.RowsAffected() != 1 { + return fmt.Errorf("complete identity-binding job: lease lost") + } + return nil +} + +func (r *Repository) ParkIdentityBindingJob(ctx context.Context, job billing.IdentityBindingJob, reason string, now time.Time) error { + tag, err := r.pool.Exec(ctx, + `UPDATE billing_identity_binding_jobs + SET status='queued', attempt_count=GREATEST(attempt_count-1,0), available_at=$2, + lease_owner=NULL, lease_expires_at=NULL, last_error_code=NULLIF($3,''), updated_at=$4 + WHERE id=$1 AND status='leased' AND lease_owner=$5`, + job.ID, now.Add(parkedRetryDelay), reason, now, job.LeaseOwner) + if err != nil { + return fmt.Errorf("park identity-binding job: %w", err) + } + if tag.RowsAffected() != 1 { + return fmt.Errorf("park identity-binding job: lease lost") + } + return nil +} + +func (r *Repository) RetryIdentityBindingJob(ctx context.Context, job billing.IdentityBindingJob, reason string, availableAt, now time.Time) error { + tag, err := r.pool.Exec(ctx, + `UPDATE billing_identity_binding_jobs + SET status=CASE WHEN attempt_count >= max_attempts THEN 'failed' ELSE 'queued' END, + available_at=$2, lease_owner=NULL, lease_expires_at=NULL, + last_error_code=NULLIF($3,''), updated_at=$4 + WHERE id=$1 AND status='leased' AND lease_owner=$5`, job.ID, availableAt, reason, now, job.LeaseOwner) + if err != nil { + return fmt.Errorf("retry identity-binding job: %w", err) + } + if tag.RowsAffected() != 1 { + return fmt.Errorf("retry identity-binding job: lease lost") + } + return nil +} + // parkedRetryDelay keeps a parked job from spinning the worker loop. const parkedRetryDelay = 5 * time.Minute @@ -310,8 +434,9 @@ func (r *Repository) CompleteAttempt(ctx context.Context, job billing.Validation // purchase (defect D-1). Both writes are deterministic functions of the // fact's own chain digest, decide nothing, and must be exactly as durable as // the fact, because the trigger they enable is written here too. + var lineage materializedLineage if factRecorded && outcome.Fact != nil { - lineage, err := materializeLineage(ctx, tx, *outcome.Fact, now) + lineage, err = materializeLineage(ctx, tx, *outcome.Fact, now) if err != nil { return err } @@ -320,6 +445,22 @@ func (r *Repository) CompleteAttempt(ctx context.Context, job billing.Validation } } + // Every fact-producing attempt gets its own durable identity job, including + // a revalidation whose fact was deduplicated. The latter can carry new + // correlator or submission evidence even though fact identity is unchanged. + if outcome.Fact != nil && len(outcome.Fact.PurchaseChainDigest) > 0 { + if len(lineage.RootDigest) == 0 { + lineage.RootDigest, err = chainRootDigest(ctx, tx, *outcome.Fact) + if err != nil { + return err + } + } + if err := enqueueIdentityBinding(ctx, tx, attempt.ID, *outcome.Fact, outcome, + lineage.RootDigest, now); err != nil { + return err + } + } + status := outcome.JobStatus if status == "" { status = "completed" @@ -342,6 +483,40 @@ func (r *Repository) CompleteAttempt(ctx context.Context, job billing.Validation return nil } +func enqueueIdentityBinding(ctx context.Context, tx pgx.Tx, attemptID string, + fact billing.TransactionFact, outcome billing.AttemptOutcome, rootDigest []byte, now time.Time) error { + + correlatorInput := outcome.Correlators + if correlatorInput == nil { + correlatorInput = []billing.AssociationCorrelator{} + } + referenceDigests := outcome.ReferenceDigests + if referenceDigests == nil { + referenceDigests = [][]byte{} + } + correlators, err := json.Marshal(correlatorInput) + if err != nil { + return fmt.Errorf("encode identity-binding correlators: %w", err) + } + acquiredAt := fact.OccurredAt + if fact.PeriodStartAt != nil && fact.PeriodStartAt.Before(acquiredAt) { + acquiredAt = *fact.PeriodStartAt + } + if _, err := tx.Exec(ctx, + `INSERT INTO billing_identity_binding_jobs( + id, project_id, environment_id, validation_attempt_id, raw_input_id, provider, + lineage_key_digest, fact_chain_digest, reference_digests, correlators, acquired_at, + status, attempt_count, max_attempts, available_at, created_at, updated_at) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,'queued',0,8,$12,$12,$12) + ON CONFLICT (validation_attempt_id) DO NOTHING`, + "bib_"+hashID(attemptID, "identity_binding", ""), fact.ProjectID, fact.EnvironmentID, + attemptID, fact.SourceRawInputID, fact.Provider, rootDigest, fact.PurchaseChainDigest, + referenceDigests, correlators, acquiredAt, now); err != nil { + return fmt.Errorf("enqueue identity binding: %w", err) + } + return nil +} + // materializeLineage creates the Purchase Lineage a newly recorded fact belongs // to, and the projection instance that lineage owns. // @@ -691,24 +866,3 @@ func (r *Repository) ExpireRawInputBodies(ctx context.Context, now time.Time, li } return tag.RowsAffected(), nil } - -// ChainRootDigest resolves the root of the purchase chain a fact belongs to. -// -// It is the same walk the fact-commit transaction performs, exposed as a read so -// the seam can name the lineage that transaction created without the transaction -// having to hand its identifiers back through the CompleteAttempt contract. -func (r *Repository) ChainRootDigest(ctx context.Context, fact billing.TransactionFact) ([]byte, error) { - if len(fact.PurchaseChainDigest) == 0 { - return nil, nil - } - tx, err := r.pool.Begin(ctx) - if err != nil { - return nil, fmt.Errorf("begin chain root read: %w", err) - } - defer func() { _ = tx.Rollback(ctx) }() - root, err := chainRootDigest(ctx, tx, fact) - if err != nil { - return nil, err - } - return root, tx.Commit(ctx) -} diff --git a/apps/api/internal/platform/billingpostgres/queue_metrics.go b/apps/api/internal/platform/billingpostgres/queue_metrics.go index bfa833c4..dfc4c5ff 100644 --- a/apps/api/internal/platform/billingpostgres/queue_metrics.go +++ b/apps/api/internal/platform/billingpostgres/queue_metrics.go @@ -12,12 +12,13 @@ import ( const queueMetricTimeout = 5 * time.Second -// billingQueues maps a metric queue label to its backing table. All three share +// billingQueues maps a metric queue label to its backing table. All four share // the status/available_at/created_at job shape the rest of Mosaic uses. var billingQueues = map[string]string{ - "validation": "billing_validation_jobs", - "reconciliation": "billing_reconciliation_runs", - "replay": "billing_replay_jobs", + "validation": "billing_validation_jobs", + "identity_binding": "billing_identity_binding_jobs", + "reconciliation": "billing_reconciliation_runs", + "replay": "billing_replay_jobs", } // RegisterQueueMetrics publishes backlog depth, oldest-job age, and dead-letter diff --git a/apps/api/internal/platform/billingwebhookpostgres/delivery_integration_test.go b/apps/api/internal/platform/billingwebhookpostgres/delivery_integration_test.go index 786ff8a1..8fa84989 100644 --- a/apps/api/internal/platform/billingwebhookpostgres/delivery_integration_test.go +++ b/apps/api/internal/platform/billingwebhookpostgres/delivery_integration_test.go @@ -4,16 +4,23 @@ import ( "bytes" "context" "database/sql" + "encoding/json" + "net/http" + "net/http/httptest" "os" + "strings" "sync" "testing" "time" + "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5/pgxpool" _ "github.com/jackc/pgx/v5/stdlib" "github.com/pressly/goose/v3" "github.com/Mujhtech/mosaic/apps/api/internal/billingwebhook" + "github.com/Mujhtech/mosaic/apps/api/internal/platform/authn" + billingwebhookhttp "github.com/Mujhtech/mosaic/apps/api/internal/transport/billingwebhook" "github.com/Mujhtech/mosaic/apps/api/migrations" ) @@ -65,6 +72,9 @@ type fixture struct { destinationID string eventID string payload string + ownerActor string + adminActor string + memberActor string } func seedWebhookFixture(t *testing.T, ctx context.Context, pool *pgxpool.Pool, suffix string) fixture { @@ -76,6 +86,9 @@ func seedWebhookFixture(t *testing.T, ctx context.Context, pool *pgxpool.Pool, s destinationID: "whd_" + suffix, eventID: "whe_" + suffix, payload: `{"eventId": "whe_` + suffix + `", "eventType": "customer.entitlements.changed"}`, + ownerActor: "actor_whd_owner_" + suffix, + adminActor: "actor_whd_admin_" + suffix, + memberActor: "actor_whd_member_" + suffix, } organizationID := "org_whd_" + suffix customerID := "bcus_whd_" + suffix @@ -91,6 +104,9 @@ func seedWebhookFixture(t *testing.T, ctx context.Context, pool *pgxpool.Pool, s {`INSERT INTO projects(id,organization_id,key,name,status,created_at,updated_at) VALUES ($1,$2,$3,'Webhook','active',$4,$4) ON CONFLICT (id) DO NOTHING`, []any{f.projectID, organizationID, "webhook-" + suffix, now}}, + {`INSERT INTO organization_members(organization_id,actor_id,role,created_at,updated_at) + VALUES ($1,$2,'owner',$5,$5),($1,$3,'admin',$5,$5),($1,$4,'member',$5,$5)`, + []any{organizationID, f.ownerActor, f.adminActor, f.memberActor, now}}, {`INSERT INTO environments(id,project_id,key,name,mode,created_at,updated_at) VALUES ($1,$2,'production','Production','production',$3,$3) ON CONFLICT (id) DO NOTHING`, []any{f.environmentID, f.projectID, now}}, @@ -153,6 +169,7 @@ func cleanupWebhookFixture(ctx context.Context, pool *pgxpool.Pool, f fixture, o } { _, _ = pool.Exec(ctx, statement, f.projectID) } + _, _ = pool.Exec(ctx, `DELETE FROM organization_members WHERE organization_id=$1`, organizationID) _, _ = pool.Exec(ctx, `DELETE FROM organizations WHERE id=$1`, organizationID) for _, statement := range []string{ `ALTER TABLE webhook_events ENABLE TRIGGER webhook_events_append_only`, @@ -163,6 +180,143 @@ func cleanupWebhookFixture(ctx context.Context, pool *pgxpool.Pool, f fixture, o } } +type authorizationSurface struct { + handler http.Handler + actorID *string +} + +func newAuthorizationSurface(pool *pgxpool.Pool) *authorizationSurface { + service := billingwebhook.NewService(New(pool), nil, nil) + actorID := "" + router := chi.NewRouter() + router.Use(authn.Middleware(authn.ResolverFunc(func(*http.Request) (authn.Principal, error) { + if actorID == "" { + return authn.Principal{}, authn.ErrUnauthenticated + } + return authn.Principal{ActorID: actorID, Method: "browser_session"}, nil + }))) + router.Route("/v1/projects/{projectId}", func(project chi.Router) { + billingwebhookhttp.RegisterProjectRoutes(project, service) + project.Route("/environments/{environmentId}/billing", func(environment chi.Router) { + billingwebhookhttp.RegisterEnvironmentRoutes(environment, service) + }) + }) + return &authorizationSurface{handler: router, actorID: &actorID} +} + +func (s *authorizationSurface) as(actorID string) *authorizationSurface { + *s.actorID = actorID + return s +} + +func (s *authorizationSurface) do(t *testing.T, method, path, body string) (int, map[string]any) { + t.Helper() + request := httptest.NewRequest(method, path, strings.NewReader(body)) + if body != "" { + request.Header.Set("Content-Type", "application/json") + } + recorder := httptest.NewRecorder() + s.handler.ServeHTTP(recorder, request) + payload := map[string]any{} + if recorder.Body.Len() > 0 { + if err := json.Unmarshal(recorder.Body.Bytes(), &payload); err != nil { + t.Fatalf("decode %s %s response: %v (%s)", method, path, err, recorder.Body.String()) + } + } + return recorder.Code, payload +} + +// Billing webhook destinations and delivery history carry authoritative +// entitlement state. This integration test proves the shipped HTTP surface +// reaches the PostgreSQL membership boundary on every management family: an +// authenticated member cannot read or mutate them, a missing session is 401, +// and owner/admin sessions still reach the data. +func TestManagementSurfaceRequiresOwnerOrAdmin(t *testing.T) { + pool, ctx := testPool(t) + f := seedWebhookFixture(t, ctx, pool, "authz") + repository := New(pool) + if _, err := repository.FanOut(ctx, time.Now().UTC(), 10); err != nil { + t.Fatal(err) + } + var deliveryID string + if err := pool.QueryRow(ctx, + `SELECT id FROM webhook_deliveries WHERE project_id=$1 AND webhook_destination_id=$2`, + f.projectID, f.destinationID).Scan(&deliveryID); err != nil { + t.Fatal(err) + } + + surface := newAuthorizationSurface(pool) + base := "/v1/projects/" + f.projectID + environment := base + "/environments/" + f.environmentID + "/billing" + routes := []struct{ method, path, body string }{ + {http.MethodGet, environment + "/webhook-destinations/", ""}, + {http.MethodPost, environment + "/webhook-destinations/", `{"url":"https://receiver.example.com/second"}`}, + {http.MethodGet, base + "/billing/webhook-destinations/" + f.destinationID + "/", ""}, + {http.MethodPatch, base + "/billing/webhook-destinations/" + f.destinationID + "/", `{"description":"changed"}`}, + {http.MethodPost, base + "/billing/webhook-destinations/" + f.destinationID + "/status", `{"status":"paused"}`}, + {http.MethodDelete, base + "/billing/webhook-destinations/" + f.destinationID + "/", ""}, + {http.MethodGet, base + "/billing/webhook-destinations/" + f.destinationID + "/secrets", ""}, + {http.MethodPost, base + "/billing/webhook-destinations/" + f.destinationID + "/secrets/rotate", ""}, + {http.MethodPost, base + "/billing/webhook-destinations/" + f.destinationID + "/secrets/secret/retire", ""}, + {http.MethodGet, base + "/billing/webhook-deliveries/", ""}, + {http.MethodGet, base + "/billing/webhook-deliveries/" + deliveryID, ""}, + {http.MethodGet, base + "/billing/webhook-deliveries/" + deliveryID + "/attempts", ""}, + {http.MethodPost, base + "/billing/webhook-deliveries/" + deliveryID + "/replay", ""}, + } + for _, route := range routes { + status, payload := surface.as(f.memberActor).do(t, route.method, route.path, route.body) + if status != http.StatusForbidden { + t.Fatalf("%s %s as member: status %d payload %v, want 403", route.method, route.path, status, payload) + } + status, payload = surface.as("").do(t, route.method, route.path, route.body) + if status != http.StatusUnauthorized { + t.Fatalf("%s %s unauthenticated: status %d payload %v, want 401", route.method, route.path, status, payload) + } + } + + if status, _ := surface.as(f.ownerActor).do(t, http.MethodGet, + environment+"/webhook-destinations/", ""); status != http.StatusOK { + t.Fatalf("owner destination list: status %d, want 200", status) + } + if status, _ := surface.as(f.adminActor).do(t, http.MethodGet, + base+"/billing/webhook-deliveries/"+deliveryID, ""); status != http.StatusOK { + t.Fatalf("admin delivery read: status %d, want 200", status) + } +} + +// Cross-tenant failures are deliberately 404, not 403, and an Environment +// cannot be paired with a different Project. These checks catch both the +// existence-oracle regression and the environment-filter bug where a foreign +// Environment previously produced a misleading successful empty list. +func TestManagementSurfaceDoesNotCrossProjectOrEnvironment(t *testing.T) { + pool, ctx := testPool(t) + first := seedWebhookFixture(t, ctx, pool, "scope_a") + second := seedWebhookFixture(t, ctx, pool, "scope_b") + repository := New(pool) + if _, err := repository.FanOut(ctx, time.Now().UTC(), 20); err != nil { + t.Fatal(err) + } + var secondDeliveryID string + if err := pool.QueryRow(ctx, `SELECT id FROM webhook_deliveries WHERE project_id=$1`, second.projectID). + Scan(&secondDeliveryID); err != nil { + t.Fatal(err) + } + + surface := newAuthorizationSurface(pool).as(first.ownerActor) + checks := []string{ + "/v1/projects/" + second.projectID + "/billing/webhook-destinations/" + second.destinationID + "/", + "/v1/projects/" + second.projectID + "/billing/webhook-deliveries/" + secondDeliveryID, + "/v1/projects/" + first.projectID + "/environments/" + second.environmentID + "/billing/webhook-destinations/", + "/v1/projects/" + first.projectID + "/billing/webhook-deliveries/?environmentId=" + second.environmentID, + } + for _, path := range checks { + status, payload := surface.do(t, http.MethodGet, path, "") + if status != http.StatusNotFound { + t.Fatalf("cross-scope GET %s: status %d payload %v, want 404", path, status, payload) + } + } +} + func countRows(t *testing.T, ctx context.Context, pool *pgxpool.Pool, query string, args ...any) int { t.Helper() var count int diff --git a/apps/api/internal/platform/billingwebhookpostgres/repository.go b/apps/api/internal/platform/billingwebhookpostgres/repository.go index 908cc5e4..944d5df7 100644 --- a/apps/api/internal/platform/billingwebhookpostgres/repository.go +++ b/apps/api/internal/platform/billingwebhookpostgres/repository.go @@ -32,6 +32,51 @@ func New(pool *pgxpool.Pool) *Repository { return &Repository{pool: pool} } var _ billingwebhook.Repository = (*Repository)(nil) +// AuthorizeProject is the single operator authorization boundary for billing +// webhook management. Billing webhooks carry authoritative entitlement state, +// so they use the same owner/admin role bar as the billing ledger and customer +// operator surfaces. +func (r *Repository) AuthorizeProject(ctx context.Context, actor billingwebhook.Actor, projectID string) error { + actorID := strings.TrimSpace(actor.ID) + if actorID == "" || strings.TrimSpace(projectID) == "" { + return billingwebhook.ErrUnauthenticated + } + var role string + err := r.pool.QueryRow(ctx, + `SELECT m.role FROM projects p + JOIN organization_members m ON m.organization_id = p.organization_id + WHERE p.id = $1 AND m.actor_id = $2`, projectID, actorID).Scan(&role) + if errors.Is(err, pgx.ErrNoRows) { + return billingwebhook.ErrNotFound + } + if err != nil { + return fmt.Errorf("resolve billing webhook operator role: %w", err) + } + switch role { + case "owner", "admin": + return nil + default: + return billingwebhook.ErrForbidden + } +} + +func (r *Repository) AuthorizeEnvironment(ctx context.Context, actor billingwebhook.Actor, projectID, environmentID string) error { + if err := r.AuthorizeProject(ctx, actor, projectID); err != nil { + return err + } + var exists bool + err := r.pool.QueryRow(ctx, + `SELECT true FROM environments WHERE id = $1 AND project_id = $2`, environmentID, projectID). + Scan(&exists) + if errors.Is(err, pgx.ErrNoRows) { + return billingwebhook.ErrNotFound + } + if err != nil { + return fmt.Errorf("resolve billing webhook environment: %w", err) + } + return nil +} + // destinationColumns is the single projection every destination read uses, so // a column added to one read cannot be forgotten by another. const destinationColumns = `id, project_id, environment_id, url, status, event_types, description, diff --git a/apps/api/internal/transport/billingwebhook/handler.go b/apps/api/internal/transport/billingwebhook/handler.go index 52c3435f..349a7e94 100644 --- a/apps/api/internal/transport/billingwebhook/handler.go +++ b/apps/api/internal/transport/billingwebhook/handler.go @@ -383,6 +383,8 @@ func writeError(w http.ResponseWriter, r *http.Request, err error) { switch { case errors.Is(err, billingwebhook.ErrUnauthenticated): status, code, message = http.StatusUnauthorized, "unauthenticated", "Authentication is required." + case errors.Is(err, billingwebhook.ErrForbidden): + status, code, message = http.StatusForbidden, "forbidden", "You do not have permission to perform this action." case errors.Is(err, billingwebhook.ErrNotFound): status, code, message = http.StatusNotFound, "not_found", "The requested resource was not found." case errors.Is(err, billingwebhook.ErrDestinationRefused): diff --git a/apps/api/migrations/00051_billing_identity_binding_jobs.sql b/apps/api/migrations/00051_billing_identity_binding_jobs.sql new file mode 100644 index 00000000..d42db98e --- /dev/null +++ b/apps/api/migrations/00051_billing_identity_binding_jobs.sql @@ -0,0 +1,59 @@ +-- Phase 9B: make the identity half of the Phase 9A -> 9B fact seam durable. +-- +-- Validation used to call the identity binder only after the fact transaction +-- committed. A process exit in that interval permanently lost the association +-- work. This queue is written in the fact/attempt transaction and contains only +-- digests and non-secret identifiers, so retry never needs the retained raw +-- provider body and never repeats provider validation. + +-- +goose Up +CREATE TABLE billing_identity_binding_jobs ( + id text PRIMARY KEY, + project_id text NOT NULL, + environment_id text NOT NULL, + validation_attempt_id text NOT NULL UNIQUE, + raw_input_id text NOT NULL, + provider text NOT NULL CHECK (provider IN ('app_store', 'google_play')), + lineage_key_digest bytea NOT NULL CHECK (octet_length(lineage_key_digest) = 32), + fact_chain_digest bytea NOT NULL CHECK (octet_length(fact_chain_digest) = 32), + reference_digests bytea[] NOT NULL DEFAULT '{}', + correlators jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(correlators) = 'array'), + acquired_at timestamptz NOT NULL, + status text NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'leased', 'completed', 'failed')), + attempt_count integer NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), + max_attempts integer NOT NULL DEFAULT 8 CHECK (max_attempts > 0), + available_at timestamptz NOT NULL, + lease_owner text, + lease_expires_at timestamptz, + last_error_code text, + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL, + CONSTRAINT billing_identity_binding_jobs_environment_fk + FOREIGN KEY (environment_id, project_id) REFERENCES environments(id, project_id), + CONSTRAINT billing_identity_binding_jobs_attempt_fk + FOREIGN KEY (validation_attempt_id, project_id) + REFERENCES billing_validation_attempts(id, project_id) ON DELETE CASCADE, + CONSTRAINT billing_identity_binding_jobs_raw_input_fk + FOREIGN KEY (raw_input_id, project_id) REFERENCES billing_raw_inputs(id, project_id), + CONSTRAINT billing_identity_binding_jobs_lease_shape CHECK ( + (status = 'leased' AND lease_owner IS NOT NULL AND lease_expires_at IS NOT NULL) + OR (status <> 'leased' AND lease_owner IS NULL AND lease_expires_at IS NULL) + ), + CONSTRAINT billing_identity_binding_jobs_reference_digests_check CHECK ( + array_position(reference_digests, NULL) IS NULL + ) +); + +CREATE INDEX billing_identity_binding_jobs_claim_idx + ON billing_identity_binding_jobs (available_at, created_at, id) + WHERE status IN ('queued', 'leased'); + +-- The partial unique index is the cross-worker serialization boundary. Two +-- evidence-bearing attempts for one lineage may be queued, but only one may be +-- inside the identity decision at a time. +CREATE UNIQUE INDEX billing_identity_binding_jobs_lineage_lease_idx + ON billing_identity_binding_jobs (environment_id, provider, lineage_key_digest) + WHERE status = 'leased'; + +-- +goose Down +DROP TABLE billing_identity_binding_jobs; diff --git a/docs/backend/operations/observability.md b/docs/backend/operations/observability.md index db119061..f48f79cc 100644 --- a/docs/backend/operations/observability.md +++ b/docs/backend/operations/observability.md @@ -102,8 +102,9 @@ rate spike can be attributed to a specific cause. Families: `analytics` (queues `aggregate`, `export`, `deletion`, `retention`), `experiment` (queue `schedule`), and — when `MOSAIC_BILLING_ENABLED` is set — -`billing` (queues `validation`, `reconciliation`, `replay`). Each executed job -logs one line with `job_family`, `worker_id`, `duration`, and `failed`. +`billing` (queues `validation`, `identity_binding`, `reconciliation`, `replay`). +Each executed job logs one line with `job_family`, `worker_id`, `duration`, and +`failed`. The worker additionally schedules `billing_rtdn` and `billing_retention`, which are polling loops rather than queues and so publish no depth. diff --git a/docs/backend/phase-9b-authoritative-entitlements.md b/docs/backend/phase-9b-authoritative-entitlements.md index c4d11b38..ea021c13 100644 --- a/docs/backend/phase-9b-authoritative-entitlements.md +++ b/docs/backend/phase-9b-authoritative-entitlements.md @@ -446,6 +446,14 @@ An association that establishes an owner enqueues a **customer-scoped** projecti already queued for that lineage is lineage-scoped — it was queued when the lineage had no customer — and a lineage-scoped command deliberately mints no customer snapshot. +The fact-to-identity seam is durable as of migration `00051`. Completing any fact-producing +Validation Attempt atomically enqueues a digest-only identity-binding job, including when a +revalidation deduplicates to an existing Transaction Fact but carries new association evidence. +The worker processes validation, identity binding, and projection as separate bounded steps. +Binding failures retry without repeating provider validation or rewriting the fact. Jobs are +serialized per Environment, provider, and lineage digest; expired leases are reclaimed, and an +expired final attempt is marked failed so it cannot block later evidence for the same lineage. + Pointer moves and conflict freezes are retry-safe across queue failures. Before a pointer changes, the service persists prior-lineage evidence naming the old customer. If enqueueing either affected aggregate fails, an identical association request reads that evidence and re-enqueues both the old diff --git a/docs/reviews/phase-9b.md b/docs/reviews/phase-9b.md index a77a1ad2..190c6b6f 100644 --- a/docs/reviews/phase-9b.md +++ b/docs/reviews/phase-9b.md @@ -171,3 +171,22 @@ Tracked follow-ups: Final Stage 5 product, UX, protocol, and quality reviews were completed. The targeted quality rereview found no blocking defect and accepted closure with the live-provider and integration follow-ups above. + +## Post-Acceptance Remediation — 2026-07-29 + +The Phase 9C entry inspection found and remediation closed two earlier-phase defects before any +Phase 9C implementation began: + +- Billing webhook management now enforces PostgreSQL-backed owner/admin membership for every + destination, signing-secret, delivery-history, and replay operation. Project and Environment + mismatches return a non-enumerating not-found response; authenticated members without the + required role are forbidden. Focused HTTP/PostgreSQL tests cover all route families. +- Migration `00051` makes fact-to-identity binding durable. A fact-producing Validation Attempt + and its digest-only binding job commit atomically; binding retries never repeat provider + validation or create another Transaction Fact. Attempt-keyed jobs preserve new evidence on a + deduplicated revalidation, serialize work per lineage, reclaim expired leases, and terminalize + exhausted leases without blocking later evidence. + +The fresh-database migration apply/down/up drill, migration preflight, full serial Go suite, +`go vet ./...`, and independent quality/security rereview passed. No Phase 9C feature was included +in this remediation. The live Apple/Google verification follow-up remains open. From 8e4c4033820092ce3db68aa6ae5e25f3d61bd14b Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Thu, 30 Jul 2026 07:42:03 +0100 Subject: [PATCH 02/25] feat(api): export telemetry over grpc or http with auth and logs OTLP export was hard-wired to HTTP with no way to authenticate to a collector, and logs never left stdout. Add OTEL_EXPORTER_OTLP_PROTOCOL to select http/protobuf or grpc for every signal, OTEL_EXPORTER_OTLP_HEADERS for collector credentials, and MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY for a private CA or self-signed certificate. Header values are secrets: they are never logged and never echoed in a validation problem. Skip-verify applies only to an https endpoint, since attaching TLS credentials to a plaintext gRPC endpoint would silently upgrade a connection the collector is not serving. Outside development and test, a plaintext or unverified collector connection is refused at startup unless MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE acknowledges it, matching the existing database and object-store guards. Logs now go to stdout and the collector at once, so they are readable beside the traces they belong to. The bridge sits at the zerolog writer rather than a hook so exported records keep every structured field. MOSAIC_OTEL_LOGS_ENABLED=false drops the export half without touching stdout, traces, or metrics. Telemetry keeps a local-only logger for its own SDK errors so an export failure cannot feed the failing exporter. One generic helper applies the endpoint, header, and TLS policy to all three exporter packages, which otherwise expose the same options under unrelated types and drift apart. --- .env.example | 17 ++ apps/api/cmd/api/main.go | 36 ++- apps/api/cmd/worker/main.go | 36 ++- apps/api/go.mod | 8 +- apps/api/go.sum | 14 + apps/api/internal/platform/config/config.go | 87 +++++- .../internal/platform/config/config_test.go | 198 +++++++++++++- apps/api/internal/platform/logging/logging.go | 26 ++ apps/api/internal/platform/logging/otlp.go | 150 ++++++++++ .../internal/platform/logging/otlp_test.go | 193 +++++++++++++ .../internal/platform/telemetry/telemetry.go | 258 +++++++++++++++++- .../platform/telemetry/telemetry_test.go | 196 +++++++++++++ compose.yaml | 10 + docs/backend/api-foundation.md | 7 +- docs/backend/operations/observability.md | 70 ++++- docs/support.md | 2 +- 16 files changed, 1269 insertions(+), 39 deletions(-) create mode 100644 apps/api/internal/platform/logging/otlp.go create mode 100644 apps/api/internal/platform/logging/otlp_test.go create mode 100644 apps/api/internal/platform/telemetry/telemetry_test.go diff --git a/.env.example b/.env.example index a8ff28bd..e6b2bb34 100644 --- a/.env.example +++ b/.env.example @@ -151,6 +151,23 @@ OTEL_SERVICE_NAME=mosaic-api # Empty means record in-process without exporting. Export failure never stops # Mosaic. OTEL_EXPORTER_OTLP_ENDPOINT= +# Transport for the endpoint above: http/protobuf (default, usually port 4318) +# or grpc (usually port 4317). Ignored when no endpoint is set. +OTEL_EXPORTER_OTLP_PROTOCOL= +# Export headers as key1=value1,key2=value2 with percent-encoded values. This is +# where a hosted collector's token goes, so it is a secret: +# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer%20token +OTEL_EXPORTER_OTLP_HEADERS= +# Skip collector certificate verification. https:// endpoints only, for a +# private CA or self-signed certificate. +MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY=false +# Acknowledge a plaintext or unverified collector connection outside development +# and test. Without it, those are rejected at startup. +MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE=false +# Ship log records to the collector alongside traces and metrics. Logs always +# keep going to stdout as well; set false when log volume is the cost that +# matters. Ignored when no endpoint is set. +MOSAIC_OTEL_LOGS_ENABLED=true # ============================================================================= # Object storage (S3-compatible) diff --git a/apps/api/cmd/api/main.go b/apps/api/cmd/api/main.go index 33490064..4f401191 100644 --- a/apps/api/cmd/api/main.go +++ b/apps/api/cmd/api/main.go @@ -119,11 +119,16 @@ func run() (runErr error) { return fmt.Errorf("configure logging: %w", err) } build := buildinfo.Current() - logger = logger.With(). - Str("service", cfg.Telemetry.ServiceName). - Str("environment", cfg.Environment). - Str("version", build.Version). - Logger() + // Applied to whichever logger ends up in use, so the local stream and the + // exported records carry the same service identity. + withServiceContext := func(base zerolog.Logger) zerolog.Logger { + return base.With(). + Str("service", cfg.Telemetry.ServiceName). + Str("environment", cfg.Environment). + Str("version", build.Version). + Logger() + } + logger = withServiceContext(logger) runContext, stop := signal.NotifyContext( context.Background(), @@ -136,11 +141,30 @@ func run() (runErr error) { ServiceName: cfg.Telemetry.ServiceName, Environment: cfg.Environment, OTLPEndpoint: cfg.Telemetry.OTLPEndpoint, - Logger: logger, + OTLPProtocol: cfg.Telemetry.OTLPProtocol, + OTLPHeaders: cfg.Telemetry.OTLPHeaders, + // Startup validation has already refused an unverified collector in a + // production-like environment unless it was explicitly acknowledged. + OTLPTLSSkipVerify: cfg.Telemetry.TLSSkipVerify, + DisableLogExport: !cfg.Telemetry.ExportLogs(), + // Telemetry keeps the local-only logger: routing its own export failures + // through the exporting logger would feed the failing exporter. + Logger: logger, }) if err != nil { return fmt.Errorf("configure telemetry: %w", err) } + if cfg.Telemetry.ExportLogs() { + // Swapped in only once the logger provider exists, so every record this + // logger writes locally also reaches the collector. + exportingLogger, err := logging.NewExporting( + cfg.Log.Level, cfg.Log.Format, os.Stdout, cfg.Telemetry.ServiceName, + ) + if err != nil { + return fmt.Errorf("configure log export: %w", err) + } + logger = withServiceContext(exportingLogger) + } defer func() { // Telemetry flush has its own budget so a slow collector cannot consume // the HTTP drain budget or delay closing the database pool. diff --git a/apps/api/cmd/worker/main.go b/apps/api/cmd/worker/main.go index f18fcf4f..68b6985b 100644 --- a/apps/api/cmd/worker/main.go +++ b/apps/api/cmd/worker/main.go @@ -80,22 +80,46 @@ func run() (runErr error) { return fmt.Errorf("configure logging: %w", err) } build := buildinfo.Current() - logger = logger.With(). - Str("service", cfg.Telemetry.ServiceName). - Str("environment", cfg.Environment). - Str("version", build.Version). - Logger() + // Applied to whichever logger ends up in use, so the local stream and the + // exported records carry the same service identity. + withServiceContext := func(base zerolog.Logger) zerolog.Logger { + return base.With(). + Str("service", cfg.Telemetry.ServiceName). + Str("environment", cfg.Environment). + Str("version", build.Version). + Logger() + } + logger = withServiceContext(logger) runContext, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() shutdownTelemetry, err := telemetry.New(runContext, telemetry.Config{ ServiceName: cfg.Telemetry.ServiceName, Environment: cfg.Environment, OTLPEndpoint: cfg.Telemetry.OTLPEndpoint, - Logger: logger, + OTLPProtocol: cfg.Telemetry.OTLPProtocol, + OTLPHeaders: cfg.Telemetry.OTLPHeaders, + // Startup validation has already refused an unverified collector in a + // production-like environment unless it was explicitly acknowledged. + OTLPTLSSkipVerify: cfg.Telemetry.TLSSkipVerify, + DisableLogExport: !cfg.Telemetry.ExportLogs(), + // Telemetry keeps the local-only logger: routing its own export failures + // through the exporting logger would feed the failing exporter. + Logger: logger, }) if err != nil { return fmt.Errorf("configure telemetry: %w", err) } + if cfg.Telemetry.ExportLogs() { + // Swapped in only once the logger provider exists, so every record this + // logger writes locally also reaches the collector. + exportingLogger, err := logging.NewExporting( + cfg.Log.Level, cfg.Log.Format, os.Stdout, cfg.Telemetry.ServiceName, + ) + if err != nil { + return fmt.Errorf("configure log export: %w", err) + } + logger = withServiceContext(exportingLogger) + } defer func() { shutdownContext, cancel := context.WithTimeout(context.Background(), cfg.HTTP.TelemetryShutdownTimeout) defer cancel() diff --git a/apps/api/go.mod b/apps/api/go.mod index c2dcc8f3..e6259e44 100644 --- a/apps/api/go.mod +++ b/apps/api/go.mod @@ -20,13 +20,20 @@ require ( github.com/rs/zerolog v1.34.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 go.opentelemetry.io/otel v1.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 + go.opentelemetry.io/otel/log v0.20.0 go.opentelemetry.io/otel/metric v1.44.0 go.opentelemetry.io/otel/sdk v1.44.0 + go.opentelemetry.io/otel/sdk/log v0.20.0 go.opentelemetry.io/otel/sdk/metric v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 golang.org/x/crypto v0.54.0 + google.golang.org/grpc v1.82.1 ) require ( @@ -66,6 +73,5 @@ require ( golang.org/x/text v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a // indirect - google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/apps/api/go.sum b/apps/api/go.sum index 8714d9af..fc37a32c 100644 --- a/apps/api/go.sum +++ b/apps/api/go.sum @@ -108,18 +108,32 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 h1:rydZ9sxbcFdm/oWrVyfLTjHIygMgv0bEeMd+3B/BvoM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0/go.mod h1:earQ25dooT0Hhspq59DZ8YCC50jWfOlFEeWoxy/P444= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 h1:owlhcJ3QO3X0YTDTCcDZ4V+6aVDkWbNmBoQ5NUp7Oww= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0/go.mod h1:MP4eemTiI9zC8fgg+DYynhYDYf3ba72S376TvP+Ye0Q= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUplec5dp06reu1zaXmOXdvqH398taqrDXqUl99jxSc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s= +go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs= +go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/log v0.20.0 h1:vM3xI7TQgKPiSghe6urZtAkyFY7SodrSpC83CffDFuY= +go.opentelemetry.io/otel/sdk/log v0.20.0/go.mod h1:Knej2nmsTUzN79T2eeXdRsjjPcoxoq2pUyUHz9TFyyU= +go.opentelemetry.io/otel/sdk/log/logtest v0.20.0 h1:OqdRZ1guyzamK3M6LlRsmGqRrjkHWw6WZOKKli5ELpg= +go.opentelemetry.io/otel/sdk/log/logtest v0.20.0/go.mod h1:PuMIlm7zAt7c3z8zfOI5ox4iT1Z87We+PF6YoINux/M= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= diff --git a/apps/api/internal/platform/config/config.go b/apps/api/internal/platform/config/config.go index c0ab5886..559f117b 100644 --- a/apps/api/internal/platform/config/config.go +++ b/apps/api/internal/platform/config/config.go @@ -12,6 +12,7 @@ import ( "github.com/joho/godotenv" "github.com/kelseyhightower/envconfig" + "github.com/Mujhtech/mosaic/apps/api/internal/platform/telemetry" "github.com/Mujhtech/mosaic/apps/api/internal/providercredential" ) @@ -260,6 +261,31 @@ type LogConfig struct { type TelemetryConfig struct { ServiceName string `envconfig:"OTEL_SERVICE_NAME" default:"mosaic-api"` OTLPEndpoint string `envconfig:"OTEL_EXPORTER_OTLP_ENDPOINT"` + // OTLPProtocol selects the OTLP transport. Empty means the telemetry + // package default (http/protobuf); "grpc" switches both the trace and the + // metric exporter to OTLP/gRPC. + OTLPProtocol string `envconfig:"OTEL_EXPORTER_OTLP_PROTOCOL"` + // OTLPHeaders holds export headers as "key1=value1,key2=value2". Collector + // authentication lives here, so it is a secret: it is never logged and + // never echoed in a validation problem. + OTLPHeaders string `envconfig:"OTEL_EXPORTER_OTLP_HEADERS"` + // TLSSkipVerify disables collector certificate verification. It only + // applies to an https:// endpoint. + TLSSkipVerify bool `envconfig:"MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY" default:"false"` + // AllowInsecure acknowledges an unauthenticated-collector connection — + // plaintext export or unverified TLS — outside development and test. + AllowInsecure bool `envconfig:"MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE" default:"false"` + // LogsEnabled ships log records to the collector alongside traces and + // metrics. It defaults on so logs are readable next to the traces they + // belong to; turn it off when log volume is the cost that matters. Local + // stdout logging is never affected. + LogsEnabled bool `envconfig:"MOSAIC_OTEL_LOGS_ENABLED" default:"true"` +} + +// ExportLogs reports whether log records should be shipped to the collector, +// which needs both a collector to ship to and the signal left enabled. +func (t TelemetryConfig) ExportLogs() bool { + return t.OTLPEndpoint != "" && t.LogsEnabled } // ValidationError aggregates every configuration problem found at startup so an @@ -293,6 +319,8 @@ func load() (Config, error) { cfg.Log.Format = strings.ToLower(strings.TrimSpace(cfg.Log.Format)) cfg.Telemetry.ServiceName = strings.TrimSpace(cfg.Telemetry.ServiceName) cfg.Telemetry.OTLPEndpoint = strings.TrimSpace(cfg.Telemetry.OTLPEndpoint) + cfg.Telemetry.OTLPProtocol = strings.ToLower(strings.TrimSpace(cfg.Telemetry.OTLPProtocol)) + cfg.Telemetry.OTLPHeaders = strings.TrimSpace(cfg.Telemetry.OTLPHeaders) cfg.BrowserAuth.CookieDomain = strings.TrimSpace(cfg.BrowserAuth.CookieDomain) cfg.Protocol.V02SchemaPath = strings.TrimSpace(cfg.Protocol.V02SchemaPath) cfg.Protocol.CommerceProviderSchemaPath = strings.TrimSpace(cfg.Protocol.CommerceProviderSchemaPath) @@ -399,9 +427,7 @@ func (cfg Config) validate() error { cfg.validateBilling(report, productionLike) cfg.validateWorker(report) - if strings.TrimSpace(cfg.Telemetry.ServiceName) == "" { - report.add("OTEL_SERVICE_NAME must not be empty") - } + cfg.validateTelemetry(report, productionLike) if cfg.BrowserAuth.SessionLifetime <= 0 { report.add("MOSAIC_SESSION_LIFETIME must be greater than zero") } @@ -544,6 +570,61 @@ func databaseSSLMode(raw string) string { return "" } +// validateTelemetry rejects an export setup that cannot work, and one that +// would put collector credentials on a connection nothing authenticates. No +// problem it reports contains a header value. +func (cfg Config) validateTelemetry(report *problems, productionLike bool) { + if strings.TrimSpace(cfg.Telemetry.ServiceName) == "" { + report.add("OTEL_SERVICE_NAME must not be empty") + } + if _, err := telemetry.NormalizeProtocol(cfg.Telemetry.OTLPProtocol); err != nil { + report.add(`OTEL_EXPORTER_OTLP_PROTOCOL must be "http/protobuf" or "grpc"`) + } + if _, err := telemetry.ParseHeaders(cfg.Telemetry.OTLPHeaders); err != nil { + report.add( + "OTEL_EXPORTER_OTLP_HEADERS must be a comma-separated list of key=value pairs " + + "with percent-encoded values", + ) + } + + // Everything below describes how Mosaic reaches the collector, which only + // matters once there is one to reach. + if cfg.Telemetry.OTLPEndpoint == "" { + return + } + endpoint, err := url.Parse(cfg.Telemetry.OTLPEndpoint) + if err != nil || endpoint.Host == "" || (endpoint.Scheme != "http" && endpoint.Scheme != "https") { + report.add("OTEL_EXPORTER_OTLP_ENDPOINT must be an absolute http:// or https:// URL") + return + } + if endpoint.User != nil { + report.add("OTEL_EXPORTER_OTLP_ENDPOINT must not embed credentials; use OTEL_EXPORTER_OTLP_HEADERS") + } + if cfg.Telemetry.TLSSkipVerify && endpoint.Scheme != "https" { + report.add( + "MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY has no effect on a plaintext OTEL_EXPORTER_OTLP_ENDPOINT; " + + "use an https:// endpoint or unset it", + ) + } + if !productionLike || cfg.Telemetry.AllowInsecure { + return + } + // Outside development, an export connection that is neither encrypted nor + // verified is a credential-disclosure risk whenever headers are set, and a + // telemetry-tampering risk even when they are not. + if endpoint.Scheme != "https" { + report.add( + "OTEL_EXPORTER_OTLP_ENDPOINT must use https outside development and test; set " + + "MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE=true only when the collector is reached over a trusted private network", + ) + } else if cfg.Telemetry.TLSSkipVerify { + report.add( + "MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY must be false outside development and test; set " + + "MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE=true to accept an unverified collector certificate", + ) + } +} + func (cfg Config) validateObjectStore(report *problems, productionLike bool) { if cfg.ObjectStore.Endpoint == "" || cfg.ObjectStore.AccessKey == "" || cfg.ObjectStore.SecretKey == "" || cfg.ObjectStore.Bucket == "" { diff --git a/apps/api/internal/platform/config/config_test.go b/apps/api/internal/platform/config/config_test.go index 95b517e6..26304924 100644 --- a/apps/api/internal/platform/config/config_test.go +++ b/apps/api/internal/platform/config/config_test.go @@ -169,6 +169,190 @@ func TestLoadRejectsInsecureHostedAuthenticationAndAssetDefaults(t *testing.T) { } } +func TestTelemetryProtocolAcceptsSupportedTransports(t *testing.T) { + for name, test := range map[string]struct { + value string + wantError bool + }{ + "unset defaults to http": {value: ""}, + "http/protobuf": {value: "http/protobuf"}, + "grpc": {value: "grpc"}, + "mixed case is tolerated": {value: "GRPC"}, + "unsupported transport": {value: "thrift", wantError: true}, + } { + t.Run(name, func(t *testing.T) { + values := map[string]string{"OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.internal:4317"} + if test.value != "" { + values["OTEL_EXPORTER_OTLP_PROTOCOL"] = test.value + } + cfg, err := loadTestConfig(t, values) + if test.wantError { + if err == nil { + t.Fatal("load succeeded with unsupported OTLP protocol") + } + if !strings.Contains(err.Error(), "OTEL_EXPORTER_OTLP_PROTOCOL") { + t.Fatalf("error = %v, want it to name the offending variable", err) + } + return + } + if err != nil { + t.Fatalf("load telemetry protocol %q: %v", test.value, err) + } + if want := strings.ToLower(test.value); cfg.Telemetry.OTLPProtocol != want { + t.Fatalf("protocol = %q, want %q", cfg.Telemetry.OTLPProtocol, want) + } + }) + } +} + +// Log export follows the collector: on by default so logs sit beside the traces +// they belong to, off when there is nowhere to send them or an operator says so. +func TestLogExportFollowsCollectorAndOptOut(t *testing.T) { + for name, test := range map[string]struct { + values map[string]string + want bool + }{ + "no collector configured": {values: map[string]string{}}, + "collector configured": { + values: map[string]string{"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318"}, + want: true, + }, + "logs disabled with a collector": { + values: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318", + "MOSAIC_OTEL_LOGS_ENABLED": "false", + }, + }, + "logs enabled without a collector": { + values: map[string]string{"MOSAIC_OTEL_LOGS_ENABLED": "true"}, + }, + } { + t.Run(name, func(t *testing.T) { + cfg, err := loadTestConfig(t, test.values) + if err != nil { + t.Fatalf("load configuration: %v", err) + } + if cfg.Telemetry.ExportLogs() != test.want { + t.Fatalf("export logs = %t, want %t", cfg.Telemetry.ExportLogs(), test.want) + } + }) + } +} + +func TestTelemetryExportSecurityIsValidated(t *testing.T) { + secretHeader := "authorization=Bearer%20collector-super-secret" + for name, test := range map[string]struct { + values map[string]string + wantError bool + }{ + "plaintext collector in development": { + values: map[string]string{"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318"}, + }, + "authenticated https collector": { + values: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "https://collector.example:4318", + "OTEL_EXPORTER_OTLP_HEADERS": secretHeader, + }, + }, + "malformed headers": { + values: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "https://collector.example:4318", + "OTEL_EXPORTER_OTLP_HEADERS": "authorization Bearer collector-super-secret", + }, + wantError: true, + }, + "endpoint embedding credentials": { + values: map[string]string{"OTEL_EXPORTER_OTLP_ENDPOINT": "https://user:collector-super-secret@collector.example:4318"}, + wantError: true, + }, + "skip verify on a plaintext endpoint": { + values: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318", + "MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY": "true", + }, + wantError: true, + }, + } { + t.Run(name, func(t *testing.T) { + _, err := loadTestConfig(t, test.values) + assertTelemetryValidation(t, err, test.wantError) + }) + } +} + +func TestProductionTelemetryRequiresVerifiedCollectorOrAcknowledgement(t *testing.T) { + for name, test := range map[string]struct { + overrides map[string]string + wantError bool + }{ + "verified https collector": { + overrides: map[string]string{"OTEL_EXPORTER_OTLP_ENDPOINT": "https://collector.example:4318"}, + }, + "plaintext collector": { + overrides: map[string]string{"OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example:4318"}, + wantError: true, + }, + "plaintext collector on a trusted network": { + overrides: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.internal:4318", + "MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE": "true", + }, + }, + "unverified certificate": { + overrides: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "https://collector.example:4318", + "MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY": "true", + }, + wantError: true, + }, + "unverified certificate acknowledged": { + overrides: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "https://collector.example:4318", + "MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY": "true", + "MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE": "true", + }, + }, + } { + t.Run(name, func(t *testing.T) { + values := productionConfigValues() + values["OTEL_EXPORTER_OTLP_HEADERS"] = "authorization=Bearer%20collector-super-secret" + for key, value := range test.overrides { + values[key] = value + } + _, err := loadTestConfig(t, values) + assertTelemetryValidation(t, err, test.wantError) + }) + } +} + +// productionConfigValues is a deployment that passes every production guard, so +// a test that overrides one variable is asserting about that variable alone. +func productionConfigValues() map[string]string { + return map[string]string{ + "MOSAIC_ENVIRONMENT": "production", + "MOSAIC_CORS_ALLOWED_ORIGINS": "https://studio.example", + "MOSAIC_SESSION_COOKIE_SECURE": "true", + "MOSAIC_PUBLIC_ASSET_BASE_URL": "https://assets.example/v1/sdk/assets", + "MOSAIC_OBJECT_STORAGE_ACCESS_KEY": "production-access", + "MOSAIC_OBJECT_STORAGE_SECRET_KEY": "production-secret", + "MOSAIC_OBJECT_STORAGE_TLS": "true", + "DATABASE_URL": "postgres://mosaic:secret-password@db.example:5432/mosaic?sslmode=verify-full", + } +} + +func assertTelemetryValidation(t *testing.T, err error, wantError bool) { + t.Helper() + if err != nil && strings.Contains(err.Error(), "collector-super-secret") { + t.Fatalf("startup error leaked a collector credential: %v", err) + } + switch { + case wantError && err == nil: + t.Fatal("load succeeded with an unsafe telemetry export configuration") + case !wantError && err != nil: + t.Fatalf("load telemetry export configuration: %v", err) + } +} + func TestLoadReadsDotEnvBeforeDecoding(t *testing.T) { clearConfigEnvironment(t) temporaryDirectory := t.TempDir() @@ -213,6 +397,9 @@ func clearConfigEnvironment(t *testing.T) { "MOSAIC_HTTP_READ_TIMEOUT", "MOSAIC_HTTP_WRITE_TIMEOUT", "MOSAIC_HTTP_IDLE_TIMEOUT", "MOSAIC_HTTP_HANDLER_TIMEOUT", "MOSAIC_HTTP_SHUTDOWN_TIMEOUT", "MOSAIC_CORS_ALLOWED_ORIGINS", "MOSAIC_LOG_LEVEL", "MOSAIC_LOG_FORMAT", "OTEL_SERVICE_NAME", "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_PROTOCOL", "OTEL_EXPORTER_OTLP_HEADERS", + "MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY", "MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE", + "MOSAIC_OTEL_LOGS_ENABLED", "DATABASE_URL", "DATABASE_MAX_CONNECTIONS", "DATABASE_MIN_CONNECTIONS", "DATABASE_CONNECT_TIMEOUT", "MOSAIC_SESSION_LIFETIME", "MOSAIC_SESSION_COOKIE_SECURE", "MOSAIC_SESSION_COOKIE_DOMAIN", "MOSAIC_AUTH_REQUESTS_PER_MINUTE", "MOSAIC_AUTH_BURST", "MOSAIC_AUTH_LIMITER_ENTRIES", @@ -263,16 +450,7 @@ func clearConfigEnvironment(t *testing.T) { // requests from any origin. Each case asserts one guard fires and that the // error names the variable without echoing its value. func TestProductionConfigurationGuards(t *testing.T) { - secureProduction := map[string]string{ - "MOSAIC_ENVIRONMENT": "production", - "MOSAIC_CORS_ALLOWED_ORIGINS": "https://studio.example", - "MOSAIC_SESSION_COOKIE_SECURE": "true", - "MOSAIC_PUBLIC_ASSET_BASE_URL": "https://assets.example/v1/sdk/assets", - "MOSAIC_OBJECT_STORAGE_ACCESS_KEY": "production-access", - "MOSAIC_OBJECT_STORAGE_SECRET_KEY": "production-secret", - "MOSAIC_OBJECT_STORAGE_TLS": "true", - "DATABASE_URL": "postgres://mosaic:secret-password@db.example:5432/mosaic?sslmode=verify-full", - } + secureProduction := productionConfigValues() for name, test := range map[string]struct { overrides map[string]string diff --git a/apps/api/internal/platform/logging/logging.go b/apps/api/internal/platform/logging/logging.go index c2318733..3637237d 100644 --- a/apps/api/internal/platform/logging/logging.go +++ b/apps/api/internal/platform/logging/logging.go @@ -8,7 +8,27 @@ import ( "github.com/rs/zerolog" ) +// New builds a logger that writes only to output. Telemetry's own SDK errors go +// to a logger built this way: a logger that exported would feed export failures +// back into the exporter that produced them. func New(level string, format string, output io.Writer) (zerolog.Logger, error) { + return build(level, format, output) +} + +// NewExporting builds a logger that writes to output and also emits every +// record to the OpenTelemetry Logs pipeline under the given scope name. The +// local stream is never given up: it stays the record of truth when the +// collector is unreachable, and it is what `docker logs` and `kubectl logs` +// show. +// +// Records are exported through the global logger provider, which telemetry.New +// installs. Building this logger before that call is safe — records emitted in +// between go to the no-op provider and appear locally only. +func NewExporting(level string, format string, output io.Writer, scope string) (zerolog.Logger, error) { + return build(level, format, output, otlpWriter{scope: scope}) +} + +func build(level string, format string, output io.Writer, extra ...io.Writer) (zerolog.Logger, error) { parsedLevel, err := zerolog.ParseLevel(level) if err != nil { return zerolog.Logger{}, fmt.Errorf("parse log level: %w", err) @@ -21,6 +41,12 @@ func New(level string, format string, output io.Writer) (zerolog.Logger, error) TimeFormat: time.RFC3339, } } + if len(extra) > 0 { + // Every writer in the tee receives the same JSON object; console + // rendering happens inside ConsoleWriter, so the OTLP writer still sees + // structured fields even in development format. + writer = zerolog.MultiLevelWriter(append([]io.Writer{writer}, extra...)...) + } return zerolog.New(writer). Level(parsedLevel). diff --git a/apps/api/internal/platform/logging/otlp.go b/apps/api/internal/platform/logging/otlp.go new file mode 100644 index 00000000..e7455efe --- /dev/null +++ b/apps/api/internal/platform/logging/otlp.go @@ -0,0 +1,150 @@ +package logging + +import ( + "context" + "encoding/json" + "time" + + "github.com/rs/zerolog" + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/global" +) + +// otlpWriter forwards every emitted log line to the OpenTelemetry Logs +// pipeline, in addition to the local stream it is teed with. +// +// It sits at the writer end of zerolog rather than at a hook because a zerolog +// hook cannot read the fields already attached to an event. The writer receives +// the finished JSON object, so the exported record keeps every structured field +// the local line has — which is the whole point of shipping logs rather than +// reading them out of a terminal. +type otlpWriter struct { + // scope names the instrumentation scope on every exported record. It is + // resolved to a logger per write because the global provider is installed + // by telemetry.New, which runs after the logger exists; the global package + // delegates to a no-op until then. + scope string +} + +// Fields the OTLP record models directly rather than as attributes, so a +// backend can filter on severity, time, and body without knowing zerolog's +// field names. +var structuralFields = map[string]struct{}{ + zerolog.LevelFieldName: {}, + zerolog.TimestampFieldName: {}, + zerolog.MessageFieldName: {}, +} + +func (w otlpWriter) Write(line []byte) (int, error) { + return w.WriteLevel(zerolog.NoLevel, line) +} + +func (w otlpWriter) WriteLevel(level zerolog.Level, line []byte) (int, error) { + // A malformed line still reached the local stream, which is the record of + // truth. Dropping it here keeps a logging problem from becoming a request + // failure, and reporting an error would make zerolog complain on stderr for + // every line. + var fields map[string]json.RawMessage + if err := json.Unmarshal(line, &fields); err != nil { + return len(line), nil + } + + var record log.Record + record.SetObservedTimestamp(time.Now()) + record.SetSeverity(severityOf(level)) + record.SetSeverityText(level.String()) + record.SetBody(log.StringValue(decodeString(fields[zerolog.MessageFieldName]))) + if timestamp, ok := decodeTimestamp(fields[zerolog.TimestampFieldName]); ok { + record.SetTimestamp(timestamp) + } + + attributes := make([]log.KeyValue, 0, len(fields)) + for key, raw := range fields { + if _, structural := structuralFields[key]; structural { + continue + } + attributes = append(attributes, attributeOf(key, raw)) + } + if len(attributes) > 0 { + record.AddAttributes(attributes...) + } + + // The batch processor owns delivery from here: Emit hands the record off + // without blocking on the collector, so an export stall cannot slow down + // the request that produced the line. + global.Logger(w.scope).Emit(context.Background(), record) + return len(line), nil +} + +// attributeOf preserves the JSON type of a field so a backend can range over +// numbers and filter on booleans. Objects and arrays keep their JSON encoding, +// which stays readable and avoids flattening nested context into new keys. +func attributeOf(key string, raw json.RawMessage) log.KeyValue { + var value any + if err := json.Unmarshal(raw, &value); err != nil { + return log.String(key, string(raw)) + } + switch typed := value.(type) { + case string: + return log.String(key, typed) + case bool: + return log.Bool(key, typed) + case float64: + // JSON has one number type; keeping integers integral matters for IDs + // and counts, which are most of Mosaic's numeric fields. + if typed == float64(int64(typed)) { + return log.Int64(key, int64(typed)) + } + return log.Float64(key, typed) + case nil: + return log.String(key, "") + default: + return log.String(key, string(raw)) + } +} + +func decodeString(raw json.RawMessage) string { + if len(raw) == 0 { + return "" + } + var value string + if err := json.Unmarshal(raw, &value); err != nil { + return string(raw) + } + return value +} + +func decodeTimestamp(raw json.RawMessage) (time.Time, bool) { + if len(raw) == 0 { + return time.Time{}, false + } + timestamp, err := time.Parse(time.RFC3339, decodeString(raw)) + if err != nil { + return time.Time{}, false + } + return timestamp, true +} + +// severityOf maps zerolog levels onto the OpenTelemetry severity range so +// backends that alert on severity see the same picture as a reader of the local +// stream. Panic sits above fatal because it carries a stack unwind with it. +func severityOf(level zerolog.Level) log.Severity { + switch level { + case zerolog.TraceLevel: + return log.SeverityTrace1 + case zerolog.DebugLevel: + return log.SeverityDebug1 + case zerolog.InfoLevel: + return log.SeverityInfo1 + case zerolog.WarnLevel: + return log.SeverityWarn1 + case zerolog.ErrorLevel: + return log.SeverityError1 + case zerolog.FatalLevel: + return log.SeverityFatal1 + case zerolog.PanicLevel: + return log.SeverityFatal2 + default: + return log.SeverityUndefined + } +} diff --git a/apps/api/internal/platform/logging/otlp_test.go b/apps/api/internal/platform/logging/otlp_test.go new file mode 100644 index 00000000..dc907f13 --- /dev/null +++ b/apps/api/internal/platform/logging/otlp_test.go @@ -0,0 +1,193 @@ +package logging + +import ( + "bytes" + "context" + "strings" + "sync" + "testing" + + "github.com/rs/zerolog" + otellog "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/global" + sdklog "go.opentelemetry.io/otel/sdk/log" +) + +// recordingExporter stands in for a collector so a test can assert on what +// would go over the wire. +type recordingExporter struct { + mutex sync.Mutex + records []sdklog.Record +} + +func (e *recordingExporter) Export(_ context.Context, records []sdklog.Record) error { + e.mutex.Lock() + defer e.mutex.Unlock() + for _, record := range records { + e.records = append(e.records, record.Clone()) + } + return nil +} + +func (e *recordingExporter) Shutdown(context.Context) error { return nil } +func (e *recordingExporter) ForceFlush(context.Context) error { return nil } + +func (e *recordingExporter) collected() []sdklog.Record { + e.mutex.Lock() + defer e.mutex.Unlock() + return append([]sdklog.Record(nil), e.records...) +} + +// installRecordingProvider points the global logger provider at an exporter for +// the duration of a test, restoring a no-op provider afterwards so a test that +// asserts nothing is exported cannot be polluted by an earlier one. +func installRecordingProvider(t *testing.T) *recordingExporter { + t.Helper() + exporter := &recordingExporter{} + provider := sdklog.NewLoggerProvider(sdklog.WithProcessor(sdklog.NewSimpleProcessor(exporter))) + global.SetLoggerProvider(provider) + t.Cleanup(func() { + if err := provider.Shutdown(context.Background()); err != nil { + t.Fatalf("shutdown recording provider: %v", err) + } + global.SetLoggerProvider(sdklog.NewLoggerProvider()) + }) + return exporter +} + +func attributesOf(t *testing.T, record sdklog.Record) map[string]string { + t.Helper() + attributes := make(map[string]string) + record.WalkAttributes(func(attribute otellog.KeyValue) bool { + attributes[attribute.Key] = attribute.Value.String() + return true + }) + return attributes +} + +// The exported record must carry the same structured fields as the local line. +// Shipping level and message alone would leave an operator back at the terminal +// they were trying not to open. +func TestExportingLoggerKeepsLocalStreamAndStructuredFields(t *testing.T) { + exporter := installRecordingProvider(t) + var local bytes.Buffer + + logger, err := NewExporting("info", "json", &local, "mosaic-api-test") + if err != nil { + t.Fatalf("build exporting logger: %v", err) + } + logger.Info(). + Str("project_id", "prj_123"). + Int("attempt", 3). + Bool("retryable", true). + Msg("published release") + + if !strings.Contains(local.String(), `"message":"published release"`) { + t.Fatalf("local stream = %q, want the log line", local.String()) + } + + records := exporter.collected() + if len(records) != 1 { + t.Fatalf("exported %d records, want 1", len(records)) + } + record := records[0] + if body := record.Body().AsString(); body != "published release" { + t.Fatalf("body = %q, want the message", body) + } + if record.Severity() != otellog.SeverityInfo1 { + t.Fatalf("severity = %v, want info", record.Severity()) + } + if record.Timestamp().IsZero() { + t.Fatal("record has no timestamp") + } + + attributes := attributesOf(t, record) + for key, want := range map[string]string{ + "project_id": "prj_123", + "attempt": "3", + "retryable": "true", + } { + if attributes[key] != want { + t.Fatalf("attribute %s = %q, want %q", key, attributes[key], want) + } + } + // Level, time, and message are modelled as record structure, so repeating + // them as attributes would duplicate them in every backend. + for _, key := range []string{zerolog.LevelFieldName, zerolog.TimestampFieldName, zerolog.MessageFieldName} { + if _, duplicated := attributes[key]; duplicated { + t.Fatalf("structural field %s was also exported as an attribute", key) + } + } +} + +func TestNonExportingLoggerStaysLocal(t *testing.T) { + exporter := installRecordingProvider(t) + var local bytes.Buffer + + logger, err := New("info", "json", &local) + if err != nil { + t.Fatalf("build logger: %v", err) + } + logger.Info().Msg("published release") + + if local.Len() == 0 { + t.Fatal("local stream is empty") + } + if records := exporter.collected(); len(records) != 0 { + t.Fatalf("exported %d records, want none from a local-only logger", len(records)) + } +} + +// Console format renders for humans at the local writer, but the exported +// record must still be built from the structured JSON. +func TestExportingLoggerKeepsFieldsInConsoleFormat(t *testing.T) { + exporter := installRecordingProvider(t) + var local bytes.Buffer + + logger, err := NewExporting("debug", "console", &local, "mosaic-api-test") + if err != nil { + t.Fatalf("build exporting logger: %v", err) + } + logger.Warn().Str("project_id", "prj_123").Msg("slow publish") + + records := exporter.collected() + if len(records) != 1 { + t.Fatalf("exported %d records, want 1", len(records)) + } + if records[0].Severity() != otellog.SeverityWarn1 { + t.Fatalf("severity = %v, want warn", records[0].Severity()) + } + if attributes := attributesOf(t, records[0]); attributes["project_id"] != "prj_123" { + t.Fatalf("attributes = %#v, want the project id", attributes) + } +} + +func TestSeverityMapsAcrossLevels(t *testing.T) { + for level, want := range map[zerolog.Level]otellog.Severity{ + zerolog.TraceLevel: otellog.SeverityTrace1, + zerolog.DebugLevel: otellog.SeverityDebug1, + zerolog.InfoLevel: otellog.SeverityInfo1, + zerolog.WarnLevel: otellog.SeverityWarn1, + zerolog.ErrorLevel: otellog.SeverityError1, + zerolog.FatalLevel: otellog.SeverityFatal1, + zerolog.PanicLevel: otellog.SeverityFatal2, + zerolog.NoLevel: otellog.SeverityUndefined, + } { + if got := severityOf(level); got != want { + t.Fatalf("severity of %s = %v, want %v", level, got, want) + } + } +} + +// A line the writer cannot parse has already reached the local stream. It must +// not surface as a write error, which zerolog would report on stderr for every +// subsequent line. +func TestWriterDropsUnparsableLinesWithoutError(t *testing.T) { + installRecordingProvider(t) + writer := otlpWriter{scope: "mosaic-api-test"} + line := []byte("not json\n") + written, err := writer.WriteLevel(zerolog.InfoLevel, line) + if err != nil || written != len(line) { + t.Fatalf("write = %d, %v; want %d and no error", written, err, len(line)) + } +} diff --git a/apps/api/internal/platform/telemetry/telemetry.go b/apps/api/internal/platform/telemetry/telemetry.go index 96384c4f..ecc796ee 100644 --- a/apps/api/internal/platform/telemetry/telemetry.go +++ b/apps/api/internal/platform/telemetry/telemetry.go @@ -2,27 +2,72 @@ package telemetry import ( "context" + "crypto/tls" "errors" "fmt" + "net/url" + "strings" "github.com/rs/zerolog" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/propagation" + sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.41.0" + "google.golang.org/grpc/credentials" "github.com/Mujhtech/mosaic/apps/api/internal/platform/buildinfo" ) +// Supported values for Config.OTLPProtocol. They mirror the OpenTelemetry +// specification's OTEL_EXPORTER_OTLP_PROTOCOL values so an operator can reuse +// the vendor documentation of whichever collector they point Mosaic at. +const ( + ProtocolHTTP = "http/protobuf" + ProtocolGRPC = "grpc" +) + +// DefaultProtocol is what an unset OTLPProtocol resolves to. HTTP stays the +// default because it survives proxies and ingress that do not speak HTTP/2, +// which is how most hosted collectors are reached. +const DefaultProtocol = ProtocolHTTP + type Config struct { ServiceName string Environment string OTLPEndpoint string + // OTLPProtocol selects the OTLP transport: "http/protobuf" (default) or + // "grpc". Both carry the same payloads; only the wire transport differs, + // and the endpoint port usually does too (4318 for HTTP, 4317 for gRPC). + OTLPProtocol string + // OTLPHeaders carries per-request export headers in the OpenTelemetry + // specification's format ("key1=value1,key2=value2", values + // percent-encoded). This is where a hosted collector's API key or bearer + // token goes, so the values are secrets: they are never logged, and never + // appear in an error returned from this package. + OTLPHeaders string + // OTLPTLSSkipVerify disables certificate verification for an https:// + // endpoint. It exists for collectors behind a private CA or a self-signed + // certificate; it is not a way to run TLS "loosely" on the public internet, + // because an unverified connection cannot tell the collector apart from + // anything that intercepts it — including whatever OTLPHeaders is sent to. + // It has no effect on an http:// endpoint, which is plaintext regardless. + OTLPTLSSkipVerify bool + // DisableLogExport stops log records from being exported while leaving + // traces and metrics alone. Log volume is the expensive signal at most + // vendors, so an operator needs to turn it off without giving up tracing. + // The local log stream is unaffected either way. + DisableLogExport bool // Logger receives OpenTelemetry's own internal errors (export failures, // dropped batches). Without it the SDK writes them to the standard library // logger, which bypasses Mosaic's JSON log stream and makes exporter @@ -57,37 +102,232 @@ func New(ctx context.Context, cfg Config) (Shutdown, error) { sdktrace.WithResource(serviceResource), } metricOptions := []sdkmetric.Option{sdkmetric.WithResource(serviceResource)} + logOptions := []sdklog.LoggerProviderOption{sdklog.WithResource(serviceResource)} if cfg.OTLPEndpoint != "" { - exporter, err := otlptracehttp.New( - ctx, - otlptracehttp.WithEndpointURL(cfg.OTLPEndpoint), - ) + export, err := resolveExport(cfg) + if err != nil { + return nil, err + } + traceExporter, err := newTraceExporter(ctx, export) if err != nil { - return nil, fmt.Errorf("create OTLP HTTP trace exporter: %w", err) + return nil, fmt.Errorf("create OTLP %s trace exporter: %w", export.protocol, err) } - options = append(options, sdktrace.WithBatcher(exporter)) - metricExporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(cfg.OTLPEndpoint)) + options = append(options, sdktrace.WithBatcher(traceExporter)) + metricExporter, err := newMetricExporter(ctx, export) if err != nil { - return nil, fmt.Errorf("create OTLP HTTP metric exporter: %w", err) + return nil, fmt.Errorf("create OTLP %s metric exporter: %w", export.protocol, err) } metricOptions = append(metricOptions, sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter))) + if !cfg.DisableLogExport { + logExporter, err := newLogExporter(ctx, export) + if err != nil { + return nil, fmt.Errorf("create OTLP %s log exporter: %w", export.protocol, err) + } + logOptions = append(logOptions, sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter))) + } } provider := sdktrace.NewTracerProvider(options...) otel.SetTracerProvider(provider) metricProvider := sdkmetric.NewMeterProvider(metricOptions...) otel.SetMeterProvider(metricProvider) + // Installing the logger provider globally is what connects an already-built + // exporting logger to the pipeline: until this call its records go to the + // no-op provider and appear on the local stream only. + logProvider := sdklog.NewLoggerProvider(logOptions...) + global.SetLoggerProvider(logProvider) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) return func(ctx context.Context) error { - return errors.Join(metricProvider.Shutdown(ctx), provider.Shutdown(ctx)) + // Logs flush first: the last records a shutdown produces are the ones an + // operator most wants to see, and they are the cheapest signal to drain. + return errors.Join( + logProvider.Shutdown(ctx), + metricProvider.Shutdown(ctx), + provider.Shutdown(ctx), + ) }, nil } +// NormalizeProtocol resolves an operator-supplied protocol name to one of the +// supported constants, treating the empty value as the default. Configuration +// loading uses it so an unsupported protocol is reported at startup validation +// alongside every other config problem rather than failing later in New. +func NormalizeProtocol(value string) (string, error) { + return normalizeProtocol(value) +} + +func normalizeProtocol(value string) (string, error) { + switch strings.ToLower(strings.TrimSpace(value)) { + case "": + return DefaultProtocol, nil + // "http" is not a spec value, but it is the obvious thing to write and + // rejecting it would only cost an operator a deploy cycle to learn. + case ProtocolHTTP, "http": + return ProtocolHTTP, nil + case ProtocolGRPC: + return ProtocolGRPC, nil + default: + return "", fmt.Errorf( + "unsupported OTLP protocol %q: want %q or %q", + value, ProtocolHTTP, ProtocolGRPC, + ) + } +} + +// ParseHeaders decodes the OpenTelemetry OTLP header format — +// "key1=value1,key2=value2" with percent-encoded values — into the map the +// exporters take. An empty input yields no headers. Errors name the offending +// key at most, never a value, because values are credentials. +func ParseHeaders(value string) (map[string]string, error) { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return nil, nil + } + headers := make(map[string]string) + for _, pair := range strings.Split(trimmed, ",") { + if strings.TrimSpace(pair) == "" { + continue + } + key, encoded, found := strings.Cut(pair, "=") + key = strings.TrimSpace(key) + if !found || key == "" { + return nil, errors.New("OTLP headers must be a comma-separated list of key=value pairs") + } + decoded, err := url.QueryUnescape(strings.TrimSpace(encoded)) + if err != nil { + return nil, fmt.Errorf("OTLP header %q has a malformed percent-encoded value", key) + } + headers[key] = decoded + } + return headers, nil +} + +// exportSettings is the resolved, validated shape of the export configuration: +// everything the exporter constructors need, already normalized. +type exportSettings struct { + protocol string + endpoint string + headers map[string]string + // tlsConfig is nil unless certificate verification is being skipped on an + // https:// endpoint, which is the only case where the default client TLS + // behaviour needs overriding. + tlsConfig *tls.Config +} + +func resolveExport(cfg Config) (exportSettings, error) { + protocol, err := normalizeProtocol(cfg.OTLPProtocol) + if err != nil { + return exportSettings{}, err + } + headers, err := ParseHeaders(cfg.OTLPHeaders) + if err != nil { + return exportSettings{}, err + } + endpoint, err := url.Parse(cfg.OTLPEndpoint) + if err != nil || endpoint.Host == "" { + return exportSettings{}, errors.New("OTLP endpoint must be an absolute http:// or https:// URL") + } + settings := exportSettings{protocol: protocol, endpoint: cfg.OTLPEndpoint, headers: headers} + // Skipping verification only means anything over TLS. Applying it to a + // plaintext endpoint would, for the gRPC exporter, quietly upgrade the + // connection to TLS against a collector that is not listening for it. + if cfg.OTLPTLSSkipVerify && endpoint.Scheme == "https" { + settings.tlsConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // opt-in, and rejected in production-like environments without an explicit acknowledgement + } + return settings, nil +} + +// exporterOptions applies the export policy — always the endpoint, headers only +// when there are any, TLS only when verification is being overridden — to one +// exporter package's option type. +// +// Every OTLP exporter package exposes the same three options under the same +// names but as unrelated types, so the alternative is this decision written out +// once per signal per transport. Six copies of one policy is how the trace, +// metric, and log exporters end up disagreeing about when a header is sent. +func exporterOptions[O any]( + export exportSettings, + withEndpointURL func(string) O, + withHeaders func(map[string]string) O, + withTLS func(*tls.Config) O, +) []O { + options := []O{withEndpointURL(export.endpoint)} + if len(export.headers) > 0 { + options = append(options, withHeaders(export.headers)) + } + if export.tlsConfig != nil { + options = append(options, withTLS(export.tlsConfig)) + } + return options +} + +// grpcTLS adapts a TLS config to the credentials form the gRPC exporters take, +// so both transports can be described by the same withTLS shape. +func grpcTLS[O any](withCredentials func(credentials.TransportCredentials) O) func(*tls.Config) O { + return func(config *tls.Config) O { + return withCredentials(credentials.NewTLS(config)) + } +} + +// newTraceExporter builds the span exporter for the resolved protocol. Both +// transports take the endpoint as a URL, so the scheme an operator writes +// (http:// vs https://) still decides whether the connection is encrypted. +func newTraceExporter(ctx context.Context, export exportSettings) (sdktrace.SpanExporter, error) { + if export.protocol == ProtocolGRPC { + return otlptracegrpc.New(ctx, exporterOptions( + export, + otlptracegrpc.WithEndpointURL, + otlptracegrpc.WithHeaders, + grpcTLS(otlptracegrpc.WithTLSCredentials), + )...) + } + return otlptracehttp.New(ctx, exporterOptions( + export, + otlptracehttp.WithEndpointURL, + otlptracehttp.WithHeaders, + otlptracehttp.WithTLSClientConfig, + )...) +} + +func newMetricExporter(ctx context.Context, export exportSettings) (sdkmetric.Exporter, error) { + if export.protocol == ProtocolGRPC { + return otlpmetricgrpc.New(ctx, exporterOptions( + export, + otlpmetricgrpc.WithEndpointURL, + otlpmetricgrpc.WithHeaders, + grpcTLS(otlpmetricgrpc.WithTLSCredentials), + )...) + } + return otlpmetrichttp.New(ctx, exporterOptions( + export, + otlpmetrichttp.WithEndpointURL, + otlpmetrichttp.WithHeaders, + otlpmetrichttp.WithTLSClientConfig, + )...) +} + +func newLogExporter(ctx context.Context, export exportSettings) (sdklog.Exporter, error) { + if export.protocol == ProtocolGRPC { + return otlploggrpc.New(ctx, exporterOptions( + export, + otlploggrpc.WithEndpointURL, + otlploggrpc.WithHeaders, + grpcTLS(otlploggrpc.WithTLSCredentials), + )...) + } + return otlploghttp.New(ctx, exporterOptions( + export, + otlploghttp.WithEndpointURL, + otlploghttp.WithHeaders, + otlploghttp.WithTLSClientConfig, + )...) +} + // errorHandler routes OpenTelemetry SDK errors into Mosaic's structured log // stream so an operator sees exporter failures in the same JSON pipeline as // every other backend error. diff --git a/apps/api/internal/platform/telemetry/telemetry_test.go b/apps/api/internal/platform/telemetry/telemetry_test.go new file mode 100644 index 00000000..cd794a5e --- /dev/null +++ b/apps/api/internal/platform/telemetry/telemetry_test.go @@ -0,0 +1,196 @@ +package telemetry + +import ( + "context" + "reflect" + "strings" + "testing" + "time" + + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/global" +) + +func TestNormalizeProtocolResolvesSupportedTransports(t *testing.T) { + for name, test := range map[string]struct { + value string + want string + wantError bool + }{ + "empty falls back to the default": {value: "", want: DefaultProtocol}, + "spec http value": {value: "http/protobuf", want: ProtocolHTTP}, + "shorthand http value": {value: "HTTP", want: ProtocolHTTP}, + "grpc": {value: " grpc ", want: ProtocolGRPC}, + "unsupported transport": {value: "http/json", wantError: true}, + } { + t.Run(name, func(t *testing.T) { + protocol, err := NormalizeProtocol(test.value) + if test.wantError { + if err == nil { + t.Fatalf("normalize %q succeeded, want rejection", test.value) + } + return + } + if err != nil { + t.Fatalf("normalize %q: %v", test.value, err) + } + if protocol != test.want { + t.Fatalf("protocol = %q, want %q", protocol, test.want) + } + }) + } +} + +func TestParseHeadersDecodesSpecFormat(t *testing.T) { + headers, err := ParseHeaders(" api-key = secret-token , x-tenant=acme%20corp ") + if err != nil { + t.Fatalf("parse headers: %v", err) + } + want := map[string]string{"api-key": "secret-token", "x-tenant": "acme corp"} + if !reflect.DeepEqual(headers, want) { + t.Fatalf("headers = %#v, want %#v", headers, want) + } + if empty, err := ParseHeaders(" "); err != nil || empty != nil { + t.Fatalf("empty headers = %#v, %v; want no headers and no error", empty, err) + } +} + +func TestParseHeadersRejectsMalformedInputWithoutLeakingValues(t *testing.T) { + for name, value := range map[string]string{ + "missing separator": "authorization Bearer super-secret", + "empty key": "=super-secret", + "bad encoding": "authorization=Bearer%zzsuper-secret", + } { + t.Run(name, func(t *testing.T) { + if _, err := ParseHeaders(value); err == nil { + t.Fatal("parse succeeded, want rejection") + } else if strings.Contains(err.Error(), "super-secret") { + t.Fatalf("error leaked a header value: %v", err) + } + }) + } +} + +// Skip-verify must stay inert on a plaintext endpoint: for the gRPC exporter, +// attaching TLS credentials there would silently switch the connection to TLS +// against a collector that is not serving it. +func TestResolveExportAppliesSkipVerifyOnlyOverTLS(t *testing.T) { + for name, test := range map[string]struct { + endpoint string + wantTLS bool + }{ + "https endpoint": {endpoint: "https://collector.invalid:4318", wantTLS: true}, + "http endpoint": {endpoint: "http://collector.invalid:4318"}, + } { + t.Run(name, func(t *testing.T) { + export, err := resolveExport(Config{OTLPEndpoint: test.endpoint, OTLPTLSSkipVerify: true}) + if err != nil { + t.Fatalf("resolve export: %v", err) + } + if test.wantTLS != (export.tlsConfig != nil) { + t.Fatalf("tls config = %#v, want present = %t", export.tlsConfig, test.wantTLS) + } + if export.tlsConfig != nil && !export.tlsConfig.InsecureSkipVerify { + t.Fatal("tls config does not skip verification") + } + }) + } +} + +// New must build both exporters for either transport without reaching the +// collector: exporter construction is lazy, so a startup failure here would be +// a configuration bug rather than an unreachable-collector symptom. +func TestNewBuildsExportersForEitherTransport(t *testing.T) { + for name, test := range map[string]struct { + protocol string + endpoint string + skipVerify bool + }{ + "http": {protocol: "http/protobuf", endpoint: "http://collector.invalid:4318"}, + "grpc": {protocol: "grpc", endpoint: "http://collector.invalid:4317"}, + "http over unverified tls": {protocol: "http/protobuf", endpoint: "https://collector.invalid:4318", skipVerify: true}, + "grpc over unverified tls": {protocol: "grpc", endpoint: "https://collector.invalid:4317", skipVerify: true}, + } { + t.Run(name, func(t *testing.T) { + shutdown, err := New(context.Background(), Config{ + ServiceName: "mosaic-api-test", + Environment: "test", + OTLPEndpoint: test.endpoint, + OTLPProtocol: test.protocol, + OTLPHeaders: "authorization=Bearer%20token,x-tenant=acme", + OTLPTLSSkipVerify: test.skipVerify, + }) + if err != nil { + t.Fatalf("configure telemetry over %s: %v", test.protocol, err) + } + // Shutdown flushes to an endpoint that does not resolve, so it is + // bounded here and its export failure is expected; the exporters + // building at all is what this test guards. + shutdownContext, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + if err := shutdown(shutdownContext); err != nil { + t.Logf("shutdown reported an export failure: %v", err) + } + }) + } +} + +// Log export is the one signal an operator turns off for cost, so disabling it +// must leave traces and metrics — and startup — untouched. +func TestNewBuildsLogPipelineUnlessDisabled(t *testing.T) { + for name, disabled := range map[string]bool{"exporting logs": false, "log export disabled": true} { + t.Run(name, func(t *testing.T) { + shutdown, err := New(context.Background(), Config{ + ServiceName: "mosaic-api-test", + Environment: "test", + OTLPEndpoint: "http://collector.invalid:4318", + DisableLogExport: disabled, + }) + if err != nil { + t.Fatalf("configure telemetry: %v", err) + } + // A logger provider is installed either way, so code that emits + // records never has to check whether export is on. + var record log.Record + record.SetBody(log.StringValue("startup")) + global.Logger("mosaic-api-test").Emit(context.Background(), record) + + shutdownContext, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + if err := shutdown(shutdownContext); err != nil { + t.Logf("shutdown reported an export failure: %v", err) + } + }) + } +} + +func TestNewRejectsUnsupportedProtocol(t *testing.T) { + _, err := New(context.Background(), Config{ + ServiceName: "mosaic-api-test", + Environment: "test", + OTLPEndpoint: "http://collector.invalid:4318", + OTLPProtocol: "thrift", + }) + if err == nil { + t.Fatal("configure telemetry succeeded with an unsupported protocol") + } + if !strings.Contains(err.Error(), "unsupported OTLP protocol") { + t.Fatalf("error = %v, want an unsupported-protocol message", err) + } +} + +// With no endpoint the exporters are never built, so the protocol value is +// irrelevant and must not turn a working no-export setup into a startup error. +func TestNewIgnoresProtocolWithoutEndpoint(t *testing.T) { + shutdown, err := New(context.Background(), Config{ + ServiceName: "mosaic-api-test", + Environment: "test", + OTLPProtocol: "thrift", + }) + if err != nil { + t.Fatalf("configure telemetry without an endpoint: %v", err) + } + if err := shutdown(context.Background()); err != nil { + t.Fatalf("shutdown telemetry: %v", err) + } +} diff --git a/compose.yaml b/compose.yaml index e4fffe08..c940218d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -157,6 +157,11 @@ services: MOSAIC_PROVIDER_OPERATION_TIMEOUT: ${MOSAIC_PROVIDER_OPERATION_TIMEOUT:-60s} OTEL_SERVICE_NAME: mosaic-api OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-} + OTEL_EXPORTER_OTLP_PROTOCOL: ${OTEL_EXPORTER_OTLP_PROTOCOL:-} + OTEL_EXPORTER_OTLP_HEADERS: ${OTEL_EXPORTER_OTLP_HEADERS:-} + MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY: ${MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY:-false} + MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE: ${MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE:-false} + MOSAIC_OTEL_LOGS_ENABLED: ${MOSAIC_OTEL_LOGS_ENABLED:-true} healthcheck: test: ["CMD", "/usr/local/bin/healthcheck", "http://127.0.0.1:8080/health/ready"] interval: 10s @@ -200,6 +205,11 @@ services: MOSAIC_WORKER_HEALTH_ADDRESS: :8081 OTEL_SERVICE_NAME: mosaic-worker OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-} + OTEL_EXPORTER_OTLP_PROTOCOL: ${OTEL_EXPORTER_OTLP_PROTOCOL:-} + OTEL_EXPORTER_OTLP_HEADERS: ${OTEL_EXPORTER_OTLP_HEADERS:-} + MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY: ${MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY:-false} + MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE: ${MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE:-false} + MOSAIC_OTEL_LOGS_ENABLED: ${MOSAIC_OTEL_LOGS_ENABLED:-true} healthcheck: test: ["CMD", "/usr/local/bin/healthcheck", "http://127.0.0.1:8081/health/ready"] interval: 10s diff --git a/docs/backend/api-foundation.md b/docs/backend/api-foundation.md index aef4cb2a..bbc4dcad 100644 --- a/docs/backend/api-foundation.md +++ b/docs/backend/api-foundation.md @@ -148,7 +148,12 @@ variables take precedence because `.env` loading does not overwrite them. | `MOSAIC_LOG_LEVEL` | `info` | Zerolog level such as `debug`, `info`, or `warn`. | | `MOSAIC_LOG_FORMAT` | `json` | `json` or developer-friendly `console`. | | `OTEL_SERVICE_NAME` | `mosaic-api` | OpenTelemetry service name. | -| `OTEL_EXPORTER_OTLP_ENDPOINT` | empty | Optional OTLP/HTTP trace endpoint. With no endpoint, trace context still exists but spans are not exported. | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | empty | Optional OTLP endpoint. With no endpoint, trace context still exists but spans are not exported. | +| `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` | OTLP transport for that endpoint: `http/protobuf` (port 4318) or `grpc` (port 4317). Ignored when unset. | +| `OTEL_EXPORTER_OTLP_HEADERS` | empty | Export headers as `key=value,key=value`, values percent-encoded. Collector authentication; treated as a secret. | +| `MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY` | `false` | Skip collector certificate verification. `https://` endpoints only. | +| `MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE` | `false` | Acknowledges a plaintext or unverified collector connection outside development and test. | +| `MOSAIC_OTEL_LOGS_ENABLED` | `true` | Ships log records to the collector as well as stdout. Stdout logging is never affected. | | `DATABASE_URL` | none; required | PostgreSQL connection URL. It is parsed but never logged. | | `DATABASE_MAX_CONNECTIONS` | `10` | Maximum pgx pool connections. | | `DATABASE_MIN_CONNECTIONS` | `2` | Minimum pgx pool connections; cannot exceed the maximum. | diff --git a/docs/backend/operations/observability.md b/docs/backend/operations/observability.md index f48f79cc..9d251593 100644 --- a/docs/backend/operations/observability.md +++ b/docs/backend/operations/observability.md @@ -1,8 +1,74 @@ # Observability -Mosaic exports traces and metrics over OTLP/HTTP when +Mosaic exports traces, metrics, and logs over OTLP when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. With no endpoint configured, Mosaic still -records spans and metrics in-process and simply does not export them. +records spans and metrics in-process and simply does not export them, and logs +go to stdout only. + +`OTEL_EXPORTER_OTLP_PROTOCOL` selects the transport: `http/protobuf` (the +default, conventionally port 4318) or `grpc` (conventionally port 4317). Both +carry identical payloads, so the choice follows what the collector and the +network path in front of it accept — HTTP survives proxies and ingress that do +not speak HTTP/2, while gRPC is cheaper on a direct connection to a sidecar or +in-cluster collector. All three signals use the same transport. The +endpoint scheme still decides encryption: `https://` for TLS, `http://` for +plaintext, under either protocol. An unsupported value is rejected at startup +validation with every other configuration problem. + +## Logs + +Logs go to two places at once. Every record is written to stdout as JSON — the +stream `docker logs` and `kubectl logs` show, and the record of truth when the +collector is unreachable — and the same record is exported over OTLP, so logs +are readable next to the traces and metrics they belong to without opening a +terminal. + +`MOSAIC_OTEL_LOGS_ENABLED=false` turns off the export half. Stdout logging is +never affected by it, and neither are traces or metrics: log volume is usually +the expensive signal at a vendor, so it can be dropped without giving up +tracing. Export is on by default and requires an endpoint — with no collector +configured there is nothing to turn off. + +The exported record carries the full structured line, not just level and +message: `MOSAIC_LOG_LEVEL` filtering applies first, the zerolog level becomes +the OTLP severity, the message becomes the record body, and every remaining +field becomes a typed attribute. Export is asynchronous and batched, so a slow +or unreachable collector cannot slow down the request that produced the line; +the batch is flushed at shutdown within `MOSAIC_TELEMETRY_SHUTDOWN_TIMEOUT`. + +OpenTelemetry's own SDK errors are deliberately logged to stdout only. Sending +them through the exporting logger would feed export failures back into the +exporter that produced them. + +## Collector Authentication And Transport Security + +`OTEL_EXPORTER_OTLP_HEADERS` carries per-request export headers in the +OpenTelemetry format — `key1=value1,key2=value2`, values percent-encoded — and +is where a hosted collector's API key or bearer token goes: + +``` +OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer%20 +``` + +The same headers go to every exporter. **Header values +are secrets.** They are never logged and never echoed in a startup validation +problem, which reports only the variable name. Credentials embedded in +`OTEL_EXPORTER_OTLP_ENDPOINT` are rejected — they would reach logs through URL +strings — so put them here instead. + +`MOSAIC_OTEL_EXPORTER_TLS_SKIP_VERIFY=true` accepts a collector certificate +without verifying it, for a private CA or a self-signed certificate. It applies +to `https://` endpoints only; on a plaintext endpoint it is rejected as a +configuration mistake rather than silently ignored, because it usually means the +operator believed the connection was encrypted. + +Outside development and test, Mosaic refuses at startup to export over a +connection that is neither encrypted nor verified — a plaintext endpoint, or an +unverified certificate. Both are credential-disclosure risks once headers are +set, and telemetry-tampering risks even without them. +`MOSAIC_OTEL_EXPORTER_ALLOW_INSECURE=true` is the explicit acknowledgement that +lifts the refusal, for a collector reached over a trusted private network such +as a sidecar or in-cluster daemonset. **Telemetry export failure never stops Mosaic.** A collector that is down or slow degrades observability, not availability. Telemetry flush at shutdown has its own diff --git a/docs/support.md b/docs/support.md index 978b7249..5520e409 100644 --- a/docs/support.md +++ b/docs/support.md @@ -49,7 +49,7 @@ Include: - **Migration state**: `migrate status` and `migrate preflight` report the current and pending schema state. - **Observability**: with `OTEL_EXPORTER_OTLP_ENDPOINT` set, the API and - worker export traces and metrics; see + worker export traces, metrics, and logs (logs also stay on stdout); see [docs/backend/operations/observability.md](backend/operations/observability.md). ## Known limitations From 09a212fd94a76bf9bc7578f955625ec9bc3d88b4 Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Thu, 30 Jul 2026 13:34:21 +0100 Subject: [PATCH 03/25] refractor: restructure the route files --- ...d.test.ts => -_hosted.route-guard.test.ts} | 4 +- .../src/routes/_hosted/organizations/new.tsx | 7 --- .../$organizationId/index.tsx | 4 +- .../$organizationId/members.tsx | 2 +- .../$environmentKey/analytics}/$surface.tsx | 2 +- .../$projectId/env/$environmentKey}/apps.tsx | 2 +- .../billing/connections/$credentialId.tsx | 2 +- .../billing/connections/index.tsx | 2 +- .../billing}/customers/$customerId.tsx | 7 ++- .../billing}/customers/index.tsx | 12 +++-- .../env/$environmentKey/billing}/health.tsx | 7 ++- .../identity-conflicts/$conflictId.tsx | 7 ++- .../billing}/identity-conflicts/index.tsx | 8 ++- .../billing/migrations/$programId.tsx | 52 +++++++++++++++++++ .../billing/migrations/index.tsx | 16 ++++++ .../billing}/projection-health.tsx | 7 ++- .../billing}/quarantine/$recordId.tsx | 7 ++- .../billing}/quarantine/index.tsx | 8 ++- .../billing}/reconciliation/$runId.tsx | 7 ++- .../billing}/reconciliation/index.tsx | 8 ++- .../env/$environmentKey/billing}/restores.tsx | 8 ++- .../billing}/subscriptions/$instanceId.tsx | 8 ++- .../billing}/transactions/$factId.tsx | 7 ++- .../billing}/transactions/index.tsx | 8 ++- .../catalog/entitlements/$entitlementId.tsx | 2 +- .../catalog/entitlements/index.tsx | 2 +- .../catalog/grant-versions.tsx | 2 +- .../catalog/plans/$planId.tsx | 2 +- .../$environmentKey}/catalog/plans/index.tsx | 2 +- .../catalog/products/$productId.tsx | 2 +- .../catalog/products/index.tsx | 2 +- .../$environmentKey}/catalog/providers.tsx | 2 +- .../catalog/providers/$connectionId.tsx | 2 +- .../$projectId/env/$environmentKey}/index.tsx | 2 +- .../$environmentKey/monetization}/assets.tsx | 8 ++- .../experiments/$experimentId.tsx | 2 +- .../monetization/experiments/index.tsx} | 2 +- .../monetization}/experiments/new.tsx | 2 +- .../monetization}/paywalls/$paywallId.tsx | 7 ++- .../monetization/paywalls/index.tsx} | 7 ++- .../monetization}/placements/$placementId.tsx | 2 +- .../monetization/placements/index.tsx} | 7 ++- .../monetization}/releases.tsx | 7 ++- .../$environmentKey}/settings/api-keys.tsx | 2 +- .../settings/environments.tsx | 2 +- .../$organizationId/projects/new.tsx | 2 +- .../dashboard/src/routes/_hosted/orgs/new.tsx | 7 +++ .../$environmentId/$paywallId/$draftId.tsx | 2 +- .../{studio.tsx => studio/index.tsx} | 2 +- 49 files changed, 208 insertions(+), 75 deletions(-) rename apps/dashboard/src/routes/{_hosted.route-guard.test.ts => -_hosted.route-guard.test.ts} (95%) delete mode 100644 apps/dashboard/src/routes/_hosted/organizations/new.tsx rename apps/dashboard/src/routes/_hosted/{organizations => orgs}/$organizationId/index.tsx (83%) rename apps/dashboard/src/routes/_hosted/{organizations => orgs}/$organizationId/members.tsx (82%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/analytics/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics}/$surface.tsx (90%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/apps.tsx (86%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/billing/connections/$credentialId.tsx (85%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/billing/connections/index.tsx (84%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/customers/$customerId.tsx (60%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/customers/index.tsx (75%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/health.tsx (61%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/identity-conflicts/$conflictId.tsx (61%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/identity-conflicts/index.tsx (74%) create mode 100644 apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId.tsx create mode 100644 apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/projection-health.tsx (61%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/quarantine/$recordId.tsx (61%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/quarantine/index.tsx (83%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/reconciliation/$runId.tsx (62%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/reconciliation/index.tsx (76%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/restores.tsx (75%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/subscriptions/$instanceId.tsx (75%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/transactions/$factId.tsx (62%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/billing/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/billing}/transactions/index.tsx (76%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/entitlements/$entitlementId.tsx (81%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/entitlements/index.tsx (84%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/grant-versions.tsx (93%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/plans/$planId.tsx (80%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/plans/index.tsx (84%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/products/$productId.tsx (93%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/products/index.tsx (95%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/providers.tsx (93%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/catalog/providers/$connectionId.tsx (83%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/index.tsx (81%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization}/assets.tsx (71%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization}/experiments/$experimentId.tsx (72%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments.tsx => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/index.tsx} (79%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization}/experiments/new.tsx (73%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization}/paywalls/$paywallId.tsx (53%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls.tsx => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/index.tsx} (58%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization}/placements/$placementId.tsx (75%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements.tsx => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/index.tsx} (59%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId/monetization/$environmentId => orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization}/releases.tsx (59%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/settings/api-keys.tsx (90%) rename apps/dashboard/src/routes/_hosted/{organizations/$organizationId/projects/$projectId => orgs/$organizationId/projects/$projectId/env/$environmentKey}/settings/environments.tsx (84%) rename apps/dashboard/src/routes/_hosted/{organizations => orgs}/$organizationId/projects/new.tsx (77%) create mode 100644 apps/dashboard/src/routes/_hosted/orgs/new.tsx rename apps/dashboard/src/routes/_studio_layout/{studio-hosted => studio}/$organizationId/$projectId/$environmentId/$paywallId/$draftId.tsx (90%) rename apps/dashboard/src/routes/_studio_layout/{studio.tsx => studio/index.tsx} (80%) diff --git a/apps/dashboard/src/routes/_hosted.route-guard.test.ts b/apps/dashboard/src/routes/-_hosted.route-guard.test.ts similarity index 95% rename from apps/dashboard/src/routes/_hosted.route-guard.test.ts rename to apps/dashboard/src/routes/-_hosted.route-guard.test.ts index 4bcd4eb2..9aa700eb 100644 --- a/apps/dashboard/src/routes/_hosted.route-guard.test.ts +++ b/apps/dashboard/src/routes/-_hosted.route-guard.test.ts @@ -63,12 +63,12 @@ async function runGuard(respond: () => Promise, href: string) { describe("hosted route guard", () => { it("redirects an unauthenticated visitor to sign-in with the requested path", async () => { - const thrown = await runGuard(unauthenticated, "/organizations/org_01/projects/project_01") + const thrown = await runGuard(unauthenticated, "/orgs/org_01/projects/project_01") expect(isRedirect(thrown)).toBe(true) expect(thrown).toMatchObject({ options: { - search: { returnTo: "/organizations/org_01/projects/project_01" }, + search: { returnTo: "/orgs/org_01/projects/project_01" }, to: "/login", }, }) diff --git a/apps/dashboard/src/routes/_hosted/organizations/new.tsx b/apps/dashboard/src/routes/_hosted/organizations/new.tsx deleted file mode 100644 index 78cd1484..00000000 --- a/apps/dashboard/src/routes/_hosted/organizations/new.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router" - -import { CreateOrganizationPage } from "@/features/organizations/components/create-organization-page" - -export const Route = createFileRoute("/_hosted/organizations/new")({ - component: CreateOrganizationPage, -}) diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/index.tsx similarity index 83% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/index.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/index.tsx index 3f2e815b..d9233696 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/index.tsx @@ -2,13 +2,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" -import { OrganizationOverviewPage } from "@/features/organizations/components/organization-overview-page" +import { OrganizationOverviewPage } from "@/features/orgs/components/organization-overview-page" interface OrganizationSearch { projectStatus?: "archived" } -export const Route = createFileRoute("/_hosted/organizations/$organizationId/")({ +export const Route = createFileRoute("/_hosted/orgs/$organizationId/")({ component: OrganizationRoute, pendingComponent: RoutePendingState, validateSearch: (search: Record): OrganizationSearch => ({ diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/members.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/members.tsx similarity index 82% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/members.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/members.tsx index 89c36de8..3aac7a57 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/members.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/members.tsx @@ -4,7 +4,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { MembersPage } from "@/features/members/components/members-page" -export const Route = createFileRoute("/_hosted/organizations/$organizationId/members")({ +export const Route = createFileRoute("/_hosted/orgs/$organizationId/members")({ component: OrganizationMembersRoute, pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface.tsx similarity index 90% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface.tsx index cc732faf..335f72be 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface.tsx @@ -7,7 +7,7 @@ import { analyticsSurfaces, type AnalyticsSurface } from "@/features/analytics/t import { parseAnalyticsFilters } from "@/features/analytics/types/analytics-filters" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface", )({ validateSearch: parseAnalyticsFilters, component: RouteComponent, diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/apps.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps.tsx similarity index 86% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/apps.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps.tsx index 6cfaf632..aa0492e1 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/apps.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps.tsx @@ -5,7 +5,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { ApplicationsPage } from "@/features/projects/components/applications-page" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/apps", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps", )({ component: ProjectAppsRoute, pendingComponent: RoutePendingState, diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId.tsx similarity index 85% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId.tsx index 8f2731a5..1c1a723b 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId.tsx @@ -5,7 +5,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { StoreConnectionDetailPage } from "@/features/store-connections/components/store-connection-detail-page" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId", )({ component: BillingConnectionDetailRoute, pendingComponent: RoutePendingState, diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index.tsx similarity index 84% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/index.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index.tsx index 4aa39844..f5a855a6 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index.tsx @@ -5,7 +5,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { StoreConnectionsPage } from "@/features/store-connections/components/store-connections-page" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/", )({ component: BillingConnectionsRoute, pendingComponent: RoutePendingState, diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId.tsx similarity index 60% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId.tsx index b31adfe0..6fcfb264 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId.tsx @@ -3,16 +3,19 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { CustomerDetailPage } from "@/features/billing-customers/components/customer-detail-page" +import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId", )({ component: CustomerDetailRoute, pendingComponent: RoutePendingState, }) function CustomerDetailRoute() { - const { customerId, environmentId, organizationId, projectId } = Route.useParams() + const { customerId, organizationId, projectId } = Route.useParams() + const { environmentId, fallback } = useRouteEnvironment() + if (!environmentId) return fallback return ( { void navigate({ - params: { customerId, environmentId, organizationId, projectId }, + params: (prev) => ({ ...prev, customerId }), search: {}, - to: "/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId", + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId", }) }} onFiltersChange={(filters) => { diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health.tsx similarity index 61% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health.tsx index bb6d15c5..dbbc68d3 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health.tsx @@ -3,16 +3,19 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { BillingHealthPage } from "@/features/billing-operations/components/billing-health-page" +import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health", )({ component: BillingHealthRoute, pendingComponent: RoutePendingState, }) function BillingHealthRoute() { - const { environmentId, organizationId, projectId } = Route.useParams() + const { organizationId, projectId } = Route.useParams() + const { environmentId, fallback } = useRouteEnvironment() + if (!environmentId) return fallback return ( ): MigrationSearch => ({ + batchId: typeof search.batchId === "string" ? search.batchId : undefined, + classification: typeof search.classification === "string" ? search.classification : "all", + runJobId: typeof search.runJobId === "string" ? search.runJobId : undefined, + tab: tabs.includes(search.tab as Tab) ? (search.tab as Tab) : "overview", + }), +}) + +function MigrationProgramRoute() { + const navigate = useNavigate({ from: Route.fullPath }) + const { organizationId, programId, projectId } = Route.useParams() + const search = Route.useSearch() + return ( + + void navigate({ search: (previous) => ({ ...previous, ...next }), replace: true }) + } + organizationId={organizationId} + programId={programId} + projectId={projectId} + /> + ) +} diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx new file mode 100644 index 00000000..240dea58 --- /dev/null +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router" + +import { RoutePendingState } from "@/components/feedback/route-feedback" +import { MigrationProgramsPage } from "@/features/billing-migrations/components/migration-programs-page" + +export const Route = createFileRoute( + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/", +)({ + component: MigrationProgramsRoute, + pendingComponent: RoutePendingState, +}) + +function MigrationProgramsRoute() { + const { organizationId, projectId } = Route.useParams() + return +} diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health.tsx similarity index 61% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health.tsx index ebc5dfe5..59a5b059 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health.tsx @@ -3,16 +3,19 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { ProjectionHealthPage } from "@/features/billing-projection/components/projection-health-page" +import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health", )({ component: ProjectionHealthRoute, pendingComponent: RoutePendingState, }) function ProjectionHealthRoute() { - const { environmentId, organizationId, projectId } = Route.useParams() + const { organizationId, projectId } = Route.useParams() + const { environmentId, fallback } = useRouteEnvironment() + if (!environmentId) return fallback return ( ): ProductReadinessSearch => { diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index.tsx similarity index 95% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/index.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index.tsx index 86fc64b8..18c2c89f 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index.tsx @@ -22,7 +22,7 @@ function isProductType(value: unknown): value is ProductFilters["type"] { } export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/", )({ component: CatalogProductsRoute, pendingComponent: RoutePendingState, diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers.tsx similarity index 93% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers.tsx index 6a4f4790..34871287 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers.tsx @@ -11,7 +11,7 @@ interface ProviderConnectionsSearch { } export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers", )({ component: ProjectProviderConnectionsRoute, pendingComponent: RoutePendingState, diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId.tsx similarity index 83% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId.tsx index e3bf0da0..ed4ef035 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId.tsx @@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import { ProviderConnectionDetailPage } from "@/features/provider-connections/components/provider-connection-detail-page" export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId", )({ component: ProjectProviderConnectionDetailRoute, }) diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index.tsx similarity index 81% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/index.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index.tsx index 6519e3f6..b22fef9b 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index.tsx @@ -4,7 +4,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { ProjectOverviewPage } from "@/features/projects/components/project-overview-page" -export const Route = createFileRoute("/_hosted/organizations/$organizationId/projects/$projectId/")( +export const Route = createFileRoute("/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/")( { component: ProjectRoute, pendingComponent: RoutePendingState, diff --git a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets.tsx similarity index 71% rename from apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets.tsx rename to apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets.tsx index 2298f80c..5d589435 100644 --- a/apps/dashboard/src/routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets.tsx @@ -4,13 +4,14 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { AssetsPage } from "@/features/assets/components/assets-page" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" +import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" interface AssetsRouteSearch { returnTo?: string } export const Route = createFileRoute( - "/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets", + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets", )({ component: RouteComponent, pendingComponent: RoutePendingState, @@ -21,8 +22,11 @@ export const Route = createFileRoute( }) function RouteComponent() { - const { environmentId, organizationId, projectId } = Route.useParams() + const { organizationId, projectId } = Route.useParams() + const { environmentId, fallback } = useRouteEnvironment() const { returnTo } = Route.useSearch() + if (!environmentId) return fallback + return ( ): HostedStudioSearch => ({ diff --git a/apps/dashboard/src/routes/_studio_layout/studio.tsx b/apps/dashboard/src/routes/_studio_layout/studio/index.tsx similarity index 80% rename from apps/dashboard/src/routes/_studio_layout/studio.tsx rename to apps/dashboard/src/routes/_studio_layout/studio/index.tsx index 07ea3b54..9472dcbe 100644 --- a/apps/dashboard/src/routes/_studio_layout/studio.tsx +++ b/apps/dashboard/src/routes/_studio_layout/studio/index.tsx @@ -5,6 +5,6 @@ const PaywallEditorWorkspace = lazyRouteComponent( "PaywallEditorWorkspace", ) -export const Route = createFileRoute("/_studio_layout/studio")({ +export const Route = createFileRoute("/_studio_layout/studio/")({ component: PaywallEditorWorkspace, }) From e4c0d0a05dc9f8417c4576e21928d2211c9d354d Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Thu, 30 Jul 2026 13:45:49 +0100 Subject: [PATCH 04/25] fix: update all broken link from route restructuring --- .../components/environment-switcher.test.tsx | 262 ++++++++++++++++ .../components/environment-switcher.tsx | 144 +++++++++ .../hooks/use-active-environment.ts | 119 ++++++++ .../hooks/use-route-environment.tsx | 42 +++ .../types/active-environment-store.test.ts | 93 ++++++ .../types/active-environment-store.ts | 80 +++++ .../types/environment-alias.test.ts | 58 ++++ .../environments/types/environment-alias.ts | 53 ++++ .../types/environment-path.test.ts | 64 ++++ .../environments/types/environment-path.ts | 30 ++ .../components/cloud-workspace-shell.tsx | 252 ---------------- .../components/organization-switcher.test.tsx | 133 --------- .../components/organization-switcher.tsx | 167 ----------- .../components/cloud-workspace-shell.test.tsx | 109 +++++++ .../orgs/components/cloud-workspace-shell.tsx | 279 ++++++++++++++++++ .../components/create-organization-page.tsx | 6 +- .../components/organization-overview-page.tsx | 14 +- .../components/organization-switcher.test.tsx | 243 +++++++++++++++ .../orgs/components/organization-switcher.tsx | 274 +++++++++++++++++ .../components/scope-mismatch-recovery.tsx | 8 +- .../workspace-entry-redirect.test.tsx | 163 ++++++++++ .../components/workspace-entry-redirect.tsx | 45 +++ .../components/workspace-home.tsx | 10 +- .../components/workspace-page.tsx | 0 .../mutations/organization-mutations.ts | 2 +- .../queries/organizations-query.ts | 0 .../orgs/queries/workspace-bootstrap-query.ts | 28 ++ .../types/nested-scope.test.ts | 2 +- .../types/nested-scope.ts | 0 .../orgs/types/workspace-entry.test.ts | 101 +++++++ .../features/orgs/types/workspace-entry.ts | 89 ++++++ .../types/workspace-navigation.test.ts | 14 +- .../types/workspace-navigation.ts | 30 +- 33 files changed, 2315 insertions(+), 599 deletions(-) create mode 100644 apps/dashboard/src/features/environments/components/environment-switcher.test.tsx create mode 100644 apps/dashboard/src/features/environments/components/environment-switcher.tsx create mode 100644 apps/dashboard/src/features/environments/hooks/use-active-environment.ts create mode 100644 apps/dashboard/src/features/environments/hooks/use-route-environment.tsx create mode 100644 apps/dashboard/src/features/environments/types/active-environment-store.test.ts create mode 100644 apps/dashboard/src/features/environments/types/active-environment-store.ts create mode 100644 apps/dashboard/src/features/environments/types/environment-alias.test.ts create mode 100644 apps/dashboard/src/features/environments/types/environment-alias.ts create mode 100644 apps/dashboard/src/features/environments/types/environment-path.test.ts create mode 100644 apps/dashboard/src/features/environments/types/environment-path.ts delete mode 100644 apps/dashboard/src/features/organizations/components/cloud-workspace-shell.tsx delete mode 100644 apps/dashboard/src/features/organizations/components/organization-switcher.test.tsx delete mode 100644 apps/dashboard/src/features/organizations/components/organization-switcher.tsx create mode 100644 apps/dashboard/src/features/orgs/components/cloud-workspace-shell.test.tsx create mode 100644 apps/dashboard/src/features/orgs/components/cloud-workspace-shell.tsx rename apps/dashboard/src/features/{organizations => orgs}/components/create-organization-page.tsx (94%) rename apps/dashboard/src/features/{organizations => orgs}/components/organization-overview-page.tsx (88%) create mode 100644 apps/dashboard/src/features/orgs/components/organization-switcher.test.tsx create mode 100644 apps/dashboard/src/features/orgs/components/organization-switcher.tsx rename apps/dashboard/src/features/{organizations => orgs}/components/scope-mismatch-recovery.tsx (86%) create mode 100644 apps/dashboard/src/features/orgs/components/workspace-entry-redirect.test.tsx create mode 100644 apps/dashboard/src/features/orgs/components/workspace-entry-redirect.tsx rename apps/dashboard/src/features/{organizations => orgs}/components/workspace-home.tsx (87%) rename apps/dashboard/src/features/{organizations => orgs}/components/workspace-page.tsx (100%) rename apps/dashboard/src/features/{organizations => orgs}/mutations/organization-mutations.ts (88%) rename apps/dashboard/src/features/{organizations => orgs}/queries/organizations-query.ts (100%) create mode 100644 apps/dashboard/src/features/orgs/queries/workspace-bootstrap-query.ts rename apps/dashboard/src/features/{organizations => orgs}/types/nested-scope.test.ts (93%) rename apps/dashboard/src/features/{organizations => orgs}/types/nested-scope.ts (100%) create mode 100644 apps/dashboard/src/features/orgs/types/workspace-entry.test.ts create mode 100644 apps/dashboard/src/features/orgs/types/workspace-entry.ts rename apps/dashboard/src/features/{organizations => orgs}/types/workspace-navigation.test.ts (74%) rename apps/dashboard/src/features/{organizations => orgs}/types/workspace-navigation.ts (52%) diff --git a/apps/dashboard/src/features/environments/components/environment-switcher.test.tsx b/apps/dashboard/src/features/environments/components/environment-switcher.test.tsx new file mode 100644 index 00000000..520b8882 --- /dev/null +++ b/apps/dashboard/src/features/environments/components/environment-switcher.test.tsx @@ -0,0 +1,262 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from "@tanstack/react-router" +import { fireEvent, render, screen } from "@testing-library/react" +import { afterEach, beforeAll, describe, expect, it } from "vitest" + +import { resetActiveEnvironmentStore } from "@/features/environments/types/active-environment-store" + +import { SidebarProvider } from "@/components/ui/sidebar" +import type { Environment } from "@/generated/api" +import { EnvironmentSwitcher } from "@/features/environments/components/environment-switcher" +import { environmentKeys } from "@/features/environments/queries/environments-query" +import { ApiError } from "@/lib/api/errors" + +// The sidebar reads a media query jsdom does not implement. +beforeAll(() => { + if (typeof window.matchMedia === "function") return + window.matchMedia = (query: string) => + ({ + addEventListener: () => {}, + addListener: () => {}, + dispatchEvent: () => false, + matches: false, + media: query, + onchange: null, + removeEventListener: () => {}, + removeListener: () => {}, + }) as MediaQueryList +}) + +const timestamps = { createdAt: "2026-07-30T00:00:00Z", updatedAt: "2026-07-30T00:00:00Z" } + +function environment(id: string, name: string, mode: Environment["mode"]): Environment { + return { ...timestamps, id, key: name.toLowerCase(), mode, name, projectId: "prj_01" } +} + +const development = environment("env_dev", "Development", "development") +const production = environment("env_prod", "Production", "production") + +const PATHS = [ + "/orgs/$organizationId", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId", +] as const + +function renderSwitcher(queryClient: QueryClient, pathname: string) { + const rootRoute = createRootRoute() + const routeTree = rootRoute.addChildren( + PATHS.map((path) => + createRoute({ + component: EnvironmentSwitcher, + getParentRoute: () => rootRoute, + path, + }), + ), + ) + + const router = createRouter({ + history: createMemoryHistory({ initialEntries: [pathname] }), + routeTree, + }) + + return render( + + + + + , + ) +} + +function seededClient(seed: (client: QueryClient) => void) { + // staleTime keeps a remount from refetching seeded data against a server that + // is not there, which would land the switcher in its error branch. + const client = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: Infinity } }, + }) + seed(client) + return client +} + +function withEnvironments(items: Environment[]) { + return (client: QueryClient) => { + client.setQueryData(environmentKeys.list("prj_01"), { items, page: { nextCursor: "" } }) + } +} + +afterEach(() => { + // The remembered Environment outlives a render by design; leaking it between + // cases would let one test decide another's default. + resetActiveEnvironmentStore() + window.localStorage.clear() +}) + +/** + * One switcher replaces the per-page selects, so it has to hold the invariant they + * each held locally: switching keeps the operator on the surface they were reading, + * search included. It also has to be honest about whether the Environment it names + * is in the address, because only then will a shared link reproduce it. + */ +describe("EnvironmentSwitcher", () => { + it("switches Environment without leaving the surface", async () => { + const client = seededClient(withEnvironments([development, production])) + + renderSwitcher(client, "/orgs/org_01/projects/prj_01/monetization/env_dev/paywalls") + + fireEvent.click( + await screen.findByRole("button", { name: /Current environment: Development, development/ }), + ) + + expect(await screen.findByRole("menuitem", { name: /Production/ })).toHaveAttribute( + "href", + "/orgs/org_01/projects/prj_01/monetization/env_prod/paywalls", + ) + }) + + it("preserves the deeper surface when switching", async () => { + const client = seededClient(withEnvironments([development, production])) + + renderSwitcher(client, "/orgs/org_01/projects/prj_01/analytics/env_dev/funnel") + fireEvent.click(await screen.findByRole("button", { name: /Current environment/ })) + + expect(await screen.findByRole("menuitem", { name: /Production/ })).toHaveAttribute( + "href", + "/orgs/org_01/projects/prj_01/analytics/env_prod/funnel", + ) + }) + + it("marks the Environment already in scope", async () => { + const client = seededClient(withEnvironments([development, production])) + + renderSwitcher(client, "/orgs/org_01/projects/prj_01/billing/env_prod/health") + fireEvent.click(await screen.findByRole("button", { name: /Current environment: Production/ })) + + expect(await screen.findByRole("menuitem", { name: /Production/ })).toHaveAttribute( + "aria-current", + "page", + ) + expect(screen.getByRole("menuitem", { name: /Development/ })).not.toHaveAttribute( + "aria-current", + ) + }) + + it("carries the current search across an in-place switch", async () => { + const client = seededClient(withEnvironments([development, production])) + + renderSwitcher( + client, + "/orgs/org_01/projects/prj_01/analytics/env_dev/funnel?window=28d", + ) + fireEvent.click(await screen.findByRole("button", { name: /Current environment/ })) + + // Analytics filters live in search; dropping them would silently reset the + // view the operator had set up. + expect(await screen.findByRole("menuitem", { name: /Production/ })).toHaveAttribute( + "href", + "/orgs/org_01/projects/prj_01/analytics/env_prod/funnel?window=28d", + ) + }) + + it("still answers the question on a surface that carries no Environment", async () => { + const client = seededClient(withEnvironments([development, production])) + + renderSwitcher(client, "/orgs/org_01/projects/prj_01/apps") + + // The Project's first Environment stands in until a choice is made, and the + // trigger admits the address does not name it. + const trigger = await screen.findByRole("button", { name: /Current environment: Development/ }) + expect(trigger).toHaveTextContent("not in this page's address") + + fireEvent.click(trigger) + // Nothing to navigate to, so selecting records the choice instead of moving + // the operator to a page they did not ask for. + expect(await screen.findByRole("menuitem", { name: /Production/ })).not.toHaveAttribute("href") + }) + + it("carries a choice made off-path onto the next Environment surface", async () => { + const client = seededClient(withEnvironments([development, production])) + + const off = renderSwitcher(client, "/orgs/org_01/projects/prj_01/apps") + fireEvent.click(await screen.findByRole("button", { name: /Current environment/ })) + fireEvent.click(await screen.findByRole("menuitem", { name: /Production/ })) + off.unmount() + + // This is the point of the store: the choice survives the page that could not + // express it in its address. + renderSwitcher(client, "/orgs/org_01/projects/prj_01/catalog/products") + expect( + await screen.findByRole("button", { name: /Current environment: Production/ }), + ).toBeInTheDocument() + }) + + it("lets the address outrank the remembered choice", async () => { + const client = seededClient(withEnvironments([development, production])) + + const off = renderSwitcher(client, "/orgs/org_01/projects/prj_01/apps") + fireEvent.click(await screen.findByRole("button", { name: /Current environment/ })) + fireEvent.click(await screen.findByRole("menuitem", { name: /Production/ })) + off.unmount() + + // Otherwise a shared link would render whatever the recipient last picked. + renderSwitcher(client, "/orgs/org_01/projects/prj_01/billing/env_dev/health") + expect( + await screen.findByRole("button", { name: /Current environment: Development/ }), + ).toBeInTheDocument() + }) + + it("is absent above a Project, where Environments do not apply", () => { + const client = seededClient(withEnvironments([development, production])) + + renderSwitcher(client, "/orgs/org_01") + + expect(screen.queryByRole("button", { name: /environment/i })).toBeNull() + }) + + it("does not mistake a literal path segment for an Environment", async () => { + const client = seededClient(withEnvironments([development, production])) + + renderSwitcher(client, "/orgs/org_01/projects/prj_01/billing/connections/cred_01") + + // "connections" reads as the Environment slot to the scope parser. Rewriting + // it in place would build a route that does not exist, so this surface counts + // as carrying no Environment and offers no link. + const trigger = await screen.findByRole("button", { name: /Current environment/ }) + expect(trigger).toHaveTextContent("not in this page's address") + fireEvent.click(trigger) + expect(await screen.findByRole("menuitem", { name: /Production/ })).not.toHaveAttribute("href") + }) + + it("offers a retry when the Environment list cannot be read", async () => { + const client = seededClient((queryClient) => { + queryClient.setQueryDefaults(environmentKeys.list("prj_01"), { + queryFn: () => + Promise.reject( + new ApiError("boom", { + code: "internal_error", + correlationId: "request_test", + retryable: true, + status: 500, + }), + ), + retry: false, + }) + }) + + renderSwitcher(client, "/orgs/org_01/projects/prj_01/monetization/env_dev/paywalls") + + expect( + await screen.findByRole("button", { name: "Retry loading environments" }), + ).toBeInTheDocument() + }) +}) diff --git a/apps/dashboard/src/features/environments/components/environment-switcher.tsx b/apps/dashboard/src/features/environments/components/environment-switcher.tsx new file mode 100644 index 00000000..f272be6e --- /dev/null +++ b/apps/dashboard/src/features/environments/components/environment-switcher.tsx @@ -0,0 +1,144 @@ +import type * as React from "react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" +import { CaretUpDownIcon } from "@phosphor-icons/react/dist/ssr/CaretUpDown" +import { CheckIcon } from "@phosphor-icons/react/dist/ssr/Check" +import { StackIcon } from "@phosphor-icons/react/dist/ssr/Stack" +import { Link } from "@tanstack/react-router" + +import { Button } from "@/components/ui/button" +import { useActiveEnvironment } from "@/features/environments/hooks/use-active-environment" +import { describeApiError } from "@/lib/api/errors" + +// Base UI exposes the trigger width as --anchor-width on positioned popups. +const DROPDOWN_CLASSNAMES = "w-(--anchor-width) min-w-56 rounded" + +function SwitcherFrame({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} + +/** + * The one place an Environment is chosen. It is present on every Project surface, + * because the choice is now readable from anywhere through useActiveEnvironment + * rather than only where the route spells it out. + * + * Selecting always records the choice. On a route that carries the Environment it + * also moves the URL, keeping the surface and its search intact, so the link stays + * an honest description of what is on screen. + */ +export function EnvironmentSwitcher() { + const { isMobile } = useSidebar() + const { active, items, pathFor, projectId, query, remember, select } = useActiveEnvironment() + + if (!projectId) return null + + if (query.isPending) { + return ( + + + + + Loading environments… + + + + ) + } + + if (query.isError) { + return ( + +
+

+ {describeApiError(query.error).description} +

+ +
+
+ ) + } + + if (!active) return null + + return ( + + + + +
+ +
+ {active.name} +
+
+ + + } + /> + + + + Environments + + + {items.map((environment) => { + const target = pathFor(environment.id) + + return ( + remember(environment.id) : () => select(environment.id)} + render={target ? : undefined} + > + + {environment.id === active.id ? ( + + ) : null} + + {environment.name} + + {environment.mode} + + + ) + })} + +
+
+
+ ) +} diff --git a/apps/dashboard/src/features/environments/hooks/use-active-environment.ts b/apps/dashboard/src/features/environments/hooks/use-active-environment.ts new file mode 100644 index 00000000..c98d2c45 --- /dev/null +++ b/apps/dashboard/src/features/environments/hooks/use-active-environment.ts @@ -0,0 +1,119 @@ +import * as React from "react" + +import { useNavigate, useRouterState } from "@tanstack/react-router" +import { useQuery } from "@tanstack/react-query" + +import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" +import { + rememberEnvironmentId, + rememberedEnvironmentId, + subscribeToActiveEnvironment, +} from "@/features/environments/types/active-environment-store" +import { + environmentAlias, + environmentForAlias, +} from "@/features/environments/types/environment-alias" +import { switchEnvironmentPath } from "@/features/environments/types/environment-path" +import { readWorkspaceScope } from "@/features/orgs/types/workspace-navigation" + +/** + * The single place to ask "which Environment am I working in?". Pages and queries + * read it here instead of threading an environmentId prop down from a route. + * + * Precedence is URL, then the remembered choice, then the Project's first + * Environment. The URL winning is what keeps a shared link honest; the remembered + * choice is what makes Catalog, Apps, and other Environment-less surfaces able to + * answer the question at all. + * + * `inPath` tells a caller whether the current route actually carries the + * Environment. Anything that must not act on an inherited Environment — provider + * and purchase setup, which refuse to default — should require it. + */ +export function useActiveEnvironment() { + const navigate = useNavigate() + const pathname = useRouterState({ select: (state) => state.location.pathname }) + const scope = readWorkspaceScope(pathname) + const projectId = scope.projectId ?? "" + + const query = useQuery({ + ...environmentsQueryOptions(projectId), + enabled: projectId.length > 0, + }) + // Stable identity: pathFor closes over this, and a fresh array every render would + // rebuild the callback and every memo downstream of it. + const items = React.useMemo(() => query.data?.items ?? [], [query.data?.items]) + + const rememberedId = React.useSyncExternalStore( + subscribeToActiveEnvironment, + () => rememberedEnvironmentId(projectId), + () => undefined, + ) + + // This is the mapping point: the alias in the address resolves to the Environment + // here, so no page has to know that the API wants an id. Candidates are validated + // against the Project's own Environments, so neither a stale remembered id nor a + // malformed segment can be mistaken for a scope. + const fromPath = environmentForAlias(items, scope.environmentSegment) + const fromMemory = items.find((environment) => environment.id === rememberedId) + const active = fromPath ?? fromMemory ?? items[0] + + // Visiting an Environment-scoped page is itself a choice; remembering it is what + // carries the Environment onto the surfaces that have none. + React.useEffect(() => { + if (fromPath) rememberEnvironmentId(projectId, fromPath.id) + }, [projectId, fromPath]) + + /** Where this route would live under another Environment, or null if it does not name one. */ + const pathFor = React.useCallback( + (environmentId: string) => { + if (!fromPath) return null + const next = items.find((environment) => environment.id === environmentId) + if (!next) return null + + return switchEnvironmentPath(pathname, environmentAlias(fromPath), environmentAlias(next)) + }, + [fromPath, items, pathname], + ) + + /** Records the choice without navigating, for callers that render their own link. */ + const remember = React.useCallback( + (environmentId: string) => rememberEnvironmentId(projectId, environmentId), + [projectId], + ) + + const select = React.useCallback( + (environmentId: string) => { + remember(environmentId) + + // On a route that carries the Environment, the URL is the authority and has + // to move too, keeping the surface and its search intact. + const target = pathFor(environmentId) + if (target) void navigate({ search: true, to: target }) + }, + [navigate, pathFor, remember], + ) + + return { + active, + activeId: active?.id, + /** + * The Environment as an address names it. Components hold ids and routes carry + * aliases, so this is the translation in the direction links need. + */ + alias: active ? environmentAlias(active) : "", + inPath: Boolean(fromPath), + items, + /** + * Only what the address itself names. A route must resolve from this, never from + * `active`: falling back to a remembered Environment would render one page while + * the address described another. + */ + pathEnvironment: fromPath, + organizationId: scope.organizationId, + pathFor, + projectId, + query, + remember, + select, + } +} diff --git a/apps/dashboard/src/features/environments/hooks/use-route-environment.tsx b/apps/dashboard/src/features/environments/hooks/use-route-environment.tsx new file mode 100644 index 00000000..bb841a4f --- /dev/null +++ b/apps/dashboard/src/features/environments/hooks/use-route-environment.tsx @@ -0,0 +1,42 @@ +import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" +import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" +import { useActiveEnvironment } from "@/features/environments/hooks/use-active-environment" + +/** + * Turns the Environment alias in the address into the id a page needs. + * + * Routes carry `prod`, `staging`, or `dev`; the API takes ids. Resolving the two + * requires the Project's Environment list, so it is asynchronous, and a route + * cannot hand an id to its page until that read lands. This is the one place that + * waits, so no page has to: + * + * const { environmentId, fallback } = useRouteEnvironment() + * if (!environmentId) return fallback + * + * `fallback` reports loading, an unreachable API, and an alias the Project has no + * Environment for, which is what a hand-edited or stale address looks like. + */ +export function useRouteEnvironment() { + const { pathEnvironment, query } = useActiveEnvironment() + + if (pathEnvironment) return { environmentId: pathEnvironment.id, fallback: undefined } + + const state = resolveHostedQueryState({ + emptyDescription: + "This address names an environment the project does not have. Choose one from the sidebar.", + emptyTitle: "Environment not found", + error: query.error, + // Resolved against the address only. Standing in a remembered Environment here + // would show one Environment while the address named another. + isEmpty: query.isSuccess, + isPending: query.isPending, + loadingDescription: "Loading the project's environments.", + onRetry: () => void query.refetch(), + permissionDescription: "Project membership is required to read this environment.", + }) + + return { + environmentId: undefined, + fallback: {null}, + } +} diff --git a/apps/dashboard/src/features/environments/types/active-environment-store.test.ts b/apps/dashboard/src/features/environments/types/active-environment-store.test.ts new file mode 100644 index 00000000..f1124dd9 --- /dev/null +++ b/apps/dashboard/src/features/environments/types/active-environment-store.test.ts @@ -0,0 +1,93 @@ +import { afterEach, describe, expect, it, vi } from "vitest" + +import { + rememberEnvironmentId, + rememberedEnvironmentId, + resetActiveEnvironmentStore, + subscribeToActiveEnvironment, +} from "@/features/environments/types/active-environment-store" + +afterEach(() => { + resetActiveEnvironmentStore() + window.localStorage.clear() + vi.restoreAllMocks() +}) + +describe("active environment store", () => { + it("keeps one value per Project", () => { + rememberEnvironmentId("prj_01", "env_prod") + rememberEnvironmentId("prj_02", "env_dev") + + expect(rememberedEnvironmentId("prj_01")).toBe("env_prod") + expect(rememberedEnvironmentId("prj_02")).toBe("env_dev") + expect(rememberedEnvironmentId("prj_unknown")).toBeUndefined() + }) + + it("returns a stable primitive so a subscribing reader does not loop", () => { + rememberEnvironmentId("prj_01", "env_prod") + + // useSyncExternalStore compares snapshots by identity; an object would make + // every read look like a change. + expect(rememberedEnvironmentId("prj_01")).toBe(rememberedEnvironmentId("prj_01")) + }) + + it("notifies readers on a change, and not on a repeat of the same value", () => { + const listener = vi.fn() + subscribeToActiveEnvironment(listener) + listener.mockClear() + + rememberEnvironmentId("prj_01", "env_prod") + expect(listener).toHaveBeenCalledTimes(1) + + rememberEnvironmentId("prj_01", "env_prod") + expect(listener).toHaveBeenCalledTimes(1) + + rememberEnvironmentId("prj_01", "env_dev") + expect(listener).toHaveBeenCalledTimes(2) + }) + + it("stops notifying after unsubscribe", () => { + const listener = vi.fn() + const unsubscribe = subscribeToActiveEnvironment(listener) + unsubscribe() + listener.mockClear() + + rememberEnvironmentId("prj_01", "env_prod") + + expect(listener).not.toHaveBeenCalled() + }) + + it("recovers the choice from storage on the first subscription", () => { + window.localStorage.setItem("mosaic.activeEnvironment.prj_01", "env_prod") + + // Hydration is deferred to subscribe, which runs in an effect: reading storage + // during render would report a value the server-rendered HTML did not have. + expect(rememberedEnvironmentId("prj_01")).toBeUndefined() + subscribeToActiveEnvironment(() => {}) + expect(rememberedEnvironmentId("prj_01")).toBe("env_prod") + }) + + it("ignores unrelated storage keys", () => { + window.localStorage.setItem("unrelated.prj_01", "env_prod") + subscribeToActiveEnvironment(() => {}) + + expect(rememberedEnvironmentId("prj_01")).toBeUndefined() + }) + + it("keeps working in memory when storage refuses the write", () => { + vi.spyOn(window.localStorage, "setItem").mockImplementation(() => { + throw new Error("quota exceeded") + }) + + expect(() => rememberEnvironmentId("prj_01", "env_prod")).not.toThrow() + expect(rememberedEnvironmentId("prj_01")).toBe("env_prod") + }) + + it("refuses a blank Project or Environment rather than storing an empty key", () => { + rememberEnvironmentId("", "env_prod") + rememberEnvironmentId("prj_01", "") + + expect(rememberedEnvironmentId("")).toBeUndefined() + expect(rememberedEnvironmentId("prj_01")).toBeUndefined() + }) +}) diff --git a/apps/dashboard/src/features/environments/types/active-environment-store.ts b/apps/dashboard/src/features/environments/types/active-environment-store.ts new file mode 100644 index 00000000..9cdd2cb4 --- /dev/null +++ b/apps/dashboard/src/features/environments/types/active-environment-store.ts @@ -0,0 +1,80 @@ +/** + * The remembered Environment per Project, held in memory so every reader sees one + * value, and mirrored to localStorage so a reload does not lose the choice. + * + * This is deliberately not the source of truth. A route that carries an + * Environment outranks it (see useActiveEnvironment), which is what keeps a shared + * link unambiguous: the recipient sees the Environment in the URL, not whichever + * one their own browser happens to remember. + */ + +const STORAGE_PREFIX = "mosaic.activeEnvironment." + +const remembered = new Map() +const listeners = new Set<() => void>() + +let hydrated = false + +function storageKey(projectId: string) { + return `${STORAGE_PREFIX}${projectId}` +} + +function notify() { + for (const listener of listeners) listener() +} + +/** + * Reads persisted choices once, on the first subscription. Subscription happens in + * an effect, so this never runs during render or hydration: a getSnapshot that + * touched storage would report a value the server-rendered HTML did not have. + */ +function hydrateOnce() { + if (hydrated) return + hydrated = true + if (typeof window === "undefined") return + + try { + for (let index = 0; index < window.localStorage.length; index += 1) { + const key = window.localStorage.key(index) + if (!key?.startsWith(STORAGE_PREFIX)) continue + const value = window.localStorage.getItem(key) + if (value) remembered.set(key.slice(STORAGE_PREFIX.length), value) + } + } catch { + // Storage can be unavailable or full. The in-memory value still works for + // the session; only persistence across reloads is lost. + } +} + +export function subscribeToActiveEnvironment(listener: () => void) { + hydrateOnce() + listeners.add(listener) + if (remembered.size > 0) listener() + return () => listeners.delete(listener) +} + +/** A primitive snapshot, so useSyncExternalStore never sees a new identity. */ +export function rememberedEnvironmentId(projectId: string) { + return projectId ? remembered.get(projectId) : undefined +} + +export function rememberEnvironmentId(projectId: string, environmentId: string) { + if (!projectId || !environmentId) return + if (remembered.get(projectId) === environmentId) return + + remembered.set(projectId, environmentId) + try { + window.localStorage.setItem(storageKey(projectId), environmentId) + } catch { + // See hydrateOnce: persistence is best effort. + } + notify() +} + +/** Test seam. Production code has no reason to discard the choice. */ +export function resetActiveEnvironmentStore() { + remembered.clear() + hydrated = false + notify() + listeners.clear() +} diff --git a/apps/dashboard/src/features/environments/types/environment-alias.test.ts b/apps/dashboard/src/features/environments/types/environment-alias.test.ts new file mode 100644 index 00000000..c428fa29 --- /dev/null +++ b/apps/dashboard/src/features/environments/types/environment-alias.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest" + +import type { Environment } from "@/generated/api" +import { + environmentAlias, + environmentForAlias, +} from "@/features/environments/types/environment-alias" + +const timestamps = { createdAt: "2026-07-30T00:00:00Z", updatedAt: "2026-07-30T00:00:00Z" } + +function environment(id: string, key: string, mode: Environment["mode"]): Environment { + return { ...timestamps, id, key, mode, name: key, projectId: "prj_01" } +} + +const development = environment("env_01", "development", "development") +const staging = environment("env_02", "staging", "staging") +const production = environment("env_03", "production", "production") +const all = [development, staging, production] + +describe("environment aliases", () => { + it("shortens the seeded keys for the address", () => { + expect(environmentAlias(development)).toBe("dev") + expect(environmentAlias(staging)).toBe("staging") + expect(environmentAlias(production)).toBe("prod") + }) + + it("falls back to the key it cannot shorten", () => { + expect(environmentAlias(environment("env_04", "canary", "staging"))).toBe("canary") + }) + + it("resolves an alias back to its Environment", () => { + expect(environmentForAlias(all, "prod")).toBe(production) + expect(environmentForAlias(all, "dev")).toBe(development) + expect(environmentForAlias(all, "staging")).toBe(staging) + }) + + it("also accepts the underlying key", () => { + expect(environmentForAlias(all, "development")).toBe(development) + expect(environmentForAlias(all, "production")).toBe(production) + }) + + it("still accepts an id, so links minted before the change keep working", () => { + expect(environmentForAlias(all, "env_03")).toBe(production) + }) + + it("resolves nothing for an absent or unknown segment", () => { + expect(environmentForAlias(all, undefined)).toBeUndefined() + expect(environmentForAlias(all, "")).toBeUndefined() + // A literal from a sibling route must not resolve to an Environment. + expect(environmentForAlias(all, "connections")).toBeUndefined() + }) + + it("round-trips every seeded Environment", () => { + for (const value of all) { + expect(environmentForAlias(all, environmentAlias(value))).toBe(value) + } + }) +}) diff --git a/apps/dashboard/src/features/environments/types/environment-alias.ts b/apps/dashboard/src/features/environments/types/environment-alias.ts new file mode 100644 index 00000000..fb9897c6 --- /dev/null +++ b/apps/dashboard/src/features/environments/types/environment-alias.ts @@ -0,0 +1,53 @@ +import type { Environment } from "@/generated/api" + +/** + * The Environment as it appears in an address: `prod`, `staging`, `dev` rather than + * `env_01H8X...`. A URL an operator can read and type is worth more than an opaque + * id, and the id is an implementation detail the address has no reason to leak. + * + * A Project's Environments are seeded once, on creation, with the keys below and no + * endpoint adds more, so this mapping is total in practice. The fallback to the raw + * key keeps an address resolvable if that ever changes. + */ +const ALIAS_BY_KEY: Record = { + development: "dev", + production: "prod", + staging: "staging", +} + +/** + * Where a link lands when the linker has no Environment to go on — workspace entry, + * for instance, which resolves before any Environment list is read. Development on + * purpose: an address that guesses must never guess production. + */ +export const DEFAULT_ENVIRONMENT_ALIAS = "dev" + +const KEY_BY_ALIAS: Record = { + dev: "development", + prod: "production", + staging: "staging", +} + +export function environmentAlias(environment: Environment) { + return ALIAS_BY_KEY[environment.key] ?? environment.key +} + +/** + * Resolves an address segment to the Environment it names, by alias or by the + * underlying key. Ids are deliberately not accepted: the address does not carry + * them, so a segment that looks like one is a malformed link, not a scope. + */ +export function environmentForAlias( + environments: readonly Environment[], + alias: string | undefined, +): Environment | undefined { + if (!alias) return undefined + + const key = KEY_BY_ALIAS[alias] + return environments.find( + (environment) => + environmentAlias(environment) === alias || + environment.key === alias || + (key !== undefined && environment.key === key), + ) +} diff --git a/apps/dashboard/src/features/environments/types/environment-path.test.ts b/apps/dashboard/src/features/environments/types/environment-path.test.ts new file mode 100644 index 00000000..b0da2c1d --- /dev/null +++ b/apps/dashboard/src/features/environments/types/environment-path.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from "vitest" + +import { switchEnvironmentPath } from "@/features/environments/types/environment-path" + +const base = "/orgs/org_01/projects/prj_01" + +describe("switchEnvironmentPath", () => { + it("keeps the operator on the same surface", () => { + const cases: [string, string][] = [ + [`${base}/monetization/env_dev/paywalls`, `${base}/monetization/env_prod/paywalls`], + [ + `${base}/monetization/env_dev/experiments/exp_01`, + `${base}/monetization/env_prod/experiments/exp_01`, + ], + [`${base}/analytics/env_dev/funnel`, `${base}/analytics/env_prod/funnel`], + [`${base}/billing/env_dev/customers/cus_01`, `${base}/billing/env_prod/customers/cus_01`], + ] + + for (const [pathname, expected] of cases) { + expect(switchEnvironmentPath(pathname, "env_dev", "env_prod")).toBe(expected) + } + }) + + it("rewrites the addressed form by alias", () => { + const addressed = "/orgs/org_01/projects/prj_01/env" + + expect(switchEnvironmentPath(`${addressed}/dev/catalog/products`, "dev", "prod")).toBe( + `${addressed}/prod/catalog/products`, + ) + expect( + switchEnvironmentPath(`${addressed}/prod/billing/quarantine/rec_01`, "prod", "dev"), + ).toBe(`${addressed}/dev/billing/quarantine/rec_01`) + // The alias, not the id, is what the address carries, so an id must not match. + expect(switchEnvironmentPath(`${addressed}/prod/apps`, "env_03", "env_01")).toBeNull() + }) + + it("reports nothing to switch on a surface with no Environment", () => { + for (const pathname of [base, `${base}/apps`, `${base}/catalog/products`, "/workspace"]) { + expect(switchEnvironmentPath(pathname, "env_dev", "env_prod")).toBeNull() + } + }) + + it("does not mistake a literal segment for an Environment", () => { + // Anchoring on position alone would rewrite "connections" and "migrations", + // producing a route that does not exist. + for (const pathname of [ + `${base}/billing/connections/cred_01`, + `${base}/billing/migrations/prog_01`, + ]) { + expect(switchEnvironmentPath(pathname, "env_dev", "env_prod")).toBeNull() + } + }) + + it("rewrites only the Environment, even when another segment repeats its id", () => { + expect( + switchEnvironmentPath(`${base}/billing/env_dev/subscriptions/env_dev`, "env_dev", "env_prod"), + ).toBe(`${base}/billing/env_prod/subscriptions/env_dev`) + }) + + it("refuses an empty Environment id rather than building a path with an empty segment", () => { + expect(switchEnvironmentPath(`${base}/analytics/env_dev/funnel`, "env_dev", "")).toBeNull() + expect(switchEnvironmentPath(`${base}/analytics//funnel`, "", "env_prod")).toBeNull() + }) +}) diff --git a/apps/dashboard/src/features/environments/types/environment-path.ts b/apps/dashboard/src/features/environments/types/environment-path.ts new file mode 100644 index 00000000..a6b6ed58 --- /dev/null +++ b/apps/dashboard/src/features/environments/types/environment-path.ts @@ -0,0 +1,30 @@ +// Every Project surface names its Environment the same way: the segment after +// `env`, carrying a readable alias rather than an id. +// +// /orgs/O/projects/P/env/prod/billing/quarantine/rec_01 +const ENVIRONMENT_SEGMENT = "env" + +/** + * Rewrites the Environment alias in place, so switching keeps the operator on the + * surface they are reading. Returns null when the path names no Environment, which + * is how the caller knows there is nothing to switch. + * + * The match is anchored on the current alias rather than on position alone, so a + * path that happens to contain the word `env` elsewhere cannot be rewritten by + * accident. + */ +export function switchEnvironmentPath( + pathname: string, + currentAlias: string, + nextAlias: string, +): string | null { + if (currentAlias === "" || nextAlias === "") return null + + const segments = pathname.split("/") + const index = segments.indexOf(ENVIRONMENT_SEGMENT) + if (index === -1 || segments[index + 1] !== currentAlias) return null + + const rewritten = [...segments] + rewritten[index + 1] = nextAlias + return rewritten.join("/") +} diff --git a/apps/dashboard/src/features/organizations/components/cloud-workspace-shell.tsx b/apps/dashboard/src/features/organizations/components/cloud-workspace-shell.tsx deleted file mode 100644 index 1eb90083..00000000 --- a/apps/dashboard/src/features/organizations/components/cloud-workspace-shell.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { CodeIcon } from "@phosphor-icons/react/dist/ssr/Code" -import { ChartLineUpIcon } from "@phosphor-icons/react/dist/ssr/ChartLineUp" -import { GearSixIcon } from "@phosphor-icons/react/dist/ssr/GearSix" -import { KeyIcon } from "@phosphor-icons/react/dist/ssr/Key" -import { PackageIcon } from "@phosphor-icons/react/dist/ssr/Package" -import { ReceiptIcon } from "@phosphor-icons/react/dist/ssr/Receipt" -import { StorefrontIcon } from "@phosphor-icons/react/dist/ssr/Storefront" -import { SquaresFourIcon } from "@phosphor-icons/react/dist/ssr/SquaresFour" -import { UsersThreeIcon } from "@phosphor-icons/react/dist/ssr/UsersThree" -import { useQuery } from "@tanstack/react-query" -import { Link, useRouterState } from "@tanstack/react-router" - -import { NavMain } from "@/components/navigation/nav-main" -import { dashboardBuildInfo } from "@/config/environment" -import { - Sidebar, - SidebarContent, - SidebarFooter, - SidebarHeader, - SidebarRail, -} from "@/components/ui/sidebar" -import { readWorkspaceScope } from "@/features/organizations/types/workspace-navigation" -import { UserMenu } from "@/features/auth/components/user-menu" -import { billingSettingsQueryOptions } from "@/features/store-connections/queries/billing-settings-queries" -import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" -import { useOrganizationAccess } from "@/hooks/use-organization-access" -import type { NavigationItem } from "@/components/navigation/nav-main" -import { OrganizationSwitcher } from "./organization-switcher" - -export function CloudWorkspaceShell() { - const pathname = useRouterState({ select: (state) => state.location.pathname }) - const scope = readWorkspaceScope(pathname) - const access = useOrganizationAccess(scope.organizationId ?? "") - - // Administrative surfaces are hidden until membership confirms management - // rights. The API remains the authority; this only prevents dead-end links. - const canManage = access.canManage - - // Mosaic Billing is per-Project opt-in. While it is off, its Environment - // surfaces have nothing to show, so the group collapses to the one page that - // can turn it on. Hiding the group outright would make billing unreachable. - const environments = useQuery({ - ...environmentsQueryOptions(scope.projectId ?? ""), - enabled: canManage && Boolean(scope.projectId), - }) - const probeEnvironmentId = scope.environmentId ?? environments.data?.items[0]?.id ?? "" - const billingSettings = useQuery({ - ...billingSettingsQueryOptions(scope.projectId ?? "", probeEnvironmentId), - enabled: canManage && Boolean(scope.projectId) && probeEnvironmentId.length > 0, - }) - // Unknown state shows the full group: a nav that hides itself because a probe - // failed is worse than one item too many. - const billingEnabled = billingSettings.data?.billingEnabled !== false - - function withManagement(items: NavigationItem[]) { - return canManage ? items : [] - } - - return ( - - - - - - {scope.projectId && ( - , - title: "Overview", - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/monetization/${scope.environmentId}/paywalls` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - icon: , - title: "Monetization", - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/analytics/${scope.environmentId}/overview` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - icon: , - title: "Analytics", - }, - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/apps`, - icon: , - title: "Apps", - }, - { - icon: , - title: "Catalog", - subItems: [ - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/catalog/plans`, - title: "Plans", - icon: <>, - }, - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/catalog/products`, - title: "Products", - icon: <>, - }, - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/catalog/entitlements`, - title: "Access", - icon: <>, - }, - // What a Product grants is versioned and immutable, so it is - // its own surface rather than a set of buttons on Product - // detail. It sits in Catalog because grant versions relate two - // Project-scoped things, Products and Entitlements. - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/catalog/grant-versions`, - title: "Grant versions", - icon: <>, - }, - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/catalog/providers`, - title: "Purchase setup", - icon: <>, - }, - ], - }, - // Mosaic Billing is per-Project opt-in and owner/admin only. The - // group is hidden from members who could not act on it, so it is - // never a dead end; Environment-scoped items fall back to the - // Project overview exactly as Monetization and Analytics do. - // "Store Server Credentials" is the frozen term, and every - // recovery link in billing uses it, so the nav does too. - ...withManagement([ - { - icon: , - title: "Billing", - subItems: [ - ...(billingEnabled - ? [ - // Customers leads the group. It answers the question - // operators actually arrive with — "does this person - // have access?" — which the ledger deliberately - // cannot. - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/customers` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Customers", - icon: <>, - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/identity-conflicts` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Identity conflicts", - icon: <>, - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/restores` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Restores", - icon: <>, - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/transactions` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Transactions", - icon: <>, - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/quarantine` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Quarantine", - icon: <>, - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/reconciliation` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Reconciliation", - icon: <>, - }, - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/health` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Billing health", - icon: <>, - }, - // A sibling of billing health, never a tab inside it: - // store input can be arriving perfectly while every - // customer is being told the wrong thing. - { - to: scope.environmentId - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/${scope.environmentId}/projection-health` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}`, - title: "Projection health", - icon: <>, - }, - ] - : []), - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/billing/connections`, - title: billingEnabled ? "Store Server Credentials" : "Set up Mosaic Billing", - icon: <>, - }, - ], - }, - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/settings/environments`, - icon: , - title: "Settings", - }, - { - to: `/organizations/${scope.organizationId}/projects/${scope.projectId}/settings/api-keys`, - icon: , - title: "API keys", - }, - ]), - ]} - /> - )} - {scope.organizationId && canManage && ( - , - title: "Members", - }, - ]} - /> - )} - - - - - Mosaic {dashboardBuildInfo.version} · diagnostics - - - - - ) -} diff --git a/apps/dashboard/src/features/organizations/components/organization-switcher.test.tsx b/apps/dashboard/src/features/organizations/components/organization-switcher.test.tsx deleted file mode 100644 index 0ccb0801..00000000 --- a/apps/dashboard/src/features/organizations/components/organization-switcher.test.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import { - RouterProvider, - createMemoryHistory, - createRootRoute, - createRoute, - createRouter, -} from "@tanstack/react-router" -import { fireEvent, render, screen } from "@testing-library/react" -import { beforeAll, describe, expect, it } from "vitest" - -import { SidebarProvider } from "@/components/ui/sidebar" -import { OrganizationSwitcher } from "@/features/organizations/components/organization-switcher" -import { organizationKeys } from "@/features/organizations/queries/organizations-query" -import { ApiError } from "@/lib/api/errors" - -// The sidebar reads a media query to decide between its desktop and mobile -// presentation; jsdom does not implement matchMedia. -beforeAll(() => { - if (typeof window.matchMedia === "function") return - window.matchMedia = (query: string) => - ({ - addEventListener: () => {}, - addListener: () => {}, - dispatchEvent: () => false, - matches: false, - media: query, - onchange: null, - removeEventListener: () => {}, - removeListener: () => {}, - }) as MediaQueryList -}) - -function renderSwitcher(queryClient: QueryClient, organizationId?: string) { - const rootRoute = createRootRoute() - const routeTree = rootRoute.addChildren([ - createRoute({ - getParentRoute: () => rootRoute, - path: "/", - component: () => ( - - - - ), - }), - createRoute({ getParentRoute: () => rootRoute, path: "/organizations/new" }), - createRoute({ getParentRoute: () => rootRoute, path: "/organizations/$organizationId" }), - createRoute({ - getParentRoute: () => rootRoute, - path: "/organizations/$organizationId/projects/new", - }), - ]) - - const router = createRouter({ history: createMemoryHistory(), routeTree }) - - return render( - - - , - ) -} - -function seededClient(seed: (client: QueryClient) => void) { - const client = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }) - seed(client) - return client -} - -/** - * The switcher shipped non-functional: its items were not links, so an operator - * could not change Organization at all, and a failed list request rendered the - * "no organizations yet" branch, which reads as "your data is gone". - */ -describe("OrganizationSwitcher", () => { - it("navigates to each Organization and names the current one", async () => { - const client = seededClient((queryClient) => { - queryClient.setQueryData(organizationKeys.list(), { - items: [ - { id: "org_01", name: "Northwind" }, - { id: "org_02", name: "Contoso" }, - ], - }) - }) - - renderSwitcher(client, "org_02") - - const trigger = await screen.findByRole("button", { - name: /Current organization: Contoso/, - }) - fireEvent.click(trigger) - - const northwind = await screen.findByRole("menuitem", { name: "Northwind" }) - expect(northwind).toHaveAttribute("href", "/organizations/org_01") - expect(screen.getByRole("menuitem", { name: "Contoso" })).toHaveAttribute( - "href", - "/organizations/org_02", - ) - expect(screen.getByRole("menuitem", { name: "Add project" })).toHaveAttribute( - "href", - "/organizations/org_02/projects/new", - ) - }) - - it("offers a retry instead of claiming there are no Organizations when the list fails", async () => { - const client = seededClient((queryClient) => { - queryClient.setQueryDefaults(organizationKeys.list(), { - queryFn: () => - Promise.reject( - new ApiError("boom", { - code: "internal_error", - correlationId: "request_test", - retryable: true, - status: 500, - }), - ), - retry: false, - }) - }) - - renderSwitcher(client) - - const trigger = await screen.findByRole("button", { name: /Switch organization/ }) - fireEvent.click(trigger) - - expect(await screen.findByRole("button", { name: "Retry loading organizations" })).toBeVisible() - expect(screen.queryByText("You do not belong to an organization yet.")).toBeNull() - // "Add project" needs an Organization in scope; offering it here would be a - // dead link. - expect(screen.queryByRole("menuitem", { name: "Add project" })).toBeNull() - }) -}) diff --git a/apps/dashboard/src/features/organizations/components/organization-switcher.tsx b/apps/dashboard/src/features/organizations/components/organization-switcher.tsx deleted file mode 100644 index e5b37827..00000000 --- a/apps/dashboard/src/features/organizations/components/organization-switcher.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import * as React from "react" - -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - useSidebar, -} from "@/components/ui/sidebar" -import { CaretUpDownIcon } from "@phosphor-icons/react/dist/ssr/CaretUpDown" -import { PlusIcon } from "@phosphor-icons/react/dist/ssr/Plus" -import { organizationsQueryOptions } from "../queries/organizations-query" -import { useQuery } from "@tanstack/react-query" -import { Avatar, AvatarFallback } from "@/components/ui/avatar" -import { Button } from "@/components/ui/button" -import { Link } from "@tanstack/react-router" -import { describeApiError } from "@/lib/api/errors" - -const AVATAR_CLASSNAMES = - "bg-sidebar-primary text-sidebar-primary-foreground size-4 rounded data-[size=lg]:size-4 data-[size=sm]:size-4 after:rounded-none" - -// Base UI exposes the trigger width as --anchor-width on positioned popups. -const DROPDOWN_CLASSNAMES = "w-(--anchor-width) min-w-56 rounded" - -export function OrganizationSwitcher({ organizationId }: { organizationId?: string }) { - const { isMobile } = useSidebar() - const organizationsQuery = useQuery(organizationsQueryOptions()) - - const organizations = React.useMemo( - () => organizationsQuery.data?.items ?? [], - [organizationsQuery.data?.items], - ) - - const currentOrganization = React.useMemo( - () => organizations.find((organization) => organization.id === organizationId), - [organizations, organizationId], - ) - - const triggerLabel = currentOrganization - ? currentOrganization.name - : organizationsQuery.isPending - ? "Loading organizations" - : "Select organization" - - return ( - - - - - -
- - - {triggerLabel.charAt(0).toUpperCase()} - - -
- {triggerLabel} -
-
- - - } - /> - - - - Organizations - - - {organizationsQuery.isPending ? ( -

- Loading organizations… -

- ) : organizationsQuery.isError ? ( -
-

- {describeApiError(organizationsQuery.error).description} -

- -
- ) : organizations.length === 0 ? ( -

- You do not belong to an organization yet. -

- ) : ( - organizations.map((organization) => ( - - } - > - - - {organization.name.charAt(0).toUpperCase()} - - - {organization.name} - - )) - )} - - - - {organizationId ? ( - - } - > -
- -
-
- Add project -
-
- ) : null} - - }> -
- -
-
- Add organization -
-
-
-
-
-
-
- ) -} diff --git a/apps/dashboard/src/features/orgs/components/cloud-workspace-shell.test.tsx b/apps/dashboard/src/features/orgs/components/cloud-workspace-shell.test.tsx new file mode 100644 index 00000000..838a468a --- /dev/null +++ b/apps/dashboard/src/features/orgs/components/cloud-workspace-shell.test.tsx @@ -0,0 +1,109 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from "@tanstack/react-router" +import { render, screen } from "@testing-library/react" +import { beforeAll, describe, expect, it } from "vitest" + +import { SidebarProvider } from "@/components/ui/sidebar" +import { CloudWorkspaceShell } from "@/features/orgs/components/cloud-workspace-shell" +import { environmentKeys } from "@/features/environments/queries/environments-query" +import { memberKeys } from "@/features/members/queries/members-query" +import { sessionKeys } from "@/features/auth/queries/session-query" +import { workspaceBootstrapKeys } from "@/features/orgs/queries/workspace-bootstrap-query" + +beforeAll(() => { + if (typeof window.matchMedia === "function") return + window.matchMedia = (query: string) => + ({ + addEventListener: () => {}, + addListener: () => {}, + dispatchEvent: () => false, + matches: false, + media: query, + onchange: null, + removeEventListener: () => {}, + removeListener: () => {}, + }) as MediaQueryList +}) + +const timestamps = { createdAt: "2026-07-30T00:00:00Z", updatedAt: "2026-07-30T00:00:00Z" } + +const PATHS = [ + "/orgs/$organizationId/projects/$projectId/env/$environmentKey", + "/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls", +] as const + +function seededClient() { + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + client.setQueryData(sessionKeys.current, { email: "operator@example.test", id: "actor_01" }) + client.setQueryData(memberKeys.list("org_01"), { + items: [{ ...timestamps, actorId: "actor_01", organizationId: "org_01", role: "owner" }], + page: { nextCursor: "" }, + }) + client.setQueryData(environmentKeys.list("prj_01"), { + items: [ + { + ...timestamps, + id: "env_dev", + key: "development", + mode: "development", + name: "Development", + projectId: "prj_01", + }, + ], + page: { nextCursor: "" }, + }) + client.setQueryData(workspaceBootstrapKeys.all, { organizations: [] }) + return client +} + +function renderShell(pathname: string) { + const rootRoute = createRootRoute() + const routeTree = rootRoute.addChildren( + PATHS.map((path) => + createRoute({ component: CloudWorkspaceShell, getParentRoute: () => rootRoute, path }), + ), + ) + const router = createRouter({ + history: createMemoryHistory({ initialEntries: [pathname] }), + routeTree, + }) + + return render( + + + + + , + ) +} + +/** + * The switcher being wired into the shell is not the same as the shell rendering + * it: a client-only guard once compiled fine and still never ran. These assert on + * the mounted output. + */ +describe("CloudWorkspaceShell", () => { + it("mounts the Environment switcher on an Environment-scoped surface", async () => { + renderShell("/orgs/org_01/projects/prj_01/monetization/env_dev/paywalls") + + expect( + await screen.findByRole("button", { name: /Current environment: Development/ }), + ).toBeInTheDocument() + }) + + it("still offers it where the path carries no Environment", async () => { + renderShell("/orgs/org_01/projects/prj_01") + + // Present on every Project surface. The Project overview names no Environment + // in its address, so the switcher says so rather than implying the link would + // reproduce it. + const trigger = await screen.findByRole("button", { name: /Current environment: Development/ }) + expect(trigger).toHaveTextContent("not in this page's address") + }) +}) diff --git a/apps/dashboard/src/features/orgs/components/cloud-workspace-shell.tsx b/apps/dashboard/src/features/orgs/components/cloud-workspace-shell.tsx new file mode 100644 index 00000000..fd69b5f0 --- /dev/null +++ b/apps/dashboard/src/features/orgs/components/cloud-workspace-shell.tsx @@ -0,0 +1,279 @@ +import { CodeIcon } from "@phosphor-icons/react/dist/ssr/Code" +import { ChartLineUpIcon } from "@phosphor-icons/react/dist/ssr/ChartLineUp" +import { GearSixIcon } from "@phosphor-icons/react/dist/ssr/GearSix" +import { KeyIcon } from "@phosphor-icons/react/dist/ssr/Key" +import { PackageIcon } from "@phosphor-icons/react/dist/ssr/Package" +import { ReceiptIcon } from "@phosphor-icons/react/dist/ssr/Receipt" +import { StorefrontIcon } from "@phosphor-icons/react/dist/ssr/Storefront" +import { SquaresFourIcon } from "@phosphor-icons/react/dist/ssr/SquaresFour" +import { UsersThreeIcon } from "@phosphor-icons/react/dist/ssr/UsersThree" +import { useQuery } from "@tanstack/react-query" +import { Link, useRouterState } from "@tanstack/react-router" + +import { NavMain } from "@/components/navigation/nav-main" +import { dashboardBuildInfo } from "@/config/environment" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarRail, +} from "@/components/ui/sidebar" +import { readWorkspaceScope } from "@/features/orgs/types/workspace-navigation" +import { UserMenu } from "@/features/auth/components/user-menu" +import { billingSettingsQueryOptions } from "@/features/store-connections/queries/billing-settings-queries" +import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" +import { useActiveEnvironment } from "@/features/environments/hooks/use-active-environment" +import { EnvironmentSwitcher } from "@/features/environments/components/environment-switcher" +import { useOrganizationAccess } from "@/hooks/use-organization-access" +import type { NavigationItem } from "@/components/navigation/nav-main" +import { OrganizationSwitcher } from "./organization-switcher" + +export function CloudWorkspaceShell() { + const pathname = useRouterState({ select: (state) => state.location.pathname }) + const scope = readWorkspaceScope(pathname) + const access = useOrganizationAccess(scope.organizationId ?? "") + // The address names the Environment by alias; the id stays internal. + const { activeId, alias } = useActiveEnvironment() + const projectBase = `/orgs/${scope.organizationId}/projects/${scope.projectId}/env/${alias}` + + // Administrative surfaces are hidden until membership confirms management + // rights. The API remains the authority; this only prevents dead-end links. + const canManage = access.canManage + + // Mosaic Billing is per-Project opt-in. While it is off, its Environment + // surfaces have nothing to show, so the group collapses to the one page that + // can turn it on. Hiding the group outright would make billing unreachable. + // const environments = useQuery({ + // ...environmentsQueryOptions(scope.projectId ?? ""), + // enabled: canManage && Boolean(scope.projectId), + // }) + const probeEnvironmentId = activeId ?? "" + const billingSettings = useQuery({ + ...billingSettingsQueryOptions(scope.projectId ?? "", probeEnvironmentId), + enabled: canManage && Boolean(scope.projectId) && probeEnvironmentId.length > 0, + }) + // Unknown state shows the full group: a nav that hides itself because a probe + // failed is worse than one item too many. + const billingEnabled = billingSettings.data?.billingEnabled !== false + + function withManagement(items: NavigationItem[]) { + return canManage ? items : [] + } + + return ( + + + + + + + {scope.projectId && ( + , + title: "Overview", + }, + { + icon: , + title: "Monetization", + subItems: [ + { + to: `${projectBase}/monetization/paywalls`, + title: "Paywalls", + icon: <> + }, + { + to: `${projectBase}/monetization/experiments`, + title: "Experiments", + icon: <> + }, + { + to: `${projectBase}/monetization/placements`, + title: "Placements", + icon: <> + }, + { + to: `${projectBase}/monetization/assets`, + title: "Assets", + icon: <> + }, + { + to: `${projectBase}/monetization/releases`, + title: "Publish history", + icon: <> + }, + ], + }, + { + to: `${projectBase}/analytics/overview`, + icon: , + title: "Analytics", + }, + { + to: `${projectBase}/apps`, + icon: , + title: "Apps", + }, + { + icon: , + title: "Catalog", + subItems: [ + { + to: `${projectBase}/catalog/plans`, + title: "Plans", + icon: <>, + }, + { + to: `${projectBase}/catalog/products`, + title: "Products", + icon: <>, + }, + { + to: `${projectBase}/catalog/entitlements`, + title: "Access", + icon: <>, + }, + // What a Product grants is versioned and immutable, so it is + // its own surface rather than a set of buttons on Product + // detail. It sits in Catalog because grant versions relate two + // Project-scoped things, Products and Entitlements. + { + to: `${projectBase}/catalog/grant-versions`, + title: "Grant versions", + icon: <>, + }, + { + to: `${projectBase}/catalog/providers`, + title: "Purchase setup", + icon: <>, + }, + ], + }, + // Mosaic Billing is per-Project opt-in and owner/admin only. The + // group is hidden from members who could not act on it, so it is + // never a dead end; Environment-scoped items fall back to the + // Project overview exactly as Monetization and Analytics do. + // "Store Server Credentials" is the frozen term, and every + // recovery link in billing uses it, so the nav does too. + ...(access.membership + ? [ + { + icon: , + title: "Billing", + subItems: [ + { + to: `${projectBase}/billing/migrations`, + title: "Migration Programs", + icon: <>, + }, + ...(billingEnabled && canManage + ? [ + // Customers leads the group. It answers the question + // operators actually arrive with — "does this person + // have access?" — which the ledger deliberately + // cannot. + { + to: `${projectBase}/billing/customers`, + title: "Customers", + icon: <>, + }, + { + to: `${projectBase}/billing/identity-conflicts`, + title: "Identity conflicts", + icon: <>, + }, + { + to: `${projectBase}/billing/restores`, + title: "Restores", + icon: <>, + }, + { + to: `${projectBase}/billing/transactions`, + title: "Transactions", + icon: <>, + }, + { + to: `${projectBase}/billing/quarantine`, + title: "Quarantine", + icon: <>, + }, + { + to: `${projectBase}/billing/reconciliation`, + title: "Reconciliation", + icon: <>, + }, + { + to: `${projectBase}/billing/health`, + title: "Billing health", + icon: <>, + }, + // A sibling of billing health, never a tab inside it: + // store input can be arriving perfectly while every + // customer is being told the wrong thing. + { + to: `${projectBase}/billing/projection-health`, + title: "Projection health", + icon: <>, + }, + ] + : []), + ...(canManage + ? [ + { + to: `${projectBase}/billing/connections`, + title: billingEnabled + ? "Store Server Credentials" + : "Set up Mosaic Billing", + icon: <>, + }, + ] + : []), + ], + }, + ] + : []), + ...withManagement([ + { + to: `${projectBase}/settings/environments`, + icon: , + title: "Settings", + }, + { + to: `${projectBase}/settings/api-keys`, + icon: , + title: "API keys", + }, + ]), + ]} + /> + )} + {scope.organizationId && canManage && ( + , + title: "Members", + }, + ]} + /> + )} + + + + + Mosaic {dashboardBuildInfo.version} · diagnostics + + + + + ) +} diff --git a/apps/dashboard/src/features/organizations/components/create-organization-page.tsx b/apps/dashboard/src/features/orgs/components/create-organization-page.tsx similarity index 94% rename from apps/dashboard/src/features/organizations/components/create-organization-page.tsx rename to apps/dashboard/src/features/orgs/components/create-organization-page.tsx index 8fda3cf7..339727bb 100644 --- a/apps/dashboard/src/features/organizations/components/create-organization-page.tsx +++ b/apps/dashboard/src/features/orgs/components/create-organization-page.tsx @@ -6,8 +6,8 @@ import { Button } from "@/components/ui/button" import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { HostedAccessBanner } from "@/features/auth/components/hosted-access-banner" -import { createOrganizationMutationOptions } from "@/features/organizations/mutations/organization-mutations" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { createOrganizationMutationOptions } from "@/features/orgs/mutations/organization-mutations" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" import { ApiError } from "@/lib/api/errors" export function CreateOrganizationPage() { @@ -20,7 +20,7 @@ export function CreateOrganizationPage() { const organization = await mutation.mutateAsync({ name: value.name.trim() }) await navigate({ params: { organizationId: organization.id }, - to: "/organizations/$organizationId", + to: "/orgs/$organizationId", }) }, }) diff --git a/apps/dashboard/src/features/organizations/components/organization-overview-page.tsx b/apps/dashboard/src/features/orgs/components/organization-overview-page.tsx similarity index 88% rename from apps/dashboard/src/features/organizations/components/organization-overview-page.tsx rename to apps/dashboard/src/features/orgs/components/organization-overview-page.tsx index 12641d00..72b91157 100644 --- a/apps/dashboard/src/features/organizations/components/organization-overview-page.tsx +++ b/apps/dashboard/src/features/orgs/components/organization-overview-page.tsx @@ -4,8 +4,8 @@ import { Link } from "@tanstack/react-router" import { buttonVariants } from "@/components/ui/button-variants" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" -import { organizationQueryOptions } from "@/features/organizations/queries/organizations-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { organizationQueryOptions } from "@/features/orgs/queries/organizations-query" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" import { projectsQueryOptions } from "@/features/projects/queries/projects-query" interface OrganizationOverviewPageProps { @@ -29,7 +29,7 @@ export function OrganizationOverviewPage({ Create project @@ -57,14 +57,14 @@ export function OrganizationOverviewPage({ Members New project @@ -104,8 +104,8 @@ export function OrganizationOverviewPage({

{project.key}

prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey" > Open project diff --git a/apps/dashboard/src/features/orgs/components/organization-switcher.test.tsx b/apps/dashboard/src/features/orgs/components/organization-switcher.test.tsx new file mode 100644 index 00000000..cbfd7569 --- /dev/null +++ b/apps/dashboard/src/features/orgs/components/organization-switcher.test.tsx @@ -0,0 +1,243 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from "@tanstack/react-router" +import { fireEvent, render, screen } from "@testing-library/react" +import { beforeAll, describe, expect, it } from "vitest" + +import { SidebarProvider } from "@/components/ui/sidebar" +import type { BootstrapOrganization, Project, Role } from "@/generated/api" +import { OrganizationSwitcher } from "@/features/orgs/components/organization-switcher" +import { workspaceBootstrapKeys } from "@/features/orgs/queries/workspace-bootstrap-query" +import { ApiError } from "@/lib/api/errors" + +// The sidebar reads a media query to decide between its desktop and mobile +// presentation; jsdom does not implement matchMedia. +beforeAll(() => { + if (typeof window.matchMedia === "function") return + window.matchMedia = (query: string) => + ({ + addEventListener: () => {}, + addListener: () => {}, + dispatchEvent: () => false, + matches: false, + media: query, + onchange: null, + removeEventListener: () => {}, + removeListener: () => {}, + }) as MediaQueryList +}) + +const timestamps = { createdAt: "2026-07-30T00:00:00Z", updatedAt: "2026-07-30T00:00:00Z" } + +function project(id: string, name: string, organizationId: string): Project { + return { ...timestamps, id, key: id, name, organizationId, status: "active" } +} + +function organization( + id: string, + name: string, + { + projectCount, + projects = [], + role = "owner" as Role, + }: { projectCount?: number; projects?: Project[]; role?: Role } = {}, +): BootstrapOrganization { + return { + organization: { ...timestamps, id, name }, + projectCount: projectCount ?? projects.length, + projects, + projectsTruncated: (projectCount ?? projects.length) > projects.length, + role, + } +} + +function renderSwitcher( + queryClient: QueryClient, + { organizationId, pathname }: { organizationId?: string; pathname: string }, +) { + const component = () => ( + + + + ) + const rootRoute = createRootRoute() + const routeTree = rootRoute.addChildren([ + createRoute({ getParentRoute: () => rootRoute, path: "/", component }), + createRoute({ getParentRoute: () => rootRoute, path: "/orgs/new" }), + createRoute({ + getParentRoute: () => rootRoute, + path: "/orgs/$organizationId", + component, + }), + createRoute({ + getParentRoute: () => rootRoute, + path: "/orgs/$organizationId/projects/new", + }), + createRoute({ + getParentRoute: () => rootRoute, + path: "/orgs/$organizationId/projects/$projectId/env/$environmentKey", + component, + }), + ]) + + const router = createRouter({ + history: createMemoryHistory({ initialEntries: [pathname] }), + routeTree, + }) + + return render( + + + , + ) +} + +function seededClient(seed: (client: QueryClient) => void) { + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + seed(client) + return client +} + +function withBootstrap(organizations: BootstrapOrganization[]) { + return (client: QueryClient) => { + client.setQueryData(workspaceBootstrapKeys.all, { organizations }) + } +} + +async function openSwitcher() { + fireEvent.click(await screen.findByRole("button", { name: /Switch project or organization/ })) +} + +/** + * The switcher shipped non-functional: its items were not links, so an operator + * could not change Organization at all, and a failed list request rendered the + * "no organizations yet" branch, which reads as "your data is gone". Projects, + * the unit of daily work, were not reachable from it at all. + */ +describe("OrganizationSwitcher", () => { + it("lists the current Organization's Projects and names the one in scope", async () => { + const client = seededClient( + withBootstrap([ + organization("org_01", "Northwind", { + projects: [project("prj_01", "Mobile", "org_01"), project("prj_02", "Web", "org_01")], + }), + ]), + ) + + renderSwitcher(client, { + organizationId: "org_01", + pathname: "/orgs/org_01/projects/prj_02", + }) + + const trigger = await screen.findByRole("button", { name: /Current project: Web/ }) + expect(trigger).toHaveAccessibleName(/Current organization: Northwind/) + fireEvent.click(trigger) + + expect(await screen.findByRole("menuitem", { name: "Mobile" })).toHaveAttribute( + "href", + "/orgs/org_01/projects/prj_01", + ) + expect(screen.getByRole("menuitem", { name: "Web" })).toHaveAttribute( + "href", + "/orgs/org_01/projects/prj_02", + ) + expect(screen.getByRole("menuitem", { name: "Add project" })).toHaveAttribute( + "href", + "/orgs/org_01/projects/new", + ) + }) + + it("moves between Organizations from the switch submenu", async () => { + const client = seededClient( + withBootstrap([ + organization("org_01", "Northwind", { projects: [project("prj_01", "Mobile", "org_01")] }), + organization("org_02", "Contoso"), + ]), + ) + + renderSwitcher(client, { organizationId: "org_01", pathname: "/orgs/org_01" }) + await openSwitcher() + + fireEvent.click(await screen.findByRole("menuitem", { name: /Switch organization/ })) + + expect(await screen.findByRole("menuitem", { name: "Contoso" })).toHaveAttribute( + "href", + "/orgs/org_02", + ) + expect(screen.getByRole("menuitem", { name: "Add organization" })).toHaveAttribute( + "href", + "/orgs/new", + ) + }) + + it("offers a retry instead of claiming there are no Projects when the read fails", async () => { + const client = seededClient((queryClient) => { + queryClient.setQueryDefaults(workspaceBootstrapKeys.all, { + queryFn: () => + Promise.reject( + new ApiError("boom", { + code: "internal_error", + correlationId: "request_test", + retryable: true, + status: 500, + }), + ), + retry: false, + }) + }) + + renderSwitcher(client, { pathname: "/" }) + await openSwitcher() + + expect(await screen.findByRole("button", { name: "Retry loading organizations" })).toBeVisible() + expect(screen.queryByText("You do not belong to an organization yet.")).toBeNull() + // "Add project" needs an Organization in scope; offering it here would be a + // dead link. + expect(screen.queryByRole("menuitem", { name: "Add project" })).toBeNull() + }) + + it("withholds Add project from a member, who cannot create one", async () => { + const client = seededClient( + withBootstrap([ + organization("org_01", "Northwind", { + projects: [project("prj_01", "Mobile", "org_01")], + role: "member", + }), + ]), + ) + + renderSwitcher(client, { organizationId: "org_01", pathname: "/orgs/org_01" }) + await openSwitcher() + + // Polled rather than read once: the popup remounts as the router settles, so + // a handle taken from findBy can be detached by the time it is asserted on. + await expect + .poll(() => screen.queryByRole("menuitem", { name: "Mobile" })?.getAttribute("href")) + .toBe("/orgs/org_01/projects/prj_01") + expect(screen.queryByRole("menuitem", { name: "Add project" })).toBeNull() + }) + + it("says how many Projects exist when the snapshot is capped", async () => { + const client = seededClient( + withBootstrap([ + organization("org_01", "Northwind", { + projectCount: 40, + projects: [project("prj_01", "Mobile", "org_01")], + }), + ]), + ) + + renderSwitcher(client, { organizationId: "org_01", pathname: "/orgs/org_01" }) + await openSwitcher() + + expect(await screen.findByRole("menuitem", { name: "View all 40 projects" })).toHaveAttribute( + "href", + "/orgs/org_01", + ) + }) +}) diff --git a/apps/dashboard/src/features/orgs/components/organization-switcher.tsx b/apps/dashboard/src/features/orgs/components/organization-switcher.tsx new file mode 100644 index 00000000..624482bb --- /dev/null +++ b/apps/dashboard/src/features/orgs/components/organization-switcher.tsx @@ -0,0 +1,274 @@ +import * as React from "react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" +import { ArrowsLeftRightIcon } from "@phosphor-icons/react/dist/ssr/ArrowsLeftRight" +import { CaretUpDownIcon } from "@phosphor-icons/react/dist/ssr/CaretUpDown" +import { PlusIcon } from "@phosphor-icons/react/dist/ssr/Plus" +import { useQuery } from "@tanstack/react-query" +import { Link, useRouterState } from "@tanstack/react-router" + +import { Avatar, AvatarFallback } from "@/components/ui/avatar" +import { Button } from "@/components/ui/button" +import { workspaceBootstrapQueryOptions } from "../queries/workspace-bootstrap-query" +import { readWorkspaceScope } from "../types/workspace-navigation" +import { describeApiError } from "@/lib/api/errors" + +const AVATAR_CLASSNAMES = + "bg-sidebar-primary text-sidebar-primary-foreground size-4 rounded data-[size=lg]:size-4 data-[size=sm]:size-4 after:rounded-none" + +// Base UI exposes the trigger width as --anchor-width on positioned popups. +const DROPDOWN_CLASSNAMES = "w-(--anchor-width) min-w-56 rounded" + +function Initial({ value }: { value: string }) { + return ( + + + {value.charAt(0).toUpperCase()} + + + ) +} + +/** + * The switcher is where an operator moves between Projects, which is the unit of + * daily work; changing Organization is the rarer move and lives one level in. + * Both read the single bootstrap snapshot entry already fetched, so opening the + * menu costs no request. + */ +export function OrganizationSwitcher({ organizationId }: { organizationId?: string }) { + const { isMobile } = useSidebar() + const bootstrap = useQuery(workspaceBootstrapQueryOptions()) + // The shell passes the Organization it read from the path; the Project comes + // from the same place, so the switcher does not need a second prop threaded + // through every caller. + const projectId = useRouterState({ + select: (state) => readWorkspaceScope(state.location.pathname).projectId, + }) + + const organizations = React.useMemo( + () => bootstrap.data?.organizations ?? [], + [bootstrap.data?.organizations], + ) + + const current = React.useMemo( + () => organizations.find((entry) => entry.organization.id === organizationId), + [organizations, organizationId], + ) + + const currentProject = React.useMemo( + () => current?.projects.find((project) => project.id === projectId), + [current, projectId], + ) + + const organizationLabel = current + ? current.organization.name + : bootstrap.isPending + ? "Loading organizations" + : "Select organization" + const triggerLabel = currentProject ? currentProject.name : organizationLabel + const canCreateProject = current?.role === "owner" || current?.role === "admin" + + const failure = bootstrap.isError ? ( +
+

+ {describeApiError(bootstrap.error).description} +

+ +
+ ) : null + + return ( + + + + + +
+ +
+ {triggerLabel} + {currentProject ? ( + + {organizationLabel} + + ) : null} +
+
+ + + } + /> + + + + Projects + + + {bootstrap.isPending ? ( +

+ Loading projects… +

+ ) : ( + (failure ?? + (!current ? ( +

+ Choose an organization to see its projects. +

+ ) : current.projects.length === 0 ? ( +

+ {current.organization.name} has no projects yet. +

+ ) : ( + <> + {current.projects.map((project) => ( + prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey" + /> + } + > + + {project.name} + + ))} + {current.projectsTruncated ? ( + // Presenting a capped list as the whole list would hide + // Projects the operator owns. + + } + > + + View all {current.projectCount} projects + + + ) : null} + + ))) + )} + + {/* Only owners and admins may create a Project, so offering the + form to a member would be a guaranteed refusal. */} + {current && canCreateProject ? ( + + } + > +
+ +
+
+ Add project +
+
+ ) : null} + + + + + + + + Switch organization + + + + + Organizations + + + {bootstrap.isPending ? ( +

+ Loading organizations… +

+ ) : ( + (failure ?? + (organizations.length === 0 ? ( +

+ You do not belong to an organization yet. +

+ ) : ( + organizations.map((entry) => ( + + } + > + + {entry.organization.name} + + )) + ))) + )} + + + + }> +
+ +
+
+ Add organization +
+
+
+
+
+
+
+
+
+ ) +} diff --git a/apps/dashboard/src/features/organizations/components/scope-mismatch-recovery.tsx b/apps/dashboard/src/features/orgs/components/scope-mismatch-recovery.tsx similarity index 86% rename from apps/dashboard/src/features/organizations/components/scope-mismatch-recovery.tsx rename to apps/dashboard/src/features/orgs/components/scope-mismatch-recovery.tsx index 83321e9e..79536d0f 100644 --- a/apps/dashboard/src/features/organizations/components/scope-mismatch-recovery.tsx +++ b/apps/dashboard/src/features/orgs/components/scope-mismatch-recovery.tsx @@ -1,7 +1,7 @@ import { Link } from "@tanstack/react-router" import { buttonVariants } from "@/components/ui/button-variants" -import type { NestedScopeMismatch } from "@/features/organizations/types/nested-scope" +import type { NestedScopeMismatch } from "@/features/orgs/types/nested-scope" interface ScopeMismatchRecoveryProps { mismatch: Exclude @@ -34,8 +34,8 @@ export function ScopeMismatchRecovery({ {mismatch === "resource" ? ( prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey" > Return to Project @@ -43,7 +43,7 @@ export function ScopeMismatchRecovery({ Return to Organization diff --git a/apps/dashboard/src/features/orgs/components/workspace-entry-redirect.test.tsx b/apps/dashboard/src/features/orgs/components/workspace-entry-redirect.test.tsx new file mode 100644 index 00000000..ca2cf343 --- /dev/null +++ b/apps/dashboard/src/features/orgs/components/workspace-entry-redirect.test.tsx @@ -0,0 +1,163 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from "@tanstack/react-router" +import { render, screen, waitFor } from "@testing-library/react" +import { beforeAll, describe, expect, it } from "vitest" + +import { SidebarProvider } from "@/components/ui/sidebar" +import type { BootstrapOrganization, Project } from "@/generated/api" +import { WorkspaceEntryRedirect } from "@/features/orgs/components/workspace-entry-redirect" +import { workspaceBootstrapKeys } from "@/features/orgs/queries/workspace-bootstrap-query" +import { ApiError } from "@/lib/api/errors" + +// The degraded branch renders the workspace shell's Organization list, which +// reads a media query jsdom does not implement. +beforeAll(() => { + if (typeof window.matchMedia === "function") return + window.matchMedia = (query: string) => + ({ + addEventListener: () => {}, + addListener: () => {}, + dispatchEvent: () => false, + matches: false, + media: query, + onchange: null, + removeEventListener: () => {}, + removeListener: () => {}, + }) as MediaQueryList +}) + +const timestamps = { createdAt: "2026-07-30T00:00:00Z", updatedAt: "2026-07-30T00:00:00Z" } + +function project(id: string, organizationId: string): Project { + return { ...timestamps, id, key: id, name: id, organizationId, status: "active" } +} + +function organization(id: string, projects: Project[] = []): BootstrapOrganization { + return { + organization: { ...timestamps, id, name: id }, + projectCount: projects.length, + projects, + projectsTruncated: false, + role: "owner", + } +} + +function renderEntry(queryClient: QueryClient) { + const rootRoute = createRootRoute() + const routeTree = rootRoute.addChildren([ + createRoute({ + component: WorkspaceEntryRedirect, + getParentRoute: () => rootRoute, + path: "/workspace", + }), + createRoute({ getParentRoute: () => rootRoute, path: "/orgs/new" }), + createRoute({ getParentRoute: () => rootRoute, path: "/orgs/$organizationId" }), + createRoute({ + getParentRoute: () => rootRoute, + path: "/orgs/$organizationId/projects/new", + }), + createRoute({ + getParentRoute: () => rootRoute, + path: "/orgs/$organizationId/projects/$projectId/env/$environmentKey", + }), + ]) + + const router = createRouter({ + history: createMemoryHistory({ initialEntries: ["/workspace"] }), + routeTree, + }) + + render( + + + + + , + ) + + return router +} + +function seededClient(seed: (client: QueryClient) => void) { + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + seed(client) + return client +} + +/** + * Entry shipped as a beforeLoad that returned early under `import.meta.env.SSR`. + * On a hard load that guard ran only on the server, and Start does not re-run it + * after hydration, so the redirect never happened and the operator sat on + * /workspace. These assertions are on the resulting location, which is the part + * that was broken while the resolver itself was correct. + */ +describe("WorkspaceEntryRedirect", () => { + it("leaves /workspace for the first Project", async () => { + const client = seededClient((queryClient) => { + queryClient.setQueryData(workspaceBootstrapKeys.all, { + organizations: [organization("org_01", [project("prj_01", "org_01")])], + }) + }) + + const router = renderEntry(client) + + await waitFor(() => { + expect(router.state.location.pathname).toBe("/orgs/org_01/projects/prj_01") + }) + }) + + it("sends an operator with no Organizations to create one", async () => { + const client = seededClient((queryClient) => { + queryClient.setQueryData(workspaceBootstrapKeys.all, { organizations: [] }) + }) + + const router = renderEntry(client) + + await waitFor(() => { + expect(router.state.location.pathname).toBe("/orgs/new") + }) + }) + + it("replaces the entry route so Back does not bounce forward again", async () => { + const client = seededClient((queryClient) => { + queryClient.setQueryData(workspaceBootstrapKeys.all, { + organizations: [organization("org_01", [project("prj_01", "org_01")])], + }) + }) + + const router = renderEntry(client) + await waitFor(() => { + expect(router.state.location.pathname).toBe("/orgs/org_01/projects/prj_01") + }) + + expect(router.history.canGoBack()).toBe(false) + }) + + it("shows the Organization list with a retry when the snapshot cannot be read", async () => { + const client = seededClient((queryClient) => { + queryClient.setQueryDefaults(workspaceBootstrapKeys.all, { + queryFn: () => + Promise.reject( + new ApiError("boom", { + code: "internal_error", + correlationId: "request_test", + retryable: true, + status: 500, + }), + ), + retry: false, + }) + }) + + const router = renderEntry(client) + + expect(await screen.findByRole("heading", { name: "Organizations" })).toBeVisible() + expect(router.state.location.pathname).toBe("/workspace") + }) +}) diff --git a/apps/dashboard/src/features/orgs/components/workspace-entry-redirect.tsx b/apps/dashboard/src/features/orgs/components/workspace-entry-redirect.tsx new file mode 100644 index 00000000..3164ac43 --- /dev/null +++ b/apps/dashboard/src/features/orgs/components/workspace-entry-redirect.tsx @@ -0,0 +1,45 @@ +import * as React from "react" + +import { useNavigate } from "@tanstack/react-router" +import { useQuery } from "@tanstack/react-query" + +import { RoutePendingState } from "@/components/feedback/route-feedback" +import { WorkspaceHome } from "@/features/orgs/components/workspace-home" +import { + resolveWorkspaceEntry, + workspaceEntryNavigation, +} from "@/features/orgs/types/workspace-entry" +import { workspaceBootstrapQueryOptions } from "@/features/orgs/queries/workspace-bootstrap-query" + +/** + * Entry resolves here rather than in the route's beforeLoad. The session lives in + * an HttpOnly cookie the SSR pass cannot read, so the guard has to be + * client-side; but a beforeLoad that returns early under `import.meta.env.SSR` + * runs only on the server for a hard load, and Start does not re-run it after + * hydration. The redirect silently never happened. A component runs on every + * hydration, so this holds for both a typed URL and an in-app navigation. + */ +export function WorkspaceEntryRedirect() { + const navigate = useNavigate() + const bootstrap = useQuery(workspaceBootstrapQueryOptions()) + + // Memoised on the cached snapshot: resolveWorkspaceEntry returns a fresh + // object each call, which would re-arm the effect on every render. + const target = React.useMemo( + () => (bootstrap.data ? resolveWorkspaceEntry(bootstrap.data) : undefined), + [bootstrap.data], + ) + + React.useEffect(() => { + if (!target) return + // Replaced, not pushed: Back must leave the workspace rather than land on + // this route again and bounce forward. + void navigate({ ...workspaceEntryNavigation(target), replace: true }) + }, [navigate, target]) + + // A failed bootstrap must not strand the operator on a blank redirect. The + // Organization list reports the outage and offers a retry. + if (bootstrap.isError) return + + return +} diff --git a/apps/dashboard/src/features/organizations/components/workspace-home.tsx b/apps/dashboard/src/features/orgs/components/workspace-home.tsx similarity index 87% rename from apps/dashboard/src/features/organizations/components/workspace-home.tsx rename to apps/dashboard/src/features/orgs/components/workspace-home.tsx index 19922be2..c0d74d8f 100644 --- a/apps/dashboard/src/features/organizations/components/workspace-home.tsx +++ b/apps/dashboard/src/features/orgs/components/workspace-home.tsx @@ -6,15 +6,15 @@ import { Link } from "@tanstack/react-router" import { buttonVariants } from "@/components/ui/button-variants" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" -import { organizationsQueryOptions } from "@/features/organizations/queries/organizations-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { organizationsQueryOptions } from "@/features/orgs/queries/organizations-query" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" export function WorkspaceHome() { const organizations = useQuery(organizationsQueryOptions()) const items = organizations.data?.items ?? [] const state = resolveHostedQueryState({ emptyAction: ( - + Create organization ), @@ -31,7 +31,7 @@ export function WorkspaceHome() { return ( + New organization @@ -50,7 +50,7 @@ export function WorkspaceHome() { {organization.name} diff --git a/apps/dashboard/src/features/organizations/components/workspace-page.tsx b/apps/dashboard/src/features/orgs/components/workspace-page.tsx similarity index 100% rename from apps/dashboard/src/features/organizations/components/workspace-page.tsx rename to apps/dashboard/src/features/orgs/components/workspace-page.tsx diff --git a/apps/dashboard/src/features/organizations/mutations/organization-mutations.ts b/apps/dashboard/src/features/orgs/mutations/organization-mutations.ts similarity index 88% rename from apps/dashboard/src/features/organizations/mutations/organization-mutations.ts rename to apps/dashboard/src/features/orgs/mutations/organization-mutations.ts index 429f53bd..8d180753 100644 --- a/apps/dashboard/src/features/organizations/mutations/organization-mutations.ts +++ b/apps/dashboard/src/features/orgs/mutations/organization-mutations.ts @@ -1,7 +1,7 @@ import { mutationOptions, type QueryClient } from "@tanstack/react-query" import { createOrganization, type CreateOrganizationRequest } from "@/generated/api" -import { organizationKeys } from "@/features/organizations/queries/organizations-query" +import { organizationKeys } from "@/features/orgs/queries/organizations-query" import { generatedDashboardClient } from "@/lib/api/generated-dashboard-client" export function createOrganizationMutationOptions(queryClient: QueryClient) { diff --git a/apps/dashboard/src/features/organizations/queries/organizations-query.ts b/apps/dashboard/src/features/orgs/queries/organizations-query.ts similarity index 100% rename from apps/dashboard/src/features/organizations/queries/organizations-query.ts rename to apps/dashboard/src/features/orgs/queries/organizations-query.ts diff --git a/apps/dashboard/src/features/orgs/queries/workspace-bootstrap-query.ts b/apps/dashboard/src/features/orgs/queries/workspace-bootstrap-query.ts new file mode 100644 index 00000000..b56f66c6 --- /dev/null +++ b/apps/dashboard/src/features/orgs/queries/workspace-bootstrap-query.ts @@ -0,0 +1,28 @@ +import { queryOptions } from "@tanstack/react-query" + +import { getWorkspaceBootstrap } from "@/generated/api" +import { generatedDashboardClient } from "@/lib/api/generated-dashboard-client" + +export const workspaceBootstrapKeys = { + all: ["workspace-bootstrap"] as const, +} + +/** + * One read that answers both "which Organizations does this operator have?" and + * "what is in them?". Entry and the Organization switcher share it, so opening + * the switcher costs nothing and entry resolves without a request per + * Organization. + */ +export function workspaceBootstrapQueryOptions() { + return queryOptions({ + queryKey: workspaceBootstrapKeys.all, + queryFn: async ({ signal }) => { + const result = await getWorkspaceBootstrap({ + client: generatedDashboardClient, + signal, + throwOnError: true, + }) + return result.data.data + }, + }) +} diff --git a/apps/dashboard/src/features/organizations/types/nested-scope.test.ts b/apps/dashboard/src/features/orgs/types/nested-scope.test.ts similarity index 93% rename from apps/dashboard/src/features/organizations/types/nested-scope.test.ts rename to apps/dashboard/src/features/orgs/types/nested-scope.test.ts index 27ca81b0..42b61cd3 100644 --- a/apps/dashboard/src/features/organizations/types/nested-scope.test.ts +++ b/apps/dashboard/src/features/orgs/types/nested-scope.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest" -import { detectNestedScopeMismatch } from "@/features/organizations/types/nested-scope" +import { detectNestedScopeMismatch } from "@/features/orgs/types/nested-scope" describe("nested hosted-route scope", () => { it("enables a project-scoped list only when the Project belongs to the routed Organization", () => { diff --git a/apps/dashboard/src/features/organizations/types/nested-scope.ts b/apps/dashboard/src/features/orgs/types/nested-scope.ts similarity index 100% rename from apps/dashboard/src/features/organizations/types/nested-scope.ts rename to apps/dashboard/src/features/orgs/types/nested-scope.ts diff --git a/apps/dashboard/src/features/orgs/types/workspace-entry.test.ts b/apps/dashboard/src/features/orgs/types/workspace-entry.test.ts new file mode 100644 index 00000000..d7f8db71 --- /dev/null +++ b/apps/dashboard/src/features/orgs/types/workspace-entry.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest" + +import type { BootstrapOrganization, Project, Role } from "@/generated/api" +import { resolveWorkspaceEntry } from "@/features/orgs/types/workspace-entry" + +const timestamps = { createdAt: "2026-07-30T00:00:00Z", updatedAt: "2026-07-30T00:00:00Z" } + +function project(id: string, organizationId: string): Project { + return { + ...timestamps, + id, + key: id, + name: id, + organizationId, + status: "active", + } +} + +function organization( + id: string, + { projects = [], role = "owner" as Role }: { projects?: Project[]; role?: Role } = {}, +): BootstrapOrganization { + return { + organization: { ...timestamps, id, name: id }, + projectCount: projects.length, + projects, + projectsTruncated: false, + role, + } +} + +describe("resolveWorkspaceEntry", () => { + it("sends an operator with no Organizations to create one", () => { + expect(resolveWorkspaceEntry({ organizations: [] })).toEqual({ + reason: "no-organizations", + to: "/orgs/new", + }) + }) + + it("lands in the first Project of the first Organization", () => { + const entry = resolveWorkspaceEntry({ + organizations: [ + organization("org_01", { + projects: [project("prj_01", "org_01"), project("prj_02", "org_01")], + }), + organization("org_02", { projects: [project("prj_03", "org_02")] }), + ], + }) + + expect(entry).toEqual({ + params: { organizationId: "org_01", projectId: "prj_01" }, + reason: "resolved", + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey", + }) + }) + + it("sends an owner of an empty first Organization to create a Project there", () => { + // The first Organization decides even though a later one already has a + // Project: entry must not silently move an owner off the Organization they + // created. + const entry = resolveWorkspaceEntry({ + organizations: [ + organization("org_01"), + organization("org_02", { projects: [project("prj_01", "org_02")] }), + ], + }) + + expect(entry).toEqual({ + params: { organizationId: "org_01" }, + reason: "no-projects", + to: "/orgs/$organizationId/projects/new", + }) + }) + + it("routes a member of an empty Organization past a create form they cannot submit", () => { + const entry = resolveWorkspaceEntry({ + organizations: [ + organization("org_01", { role: "member" }), + organization("org_02", { projects: [project("prj_01", "org_02")], role: "member" }), + ], + }) + + expect(entry).toEqual({ + params: { organizationId: "org_02", projectId: "prj_01" }, + reason: "resolved", + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey", + }) + }) + + it("stops at the Organization overview when a member has nowhere to land", () => { + const entry = resolveWorkspaceEntry({ + organizations: [organization("org_01", { role: "member" })], + }) + + expect(entry).toEqual({ + params: { organizationId: "org_01" }, + reason: "cannot-create-project", + to: "/orgs/$organizationId", + }) + }) +}) diff --git a/apps/dashboard/src/features/orgs/types/workspace-entry.ts b/apps/dashboard/src/features/orgs/types/workspace-entry.ts new file mode 100644 index 00000000..a6153a92 --- /dev/null +++ b/apps/dashboard/src/features/orgs/types/workspace-entry.ts @@ -0,0 +1,89 @@ +import type { BootstrapOrganization, WorkspaceBootstrap } from "@/generated/api" +import { DEFAULT_ENVIRONMENT_ALIAS } from "@/features/environments/types/environment-alias" + +/** + * Where entry sends an operator, as a router target so the decision is asserted + * against real paths rather than against an intermediate vocabulary. + */ +export type WorkspaceEntryTarget = + | { reason: "no-organizations"; to: "/orgs/new" } + | { + params: { organizationId: string } + reason: "no-projects" + to: "/orgs/$organizationId/projects/new" + } + | { + params: { organizationId: string } + reason: "cannot-create-project" + to: "/orgs/$organizationId" + } + | { + params: { environmentKey: string; organizationId: string; projectId: string } + reason: "resolved" + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey" + } + +/** + * The router payload for a target, without the `reason` the resolver carries for + * the benefit of readers and tests. + */ +export function workspaceEntryNavigation(target: WorkspaceEntryTarget) { + return target.to === "/orgs/new" + ? { to: target.to } + : { params: target.params, to: target.to } +} + +function canCreateProject(entry: BootstrapOrganization) { + return entry.role === "owner" || entry.role === "admin" +} + +/** + * Resolves entry from a single bootstrap read: no Organizations means create + * one, otherwise the first Organization decides, and a first Organization + * without Projects means create one there. + * + * The one place this looks past the first Organization is a member of an empty + * Organization: only owners and admins may create a Project, so sending a member + * to the create form would be a guaranteed 403. They fall through to an + * Organization that does have a Project, and land on the Organization overview + * when none does. + */ +export function resolveWorkspaceEntry(bootstrap: WorkspaceBootstrap): WorkspaceEntryTarget { + const organizations = bootstrap.organizations + const [first] = organizations + if (!first) { + return { reason: "no-organizations", to: "/orgs/new" } + } + + const populated = organizations.find((entry) => entry.projects.length > 0) + const target = first.projects.length > 0 || canCreateProject(first) ? first : (populated ?? first) + + const [project] = target.projects + if (project) { + return { + params: { + // Entry resolves before any Environment list is read, so it addresses the + // one Environment every Project is guaranteed to have. + environmentKey: DEFAULT_ENVIRONMENT_ALIAS, + organizationId: target.organization.id, + projectId: project.id, + }, + reason: "resolved", + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey", + } + } + + if (canCreateProject(target)) { + return { + params: { organizationId: target.organization.id }, + reason: "no-projects", + to: "/orgs/$organizationId/projects/new", + } + } + + return { + params: { organizationId: target.organization.id }, + reason: "cannot-create-project", + to: "/orgs/$organizationId", + } +} diff --git a/apps/dashboard/src/features/organizations/types/workspace-navigation.test.ts b/apps/dashboard/src/features/orgs/types/workspace-navigation.test.ts similarity index 74% rename from apps/dashboard/src/features/organizations/types/workspace-navigation.test.ts rename to apps/dashboard/src/features/orgs/types/workspace-navigation.test.ts index 8b927529..386fe1e3 100644 --- a/apps/dashboard/src/features/organizations/types/workspace-navigation.test.ts +++ b/apps/dashboard/src/features/orgs/types/workspace-navigation.test.ts @@ -4,11 +4,11 @@ import { isEnvironmentSurface, isProjectWideSurface, readWorkspaceScope, -} from "@/features/organizations/types/workspace-navigation" +} from "@/features/orgs/types/workspace-navigation" describe("hosted workspace route scope", () => { it("derives organization and project identity from the URL without a client store", () => { - const productRoute = "/organizations/org_one/projects/project_one/catalog/products/product_one" + const productRoute = "/orgs/org_one/projects/project_one/catalog/products/product_one" expect(readWorkspaceScope(productRoute)).toEqual({ environmentId: undefined, @@ -20,18 +20,18 @@ describe("hosted workspace route scope", () => { }) it("does not treat creation sentinels as selected scope", () => { - expect(readWorkspaceScope("/organizations/new")).toEqual({ + expect(readWorkspaceScope("/orgs/new")).toEqual({ environmentId: undefined, organizationId: undefined, projectId: undefined, }) expect( - isEnvironmentSurface("/organizations/org_one/projects/project_one/settings/api-keys"), + isEnvironmentSurface("/orgs/org_one/projects/project_one/settings/api-keys"), ).toBe(true) }) it("keeps monetization Environment identity URL-owned", () => { - const route = "/organizations/org_one/projects/project_one/monetization/env_staging/paywalls" + const route = "/orgs/org_one/projects/project_one/monetization/env_staging/paywalls" expect(readWorkspaceScope(route)).toEqual({ environmentId: "env_staging", @@ -43,7 +43,7 @@ describe("hosted workspace route scope", () => { }) it("keeps Analytics Environment identity URL-owned", () => { - const route = "/organizations/org_one/projects/project_one/analytics/env_production/paywalls" + const route = "/orgs/org_one/projects/project_one/analytics/env_production/paywalls" expect(readWorkspaceScope(route)).toEqual({ environmentId: "env_production", @@ -56,7 +56,7 @@ describe("hosted workspace route scope", () => { it("keeps Billing Environment identity URL-owned", () => { const route = - "/organizations/org_one/projects/project_one/billing/env_production/projection-health" + "/orgs/org_one/projects/project_one/billing/env_production/projection-health" expect(readWorkspaceScope(route)).toEqual({ environmentId: "env_production", diff --git a/apps/dashboard/src/features/organizations/types/workspace-navigation.ts b/apps/dashboard/src/features/orgs/types/workspace-navigation.ts similarity index 52% rename from apps/dashboard/src/features/organizations/types/workspace-navigation.ts rename to apps/dashboard/src/features/orgs/types/workspace-navigation.ts index 30494424..7b44f286 100644 --- a/apps/dashboard/src/features/organizations/types/workspace-navigation.ts +++ b/apps/dashboard/src/features/orgs/types/workspace-navigation.ts @@ -1,26 +1,22 @@ export interface WorkspaceScope { - environmentId?: string + /** + * The Environment as the address names it: the alias under `/env`, e.g. `prod`. + * Resolving it to an Environment is useActiveEnvironment's job, because only the + * Project's own Environment list can say whether the segment means anything. + */ + environmentSegment?: string organizationId?: string projectId?: string } export function readWorkspaceScope(pathname: string): WorkspaceScope { const segments = pathname.split("/").filter(Boolean) - const organizationIndex = segments.indexOf("organizations") + const organizationIndex = segments.indexOf("orgs") const projectIndex = segments.indexOf("projects") - const monetizationIndex = segments.indexOf("monetization") - const analyticsIndex = segments.indexOf("analytics") - const billingIndex = segments.indexOf("billing") + const environmentIndex = segments.indexOf("env") return { - environmentId: - monetizationIndex >= 0 - ? segments[monetizationIndex + 1] - : analyticsIndex >= 0 - ? segments[analyticsIndex + 1] - : billingIndex >= 0 - ? segments[billingIndex + 1] - : undefined, + environmentSegment: environmentIndex >= 0 ? segments[environmentIndex + 1] : undefined, organizationId: organizationIndex >= 0 && segments[organizationIndex + 1] !== "new" ? segments[organizationIndex + 1] @@ -36,11 +32,7 @@ export function isProjectWideSurface(pathname: string) { return pathname.includes("/apps") || pathname.includes("/catalog/") } +/** Every surface under a Project now names an Environment, uniformly, under `/env`. */ export function isEnvironmentSurface(pathname: string) { - return ( - pathname.endsWith("/settings/api-keys") || - pathname.includes("/monetization/") || - pathname.includes("/analytics/") || - pathname.includes("/billing/") - ) + return pathname.includes("/env/") } From cd0c6f402e28103d043e9635bcd56000991986b2 Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Thu, 30 Jul 2026 13:47:07 +0100 Subject: [PATCH 05/25] chore: remove border radius style from mosaic-status-message class --- packages/design-system/src/styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/design-system/src/styles.css b/packages/design-system/src/styles.css index 5e232167..0cc27c22 100644 --- a/packages/design-system/src/styles.css +++ b/packages/design-system/src/styles.css @@ -22,7 +22,6 @@ width: fit-content; padding: var(--mosaic-space-1) var(--mosaic-space-2); border: 1px solid var(--mosaic-border); - border-radius: var(--mosaic-radius-full); background: var(--mosaic-surface-raised); color: var(--mosaic-text-muted); font-size: var(--mosaic-type-xs); From 6e7fba3fce6b3a050827c218a9604592799f9c40 Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Thu, 30 Jul 2026 13:53:20 +0100 Subject: [PATCH 06/25] chore: update routes --- apps/dashboard/src/lib/routing/workspace-hrefs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/lib/routing/workspace-hrefs.ts b/apps/dashboard/src/lib/routing/workspace-hrefs.ts index 8d39ca06..d5e4beda 100644 --- a/apps/dashboard/src/lib/routing/workspace-hrefs.ts +++ b/apps/dashboard/src/lib/routing/workspace-hrefs.ts @@ -29,7 +29,7 @@ export function appendSearch(href: string, values: Record Date: Thu, 30 Jul 2026 13:54:53 +0100 Subject: [PATCH 07/25] chore: update routing and file import --- .../src/components/navigation/nav-main.tsx | 6 ++- .../components/analytics-workspace.tsx | 37 +++---------------- .../components/data-privacy-panel.tsx | 2 +- .../analytics/components/issues-panel.tsx | 6 +-- .../api-keys/components/api-keys-page.tsx | 8 ++-- .../assets/components/assets-page.tsx | 6 +-- .../features/auth/types/hosted-access.test.ts | 2 +- .../auth/types/hosted-query-state.test.ts | 4 +- 8 files changed, 25 insertions(+), 46 deletions(-) diff --git a/apps/dashboard/src/components/navigation/nav-main.tsx b/apps/dashboard/src/components/navigation/nav-main.tsx index 1655f9f0..8d574f1d 100644 --- a/apps/dashboard/src/components/navigation/nav-main.tsx +++ b/apps/dashboard/src/components/navigation/nav-main.tsx @@ -40,7 +40,11 @@ export function NavMain({ items, label }: NavMainProps) { subItem.to === pathname)} + defaultOpen={item.subItems.some( + (subItem) => + subItem.to === pathname || + (subItem.to ? pathname.startsWith(`${subItem.to}/`) : false), + )} className="group/collapsible" > diff --git a/apps/dashboard/src/features/analytics/components/analytics-workspace.tsx b/apps/dashboard/src/features/analytics/components/analytics-workspace.tsx index bb63d0d4..f414ff86 100644 --- a/apps/dashboard/src/features/analytics/components/analytics-workspace.tsx +++ b/apps/dashboard/src/features/analytics/components/analytics-workspace.tsx @@ -5,7 +5,7 @@ import { Link, useNavigate } from "@tanstack/react-router" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" import { projectQueryOptions } from "@/features/projects/queries/projects-query" import { cn } from "@/lib/utils" import { restAnalyticsAdapter } from "../api/rest-analytics-adapter" @@ -74,33 +74,8 @@ export function AnalyticsWorkspace({ >
+ {/* The Environment is chosen once, in the sidebar switcher. */}
- Single-Environment reporting · event-count basis @@ -116,9 +91,9 @@ export function AnalyticsWorkspace({ : "text-muted-foreground hover:bg-muted hover:text-foreground", )} key={tab.surface} - params={{ environmentId, organizationId, projectId, surface: tab.surface }} + params={(prev) => ({ ...prev, surface: tab.surface })} search={filters} - to="/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface" + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface" > {tab.label} @@ -130,10 +105,10 @@ export function AnalyticsWorkspace({ filters={filters} onChange={(next) => void navigate({ - params: { environmentId, organizationId, projectId, surface }, + params: (prev) => ({ ...prev, surface }), replace: true, search: next, - to: "/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface", + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface", }) } /> diff --git a/apps/dashboard/src/features/analytics/components/data-privacy-panel.tsx b/apps/dashboard/src/features/analytics/components/data-privacy-panel.tsx index e1a9588b..7f59c3b4 100644 --- a/apps/dashboard/src/features/analytics/components/data-privacy-panel.tsx +++ b/apps/dashboard/src/features/analytics/components/data-privacy-panel.tsx @@ -4,7 +4,7 @@ import { useState } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { WorkflowPanel } from "@/features/orgs/components/workspace-page" import type { AnalyticsAdapter } from "../api/analytics-adapter" import { analyticsKeys, jobQueryOptions, settingsQueryOptions } from "../queries/analytics-queries" import type { diff --git a/apps/dashboard/src/features/analytics/components/issues-panel.tsx b/apps/dashboard/src/features/analytics/components/issues-panel.tsx index 596c57a1..39ce98c3 100644 --- a/apps/dashboard/src/features/analytics/components/issues-panel.tsx +++ b/apps/dashboard/src/features/analytics/components/issues-panel.tsx @@ -63,10 +63,10 @@ export function IssuesPanel({ function IssueTableRow({ issue, scope }: { issue: IssueRow; scope: AnalyticsScope }) { const href = issue.kind === "provider" - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/catalog/providers?environmentId=${scope.environmentId}` + ? `/orgs/${scope.organizationId}/projects/${scope.projectId}/catalog/providers?environmentId=${scope.environmentId}` : issue.kind === "product" - ? `/organizations/${scope.organizationId}/projects/${scope.projectId}/catalog/products` - : `/organizations/${scope.organizationId}/projects/${scope.projectId}/monetization/${scope.environmentId}/placements${issue.recoveryId ? `/${issue.recoveryId}` : ""}` + ? `/orgs/${scope.organizationId}/projects/${scope.projectId}/catalog/products` + : `/orgs/${scope.organizationId}/projects/${scope.projectId}/monetization/${scope.environmentId}/placements${issue.recoveryId ? `/${issue.recoveryId}` : ""}` return ( diff --git a/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx b/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx index ed570c5d..73ab0aeb 100644 --- a/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx +++ b/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx @@ -27,8 +27,8 @@ import { } from "@/features/api-keys/mutations/api-key-secret-cache" import { apiKeysQueryOptions } from "@/features/api-keys/queries/api-keys-query" import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" -import { ScopeMismatchRecovery } from "@/features/organizations/components/scope-mismatch-recovery" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" import { applicationsQueryOptions } from "@/features/projects/queries/projects-query" import type { ApiKey, ApiKeySecretResult } from "@/generated/api" @@ -137,9 +137,9 @@ export function ApiKeysPage({ environmentId, organizationId, projectId }: ApiKey dismissSecret() setPendingAction(null) void navigate({ - params: { organizationId, projectId }, + params: (prev) => prev, search: { environmentId: event.target.value }, - to: "/organizations/$organizationId/projects/$projectId/settings/api-keys", + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys", }) }} value={selectedEnvironment?.id ?? ""} diff --git a/apps/dashboard/src/features/assets/components/assets-page.tsx b/apps/dashboard/src/features/assets/components/assets-page.tsx index 4bfcbf06..845af20d 100644 --- a/apps/dashboard/src/features/assets/components/assets-page.tsx +++ b/apps/dashboard/src/features/assets/components/assets-page.tsx @@ -18,7 +18,7 @@ import { assetsQueryOptions, assetUsageQueryOptions } from "@/features/assets/qu import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { MonetizationWorkspace } from "@/features/environments/components/monetization-workspace" -import { WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { WorkflowPanel } from "@/features/orgs/components/workspace-page" import type { HostedAsset } from "@/features/publishing/api/hosted-publishing-adapter" function formatBytes(bytes: number) { @@ -193,8 +193,8 @@ export function AssetsPage({ permissionAction: ( prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls" > Return to Paywalls diff --git a/apps/dashboard/src/features/auth/types/hosted-access.test.ts b/apps/dashboard/src/features/auth/types/hosted-access.test.ts index 7ece1a53..0a91ce67 100644 --- a/apps/dashboard/src/features/auth/types/hosted-access.test.ts +++ b/apps/dashboard/src/features/auth/types/hosted-access.test.ts @@ -5,7 +5,7 @@ import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" describe("hosted authentication return paths", () => { it("preserves internal Studio routes and rejects external redirects", () => { - const studio = "/studio-hosted/org/project/env/paywall/draft?panel=products#binding" + const studio = "/studio/org/project/env/paywall/draft?panel=products#binding" expect(safeInternalReturnTo(studio)).toBe(studio) expect(safeInternalReturnTo("https://attacker.example/steal")).toBe("/workspace") diff --git a/apps/dashboard/src/features/auth/types/hosted-query-state.test.ts b/apps/dashboard/src/features/auth/types/hosted-query-state.test.ts index fc8dcfd5..4030f9cd 100644 --- a/apps/dashboard/src/features/auth/types/hosted-query-state.test.ts +++ b/apps/dashboard/src/features/auth/types/hosted-query-state.test.ts @@ -81,7 +81,7 @@ describe("hosted query recovery state", () => { expect(state).toMatchObject({ kind: "error", recovery: { - href: "/organizations/org_01/projects/project_01/monetization/env_prod/placements", + href: "/orgs/org_01/projects/project_01/monetization/env_prod/placements", label: "Review Placements", }, }) @@ -124,7 +124,7 @@ describe("hosted query recovery state", () => { expect(state).toMatchObject({ kind: "error", recovery: { - href: "/organizations/org_01/projects/project_01/settings/environments", + href: "/orgs/org_01/projects/project_01/settings/environments", label: "Open Environment settings", }, }) From e9de2dcc2dc5d501b280eb058f21f5d8bcd44308 Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Thu, 30 Jul 2026 15:23:38 +0100 Subject: [PATCH 08/25] feat(dashboard): regenerate openapi client, regenerate route tree and update some import issue --- apps/dashboard/src/generated/api/index.ts | 4 +- apps/dashboard/src/generated/api/sdk.gen.ts | 973 +- apps/dashboard/src/generated/api/types.gen.ts | 17266 ++++++++++------ apps/dashboard/src/routeTree.gen.ts | 1841 +- apps/dashboard/src/routes/_hosted.tsx | 2 +- .../src/routes/_hosted/workspace.tsx | 7 +- 6 files changed, 12382 insertions(+), 7711 deletions(-) diff --git a/apps/dashboard/src/generated/api/index.ts b/apps/dashboard/src/generated/api/index.ts index 6c81cf8e..bb4b73cb 100644 --- a/apps/dashboard/src/generated/api/index.ts +++ b/apps/dashboard/src/generated/api/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { addMember, addPlanProduct, addProductEntitlement, archiveAsset, archivePlacementAttribute, archivePlacementRuleSet, archivePlacementWithUsageCheck, archiveProduct, archiveProject, archiveProviderMapping, attachBillingCustomerAlias, bindPlacement, checkCustomerEntitlements, clearActiveProviderAssignment, clonePaywallVersionToDraft, clonePlacementRuleSetVersion, closeQuarantineRecordSuperseded, compareAnalyticsPaywallVersions, createAnalyticsEventExport, createAnalyticsPrivacyDeletion, createAnalyticsPrivacyExport, createApiKey, createApplication, createBillingCustomerSyncRequest, createBillingProjectionReplay, createEntitlement, createExperiment, createExperimentGroupVersion, createExperimentMutualExclusionGroupVersion, createExperimentQaOverride, createExperimentRawExport, createOrganization, createPaywall, createPaywallDraft, createPlacement, createPlacementAlias, createPlacementAttribute, createPlacementQaOverride, createPlacementRuleSet, createPlan, createProduct, createProject, createProviderConnection, createProviderMapping, createProviderMappingDraft, createProviderMappingObservation, createReconciliationRun, createReplayJob, createStoreServerCredential, createWebhookDestination, deleteProduct, deleteWebhookDestination, downloadAnalyticsJob, enqueueProviderSync, getActivePaywallDraft, getActiveProviderAssignment, getAnalyticsBreakdown, getAnalyticsFreshness, getAnalyticsFunnel, getAnalyticsJob, getAnalyticsOverview, getAnalyticsProductAvailabilityFailures, getAnalyticsProviderErrors, getAnalyticsSettings, getAsset, getAssetContent, getAssetUsage, getBillingCustomer, getBillingCustomerEntitlementSnapshot, getBillingHealth, getBillingIdentityConflict, getBillingProjectionHealth, getBillingRestoreJob, getBillingSettings, getBillingSubscription, getCustomerEntitlementSnapshot, getEntitlement, getExperiment, getExperimentResults, getExperimentSampleRatioMismatch, getHealth, getNativeProviderProfile, getOperatorBillingCustomer, getOperatorBillingIdentityConflict, getOrganization, getPaywall, getPaywallDraft, getPaywallVersion, getPlacementBinding, getPlacementDecision, getPlacementUsage, getPlan, getProduct, getProductEntitlementGrantVersion, getProductReadiness, getProductUsage, getProject, getProviderConnection, getProviderConnectionCapabilities, getProviderConnectionHealth, getProviderMappingMetadata, getProviderMappingUsage, getProviderReadiness, getQuarantineRecord, getReadiness, getSdkCommerceConfiguration, getSdkConfiguration, getSdkRestore, getServerRestore, getSession, getStoreServerCredential, getSubscriptionSnapshot, getWebhookDelivery, getWebhookDestination, identifyBillingCustomer, importProviderProducts, ingestAnalyticsEventBatch, issueCustomerAccessToken, listApiKeys, listApplications, listAssets, listAuditEvents, listBillingCustomerAliases, listBillingCustomers, listBillingCustomerSubscriptions, listBillingIdentityConflicts, listBillingLedger, listBillingQuarantine, listBillingRestoreJobs, listBillingSubscriptionTimeline, listConfigurationReleases, listCustomerAccessTokens, listCustomerSubscriptions, listEntitlements, listEnvironments, listExperimentGroups, listExperimentHistory, listExperimentMetricDefinitions, listExperimentMutualExclusionGroupVersions, listExperimentQaOverrides, listExperiments, listExperimentVersions, listMembers, listOperatorBillingIdentityConflicts, listOrganizations, listPaywalls, listPaywallVersions, listPlacementAliases, listPlacementAttributes, listPlacementQaOverrides, listPlacementRuleSetVersions, listPlacements, listPlanProducts, listPlans, listProductEntitlementGrantVersions, listProductEntitlements, listProducts, listProjects, listProviderConnectionDiagnostics, listProviderConnections, listProviderMappingObservations, listProviderMappings, listProviderSyncRuns, listReconciliationRuns, listReplayJobs, listStoreServerCredentials, listSubscriptionTimeline, listTransactionFacts, listValidationAttempts, listWebhookDeliveries, listWebhookDeliveryAttempts, listWebhookDestinations, listWebhookSigningSecrets, login, logout, lookupBillingCustomer, type Options, previewAnalyticsPrivacyRequest, previewProductEntitlementGrantImpact, previewProviderCatalog, publishConfiguration, publishExperiment, publishPlacementRuleSet, publishProductEntitlementGrantVersion, receiveAppleStoreNotification, reconnectProviderConnection, removeMember, removePlanProduct, removeProductEntitlement, replaceProviderConnectionScopes, replaceProviderMapping, replayWebhookDelivery, requestBillingCustomerSync, resolveBillingIdentityConflict, restoreProduct, restoreProject, retireWebhookSigningSecret, retryQuarantinedInput, revokeApiKey, revokeBillingCustomerAlias, revokeCustomerAccessToken, revokeExperimentQaOverride, revokePlacementQaOverride, revokeProviderConnection, revokeStoreServerCredential, rollbackConfigurationRelease, rotateApiKey, rotateProviderCredential, rotateStoreServerCredential, rotateWebhookSigningSecret, setActiveProviderAssignment, setEnvironmentMode, setProductReplacement, setWebhookDestinationStatus, signUp, simulatePlacementDecision, submitSdkRestore, submitServerRestore, submitServerTransactionObservation, submitTransactionObservation, syncCustomerEntitlements, syncCustomerEntitlementsWithNegotiation, testProviderConnection, testStoreServerCredential, transitionExperimentLifecycle, updateAnalyticsSettings, updateBillingSettings, updateEntitlement, updateEnvironment, updateExperimentDraft, updateMember, updateOrganization, updatePaywall, updatePaywallDraft, updatePlacement, updatePlacementRuleSetDraft, updatePlan, updateProduct, updateProductEntitlementGrantVersion, updateProject, updateWebhookDestination, uploadAsset, validateExperimentDraft, validatePaywallDraft, validatePlacementRuleSet } from './sdk.gen'; -export type { ActiveProviderAssignment, ActorId, AddMemberData, AddMemberError, AddMemberErrors, AddMemberRequest, AddMemberResponse, AddMemberResponses, AddPlanProductData, AddPlanProductError, AddPlanProductErrors, AddPlanProductResponse, AddPlanProductResponses, AddProductEntitlementData, AddProductEntitlementError, AddProductEntitlementErrors, AddProductEntitlementResponse, AddProductEntitlementResponses, AnalyticsApplicationVersion, AnalyticsEventBatch, AnalyticsEventResult, AnalyticsFreshness, AnalyticsFrom, AnalyticsIdentityRequest, AnalyticsIngestionResult, AnalyticsJob, AnalyticsJobEnvelope, AnalyticsLocale, AnalyticsMetric, AnalyticsMetricBasis, AnalyticsPlatform, AnalyticsPrivacyPreview, AnalyticsPrivacyPreviewEnvelope, AnalyticsResult, AnalyticsResultEnvelope, AnalyticsSettings, AnalyticsSettingsEnvelope, AnalyticsTimezone, AnalyticsTo, ApiKey, ApiKeyEnvelope, ApiKeyId, ApiKeyKind, ApiKeyList, ApiKeyListEnvelope, ApiKeySecretEnvelope, ApiKeySecretResult, Application, ApplicationEnvelope, ApplicationId, ApplicationList, ApplicationListEnvelope, ArchiveAssetData, ArchiveAssetError, ArchiveAssetErrors, ArchiveAssetResponse, ArchiveAssetResponses, ArchivePlacementAttributeData, ArchivePlacementAttributeError, ArchivePlacementAttributeErrors, ArchivePlacementAttributeResponse, ArchivePlacementAttributeResponses, ArchivePlacementRuleSetData, ArchivePlacementRuleSetError, ArchivePlacementRuleSetErrors, ArchivePlacementRuleSetResponse, ArchivePlacementRuleSetResponses, ArchivePlacementWithUsageCheckData, ArchivePlacementWithUsageCheckError, ArchivePlacementWithUsageCheckErrors, ArchivePlacementWithUsageCheckResponse, ArchivePlacementWithUsageCheckResponses, ArchiveProductData, ArchiveProductError, ArchiveProductErrors, ArchiveProductResponse, ArchiveProductResponses, ArchiveProjectData, ArchiveProjectError, ArchiveProjectErrors, ArchiveProjectResponse, ArchiveProjectResponses, ArchiveProviderMappingData, ArchiveProviderMappingError, ArchiveProviderMappingErrors, ArchiveProviderMappingResponse, ArchiveProviderMappingResponses, Asset, AssetEnvelope, AssetId, AssetListEnvelope, AssetUsage, AssetUsageEnvelope, AttachBillingCustomerAliasData, AttachBillingCustomerAliasError, AttachBillingCustomerAliasErrors, AttachBillingCustomerAliasRequest, AttachBillingCustomerAliasResponse, AttachBillingCustomerAliasResponses, AuditEvent, AuditEventList, AuditEventListEnvelope, BillingCursor, BillingCustomer, BillingCustomerAlias, BillingCustomerDetail, BillingCustomerId, BillingCustomerLookupRequest, BillingCustomerLookupResult, BillingCustomerSummary, BillingEntitlementSnapshot, BillingEntitlementSnapshotEntry, BillingEntitlementSource, BillingFrom, BillingHealth, BillingIdentityConflict, BillingIdentityConflictDetail, BillingIdentityCustomer, BillingLedgerEntry, BillingOneTimePurchase, BillingProjectionHealth, BillingProjectionStatus, BillingProviderFilter, BillingPurchaseLineage, BillingRestoreJob, BillingSettings, BillingSubscriptionSnapshot, BillingSyncRequest, BillingTimelineEntry, BillingTo, BindPlacementData, BindPlacementError, BindPlacementErrors, BindPlacementRequest, BindPlacementResponse, BindPlacementResponses, CheckCustomerEntitlementsData, CheckCustomerEntitlementsError, CheckCustomerEntitlementsErrors, CheckCustomerEntitlementsResponse, CheckCustomerEntitlementsResponses, ClearActiveProviderAssignmentData, ClearActiveProviderAssignmentError, ClearActiveProviderAssignmentErrors, ClearActiveProviderAssignmentResponse, ClearActiveProviderAssignmentResponses, ClientOptions, ClientTransactionObservation, ClientTransactionObservationRecord, ClonePaywallVersionToDraftData, ClonePaywallVersionToDraftError, ClonePaywallVersionToDraftErrors, ClonePaywallVersionToDraftResponse, ClonePaywallVersionToDraftResponses, ClonePlacementRuleSetVersionData, ClonePlacementRuleSetVersionError, ClonePlacementRuleSetVersionErrors, ClonePlacementRuleSetVersionResponse, ClonePlacementRuleSetVersionResponses, CloseQuarantineRecordSupersededData, CloseQuarantineRecordSupersededError, CloseQuarantineRecordSupersededErrors, CloseQuarantineRecordSupersededResponse, CloseQuarantineRecordSupersededResponses, CompareAnalyticsPaywallVersionsData, CompareAnalyticsPaywallVersionsError, CompareAnalyticsPaywallVersionsErrors, CompareAnalyticsPaywallVersionsResponse, CompareAnalyticsPaywallVersionsResponses, ConfigurationRelease, CreateAnalyticsEventExportData, CreateAnalyticsEventExportError, CreateAnalyticsEventExportErrors, CreateAnalyticsEventExportRequest, CreateAnalyticsEventExportResponse, CreateAnalyticsEventExportResponses, CreateAnalyticsPrivacyDeletionData, CreateAnalyticsPrivacyDeletionError, CreateAnalyticsPrivacyDeletionErrors, CreateAnalyticsPrivacyDeletionRequest, CreateAnalyticsPrivacyDeletionResponse, CreateAnalyticsPrivacyDeletionResponses, CreateAnalyticsPrivacyExportData, CreateAnalyticsPrivacyExportError, CreateAnalyticsPrivacyExportErrors, CreateAnalyticsPrivacyExportRequest, CreateAnalyticsPrivacyExportResponse, CreateAnalyticsPrivacyExportResponses, CreateApiKeyData, CreateApiKeyError, CreateApiKeyErrors, CreateApiKeyRequest, CreateApiKeyResponse, CreateApiKeyResponses, CreateApplicationData, CreateApplicationError, CreateApplicationErrors, CreateApplicationRequest, CreateApplicationResponse, CreateApplicationResponses, CreateBillingCustomerSyncRequestData, CreateBillingCustomerSyncRequestError, CreateBillingCustomerSyncRequestErrors, CreateBillingCustomerSyncRequestResponse, CreateBillingCustomerSyncRequestResponses, CreateBillingProjectionReplayData, CreateBillingProjectionReplayError, CreateBillingProjectionReplayErrors, CreateBillingProjectionReplayResponse, CreateBillingProjectionReplayResponses, CreateCatalogResourceRequest, CreateDraftRequest, CreateEntitlementData, CreateEntitlementError, CreateEntitlementErrors, CreateEntitlementResponse, CreateEntitlementResponses, CreateExperimentData, CreateExperimentError, CreateExperimentErrors, CreateExperimentExportRequest, CreateExperimentGroupRequest, CreateExperimentGroupVersionData, CreateExperimentGroupVersionError, CreateExperimentGroupVersionErrors, CreateExperimentGroupVersionRequest, CreateExperimentGroupVersionResponse, CreateExperimentGroupVersionResponses, CreateExperimentMutualExclusionGroupVersionData, CreateExperimentMutualExclusionGroupVersionError, CreateExperimentMutualExclusionGroupVersionErrors, CreateExperimentMutualExclusionGroupVersionResponse, CreateExperimentMutualExclusionGroupVersionResponses, CreateExperimentQaOverrideData, CreateExperimentQaOverrideError, CreateExperimentQaOverrideErrors, CreateExperimentQaOverrideRequest, CreateExperimentQaOverrideResponse, CreateExperimentQaOverrideResponses, CreateExperimentRawExportData, CreateExperimentRawExportError, CreateExperimentRawExportErrors, CreateExperimentRawExportResponse, CreateExperimentRawExportResponses, CreateExperimentRequest, CreateExperimentResponse, CreateExperimentResponses, CreateOrganizationData, CreateOrganizationError, CreateOrganizationErrors, CreateOrganizationRequest, CreateOrganizationResponse, CreateOrganizationResponses, CreatePaywallData, CreatePaywallDraftData, CreatePaywallDraftError, CreatePaywallDraftErrors, CreatePaywallDraftResponse, CreatePaywallDraftResponses, CreatePaywallError, CreatePaywallErrors, CreatePaywallRequest, CreatePaywallResponse, CreatePaywallResponses, CreatePlacementAliasData, CreatePlacementAliasError, CreatePlacementAliasErrors, CreatePlacementAliasResponse, CreatePlacementAliasResponses, CreatePlacementAttributeData, CreatePlacementAttributeError, CreatePlacementAttributeErrors, CreatePlacementAttributeRequest, CreatePlacementAttributeResponse, CreatePlacementAttributeResponses, CreatePlacementData, CreatePlacementError, CreatePlacementErrors, CreatePlacementQaOverrideData, CreatePlacementQaOverrideError, CreatePlacementQaOverrideErrors, CreatePlacementQaOverrideResponse, CreatePlacementQaOverrideResponses, CreatePlacementRequest, CreatePlacementResponse, CreatePlacementResponses, CreatePlacementRuleSetData, CreatePlacementRuleSetError, CreatePlacementRuleSetErrors, CreatePlacementRuleSetResponse, CreatePlacementRuleSetResponses, CreatePlanData, CreatePlanError, CreatePlanErrors, CreatePlanResponse, CreatePlanResponses, CreateProductData, CreateProductError, CreateProductErrors, CreateProductRequest, CreateProductResponse, CreateProductResponses, CreateProjectData, CreateProjectError, CreateProjectErrors, CreateProjectionReplayRequest, CreateProjectRequest, CreateProjectResponse, CreateProjectResponses, CreateProviderConnectionData, CreateProviderConnectionError, CreateProviderConnectionErrors, CreateProviderConnectionRequest, CreateProviderConnectionRequestWritable, CreateProviderConnectionResponse, CreateProviderConnectionResponses, CreateProviderMappingData, CreateProviderMappingDraftData, CreateProviderMappingDraftError, CreateProviderMappingDraftErrors, CreateProviderMappingDraftRequest, CreateProviderMappingDraftResponse, CreateProviderMappingDraftResponses, CreateProviderMappingError, CreateProviderMappingErrors, CreateProviderMappingObservationData, CreateProviderMappingObservationError, CreateProviderMappingObservationErrors, CreateProviderMappingObservationRequest, CreateProviderMappingObservationResponse, CreateProviderMappingObservationResponses, CreateProviderMappingRequest, CreateProviderMappingResponse, CreateProviderMappingResponses, CreateQaOverrideRequest, CreateQaOverrideRequestWritable, CreateReconciliationRunData, CreateReconciliationRunError, CreateReconciliationRunErrors, CreateReconciliationRunRequest, CreateReconciliationRunResponse, CreateReconciliationRunResponses, CreateReplayJobData, CreateReplayJobError, CreateReplayJobErrors, CreateReplayJobRequest, CreateReplayJobResponse, CreateReplayJobResponses, CreateStoreServerCredentialData, CreateStoreServerCredentialError, CreateStoreServerCredentialErrors, CreateStoreServerCredentialRequest, CreateStoreServerCredentialResponse, CreateStoreServerCredentialResponses, CreateWebhookDestinationData, CreateWebhookDestinationError, CreateWebhookDestinationErrors, CreateWebhookDestinationRequest, CreateWebhookDestinationResponse, CreateWebhookDestinationResponses, Cursor, CustomerAccessTokenIssuanceRequest, CustomerAccessTokenIssuanceResult, CustomerAccessTokenMetadata, CustomerEntitlementSnapshotRecord, DeleteProductData, DeleteProductError, DeleteProductErrors, DeleteProductResponse, DeleteProductResponses, DeleteWebhookDestinationData, DeleteWebhookDestinationError, DeleteWebhookDestinationErrors, DeleteWebhookDestinationResponse, DeleteWebhookDestinationResponses, DownloadAnalyticsJobData, DownloadAnalyticsJobError, DownloadAnalyticsJobErrors, DownloadAnalyticsJobResponse, DownloadAnalyticsJobResponses, Draft, DraftEnvelope, DraftId, DraftResource, EnqueueProviderSyncData, EnqueueProviderSyncError, EnqueueProviderSyncErrors, EnqueueProviderSyncResponse, EnqueueProviderSyncResponses, Entitlement, EntitlementCheckRequestRecord, EntitlementCheckResultRecord, EntitlementEntry, EntitlementEnvelope, EntitlementId, EntitlementList, EntitlementListEnvelope, EntitlementReferenceRequest, EntitlementSourceSummary, EntitlementSyncRequestRecord, Environment, EnvironmentEnvelope, EnvironmentId, EnvironmentList, EnvironmentListEnvelope, EnvironmentMode, ErrorEnvelope, Experiment, ExperimentDraft, ExperimentDraftDocument, ExperimentDraftEnvelope, ExperimentEnvelope, ExperimentGroup, ExperimentGroupCreated, ExperimentGroupCreatedEnvelope, ExperimentGroupListEnvelope, ExperimentGroupMemberInput, ExperimentGroupVersion, ExperimentGroupVersionEnvelope, ExperimentGroupVersionListEnvelope, ExperimentGuardrailMaturity, ExperimentGuardrailResult, ExperimentGuardrailVariantResult, ExperimentHistory, ExperimentHistoryListEnvelope, ExperimentId, ExperimentInterval, ExperimentLift, ExperimentListEnvelope, ExperimentMetricDefinition, ExperimentMetricListEnvelope, ExperimentQaOverride, ExperimentQaOverrideCreated, ExperimentQaOverrideCreatedEnvelope, ExperimentQaOverrideCreatedEnvelopeWritable, ExperimentQaOverrideCreatedWritable, ExperimentQaOverrideListEnvelope, ExperimentResults, ExperimentResultsEnvelope, ExperimentSchedule, ExperimentSrm, ExperimentValidation, ExperimentValidationEnvelope, ExperimentValidationIssue, ExperimentVariantDraft, ExperimentVariantResult, ExperimentVariantVersion, ExperimentVersion, ExperimentVersionEnvelope, ExperimentVersionListEnvelope, GetActivePaywallDraftData, GetActivePaywallDraftError, GetActivePaywallDraftErrors, GetActivePaywallDraftResponse, GetActivePaywallDraftResponses, GetActiveProviderAssignmentData, GetActiveProviderAssignmentError, GetActiveProviderAssignmentErrors, GetActiveProviderAssignmentResponse, GetActiveProviderAssignmentResponses, GetAnalyticsBreakdownData, GetAnalyticsBreakdownError, GetAnalyticsBreakdownErrors, GetAnalyticsBreakdownResponse, GetAnalyticsBreakdownResponses, GetAnalyticsFreshnessData, GetAnalyticsFreshnessError, GetAnalyticsFreshnessErrors, GetAnalyticsFreshnessResponse, GetAnalyticsFreshnessResponses, GetAnalyticsFunnelData, GetAnalyticsFunnelError, GetAnalyticsFunnelErrors, GetAnalyticsFunnelResponse, GetAnalyticsFunnelResponses, GetAnalyticsJobData, GetAnalyticsJobError, GetAnalyticsJobErrors, GetAnalyticsJobResponse, GetAnalyticsJobResponses, GetAnalyticsOverviewData, GetAnalyticsOverviewError, GetAnalyticsOverviewErrors, GetAnalyticsOverviewResponse, GetAnalyticsOverviewResponses, GetAnalyticsProductAvailabilityFailuresData, GetAnalyticsProductAvailabilityFailuresError, GetAnalyticsProductAvailabilityFailuresErrors, GetAnalyticsProductAvailabilityFailuresResponse, GetAnalyticsProductAvailabilityFailuresResponses, GetAnalyticsProviderErrorsData, GetAnalyticsProviderErrorsError, GetAnalyticsProviderErrorsErrors, GetAnalyticsProviderErrorsResponse, GetAnalyticsProviderErrorsResponses, GetAnalyticsSettingsData, GetAnalyticsSettingsError, GetAnalyticsSettingsErrors, GetAnalyticsSettingsResponse, GetAnalyticsSettingsResponses, GetAssetContentData, GetAssetContentError, GetAssetContentErrors, GetAssetContentResponse, GetAssetContentResponses, GetAssetData, GetAssetError, GetAssetErrors, GetAssetResponse, GetAssetResponses, GetAssetUsageData, GetAssetUsageError, GetAssetUsageErrors, GetAssetUsageResponse, GetAssetUsageResponses, GetBillingCustomerData, GetBillingCustomerEntitlementSnapshotData, GetBillingCustomerEntitlementSnapshotError, GetBillingCustomerEntitlementSnapshotErrors, GetBillingCustomerEntitlementSnapshotResponse, GetBillingCustomerEntitlementSnapshotResponses, GetBillingCustomerError, GetBillingCustomerErrors, GetBillingCustomerResponse, GetBillingCustomerResponses, GetBillingHealthData, GetBillingHealthError, GetBillingHealthErrors, GetBillingHealthResponse, GetBillingHealthResponses, GetBillingIdentityConflictData, GetBillingIdentityConflictError, GetBillingIdentityConflictErrors, GetBillingIdentityConflictResponse, GetBillingIdentityConflictResponses, GetBillingProjectionHealthData, GetBillingProjectionHealthError, GetBillingProjectionHealthErrors, GetBillingProjectionHealthResponse, GetBillingProjectionHealthResponses, GetBillingRestoreJobData, GetBillingRestoreJobError, GetBillingRestoreJobErrors, GetBillingRestoreJobResponse, GetBillingRestoreJobResponses, GetBillingSettingsData, GetBillingSettingsError, GetBillingSettingsErrors, GetBillingSettingsResponse, GetBillingSettingsResponses, GetBillingSubscriptionData, GetBillingSubscriptionError, GetBillingSubscriptionErrors, GetBillingSubscriptionResponse, GetBillingSubscriptionResponses, GetCustomerEntitlementSnapshotData, GetCustomerEntitlementSnapshotError, GetCustomerEntitlementSnapshotErrors, GetCustomerEntitlementSnapshotResponse, GetCustomerEntitlementSnapshotResponses, GetEntitlementData, GetEntitlementError, GetEntitlementErrors, GetEntitlementResponse, GetEntitlementResponses, GetExperimentData, GetExperimentError, GetExperimentErrors, GetExperimentResponse, GetExperimentResponses, GetExperimentResultsData, GetExperimentResultsResponse, GetExperimentResultsResponses, GetExperimentSampleRatioMismatchData, GetExperimentSampleRatioMismatchResponse, GetExperimentSampleRatioMismatchResponses, GetHealthData, GetHealthResponse, GetHealthResponses, GetNativeProviderProfileData, GetNativeProviderProfileError, GetNativeProviderProfileErrors, GetNativeProviderProfileResponse, GetNativeProviderProfileResponses, GetOperatorBillingCustomerData, GetOperatorBillingCustomerError, GetOperatorBillingCustomerErrors, GetOperatorBillingCustomerResponse, GetOperatorBillingCustomerResponses, GetOperatorBillingIdentityConflictData, GetOperatorBillingIdentityConflictError, GetOperatorBillingIdentityConflictErrors, GetOperatorBillingIdentityConflictResponse, GetOperatorBillingIdentityConflictResponses, GetOrganizationData, GetOrganizationError, GetOrganizationErrors, GetOrganizationResponse, GetOrganizationResponses, GetPaywallData, GetPaywallDraftData, GetPaywallDraftError, GetPaywallDraftErrors, GetPaywallDraftResponse, GetPaywallDraftResponses, GetPaywallError, GetPaywallErrors, GetPaywallResponse, GetPaywallResponses, GetPaywallVersionData, GetPaywallVersionError, GetPaywallVersionErrors, GetPaywallVersionResponse, GetPaywallVersionResponses, GetPlacementBindingData, GetPlacementBindingError, GetPlacementBindingErrors, GetPlacementBindingResponse, GetPlacementBindingResponses, GetPlacementDecisionData, GetPlacementDecisionError, GetPlacementDecisionErrors, GetPlacementDecisionResponse, GetPlacementDecisionResponses, GetPlacementUsageData, GetPlacementUsageError, GetPlacementUsageErrors, GetPlacementUsageResponse, GetPlacementUsageResponses, GetPlanData, GetPlanError, GetPlanErrors, GetPlanResponse, GetPlanResponses, GetProductData, GetProductEntitlementGrantVersionData, GetProductEntitlementGrantVersionError, GetProductEntitlementGrantVersionErrors, GetProductEntitlementGrantVersionResponse, GetProductEntitlementGrantVersionResponses, GetProductError, GetProductErrors, GetProductReadinessData, GetProductReadinessError, GetProductReadinessErrors, GetProductReadinessResponse, GetProductReadinessResponses, GetProductResponse, GetProductResponses, GetProductUsageData, GetProductUsageError, GetProductUsageErrors, GetProductUsageResponse, GetProductUsageResponses, GetProjectData, GetProjectError, GetProjectErrors, GetProjectResponse, GetProjectResponses, GetProviderConnectionCapabilitiesData, GetProviderConnectionCapabilitiesError, GetProviderConnectionCapabilitiesErrors, GetProviderConnectionCapabilitiesResponse, GetProviderConnectionCapabilitiesResponses, GetProviderConnectionData, GetProviderConnectionError, GetProviderConnectionErrors, GetProviderConnectionHealthData, GetProviderConnectionHealthError, GetProviderConnectionHealthErrors, GetProviderConnectionHealthResponse, GetProviderConnectionHealthResponses, GetProviderConnectionResponse, GetProviderConnectionResponses, GetProviderMappingMetadataData, GetProviderMappingMetadataError, GetProviderMappingMetadataErrors, GetProviderMappingMetadataResponse, GetProviderMappingMetadataResponses, GetProviderMappingUsageData, GetProviderMappingUsageError, GetProviderMappingUsageErrors, GetProviderMappingUsageResponse, GetProviderMappingUsageResponses, GetProviderReadinessData, GetProviderReadinessError, GetProviderReadinessErrors, GetProviderReadinessResponse, GetProviderReadinessResponses, GetQuarantineRecordData, GetQuarantineRecordError, GetQuarantineRecordErrors, GetQuarantineRecordResponse, GetQuarantineRecordResponses, GetReadinessData, GetReadinessError, GetReadinessErrors, GetReadinessResponse, GetReadinessResponses, GetSdkCommerceConfigurationData, GetSdkCommerceConfigurationError, GetSdkCommerceConfigurationErrors, GetSdkCommerceConfigurationResponse, GetSdkCommerceConfigurationResponses, GetSdkConfigurationData, GetSdkConfigurationError, GetSdkConfigurationErrors, GetSdkConfigurationResponse, GetSdkConfigurationResponses, GetSdkRestoreData, GetSdkRestoreError, GetSdkRestoreErrors, GetSdkRestoreResponse, GetSdkRestoreResponses, GetServerRestoreData, GetServerRestoreError, GetServerRestoreErrors, GetServerRestoreResponse, GetServerRestoreResponses, GetSessionData, GetSessionError, GetSessionErrors, GetSessionResponse, GetSessionResponses, GetStoreServerCredentialData, GetStoreServerCredentialError, GetStoreServerCredentialErrors, GetStoreServerCredentialResponse, GetStoreServerCredentialResponses, GetSubscriptionSnapshotData, GetSubscriptionSnapshotError, GetSubscriptionSnapshotErrors, GetSubscriptionSnapshotResponse, GetSubscriptionSnapshotResponses, GetWebhookDeliveryData, GetWebhookDeliveryError, GetWebhookDeliveryErrors, GetWebhookDeliveryResponse, GetWebhookDeliveryResponses, GetWebhookDestinationData, GetWebhookDestinationError, GetWebhookDestinationErrors, GetWebhookDestinationResponse, GetWebhookDestinationResponses, GrantAccessPolicy, GrantVersionImpact, HealthEnvelope, IdempotencyKey, IdentifyBillingCustomerData, IdentifyBillingCustomerError, IdentifyBillingCustomerErrors, IdentifyBillingCustomerRequest, IdentifyBillingCustomerResponse, IdentifyBillingCustomerResponses, IdentityConflictId, IfMatch, ImportProviderProductsData, ImportProviderProductsError, ImportProviderProductsErrors, ImportProviderProductsResponse, ImportProviderProductsResponses, IngestAnalyticsEventBatchData, IngestAnalyticsEventBatchError, IngestAnalyticsEventBatchErrors, IngestAnalyticsEventBatchResponse, IngestAnalyticsEventBatchResponses, IssueCustomerAccessTokenData, IssueCustomerAccessTokenError, IssueCustomerAccessTokenErrors, IssueCustomerAccessTokenResponse, IssueCustomerAccessTokenResponses, Limit, ListApiKeysData, ListApiKeysError, ListApiKeysErrors, ListApiKeysResponse, ListApiKeysResponses, ListApplicationsData, ListApplicationsError, ListApplicationsErrors, ListApplicationsResponse, ListApplicationsResponses, ListAssetsData, ListAssetsError, ListAssetsErrors, ListAssetsResponse, ListAssetsResponses, ListAuditEventsData, ListAuditEventsError, ListAuditEventsErrors, ListAuditEventsResponse, ListAuditEventsResponses, ListBillingCustomerAliasesData, ListBillingCustomerAliasesError, ListBillingCustomerAliasesErrors, ListBillingCustomerAliasesResponse, ListBillingCustomerAliasesResponses, ListBillingCustomersData, ListBillingCustomersError, ListBillingCustomersErrors, ListBillingCustomersResponse, ListBillingCustomersResponses, ListBillingCustomerSubscriptionsData, ListBillingCustomerSubscriptionsError, ListBillingCustomerSubscriptionsErrors, ListBillingCustomerSubscriptionsResponse, ListBillingCustomerSubscriptionsResponses, ListBillingIdentityConflictsData, ListBillingIdentityConflictsError, ListBillingIdentityConflictsErrors, ListBillingIdentityConflictsResponse, ListBillingIdentityConflictsResponses, ListBillingLedgerData, ListBillingLedgerError, ListBillingLedgerErrors, ListBillingLedgerResponse, ListBillingLedgerResponses, ListBillingQuarantineData, ListBillingQuarantineError, ListBillingQuarantineErrors, ListBillingQuarantineResponse, ListBillingQuarantineResponses, ListBillingRestoreJobsData, ListBillingRestoreJobsError, ListBillingRestoreJobsErrors, ListBillingRestoreJobsResponse, ListBillingRestoreJobsResponses, ListBillingSubscriptionTimelineData, ListBillingSubscriptionTimelineError, ListBillingSubscriptionTimelineErrors, ListBillingSubscriptionTimelineResponse, ListBillingSubscriptionTimelineResponses, ListConfigurationReleasesData, ListConfigurationReleasesError, ListConfigurationReleasesErrors, ListConfigurationReleasesResponse, ListConfigurationReleasesResponses, ListCustomerAccessTokensData, ListCustomerAccessTokensError, ListCustomerAccessTokensErrors, ListCustomerAccessTokensResponse, ListCustomerAccessTokensResponses, ListCustomerSubscriptionsData, ListCustomerSubscriptionsError, ListCustomerSubscriptionsErrors, ListCustomerSubscriptionsResponse, ListCustomerSubscriptionsResponses, ListEntitlementsData, ListEntitlementsError, ListEntitlementsErrors, ListEntitlementsResponse, ListEntitlementsResponses, ListEnvironmentsData, ListEnvironmentsError, ListEnvironmentsErrors, ListEnvironmentsResponse, ListEnvironmentsResponses, ListExperimentGroupsData, ListExperimentGroupsResponse, ListExperimentGroupsResponses, ListExperimentHistoryData, ListExperimentHistoryResponse, ListExperimentHistoryResponses, ListExperimentMetricDefinitionsData, ListExperimentMetricDefinitionsResponse, ListExperimentMetricDefinitionsResponses, ListExperimentMutualExclusionGroupVersionsData, ListExperimentMutualExclusionGroupVersionsError, ListExperimentMutualExclusionGroupVersionsErrors, ListExperimentMutualExclusionGroupVersionsResponse, ListExperimentMutualExclusionGroupVersionsResponses, ListExperimentQaOverridesData, ListExperimentQaOverridesResponse, ListExperimentQaOverridesResponses, ListExperimentsData, ListExperimentsResponse, ListExperimentsResponses, ListExperimentVersionsData, ListExperimentVersionsResponse, ListExperimentVersionsResponses, ListMembersData, ListMembersError, ListMembersErrors, ListMembersResponse, ListMembersResponses, ListOperatorBillingIdentityConflictsData, ListOperatorBillingIdentityConflictsError, ListOperatorBillingIdentityConflictsErrors, ListOperatorBillingIdentityConflictsResponse, ListOperatorBillingIdentityConflictsResponses, ListOrganizationsData, ListOrganizationsError, ListOrganizationsErrors, ListOrganizationsResponse, ListOrganizationsResponses, ListPaywallsData, ListPaywallsError, ListPaywallsErrors, ListPaywallsResponse, ListPaywallsResponses, ListPaywallVersionsData, ListPaywallVersionsError, ListPaywallVersionsErrors, ListPaywallVersionsResponse, ListPaywallVersionsResponses, ListPlacementAliasesData, ListPlacementAliasesResponse, ListPlacementAliasesResponses, ListPlacementAttributesData, ListPlacementAttributesError, ListPlacementAttributesErrors, ListPlacementAttributesResponse, ListPlacementAttributesResponses, ListPlacementQaOverridesData, ListPlacementQaOverridesResponse, ListPlacementQaOverridesResponses, ListPlacementRuleSetVersionsData, ListPlacementRuleSetVersionsResponse, ListPlacementRuleSetVersionsResponses, ListPlacementsData, ListPlacementsError, ListPlacementsErrors, ListPlacementsResponse, ListPlacementsResponses, ListPlanProductsData, ListPlanProductsError, ListPlanProductsErrors, ListPlanProductsResponse, ListPlanProductsResponses, ListPlansData, ListPlansError, ListPlansErrors, ListPlansResponse, ListPlansResponses, ListProductEntitlementGrantVersionsData, ListProductEntitlementGrantVersionsError, ListProductEntitlementGrantVersionsErrors, ListProductEntitlementGrantVersionsResponse, ListProductEntitlementGrantVersionsResponses, ListProductEntitlementsData, ListProductEntitlementsError, ListProductEntitlementsErrors, ListProductEntitlementsResponse, ListProductEntitlementsResponses, ListProductsData, ListProductsError, ListProductsErrors, ListProductsResponse, ListProductsResponses, ListProjectsData, ListProjectsError, ListProjectsErrors, ListProjectsResponse, ListProjectsResponses, ListProviderConnectionDiagnosticsData, ListProviderConnectionDiagnosticsError, ListProviderConnectionDiagnosticsErrors, ListProviderConnectionDiagnosticsResponse, ListProviderConnectionDiagnosticsResponses, ListProviderConnectionsData, ListProviderConnectionsError, ListProviderConnectionsErrors, ListProviderConnectionsResponse, ListProviderConnectionsResponses, ListProviderMappingObservationsData, ListProviderMappingObservationsError, ListProviderMappingObservationsErrors, ListProviderMappingObservationsResponse, ListProviderMappingObservationsResponses, ListProviderMappingsData, ListProviderMappingsError, ListProviderMappingsErrors, ListProviderMappingsResponse, ListProviderMappingsResponses, ListProviderSyncRunsData, ListProviderSyncRunsError, ListProviderSyncRunsErrors, ListProviderSyncRunsResponse, ListProviderSyncRunsResponses, ListReconciliationRunsData, ListReconciliationRunsError, ListReconciliationRunsErrors, ListReconciliationRunsResponse, ListReconciliationRunsResponses, ListReplayJobsData, ListReplayJobsError, ListReplayJobsErrors, ListReplayJobsResponse, ListReplayJobsResponses, ListStoreServerCredentialsData, ListStoreServerCredentialsError, ListStoreServerCredentialsErrors, ListStoreServerCredentialsResponse, ListStoreServerCredentialsResponses, ListSubscriptionTimelineData, ListSubscriptionTimelineError, ListSubscriptionTimelineErrors, ListSubscriptionTimelineResponse, ListSubscriptionTimelineResponses, ListTransactionFactsData, ListTransactionFactsError, ListTransactionFactsErrors, ListTransactionFactsResponse, ListTransactionFactsResponses, ListValidationAttemptsData, ListValidationAttemptsError, ListValidationAttemptsErrors, ListValidationAttemptsResponse, ListValidationAttemptsResponses, ListWebhookDeliveriesData, ListWebhookDeliveriesError, ListWebhookDeliveriesErrors, ListWebhookDeliveriesResponse, ListWebhookDeliveriesResponses, ListWebhookDeliveryAttemptsData, ListWebhookDeliveryAttemptsError, ListWebhookDeliveryAttemptsErrors, ListWebhookDeliveryAttemptsResponse, ListWebhookDeliveryAttemptsResponses, ListWebhookDestinationsData, ListWebhookDestinationsError, ListWebhookDestinationsErrors, ListWebhookDestinationsResponse, ListWebhookDestinationsResponses, ListWebhookSigningSecretsData, ListWebhookSigningSecretsError, ListWebhookSigningSecretsErrors, ListWebhookSigningSecretsResponse, ListWebhookSigningSecretsResponses, LoginData, LoginError, LoginErrors, LoginRequest, LoginResponse, LoginResponses, LogoutData, LogoutResponse, LogoutResponses, LookupBillingCustomerData, LookupBillingCustomerError, LookupBillingCustomerErrors, LookupBillingCustomerResponse, LookupBillingCustomerResponses, Membership, MembershipEnvelope, MembershipList, MembershipListEnvelope, MetadataSource, ObservationContext, ObservationCorrelation, ObservationSubmissionResult, ObservationSubmissionResultRecord, OperatorBillingCustomerAlias, OperatorBillingIdentityConflict, OperatorBillingIdentityConflictDetail, OperatorBillingSyncRequest, Organization, OrganizationEnvelope, OrganizationId, OrganizationList, OrganizationListEnvelope, Page, Paywall, PaywallEnvelope, PaywallId, PaywallListEnvelope, PaywallVersion, PaywallVersionEnvelope, PaywallVersionListEnvelope, Placement, PlacementAlias, PlacementAliasEnvelope, PlacementAliasListEnvelope, PlacementAttribute, PlacementAttributeEnvelope, PlacementAttributeListEnvelope, PlacementBinding, PlacementBindingEnvelope, PlacementDecisionDocument, PlacementDecisionDocumentRequest, PlacementEnvelope, PlacementId, PlacementListEnvelope, PlacementOutcome, PlacementRuleSet, PlacementRuleSetDraft, PlacementRuleSetDraftEnvelope, PlacementRuleSetDraftResource, PlacementRuleSetVersion, PlacementRuleSetVersionEnvelope, PlacementRuleSetVersionListEnvelope, PlacementSimulationEnvelope, PlacementSimulationRequest, PlacementSimulationRequestWritable, PlacementSimulationResult, PlacementUsage, PlacementUsageEnvelope, PlacementValidation, PlacementValidationEnvelope, PlacementValidationIssue, Plan, PlanEnvelope, PlanId, PlanList, PlanListEnvelope, PlanProduct, PlanProductEnvelope, Platform, PreviewAnalyticsPrivacyRequestData, PreviewAnalyticsPrivacyRequestError, PreviewAnalyticsPrivacyRequestErrors, PreviewAnalyticsPrivacyRequestResponse, PreviewAnalyticsPrivacyRequestResponses, PreviewProductEntitlementGrantImpactData, PreviewProductEntitlementGrantImpactError, PreviewProductEntitlementGrantImpactErrors, PreviewProductEntitlementGrantImpactResponse, PreviewProductEntitlementGrantImpactResponses, PreviewProviderCatalogData, PreviewProviderCatalogError, PreviewProviderCatalogErrors, PreviewProviderCatalogResponse, PreviewProviderCatalogResponses, PrimaryExplanation, Product, ProductEntitlementGrant, ProductEntitlementGrantEnvelope, ProductEntitlementGrantVersion, ProductEnvelope, ProductId, ProductList, ProductListEnvelope, ProductReadiness, ProductReadinessEnvelope, ProductReferenceRequest, ProductStatus, ProductType, ProductUsage, ProductUsageEnvelope, Project, ProjectEnvelope, ProjectId, ProjectionReplayResult, ProjectionStatus, ProjectList, ProjectListEnvelope, ProjectStatus, ProviderActivationKind, ProviderAssignmentEnvelope, ProviderAvailability, ProviderCapability, ProviderCatalogApplication, ProviderCatalogEntitlement, ProviderCatalogOffering, ProviderCatalogPackage, ProviderCatalogPreview, ProviderCatalogProduct, ProviderConnection, ProviderConnectionCapabilities, ProviderConnectionEnvelope, ProviderConnectionHealth, ProviderConnectionId, ProviderConnectionKind, ProviderConnectionList, ProviderConnectionListEnvelope, ProviderConnectionMode, ProviderConnectionStatus, ProviderCredential, ProviderCredentialRequest, ProviderDiagnostic, ProviderEntitlementImportRequest, ProviderErrorCode, ProviderHealthStatus, ProviderImport, ProviderImportItem, ProviderImportRequest, ProviderImportResult, ProviderIntegrationMode, ProviderKind, ProviderMappingEnvelope, ProviderMappingId, ProviderMappingList, ProviderMappingListEnvelope, ProviderMappingObservation, ProviderMappingObservationEnvelope, ProviderMappingObservationMetadata, ProviderMappingStatus, ProviderMappingUsage, ProviderMappingUsageEnvelope, ProviderOrderReference, ProviderProductImportItemRequest, ProviderProductMapping, ProviderProductMetadataSnapshot, ProviderProfile, ProviderProfileEnvelope, ProviderReadiness, ProviderReadinessEnvelope, ProviderReadinessIssue, ProviderSyncJob, ProviderSyncRun, ProviderSyncState, PublishConfigurationData, PublishConfigurationError, PublishConfigurationErrors, PublishConfigurationResponse, PublishConfigurationResponses, PublishExperimentData, PublishExperimentError, PublishExperimentErrors, PublishExperimentRequest, PublishExperimentResponse, PublishExperimentResponses, PublishGrantVersionRequest, PublishPlacementRuleSetData, PublishPlacementRuleSetError, PublishPlacementRuleSetErrors, PublishPlacementRuleSetResponse, PublishPlacementRuleSetResponses, PublishProductEntitlementGrantVersionData, PublishProductEntitlementGrantVersionError, PublishProductEntitlementGrantVersionErrors, PublishProductEntitlementGrantVersionResponse, PublishProductEntitlementGrantVersionResponses, PublishRequest, PublishResult, PublishResultEnvelope, QaOverride, QaOverrideCreated, QaOverrideCreatedEnvelope, QaOverrideListEnvelope, QuarantineRecord, QuarantineRecordId, ReceiveAppleStoreNotificationData, ReceiveAppleStoreNotificationError, ReceiveAppleStoreNotificationErrors, ReceiveAppleStoreNotificationResponse, ReceiveAppleStoreNotificationResponses, ReconciliationRun, ReconnectProviderConnectionData, ReconnectProviderConnectionError, ReconnectProviderConnectionErrors, ReconnectProviderConnectionResponse, ReconnectProviderConnectionResponses, ReleaseEnvelope, ReleaseId, ReleaseListEnvelope, RemoveMemberData, RemoveMemberError, RemoveMemberErrors, RemoveMemberResponse, RemoveMemberResponses, RemovePlanProductData, RemovePlanProductError, RemovePlanProductErrors, RemovePlanProductResponse, RemovePlanProductResponses, RemoveProductEntitlementData, RemoveProductEntitlementError, RemoveProductEntitlementErrors, RemoveProductEntitlementResponse, RemoveProductEntitlementResponses, ReplaceProviderConnectionScopesData, ReplaceProviderConnectionScopesError, ReplaceProviderConnectionScopesErrors, ReplaceProviderConnectionScopesRequest, ReplaceProviderConnectionScopesResponse, ReplaceProviderConnectionScopesResponses, ReplaceProviderMappingData, ReplaceProviderMappingError, ReplaceProviderMappingErrors, ReplaceProviderMappingRequest, ReplaceProviderMappingResponse, ReplaceProviderMappingResponses, ReplayJob, ReplayWebhookDeliveryData, ReplayWebhookDeliveryError, ReplayWebhookDeliveryErrors, ReplayWebhookDeliveryResponse, ReplayWebhookDeliveryResponses, RequestBillingCustomerSyncData, RequestBillingCustomerSyncError, RequestBillingCustomerSyncErrors, RequestBillingCustomerSyncResponse, RequestBillingCustomerSyncResponses, ResolveBillingIdentityConflictData, ResolveBillingIdentityConflictError, ResolveBillingIdentityConflictErrors, ResolveBillingIdentityConflictResponse, ResolveBillingIdentityConflictResponses, ResolveIdentityConflictRequest, RestoreJobId, RestoreProductData, RestoreProductError, RestoreProductErrors, RestoreProductResponse, RestoreProductResponses, RestoreProjectData, RestoreProjectError, RestoreProjectErrors, RestoreProjectResponse, RestoreProjectResponses, RestoreRequestRecord, RestoreResultRecord, RetireWebhookSigningSecretData, RetireWebhookSigningSecretError, RetireWebhookSigningSecretErrors, RetireWebhookSigningSecretResponse, RetireWebhookSigningSecretResponses, RetryQuarantinedInputData, RetryQuarantinedInputError, RetryQuarantinedInputErrors, RetryQuarantinedInputResponse, RetryQuarantinedInputResponses, RevokeApiKeyData, RevokeApiKeyError, RevokeApiKeyErrors, RevokeApiKeyResponse, RevokeApiKeyResponses, RevokeBillingCustomerAliasData, RevokeBillingCustomerAliasError, RevokeBillingCustomerAliasErrors, RevokeBillingCustomerAliasResponse, RevokeBillingCustomerAliasResponses, RevokeCustomerAccessTokenData, RevokeCustomerAccessTokenError, RevokeCustomerAccessTokenErrors, RevokeCustomerAccessTokenResponse, RevokeCustomerAccessTokenResponses, RevokeExperimentQaOverrideData, RevokeExperimentQaOverrideError, RevokeExperimentQaOverrideErrors, RevokeExperimentQaOverrideResponse, RevokeExperimentQaOverrideResponses, RevokePlacementQaOverrideData, RevokePlacementQaOverrideError, RevokePlacementQaOverrideErrors, RevokePlacementQaOverrideResponse, RevokePlacementQaOverrideResponses, RevokeProviderConnectionData, RevokeProviderConnectionError, RevokeProviderConnectionErrors, RevokeProviderConnectionResponse, RevokeProviderConnectionResponses, RevokeStoreServerCredentialData, RevokeStoreServerCredentialError, RevokeStoreServerCredentialErrors, RevokeStoreServerCredentialResponse, RevokeStoreServerCredentialResponses, Role, RollbackConfigurationReleaseData, RollbackConfigurationReleaseError, RollbackConfigurationReleaseErrors, RollbackConfigurationReleaseResponse, RollbackConfigurationReleaseResponses, RotateApiKeyData, RotateApiKeyError, RotateApiKeyErrors, RotateApiKeyResponse, RotateApiKeyResponses, RotateProviderCredentialData, RotateProviderCredentialError, RotateProviderCredentialErrors, RotateProviderCredentialResponse, RotateProviderCredentialResponses, RotateStoreServerCredentialData, RotateStoreServerCredentialError, RotateStoreServerCredentialErrors, RotateStoreServerCredentialResponse, RotateStoreServerCredentialResponses, RotateWebhookSigningSecretData, RotateWebhookSigningSecretError, RotateWebhookSigningSecretErrors, RotateWebhookSigningSecretResponse, RotateWebhookSigningSecretResponses, RuleSetId, ServerTransactionObservation, ServerTransactionObservationRecord, SetActiveProviderAssignmentData, SetActiveProviderAssignmentError, SetActiveProviderAssignmentErrors, SetActiveProviderAssignmentResponse, SetActiveProviderAssignmentResponses, SetEnvironmentModeData, SetEnvironmentModeError, SetEnvironmentModeErrors, SetEnvironmentModeRequest, SetEnvironmentModeResponse, SetEnvironmentModeResponses, SetProductReplacementData, SetProductReplacementError, SetProductReplacementErrors, SetProductReplacementResponse, SetProductReplacementResponses, SetProviderAssignmentRequest, SetWebhookDestinationStatusData, SetWebhookDestinationStatusError, SetWebhookDestinationStatusErrors, SetWebhookDestinationStatusRequest, SetWebhookDestinationStatusResponse, SetWebhookDestinationStatusResponses, SignUpData, SignUpError, SignUpErrors, SignUpRequest, SignUpResponse, SignUpResponses, SimulatePlacementDecisionData, SimulatePlacementDecisionError, SimulatePlacementDecisionErrors, SimulatePlacementDecisionResponse, SimulatePlacementDecisionResponses, StoreCredentialId, StoreEnvironmentClassification, StoreServerCredential, StoreServerCredentialApplication, StoreServerCredentialWithEndpoint, SubmitSdkRestoreData, SubmitSdkRestoreError, SubmitSdkRestoreErrors, SubmitSdkRestoreResponse, SubmitSdkRestoreResponses, SubmitServerRestoreData, SubmitServerRestoreError, SubmitServerRestoreErrors, SubmitServerRestoreResponse, SubmitServerRestoreResponses, SubmitServerTransactionObservationData, SubmitServerTransactionObservationError, SubmitServerTransactionObservationErrors, SubmitServerTransactionObservationResponse, SubmitServerTransactionObservationResponses, SubmitTransactionObservationData, SubmitTransactionObservationError, SubmitTransactionObservationErrors, SubmitTransactionObservationResponse, SubmitTransactionObservationResponses, SubscriptionInstanceId, SubscriptionSnapshotRecord, SubscriptionSummary, SubscriptionTimelineEntry, SyncCustomerEntitlementsData, SyncCustomerEntitlementsError, SyncCustomerEntitlementsErrors, SyncCustomerEntitlementsResponse, SyncCustomerEntitlementsResponses, SyncCustomerEntitlementsWithNegotiationData, SyncCustomerEntitlementsWithNegotiationError, SyncCustomerEntitlementsWithNegotiationErrors, SyncCustomerEntitlementsWithNegotiationResponse, SyncCustomerEntitlementsWithNegotiationResponses, TestProviderConnectionData, TestProviderConnectionError, TestProviderConnectionErrors, TestProviderConnectionResponse, TestProviderConnectionResponses, TestStoreServerCredentialData, TestStoreServerCredentialError, TestStoreServerCredentialErrors, TestStoreServerCredentialResponse, TestStoreServerCredentialResponses, Timestamp, TransactionFact, TransactionReference, TransitionExperimentLifecycleData, TransitionExperimentLifecycleError, TransitionExperimentLifecycleErrors, TransitionExperimentLifecycleResponse, TransitionExperimentLifecycleResponses, Uncertainty, UpdateAnalyticsSettingsData, UpdateAnalyticsSettingsError, UpdateAnalyticsSettingsErrors, UpdateAnalyticsSettingsRequest, UpdateAnalyticsSettingsResponse, UpdateAnalyticsSettingsResponses, UpdateBillingSettingsData, UpdateBillingSettingsError, UpdateBillingSettingsErrors, UpdateBillingSettingsResponse, UpdateBillingSettingsResponses, UpdateDraftRequest, UpdateEntitlementData, UpdateEntitlementError, UpdateEntitlementErrors, UpdateEntitlementResponse, UpdateEntitlementResponses, UpdateEnvironmentData, UpdateEnvironmentError, UpdateEnvironmentErrors, UpdateEnvironmentResponse, UpdateEnvironmentResponses, UpdateExperimentDraftData, UpdateExperimentDraftError, UpdateExperimentDraftErrors, UpdateExperimentDraftRequest, UpdateExperimentDraftResponse, UpdateExperimentDraftResponses, UpdateMemberData, UpdateMemberError, UpdateMemberErrors, UpdateMemberRequest, UpdateMemberResponse, UpdateMemberResponses, UpdateNameRequest, UpdateOrganizationData, UpdateOrganizationError, UpdateOrganizationErrors, UpdateOrganizationRequest, UpdateOrganizationResponse, UpdateOrganizationResponses, UpdatePaywallData, UpdatePaywallDraftData, UpdatePaywallDraftError, UpdatePaywallDraftErrors, UpdatePaywallDraftResponse, UpdatePaywallDraftResponses, UpdatePaywallError, UpdatePaywallErrors, UpdatePaywallRequest, UpdatePaywallResponse, UpdatePaywallResponses, UpdatePlacementData, UpdatePlacementError, UpdatePlacementErrors, UpdatePlacementRequest, UpdatePlacementResponse, UpdatePlacementResponses, UpdatePlacementRuleSetDraftData, UpdatePlacementRuleSetDraftError, UpdatePlacementRuleSetDraftErrors, UpdatePlacementRuleSetDraftResponse, UpdatePlacementRuleSetDraftResponses, UpdatePlanData, UpdatePlanError, UpdatePlanErrors, UpdatePlanResponse, UpdatePlanResponses, UpdateProductData, UpdateProductEntitlementGrantVersionData, UpdateProductEntitlementGrantVersionError, UpdateProductEntitlementGrantVersionErrors, UpdateProductError, UpdateProductErrors, UpdateProductResponse, UpdateProductResponses, UpdateProjectData, UpdateProjectError, UpdateProjectErrors, UpdateProjectResponse, UpdateProjectResponses, UpdateWebhookDestinationData, UpdateWebhookDestinationError, UpdateWebhookDestinationErrors, UpdateWebhookDestinationRequest, UpdateWebhookDestinationResponse, UpdateWebhookDestinationResponses, UploadAssetData, UploadAssetError, UploadAssetErrors, UploadAssetResponse, UploadAssetResponses, User, UserEnvelope, ValidateExperimentDraftData, ValidateExperimentDraftResponse, ValidateExperimentDraftResponses, ValidatePaywallDraftData, ValidatePaywallDraftError, ValidatePaywallDraftErrors, ValidatePaywallDraftResponse, ValidatePaywallDraftResponses, ValidatePlacementRuleSetData, ValidatePlacementRuleSetResponse, ValidatePlacementRuleSetResponses, ValidationAttempt, ValidationSummary, ValidationSummaryEnvelope, VersionId, WebhookDelivery, WebhookDeliveryAttempt, WebhookDeliveryId, WebhookDestination, WebhookDestinationId, WebhookDestinationWithSecret, WebhookSigningSecretMetadata } from './types.gen'; +export { addMember, addPlanProduct, addProductEntitlement, approveBillingMigrationLegalHold, approveBillingMigrationProposal, archiveAsset, archivePlacementAttribute, archivePlacementRuleSet, archivePlacementWithUsageCheck, archiveProduct, archiveProject, archiveProviderMapping, assessBillingMigrationReadiness, assessBillingMigrationRollbackReadiness, attachBillingCustomerAlias, bindPlacement, checkCustomerEntitlements, clearActiveProviderAssignment, clonePaywallVersionToDraft, clonePlacementRuleSetVersion, closeQuarantineRecordSuperseded, compareAnalyticsPaywallVersions, completeBillingMigration, createAnalyticsEventExport, createAnalyticsPrivacyDeletion, createAnalyticsPrivacyExport, createApiKey, createApplication, createBillingCustomerSyncRequest, createBillingMigrationCase, createBillingMigrationCheckpoint, createBillingMigrationImportBatch, createBillingMigrationMappingSet, createBillingMigrationProgram, createBillingProjectionReplay, createEntitlement, createExperiment, createExperimentGroupVersion, createExperimentMutualExclusionGroupVersion, createExperimentQaOverride, createExperimentRawExport, createOrganization, createPaywall, createPaywallDraft, createPlacement, createPlacementAlias, createPlacementAttribute, createPlacementQaOverride, createPlacementRuleSet, createPlan, createProduct, createProject, createProviderConnection, createProviderMapping, createProviderMappingDraft, createProviderMappingObservation, createReconciliationRun, createReplayJob, createStoreServerCredential, createWebhookDestination, deleteProduct, deleteWebhookDestination, downloadAnalyticsJob, enqueueProviderSync, executeBillingMigrationCutover, executeBillingMigrationRepair, executeBillingMigrationRollback, freezeBillingMigrationMappingSet, freezeBillingMigrationStabilizationPolicy, getActivePaywallDraft, getActiveProviderAssignment, getAnalyticsBreakdown, getAnalyticsFreshness, getAnalyticsFunnel, getAnalyticsJob, getAnalyticsOverview, getAnalyticsProductAvailabilityFailures, getAnalyticsProviderErrors, getAnalyticsSettings, getAsset, getAssetContent, getAssetUsage, getBillingCustomer, getBillingCustomerEntitlementSnapshot, getBillingHealth, getBillingIdentityConflict, getBillingMigrationApproval, getBillingMigrationAuthorityExecution, getBillingMigrationCase, getBillingMigrationCheckpoint, getBillingMigrationCompletionReport, getBillingMigrationCredentialRemoval, getBillingMigrationImportBatch, getBillingMigrationLegalHold, getBillingMigrationLegalHoldProposal, getBillingMigrationProgram, getBillingMigrationProposal, getBillingMigrationRepairExecution, getBillingMigrationRepairPreview, getBillingMigrationRunJob, getBillingMigrationSourcePull, getBillingMigrationWebhookRedelivery, getBillingProjectionHealth, getBillingRestoreJob, getBillingSettings, getBillingSubscription, getCurrentBillingMigrationCredentialRemoval, getCurrentBillingMigrationLegalHold, getCurrentBillingMigrationStabilizationPolicy, getCustomerEntitlementSnapshot, getEntitlement, getExperiment, getExperimentResults, getExperimentSampleRatioMismatch, getHealth, getLatestBillingMigrationCheckpoint, getLatestBillingMigrationReadiness, getLatestBillingMigrationRollbackReadinessAssessment, getLatestBillingMigrationRollbackReadinessCheckpoint, getLatestBillingMigrationStabilizationObservation, getNativeProviderProfile, getOperatorBillingCustomer, getOperatorBillingIdentityConflict, getOrganization, getPaywall, getPaywallDraft, getPaywallVersion, getPlacementBinding, getPlacementDecision, getPlacementUsage, getPlan, getProduct, getProductEntitlementGrantVersion, getProductReadiness, getProductUsage, getProject, getProviderConnection, getProviderConnectionCapabilities, getProviderConnectionHealth, getProviderMappingMetadata, getProviderMappingUsage, getProviderReadiness, getQuarantineRecord, getReadiness, getSdkCommerceConfiguration, getSdkConfiguration, getSdkRestore, getServerRestore, getSession, getStoreServerCredential, getSubscriptionSnapshot, getWebhookDelivery, getWebhookDestination, getWorkspaceBootstrap, identifyBillingCustomer, importProviderProducts, ingestAnalyticsEventBatch, inspectBillingMigrationCompletion, issueCustomerAccessToken, listApiKeys, listApplications, listAssets, listAuditEvents, listBillingCustomerAliases, listBillingCustomers, listBillingCustomerSubscriptions, listBillingIdentityConflicts, listBillingLedger, listBillingMigrationApprovals, listBillingMigrationAuthorityExecutions, listBillingMigrationCaseActions, listBillingMigrationCases, listBillingMigrationCheckpoints, listBillingMigrationCompletionHistory, listBillingMigrationCredentialRemovals, listBillingMigrationDivergences, listBillingMigrationImportBatches, listBillingMigrationLegalHoldProposals, listBillingMigrationLegalHolds, listBillingMigrationMappingSets, listBillingMigrationPrograms, listBillingMigrationProposals, listBillingMigrationRepairExecutions, listBillingMigrationRepairPreviews, listBillingMigrationRollbackReadinessAssessments, listBillingMigrationSourceManifests, listBillingMigrationSourcePulls, listBillingMigrationStabilizationObservations, listBillingMigrationWebhookRedeliveries, listBillingQuarantine, listBillingRestoreJobs, listBillingSubscriptionTimeline, listConfigurationReleases, listCustomerAccessTokens, listCustomerSubscriptions, listEntitlements, listEnvironments, listExperimentGroups, listExperimentHistory, listExperimentMetricDefinitions, listExperimentMutualExclusionGroupVersions, listExperimentQaOverrides, listExperiments, listExperimentVersions, listMembers, listOperatorBillingIdentityConflicts, listOrganizations, listPaywalls, listPaywallVersions, listPlacementAliases, listPlacementAttributes, listPlacementQaOverrides, listPlacementRuleSetVersions, listPlacements, listPlanProducts, listPlans, listProductEntitlementGrantVersions, listProductEntitlements, listProducts, listProjects, listProviderConnectionDiagnostics, listProviderConnections, listProviderMappingObservations, listProviderMappings, listProviderSyncRuns, listReconciliationRuns, listReplayJobs, listStoreServerCredentials, listSubscriptionTimeline, listTransactionFacts, listValidationAttempts, listWebhookDeliveries, listWebhookDeliveryAttempts, listWebhookDestinations, listWebhookSigningSecrets, login, logout, lookupBillingCustomer, observeBillingMigrationStabilization, type Options, previewAnalyticsPrivacyRequest, previewBillingMigrationRepair, previewProductEntitlementGrantImpact, previewProviderCatalog, promoteBillingMigrationReady, proposeBillingMigrationCutover, proposeBillingMigrationLegalHold, proposeBillingMigrationRollback, publishConfiguration, publishExperiment, publishPlacementRuleSet, publishProductEntitlementGrantVersion, queueBillingMigrationDryRun, queueBillingMigrationShadowRun, queueBillingMigrationSourcePull, receiveAppleStoreNotification, reconnectProviderConnection, redeliverBillingMigrationWebhook, removeBillingMigrationCredential, removeMember, removePlanProduct, removeProductEntitlement, replaceProviderConnectionScopes, replaceProviderMapping, replayWebhookDelivery, requestBillingCustomerSync, resolveBillingIdentityConflict, restoreProduct, restoreProject, retireWebhookSigningSecret, retryQuarantinedInput, revokeApiKey, revokeBillingCustomerAlias, revokeCustomerAccessToken, revokeExperimentQaOverride, revokePlacementQaOverride, revokeProviderConnection, revokeStoreServerCredential, rollbackConfigurationRelease, rotateApiKey, rotateProviderCredential, rotateStoreServerCredential, rotateWebhookSigningSecret, setActiveProviderAssignment, setEnvironmentMode, setProductReplacement, setWebhookDestinationStatus, signUp, simulatePlacementDecision, submitSdkRestore, submitServerRestore, submitServerTransactionObservation, submitTransactionObservation, syncCustomerEntitlements, syncCustomerEntitlementsWithNegotiation, testProviderConnection, testStoreServerCredential, transitionBillingMigrationCase, transitionExperimentLifecycle, updateAnalyticsSettings, updateBillingSettings, updateEntitlement, updateEnvironment, updateExperimentDraft, updateMember, updateOrganization, updatePaywall, updatePaywallDraft, updatePlacement, updatePlacementRuleSetDraft, updatePlan, updateProduct, updateProductEntitlementGrantVersion, updateProject, updateWebhookDestination, uploadAsset, validateExperimentDraft, validatePaywallDraft, validatePlacementRuleSet } from './sdk.gen'; +export type { ActiveProviderAssignment, ActorId, AddMemberData, AddMemberError, AddMemberErrors, AddMemberRequest, AddMemberResponse, AddMemberResponses, AddPlanProductData, AddPlanProductError, AddPlanProductErrors, AddPlanProductResponse, AddPlanProductResponses, AddProductEntitlementData, AddProductEntitlementError, AddProductEntitlementErrors, AddProductEntitlementResponse, AddProductEntitlementResponses, AnalyticsApplicationVersion, AnalyticsEventBatch, AnalyticsEventResult, AnalyticsFreshness, AnalyticsFrom, AnalyticsIdentityRequest, AnalyticsIngestionResult, AnalyticsJob, AnalyticsJobEnvelope, AnalyticsLocale, AnalyticsMetric, AnalyticsMetricBasis, AnalyticsPlatform, AnalyticsPrivacyPreview, AnalyticsPrivacyPreviewEnvelope, AnalyticsResult, AnalyticsResultEnvelope, AnalyticsSettings, AnalyticsSettingsEnvelope, AnalyticsTimezone, AnalyticsTo, ApiKey, ApiKeyEnvelope, ApiKeyId, ApiKeyKind, ApiKeyList, ApiKeyListEnvelope, ApiKeySecretEnvelope, ApiKeySecretResult, Application, ApplicationEnvelope, ApplicationId, ApplicationList, ApplicationListEnvelope, ApproveBillingMigrationLegalHoldData, ApproveBillingMigrationLegalHoldError, ApproveBillingMigrationLegalHoldErrors, ApproveBillingMigrationLegalHoldResponse, ApproveBillingMigrationLegalHoldResponses, ApproveBillingMigrationProposalData, ApproveBillingMigrationProposalError, ApproveBillingMigrationProposalErrors, ApproveBillingMigrationProposalResponse, ApproveBillingMigrationProposalResponses, ArchiveAssetData, ArchiveAssetError, ArchiveAssetErrors, ArchiveAssetResponse, ArchiveAssetResponses, ArchivePlacementAttributeData, ArchivePlacementAttributeError, ArchivePlacementAttributeErrors, ArchivePlacementAttributeResponse, ArchivePlacementAttributeResponses, ArchivePlacementRuleSetData, ArchivePlacementRuleSetError, ArchivePlacementRuleSetErrors, ArchivePlacementRuleSetResponse, ArchivePlacementRuleSetResponses, ArchivePlacementWithUsageCheckData, ArchivePlacementWithUsageCheckError, ArchivePlacementWithUsageCheckErrors, ArchivePlacementWithUsageCheckResponse, ArchivePlacementWithUsageCheckResponses, ArchiveProductData, ArchiveProductError, ArchiveProductErrors, ArchiveProductResponse, ArchiveProductResponses, ArchiveProjectData, ArchiveProjectError, ArchiveProjectErrors, ArchiveProjectResponse, ArchiveProjectResponses, ArchiveProviderMappingData, ArchiveProviderMappingError, ArchiveProviderMappingErrors, ArchiveProviderMappingResponse, ArchiveProviderMappingResponses, AssessBillingMigrationReadinessData, AssessBillingMigrationReadinessError, AssessBillingMigrationReadinessErrors, AssessBillingMigrationReadinessResponse, AssessBillingMigrationReadinessResponses, AssessBillingMigrationRollbackReadinessData, AssessBillingMigrationRollbackReadinessError, AssessBillingMigrationRollbackReadinessErrors, AssessBillingMigrationRollbackReadinessResponse, AssessBillingMigrationRollbackReadinessResponses, Asset, AssetEnvelope, AssetId, AssetListEnvelope, AssetUsage, AssetUsageEnvelope, AttachBillingCustomerAliasData, AttachBillingCustomerAliasError, AttachBillingCustomerAliasErrors, AttachBillingCustomerAliasRequest, AttachBillingCustomerAliasResponse, AttachBillingCustomerAliasResponses, AuditEvent, AuditEventList, AuditEventListEnvelope, AuthoritativeEntitlementSyncResponse, AuthorityCustomerEntitlementSnapshotRecord, AuthorityEntitlementSyncRequestRecord, AuthoritySnapshotPayloadV1, AuthoritySnapshotUnchangedRecord, AuthorityUnavailableRecord, AuthorityUnchangedPayloadV1, BillingAuthority, BillingAuthorityMinimumSupport, BillingAuthorityScope, BillingCursor, BillingCustomer, BillingCustomerAlias, BillingCustomerDetail, BillingCustomerId, BillingCustomerLookupRequest, BillingCustomerLookupResult, BillingCustomerSummary, BillingEntitlementSnapshot, BillingEntitlementSnapshotEntry, BillingEntitlementSource, BillingFrom, BillingHealth, BillingIdentityConflict, BillingIdentityConflictDetail, BillingIdentityCustomer, BillingLedgerEntry, BillingMigrationApproval, BillingMigrationApprovalEnvelope, BillingMigrationApprovalPageEnvelope, BillingMigrationApprovalRecord, BillingMigrationAssessRollbackReadinessRequest, BillingMigrationAuthorityExecution, BillingMigrationAuthorityExecutionEnvelope, BillingMigrationAuthorityExecutionPageEnvelope, BillingMigrationAuthorityExecutionRecord, BillingMigrationCase, BillingMigrationCaseAction, BillingMigrationCaseActionPageEnvelope, BillingMigrationCaseEnvelope, BillingMigrationCasePageEnvelope, BillingMigrationCaseRecord, BillingMigrationCaseRequest, BillingMigrationCaseTransitionRequest, BillingMigrationCheckpoint, BillingMigrationCheckpointEnvelope, BillingMigrationCheckpointPageEnvelope, BillingMigrationCheckpointRecord, BillingMigrationCheckpointRequest, BillingMigrationCompletionPrerequisites, BillingMigrationCompletionPrerequisitesEnvelope, BillingMigrationCompletionPrerequisitesRecord, BillingMigrationCompletionReport, BillingMigrationCompletionReportEnvelope, BillingMigrationCompletionReportPageEnvelope, BillingMigrationCompletionReportRecord, BillingMigrationCompletionRequest, BillingMigrationCredentialRemoval, BillingMigrationCredentialRemovalEnvelope, BillingMigrationCredentialRemovalPageEnvelope, BillingMigrationCredentialRemovalRecord, BillingMigrationCredentialRemovalRequest, BillingMigrationCutoverExecutionRequest, BillingMigrationCutoverProposalRequest, BillingMigrationDigest, BillingMigrationDigestSet, BillingMigrationDivergence, BillingMigrationDivergenceListEnvelope, BillingMigrationDivergenceRecord, BillingMigrationExecutionScope, BillingMigrationFreezeStabilizationPolicyRequest, BillingMigrationIdempotencyKey, BillingMigrationImportBatch, BillingMigrationImportBatchEnvelope, BillingMigrationImportBatchListEnvelope, BillingMigrationImportBatchRecord, BillingMigrationLegalHold, BillingMigrationLegalHoldApprovalRequest, BillingMigrationLegalHoldEnvelope, BillingMigrationLegalHoldPageEnvelope, BillingMigrationLegalHoldProposal, BillingMigrationLegalHoldProposalEnvelope, BillingMigrationLegalHoldProposalPageEnvelope, BillingMigrationLegalHoldProposalRecord, BillingMigrationLegalHoldProposalRequest, BillingMigrationLegalHoldRecord, BillingMigrationMappingEntry, BillingMigrationMappingSet, BillingMigrationMappingSetEnvelope, BillingMigrationMappingSetListEnvelope, BillingMigrationObserveStabilizationRequest, BillingMigrationOperationEnvelope, BillingMigrationProgram, BillingMigrationProgramDetail, BillingMigrationProgramEnvelope, BillingMigrationProgramId, BillingMigrationProgramListEnvelope, BillingMigrationProgramRecord, BillingMigrationProposal, BillingMigrationProposalEnvelope, BillingMigrationProposalPageEnvelope, BillingMigrationProposalRecord, BillingMigrationReadiness, BillingMigrationReadinessEnvelope, BillingMigrationRedeliveryRequest, BillingMigrationRepairExecution, BillingMigrationRepairExecutionCommandPending, BillingMigrationRepairExecutionCommandPendingEnvelope, BillingMigrationRepairExecutionCommandTerminal, BillingMigrationRepairExecutionCommandTerminalEnvelope, BillingMigrationRepairExecutionEnvelope, BillingMigrationRepairExecutionPageEnvelope, BillingMigrationRepairExecutionPending, BillingMigrationRepairExecutionRecord, BillingMigrationRepairExecutionRequest, BillingMigrationRepairExecutionTerminal, BillingMigrationRepairPreview, BillingMigrationRepairPreviewCommand, BillingMigrationRepairPreviewCommandEnvelope, BillingMigrationRepairPreviewEnvelope, BillingMigrationRepairPreviewPageEnvelope, BillingMigrationRepairPreviewRecord, BillingMigrationRepairPreviewRequest, BillingMigrationRollbackExecutionRequest, BillingMigrationRollbackProposalRequest, BillingMigrationRollbackReadinessAssessment, BillingMigrationRollbackReadinessAssessmentEnvelope, BillingMigrationRollbackReadinessAssessmentPageEnvelope, BillingMigrationRollbackReadinessAssessmentRecord, BillingMigrationRollbackReadinessCheckpoint, BillingMigrationRollbackReadinessCheckpointEnvelope, BillingMigrationRollbackReadinessCheckpointRecord, BillingMigrationRunJob, BillingMigrationRunJobEnvelope, BillingMigrationScope, BillingMigrationScopeItem, BillingMigrationSourceManifest, BillingMigrationSourceManifestListEnvelope, BillingMigrationSourcePull, BillingMigrationSourcePullEnvelope, BillingMigrationSourcePullPageEnvelope, BillingMigrationSourcePullRecord, BillingMigrationSourcePullRequest, BillingMigrationStabilizationMetrics, BillingMigrationStabilizationObservation, BillingMigrationStabilizationObservationEnvelope, BillingMigrationStabilizationObservationPageEnvelope, BillingMigrationStabilizationObservationRecord, BillingMigrationStabilizationPolicy, BillingMigrationStabilizationPolicyEnvelope, BillingMigrationStabilizationPolicyRecord, BillingMigrationStabilizationThresholds, BillingMigrationStateVersionRequest, BillingMigrationWebhookRedelivery, BillingMigrationWebhookRedeliveryEnvelope, BillingMigrationWebhookRedeliveryPageEnvelope, BillingMigrationWebhookRedeliveryRecord, BillingOneTimePurchase, BillingProjectionHealth, BillingProjectionStatus, BillingProviderFilter, BillingPurchaseLineage, BillingRestoreJob, BillingSettings, BillingSubscriptionSnapshot, BillingSyncRequest, BillingTimelineEntry, BillingTo, BindPlacementData, BindPlacementError, BindPlacementErrors, BindPlacementRequest, BindPlacementResponse, BindPlacementResponses, BootstrapOrganization, CheckCustomerEntitlementsData, CheckCustomerEntitlementsError, CheckCustomerEntitlementsErrors, CheckCustomerEntitlementsResponse, CheckCustomerEntitlementsResponses, ClearActiveProviderAssignmentData, ClearActiveProviderAssignmentError, ClearActiveProviderAssignmentErrors, ClearActiveProviderAssignmentResponse, ClearActiveProviderAssignmentResponses, ClientOptions, ClientTransactionObservation, ClientTransactionObservationRecord, ClonePaywallVersionToDraftData, ClonePaywallVersionToDraftError, ClonePaywallVersionToDraftErrors, ClonePaywallVersionToDraftResponse, ClonePaywallVersionToDraftResponses, ClonePlacementRuleSetVersionData, ClonePlacementRuleSetVersionError, ClonePlacementRuleSetVersionErrors, ClonePlacementRuleSetVersionResponse, ClonePlacementRuleSetVersionResponses, CloseQuarantineRecordSupersededData, CloseQuarantineRecordSupersededError, CloseQuarantineRecordSupersededErrors, CloseQuarantineRecordSupersededResponse, CloseQuarantineRecordSupersededResponses, CompareAnalyticsPaywallVersionsData, CompareAnalyticsPaywallVersionsError, CompareAnalyticsPaywallVersionsErrors, CompareAnalyticsPaywallVersionsResponse, CompareAnalyticsPaywallVersionsResponses, CompleteBillingMigrationData, CompleteBillingMigrationError, CompleteBillingMigrationErrors, CompleteBillingMigrationResponse, CompleteBillingMigrationResponses, ConfigurationRelease, CreateAnalyticsEventExportData, CreateAnalyticsEventExportError, CreateAnalyticsEventExportErrors, CreateAnalyticsEventExportRequest, CreateAnalyticsEventExportResponse, CreateAnalyticsEventExportResponses, CreateAnalyticsPrivacyDeletionData, CreateAnalyticsPrivacyDeletionError, CreateAnalyticsPrivacyDeletionErrors, CreateAnalyticsPrivacyDeletionRequest, CreateAnalyticsPrivacyDeletionResponse, CreateAnalyticsPrivacyDeletionResponses, CreateAnalyticsPrivacyExportData, CreateAnalyticsPrivacyExportError, CreateAnalyticsPrivacyExportErrors, CreateAnalyticsPrivacyExportRequest, CreateAnalyticsPrivacyExportResponse, CreateAnalyticsPrivacyExportResponses, CreateApiKeyData, CreateApiKeyError, CreateApiKeyErrors, CreateApiKeyRequest, CreateApiKeyResponse, CreateApiKeyResponses, CreateApplicationData, CreateApplicationError, CreateApplicationErrors, CreateApplicationRequest, CreateApplicationResponse, CreateApplicationResponses, CreateBillingCustomerSyncRequestData, CreateBillingCustomerSyncRequestError, CreateBillingCustomerSyncRequestErrors, CreateBillingCustomerSyncRequestResponse, CreateBillingCustomerSyncRequestResponses, CreateBillingMigrationCaseData, CreateBillingMigrationCaseError, CreateBillingMigrationCaseErrors, CreateBillingMigrationCaseResponse, CreateBillingMigrationCaseResponses, CreateBillingMigrationCheckpointData, CreateBillingMigrationCheckpointError, CreateBillingMigrationCheckpointErrors, CreateBillingMigrationCheckpointResponse, CreateBillingMigrationCheckpointResponses, CreateBillingMigrationImportBatchData, CreateBillingMigrationImportBatchError, CreateBillingMigrationImportBatchErrors, CreateBillingMigrationImportBatchRequest, CreateBillingMigrationImportBatchResponse, CreateBillingMigrationImportBatchResponses, CreateBillingMigrationMappingSetData, CreateBillingMigrationMappingSetError, CreateBillingMigrationMappingSetErrors, CreateBillingMigrationMappingSetRequest, CreateBillingMigrationMappingSetResponse, CreateBillingMigrationMappingSetResponses, CreateBillingMigrationProgramData, CreateBillingMigrationProgramError, CreateBillingMigrationProgramErrors, CreateBillingMigrationProgramRequest, CreateBillingMigrationProgramRequestWritable, CreateBillingMigrationProgramResponse, CreateBillingMigrationProgramResponses, CreateBillingProjectionReplayData, CreateBillingProjectionReplayError, CreateBillingProjectionReplayErrors, CreateBillingProjectionReplayResponse, CreateBillingProjectionReplayResponses, CreateCatalogResourceRequest, CreateDraftRequest, CreateEntitlementData, CreateEntitlementError, CreateEntitlementErrors, CreateEntitlementResponse, CreateEntitlementResponses, CreateExperimentData, CreateExperimentError, CreateExperimentErrors, CreateExperimentExportRequest, CreateExperimentGroupRequest, CreateExperimentGroupVersionData, CreateExperimentGroupVersionError, CreateExperimentGroupVersionErrors, CreateExperimentGroupVersionRequest, CreateExperimentGroupVersionResponse, CreateExperimentGroupVersionResponses, CreateExperimentMutualExclusionGroupVersionData, CreateExperimentMutualExclusionGroupVersionError, CreateExperimentMutualExclusionGroupVersionErrors, CreateExperimentMutualExclusionGroupVersionResponse, CreateExperimentMutualExclusionGroupVersionResponses, CreateExperimentQaOverrideData, CreateExperimentQaOverrideError, CreateExperimentQaOverrideErrors, CreateExperimentQaOverrideRequest, CreateExperimentQaOverrideResponse, CreateExperimentQaOverrideResponses, CreateExperimentRawExportData, CreateExperimentRawExportError, CreateExperimentRawExportErrors, CreateExperimentRawExportResponse, CreateExperimentRawExportResponses, CreateExperimentRequest, CreateExperimentResponse, CreateExperimentResponses, CreateOrganizationData, CreateOrganizationError, CreateOrganizationErrors, CreateOrganizationRequest, CreateOrganizationResponse, CreateOrganizationResponses, CreatePaywallData, CreatePaywallDraftData, CreatePaywallDraftError, CreatePaywallDraftErrors, CreatePaywallDraftResponse, CreatePaywallDraftResponses, CreatePaywallError, CreatePaywallErrors, CreatePaywallRequest, CreatePaywallResponse, CreatePaywallResponses, CreatePlacementAliasData, CreatePlacementAliasError, CreatePlacementAliasErrors, CreatePlacementAliasResponse, CreatePlacementAliasResponses, CreatePlacementAttributeData, CreatePlacementAttributeError, CreatePlacementAttributeErrors, CreatePlacementAttributeRequest, CreatePlacementAttributeResponse, CreatePlacementAttributeResponses, CreatePlacementData, CreatePlacementError, CreatePlacementErrors, CreatePlacementQaOverrideData, CreatePlacementQaOverrideError, CreatePlacementQaOverrideErrors, CreatePlacementQaOverrideResponse, CreatePlacementQaOverrideResponses, CreatePlacementRequest, CreatePlacementResponse, CreatePlacementResponses, CreatePlacementRuleSetData, CreatePlacementRuleSetError, CreatePlacementRuleSetErrors, CreatePlacementRuleSetResponse, CreatePlacementRuleSetResponses, CreatePlanData, CreatePlanError, CreatePlanErrors, CreatePlanResponse, CreatePlanResponses, CreateProductData, CreateProductError, CreateProductErrors, CreateProductRequest, CreateProductResponse, CreateProductResponses, CreateProjectData, CreateProjectError, CreateProjectErrors, CreateProjectionReplayRequest, CreateProjectRequest, CreateProjectResponse, CreateProjectResponses, CreateProviderConnectionData, CreateProviderConnectionError, CreateProviderConnectionErrors, CreateProviderConnectionRequest, CreateProviderConnectionRequestWritable, CreateProviderConnectionResponse, CreateProviderConnectionResponses, CreateProviderMappingData, CreateProviderMappingDraftData, CreateProviderMappingDraftError, CreateProviderMappingDraftErrors, CreateProviderMappingDraftRequest, CreateProviderMappingDraftResponse, CreateProviderMappingDraftResponses, CreateProviderMappingError, CreateProviderMappingErrors, CreateProviderMappingObservationData, CreateProviderMappingObservationError, CreateProviderMappingObservationErrors, CreateProviderMappingObservationRequest, CreateProviderMappingObservationResponse, CreateProviderMappingObservationResponses, CreateProviderMappingRequest, CreateProviderMappingResponse, CreateProviderMappingResponses, CreateQaOverrideRequest, CreateQaOverrideRequestWritable, CreateReconciliationRunData, CreateReconciliationRunError, CreateReconciliationRunErrors, CreateReconciliationRunRequest, CreateReconciliationRunResponse, CreateReconciliationRunResponses, CreateReplayJobData, CreateReplayJobError, CreateReplayJobErrors, CreateReplayJobRequest, CreateReplayJobResponse, CreateReplayJobResponses, CreateStoreServerCredentialData, CreateStoreServerCredentialError, CreateStoreServerCredentialErrors, CreateStoreServerCredentialRequest, CreateStoreServerCredentialResponse, CreateStoreServerCredentialResponses, CreateWebhookDestinationData, CreateWebhookDestinationError, CreateWebhookDestinationErrors, CreateWebhookDestinationRequest, CreateWebhookDestinationResponse, CreateWebhookDestinationResponses, Cursor, CustomerAccessTokenIssuanceRequest, CustomerAccessTokenIssuanceResult, CustomerAccessTokenMetadata, CustomerEntitlementSnapshotRecord, DeleteProductData, DeleteProductError, DeleteProductErrors, DeleteProductResponse, DeleteProductResponses, DeleteWebhookDestinationData, DeleteWebhookDestinationError, DeleteWebhookDestinationErrors, DeleteWebhookDestinationResponse, DeleteWebhookDestinationResponses, DownloadAnalyticsJobData, DownloadAnalyticsJobError, DownloadAnalyticsJobErrors, DownloadAnalyticsJobResponse, DownloadAnalyticsJobResponses, Draft, DraftEnvelope, DraftId, DraftResource, EnqueueProviderSyncData, EnqueueProviderSyncError, EnqueueProviderSyncErrors, EnqueueProviderSyncResponse, EnqueueProviderSyncResponses, Entitlement, EntitlementCheckRequestRecord, EntitlementCheckResultRecord, EntitlementEntry, EntitlementEnvelope, EntitlementId, EntitlementList, EntitlementListEnvelope, EntitlementReferenceRequest, EntitlementSourceSummary, EntitlementSyncRequestRecord, EntitlementSyncRequestUnion, Environment, EnvironmentEnvelope, EnvironmentId, EnvironmentList, EnvironmentListEnvelope, EnvironmentMode, ErrorEnvelope, ExecuteBillingMigrationCutoverData, ExecuteBillingMigrationCutoverError, ExecuteBillingMigrationCutoverErrors, ExecuteBillingMigrationCutoverResponse, ExecuteBillingMigrationCutoverResponses, ExecuteBillingMigrationRepairData, ExecuteBillingMigrationRepairError, ExecuteBillingMigrationRepairErrors, ExecuteBillingMigrationRepairResponse, ExecuteBillingMigrationRepairResponses, ExecuteBillingMigrationRollbackData, ExecuteBillingMigrationRollbackError, ExecuteBillingMigrationRollbackErrors, ExecuteBillingMigrationRollbackResponse, ExecuteBillingMigrationRollbackResponses, Experiment, ExperimentDraft, ExperimentDraftDocument, ExperimentDraftEnvelope, ExperimentEnvelope, ExperimentGroup, ExperimentGroupCreated, ExperimentGroupCreatedEnvelope, ExperimentGroupListEnvelope, ExperimentGroupMemberInput, ExperimentGroupVersion, ExperimentGroupVersionEnvelope, ExperimentGroupVersionListEnvelope, ExperimentGuardrailMaturity, ExperimentGuardrailResult, ExperimentGuardrailVariantResult, ExperimentHistory, ExperimentHistoryListEnvelope, ExperimentId, ExperimentInterval, ExperimentLift, ExperimentListEnvelope, ExperimentMetricDefinition, ExperimentMetricListEnvelope, ExperimentQaOverride, ExperimentQaOverrideCreated, ExperimentQaOverrideCreatedEnvelope, ExperimentQaOverrideCreatedEnvelopeWritable, ExperimentQaOverrideCreatedWritable, ExperimentQaOverrideListEnvelope, ExperimentResults, ExperimentResultsEnvelope, ExperimentSchedule, ExperimentSrm, ExperimentValidation, ExperimentValidationEnvelope, ExperimentValidationIssue, ExperimentVariantDraft, ExperimentVariantResult, ExperimentVariantVersion, ExperimentVersion, ExperimentVersionEnvelope, ExperimentVersionListEnvelope, FreezeBillingMigrationMappingSetData, FreezeBillingMigrationMappingSetError, FreezeBillingMigrationMappingSetErrors, FreezeBillingMigrationMappingSetResponses, FreezeBillingMigrationStabilizationPolicyData, FreezeBillingMigrationStabilizationPolicyError, FreezeBillingMigrationStabilizationPolicyErrors, FreezeBillingMigrationStabilizationPolicyResponse, FreezeBillingMigrationStabilizationPolicyResponses, GetActivePaywallDraftData, GetActivePaywallDraftError, GetActivePaywallDraftErrors, GetActivePaywallDraftResponse, GetActivePaywallDraftResponses, GetActiveProviderAssignmentData, GetActiveProviderAssignmentError, GetActiveProviderAssignmentErrors, GetActiveProviderAssignmentResponse, GetActiveProviderAssignmentResponses, GetAnalyticsBreakdownData, GetAnalyticsBreakdownError, GetAnalyticsBreakdownErrors, GetAnalyticsBreakdownResponse, GetAnalyticsBreakdownResponses, GetAnalyticsFreshnessData, GetAnalyticsFreshnessError, GetAnalyticsFreshnessErrors, GetAnalyticsFreshnessResponse, GetAnalyticsFreshnessResponses, GetAnalyticsFunnelData, GetAnalyticsFunnelError, GetAnalyticsFunnelErrors, GetAnalyticsFunnelResponse, GetAnalyticsFunnelResponses, GetAnalyticsJobData, GetAnalyticsJobError, GetAnalyticsJobErrors, GetAnalyticsJobResponse, GetAnalyticsJobResponses, GetAnalyticsOverviewData, GetAnalyticsOverviewError, GetAnalyticsOverviewErrors, GetAnalyticsOverviewResponse, GetAnalyticsOverviewResponses, GetAnalyticsProductAvailabilityFailuresData, GetAnalyticsProductAvailabilityFailuresError, GetAnalyticsProductAvailabilityFailuresErrors, GetAnalyticsProductAvailabilityFailuresResponse, GetAnalyticsProductAvailabilityFailuresResponses, GetAnalyticsProviderErrorsData, GetAnalyticsProviderErrorsError, GetAnalyticsProviderErrorsErrors, GetAnalyticsProviderErrorsResponse, GetAnalyticsProviderErrorsResponses, GetAnalyticsSettingsData, GetAnalyticsSettingsError, GetAnalyticsSettingsErrors, GetAnalyticsSettingsResponse, GetAnalyticsSettingsResponses, GetAssetContentData, GetAssetContentError, GetAssetContentErrors, GetAssetContentResponse, GetAssetContentResponses, GetAssetData, GetAssetError, GetAssetErrors, GetAssetResponse, GetAssetResponses, GetAssetUsageData, GetAssetUsageError, GetAssetUsageErrors, GetAssetUsageResponse, GetAssetUsageResponses, GetBillingCustomerData, GetBillingCustomerEntitlementSnapshotData, GetBillingCustomerEntitlementSnapshotError, GetBillingCustomerEntitlementSnapshotErrors, GetBillingCustomerEntitlementSnapshotResponse, GetBillingCustomerEntitlementSnapshotResponses, GetBillingCustomerError, GetBillingCustomerErrors, GetBillingCustomerResponse, GetBillingCustomerResponses, GetBillingHealthData, GetBillingHealthError, GetBillingHealthErrors, GetBillingHealthResponse, GetBillingHealthResponses, GetBillingIdentityConflictData, GetBillingIdentityConflictError, GetBillingIdentityConflictErrors, GetBillingIdentityConflictResponse, GetBillingIdentityConflictResponses, GetBillingMigrationApprovalData, GetBillingMigrationApprovalError, GetBillingMigrationApprovalErrors, GetBillingMigrationApprovalResponse, GetBillingMigrationApprovalResponses, GetBillingMigrationAuthorityExecutionData, GetBillingMigrationAuthorityExecutionError, GetBillingMigrationAuthorityExecutionErrors, GetBillingMigrationAuthorityExecutionResponse, GetBillingMigrationAuthorityExecutionResponses, GetBillingMigrationCaseData, GetBillingMigrationCaseError, GetBillingMigrationCaseErrors, GetBillingMigrationCaseResponse, GetBillingMigrationCaseResponses, GetBillingMigrationCheckpointData, GetBillingMigrationCheckpointError, GetBillingMigrationCheckpointErrors, GetBillingMigrationCheckpointResponse, GetBillingMigrationCheckpointResponses, GetBillingMigrationCompletionReportData, GetBillingMigrationCompletionReportError, GetBillingMigrationCompletionReportErrors, GetBillingMigrationCompletionReportResponse, GetBillingMigrationCompletionReportResponses, GetBillingMigrationCredentialRemovalData, GetBillingMigrationCredentialRemovalError, GetBillingMigrationCredentialRemovalErrors, GetBillingMigrationCredentialRemovalResponse, GetBillingMigrationCredentialRemovalResponses, GetBillingMigrationImportBatchData, GetBillingMigrationImportBatchError, GetBillingMigrationImportBatchErrors, GetBillingMigrationImportBatchResponse, GetBillingMigrationImportBatchResponses, GetBillingMigrationLegalHoldData, GetBillingMigrationLegalHoldError, GetBillingMigrationLegalHoldErrors, GetBillingMigrationLegalHoldProposalData, GetBillingMigrationLegalHoldProposalError, GetBillingMigrationLegalHoldProposalErrors, GetBillingMigrationLegalHoldProposalResponse, GetBillingMigrationLegalHoldProposalResponses, GetBillingMigrationLegalHoldResponse, GetBillingMigrationLegalHoldResponses, GetBillingMigrationProgramData, GetBillingMigrationProgramError, GetBillingMigrationProgramErrors, GetBillingMigrationProgramResponse, GetBillingMigrationProgramResponses, GetBillingMigrationProposalData, GetBillingMigrationProposalError, GetBillingMigrationProposalErrors, GetBillingMigrationProposalResponse, GetBillingMigrationProposalResponses, GetBillingMigrationRepairExecutionData, GetBillingMigrationRepairExecutionError, GetBillingMigrationRepairExecutionErrors, GetBillingMigrationRepairExecutionResponse, GetBillingMigrationRepairExecutionResponses, GetBillingMigrationRepairPreviewData, GetBillingMigrationRepairPreviewError, GetBillingMigrationRepairPreviewErrors, GetBillingMigrationRepairPreviewResponse, GetBillingMigrationRepairPreviewResponses, GetBillingMigrationRunJobData, GetBillingMigrationRunJobError, GetBillingMigrationRunJobErrors, GetBillingMigrationRunJobResponse, GetBillingMigrationRunJobResponses, GetBillingMigrationSourcePullData, GetBillingMigrationSourcePullError, GetBillingMigrationSourcePullErrors, GetBillingMigrationSourcePullResponse, GetBillingMigrationSourcePullResponses, GetBillingMigrationWebhookRedeliveryData, GetBillingMigrationWebhookRedeliveryError, GetBillingMigrationWebhookRedeliveryErrors, GetBillingMigrationWebhookRedeliveryResponse, GetBillingMigrationWebhookRedeliveryResponses, GetBillingProjectionHealthData, GetBillingProjectionHealthError, GetBillingProjectionHealthErrors, GetBillingProjectionHealthResponse, GetBillingProjectionHealthResponses, GetBillingRestoreJobData, GetBillingRestoreJobError, GetBillingRestoreJobErrors, GetBillingRestoreJobResponse, GetBillingRestoreJobResponses, GetBillingSettingsData, GetBillingSettingsError, GetBillingSettingsErrors, GetBillingSettingsResponse, GetBillingSettingsResponses, GetBillingSubscriptionData, GetBillingSubscriptionError, GetBillingSubscriptionErrors, GetBillingSubscriptionResponse, GetBillingSubscriptionResponses, GetCurrentBillingMigrationCredentialRemovalData, GetCurrentBillingMigrationCredentialRemovalError, GetCurrentBillingMigrationCredentialRemovalErrors, GetCurrentBillingMigrationCredentialRemovalResponse, GetCurrentBillingMigrationCredentialRemovalResponses, GetCurrentBillingMigrationLegalHoldData, GetCurrentBillingMigrationLegalHoldError, GetCurrentBillingMigrationLegalHoldErrors, GetCurrentBillingMigrationLegalHoldResponse, GetCurrentBillingMigrationLegalHoldResponses, GetCurrentBillingMigrationStabilizationPolicyData, GetCurrentBillingMigrationStabilizationPolicyError, GetCurrentBillingMigrationStabilizationPolicyErrors, GetCurrentBillingMigrationStabilizationPolicyResponse, GetCurrentBillingMigrationStabilizationPolicyResponses, GetCustomerEntitlementSnapshotData, GetCustomerEntitlementSnapshotError, GetCustomerEntitlementSnapshotErrors, GetCustomerEntitlementSnapshotResponse, GetCustomerEntitlementSnapshotResponses, GetEntitlementData, GetEntitlementError, GetEntitlementErrors, GetEntitlementResponse, GetEntitlementResponses, GetExperimentData, GetExperimentError, GetExperimentErrors, GetExperimentResponse, GetExperimentResponses, GetExperimentResultsData, GetExperimentResultsResponse, GetExperimentResultsResponses, GetExperimentSampleRatioMismatchData, GetExperimentSampleRatioMismatchResponse, GetExperimentSampleRatioMismatchResponses, GetHealthData, GetHealthResponse, GetHealthResponses, GetLatestBillingMigrationCheckpointData, GetLatestBillingMigrationCheckpointError, GetLatestBillingMigrationCheckpointErrors, GetLatestBillingMigrationCheckpointResponse, GetLatestBillingMigrationCheckpointResponses, GetLatestBillingMigrationReadinessData, GetLatestBillingMigrationReadinessError, GetLatestBillingMigrationReadinessErrors, GetLatestBillingMigrationReadinessResponse, GetLatestBillingMigrationReadinessResponses, GetLatestBillingMigrationRollbackReadinessAssessmentData, GetLatestBillingMigrationRollbackReadinessAssessmentError, GetLatestBillingMigrationRollbackReadinessAssessmentErrors, GetLatestBillingMigrationRollbackReadinessAssessmentResponse, GetLatestBillingMigrationRollbackReadinessAssessmentResponses, GetLatestBillingMigrationRollbackReadinessCheckpointData, GetLatestBillingMigrationRollbackReadinessCheckpointError, GetLatestBillingMigrationRollbackReadinessCheckpointErrors, GetLatestBillingMigrationRollbackReadinessCheckpointResponse, GetLatestBillingMigrationRollbackReadinessCheckpointResponses, GetLatestBillingMigrationStabilizationObservationData, GetLatestBillingMigrationStabilizationObservationError, GetLatestBillingMigrationStabilizationObservationErrors, GetLatestBillingMigrationStabilizationObservationResponse, GetLatestBillingMigrationStabilizationObservationResponses, GetNativeProviderProfileData, GetNativeProviderProfileError, GetNativeProviderProfileErrors, GetNativeProviderProfileResponse, GetNativeProviderProfileResponses, GetOperatorBillingCustomerData, GetOperatorBillingCustomerError, GetOperatorBillingCustomerErrors, GetOperatorBillingCustomerResponse, GetOperatorBillingCustomerResponses, GetOperatorBillingIdentityConflictData, GetOperatorBillingIdentityConflictError, GetOperatorBillingIdentityConflictErrors, GetOperatorBillingIdentityConflictResponse, GetOperatorBillingIdentityConflictResponses, GetOrganizationData, GetOrganizationError, GetOrganizationErrors, GetOrganizationResponse, GetOrganizationResponses, GetPaywallData, GetPaywallDraftData, GetPaywallDraftError, GetPaywallDraftErrors, GetPaywallDraftResponse, GetPaywallDraftResponses, GetPaywallError, GetPaywallErrors, GetPaywallResponse, GetPaywallResponses, GetPaywallVersionData, GetPaywallVersionError, GetPaywallVersionErrors, GetPaywallVersionResponse, GetPaywallVersionResponses, GetPlacementBindingData, GetPlacementBindingError, GetPlacementBindingErrors, GetPlacementBindingResponse, GetPlacementBindingResponses, GetPlacementDecisionData, GetPlacementDecisionError, GetPlacementDecisionErrors, GetPlacementDecisionResponse, GetPlacementDecisionResponses, GetPlacementUsageData, GetPlacementUsageError, GetPlacementUsageErrors, GetPlacementUsageResponse, GetPlacementUsageResponses, GetPlanData, GetPlanError, GetPlanErrors, GetPlanResponse, GetPlanResponses, GetProductData, GetProductEntitlementGrantVersionData, GetProductEntitlementGrantVersionError, GetProductEntitlementGrantVersionErrors, GetProductEntitlementGrantVersionResponse, GetProductEntitlementGrantVersionResponses, GetProductError, GetProductErrors, GetProductReadinessData, GetProductReadinessError, GetProductReadinessErrors, GetProductReadinessResponse, GetProductReadinessResponses, GetProductResponse, GetProductResponses, GetProductUsageData, GetProductUsageError, GetProductUsageErrors, GetProductUsageResponse, GetProductUsageResponses, GetProjectData, GetProjectError, GetProjectErrors, GetProjectResponse, GetProjectResponses, GetProviderConnectionCapabilitiesData, GetProviderConnectionCapabilitiesError, GetProviderConnectionCapabilitiesErrors, GetProviderConnectionCapabilitiesResponse, GetProviderConnectionCapabilitiesResponses, GetProviderConnectionData, GetProviderConnectionError, GetProviderConnectionErrors, GetProviderConnectionHealthData, GetProviderConnectionHealthError, GetProviderConnectionHealthErrors, GetProviderConnectionHealthResponse, GetProviderConnectionHealthResponses, GetProviderConnectionResponse, GetProviderConnectionResponses, GetProviderMappingMetadataData, GetProviderMappingMetadataError, GetProviderMappingMetadataErrors, GetProviderMappingMetadataResponse, GetProviderMappingMetadataResponses, GetProviderMappingUsageData, GetProviderMappingUsageError, GetProviderMappingUsageErrors, GetProviderMappingUsageResponse, GetProviderMappingUsageResponses, GetProviderReadinessData, GetProviderReadinessError, GetProviderReadinessErrors, GetProviderReadinessResponse, GetProviderReadinessResponses, GetQuarantineRecordData, GetQuarantineRecordError, GetQuarantineRecordErrors, GetQuarantineRecordResponse, GetQuarantineRecordResponses, GetReadinessData, GetReadinessError, GetReadinessErrors, GetReadinessResponse, GetReadinessResponses, GetSdkCommerceConfigurationData, GetSdkCommerceConfigurationError, GetSdkCommerceConfigurationErrors, GetSdkCommerceConfigurationResponse, GetSdkCommerceConfigurationResponses, GetSdkConfigurationData, GetSdkConfigurationError, GetSdkConfigurationErrors, GetSdkConfigurationResponse, GetSdkConfigurationResponses, GetSdkRestoreData, GetSdkRestoreError, GetSdkRestoreErrors, GetSdkRestoreResponse, GetSdkRestoreResponses, GetServerRestoreData, GetServerRestoreError, GetServerRestoreErrors, GetServerRestoreResponse, GetServerRestoreResponses, GetSessionData, GetSessionError, GetSessionErrors, GetSessionResponse, GetSessionResponses, GetStoreServerCredentialData, GetStoreServerCredentialError, GetStoreServerCredentialErrors, GetStoreServerCredentialResponse, GetStoreServerCredentialResponses, GetSubscriptionSnapshotData, GetSubscriptionSnapshotError, GetSubscriptionSnapshotErrors, GetSubscriptionSnapshotResponse, GetSubscriptionSnapshotResponses, GetWebhookDeliveryData, GetWebhookDeliveryError, GetWebhookDeliveryErrors, GetWebhookDeliveryResponse, GetWebhookDeliveryResponses, GetWebhookDestinationData, GetWebhookDestinationError, GetWebhookDestinationErrors, GetWebhookDestinationResponse, GetWebhookDestinationResponses, GetWorkspaceBootstrapData, GetWorkspaceBootstrapError, GetWorkspaceBootstrapErrors, GetWorkspaceBootstrapResponse, GetWorkspaceBootstrapResponses, GrantAccessPolicy, GrantVersionImpact, HealthEnvelope, IdempotencyKey, IdentifyBillingCustomerData, IdentifyBillingCustomerError, IdentifyBillingCustomerErrors, IdentifyBillingCustomerRequest, IdentifyBillingCustomerResponse, IdentifyBillingCustomerResponses, IdentityConflictId, IfMatch, ImportProviderProductsData, ImportProviderProductsError, ImportProviderProductsErrors, ImportProviderProductsResponse, ImportProviderProductsResponses, IngestAnalyticsEventBatchData, IngestAnalyticsEventBatchError, IngestAnalyticsEventBatchErrors, IngestAnalyticsEventBatchResponse, IngestAnalyticsEventBatchResponses, InspectBillingMigrationCompletionData, InspectBillingMigrationCompletionError, InspectBillingMigrationCompletionErrors, InspectBillingMigrationCompletionResponse, InspectBillingMigrationCompletionResponses, IssueCustomerAccessTokenData, IssueCustomerAccessTokenError, IssueCustomerAccessTokenErrors, IssueCustomerAccessTokenResponse, IssueCustomerAccessTokenResponses, Limit, ListApiKeysData, ListApiKeysError, ListApiKeysErrors, ListApiKeysResponse, ListApiKeysResponses, ListApplicationsData, ListApplicationsError, ListApplicationsErrors, ListApplicationsResponse, ListApplicationsResponses, ListAssetsData, ListAssetsError, ListAssetsErrors, ListAssetsResponse, ListAssetsResponses, ListAuditEventsData, ListAuditEventsError, ListAuditEventsErrors, ListAuditEventsResponse, ListAuditEventsResponses, ListBillingCustomerAliasesData, ListBillingCustomerAliasesError, ListBillingCustomerAliasesErrors, ListBillingCustomerAliasesResponse, ListBillingCustomerAliasesResponses, ListBillingCustomersData, ListBillingCustomersError, ListBillingCustomersErrors, ListBillingCustomersResponse, ListBillingCustomersResponses, ListBillingCustomerSubscriptionsData, ListBillingCustomerSubscriptionsError, ListBillingCustomerSubscriptionsErrors, ListBillingCustomerSubscriptionsResponse, ListBillingCustomerSubscriptionsResponses, ListBillingIdentityConflictsData, ListBillingIdentityConflictsError, ListBillingIdentityConflictsErrors, ListBillingIdentityConflictsResponse, ListBillingIdentityConflictsResponses, ListBillingLedgerData, ListBillingLedgerError, ListBillingLedgerErrors, ListBillingLedgerResponse, ListBillingLedgerResponses, ListBillingMigrationApprovalsData, ListBillingMigrationApprovalsResponse, ListBillingMigrationApprovalsResponses, ListBillingMigrationAuthorityExecutionsData, ListBillingMigrationAuthorityExecutionsResponse, ListBillingMigrationAuthorityExecutionsResponses, ListBillingMigrationCaseActionsData, ListBillingMigrationCaseActionsResponse, ListBillingMigrationCaseActionsResponses, ListBillingMigrationCasesData, ListBillingMigrationCasesResponse, ListBillingMigrationCasesResponses, ListBillingMigrationCheckpointsData, ListBillingMigrationCheckpointsResponse, ListBillingMigrationCheckpointsResponses, ListBillingMigrationCompletionHistoryData, ListBillingMigrationCompletionHistoryResponse, ListBillingMigrationCompletionHistoryResponses, ListBillingMigrationCredentialRemovalsData, ListBillingMigrationCredentialRemovalsResponse, ListBillingMigrationCredentialRemovalsResponses, ListBillingMigrationDivergencesData, ListBillingMigrationDivergencesError, ListBillingMigrationDivergencesErrors, ListBillingMigrationDivergencesResponse, ListBillingMigrationDivergencesResponses, ListBillingMigrationImportBatchesData, ListBillingMigrationImportBatchesError, ListBillingMigrationImportBatchesErrors, ListBillingMigrationImportBatchesResponse, ListBillingMigrationImportBatchesResponses, ListBillingMigrationLegalHoldProposalsData, ListBillingMigrationLegalHoldProposalsResponse, ListBillingMigrationLegalHoldProposalsResponses, ListBillingMigrationLegalHoldsData, ListBillingMigrationLegalHoldsResponse, ListBillingMigrationLegalHoldsResponses, ListBillingMigrationMappingSetsData, ListBillingMigrationMappingSetsError, ListBillingMigrationMappingSetsErrors, ListBillingMigrationMappingSetsResponse, ListBillingMigrationMappingSetsResponses, ListBillingMigrationProgramsData, ListBillingMigrationProgramsError, ListBillingMigrationProgramsErrors, ListBillingMigrationProgramsResponse, ListBillingMigrationProgramsResponses, ListBillingMigrationProposalsData, ListBillingMigrationProposalsResponse, ListBillingMigrationProposalsResponses, ListBillingMigrationRepairExecutionsData, ListBillingMigrationRepairExecutionsResponse, ListBillingMigrationRepairExecutionsResponses, ListBillingMigrationRepairPreviewsData, ListBillingMigrationRepairPreviewsResponse, ListBillingMigrationRepairPreviewsResponses, ListBillingMigrationRollbackReadinessAssessmentsData, ListBillingMigrationRollbackReadinessAssessmentsResponse, ListBillingMigrationRollbackReadinessAssessmentsResponses, ListBillingMigrationSourceManifestsData, ListBillingMigrationSourceManifestsError, ListBillingMigrationSourceManifestsErrors, ListBillingMigrationSourceManifestsResponse, ListBillingMigrationSourceManifestsResponses, ListBillingMigrationSourcePullsData, ListBillingMigrationSourcePullsError, ListBillingMigrationSourcePullsErrors, ListBillingMigrationSourcePullsResponse, ListBillingMigrationSourcePullsResponses, ListBillingMigrationStabilizationObservationsData, ListBillingMigrationStabilizationObservationsResponse, ListBillingMigrationStabilizationObservationsResponses, ListBillingMigrationWebhookRedeliveriesData, ListBillingMigrationWebhookRedeliveriesResponse, ListBillingMigrationWebhookRedeliveriesResponses, ListBillingQuarantineData, ListBillingQuarantineError, ListBillingQuarantineErrors, ListBillingQuarantineResponse, ListBillingQuarantineResponses, ListBillingRestoreJobsData, ListBillingRestoreJobsError, ListBillingRestoreJobsErrors, ListBillingRestoreJobsResponse, ListBillingRestoreJobsResponses, ListBillingSubscriptionTimelineData, ListBillingSubscriptionTimelineError, ListBillingSubscriptionTimelineErrors, ListBillingSubscriptionTimelineResponse, ListBillingSubscriptionTimelineResponses, ListConfigurationReleasesData, ListConfigurationReleasesError, ListConfigurationReleasesErrors, ListConfigurationReleasesResponse, ListConfigurationReleasesResponses, ListCustomerAccessTokensData, ListCustomerAccessTokensError, ListCustomerAccessTokensErrors, ListCustomerAccessTokensResponse, ListCustomerAccessTokensResponses, ListCustomerSubscriptionsData, ListCustomerSubscriptionsError, ListCustomerSubscriptionsErrors, ListCustomerSubscriptionsResponse, ListCustomerSubscriptionsResponses, ListEntitlementsData, ListEntitlementsError, ListEntitlementsErrors, ListEntitlementsResponse, ListEntitlementsResponses, ListEnvironmentsData, ListEnvironmentsError, ListEnvironmentsErrors, ListEnvironmentsResponse, ListEnvironmentsResponses, ListExperimentGroupsData, ListExperimentGroupsResponse, ListExperimentGroupsResponses, ListExperimentHistoryData, ListExperimentHistoryResponse, ListExperimentHistoryResponses, ListExperimentMetricDefinitionsData, ListExperimentMetricDefinitionsResponse, ListExperimentMetricDefinitionsResponses, ListExperimentMutualExclusionGroupVersionsData, ListExperimentMutualExclusionGroupVersionsError, ListExperimentMutualExclusionGroupVersionsErrors, ListExperimentMutualExclusionGroupVersionsResponse, ListExperimentMutualExclusionGroupVersionsResponses, ListExperimentQaOverridesData, ListExperimentQaOverridesResponse, ListExperimentQaOverridesResponses, ListExperimentsData, ListExperimentsResponse, ListExperimentsResponses, ListExperimentVersionsData, ListExperimentVersionsResponse, ListExperimentVersionsResponses, ListMembersData, ListMembersError, ListMembersErrors, ListMembersResponse, ListMembersResponses, ListOperatorBillingIdentityConflictsData, ListOperatorBillingIdentityConflictsError, ListOperatorBillingIdentityConflictsErrors, ListOperatorBillingIdentityConflictsResponse, ListOperatorBillingIdentityConflictsResponses, ListOrganizationsData, ListOrganizationsError, ListOrganizationsErrors, ListOrganizationsResponse, ListOrganizationsResponses, ListPaywallsData, ListPaywallsError, ListPaywallsErrors, ListPaywallsResponse, ListPaywallsResponses, ListPaywallVersionsData, ListPaywallVersionsError, ListPaywallVersionsErrors, ListPaywallVersionsResponse, ListPaywallVersionsResponses, ListPlacementAliasesData, ListPlacementAliasesResponse, ListPlacementAliasesResponses, ListPlacementAttributesData, ListPlacementAttributesError, ListPlacementAttributesErrors, ListPlacementAttributesResponse, ListPlacementAttributesResponses, ListPlacementQaOverridesData, ListPlacementQaOverridesResponse, ListPlacementQaOverridesResponses, ListPlacementRuleSetVersionsData, ListPlacementRuleSetVersionsResponse, ListPlacementRuleSetVersionsResponses, ListPlacementsData, ListPlacementsError, ListPlacementsErrors, ListPlacementsResponse, ListPlacementsResponses, ListPlanProductsData, ListPlanProductsError, ListPlanProductsErrors, ListPlanProductsResponse, ListPlanProductsResponses, ListPlansData, ListPlansError, ListPlansErrors, ListPlansResponse, ListPlansResponses, ListProductEntitlementGrantVersionsData, ListProductEntitlementGrantVersionsError, ListProductEntitlementGrantVersionsErrors, ListProductEntitlementGrantVersionsResponse, ListProductEntitlementGrantVersionsResponses, ListProductEntitlementsData, ListProductEntitlementsError, ListProductEntitlementsErrors, ListProductEntitlementsResponse, ListProductEntitlementsResponses, ListProductsData, ListProductsError, ListProductsErrors, ListProductsResponse, ListProductsResponses, ListProjectsData, ListProjectsError, ListProjectsErrors, ListProjectsResponse, ListProjectsResponses, ListProviderConnectionDiagnosticsData, ListProviderConnectionDiagnosticsError, ListProviderConnectionDiagnosticsErrors, ListProviderConnectionDiagnosticsResponse, ListProviderConnectionDiagnosticsResponses, ListProviderConnectionsData, ListProviderConnectionsError, ListProviderConnectionsErrors, ListProviderConnectionsResponse, ListProviderConnectionsResponses, ListProviderMappingObservationsData, ListProviderMappingObservationsError, ListProviderMappingObservationsErrors, ListProviderMappingObservationsResponse, ListProviderMappingObservationsResponses, ListProviderMappingsData, ListProviderMappingsError, ListProviderMappingsErrors, ListProviderMappingsResponse, ListProviderMappingsResponses, ListProviderSyncRunsData, ListProviderSyncRunsError, ListProviderSyncRunsErrors, ListProviderSyncRunsResponse, ListProviderSyncRunsResponses, ListReconciliationRunsData, ListReconciliationRunsError, ListReconciliationRunsErrors, ListReconciliationRunsResponse, ListReconciliationRunsResponses, ListReplayJobsData, ListReplayJobsError, ListReplayJobsErrors, ListReplayJobsResponse, ListReplayJobsResponses, ListStoreServerCredentialsData, ListStoreServerCredentialsError, ListStoreServerCredentialsErrors, ListStoreServerCredentialsResponse, ListStoreServerCredentialsResponses, ListSubscriptionTimelineData, ListSubscriptionTimelineError, ListSubscriptionTimelineErrors, ListSubscriptionTimelineResponse, ListSubscriptionTimelineResponses, ListTransactionFactsData, ListTransactionFactsError, ListTransactionFactsErrors, ListTransactionFactsResponse, ListTransactionFactsResponses, ListValidationAttemptsData, ListValidationAttemptsError, ListValidationAttemptsErrors, ListValidationAttemptsResponse, ListValidationAttemptsResponses, ListWebhookDeliveriesData, ListWebhookDeliveriesError, ListWebhookDeliveriesErrors, ListWebhookDeliveriesResponse, ListWebhookDeliveriesResponses, ListWebhookDeliveryAttemptsData, ListWebhookDeliveryAttemptsError, ListWebhookDeliveryAttemptsErrors, ListWebhookDeliveryAttemptsResponse, ListWebhookDeliveryAttemptsResponses, ListWebhookDestinationsData, ListWebhookDestinationsError, ListWebhookDestinationsErrors, ListWebhookDestinationsResponse, ListWebhookDestinationsResponses, ListWebhookSigningSecretsData, ListWebhookSigningSecretsError, ListWebhookSigningSecretsErrors, ListWebhookSigningSecretsResponse, ListWebhookSigningSecretsResponses, LoginData, LoginError, LoginErrors, LoginRequest, LoginResponse, LoginResponses, LogoutData, LogoutResponse, LogoutResponses, LookupBillingCustomerData, LookupBillingCustomerError, LookupBillingCustomerErrors, LookupBillingCustomerResponse, LookupBillingCustomerResponses, Membership, MembershipEnvelope, MembershipList, MembershipListEnvelope, MetadataSource, ObservationContext, ObservationCorrelation, ObservationSubmissionResult, ObservationSubmissionResultRecord, ObserveBillingMigrationStabilizationData, ObserveBillingMigrationStabilizationError, ObserveBillingMigrationStabilizationErrors, ObserveBillingMigrationStabilizationResponse, ObserveBillingMigrationStabilizationResponses, OperatorBillingCustomerAlias, OperatorBillingIdentityConflict, OperatorBillingIdentityConflictDetail, OperatorBillingSyncRequest, Organization, OrganizationEnvelope, OrganizationId, OrganizationList, OrganizationListEnvelope, Page, Paywall, PaywallEnvelope, PaywallId, PaywallListEnvelope, PaywallVersion, PaywallVersionEnvelope, PaywallVersionListEnvelope, Placement, PlacementAlias, PlacementAliasEnvelope, PlacementAliasListEnvelope, PlacementAttribute, PlacementAttributeEnvelope, PlacementAttributeListEnvelope, PlacementBinding, PlacementBindingEnvelope, PlacementDecisionDocument, PlacementDecisionDocumentRequest, PlacementEnvelope, PlacementId, PlacementListEnvelope, PlacementOutcome, PlacementRuleSet, PlacementRuleSetDraft, PlacementRuleSetDraftEnvelope, PlacementRuleSetDraftResource, PlacementRuleSetVersion, PlacementRuleSetVersionEnvelope, PlacementRuleSetVersionListEnvelope, PlacementSimulationEnvelope, PlacementSimulationRequest, PlacementSimulationRequestWritable, PlacementSimulationResult, PlacementUsage, PlacementUsageEnvelope, PlacementValidation, PlacementValidationEnvelope, PlacementValidationIssue, Plan, PlanEnvelope, PlanId, PlanList, PlanListEnvelope, PlanProduct, PlanProductEnvelope, Platform, PreviewAnalyticsPrivacyRequestData, PreviewAnalyticsPrivacyRequestError, PreviewAnalyticsPrivacyRequestErrors, PreviewAnalyticsPrivacyRequestResponse, PreviewAnalyticsPrivacyRequestResponses, PreviewBillingMigrationRepairData, PreviewBillingMigrationRepairError, PreviewBillingMigrationRepairErrors, PreviewBillingMigrationRepairResponse, PreviewBillingMigrationRepairResponses, PreviewProductEntitlementGrantImpactData, PreviewProductEntitlementGrantImpactError, PreviewProductEntitlementGrantImpactErrors, PreviewProductEntitlementGrantImpactResponse, PreviewProductEntitlementGrantImpactResponses, PreviewProviderCatalogData, PreviewProviderCatalogError, PreviewProviderCatalogErrors, PreviewProviderCatalogResponse, PreviewProviderCatalogResponses, PrimaryExplanation, Product, ProductEntitlementGrant, ProductEntitlementGrantEnvelope, ProductEntitlementGrantVersion, ProductEnvelope, ProductId, ProductList, ProductListEnvelope, ProductReadiness, ProductReadinessEnvelope, ProductReferenceRequest, ProductStatus, ProductType, ProductUsage, ProductUsageEnvelope, Project, ProjectEnvelope, ProjectId, ProjectionReplayResult, ProjectionStatus, ProjectList, ProjectListEnvelope, ProjectStatus, PromoteBillingMigrationReadyData, PromoteBillingMigrationReadyError, PromoteBillingMigrationReadyErrors, PromoteBillingMigrationReadyResponse, PromoteBillingMigrationReadyResponses, ProposeBillingMigrationCutoverData, ProposeBillingMigrationCutoverError, ProposeBillingMigrationCutoverErrors, ProposeBillingMigrationCutoverResponse, ProposeBillingMigrationCutoverResponses, ProposeBillingMigrationLegalHoldData, ProposeBillingMigrationLegalHoldError, ProposeBillingMigrationLegalHoldErrors, ProposeBillingMigrationLegalHoldResponse, ProposeBillingMigrationLegalHoldResponses, ProposeBillingMigrationRollbackData, ProposeBillingMigrationRollbackError, ProposeBillingMigrationRollbackErrors, ProposeBillingMigrationRollbackResponse, ProposeBillingMigrationRollbackResponses, ProviderActivationKind, ProviderAssignmentEnvelope, ProviderAvailability, ProviderCapability, ProviderCatalogApplication, ProviderCatalogEntitlement, ProviderCatalogOffering, ProviderCatalogPackage, ProviderCatalogPreview, ProviderCatalogProduct, ProviderConnection, ProviderConnectionCapabilities, ProviderConnectionEnvelope, ProviderConnectionHealth, ProviderConnectionId, ProviderConnectionKind, ProviderConnectionList, ProviderConnectionListEnvelope, ProviderConnectionMode, ProviderConnectionStatus, ProviderCredential, ProviderCredentialRequest, ProviderDiagnostic, ProviderEntitlementImportRequest, ProviderErrorCode, ProviderHealthStatus, ProviderImport, ProviderImportItem, ProviderImportRequest, ProviderImportResult, ProviderIntegrationMode, ProviderKind, ProviderMappingEnvelope, ProviderMappingId, ProviderMappingList, ProviderMappingListEnvelope, ProviderMappingObservation, ProviderMappingObservationEnvelope, ProviderMappingObservationMetadata, ProviderMappingStatus, ProviderMappingUsage, ProviderMappingUsageEnvelope, ProviderOrderReference, ProviderProductImportItemRequest, ProviderProductMapping, ProviderProductMetadataSnapshot, ProviderProfile, ProviderProfileEnvelope, ProviderReadiness, ProviderReadinessEnvelope, ProviderReadinessIssue, ProviderSyncJob, ProviderSyncRun, ProviderSyncState, PublishConfigurationData, PublishConfigurationError, PublishConfigurationErrors, PublishConfigurationResponse, PublishConfigurationResponses, PublishExperimentData, PublishExperimentError, PublishExperimentErrors, PublishExperimentRequest, PublishExperimentResponse, PublishExperimentResponses, PublishGrantVersionRequest, PublishPlacementRuleSetData, PublishPlacementRuleSetError, PublishPlacementRuleSetErrors, PublishPlacementRuleSetResponse, PublishPlacementRuleSetResponses, PublishProductEntitlementGrantVersionData, PublishProductEntitlementGrantVersionError, PublishProductEntitlementGrantVersionErrors, PublishProductEntitlementGrantVersionResponse, PublishProductEntitlementGrantVersionResponses, PublishRequest, PublishResult, PublishResultEnvelope, QaOverride, QaOverrideCreated, QaOverrideCreatedEnvelope, QaOverrideListEnvelope, QuarantineRecord, QuarantineRecordId, QueueBillingMigrationDryRunData, QueueBillingMigrationDryRunError, QueueBillingMigrationDryRunErrors, QueueBillingMigrationDryRunResponse, QueueBillingMigrationDryRunResponses, QueueBillingMigrationRunRequest, QueueBillingMigrationShadowRunData, QueueBillingMigrationShadowRunError, QueueBillingMigrationShadowRunErrors, QueueBillingMigrationShadowRunResponse, QueueBillingMigrationShadowRunResponses, QueueBillingMigrationSourcePullData, QueueBillingMigrationSourcePullError, QueueBillingMigrationSourcePullErrors, QueueBillingMigrationSourcePullResponse, QueueBillingMigrationSourcePullResponses, ReceiveAppleStoreNotificationData, ReceiveAppleStoreNotificationError, ReceiveAppleStoreNotificationErrors, ReceiveAppleStoreNotificationResponse, ReceiveAppleStoreNotificationResponses, ReconciliationRun, ReconnectProviderConnectionData, ReconnectProviderConnectionError, ReconnectProviderConnectionErrors, ReconnectProviderConnectionResponse, ReconnectProviderConnectionResponses, RedeliverBillingMigrationWebhookData, RedeliverBillingMigrationWebhookError, RedeliverBillingMigrationWebhookErrors, RedeliverBillingMigrationWebhookResponse, RedeliverBillingMigrationWebhookResponses, ReleaseEnvelope, ReleaseId, ReleaseListEnvelope, RemoveBillingMigrationCredentialData, RemoveBillingMigrationCredentialError, RemoveBillingMigrationCredentialErrors, RemoveBillingMigrationCredentialResponse, RemoveBillingMigrationCredentialResponses, RemoveMemberData, RemoveMemberError, RemoveMemberErrors, RemoveMemberResponse, RemoveMemberResponses, RemovePlanProductData, RemovePlanProductError, RemovePlanProductErrors, RemovePlanProductResponse, RemovePlanProductResponses, RemoveProductEntitlementData, RemoveProductEntitlementError, RemoveProductEntitlementErrors, RemoveProductEntitlementResponse, RemoveProductEntitlementResponses, ReplaceProviderConnectionScopesData, ReplaceProviderConnectionScopesError, ReplaceProviderConnectionScopesErrors, ReplaceProviderConnectionScopesRequest, ReplaceProviderConnectionScopesResponse, ReplaceProviderConnectionScopesResponses, ReplaceProviderMappingData, ReplaceProviderMappingError, ReplaceProviderMappingErrors, ReplaceProviderMappingRequest, ReplaceProviderMappingResponse, ReplaceProviderMappingResponses, ReplayJob, ReplayWebhookDeliveryData, ReplayWebhookDeliveryError, ReplayWebhookDeliveryErrors, ReplayWebhookDeliveryResponse, ReplayWebhookDeliveryResponses, RequestBillingCustomerSyncData, RequestBillingCustomerSyncError, RequestBillingCustomerSyncErrors, RequestBillingCustomerSyncResponse, RequestBillingCustomerSyncResponses, ResolveBillingIdentityConflictData, ResolveBillingIdentityConflictError, ResolveBillingIdentityConflictErrors, ResolveBillingIdentityConflictResponse, ResolveBillingIdentityConflictResponses, ResolveIdentityConflictRequest, RestoreJobId, RestoreProductData, RestoreProductError, RestoreProductErrors, RestoreProductResponse, RestoreProductResponses, RestoreProjectData, RestoreProjectError, RestoreProjectErrors, RestoreProjectResponse, RestoreProjectResponses, RestoreRequestRecord, RestoreResultRecord, RetireWebhookSigningSecretData, RetireWebhookSigningSecretError, RetireWebhookSigningSecretErrors, RetireWebhookSigningSecretResponse, RetireWebhookSigningSecretResponses, RetryQuarantinedInputData, RetryQuarantinedInputError, RetryQuarantinedInputErrors, RetryQuarantinedInputResponse, RetryQuarantinedInputResponses, RevokeApiKeyData, RevokeApiKeyError, RevokeApiKeyErrors, RevokeApiKeyResponse, RevokeApiKeyResponses, RevokeBillingCustomerAliasData, RevokeBillingCustomerAliasError, RevokeBillingCustomerAliasErrors, RevokeBillingCustomerAliasResponse, RevokeBillingCustomerAliasResponses, RevokeCustomerAccessTokenData, RevokeCustomerAccessTokenError, RevokeCustomerAccessTokenErrors, RevokeCustomerAccessTokenResponse, RevokeCustomerAccessTokenResponses, RevokeExperimentQaOverrideData, RevokeExperimentQaOverrideError, RevokeExperimentQaOverrideErrors, RevokeExperimentQaOverrideResponse, RevokeExperimentQaOverrideResponses, RevokePlacementQaOverrideData, RevokePlacementQaOverrideError, RevokePlacementQaOverrideErrors, RevokePlacementQaOverrideResponse, RevokePlacementQaOverrideResponses, RevokeProviderConnectionData, RevokeProviderConnectionError, RevokeProviderConnectionErrors, RevokeProviderConnectionResponse, RevokeProviderConnectionResponses, RevokeStoreServerCredentialData, RevokeStoreServerCredentialError, RevokeStoreServerCredentialErrors, RevokeStoreServerCredentialResponse, RevokeStoreServerCredentialResponses, Role, RollbackConfigurationReleaseData, RollbackConfigurationReleaseError, RollbackConfigurationReleaseErrors, RollbackConfigurationReleaseResponse, RollbackConfigurationReleaseResponses, RotateApiKeyData, RotateApiKeyError, RotateApiKeyErrors, RotateApiKeyResponse, RotateApiKeyResponses, RotateProviderCredentialData, RotateProviderCredentialError, RotateProviderCredentialErrors, RotateProviderCredentialResponse, RotateProviderCredentialResponses, RotateStoreServerCredentialData, RotateStoreServerCredentialError, RotateStoreServerCredentialErrors, RotateStoreServerCredentialResponse, RotateStoreServerCredentialResponses, RotateWebhookSigningSecretData, RotateWebhookSigningSecretError, RotateWebhookSigningSecretErrors, RotateWebhookSigningSecretResponse, RotateWebhookSigningSecretResponses, RuleSetId, ServerTransactionObservation, ServerTransactionObservationRecord, SetActiveProviderAssignmentData, SetActiveProviderAssignmentError, SetActiveProviderAssignmentErrors, SetActiveProviderAssignmentResponse, SetActiveProviderAssignmentResponses, SetEnvironmentModeData, SetEnvironmentModeError, SetEnvironmentModeErrors, SetEnvironmentModeRequest, SetEnvironmentModeResponse, SetEnvironmentModeResponses, SetProductReplacementData, SetProductReplacementError, SetProductReplacementErrors, SetProductReplacementResponse, SetProductReplacementResponses, SetProviderAssignmentRequest, SetWebhookDestinationStatusData, SetWebhookDestinationStatusError, SetWebhookDestinationStatusErrors, SetWebhookDestinationStatusRequest, SetWebhookDestinationStatusResponse, SetWebhookDestinationStatusResponses, SignUpData, SignUpError, SignUpErrors, SignUpRequest, SignUpResponse, SignUpResponses, SimulatePlacementDecisionData, SimulatePlacementDecisionError, SimulatePlacementDecisionErrors, SimulatePlacementDecisionResponse, SimulatePlacementDecisionResponses, StoreCredentialId, StoreEnvironmentClassification, StoreServerCredential, StoreServerCredentialApplication, StoreServerCredentialWithEndpoint, SubmitSdkRestoreData, SubmitSdkRestoreError, SubmitSdkRestoreErrors, SubmitSdkRestoreResponse, SubmitSdkRestoreResponses, SubmitServerRestoreData, SubmitServerRestoreError, SubmitServerRestoreErrors, SubmitServerRestoreResponse, SubmitServerRestoreResponses, SubmitServerTransactionObservationData, SubmitServerTransactionObservationError, SubmitServerTransactionObservationErrors, SubmitServerTransactionObservationResponse, SubmitServerTransactionObservationResponses, SubmitTransactionObservationData, SubmitTransactionObservationError, SubmitTransactionObservationErrors, SubmitTransactionObservationResponse, SubmitTransactionObservationResponses, SubscriptionInstanceId, SubscriptionSnapshotRecord, SubscriptionSummary, SubscriptionTimelineEntry, SyncCustomerEntitlementsData, SyncCustomerEntitlementsError, SyncCustomerEntitlementsErrors, SyncCustomerEntitlementsResponse, SyncCustomerEntitlementsResponses, SyncCustomerEntitlementsWithNegotiationData, SyncCustomerEntitlementsWithNegotiationError, SyncCustomerEntitlementsWithNegotiationErrors, SyncCustomerEntitlementsWithNegotiationResponse, SyncCustomerEntitlementsWithNegotiationResponses, TestProviderConnectionData, TestProviderConnectionError, TestProviderConnectionErrors, TestProviderConnectionResponse, TestProviderConnectionResponses, TestStoreServerCredentialData, TestStoreServerCredentialError, TestStoreServerCredentialErrors, TestStoreServerCredentialResponse, TestStoreServerCredentialResponses, Timestamp, TransactionFact, TransactionReference, TransitionBillingMigrationCaseData, TransitionBillingMigrationCaseError, TransitionBillingMigrationCaseErrors, TransitionBillingMigrationCaseResponse, TransitionBillingMigrationCaseResponses, TransitionExperimentLifecycleData, TransitionExperimentLifecycleError, TransitionExperimentLifecycleErrors, TransitionExperimentLifecycleResponse, TransitionExperimentLifecycleResponses, Uncertainty, UpdateAnalyticsSettingsData, UpdateAnalyticsSettingsError, UpdateAnalyticsSettingsErrors, UpdateAnalyticsSettingsRequest, UpdateAnalyticsSettingsResponse, UpdateAnalyticsSettingsResponses, UpdateBillingSettingsData, UpdateBillingSettingsError, UpdateBillingSettingsErrors, UpdateBillingSettingsResponse, UpdateBillingSettingsResponses, UpdateDraftRequest, UpdateEntitlementData, UpdateEntitlementError, UpdateEntitlementErrors, UpdateEntitlementResponse, UpdateEntitlementResponses, UpdateEnvironmentData, UpdateEnvironmentError, UpdateEnvironmentErrors, UpdateEnvironmentResponse, UpdateEnvironmentResponses, UpdateExperimentDraftData, UpdateExperimentDraftError, UpdateExperimentDraftErrors, UpdateExperimentDraftRequest, UpdateExperimentDraftResponse, UpdateExperimentDraftResponses, UpdateMemberData, UpdateMemberError, UpdateMemberErrors, UpdateMemberRequest, UpdateMemberResponse, UpdateMemberResponses, UpdateNameRequest, UpdateOrganizationData, UpdateOrganizationError, UpdateOrganizationErrors, UpdateOrganizationRequest, UpdateOrganizationResponse, UpdateOrganizationResponses, UpdatePaywallData, UpdatePaywallDraftData, UpdatePaywallDraftError, UpdatePaywallDraftErrors, UpdatePaywallDraftResponse, UpdatePaywallDraftResponses, UpdatePaywallError, UpdatePaywallErrors, UpdatePaywallRequest, UpdatePaywallResponse, UpdatePaywallResponses, UpdatePlacementData, UpdatePlacementError, UpdatePlacementErrors, UpdatePlacementRequest, UpdatePlacementResponse, UpdatePlacementResponses, UpdatePlacementRuleSetDraftData, UpdatePlacementRuleSetDraftError, UpdatePlacementRuleSetDraftErrors, UpdatePlacementRuleSetDraftResponse, UpdatePlacementRuleSetDraftResponses, UpdatePlanData, UpdatePlanError, UpdatePlanErrors, UpdatePlanResponse, UpdatePlanResponses, UpdateProductData, UpdateProductEntitlementGrantVersionData, UpdateProductEntitlementGrantVersionError, UpdateProductEntitlementGrantVersionErrors, UpdateProductError, UpdateProductErrors, UpdateProductResponse, UpdateProductResponses, UpdateProjectData, UpdateProjectError, UpdateProjectErrors, UpdateProjectResponse, UpdateProjectResponses, UpdateWebhookDestinationData, UpdateWebhookDestinationError, UpdateWebhookDestinationErrors, UpdateWebhookDestinationRequest, UpdateWebhookDestinationResponse, UpdateWebhookDestinationResponses, UploadAssetData, UploadAssetError, UploadAssetErrors, UploadAssetResponse, UploadAssetResponses, User, UserEnvelope, ValidateExperimentDraftData, ValidateExperimentDraftResponse, ValidateExperimentDraftResponses, ValidatePaywallDraftData, ValidatePaywallDraftError, ValidatePaywallDraftErrors, ValidatePaywallDraftResponse, ValidatePaywallDraftResponses, ValidatePlacementRuleSetData, ValidatePlacementRuleSetResponse, ValidatePlacementRuleSetResponses, ValidationAttempt, ValidationSummary, ValidationSummaryEnvelope, VersionId, WebhookDelivery, WebhookDeliveryAttempt, WebhookDeliveryId, WebhookDestination, WebhookDestinationId, WebhookDestinationWithSecret, WebhookSigningSecretMetadata, WorkspaceBootstrap, WorkspaceBootstrapEnvelope } from './types.gen'; diff --git a/apps/dashboard/src/generated/api/sdk.gen.ts b/apps/dashboard/src/generated/api/sdk.gen.ts index a5cae79b..e1d835fd 100644 --- a/apps/dashboard/src/generated/api/sdk.gen.ts +++ b/apps/dashboard/src/generated/api/sdk.gen.ts @@ -2,7 +2,7 @@ import { type Client, type ClientMeta, formDataBodySerializer, type Options as Options2, type RequestResult, type TDataShape } from './client'; import { client } from './client.gen'; -import type { AddMemberData, AddMemberErrors, AddMemberResponses, AddPlanProductData, AddPlanProductErrors, AddPlanProductResponses, AddProductEntitlementData, AddProductEntitlementErrors, AddProductEntitlementResponses, ArchiveAssetData, ArchiveAssetErrors, ArchiveAssetResponses, ArchivePlacementAttributeData, ArchivePlacementAttributeErrors, ArchivePlacementAttributeResponses, ArchivePlacementRuleSetData, ArchivePlacementRuleSetErrors, ArchivePlacementRuleSetResponses, ArchivePlacementWithUsageCheckData, ArchivePlacementWithUsageCheckErrors, ArchivePlacementWithUsageCheckResponses, ArchiveProductData, ArchiveProductErrors, ArchiveProductResponses, ArchiveProjectData, ArchiveProjectErrors, ArchiveProjectResponses, ArchiveProviderMappingData, ArchiveProviderMappingErrors, ArchiveProviderMappingResponses, AttachBillingCustomerAliasData, AttachBillingCustomerAliasErrors, AttachBillingCustomerAliasResponses, BindPlacementData, BindPlacementErrors, BindPlacementResponses, CheckCustomerEntitlementsData, CheckCustomerEntitlementsErrors, CheckCustomerEntitlementsResponses, ClearActiveProviderAssignmentData, ClearActiveProviderAssignmentErrors, ClearActiveProviderAssignmentResponses, ClonePaywallVersionToDraftData, ClonePaywallVersionToDraftErrors, ClonePaywallVersionToDraftResponses, ClonePlacementRuleSetVersionData, ClonePlacementRuleSetVersionErrors, ClonePlacementRuleSetVersionResponses, CloseQuarantineRecordSupersededData, CloseQuarantineRecordSupersededErrors, CloseQuarantineRecordSupersededResponses, CompareAnalyticsPaywallVersionsData, CompareAnalyticsPaywallVersionsErrors, CompareAnalyticsPaywallVersionsResponses, CreateAnalyticsEventExportData, CreateAnalyticsEventExportErrors, CreateAnalyticsEventExportResponses, CreateAnalyticsPrivacyDeletionData, CreateAnalyticsPrivacyDeletionErrors, CreateAnalyticsPrivacyDeletionResponses, CreateAnalyticsPrivacyExportData, CreateAnalyticsPrivacyExportErrors, CreateAnalyticsPrivacyExportResponses, CreateApiKeyData, CreateApiKeyErrors, CreateApiKeyResponses, CreateApplicationData, CreateApplicationErrors, CreateApplicationResponses, CreateBillingCustomerSyncRequestData, CreateBillingCustomerSyncRequestErrors, CreateBillingCustomerSyncRequestResponses, CreateBillingProjectionReplayData, CreateBillingProjectionReplayErrors, CreateBillingProjectionReplayResponses, CreateEntitlementData, CreateEntitlementErrors, CreateEntitlementResponses, CreateExperimentData, CreateExperimentErrors, CreateExperimentGroupVersionData, CreateExperimentGroupVersionErrors, CreateExperimentGroupVersionResponses, CreateExperimentMutualExclusionGroupVersionData, CreateExperimentMutualExclusionGroupVersionErrors, CreateExperimentMutualExclusionGroupVersionResponses, CreateExperimentQaOverrideData, CreateExperimentQaOverrideErrors, CreateExperimentQaOverrideResponses, CreateExperimentRawExportData, CreateExperimentRawExportErrors, CreateExperimentRawExportResponses, CreateExperimentResponses, CreateOrganizationData, CreateOrganizationErrors, CreateOrganizationResponses, CreatePaywallData, CreatePaywallDraftData, CreatePaywallDraftErrors, CreatePaywallDraftResponses, CreatePaywallErrors, CreatePaywallResponses, CreatePlacementAliasData, CreatePlacementAliasErrors, CreatePlacementAliasResponses, CreatePlacementAttributeData, CreatePlacementAttributeErrors, CreatePlacementAttributeResponses, CreatePlacementData, CreatePlacementErrors, CreatePlacementQaOverrideData, CreatePlacementQaOverrideErrors, CreatePlacementQaOverrideResponses, CreatePlacementResponses, CreatePlacementRuleSetData, CreatePlacementRuleSetErrors, CreatePlacementRuleSetResponses, CreatePlanData, CreatePlanErrors, CreatePlanResponses, CreateProductData, CreateProductErrors, CreateProductResponses, CreateProjectData, CreateProjectErrors, CreateProjectResponses, CreateProviderConnectionData, CreateProviderConnectionErrors, CreateProviderConnectionResponses, CreateProviderMappingData, CreateProviderMappingDraftData, CreateProviderMappingDraftErrors, CreateProviderMappingDraftResponses, CreateProviderMappingErrors, CreateProviderMappingObservationData, CreateProviderMappingObservationErrors, CreateProviderMappingObservationResponses, CreateProviderMappingResponses, CreateReconciliationRunData, CreateReconciliationRunErrors, CreateReconciliationRunResponses, CreateReplayJobData, CreateReplayJobErrors, CreateReplayJobResponses, CreateStoreServerCredentialData, CreateStoreServerCredentialErrors, CreateStoreServerCredentialResponses, CreateWebhookDestinationData, CreateWebhookDestinationErrors, CreateWebhookDestinationResponses, DeleteProductData, DeleteProductErrors, DeleteProductResponses, DeleteWebhookDestinationData, DeleteWebhookDestinationErrors, DeleteWebhookDestinationResponses, DownloadAnalyticsJobData, DownloadAnalyticsJobErrors, DownloadAnalyticsJobResponses, EnqueueProviderSyncData, EnqueueProviderSyncErrors, EnqueueProviderSyncResponses, GetActivePaywallDraftData, GetActivePaywallDraftErrors, GetActivePaywallDraftResponses, GetActiveProviderAssignmentData, GetActiveProviderAssignmentErrors, GetActiveProviderAssignmentResponses, GetAnalyticsBreakdownData, GetAnalyticsBreakdownErrors, GetAnalyticsBreakdownResponses, GetAnalyticsFreshnessData, GetAnalyticsFreshnessErrors, GetAnalyticsFreshnessResponses, GetAnalyticsFunnelData, GetAnalyticsFunnelErrors, GetAnalyticsFunnelResponses, GetAnalyticsJobData, GetAnalyticsJobErrors, GetAnalyticsJobResponses, GetAnalyticsOverviewData, GetAnalyticsOverviewErrors, GetAnalyticsOverviewResponses, GetAnalyticsProductAvailabilityFailuresData, GetAnalyticsProductAvailabilityFailuresErrors, GetAnalyticsProductAvailabilityFailuresResponses, GetAnalyticsProviderErrorsData, GetAnalyticsProviderErrorsErrors, GetAnalyticsProviderErrorsResponses, GetAnalyticsSettingsData, GetAnalyticsSettingsErrors, GetAnalyticsSettingsResponses, GetAssetContentData, GetAssetContentErrors, GetAssetContentResponses, GetAssetData, GetAssetErrors, GetAssetResponses, GetAssetUsageData, GetAssetUsageErrors, GetAssetUsageResponses, GetBillingCustomerData, GetBillingCustomerEntitlementSnapshotData, GetBillingCustomerEntitlementSnapshotErrors, GetBillingCustomerEntitlementSnapshotResponses, GetBillingCustomerErrors, GetBillingCustomerResponses, GetBillingHealthData, GetBillingHealthErrors, GetBillingHealthResponses, GetBillingIdentityConflictData, GetBillingIdentityConflictErrors, GetBillingIdentityConflictResponses, GetBillingProjectionHealthData, GetBillingProjectionHealthErrors, GetBillingProjectionHealthResponses, GetBillingRestoreJobData, GetBillingRestoreJobErrors, GetBillingRestoreJobResponses, GetBillingSettingsData, GetBillingSettingsErrors, GetBillingSettingsResponses, GetBillingSubscriptionData, GetBillingSubscriptionErrors, GetBillingSubscriptionResponses, GetCustomerEntitlementSnapshotData, GetCustomerEntitlementSnapshotErrors, GetCustomerEntitlementSnapshotResponses, GetEntitlementData, GetEntitlementErrors, GetEntitlementResponses, GetExperimentData, GetExperimentErrors, GetExperimentResponses, GetExperimentResultsData, GetExperimentResultsResponses, GetExperimentSampleRatioMismatchData, GetExperimentSampleRatioMismatchResponses, GetHealthData, GetHealthResponses, GetNativeProviderProfileData, GetNativeProviderProfileErrors, GetNativeProviderProfileResponses, GetOperatorBillingCustomerData, GetOperatorBillingCustomerErrors, GetOperatorBillingCustomerResponses, GetOperatorBillingIdentityConflictData, GetOperatorBillingIdentityConflictErrors, GetOperatorBillingIdentityConflictResponses, GetOrganizationData, GetOrganizationErrors, GetOrganizationResponses, GetPaywallData, GetPaywallDraftData, GetPaywallDraftErrors, GetPaywallDraftResponses, GetPaywallErrors, GetPaywallResponses, GetPaywallVersionData, GetPaywallVersionErrors, GetPaywallVersionResponses, GetPlacementBindingData, GetPlacementBindingErrors, GetPlacementBindingResponses, GetPlacementDecisionData, GetPlacementDecisionErrors, GetPlacementDecisionResponses, GetPlacementUsageData, GetPlacementUsageErrors, GetPlacementUsageResponses, GetPlanData, GetPlanErrors, GetPlanResponses, GetProductData, GetProductEntitlementGrantVersionData, GetProductEntitlementGrantVersionErrors, GetProductEntitlementGrantVersionResponses, GetProductErrors, GetProductReadinessData, GetProductReadinessErrors, GetProductReadinessResponses, GetProductResponses, GetProductUsageData, GetProductUsageErrors, GetProductUsageResponses, GetProjectData, GetProjectErrors, GetProjectResponses, GetProviderConnectionCapabilitiesData, GetProviderConnectionCapabilitiesErrors, GetProviderConnectionCapabilitiesResponses, GetProviderConnectionData, GetProviderConnectionErrors, GetProviderConnectionHealthData, GetProviderConnectionHealthErrors, GetProviderConnectionHealthResponses, GetProviderConnectionResponses, GetProviderMappingMetadataData, GetProviderMappingMetadataErrors, GetProviderMappingMetadataResponses, GetProviderMappingUsageData, GetProviderMappingUsageErrors, GetProviderMappingUsageResponses, GetProviderReadinessData, GetProviderReadinessErrors, GetProviderReadinessResponses, GetQuarantineRecordData, GetQuarantineRecordErrors, GetQuarantineRecordResponses, GetReadinessData, GetReadinessErrors, GetReadinessResponses, GetSdkCommerceConfigurationData, GetSdkCommerceConfigurationErrors, GetSdkCommerceConfigurationResponses, GetSdkConfigurationData, GetSdkConfigurationErrors, GetSdkConfigurationResponses, GetSdkRestoreData, GetSdkRestoreErrors, GetSdkRestoreResponses, GetServerRestoreData, GetServerRestoreErrors, GetServerRestoreResponses, GetSessionData, GetSessionErrors, GetSessionResponses, GetStoreServerCredentialData, GetStoreServerCredentialErrors, GetStoreServerCredentialResponses, GetSubscriptionSnapshotData, GetSubscriptionSnapshotErrors, GetSubscriptionSnapshotResponses, GetWebhookDeliveryData, GetWebhookDeliveryErrors, GetWebhookDeliveryResponses, GetWebhookDestinationData, GetWebhookDestinationErrors, GetWebhookDestinationResponses, IdentifyBillingCustomerData, IdentifyBillingCustomerErrors, IdentifyBillingCustomerResponses, ImportProviderProductsData, ImportProviderProductsErrors, ImportProviderProductsResponses, IngestAnalyticsEventBatchData, IngestAnalyticsEventBatchErrors, IngestAnalyticsEventBatchResponses, IssueCustomerAccessTokenData, IssueCustomerAccessTokenErrors, IssueCustomerAccessTokenResponses, ListApiKeysData, ListApiKeysErrors, ListApiKeysResponses, ListApplicationsData, ListApplicationsErrors, ListApplicationsResponses, ListAssetsData, ListAssetsErrors, ListAssetsResponses, ListAuditEventsData, ListAuditEventsErrors, ListAuditEventsResponses, ListBillingCustomerAliasesData, ListBillingCustomerAliasesErrors, ListBillingCustomerAliasesResponses, ListBillingCustomersData, ListBillingCustomersErrors, ListBillingCustomersResponses, ListBillingCustomerSubscriptionsData, ListBillingCustomerSubscriptionsErrors, ListBillingCustomerSubscriptionsResponses, ListBillingIdentityConflictsData, ListBillingIdentityConflictsErrors, ListBillingIdentityConflictsResponses, ListBillingLedgerData, ListBillingLedgerErrors, ListBillingLedgerResponses, ListBillingQuarantineData, ListBillingQuarantineErrors, ListBillingQuarantineResponses, ListBillingRestoreJobsData, ListBillingRestoreJobsErrors, ListBillingRestoreJobsResponses, ListBillingSubscriptionTimelineData, ListBillingSubscriptionTimelineErrors, ListBillingSubscriptionTimelineResponses, ListConfigurationReleasesData, ListConfigurationReleasesErrors, ListConfigurationReleasesResponses, ListCustomerAccessTokensData, ListCustomerAccessTokensErrors, ListCustomerAccessTokensResponses, ListCustomerSubscriptionsData, ListCustomerSubscriptionsErrors, ListCustomerSubscriptionsResponses, ListEntitlementsData, ListEntitlementsErrors, ListEntitlementsResponses, ListEnvironmentsData, ListEnvironmentsErrors, ListEnvironmentsResponses, ListExperimentGroupsData, ListExperimentGroupsResponses, ListExperimentHistoryData, ListExperimentHistoryResponses, ListExperimentMetricDefinitionsData, ListExperimentMetricDefinitionsResponses, ListExperimentMutualExclusionGroupVersionsData, ListExperimentMutualExclusionGroupVersionsErrors, ListExperimentMutualExclusionGroupVersionsResponses, ListExperimentQaOverridesData, ListExperimentQaOverridesResponses, ListExperimentsData, ListExperimentsResponses, ListExperimentVersionsData, ListExperimentVersionsResponses, ListMembersData, ListMembersErrors, ListMembersResponses, ListOperatorBillingIdentityConflictsData, ListOperatorBillingIdentityConflictsErrors, ListOperatorBillingIdentityConflictsResponses, ListOrganizationsData, ListOrganizationsErrors, ListOrganizationsResponses, ListPaywallsData, ListPaywallsErrors, ListPaywallsResponses, ListPaywallVersionsData, ListPaywallVersionsErrors, ListPaywallVersionsResponses, ListPlacementAliasesData, ListPlacementAliasesResponses, ListPlacementAttributesData, ListPlacementAttributesErrors, ListPlacementAttributesResponses, ListPlacementQaOverridesData, ListPlacementQaOverridesResponses, ListPlacementRuleSetVersionsData, ListPlacementRuleSetVersionsResponses, ListPlacementsData, ListPlacementsErrors, ListPlacementsResponses, ListPlanProductsData, ListPlanProductsErrors, ListPlanProductsResponses, ListPlansData, ListPlansErrors, ListPlansResponses, ListProductEntitlementGrantVersionsData, ListProductEntitlementGrantVersionsErrors, ListProductEntitlementGrantVersionsResponses, ListProductEntitlementsData, ListProductEntitlementsErrors, ListProductEntitlementsResponses, ListProductsData, ListProductsErrors, ListProductsResponses, ListProjectsData, ListProjectsErrors, ListProjectsResponses, ListProviderConnectionDiagnosticsData, ListProviderConnectionDiagnosticsErrors, ListProviderConnectionDiagnosticsResponses, ListProviderConnectionsData, ListProviderConnectionsErrors, ListProviderConnectionsResponses, ListProviderMappingObservationsData, ListProviderMappingObservationsErrors, ListProviderMappingObservationsResponses, ListProviderMappingsData, ListProviderMappingsErrors, ListProviderMappingsResponses, ListProviderSyncRunsData, ListProviderSyncRunsErrors, ListProviderSyncRunsResponses, ListReconciliationRunsData, ListReconciliationRunsErrors, ListReconciliationRunsResponses, ListReplayJobsData, ListReplayJobsErrors, ListReplayJobsResponses, ListStoreServerCredentialsData, ListStoreServerCredentialsErrors, ListStoreServerCredentialsResponses, ListSubscriptionTimelineData, ListSubscriptionTimelineErrors, ListSubscriptionTimelineResponses, ListTransactionFactsData, ListTransactionFactsErrors, ListTransactionFactsResponses, ListValidationAttemptsData, ListValidationAttemptsErrors, ListValidationAttemptsResponses, ListWebhookDeliveriesData, ListWebhookDeliveriesErrors, ListWebhookDeliveriesResponses, ListWebhookDeliveryAttemptsData, ListWebhookDeliveryAttemptsErrors, ListWebhookDeliveryAttemptsResponses, ListWebhookDestinationsData, ListWebhookDestinationsErrors, ListWebhookDestinationsResponses, ListWebhookSigningSecretsData, ListWebhookSigningSecretsErrors, ListWebhookSigningSecretsResponses, LoginData, LoginErrors, LoginResponses, LogoutData, LogoutResponses, LookupBillingCustomerData, LookupBillingCustomerErrors, LookupBillingCustomerResponses, PreviewAnalyticsPrivacyRequestData, PreviewAnalyticsPrivacyRequestErrors, PreviewAnalyticsPrivacyRequestResponses, PreviewProductEntitlementGrantImpactData, PreviewProductEntitlementGrantImpactErrors, PreviewProductEntitlementGrantImpactResponses, PreviewProviderCatalogData, PreviewProviderCatalogErrors, PreviewProviderCatalogResponses, PublishConfigurationData, PublishConfigurationErrors, PublishConfigurationResponses, PublishExperimentData, PublishExperimentErrors, PublishExperimentResponses, PublishPlacementRuleSetData, PublishPlacementRuleSetErrors, PublishPlacementRuleSetResponses, PublishProductEntitlementGrantVersionData, PublishProductEntitlementGrantVersionErrors, PublishProductEntitlementGrantVersionResponses, ReceiveAppleStoreNotificationData, ReceiveAppleStoreNotificationErrors, ReceiveAppleStoreNotificationResponses, ReconnectProviderConnectionData, ReconnectProviderConnectionErrors, ReconnectProviderConnectionResponses, RemoveMemberData, RemoveMemberErrors, RemoveMemberResponses, RemovePlanProductData, RemovePlanProductErrors, RemovePlanProductResponses, RemoveProductEntitlementData, RemoveProductEntitlementErrors, RemoveProductEntitlementResponses, ReplaceProviderConnectionScopesData, ReplaceProviderConnectionScopesErrors, ReplaceProviderConnectionScopesResponses, ReplaceProviderMappingData, ReplaceProviderMappingErrors, ReplaceProviderMappingResponses, ReplayWebhookDeliveryData, ReplayWebhookDeliveryErrors, ReplayWebhookDeliveryResponses, RequestBillingCustomerSyncData, RequestBillingCustomerSyncErrors, RequestBillingCustomerSyncResponses, ResolveBillingIdentityConflictData, ResolveBillingIdentityConflictErrors, ResolveBillingIdentityConflictResponses, RestoreProductData, RestoreProductErrors, RestoreProductResponses, RestoreProjectData, RestoreProjectErrors, RestoreProjectResponses, RetireWebhookSigningSecretData, RetireWebhookSigningSecretErrors, RetireWebhookSigningSecretResponses, RetryQuarantinedInputData, RetryQuarantinedInputErrors, RetryQuarantinedInputResponses, RevokeApiKeyData, RevokeApiKeyErrors, RevokeApiKeyResponses, RevokeBillingCustomerAliasData, RevokeBillingCustomerAliasErrors, RevokeBillingCustomerAliasResponses, RevokeCustomerAccessTokenData, RevokeCustomerAccessTokenErrors, RevokeCustomerAccessTokenResponses, RevokeExperimentQaOverrideData, RevokeExperimentQaOverrideErrors, RevokeExperimentQaOverrideResponses, RevokePlacementQaOverrideData, RevokePlacementQaOverrideErrors, RevokePlacementQaOverrideResponses, RevokeProviderConnectionData, RevokeProviderConnectionErrors, RevokeProviderConnectionResponses, RevokeStoreServerCredentialData, RevokeStoreServerCredentialErrors, RevokeStoreServerCredentialResponses, RollbackConfigurationReleaseData, RollbackConfigurationReleaseErrors, RollbackConfigurationReleaseResponses, RotateApiKeyData, RotateApiKeyErrors, RotateApiKeyResponses, RotateProviderCredentialData, RotateProviderCredentialErrors, RotateProviderCredentialResponses, RotateStoreServerCredentialData, RotateStoreServerCredentialErrors, RotateStoreServerCredentialResponses, RotateWebhookSigningSecretData, RotateWebhookSigningSecretErrors, RotateWebhookSigningSecretResponses, SetActiveProviderAssignmentData, SetActiveProviderAssignmentErrors, SetActiveProviderAssignmentResponses, SetEnvironmentModeData, SetEnvironmentModeErrors, SetEnvironmentModeResponses, SetProductReplacementData, SetProductReplacementErrors, SetProductReplacementResponses, SetWebhookDestinationStatusData, SetWebhookDestinationStatusErrors, SetWebhookDestinationStatusResponses, SignUpData, SignUpErrors, SignUpResponses, SimulatePlacementDecisionData, SimulatePlacementDecisionErrors, SimulatePlacementDecisionResponses, SubmitSdkRestoreData, SubmitSdkRestoreErrors, SubmitSdkRestoreResponses, SubmitServerRestoreData, SubmitServerRestoreErrors, SubmitServerRestoreResponses, SubmitServerTransactionObservationData, SubmitServerTransactionObservationErrors, SubmitServerTransactionObservationResponses, SubmitTransactionObservationData, SubmitTransactionObservationErrors, SubmitTransactionObservationResponses, SyncCustomerEntitlementsData, SyncCustomerEntitlementsErrors, SyncCustomerEntitlementsResponses, SyncCustomerEntitlementsWithNegotiationData, SyncCustomerEntitlementsWithNegotiationErrors, SyncCustomerEntitlementsWithNegotiationResponses, TestProviderConnectionData, TestProviderConnectionErrors, TestProviderConnectionResponses, TestStoreServerCredentialData, TestStoreServerCredentialErrors, TestStoreServerCredentialResponses, TransitionExperimentLifecycleData, TransitionExperimentLifecycleErrors, TransitionExperimentLifecycleResponses, UpdateAnalyticsSettingsData, UpdateAnalyticsSettingsErrors, UpdateAnalyticsSettingsResponses, UpdateBillingSettingsData, UpdateBillingSettingsErrors, UpdateBillingSettingsResponses, UpdateEntitlementData, UpdateEntitlementErrors, UpdateEntitlementResponses, UpdateEnvironmentData, UpdateEnvironmentErrors, UpdateEnvironmentResponses, UpdateExperimentDraftData, UpdateExperimentDraftErrors, UpdateExperimentDraftResponses, UpdateMemberData, UpdateMemberErrors, UpdateMemberResponses, UpdateOrganizationData, UpdateOrganizationErrors, UpdateOrganizationResponses, UpdatePaywallData, UpdatePaywallDraftData, UpdatePaywallDraftErrors, UpdatePaywallDraftResponses, UpdatePaywallErrors, UpdatePaywallResponses, UpdatePlacementData, UpdatePlacementErrors, UpdatePlacementResponses, UpdatePlacementRuleSetDraftData, UpdatePlacementRuleSetDraftErrors, UpdatePlacementRuleSetDraftResponses, UpdatePlanData, UpdatePlanErrors, UpdatePlanResponses, UpdateProductData, UpdateProductEntitlementGrantVersionData, UpdateProductEntitlementGrantVersionErrors, UpdateProductErrors, UpdateProductResponses, UpdateProjectData, UpdateProjectErrors, UpdateProjectResponses, UpdateWebhookDestinationData, UpdateWebhookDestinationErrors, UpdateWebhookDestinationResponses, UploadAssetData, UploadAssetErrors, UploadAssetResponses, ValidateExperimentDraftData, ValidateExperimentDraftResponses, ValidatePaywallDraftData, ValidatePaywallDraftErrors, ValidatePaywallDraftResponses, ValidatePlacementRuleSetData, ValidatePlacementRuleSetResponses } from './types.gen'; +import type { AddMemberData, AddMemberErrors, AddMemberResponses, AddPlanProductData, AddPlanProductErrors, AddPlanProductResponses, AddProductEntitlementData, AddProductEntitlementErrors, AddProductEntitlementResponses, ApproveBillingMigrationLegalHoldData, ApproveBillingMigrationLegalHoldErrors, ApproveBillingMigrationLegalHoldResponses, ApproveBillingMigrationProposalData, ApproveBillingMigrationProposalErrors, ApproveBillingMigrationProposalResponses, ArchiveAssetData, ArchiveAssetErrors, ArchiveAssetResponses, ArchivePlacementAttributeData, ArchivePlacementAttributeErrors, ArchivePlacementAttributeResponses, ArchivePlacementRuleSetData, ArchivePlacementRuleSetErrors, ArchivePlacementRuleSetResponses, ArchivePlacementWithUsageCheckData, ArchivePlacementWithUsageCheckErrors, ArchivePlacementWithUsageCheckResponses, ArchiveProductData, ArchiveProductErrors, ArchiveProductResponses, ArchiveProjectData, ArchiveProjectErrors, ArchiveProjectResponses, ArchiveProviderMappingData, ArchiveProviderMappingErrors, ArchiveProviderMappingResponses, AssessBillingMigrationReadinessData, AssessBillingMigrationReadinessErrors, AssessBillingMigrationReadinessResponses, AssessBillingMigrationRollbackReadinessData, AssessBillingMigrationRollbackReadinessErrors, AssessBillingMigrationRollbackReadinessResponses, AttachBillingCustomerAliasData, AttachBillingCustomerAliasErrors, AttachBillingCustomerAliasResponses, BindPlacementData, BindPlacementErrors, BindPlacementResponses, CheckCustomerEntitlementsData, CheckCustomerEntitlementsErrors, CheckCustomerEntitlementsResponses, ClearActiveProviderAssignmentData, ClearActiveProviderAssignmentErrors, ClearActiveProviderAssignmentResponses, ClonePaywallVersionToDraftData, ClonePaywallVersionToDraftErrors, ClonePaywallVersionToDraftResponses, ClonePlacementRuleSetVersionData, ClonePlacementRuleSetVersionErrors, ClonePlacementRuleSetVersionResponses, CloseQuarantineRecordSupersededData, CloseQuarantineRecordSupersededErrors, CloseQuarantineRecordSupersededResponses, CompareAnalyticsPaywallVersionsData, CompareAnalyticsPaywallVersionsErrors, CompareAnalyticsPaywallVersionsResponses, CompleteBillingMigrationData, CompleteBillingMigrationErrors, CompleteBillingMigrationResponses, CreateAnalyticsEventExportData, CreateAnalyticsEventExportErrors, CreateAnalyticsEventExportResponses, CreateAnalyticsPrivacyDeletionData, CreateAnalyticsPrivacyDeletionErrors, CreateAnalyticsPrivacyDeletionResponses, CreateAnalyticsPrivacyExportData, CreateAnalyticsPrivacyExportErrors, CreateAnalyticsPrivacyExportResponses, CreateApiKeyData, CreateApiKeyErrors, CreateApiKeyResponses, CreateApplicationData, CreateApplicationErrors, CreateApplicationResponses, CreateBillingCustomerSyncRequestData, CreateBillingCustomerSyncRequestErrors, CreateBillingCustomerSyncRequestResponses, CreateBillingMigrationCaseData, CreateBillingMigrationCaseErrors, CreateBillingMigrationCaseResponses, CreateBillingMigrationCheckpointData, CreateBillingMigrationCheckpointErrors, CreateBillingMigrationCheckpointResponses, CreateBillingMigrationImportBatchData, CreateBillingMigrationImportBatchErrors, CreateBillingMigrationImportBatchResponses, CreateBillingMigrationMappingSetData, CreateBillingMigrationMappingSetErrors, CreateBillingMigrationMappingSetResponses, CreateBillingMigrationProgramData, CreateBillingMigrationProgramErrors, CreateBillingMigrationProgramResponses, CreateBillingProjectionReplayData, CreateBillingProjectionReplayErrors, CreateBillingProjectionReplayResponses, CreateEntitlementData, CreateEntitlementErrors, CreateEntitlementResponses, CreateExperimentData, CreateExperimentErrors, CreateExperimentGroupVersionData, CreateExperimentGroupVersionErrors, CreateExperimentGroupVersionResponses, CreateExperimentMutualExclusionGroupVersionData, CreateExperimentMutualExclusionGroupVersionErrors, CreateExperimentMutualExclusionGroupVersionResponses, CreateExperimentQaOverrideData, CreateExperimentQaOverrideErrors, CreateExperimentQaOverrideResponses, CreateExperimentRawExportData, CreateExperimentRawExportErrors, CreateExperimentRawExportResponses, CreateExperimentResponses, CreateOrganizationData, CreateOrganizationErrors, CreateOrganizationResponses, CreatePaywallData, CreatePaywallDraftData, CreatePaywallDraftErrors, CreatePaywallDraftResponses, CreatePaywallErrors, CreatePaywallResponses, CreatePlacementAliasData, CreatePlacementAliasErrors, CreatePlacementAliasResponses, CreatePlacementAttributeData, CreatePlacementAttributeErrors, CreatePlacementAttributeResponses, CreatePlacementData, CreatePlacementErrors, CreatePlacementQaOverrideData, CreatePlacementQaOverrideErrors, CreatePlacementQaOverrideResponses, CreatePlacementResponses, CreatePlacementRuleSetData, CreatePlacementRuleSetErrors, CreatePlacementRuleSetResponses, CreatePlanData, CreatePlanErrors, CreatePlanResponses, CreateProductData, CreateProductErrors, CreateProductResponses, CreateProjectData, CreateProjectErrors, CreateProjectResponses, CreateProviderConnectionData, CreateProviderConnectionErrors, CreateProviderConnectionResponses, CreateProviderMappingData, CreateProviderMappingDraftData, CreateProviderMappingDraftErrors, CreateProviderMappingDraftResponses, CreateProviderMappingErrors, CreateProviderMappingObservationData, CreateProviderMappingObservationErrors, CreateProviderMappingObservationResponses, CreateProviderMappingResponses, CreateReconciliationRunData, CreateReconciliationRunErrors, CreateReconciliationRunResponses, CreateReplayJobData, CreateReplayJobErrors, CreateReplayJobResponses, CreateStoreServerCredentialData, CreateStoreServerCredentialErrors, CreateStoreServerCredentialResponses, CreateWebhookDestinationData, CreateWebhookDestinationErrors, CreateWebhookDestinationResponses, DeleteProductData, DeleteProductErrors, DeleteProductResponses, DeleteWebhookDestinationData, DeleteWebhookDestinationErrors, DeleteWebhookDestinationResponses, DownloadAnalyticsJobData, DownloadAnalyticsJobErrors, DownloadAnalyticsJobResponses, EnqueueProviderSyncData, EnqueueProviderSyncErrors, EnqueueProviderSyncResponses, ExecuteBillingMigrationCutoverData, ExecuteBillingMigrationCutoverErrors, ExecuteBillingMigrationCutoverResponses, ExecuteBillingMigrationRepairData, ExecuteBillingMigrationRepairErrors, ExecuteBillingMigrationRepairResponses, ExecuteBillingMigrationRollbackData, ExecuteBillingMigrationRollbackErrors, ExecuteBillingMigrationRollbackResponses, FreezeBillingMigrationMappingSetData, FreezeBillingMigrationMappingSetErrors, FreezeBillingMigrationMappingSetResponses, FreezeBillingMigrationStabilizationPolicyData, FreezeBillingMigrationStabilizationPolicyErrors, FreezeBillingMigrationStabilizationPolicyResponses, GetActivePaywallDraftData, GetActivePaywallDraftErrors, GetActivePaywallDraftResponses, GetActiveProviderAssignmentData, GetActiveProviderAssignmentErrors, GetActiveProviderAssignmentResponses, GetAnalyticsBreakdownData, GetAnalyticsBreakdownErrors, GetAnalyticsBreakdownResponses, GetAnalyticsFreshnessData, GetAnalyticsFreshnessErrors, GetAnalyticsFreshnessResponses, GetAnalyticsFunnelData, GetAnalyticsFunnelErrors, GetAnalyticsFunnelResponses, GetAnalyticsJobData, GetAnalyticsJobErrors, GetAnalyticsJobResponses, GetAnalyticsOverviewData, GetAnalyticsOverviewErrors, GetAnalyticsOverviewResponses, GetAnalyticsProductAvailabilityFailuresData, GetAnalyticsProductAvailabilityFailuresErrors, GetAnalyticsProductAvailabilityFailuresResponses, GetAnalyticsProviderErrorsData, GetAnalyticsProviderErrorsErrors, GetAnalyticsProviderErrorsResponses, GetAnalyticsSettingsData, GetAnalyticsSettingsErrors, GetAnalyticsSettingsResponses, GetAssetContentData, GetAssetContentErrors, GetAssetContentResponses, GetAssetData, GetAssetErrors, GetAssetResponses, GetAssetUsageData, GetAssetUsageErrors, GetAssetUsageResponses, GetBillingCustomerData, GetBillingCustomerEntitlementSnapshotData, GetBillingCustomerEntitlementSnapshotErrors, GetBillingCustomerEntitlementSnapshotResponses, GetBillingCustomerErrors, GetBillingCustomerResponses, GetBillingHealthData, GetBillingHealthErrors, GetBillingHealthResponses, GetBillingIdentityConflictData, GetBillingIdentityConflictErrors, GetBillingIdentityConflictResponses, GetBillingMigrationApprovalData, GetBillingMigrationApprovalErrors, GetBillingMigrationApprovalResponses, GetBillingMigrationAuthorityExecutionData, GetBillingMigrationAuthorityExecutionErrors, GetBillingMigrationAuthorityExecutionResponses, GetBillingMigrationCaseData, GetBillingMigrationCaseErrors, GetBillingMigrationCaseResponses, GetBillingMigrationCheckpointData, GetBillingMigrationCheckpointErrors, GetBillingMigrationCheckpointResponses, GetBillingMigrationCompletionReportData, GetBillingMigrationCompletionReportErrors, GetBillingMigrationCompletionReportResponses, GetBillingMigrationCredentialRemovalData, GetBillingMigrationCredentialRemovalErrors, GetBillingMigrationCredentialRemovalResponses, GetBillingMigrationImportBatchData, GetBillingMigrationImportBatchErrors, GetBillingMigrationImportBatchResponses, GetBillingMigrationLegalHoldData, GetBillingMigrationLegalHoldErrors, GetBillingMigrationLegalHoldProposalData, GetBillingMigrationLegalHoldProposalErrors, GetBillingMigrationLegalHoldProposalResponses, GetBillingMigrationLegalHoldResponses, GetBillingMigrationProgramData, GetBillingMigrationProgramErrors, GetBillingMigrationProgramResponses, GetBillingMigrationProposalData, GetBillingMigrationProposalErrors, GetBillingMigrationProposalResponses, GetBillingMigrationRepairExecutionData, GetBillingMigrationRepairExecutionErrors, GetBillingMigrationRepairExecutionResponses, GetBillingMigrationRepairPreviewData, GetBillingMigrationRepairPreviewErrors, GetBillingMigrationRepairPreviewResponses, GetBillingMigrationRunJobData, GetBillingMigrationRunJobErrors, GetBillingMigrationRunJobResponses, GetBillingMigrationSourcePullData, GetBillingMigrationSourcePullErrors, GetBillingMigrationSourcePullResponses, GetBillingMigrationWebhookRedeliveryData, GetBillingMigrationWebhookRedeliveryErrors, GetBillingMigrationWebhookRedeliveryResponses, GetBillingProjectionHealthData, GetBillingProjectionHealthErrors, GetBillingProjectionHealthResponses, GetBillingRestoreJobData, GetBillingRestoreJobErrors, GetBillingRestoreJobResponses, GetBillingSettingsData, GetBillingSettingsErrors, GetBillingSettingsResponses, GetBillingSubscriptionData, GetBillingSubscriptionErrors, GetBillingSubscriptionResponses, GetCurrentBillingMigrationCredentialRemovalData, GetCurrentBillingMigrationCredentialRemovalErrors, GetCurrentBillingMigrationCredentialRemovalResponses, GetCurrentBillingMigrationLegalHoldData, GetCurrentBillingMigrationLegalHoldErrors, GetCurrentBillingMigrationLegalHoldResponses, GetCurrentBillingMigrationStabilizationPolicyData, GetCurrentBillingMigrationStabilizationPolicyErrors, GetCurrentBillingMigrationStabilizationPolicyResponses, GetCustomerEntitlementSnapshotData, GetCustomerEntitlementSnapshotErrors, GetCustomerEntitlementSnapshotResponses, GetEntitlementData, GetEntitlementErrors, GetEntitlementResponses, GetExperimentData, GetExperimentErrors, GetExperimentResponses, GetExperimentResultsData, GetExperimentResultsResponses, GetExperimentSampleRatioMismatchData, GetExperimentSampleRatioMismatchResponses, GetHealthData, GetHealthResponses, GetLatestBillingMigrationCheckpointData, GetLatestBillingMigrationCheckpointErrors, GetLatestBillingMigrationCheckpointResponses, GetLatestBillingMigrationReadinessData, GetLatestBillingMigrationReadinessErrors, GetLatestBillingMigrationReadinessResponses, GetLatestBillingMigrationRollbackReadinessAssessmentData, GetLatestBillingMigrationRollbackReadinessAssessmentErrors, GetLatestBillingMigrationRollbackReadinessAssessmentResponses, GetLatestBillingMigrationRollbackReadinessCheckpointData, GetLatestBillingMigrationRollbackReadinessCheckpointErrors, GetLatestBillingMigrationRollbackReadinessCheckpointResponses, GetLatestBillingMigrationStabilizationObservationData, GetLatestBillingMigrationStabilizationObservationErrors, GetLatestBillingMigrationStabilizationObservationResponses, GetNativeProviderProfileData, GetNativeProviderProfileErrors, GetNativeProviderProfileResponses, GetOperatorBillingCustomerData, GetOperatorBillingCustomerErrors, GetOperatorBillingCustomerResponses, GetOperatorBillingIdentityConflictData, GetOperatorBillingIdentityConflictErrors, GetOperatorBillingIdentityConflictResponses, GetOrganizationData, GetOrganizationErrors, GetOrganizationResponses, GetPaywallData, GetPaywallDraftData, GetPaywallDraftErrors, GetPaywallDraftResponses, GetPaywallErrors, GetPaywallResponses, GetPaywallVersionData, GetPaywallVersionErrors, GetPaywallVersionResponses, GetPlacementBindingData, GetPlacementBindingErrors, GetPlacementBindingResponses, GetPlacementDecisionData, GetPlacementDecisionErrors, GetPlacementDecisionResponses, GetPlacementUsageData, GetPlacementUsageErrors, GetPlacementUsageResponses, GetPlanData, GetPlanErrors, GetPlanResponses, GetProductData, GetProductEntitlementGrantVersionData, GetProductEntitlementGrantVersionErrors, GetProductEntitlementGrantVersionResponses, GetProductErrors, GetProductReadinessData, GetProductReadinessErrors, GetProductReadinessResponses, GetProductResponses, GetProductUsageData, GetProductUsageErrors, GetProductUsageResponses, GetProjectData, GetProjectErrors, GetProjectResponses, GetProviderConnectionCapabilitiesData, GetProviderConnectionCapabilitiesErrors, GetProviderConnectionCapabilitiesResponses, GetProviderConnectionData, GetProviderConnectionErrors, GetProviderConnectionHealthData, GetProviderConnectionHealthErrors, GetProviderConnectionHealthResponses, GetProviderConnectionResponses, GetProviderMappingMetadataData, GetProviderMappingMetadataErrors, GetProviderMappingMetadataResponses, GetProviderMappingUsageData, GetProviderMappingUsageErrors, GetProviderMappingUsageResponses, GetProviderReadinessData, GetProviderReadinessErrors, GetProviderReadinessResponses, GetQuarantineRecordData, GetQuarantineRecordErrors, GetQuarantineRecordResponses, GetReadinessData, GetReadinessErrors, GetReadinessResponses, GetSdkCommerceConfigurationData, GetSdkCommerceConfigurationErrors, GetSdkCommerceConfigurationResponses, GetSdkConfigurationData, GetSdkConfigurationErrors, GetSdkConfigurationResponses, GetSdkRestoreData, GetSdkRestoreErrors, GetSdkRestoreResponses, GetServerRestoreData, GetServerRestoreErrors, GetServerRestoreResponses, GetSessionData, GetSessionErrors, GetSessionResponses, GetStoreServerCredentialData, GetStoreServerCredentialErrors, GetStoreServerCredentialResponses, GetSubscriptionSnapshotData, GetSubscriptionSnapshotErrors, GetSubscriptionSnapshotResponses, GetWebhookDeliveryData, GetWebhookDeliveryErrors, GetWebhookDeliveryResponses, GetWebhookDestinationData, GetWebhookDestinationErrors, GetWebhookDestinationResponses, GetWorkspaceBootstrapData, GetWorkspaceBootstrapErrors, GetWorkspaceBootstrapResponses, IdentifyBillingCustomerData, IdentifyBillingCustomerErrors, IdentifyBillingCustomerResponses, ImportProviderProductsData, ImportProviderProductsErrors, ImportProviderProductsResponses, IngestAnalyticsEventBatchData, IngestAnalyticsEventBatchErrors, IngestAnalyticsEventBatchResponses, InspectBillingMigrationCompletionData, InspectBillingMigrationCompletionErrors, InspectBillingMigrationCompletionResponses, IssueCustomerAccessTokenData, IssueCustomerAccessTokenErrors, IssueCustomerAccessTokenResponses, ListApiKeysData, ListApiKeysErrors, ListApiKeysResponses, ListApplicationsData, ListApplicationsErrors, ListApplicationsResponses, ListAssetsData, ListAssetsErrors, ListAssetsResponses, ListAuditEventsData, ListAuditEventsErrors, ListAuditEventsResponses, ListBillingCustomerAliasesData, ListBillingCustomerAliasesErrors, ListBillingCustomerAliasesResponses, ListBillingCustomersData, ListBillingCustomersErrors, ListBillingCustomersResponses, ListBillingCustomerSubscriptionsData, ListBillingCustomerSubscriptionsErrors, ListBillingCustomerSubscriptionsResponses, ListBillingIdentityConflictsData, ListBillingIdentityConflictsErrors, ListBillingIdentityConflictsResponses, ListBillingLedgerData, ListBillingLedgerErrors, ListBillingLedgerResponses, ListBillingMigrationApprovalsData, ListBillingMigrationApprovalsResponses, ListBillingMigrationAuthorityExecutionsData, ListBillingMigrationAuthorityExecutionsResponses, ListBillingMigrationCaseActionsData, ListBillingMigrationCaseActionsResponses, ListBillingMigrationCasesData, ListBillingMigrationCasesResponses, ListBillingMigrationCheckpointsData, ListBillingMigrationCheckpointsResponses, ListBillingMigrationCompletionHistoryData, ListBillingMigrationCompletionHistoryResponses, ListBillingMigrationCredentialRemovalsData, ListBillingMigrationCredentialRemovalsResponses, ListBillingMigrationDivergencesData, ListBillingMigrationDivergencesErrors, ListBillingMigrationDivergencesResponses, ListBillingMigrationImportBatchesData, ListBillingMigrationImportBatchesErrors, ListBillingMigrationImportBatchesResponses, ListBillingMigrationLegalHoldProposalsData, ListBillingMigrationLegalHoldProposalsResponses, ListBillingMigrationLegalHoldsData, ListBillingMigrationLegalHoldsResponses, ListBillingMigrationMappingSetsData, ListBillingMigrationMappingSetsErrors, ListBillingMigrationMappingSetsResponses, ListBillingMigrationProgramsData, ListBillingMigrationProgramsErrors, ListBillingMigrationProgramsResponses, ListBillingMigrationProposalsData, ListBillingMigrationProposalsResponses, ListBillingMigrationRepairExecutionsData, ListBillingMigrationRepairExecutionsResponses, ListBillingMigrationRepairPreviewsData, ListBillingMigrationRepairPreviewsResponses, ListBillingMigrationRollbackReadinessAssessmentsData, ListBillingMigrationRollbackReadinessAssessmentsResponses, ListBillingMigrationSourceManifestsData, ListBillingMigrationSourceManifestsErrors, ListBillingMigrationSourceManifestsResponses, ListBillingMigrationSourcePullsData, ListBillingMigrationSourcePullsErrors, ListBillingMigrationSourcePullsResponses, ListBillingMigrationStabilizationObservationsData, ListBillingMigrationStabilizationObservationsResponses, ListBillingMigrationWebhookRedeliveriesData, ListBillingMigrationWebhookRedeliveriesResponses, ListBillingQuarantineData, ListBillingQuarantineErrors, ListBillingQuarantineResponses, ListBillingRestoreJobsData, ListBillingRestoreJobsErrors, ListBillingRestoreJobsResponses, ListBillingSubscriptionTimelineData, ListBillingSubscriptionTimelineErrors, ListBillingSubscriptionTimelineResponses, ListConfigurationReleasesData, ListConfigurationReleasesErrors, ListConfigurationReleasesResponses, ListCustomerAccessTokensData, ListCustomerAccessTokensErrors, ListCustomerAccessTokensResponses, ListCustomerSubscriptionsData, ListCustomerSubscriptionsErrors, ListCustomerSubscriptionsResponses, ListEntitlementsData, ListEntitlementsErrors, ListEntitlementsResponses, ListEnvironmentsData, ListEnvironmentsErrors, ListEnvironmentsResponses, ListExperimentGroupsData, ListExperimentGroupsResponses, ListExperimentHistoryData, ListExperimentHistoryResponses, ListExperimentMetricDefinitionsData, ListExperimentMetricDefinitionsResponses, ListExperimentMutualExclusionGroupVersionsData, ListExperimentMutualExclusionGroupVersionsErrors, ListExperimentMutualExclusionGroupVersionsResponses, ListExperimentQaOverridesData, ListExperimentQaOverridesResponses, ListExperimentsData, ListExperimentsResponses, ListExperimentVersionsData, ListExperimentVersionsResponses, ListMembersData, ListMembersErrors, ListMembersResponses, ListOperatorBillingIdentityConflictsData, ListOperatorBillingIdentityConflictsErrors, ListOperatorBillingIdentityConflictsResponses, ListOrganizationsData, ListOrganizationsErrors, ListOrganizationsResponses, ListPaywallsData, ListPaywallsErrors, ListPaywallsResponses, ListPaywallVersionsData, ListPaywallVersionsErrors, ListPaywallVersionsResponses, ListPlacementAliasesData, ListPlacementAliasesResponses, ListPlacementAttributesData, ListPlacementAttributesErrors, ListPlacementAttributesResponses, ListPlacementQaOverridesData, ListPlacementQaOverridesResponses, ListPlacementRuleSetVersionsData, ListPlacementRuleSetVersionsResponses, ListPlacementsData, ListPlacementsErrors, ListPlacementsResponses, ListPlanProductsData, ListPlanProductsErrors, ListPlanProductsResponses, ListPlansData, ListPlansErrors, ListPlansResponses, ListProductEntitlementGrantVersionsData, ListProductEntitlementGrantVersionsErrors, ListProductEntitlementGrantVersionsResponses, ListProductEntitlementsData, ListProductEntitlementsErrors, ListProductEntitlementsResponses, ListProductsData, ListProductsErrors, ListProductsResponses, ListProjectsData, ListProjectsErrors, ListProjectsResponses, ListProviderConnectionDiagnosticsData, ListProviderConnectionDiagnosticsErrors, ListProviderConnectionDiagnosticsResponses, ListProviderConnectionsData, ListProviderConnectionsErrors, ListProviderConnectionsResponses, ListProviderMappingObservationsData, ListProviderMappingObservationsErrors, ListProviderMappingObservationsResponses, ListProviderMappingsData, ListProviderMappingsErrors, ListProviderMappingsResponses, ListProviderSyncRunsData, ListProviderSyncRunsErrors, ListProviderSyncRunsResponses, ListReconciliationRunsData, ListReconciliationRunsErrors, ListReconciliationRunsResponses, ListReplayJobsData, ListReplayJobsErrors, ListReplayJobsResponses, ListStoreServerCredentialsData, ListStoreServerCredentialsErrors, ListStoreServerCredentialsResponses, ListSubscriptionTimelineData, ListSubscriptionTimelineErrors, ListSubscriptionTimelineResponses, ListTransactionFactsData, ListTransactionFactsErrors, ListTransactionFactsResponses, ListValidationAttemptsData, ListValidationAttemptsErrors, ListValidationAttemptsResponses, ListWebhookDeliveriesData, ListWebhookDeliveriesErrors, ListWebhookDeliveriesResponses, ListWebhookDeliveryAttemptsData, ListWebhookDeliveryAttemptsErrors, ListWebhookDeliveryAttemptsResponses, ListWebhookDestinationsData, ListWebhookDestinationsErrors, ListWebhookDestinationsResponses, ListWebhookSigningSecretsData, ListWebhookSigningSecretsErrors, ListWebhookSigningSecretsResponses, LoginData, LoginErrors, LoginResponses, LogoutData, LogoutResponses, LookupBillingCustomerData, LookupBillingCustomerErrors, LookupBillingCustomerResponses, ObserveBillingMigrationStabilizationData, ObserveBillingMigrationStabilizationErrors, ObserveBillingMigrationStabilizationResponses, PreviewAnalyticsPrivacyRequestData, PreviewAnalyticsPrivacyRequestErrors, PreviewAnalyticsPrivacyRequestResponses, PreviewBillingMigrationRepairData, PreviewBillingMigrationRepairErrors, PreviewBillingMigrationRepairResponses, PreviewProductEntitlementGrantImpactData, PreviewProductEntitlementGrantImpactErrors, PreviewProductEntitlementGrantImpactResponses, PreviewProviderCatalogData, PreviewProviderCatalogErrors, PreviewProviderCatalogResponses, PromoteBillingMigrationReadyData, PromoteBillingMigrationReadyErrors, PromoteBillingMigrationReadyResponses, ProposeBillingMigrationCutoverData, ProposeBillingMigrationCutoverErrors, ProposeBillingMigrationCutoverResponses, ProposeBillingMigrationLegalHoldData, ProposeBillingMigrationLegalHoldErrors, ProposeBillingMigrationLegalHoldResponses, ProposeBillingMigrationRollbackData, ProposeBillingMigrationRollbackErrors, ProposeBillingMigrationRollbackResponses, PublishConfigurationData, PublishConfigurationErrors, PublishConfigurationResponses, PublishExperimentData, PublishExperimentErrors, PublishExperimentResponses, PublishPlacementRuleSetData, PublishPlacementRuleSetErrors, PublishPlacementRuleSetResponses, PublishProductEntitlementGrantVersionData, PublishProductEntitlementGrantVersionErrors, PublishProductEntitlementGrantVersionResponses, QueueBillingMigrationDryRunData, QueueBillingMigrationDryRunErrors, QueueBillingMigrationDryRunResponses, QueueBillingMigrationShadowRunData, QueueBillingMigrationShadowRunErrors, QueueBillingMigrationShadowRunResponses, QueueBillingMigrationSourcePullData, QueueBillingMigrationSourcePullErrors, QueueBillingMigrationSourcePullResponses, ReceiveAppleStoreNotificationData, ReceiveAppleStoreNotificationErrors, ReceiveAppleStoreNotificationResponses, ReconnectProviderConnectionData, ReconnectProviderConnectionErrors, ReconnectProviderConnectionResponses, RedeliverBillingMigrationWebhookData, RedeliverBillingMigrationWebhookErrors, RedeliverBillingMigrationWebhookResponses, RemoveBillingMigrationCredentialData, RemoveBillingMigrationCredentialErrors, RemoveBillingMigrationCredentialResponses, RemoveMemberData, RemoveMemberErrors, RemoveMemberResponses, RemovePlanProductData, RemovePlanProductErrors, RemovePlanProductResponses, RemoveProductEntitlementData, RemoveProductEntitlementErrors, RemoveProductEntitlementResponses, ReplaceProviderConnectionScopesData, ReplaceProviderConnectionScopesErrors, ReplaceProviderConnectionScopesResponses, ReplaceProviderMappingData, ReplaceProviderMappingErrors, ReplaceProviderMappingResponses, ReplayWebhookDeliveryData, ReplayWebhookDeliveryErrors, ReplayWebhookDeliveryResponses, RequestBillingCustomerSyncData, RequestBillingCustomerSyncErrors, RequestBillingCustomerSyncResponses, ResolveBillingIdentityConflictData, ResolveBillingIdentityConflictErrors, ResolveBillingIdentityConflictResponses, RestoreProductData, RestoreProductErrors, RestoreProductResponses, RestoreProjectData, RestoreProjectErrors, RestoreProjectResponses, RetireWebhookSigningSecretData, RetireWebhookSigningSecretErrors, RetireWebhookSigningSecretResponses, RetryQuarantinedInputData, RetryQuarantinedInputErrors, RetryQuarantinedInputResponses, RevokeApiKeyData, RevokeApiKeyErrors, RevokeApiKeyResponses, RevokeBillingCustomerAliasData, RevokeBillingCustomerAliasErrors, RevokeBillingCustomerAliasResponses, RevokeCustomerAccessTokenData, RevokeCustomerAccessTokenErrors, RevokeCustomerAccessTokenResponses, RevokeExperimentQaOverrideData, RevokeExperimentQaOverrideErrors, RevokeExperimentQaOverrideResponses, RevokePlacementQaOverrideData, RevokePlacementQaOverrideErrors, RevokePlacementQaOverrideResponses, RevokeProviderConnectionData, RevokeProviderConnectionErrors, RevokeProviderConnectionResponses, RevokeStoreServerCredentialData, RevokeStoreServerCredentialErrors, RevokeStoreServerCredentialResponses, RollbackConfigurationReleaseData, RollbackConfigurationReleaseErrors, RollbackConfigurationReleaseResponses, RotateApiKeyData, RotateApiKeyErrors, RotateApiKeyResponses, RotateProviderCredentialData, RotateProviderCredentialErrors, RotateProviderCredentialResponses, RotateStoreServerCredentialData, RotateStoreServerCredentialErrors, RotateStoreServerCredentialResponses, RotateWebhookSigningSecretData, RotateWebhookSigningSecretErrors, RotateWebhookSigningSecretResponses, SetActiveProviderAssignmentData, SetActiveProviderAssignmentErrors, SetActiveProviderAssignmentResponses, SetEnvironmentModeData, SetEnvironmentModeErrors, SetEnvironmentModeResponses, SetProductReplacementData, SetProductReplacementErrors, SetProductReplacementResponses, SetWebhookDestinationStatusData, SetWebhookDestinationStatusErrors, SetWebhookDestinationStatusResponses, SignUpData, SignUpErrors, SignUpResponses, SimulatePlacementDecisionData, SimulatePlacementDecisionErrors, SimulatePlacementDecisionResponses, SubmitSdkRestoreData, SubmitSdkRestoreErrors, SubmitSdkRestoreResponses, SubmitServerRestoreData, SubmitServerRestoreErrors, SubmitServerRestoreResponses, SubmitServerTransactionObservationData, SubmitServerTransactionObservationErrors, SubmitServerTransactionObservationResponses, SubmitTransactionObservationData, SubmitTransactionObservationErrors, SubmitTransactionObservationResponses, SyncCustomerEntitlementsData, SyncCustomerEntitlementsErrors, SyncCustomerEntitlementsResponses, SyncCustomerEntitlementsWithNegotiationData, SyncCustomerEntitlementsWithNegotiationErrors, SyncCustomerEntitlementsWithNegotiationResponses, TestProviderConnectionData, TestProviderConnectionErrors, TestProviderConnectionResponses, TestStoreServerCredentialData, TestStoreServerCredentialErrors, TestStoreServerCredentialResponses, TransitionBillingMigrationCaseData, TransitionBillingMigrationCaseErrors, TransitionBillingMigrationCaseResponses, TransitionExperimentLifecycleData, TransitionExperimentLifecycleErrors, TransitionExperimentLifecycleResponses, UpdateAnalyticsSettingsData, UpdateAnalyticsSettingsErrors, UpdateAnalyticsSettingsResponses, UpdateBillingSettingsData, UpdateBillingSettingsErrors, UpdateBillingSettingsResponses, UpdateEntitlementData, UpdateEntitlementErrors, UpdateEntitlementResponses, UpdateEnvironmentData, UpdateEnvironmentErrors, UpdateEnvironmentResponses, UpdateExperimentDraftData, UpdateExperimentDraftErrors, UpdateExperimentDraftResponses, UpdateMemberData, UpdateMemberErrors, UpdateMemberResponses, UpdateOrganizationData, UpdateOrganizationErrors, UpdateOrganizationResponses, UpdatePaywallData, UpdatePaywallDraftData, UpdatePaywallDraftErrors, UpdatePaywallDraftResponses, UpdatePaywallErrors, UpdatePaywallResponses, UpdatePlacementData, UpdatePlacementErrors, UpdatePlacementResponses, UpdatePlacementRuleSetDraftData, UpdatePlacementRuleSetDraftErrors, UpdatePlacementRuleSetDraftResponses, UpdatePlanData, UpdatePlanErrors, UpdatePlanResponses, UpdateProductData, UpdateProductEntitlementGrantVersionData, UpdateProductEntitlementGrantVersionErrors, UpdateProductErrors, UpdateProductResponses, UpdateProjectData, UpdateProjectErrors, UpdateProjectResponses, UpdateWebhookDestinationData, UpdateWebhookDestinationErrors, UpdateWebhookDestinationResponses, UploadAssetData, UploadAssetErrors, UploadAssetResponses, ValidateExperimentDraftData, ValidateExperimentDraftResponses, ValidatePaywallDraftData, ValidatePaywallDraftErrors, ValidatePaywallDraftResponses, ValidatePlacementRuleSetData, ValidatePlacementRuleSetResponses } from './types.gen'; export type Options = Options2 & { /** @@ -2107,9 +2107,11 @@ export const syncCustomerEntitlements = (o }); /** - * The conditional and negotiated form of the sync. The body is the contract's - * entitlementSyncRequest: supported contract versions, the snapshot version the caller - * already holds, its entity tag, and optionally the Entitlement keys to narrow to. + * Strictly discriminated Authoritative Entitlement v1 or v2 sync. V1 preserves the + * existing legacy shape byte-for-byte while source authority remains active. V2 derives + * Project, Environment and customer exclusively from SDK-key/CAT authentication, verifies + * the request Application/platform copies, and selects authority and snapshot only through + * the exact scoped authority and current-pointer rows. * * billingCustomerId in the body is a hint only. The server derives the customer from the * token and verifies the hint against it; a mismatch is refused rather than ignored, @@ -2119,10 +2121,10 @@ export const syncCustomerEntitlements = (o * Narrowing removes both entries and the sources no remaining entry references, so a * narrowed snapshot never carries an orphan source. * - * This is the ratified cross-SDK flow, and knownSnapshotVersion is the only conditional - * mechanism this surface has. A matching knownSnapshotVersion always answers 200 with the - * snapshotUnchanged record, never a bare 304, because the contract cannot guarantee - * freshness that lives only in undocumented headers. + * V2 knownSnapshotAuthorityDigest is verification input only. Only an exact digest plus + * unchanged authenticated scope, authority epoch and snapshot version yields + * snapshotUnchanged; absence or mismatch returns a full snapshot. This form never answers + * a bare 304. * */ export const syncCustomerEntitlementsWithNegotiation = (options: Options): RequestResult => (options.client ?? client).post({ @@ -3513,3 +3515,958 @@ export const closeQuarantineRecordSuperseded = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs', + ...options +}); + +/** + * Creates an immutable explicit migration scope and separately encrypted RevenueCat + * migration credential. Mosaic performs a bounded read-only capability assessment before + * opening the database transaction. Source records remain migration evidence and never + * become Billing Transaction Facts through this operation. Owner only. Audited. + * + */ +export const createBillingMigrationProgram = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Returns one strict Billing Migration Operations v1 migrationProgram record. + */ +export const getBillingMigrationProgram = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}', + ...options +}); + +/** + * Lists immutable manifests created by the internal verified-object ingestion boundary. There is intentionally no operator manifest-create endpoint. + */ +export const listBillingMigrationSourceManifests = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/manifests', + ...options +}); + +export const listBillingMigrationMappingSets = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/mapping-sets', + ...options +}); + +/** + * Creates a new draft mapping version. Owner or admin only. + */ +export const createBillingMigrationMappingSet = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/mapping-sets', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Performs the one-way draft-to-frozen transition. Owner or admin only. + */ +export const freezeBillingMigrationMappingSet = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/mapping-sets/{mappingSetId}/freeze', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationImportBatches = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/import-batches', + ...options +}); + +/** + * Queues an idempotent import batch of at most 1,000 evidence records. It cannot create Billing Transaction Facts. + */ +export const createBillingMigrationImportBatch = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/import-batches', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const getBillingMigrationImportBatch = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/import-batches/{batchId}', + ...options +}); + +/** + * Queues a durable dry-run job against verified immutable manifest and frozen mapping digests. + */ +export const queueBillingMigrationDryRun = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/dry-runs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Queues a durable shadow comparison job; this does not change billing authority. + */ +export const queueBillingMigrationShadowRun = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/shadow-runs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const getBillingMigrationRunJob = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/runs/{runJobId}', + ...options +}); + +export const listBillingMigrationDivergences = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/divergences', + ...options +}); + +/** + * Computes and persists readiness solely from stored evidence. Owner only; callers cannot submit metrics or gate values. + */ +export const assessBillingMigrationReadiness = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/readiness-assessments', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const getLatestBillingMigrationReadiness = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/readiness-assessments/latest', + ...options +}); + +export const listBillingMigrationSourcePulls = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/source-pulls', + ...options +}); + +/** + * Queues a bounded, read-only RevenueCat source pull. Available only when migration source access is configured; source records remain evidence and never become Billing Transaction Facts directly. + */ +export const queueBillingMigrationSourcePull = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/source-pulls', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const getBillingMigrationSourcePull = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/source-pulls/{sourcePullId}', + ...options +}); + +/** + * Promotes a program only from authoritative readiness evidence stored by Mosaic. + */ +export const promoteBillingMigrationReady = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/promote-ready', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Creates an expiring cutover proposal bound to explicit state and evidence digests. It does not change authority. + */ +export const proposeBillingMigrationCutover = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cutover-proposals', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Creates an expiring rollback proposal bound to checkpoint, authority, prerequisite, and state versions. It does not change authority. + */ +export const proposeBillingMigrationRollback = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-proposals', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Records a separate authorized approval for a cutover or rollback proposal; client role labels do not grant authority. + */ +export const approveBillingMigrationProposal = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/proposals/{proposalId}/approvals', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationCheckpoints = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints', + ...options +}); + +/** + * Creates an immutable authority checkpoint bound to the approved digest set and cohort. + */ +export const createBillingMigrationCheckpoint = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Executes an approved compare-and-swap authority transition using explicit scope, state, epoch, checkpoint, approval, and evidence digests. + */ +export const executeBillingMigrationCutover = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cutover-executions', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Executes an approved compare-and-swap rollback using explicit checkpoint, authority, prerequisite, approval, state, epoch, and scope values. + */ +export const executeBillingMigrationRollback = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-executions', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationCases = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases', + ...options +}); + +/** + * Creates an auditable operator case linked only by opaque evidence references. + */ +export const createBillingMigrationCase = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Performs an explicit case state transition bound to the current case digest and program state version. + */ +export const transitionBillingMigrationCase = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases/{caseId}/transitions', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationRepairPreviews = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-previews', + ...options +}); + +/** + * Creates a bounded repair preview only when the Phase 9C execution plane and concrete repair executor are enabled. + */ +export const previewBillingMigrationRepair = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-previews', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationRepairExecutions = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-executions', + ...options +}); + +/** + * Executes an unexpired repair preview only when the Phase 9C execution plane and concrete repair executor are enabled. + */ +export const executeBillingMigrationRepair = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-executions', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationWebhookRedeliveries = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/webhook-redeliveries', + ...options +}); + +/** + * Queues a bounded webhook redelivery from stored event data; raw payloads and destinations cannot be supplied by the caller. + */ +export const redeliverBillingMigrationWebhook = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/webhook-redeliveries', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationCredentialRemovals = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals', + ...options +}); + +/** + * Irreversibly removes the separately encrypted migration credential after an explicit acknowledgement. + */ +export const removeBillingMigrationCredential = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationLegalHoldProposals = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals', + ...options +}); + +/** + * Proposes setting or releasing a legal hold; a separate authorized approval is required. + */ +export const proposeBillingMigrationLegalHold = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Applies an approved legal-hold command bound to its immutable proposal digest. + */ +export const approveBillingMigrationLegalHold = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals/{proposalId}/approvals', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Inspects server-derived completion prerequisites without changing migration state. + */ +export const inspectBillingMigrationCompletion = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion', + ...options +}); + +/** + * Completes migration only when server-derived prerequisites match the supplied state, policy, authority, and stability evidence digests. + */ +export const completeBillingMigration = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const listBillingMigrationProposals = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/proposals', + ...options +}); + +export const getBillingMigrationProposal = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/proposals/{proposalId}', + ...options +}); + +export const listBillingMigrationApprovals = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/approvals', + ...options +}); + +export const getBillingMigrationApproval = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/approvals/{approvalId}', + ...options +}); + +export const getLatestBillingMigrationCheckpoint = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints/latest', + ...options +}); + +export const getBillingMigrationCheckpoint = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints/{checkpointId}', + ...options +}); + +export const listBillingMigrationAuthorityExecutions = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/authority-executions', + ...options +}); + +export const getBillingMigrationAuthorityExecution = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/authority-executions/{executionId}', + ...options +}); + +export const getBillingMigrationCase = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases/{caseId}', + ...options +}); + +export const listBillingMigrationCaseActions = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases/{caseId}/actions', + ...options +}); + +export const getBillingMigrationRepairPreview = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-previews/{previewId}', + ...options +}); + +export const getBillingMigrationRepairExecution = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-executions/{executionId}', + ...options +}); + +export const getBillingMigrationWebhookRedelivery = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/webhook-redeliveries/{redeliveryId}', + ...options +}); + +export const getCurrentBillingMigrationCredentialRemoval = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals/current', + ...options +}); + +export const getBillingMigrationCredentialRemoval = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals/{removalId}', + ...options +}); + +export const getBillingMigrationLegalHoldProposal = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals/{proposalId}', + ...options +}); + +export const listBillingMigrationLegalHolds = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-holds', + ...options +}); + +export const getCurrentBillingMigrationLegalHold = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-holds/current', + ...options +}); + +export const getBillingMigrationLegalHold = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-holds/{holdId}', + ...options +}); + +export const listBillingMigrationCompletionHistory = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion-history', + ...options +}); + +export const getBillingMigrationCompletionReport = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion-history/{reportId}', + ...options +}); + +/** + * Freezes the operator-selected thresholds once. Runtime metrics are never accepted from this public endpoint. + */ +export const freezeBillingMigrationStabilizationPolicy = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-policy', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const getCurrentBillingMigrationStabilizationPolicy = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-policy/current', + ...options +}); + +export const listBillingMigrationStabilizationObservations = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-observations', + ...options +}); + +/** + * Records a server-derived observation using stored trusted evidence. Callers provide only expected state, epoch, and policy digest—not metrics or health booleans. + */ +export const observeBillingMigrationStabilization = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-observations', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const getLatestBillingMigrationStabilizationObservation = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-observations/latest', + ...options +}); + +export const listBillingMigrationRollbackReadinessAssessments = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-assessments', + ...options +}); + +/** + * Computes readiness from stored evidence. Callers provide only the observation ID and expected state, epoch, and observation digest. + */ +export const assessBillingMigrationRollbackReadiness = (options: Options): RequestResult => (options.client ?? client).post({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-assessments', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const getLatestBillingMigrationRollbackReadinessAssessment = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-assessments/latest', + ...options +}); + +export const getLatestBillingMigrationRollbackReadinessCheckpoint = (options: Options): RequestResult => (options.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-checkpoints/latest', + ...options +}); + +/** + * Every Organization the caller belongs to, with the caller's role and that + * Organization's active Projects, in one read. Workspace entry uses this to + * resolve its destination without a request per Organization: no + * Organizations means create one, an Organization without Projects means + * create a Project, otherwise land in a Project. Projects are capped per + * Organization; projectsTruncated reports when the list is partial and + * projectCount carries the true total. + * + */ +export const getWorkspaceBootstrap = (options?: Options): RequestResult => (options?.client ?? client).get({ + security: [{ + in: 'cookie', + name: 'mosaic_session', + type: 'apiKey' + }], + url: '/v1/workspace/bootstrap', + ...options +}); diff --git a/apps/dashboard/src/generated/api/types.gen.ts b/apps/dashboard/src/generated/api/types.gen.ts index c06eb503..af2c1143 100644 --- a/apps/dashboard/src/generated/api/types.gen.ts +++ b/apps/dashboard/src/generated/api/types.gen.ts @@ -4,3871 +4,6961 @@ export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; -/** - * The provider reference a validation is performed against. Raw receipts, signed payloads, - * JWS representations, purchase tokens, and service-account material are structurally - * impossible to carry here: an Apple value is at most 24 decimal digits and a Google value - * is exactly 64 lowercase hexadecimal characters. - * - */ -export type TransactionReference = { - referenceKind: 'app_store_transaction_id' | 'google_play_token_digest'; - value: string; +export type BillingMigrationScopeItem = { + applicationId: string; + platform: 'ios' | 'android'; }; -/** - * Optional Google Play order reference. A join handle only; never the identity of a Transaction Fact, because promotional purchases have none. - */ -export type ProviderOrderReference = { - referenceKind: 'google_play_order_id'; - value: string; +export type BillingMigrationScope = { + projectId: string; + environmentId: string; + applications: Array; }; -/** - * SDK context. It carries no Organization, Project, Environment, or Application identity: tenant scope is derived from the authenticated key. - */ -export type ObservationContext = { - platform: 'ios' | 'android'; - sdkFamily: 'flutter' | 'ios' | 'android'; - sdkVersion: string; - operatingSystemVersion?: string; - applicationVersion?: string; +export type CreateBillingMigrationProgramRequest = { + environmentId: string; + applications: Array; + /** + * Preserved opaque RevenueCat project identifier. + */ + revenueCatProjectId: string; + stabilizationDays?: number; + rollbackWindowDays?: number; }; -/** - * Correlation to Analytics Event 1/2 through the existing opaque handles only. - */ -export type ObservationCorrelation = { - purchaseAttemptId?: string; - providerOperationId?: string; - providerUpdateId?: string; +export type BillingMigrationProgram = { + programId: string; + stateVersion: number; + state: 'draft' | 'assessing' | 'mapping' | 'importing' | 'dry_run' | 'shadowing' | 'ready' | 'cutover_pending' | 'stabilizing' | 'completed' | 'rolled_back' | 'failed' | 'cancelled'; + scope: BillingMigrationScope; + source: { + adapter: 'revenuecat'; + adapterVersion: string; + /** + * Opaque encrypted credential row reference + */ + credentialReference: string; + }; + authorityEpochBefore: number; + authorityEpochAfter?: number; + stabilizationDays: number; + rollbackWindowDays: number; }; -/** - * Server-derived Store Environment classification. A client never asserts it: no observation submitted by a client carries this property at all. - */ -export type StoreEnvironmentClassification = { - classification: 'sandbox' | 'production' | 'unclassified'; - basis: 'provider_asserted' | 'provider_endpoint' | 'signature_environment' | 'mosaic_environment_policy' | 'unknown'; +export type BillingMigrationProgramRecord = { + billingMigrationOperationsContractVersion: '1'; + recordType: 'migrationProgram'; + payload: BillingMigrationProgramDetail; }; -/** - * The Billing Ingestion Contract v1 clientTransactionObservation record, accepted verbatim - * so one platform-neutral document travels from four SDKs to one server. - * - */ -export type ClientTransactionObservationRecord = { - billingIngestionContractVersion: '1'; - recordType: 'clientTransactionObservation'; - payload: ClientTransactionObservation; +export type BillingMigrationProgramDetail = { + program: BillingMigrationProgram; + sourceCapabilityAssessment?: { + programId: string; + stateVersion: number; + adapter: string; + providerApiVersion: string; + capabilities: Array; + assessedAt: Timestamp; + }; + operatorCapabilities: Array<'view' | 'manage-source' | 'manage-mappings' | 'run-import' | 'assess-readiness' | 'propose-cutover' | 'approve-cutover' | 'execute-cutover' | 'execute-rollback' | 'resolve-cases' | 'execute-repair' | 'delete-source' | 'remove-credential' | 'manage-legal-hold' | 'complete-migration'>; }; -/** - * An untrusted claim that a transaction may exist. It carries no receipt, no signed - * payload, no purchase token, no credential, no price, no entitlement assertion, no tenant - * identity, and no Store Environment assertion. - * - * There is deliberately no storeEnvironmentClassification member, and additionalProperties - * is false, so a client-asserted Store Environment is rejected with `unknown_field` rather - * than ignored. Classification comes only from server-side validation. - * - * sourceAuthority must be `client_observation`: a public SDK key proves only that a client - * sent the document, and any higher authority claimed here is refused with - * `authority_not_allowed`. - * - */ -export type ClientTransactionObservation = { - observationId: string; - /** - * Deterministic idempotency key computed by the SDK. Never derived from a timestamp, price, Product, or subject. - */ - submissionId: string; - providerId: string; - storePlatform: 'apple_app_store' | 'google_play'; - transactionReference: TransactionReference; - providerOrderReference?: ProviderOrderReference; - /** - * UTC timestamp, RFC 3339 with a literal Z. - */ - observedAt: string; - sourceAuthority: 'client_observation'; - context: ObservationContext; - correlation?: ObservationCorrelation; - /** - * A claim only. The server resolves the Mosaic Product independently; a mismatch is a diagnostic and never an override. - */ - claimedMosaicProductId?: string; +export type BillingMigrationProgramEnvelope = { + data: BillingMigrationProgramRecord; }; -export type ServerTransactionObservationRecord = { - billingIngestionContractVersion: '1'; - recordType: 'serverTransactionObservation'; - payload: ServerTransactionObservation; +export type BillingMigrationProgramListEnvelope = { + data: { + items: Array; + }; }; -/** - * A trusted app-backend observation. It records how trust was established, never the - * credential that established it. Like the client record it carries no purchase token: the - * reference is the same digest a client would send. - * - * On this endpoint sourceAuthority must be `trusted_server_observation`. A Mosaic secret - * server key proves a trusted backend sent the document; it proves nothing about a provider - * having signed anything, so `provider_notification`, `reconciliation_discovery`, and - * `manual_revalidation` — authorities only Mosaic's own pipeline may author — are refused - * with `authority_not_allowed`. - * - */ -export type ServerTransactionObservation = { - observationId: string; - submissionId: string; - providerId: string; - storePlatform: 'apple_app_store' | 'google_play'; - transactionReference: TransactionReference; - providerOrderReference?: ProviderOrderReference; - sourceAuthority: 'trusted_server_observation'; - trustBasis: 'provider_signature_verified' | 'mutual_tls' | 'provider_server_api' | 'operator_initiated'; - /** - * The full Google Play purchase token, permitted only here, only on a `google_play` - * record, and only under `trusted_server_observation` authority. The client record has - * no such member and rejects one as `unknown_field`. - * - * It is a transaction reference the buyer's own purchase produced, not a Mosaic provider - * credential; service-account keys, signing keys, and Authorization values remain - * forbidden everywhere. It is encrypted at rest on receipt, never logged, never returned - * on any read, and never relieves the record of full provider validation. - * - * When present it MUST SHA-256-digest to this record's own `transactionReference.value`. - * Without that binding a caller could file a real token under a different transaction's - * reference, and Mosaic would validate the token, get a genuine answer from Google, and - * record it as a fact about the transaction the reference named. A mismatch is rejected - * with `provider_reference_malformed`. - * - * It exists because a digest cannot be reversed: without a token a Google observation - * has nothing to validate against and can only wait for the notification. - * - */ - purchaseToken?: string; - receivedAt: string; - providerReportedAt?: string; - /** - * Opaque provider notification identifier. Never the notification body. - */ - providerNotificationReference?: string; - storeEnvironmentClassification?: StoreEnvironmentClassification; - correlation?: ObservationCorrelation; - /** - * Referencing a client observation never raises that observation's authority. - */ - originatingObservationId?: string; +export type BillingMigrationStateVersionRequest = { + expectedStateVersion: number; }; -/** - * The Billing Ingestion Contract v1 observationSubmissionResult record, returned verbatim - * by both observation endpoints so every SDK decodes one platform-neutral shape. Validated - * against protocol/schema/billing-ingestion/v1/submission-response.schema.json. - * - */ -export type ObservationSubmissionResultRecord = { - billingIngestionContractVersion: '1'; - recordType: 'observationSubmissionResult'; - payload: ObservationSubmissionResult; +export type BillingMigrationOperationEnvelope = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: string; + payload: { + [key: string]: unknown; + }; + }; }; -/** - * The status set contains no member named validated, verified, confirmed, or entitled. - * Acceptance means the observation is well formed and queued and asserts nothing about the - * transaction being real. A reader must never grant access, unlock content, or emit a - * provider-confirmed Analytics Event on accepted_for_validation. - * - */ -export type ObservationSubmissionResult = { - submissionId: string; - /** - * UTC timestamp, RFC 3339 with a literal Z and at most microsecond precision. - */ - receivedAt: string; - status: 'accepted_for_validation' | 'duplicate' | 'permanently_rejected' | 'retryable_failure'; - /** - * Required for permanently_rejected and retryable_failure, absent otherwise. - */ - code?: 'observation_schema_invalid' | 'unsupported_contract_version' | 'unsupported_record_type' | 'unknown_field' | 'invalid_identifier' | 'invalid_timestamp' | 'observed_at_too_far_future' | 'observation_expired' | 'observation_too_large' | 'provider_reference_malformed' | 'provider_reference_too_long' | 'reference_kind_not_supported_for_platform' | 'credential_shaped_value_rejected' | 'sensitive_value_rejected' | 'tenant_field_forbidden' | 'authority_not_allowed' | 'unknown_provider' | 'billing_not_enabled_for_environment' | 'observation_id_conflict' | 'rate_limited' | 'storage_temporarily_unavailable' | 'service_temporarily_unavailable' | 'ingestion_timeout' | 'validation_backlog_saturated'; - /** - * Advisory hint on retryable_failure only. - */ - retryAfterSeconds?: number; - /** - * Advisory hint on accepted_for_validation only. Says when validation is likely to run, never that it succeeded. - */ - estimatedValidationDelaySeconds?: number; +export type BillingMigrationCase = { + caseId: string; + programId: string; + classification: 'critical' | 'blocking' | 'warning' | 'informational'; + status: 'open' | 'in_progress' | 'resolved' | 'dismissed'; + reason: string; + stateVersion: number; + caseDigest: BillingMigrationDigest; + linkedDivergenceId?: string; + linkedSourceRecordId?: string; + openedAt: Timestamp; + updatedAt: Timestamp; + resolvedAt?: Timestamp; }; -export type StoreServerCredentialApplication = { - applicationId?: string; - platform?: 'ios' | 'android'; - /** - * Apple bundle id or Google package name. The verified payload must match one of these. - */ - providerApplicationIdentifier?: string; +export type BillingMigrationCaseAction = { + actionId: string; + caseId: string; + programId: string; + actorId: string; + action: string; + beforeDigest: BillingMigrationDigest; + afterDigest: BillingMigrationDigest; + createdAt: Timestamp; }; -/** - * Encrypted Apple or Google server credential. Secret material is never returned. - */ -export type StoreServerCredential = { - id?: string; - projectId?: string; - environmentId?: string; - provider?: 'app_store' | 'google_play'; - /** - * Store Environment - */ - storeEnvironment?: 'sandbox' | 'production'; - name?: string; - status?: 'active' | 'revoked'; - healthStatus?: 'untested' | 'healthy' | 'degraded' | 'unavailable' | 'revoked'; - appleIssuerId?: string; - appleKeyId?: string; - googleClientEmail?: string; - googlePubSubProjectId?: string; - googlePubSubSubscriptionId?: string; - applications?: Array; - lastErrorCode?: string; - lastTestedAt?: string; - createdAt?: string; - rotatedAt?: string; - revokedAt?: string; - updatedAt?: string; +export type BillingMigrationRepairPreview = { + previewId: string; + caseId: string; + programId: string; + repairKind: 'provider_revalidate' | 'projection_replay' | 'attach_proven_alias' | 'replace_mapping_set' | 'retry_quarantined_record'; + scopeKind: string; + reason: string; + scopeReferences: Array; + affectedCount: number; + stateVersion: number; + beforeDigest: BillingMigrationDigest; + afterDigest: BillingMigrationDigest; + previewDigest: BillingMigrationDigest; + caseDigest: BillingMigrationDigest; + policyDigest: BillingMigrationDigest; + scopeDigest: BillingMigrationDigest; + createdByActorId: string; + createdAt: Timestamp; + expiresAt: Timestamp; }; -export type StoreServerCredentialWithEndpoint = StoreServerCredential & { - /** - * Full Apple notification endpoint including the intake token. Returned only on - * create and rotate. The token is stored as SHA-256 only and cannot be recovered. - * - */ - notificationEndpointUrl?: string; +export type BillingMigrationRepairPreviewCommand = { + previewId: string; + caseId: string; + programId: string; + repairKind: 'provider_revalidate' | 'projection_replay' | 'attach_proven_alias' | 'replace_mapping_set' | 'retry_quarantined_record'; + scopeKind: string; + reason: string; + scopeReferences: Array; + affectedCount: number; + stateVersion: number; + beforeDigest: BillingMigrationDigest; + afterDigest: BillingMigrationDigest; + previewDigest: BillingMigrationDigest; + caseDigest: BillingMigrationDigest; + policyDigest: BillingMigrationDigest; + scopeDigest: BillingMigrationDigest; + createdAt: Timestamp; + expiresAt: Timestamp; }; -export type CreateStoreServerCredentialRequest = { - environmentId: string; - provider: 'app_store' | 'google_play'; - /** - * Must align with the Environment mode; sandbox and production never mix. - */ - storeEnvironment: 'sandbox' | 'production'; - name: string; - /** - * Write-only. Apple .p8 PEM or Google service-account JSON. Validated before persistence, never returned. - */ - secret: string; - appleIssuerId?: string; - appleKeyId?: string; - googleClientEmail?: string; - googlePubSubProjectId?: string; - googlePubSubSubscriptionId?: string; - applications: Array; +export type BillingMigrationRepairPreviewCommandEnvelope = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'repairPreview'; + payload: BillingMigrationRepairPreviewCommand; + }; }; -/** - * A normalized, provider-independent statement that a store confirmed something - * happened. Never a subscription, an entitlement, or an access grant. Carries no - * customer identity, price, or currency. - * - */ -export type TransactionFact = { - id?: string; - projectId?: string; - environmentId?: string; - applicationId?: string; - provider?: 'app_store' | 'google_play'; - storeEnvironment?: 'sandbox' | 'production'; - providerTransactionId?: string; - providerOriginalTransactionId?: string; - transactionType?: 'auto_renewable_subscription' | 'non_consumable'; - factKind?: 'initial_purchase' | 'renewal' | 'one_time_purchase' | 'plan_change' | 'offer_redeemed' | 'refund' | 'revocation' | 'expiration' | 'grace_period_start' | 'billing_retry_start' | 'cancellation_scheduled' | 'auto_renew_disabled' | 'auto_renew_enabled' | 'purchase_superseded' | 'paused' | 'resumed'; - occurredAt?: string; - /** - * Provider-stated validity only. Never interpreted as customer access. - */ - periodStartAt?: string; - /** - * Provider-stated validity only. Never interpreted as customer access. - */ - periodEndAt?: string; - revokedAt?: string; - refundedAt?: string; - renewalExpected?: boolean; - isTestTransaction?: boolean; - providerProductIdentifier?: string; - providerBasePlanIdentifier?: string; - providerOfferIdentifier?: string; - resolutionState?: 'active_mapping' | 'archived_mapping' | 'replacement_chain' | 'unresolved'; - mosaicProductId?: string; - providerProductMappingId?: string; - /** - * Resolution Snapshot version - */ - resolvedMappingVersion?: number; - validatorVersion?: number; - factVersion?: number; - sourceRawInputId?: string; - validationAttemptId?: string; - recordedAt?: string; +export type BillingMigrationRepairExecution = BillingMigrationRepairExecutionPending | BillingMigrationRepairExecutionTerminal; + +export type BillingMigrationRepairExecutionPending = { + executionId: string; + previewId: string; + programId: string; + executionStatus: 'pending'; + beforeDigest: BillingMigrationDigest; + attemptNumber: number; + executedByActorId: string; + reservedAt: Timestamp; }; -/** - * One append-only record of one validation try. No provider response body is ever stored. - */ -export type ValidationAttempt = { - id?: string; - projectId?: string; - environmentId?: string; - rawInputId?: string; - credentialId?: string; - attemptNumber?: number; - validatorVersion?: number; - startedAt?: string; - completedAt?: string; - outcome?: 'validated' | 'recorded_no_fact' | 'quarantined' | 'retryable_failure' | 'permanently_failed'; - retryable?: boolean; - failureCategory?: 'transient' | 'rate_limited' | 'auth' | 'quota' | 'not_found_retryable' | 'not_found_terminal' | 'invalid' | 'signature' | 'resolution' | 'configuration'; - /** - * Mosaic-owned stable code. - */ - diagnosticCode?: string; - /** - * Provider machine code - */ - providerCode?: string; - providerHttpStatus?: number; - storeEnvironment?: 'sandbox' | 'production' | 'unclassified'; - latencyMs?: number; - replayOfAttemptId?: string; - correlationId?: string; +export type BillingMigrationRepairExecutionTerminal = { + executionId: string; + previewId: string; + programId: string; + executionStatus: 'completed'; + result: 'succeeded' | 'failed' | 'no_change'; + errorCode?: string; + beforeDigest: BillingMigrationDigest; + afterDigest: BillingMigrationDigest; + resultDigest: BillingMigrationDigest; + attemptNumber: number; + executedByActorId: string; + reservedAt: Timestamp; + executedAt: Timestamp; +}; + +export type BillingMigrationRepairExecutionCommandPending = { + executionId: string; + previewId: string; + programId: string; + executionStatus: 'pending'; + beforeDigest: BillingMigrationDigest; + attemptNumber: number; + executedAt: Timestamp; +}; + +export type BillingMigrationRepairExecutionCommandTerminal = { + executionId: string; + previewId: string; + programId: string; + executionStatus: 'completed'; + result: 'succeeded' | 'failed' | 'no_change'; + errorCode?: string; + beforeDigest: BillingMigrationDigest; + afterDigest: BillingMigrationDigest; + resultDigest: BillingMigrationDigest; + attemptNumber: number; + executedAt: Timestamp; }; -export type BillingLedgerEntry = { - id?: string; - projectId?: string; - environmentId?: string; - entryType?: 'input_received' | 'input_authenticated' | 'input_duplicate_detected' | 'validation_started' | 'validation_succeeded' | 'validation_failed' | 'product_resolved' | 'product_resolution_failed' | 'fact_recorded' | 'fact_deduplicated' | 'input_quarantined' | 'quarantine_closed' | 'reconciliation_started' | 'reconciliation_discovery' | 'reconciliation_completed' | 'replay_started' | 'replay_completed' | 'revalidation_completed' | 'credential_health_changed'; - rawInputId?: string; - validationAttemptId?: string; - transactionFactId?: string; - credentialId?: string; - correlationId?: string; - occurredAt?: string; +export type BillingMigrationRepairExecutionCommandPendingEnvelope = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'repairExecution'; + payload: BillingMigrationRepairExecutionCommandPending; + }; }; -/** - * An input that cannot safely proceed. The only status that follows a successful - * revalidation is closed_after_success, and it always names the attempt that justified - * it. There is no status, field, or action meaning an operator declared the input valid. - * - */ -export type QuarantineRecord = { - id?: string; - projectId?: string; - environmentId?: string; - rawInputId?: string; - applicationId?: string; - provider?: 'app_store' | 'google_play'; - /** - * Store Environment of the quarantined input, always distinct from the Mosaic - * Environment. Never absent: an input whose environment was not classified before it - * quarantined reports `unclassified` explicitly, because a missing value on an operator - * surface reads as production to a careless eye. - * - */ - storeEnvironment?: 'sandbox' | 'production' | 'unclassified'; - /** - * The store Product the quarantined input named, carried from the input's most recent - * resolution attempt. For the common `product_unknown` case it is the single most - * actionable field on the record: it is exactly what the operator has to create a mapping - * for. - * - */ - providerProductIdentifier?: string; - reasonCode?: 'signature_invalid' | 'application_mismatch' | 'environment_mismatch' | 'store_environment_mismatch' | 'credential_unavailable' | 'credential_revoked' | 'missing_validation_credential' | 'product_unknown' | 'product_ambiguous' | 'cross_environment_mismatch' | 'unsupported_product_type' | 'unsupported_transaction_type' | 'malformed_reference' | 'input_content_conflict' | 'replay_conflict' | 'provider_permanently_failed' | 'validation_exhausted'; - severity?: 'warning' | 'error' | 'security'; - scopes?: Array; - status?: 'open' | 'retrying' | 'closed_after_success' | 'closed_superseded'; - attemptCount?: number; - firstSeenAt?: string; - lastAttemptAt?: string; - /** - * The successful attempt that justified closure. - */ - closingAttemptId?: string; - supersededByRecordId?: string; - closedAt?: string; - diagnosticCode?: string; +export type BillingMigrationRepairExecutionCommandTerminalEnvelope = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'repairExecution'; + payload: BillingMigrationRepairExecutionCommandTerminal; + }; }; -export type ReconciliationRun = { - id?: string; - projectId?: string; - environmentId?: string; - credentialId?: string; - provider?: 'app_store' | 'google_play'; - trigger?: 'scheduled' | 'manual'; - strategy?: 'apple_notification_history' | 'apple_transaction_history' | 'google_token_requery'; - status?: 'queued' | 'leased' | 'completed' | 'partial' | 'failed'; - windowStart?: string; - windowEnd?: string; - examinedCount?: number; - discoveredCount?: number; - duplicateCount?: number; - /** - * Discoveries that contradicted a fact already on record, as distinct from discoveries - * that were merely new. Gate 9A requires reconciliation to detect missing *or* - * conflicting state; without a separate counter the two are indistinguishable. Nothing is - * overwritten — both facts stand — and each conflict also opens a quarantine record. - * - */ - conflictCount?: number; - failureCount?: number; - lastErrorCode?: string; - createdAt?: string; - startedAt?: string; - completedAt?: string; +export type BillingMigrationWebhookRedelivery = { + redeliveryId: string; + programId: string; + eventId: string; + destinationId: string; + deliveryId: string; + reason: string; + actorId: string; + stateVersion: number; + expectedEventDigest: BillingMigrationDigest; + createdAt: Timestamp; }; -export type CreateReconciliationRunRequest = { +export type BillingMigrationCredentialRemoval = { + removalId: string; + programId: string; credentialId: string; - provider: 'app_store' | 'google_play'; - /** - * apple_transaction_history is deliberately absent: the worker has no run loop for it, so - * accepting it produced a 202 followed by a run that failed with `unsupported_strategy` - * and no explanation anywhere in the product. It remains in the stored enumeration for - * forward compatibility and is rejected at the API boundary until the loop exists. - * - */ - strategy: 'apple_notification_history' | 'google_token_requery'; - windowStart: string; - /** - * The window may not exceed 180 days - */ - windowEnd: string; + reason: string; + actorId: string; + removalDigest: BillingMigrationDigest; + stateVersion: number; + early: boolean; + removedAt: Timestamp; }; -export type ReplayJob = { - id?: string; - projectId?: string; - environmentId?: string; - kind?: 'replay' | 'revalidation'; - rawInputId?: string; - windowStart?: string; - windowEnd?: string; - validatorVersion?: number; - status?: 'queued' | 'leased' | 'completed' | 'failed'; - comparisonResult?: 'identical' | 'new_facts' | 'conflicting' | 'still_failing'; - examinedCount?: number; - unchangedCount?: number; - newFactCount?: number; - conflictCount?: number; - lastErrorCode?: string; - createdAt?: string; - completedAt?: string; +export type BillingMigrationLegalHoldProposal = { + proposalId: string; + programId: string; + command: 'set' | 'release'; + reason: string; + externalComplianceReference: string; + proposerActorId: string; + expectedPreviousCommandDigest?: BillingMigrationDigest; + proposalDigest: BillingMigrationDigest; + status: 'pending' | 'approved' | 'expired' | 'invalidated'; + proposedAt: Timestamp; + expiresAt: Timestamp; }; -/** - * Supply either a single rawInputId or a bounded window, never both. - */ -export type CreateReplayJobRequest = { - kind: 'replay' | 'revalidation'; - rawInputId?: string; - windowStart?: string; - windowEnd?: string; - validatorVersion?: number; +export type BillingMigrationLegalHold = { + holdId: string; + proposalId: string; + programId: string; + command: 'set' | 'release'; + reason: string; + externalComplianceReference: string; + proposerActorId: string; + approverActorId: string; + previousCommandId?: string; + commandDigest: BillingMigrationDigest; + production: boolean; + commandedAt: Timestamp; +}; + +export type BillingMigrationCompletionReport = { + reportId: string; + programId: string; + stateVersion: number; + completionDigest: BillingMigrationDigest; + authorityDigest: BillingMigrationDigest; + stabilityEvidenceDigest: BillingMigrationDigest; + policyDigest: BillingMigrationDigest; + completedByActorId: string; + completedAt: Timestamp; + stabilizationEndedAt: Timestamp; + rollbackWindowEndedAt: Timestamp; + credentialRemovedAt: Timestamp; + legalHold: boolean; + sourceObjectsDeleteAt?: Timestamp; +}; + +export type BillingMigrationCompletionPrerequisites = { + programId: string; + state: string; + policyDigest: BillingMigrationDigest; + authorityDigest: BillingMigrationDigest; + stabilityEvidenceDigest: BillingMigrationDigest; + stateVersion: number; + stabilizationEndsAt: Timestamp; + rollbackWindowEndsAt: Timestamp; + credentialRemoved: boolean; + unresolvedCriticalBlocking: number; + authorityStable: boolean; + webhookReady: boolean; + eligible: boolean; +}; + +export type BillingMigrationStabilizationPolicy = { + policyId: string; + programId: string; + policyDigest: BillingMigrationDigest; + frozenByActorId: string; + stateVersion: number; + thresholds: BillingMigrationStabilizationThresholds; + frozenAt: Timestamp; +}; + +export type BillingMigrationStabilizationMetrics = { + authorityMismatches: number; + accessApiErrors: number; + sdkSyncFailures: number; + divergences: number; + validationBacklog: number; + sourceDeltaLagSeconds: number; + webhookFailures: number; + webhookAgeSeconds: number; + quarantinedRecords: number; + supportCases: number; + oldAppVersions: number; + unhealthyWorkers: number; +}; + +export type BillingMigrationStabilizationObservation = { + observationId: string; + programId: string; + policyId: string; + policyDigest: BillingMigrationDigest; + evidenceDigest: BillingMigrationDigest; + stateVersion: number; + authorityEpoch: number; + metrics: BillingMigrationStabilizationMetrics; + sourceWatermark: Timestamp; + webhookLastSuccessAt: Timestamp; + observedAt: Timestamp; + breachCodes: Array; + healthy: boolean; }; -export type CustomerAccessTokenIssuanceRequest = { - customerAccessTokenContractVersion: '1'; - recordType: 'customerAccessTokenIssuanceRequest'; - payload: { - billingCustomerId: string; - /** - * Only sdk_sync is issued in Phase 9B. - */ - audience: 'sdk_sync' | 'server_check'; - scopes: Array<'entitlements.read' | 'entitlements.sync' | 'restore.request'>; - /** - * A request, not an instruction. Clamped to the contract maximum. - */ - requestedTtlSeconds?: number; - correlationId: string; - }; +export type BillingMigrationRollbackReadinessAssessment = { + assessmentId: string; + programId: string; + observationId: string; + latestDeltaId: string; + readinessDigest: BillingMigrationDigest; + stateVersion: number; + sourceSupportAvailable: boolean; + sourceHealthy: boolean; + sourceHealthDigest: BillingMigrationDigest; + sourceCurrentAccessDigest: BillingMigrationDigest; + sourceCurrentAccessAt: Timestamp; + latestDeltaDigest: BillingMigrationDigest; + customerImpactCount: number; + customerImpactDigest: BillingMigrationDigest; + applicationCompatible: boolean; + applicationCompatibilityDigest: BillingMigrationDigest; + limitationsBlocking: boolean; + limitationReportDigest: BillingMigrationDigest; + auditDigest: BillingMigrationDigest; + stabilizationHealthy: boolean; + ready: boolean; + assessedByActorId: string; + assessedAt: Timestamp; +}; + +export type BillingMigrationRollbackReadinessCheckpoint = { + checkpointId: string; + programId: string; + assessmentId: string; + authorityDigest: BillingMigrationDigest; + policyDigest: BillingMigrationDigest; + evidenceDigest: BillingMigrationDigest; + readinessDigest: BillingMigrationDigest; + checkpointDigest: BillingMigrationDigest; + stateVersion: number; + authorityEpoch: number; + createdByActorId: string; + createdAt: Timestamp; }; -export type CustomerAccessTokenIssuanceResult = { - customerAccessTokenContractVersion: '1'; - recordType: 'customerAccessTokenIssuanceResult'; - payload: { - /** - * The opaque credential. Returned exactly once; Mosaic stores only its SHA-256 digest and can never reproduce it. - */ - token: string; - metadata: CustomerAccessTokenMetadata; - correlationId: string; +export type BillingMigrationSourcePull = { + sourcePullId: string; + programId: string; + intent: 'snapshot' | 'delta' | 'final_delta'; + status: string; + startingCursor?: string; + startingWatermark?: string; + predecessorPullJobId?: string; + resultSourceObjectId?: string; + resultManifestId?: string; + resultImportBatchId?: string; + resultFinalDeltaJobId?: string; + resultImportStatus?: string; + failureCode?: string; + stateVersion: number; + attemptCount: number; + maxAttempts: number; + createdAt: Timestamp; + updatedAt: Timestamp; + startedAt?: Timestamp; + completedAt?: Timestamp; + failedAt?: Timestamp; +}; + +export type BillingMigrationProposal = { + proposalId: string; + programId: string; + stateVersion: number; + command: 'cutover' | 'rollback'; + proposerActorId: string; + reason: string; + proposalDigest: BillingMigrationDigest; + status: string; + proposedAt: Timestamp; + expiresAt: Timestamp; +}; + +export type BillingMigrationApproval = { + approvalId: string; + programId: string; + stateVersion: number; + command: 'cutover' | 'rollback'; + proposerActorId: string; + approverActorId: string; + approvalDigest: BillingMigrationDigest; + approvedAt: Timestamp; + expiresAt: Timestamp; +}; + +export type BillingMigrationCheckpoint = { + checkpointId: string; + programId: string; + stateVersion: number; + scope: BillingMigrationScope; + authorityEpoch: number; + sourceWatermark: Timestamp; + providerWatermark: Timestamp; + shadowWatermark: Timestamp; + manifestDigest: BillingMigrationDigest; + mappingDigest: BillingMigrationDigest; + policyDigest: BillingMigrationDigest; + readinessDigest: BillingMigrationDigest; + checkpointDigest: BillingMigrationDigest; + cohortDigest: BillingMigrationDigest; + createdAt: Timestamp; +}; + +export type BillingMigrationAuthorityExecution = { + executionId: string; + programId: string; + command: 'cutover' | 'rollback'; + state: string; + stateVersion: number; + authorityEpoch: number; + transitionIds: Array; + executedAt: Timestamp; +}; + +export type BillingMigrationSourcePullPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -/** - * Everything Mosaic knows about a token, which is deliberately everything the token itself does not carry. - */ -export type CustomerAccessTokenMetadata = { - /** - * A stable public handle, safe to log and to name in an audit event. It is not the token and cannot be presented as one. - */ - tokenId: string; - projectId: string; - environmentId: string; - billingCustomerId: string; - audience: 'sdk_sync' | 'server_check'; - scopes: Array<'entitlements.read' | 'entitlements.sync' | 'restore.request'>; - issuer: string; - issuedAt: string; - expiresAt: string; - status: 'active' | 'expired' | 'revoked'; - revokedAt?: string; - revocationReason?: 'customer_signed_out' | 'identity_changed' | 'operator_revoked' | 'customer_deleted' | 'key_rotated' | 'suspected_compromise' | 'superseded_by_new_token'; - lastUsedAt?: string; - tokenPrefix: 'mcat_'; - digestAlgorithm: 'sha256'; +export type BillingMigrationProposalPageEnvelope = { + data: { + items: Array; + nextCursor?: string; + }; }; -export type EntitlementSyncRequestRecord = { - authoritativeEntitlementContractVersion: '1'; - recordType: 'entitlementSyncRequest'; - payload: { - /** - * A hint only. Verified against the token; a mismatch is refused. - */ - billingCustomerId?: string; - knownSnapshotVersion?: number; - entityTag?: string; - supportedAuthoritativeEntitlementContracts: Array<'1'>; - requestedEntitlementKeys?: Array; - correlationId: string; +export type BillingMigrationApprovalPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -/** - * The Authoritative Entitlement Contract v1 restoreRequest envelope. The normative shape is - * protocol/schema/authoritative-entitlement/v1/restore.schema.json; this declaration exists - * so the operation has a resolvable request schema and is deliberately not a second source - * of truth for the contract. - * - */ -export type RestoreRequestRecord = { - authoritativeEntitlementContractVersion: '1'; - recordType: 'restoreRequest'; - payload: { - storePlatform: 'apple_app_store' | 'google_play'; - /** - * What the native restore itself did. Recorded verbatim and never merged into Mosaic's own outcome. - */ - providerOutcome: 'completed' | 'no_purchases_found' | 'cancelled' | 'failed' | 'unsupported' | 'not_attempted'; - /** - * Observations already submitted for this restore. No provider transaction reference travels on this surface. - */ - observationSubmissionIds?: Array; - /** - * Honoured on the trusted surface only. The SDK surface drops it. - */ - billingCustomerId?: string; - correlationId: string; +export type BillingMigrationCheckpointPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -/** - * The Authoritative Entitlement Contract v1 restoreResult envelope. Normative shape: - * protocol/schema/authoritative-entitlement/v1/restore.schema.json. - * - */ -export type RestoreResultRecord = { - authoritativeEntitlementContractVersion: '1'; - recordType: 'restoreResult'; - payload: { - restoreId?: string; - /** - * Absent while identity is unresolved - */ - billingCustomerId?: string; - projectId?: string; - environmentId?: string; - storePlatform?: 'apple_app_store' | 'google_play'; - /** - * Mosaic's own answer. `restored` is only ever written together with the accepted snapshot version that demonstrates it. - */ - outcome?: 'restored' | 'no_additional_purchases' | 'validation_pending' | 'identity_unresolved' | 'product_unresolved' | 'provider_unavailable' | 'failed'; - providerOutcome?: 'completed' | 'no_purchases_found' | 'cancelled' | 'failed' | 'unsupported' | 'not_attempted'; - /** - * The accepted snapshot that reflects the restore. Present only for `restored` - */ - snapshotVersion?: number; - /** - * Inputs actually linked - */ - observedTransactionCount?: number; - pendingValidationCount?: number; - uncertainty?: { - reason?: 'none' | 'provider_unavailable' | 'missing_fact' | 'identity_unresolved' | 'product_unresolved' | 'conflicting_facts' | 'projection_failed' | 'stale_validation' | 'unsupported_provider_state'; - since?: string; - diagnosticCode?: string; - }; - requestedAt?: string; - completedAt?: string; - correlationId?: string; +export type BillingMigrationAuthorityExecutionPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -/** - * The Authoritative Entitlement Contract v1 customerEntitlementSnapshot envelope. The - * normative shape is protocol/schema/authoritative-entitlement/v1/snapshot.schema.json; - * this declaration exists so the operation has a resolvable response schema and is - * deliberately not a second source of truth for the contract. - * - */ -export type CustomerEntitlementSnapshotRecord = { - authoritativeEntitlementContractVersion: '1'; - recordType: 'customerEntitlementSnapshot' | 'snapshotUnchanged'; - payload: { - snapshotId?: string; - billingCustomerId?: string; - projectId?: string; - environmentId?: string; - /** - * Monotonic per (customer - */ - snapshotVersion?: number; - previousSnapshotVersion?: number; - projectionRuleVersion?: number; - issuedAt?: string; - asOf?: string; - refreshAfter?: string; - validUntil?: string; - staleGraceSeconds?: number; - entityTag?: string; - contentDigest?: string; - entries?: Array; - sources?: Array; - projectionStatus?: ProjectionStatus; - changeReason?: string; - correlationId?: string; +export type BillingMigrationCasePageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -/** - * Product and Subscription Instance identity are deliberately absent; they live on the contributing source summaries, which sourceIds resolves against. - */ -export type EntitlementEntry = { - entitlementId: string; - entitlementKey: string; - /** - * `unavailable` is deliberately absent: it describes Mosaic's ability to answer, never the customer's access. - */ - state: 'active' | 'inactive' | 'unknown'; - effectiveStart?: string; - effectiveEnd?: string; - /** - * False means an active source has an uncertain end - */ - endKnown: boolean; - sourceIds: Array; - sourceCount: number; - primaryExplanation: PrimaryExplanation; - uncertainty?: Uncertainty; +export type BillingMigrationCaseActionPageEnvelope = { + data: { + items: Array; + nextCursor?: string; + }; }; -export type EntitlementSourceSummary = { - /** - * Derived from (purchase lineage - */ - sourceId: string; - sourceType: 'active_subscription' | 'trial' | 'grace_period' | 'billing_retry' | 'one_time_non_consumable' | 'family_shared'; - subscriptionInstanceId?: string; - oneTimePurchaseInstanceId?: string; - mosaicProductId: string; - grantVersionId: string; - sourceSnapshotId: string; - storePlatform?: 'apple_app_store' | 'google_play'; - start: string; - /** - * Absent means this source has no finite end Mosaic can state. - */ - end?: string; - sourceState: 'granting' | 'not_granting' | 'unknown'; - uncertainty: Uncertainty; - explanationCode: string; - /** - * True for an Apple sandbox transaction or a Google Play license-tester purchase - */ - isTestSource: boolean; +export type BillingMigrationRepairPreviewPageEnvelope = { + data: { + items: Array; + nextCursor?: string; + }; }; -export type ProjectionStatus = { - state: 'current' | 'pending' | 'stale' | 'degraded' | 'failed'; - lastProjectedAt: string; - pendingFactCount?: number; - diagnosticCode?: string; +export type BillingMigrationRepairExecutionPageEnvelope = { + data: { + items: Array; + nextCursor?: string; + }; }; -/** - * Why a state is not definitive. reason `none` means the state is definitive, and a definitive state carries no since instant. - */ -export type Uncertainty = { - reason: 'none' | 'provider_unavailable' | 'missing_fact' | 'identity_unresolved' | 'product_unresolved' | 'conflicting_facts' | 'projection_failed' | 'stale_validation' | 'unsupported_provider_state'; - since?: string; - expectedResolution?: 'automatic_retry' | 'next_provider_notification' | 'next_projection_run' | 'operator_action' | 'customer_action' | 'none_expected'; - diagnosticCode?: string; +export type BillingMigrationWebhookRedeliveryPageEnvelope = { + data: { + items: Array; + nextCursor?: string; + }; }; -export type PrimaryExplanation = { - /** - * A member of the contract's closed explanation vocabulary. A reader may render its own copy for a code but must never invent one. - */ - code: string; - sourceId?: string; - safeSummary?: string; +export type BillingMigrationCredentialRemovalPageEnvelope = { + data: { + items: Array; + nextCursor?: string; + }; }; -export type EntitlementCheckRequestRecord = { - authoritativeEntitlementContractVersion: '1'; - recordType: 'entitlementCheckRequest'; - payload: { - billingCustomerId: string; - entitlementKeys: Array; - expectedSnapshotVersion?: number; - supportedAuthoritativeEntitlementContracts: Array<'1'>; - correlationId: string; +export type BillingMigrationLegalHoldProposalPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -export type EntitlementCheckResultRecord = { - authoritativeEntitlementContractVersion: '1'; - recordType: 'entitlementCheckResult'; - payload: { - billingCustomerId: string; - projectId: string; - environmentId: string; - /** - * Absent only when no snapshot could be read at all - */ - snapshotVersion?: number; - projectionRuleVersion?: number; - issuedAt: string; - asOf?: string; - results: Array<{ - entitlementKey: string; - /** - * This is the only place `unavailable` is admissible for an Entitlement: it says Mosaic could not answer, not that the customer lacks access. - */ - state: 'active' | 'inactive' | 'unknown' | 'unavailable'; - effectiveStart?: string; - effectiveEnd?: string; - endKnown: boolean; - sourceCount: number; - sourceIds?: Array; - primaryExplanation: PrimaryExplanation; - uncertainty?: Uncertainty; - isTestSource?: boolean; - }>; - projectionStatus?: ProjectionStatus; - correlationId: string; +export type BillingMigrationLegalHoldPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -/** - * The Authoritative Entitlement Contract v1 subscriptionSnapshot envelope. The normative - * shape is protocol/schema/authoritative-entitlement/v1/subscription.schema.json. - * - */ -export type SubscriptionSnapshotRecord = { - authoritativeEntitlementContractVersion: '1'; - recordType: 'subscriptionSnapshot'; - payload: { - subscriptionSnapshotId?: string; - subscriptionInstanceId?: string; - purchaseLineageId?: string; - billingCustomerId?: string; - projectId?: string; - environmentId?: string; - projectionVersion?: number; - projectionRuleVersion?: number; - computedAt?: string; - asOf?: string; - storePlatform?: 'apple_app_store' | 'google_play'; - mosaicProductId?: string; - priorMosaicProductId?: string; - accessState?: 'active' | 'inactive' | 'unknown' | 'unavailable'; - lifecycleState?: 'trialing' | 'active' | 'grace_period' | 'billing_retry' | 'paused' | 'expired' | 'revoked' | 'refunded' | 'superseded' | 'unknown'; - renewalIntent?: 'auto_renew_enabled' | 'auto_renew_disabled' | 'provider_managed' | 'paused' | 'unknown'; - billingState?: 'current' | 'retrying' | 'grace' | 'failed' | 'refunded' | 'revoked' | 'unknown'; - uncertainty?: Uncertainty; - periodStart?: string; - periodEnd?: string; - gracePeriodEnd?: string; - billingRetryStart?: string; - pauseEffectiveAt?: string; - pauseResumeAt?: string; - /** - * When the provider recorded the cancellation. It changes renewalIntent; it does not end access. - */ - cancellationEffectiveAt?: string; - expirationEffectiveAt?: string; - revocationEffectiveAt?: string; - refundEffectiveAt?: string; - supersededBySubscriptionInstanceId?: string; - isTestSource?: boolean; - sourceFactCount?: number; - checksum?: string; - changeReason?: string; - explanationCode?: string; - correlationId?: string; +export type BillingMigrationCompletionReportPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -export type SubscriptionSummary = { - subscriptionInstanceId?: string; - subscriptionSnapshotId?: string; - projectionVersion?: number; - accessState?: 'active' | 'inactive' | 'unknown'; - lifecycleState?: string; - renewalIntent?: string; - billingState?: string; - isTestSource?: boolean; - asOf?: string; +export type BillingMigrationStabilizationObservationPageEnvelope = { + data: { + items: Array; + nextCursor?: string; + }; }; -export type SubscriptionTimelineEntry = { - timelineEntryId?: string; - entryType?: string; - effectiveAt?: string; - observedAt?: string; - explanationCode?: string; - mosaicProductId?: string; - detail?: { - [key: string]: string; +export type BillingMigrationRollbackReadinessAssessmentPageEnvelope = { + data: { + items: Array; + nextCursor?: string; }; }; -/** - * A Billing Customer as the trusted-server API reports it. Alias values never appear: aliases are stored as digests. - */ -export type BillingCustomer = { - billingCustomerId?: string; - projectId?: string; - status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; - diagnosticsStatus?: 'none' | 'identity_conflict' | 'projection_stale' | 'projection_failed'; - currentProjectionVersion?: number; - lastProjectedAt?: string; - /** - * True when an application backend has named this customer. False means purchase-anchored but never identified. - */ - identified?: boolean; - createdAt?: string; - updatedAt?: string; +export type BillingMigrationSourcePullEnvelope = BillingMigrationSourcePullRecord; + +export type BillingMigrationProposalEnvelope = BillingMigrationProposalRecord; + +export type BillingMigrationApprovalEnvelope = BillingMigrationApprovalRecord; + +export type BillingMigrationCheckpointEnvelope = BillingMigrationCheckpointRecord; + +export type BillingMigrationAuthorityExecutionEnvelope = BillingMigrationAuthorityExecutionRecord; + +export type BillingMigrationCaseEnvelope = BillingMigrationCaseRecord; + +export type BillingMigrationRepairPreviewEnvelope = BillingMigrationRepairPreviewRecord; + +export type BillingMigrationRepairExecutionEnvelope = BillingMigrationRepairExecutionRecord; + +export type BillingMigrationWebhookRedeliveryEnvelope = BillingMigrationWebhookRedeliveryRecord; + +export type BillingMigrationCredentialRemovalEnvelope = BillingMigrationCredentialRemovalRecord; + +export type BillingMigrationLegalHoldProposalEnvelope = BillingMigrationLegalHoldProposalRecord; + +export type BillingMigrationLegalHoldEnvelope = BillingMigrationLegalHoldRecord; + +export type BillingMigrationCompletionReportEnvelope = BillingMigrationCompletionReportRecord; + +export type BillingMigrationCompletionPrerequisitesEnvelope = BillingMigrationCompletionPrerequisitesRecord; + +export type BillingMigrationStabilizationPolicyEnvelope = BillingMigrationStabilizationPolicyRecord; + +export type BillingMigrationStabilizationObservationEnvelope = BillingMigrationStabilizationObservationRecord; + +export type BillingMigrationRollbackReadinessAssessmentEnvelope = BillingMigrationRollbackReadinessAssessmentRecord; + +export type BillingMigrationRollbackReadinessCheckpointEnvelope = BillingMigrationRollbackReadinessCheckpointRecord; + +export type BillingMigrationSourcePullRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'sourcePullJob'; + payload: BillingMigrationSourcePull; + }; }; -/** - * Per-Project Mosaic Billing configuration. Off by default. - */ -export type BillingSettings = { - projectId?: string; - billingEnabled?: boolean; - /** - * Active Store Server Credentials. Disabling is refused while this is above zero. - */ - activeCredentialCount?: number; - /** - * Whether a disable would be accepted right now. False while any Store Server Credential - * is active: revoking the credential is what actually stops the store delivering. - * - */ - canDisable?: boolean; - updatedAt?: string; +export type BillingMigrationProposalRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'migrationProposal'; + payload: BillingMigrationProposal; + }; }; -export type BillingHealth = { - environmentId?: string; - billingEnabled?: boolean; - credentialCount?: number; - unhealthyCredentials?: number; - queueDepth?: number; - oldestQueuedAgeSeconds?: number; - openQuarantineCount?: number; - factCount?: number; - lastFactRecordedAt?: string; - lastReconciliationAt?: string; +export type BillingMigrationApprovalRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'migrationApproval'; + payload: BillingMigrationApproval; + }; }; -export type IdentifyBillingCustomerRequest = { - /** - * Your own identifier for the user. Stored only as a domain-separated SHA-256 digest. - */ - applicationUserId: string; +export type BillingMigrationCheckpointRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'migrationCheckpoint'; + payload: BillingMigrationCheckpoint; + }; }; -export type AttachBillingCustomerAliasRequest = { - applicationUserId: string; +export type BillingMigrationAuthorityExecutionRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'authorityExecution'; + payload: BillingMigrationAuthorityExecution; + }; }; -export type BillingIdentityCustomer = { - billingCustomerId?: string; - projectId?: string; - status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; - diagnosticsStatus?: string; - currentProjectionVersion?: number; - createdAt?: string; - updatedAt?: string; - lastProjectedAt?: string; +export type BillingMigrationCaseRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'migrationCase'; + payload: BillingMigrationCase; + }; }; -/** - * An alias record. The alias digest is never a member of this shape. - */ -export type BillingCustomerAlias = { - aliasId?: string; - billingCustomerId?: string; - aliasType?: 'application_user_id' | 'installation_id' | 'apple_app_account_token' | 'google_obfuscated_account_id'; - sourceAuthority?: string; - verificationStatus?: string; - effectiveStart?: string; - effectiveEnd?: string; - createdAt?: string; +export type BillingMigrationRepairPreviewRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'repairPreview'; + payload: BillingMigrationRepairPreview; + }; }; -export type BillingIdentityConflict = { - conflictId?: string; - projectId?: string; - scope?: 'lineage' | 'alias'; - status?: 'open' | 'resolved'; - /** - * The incumbent. - */ - firstCustomerId?: string; - /** - * The challenger. - */ - secondCustomerId?: string; - /** - * Present only for a lineage-scoped conflict. - */ - purchaseLineageId?: string; - /** - * Present only for an alias-scoped conflict. The digest is never reported. - */ - aliasType?: string; - diagnosticCode?: string; - openedAt?: string; - resolvedAt?: string; - resolutionAction?: 'assigned_first' | 'assigned_second' | 'detached_both'; +export type BillingMigrationRepairExecutionRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'repairExecution'; + payload: BillingMigrationRepairExecution; + }; }; -export type BillingIdentityConflictDetail = { - conflict?: BillingIdentityConflict; - /** - * The disputed Purchase Lineage, for a lineage-scoped conflict. - */ - lineage?: { - purchaseLineageId?: string; - environmentId?: string; - provider?: 'app_store' | 'google_play'; - storeEnvironment?: 'sandbox' | 'production'; - lineageType?: 'subscription' | 'one_time'; - projectionFrozen?: boolean; - diagnosticStatus?: string; +export type BillingMigrationWebhookRedeliveryRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'webhookRedelivery'; + payload: BillingMigrationWebhookRedelivery; }; }; -export type BillingSyncRequest = { - billingCustomerId?: string; - projectId?: string; - environmentId?: string; - projectionScopeKey?: string; - triggerKind?: 'manual_sync'; - requestedAt?: string; - status?: 'queued'; +export type BillingMigrationCredentialRemovalRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'credentialRemoval'; + payload: BillingMigrationCredentialRemoval; + }; }; -/** - * Must be bounded by at least one of subscriptionInstanceId, billingCustomerId, or a complete window. - */ -export type CreateProjectionReplayRequest = { - subscriptionInstanceId?: string; - billingCustomerId?: string; - /** - * Bounds on facts rather than on lineage creation - a scope is in scope when it holds a fact whose effective or recorded time falls inside the window. - */ - windowStart?: string; - windowEnd?: string; - /** - * Zero selects the active version. A version this build does not derive under is refused rather than approximated. - */ - projectionRuleVersion?: number; - limit?: number; +export type BillingMigrationLegalHoldProposalRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'legalHoldProposal'; + payload: BillingMigrationLegalHoldProposal; + }; }; -export type ProjectionReplayResult = { - projectionRuleVersion?: number; - scopesReplayed?: number; - scopesChanged?: number; - outcomes?: Array<{ - projectionScopeKey?: string; - /** - * The whole point of a replay - proving determinism - */ - comparison?: 'unchanged' | 'changed'; - /** - * Whether a new snapshot was written. Materialization is changes-only - */ - materialized?: boolean; - changedEntitlementIds?: Array; - }>; +export type BillingMigrationLegalHoldRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'legalHold'; + payload: BillingMigrationLegalHold; + }; }; -/** - * One immutable Product-to-Entitlement grant interval. Half-open - [effectiveStart, effectiveEnd). - */ -export type ProductEntitlementGrantVersion = { - grantVersionId?: string; - projectId?: string; - productId?: string; - productKey?: string; - entitlementId?: string; - entitlementKey?: string; - /** - * Monotonic per (Product - */ - version?: number; - /** - * The access-policy vocabulary this version was written under - */ - grantPolicyVersion?: number; - effectiveStart?: string; - /** - * Absent on the current version. Set once - */ - effectiveEnd?: string; - current?: boolean; - /** - * Derived from effectiveStart preceding createdAt - a version whose meaning began before it existed was backdated. - */ - retroactive?: boolean; - supportedPurchaseTypes?: Array<'auto_renewable_subscription' | 'non_consumable'>; - accessPolicy?: GrantAccessPolicy; - createdAt?: string; - createdByActorId?: string; - reason?: string; +export type BillingMigrationCompletionReportRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'completionReport'; + payload: BillingMigrationCompletionReport; + }; }; -/** - * Which subscription states this grant treats as granting access (plan policy version 1). - */ -export type GrantAccessPolicy = { - grantsInActive?: boolean; - grantsInTrial?: boolean; - /** - * Both providers grant access during grace - */ - grantsInGrace?: boolean; - /** - * Contradicts both providers' documentation. Closed by default and settable only by an organization owner. - */ - grantsInBillingRetry?: boolean; - /** - * Always false. Google's pause never grants access and the policy is not overridable. - */ - grantsInPaused?: boolean; - grantsInOneTimeOwnership?: boolean; +export type BillingMigrationCompletionPrerequisitesRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'completionPrerequisites'; + payload: BillingMigrationCompletionPrerequisites; + }; }; -/** - * A proposed grant version. The same body is accepted by the impact preview and by publish, - * so what is previewed is what is published. Omitted policy flags take their documented - * defaults rather than false - a missing grantsInActive defaulting to false would publish a - * version granting nothing during an active subscription, the opposite of what an operator - * leaving the field out meant. - * - */ -export type PublishGrantVersionRequest = { - productId: string; - entitlementId: string; - /** - * Now or later unless retroactive is true. - */ - effectiveStart: string; - /** - * Backdate the change. Held to the additive-superset rule and confined to the currently open interval. - */ - retroactive?: boolean; - supportedPurchaseTypes?: Array<'auto_renewable_subscription' | 'non_consumable'>; - grantsInActive?: boolean; - grantsInTrial?: boolean; - grantsInGrace?: boolean; - grantsInBillingRetry?: boolean; - /** - * Accepted only so it can be refused with 422. - */ - grantsInPaused?: boolean; - grantsInOneTimeOwnership?: boolean; - /** - * Required to publish. It is what an investigation reads months later - */ - reason?: string; +export type BillingMigrationStabilizationPolicyRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'stabilizationPolicy'; + payload: BillingMigrationStabilizationPolicy; + }; }; -/** - * Read-only counts of what a proposed grant change would touch, from current committed state. - */ -export type GrantVersionImpact = { - productId?: string; - entitlementId?: string; - /** - * Products a reprojection of the affected customers would re-derive. - */ - impactedProducts?: number; - /** - * Entitlements a reprojection of the affected customers would re-derive. - */ - impactedEntitlements?: number; - /** - * Billing Customers whose current snapshot cites this Product. - */ - impactedCustomers?: number; - /** - * How many of those citations are currently granting access - the number that answers how many people could lose access. - */ - impactedActiveSources?: number; - /** - * Purchase lineages resolved to this Product - */ - impactedLineages?: number; - retroactive?: boolean; - /** - * Whether the proposal passes the widen-only rule. Meaningful for a retroactive change; a preview reports it - */ - additiveSuperset?: boolean; - /** - * The first narrowing found when additiveSuperset is false - */ - narrowingCode?: string; - currentVersion?: ProductEntitlementGrantVersion; - observedAt?: string; +export type BillingMigrationStabilizationObservationRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'stabilizationObservation'; + payload: BillingMigrationStabilizationObservation; + }; }; -export type WebhookDestination = { - id?: string; - projectId?: string; - environmentId?: string; - /** - * HTTPS only. Screened against the resolved address at registration and again on every delivery attempt. - */ - url?: string; - status?: 'active' | 'paused' | 'disabled'; - /** - * Phase 9B emits one event type. The other nine the contract declares are reserved names. - */ - eventTypes?: Array<'customer.entitlements.changed'>; - description?: string; - createdAt?: string; - updatedAt?: string; - secretLastRotatedAt?: string; - /** - * What an operator wrote when they disabled it by hand. - */ - disabledReason?: string; - /** - * Exhausted deliveries in a row. Any success resets it - */ - consecutiveFailureCount?: number; - autoDisabledAt?: string; - /** - * Set only by the automatic path - */ - autoDisableReason?: 'consecutive_exhausted_deliveries' | 'destination_refused'; +export type BillingMigrationRollbackReadinessAssessmentRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'rollbackReadinessAssessment'; + payload: BillingMigrationRollbackReadinessAssessment; + }; }; -export type WebhookDestinationWithSecret = { - destination?: WebhookDestination; - /** - * Displayed exactly once. Mosaic keeps only the sealed form - */ - secret?: string; - secretId?: string; - /** - * Set by a rotation. Until this instant the superseded secret still signs. Absent on a create - */ - previousSecretHonoredUntil?: string; +export type BillingMigrationRollbackReadinessCheckpointRecord = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'rollbackReadinessCheckpoint'; + payload: BillingMigrationRollbackReadinessCheckpoint; + }; }; -export type WebhookSigningSecretMetadata = { - id?: string; - status?: 'active' | 'retired'; - createdAt?: string; - retiredAt?: string; - /** - * A retired secret keeps signing until this instant - */ - honoredUntil?: string; +export type BillingMigrationStabilizationThresholds = { + authorityMismatchMax: number; + accessApiErrorMax: number; + sdkSyncFailureMax: number; + divergenceMax: number; + validationBacklogMax: number; + sourceDeltaLagMaxSeconds: number; + webhookFailureMax: number; + webhookFreshnessMaxSeconds: number; + quarantineMax: number; + supportCaseMax: number; + oldAppVersionMax: number; + workerUnhealthyMax: number; }; -export type CreateWebhookDestinationRequest = { - url: string; - eventTypes?: Array<'customer.entitlements.changed'>; - description?: string; +export type BillingMigrationFreezeStabilizationPolicyRequest = { + expectedStateVersion: number; + thresholds: BillingMigrationStabilizationThresholds; }; -export type UpdateWebhookDestinationRequest = { - url?: string; - eventTypes?: Array<'customer.entitlements.changed'>; - description?: string; +export type BillingMigrationObserveStabilizationRequest = { + expectedStateVersion: number; + expectedAuthorityEpoch: number; + expectedPolicyDigest: BillingMigrationDigest; }; -export type SetWebhookDestinationStatusRequest = { - status: 'active' | 'paused' | 'disabled'; - reason?: string; +export type BillingMigrationAssessRollbackReadinessRequest = { + observationId: string; + expectedStateVersion: number; + expectedAuthorityEpoch: number; + expectedObservationDigest: BillingMigrationDigest; }; -export type WebhookDelivery = { - id?: string; - projectId?: string; - environmentId?: string; - /** - * Stable across every attempt and every manual replay. It is the consumer's deduplication key. - */ - eventId?: string; - destinationId?: string; - status?: 'pending' | 'succeeded' | 'failed' | 'exhausted' | 'skipped'; - skippedReason?: 'destination_disabled' | 'event_type_not_enabled' | 'destination_deleted' | 'tenant_suspended'; - attemptCount?: number; - maxAttempts?: number; - nextAttemptAt?: string; - createdAt?: string; - updatedAt?: string; - completedAt?: string; +export type BillingMigrationDigestSet = { + scope: BillingMigrationDigest; + manifest: BillingMigrationDigest; + mapping: BillingMigrationDigest; + policy: BillingMigrationDigest; + evidence: BillingMigrationDigest; + readiness: BillingMigrationDigest; + finalWatermark: BillingMigrationDigest; + applicationVersion: BillingMigrationDigest; }; -/** - * One recorded try. Mirrors the Billing State Webhook Contract v1 webhookDeliveryAttempt - * record; the normative shape is protocol/schema/billing-state-webhook/v1/delivery.schema.json. - * - */ -export type WebhookDeliveryAttempt = { - id?: string; - deliveryId?: string; - eventId?: string; - destinationId?: string; - attempt?: number; - maxAttempts?: number; - outcome?: 'delivered' | 'retryable_failure' | 'permanent_failure' | 'exhausted' | 'skipped'; - responseStatusCode?: number; - /** - * Bounded and control-character-free. Never parsed - */ - responseExcerpt?: string; - errorCode?: string; - latencyMs?: number; - skippedReason?: 'destination_disabled' | 'event_type_not_enabled' | 'destination_deleted' | 'tenant_suspended'; - requestedAt?: string; - respondedAt?: string; - nextAttemptAt?: string; +export type BillingMigrationSourcePullRequest = { + intent: 'snapshot' | 'delta' | 'final_delta'; + startingCursor?: string; + startingWatermark?: string; + startingWatermarkDigest?: BillingMigrationDigest; + expectedStateVersion: number; }; -export type BillingProjectionHealth = { - environmentId?: string; - billingEnabled?: boolean; - /** - * The rule version new projections are computed under. - */ - activeProjectionRuleVersion?: number; - /** - * A count above one with no replay in flight means a promotion was prepared and never run. - */ - projectionRuleVersionCount?: number; - projectionQueueDepth?: number; - /** - * The alerting signal. Depth alone cannot distinguish a busy queue from a stuck one. - */ - projectionOldestQueuedAgeSeconds?: number; - projectionFailedJobs?: number; - /** - * A rate signal the queue depth cannot give - */ - projectionFailuresLastHour?: number; - /** - * Customers whose committed state in this Environment is older than the staleness threshold. - */ - staleCustomers?: number; - neverProjectedCustomers?: number; - /** - * A spike here is a security-relevant signal - */ - openIdentityConflicts?: number; - frozenLineages?: number; - unresolvedLineages?: number; - /** - * Entries on current snapshots stating unknown - how often Mosaic is declining to answer. - */ - unknownEntitlementEntries?: number; - restoreBacklog?: number; - restoreFailedJobs?: number; - webhookDeliveryBacklog?: number; - webhookDeliveriesExhausted?: number; - activeWebhookDestinations?: number; - lastProjectionCommittedAt?: string; - observedAt?: string; +export type BillingMigrationCutoverProposalRequest = { + expectedStateVersion: number; + expectedDigests: BillingMigrationDigestSet; + reason: string; + expiresAt: Timestamp; }; -/** - * One Billing Customer as the operator list and the detail header report it. It carries no - * alias value and no alias digest, because Mosaic exposes neither on any surface. - * - */ -export type BillingCustomerSummary = { - billingCustomerId?: string; - projectId?: string; - environmentId?: string; - status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; - diagnosticsStatus?: 'none' | 'identity_conflict' | 'projection_stale' | 'projection_failed'; - /** - * An active application-user alias exists - a person is attached. - */ - identified?: boolean; - /** - * A purchase lineage exists in this Environment - revenue is attached. - */ - purchaseAnchored?: boolean; - hasOpenIdentityConflict?: boolean; - frozenLineageCount?: number; - currentProjectionVersion?: number; - lastProjectedAt?: string; - /** - * Absent when the customer has never been projected in this Environment, which is not the same as having no entitlements. - */ - snapshotVersion?: number; - snapshotUpdatedAt?: string; - createdAt?: string; - updatedAt?: string; +export type BillingMigrationRollbackProposalRequest = { + checkpointId: string; + expectedStateVersion: number; + expectedCheckpointDigest: BillingMigrationDigest; + expectedAuthorityDigest: BillingMigrationDigest; + expectedRollbackPrerequisitesDigest: BillingMigrationDigest; + reason: string; + expiresAt: Timestamp; }; -export type BillingCustomerLookupRequest = { - identifierType: 'billing_customer_id' | 'application_user_id' | 'installation_id'; +export type BillingMigrationCheckpointRequest = { + approvalId: string; + expectedStateVersion: number; + expectedDigests: BillingMigrationDigestSet; + approvalDigest: BillingMigrationDigest; + cohortDigest: BillingMigrationDigest; +}; + +export type BillingMigrationExecutionScope = { + environmentId: string; + applications: Array; +}; + +export type BillingMigrationCutoverExecutionRequest = { + expectedStateVersion: number; + expectedDigests: BillingMigrationDigestSet; + approvalDigest: BillingMigrationDigest; + reason: string; + scope: BillingMigrationExecutionScope; + checkpointId: string; + approvalId: string; + expectedAuthorityEpoch: number; +}; + +export type BillingMigrationRollbackExecutionRequest = { + expectedStateVersion: number; + expectedCheckpointDigest: BillingMigrationDigest; + expectedAuthorityDigest: BillingMigrationDigest; + expectedRollbackPrerequisitesDigest: BillingMigrationDigest; + expectedApprovalDigest: BillingMigrationDigest; + reason: string; + scope: BillingMigrationExecutionScope; + checkpointId: string; + approvalId: string; + expectedAuthorityEpoch: number; +}; + +export type BillingMigrationCaseRequest = { + expectedStateVersion: number; + classification: 'critical' | 'blocking' | 'warning'; + reason: string; + linkedDivergenceId?: string; + linkedSourceRecordId?: string; +}; + +export type BillingMigrationCaseTransitionRequest = { + expectedStateVersion: number; + expectedCaseDigest: BillingMigrationDigest; + status: 'in_progress' | 'resolved' | 'dismissed'; + reason: string; +}; + +export type BillingMigrationRepairPreviewRequest = { + caseId: string; + repairKind: 'provider_revalidate' | 'projection_replay' | 'attach_proven_alias' | 'replace_mapping_set' | 'retry_quarantined_record'; + scopeKind: string; + scopeReferences: Array; + expectedStateVersion: number; + expectedCaseDigest: BillingMigrationDigest; + expectedPolicyDigest: BillingMigrationDigest; + expectedScopeDigest: BillingMigrationDigest; + reason: string; + expiresAt: Timestamp; +}; + +export type BillingMigrationRepairExecutionRequest = { + previewId: string; + expectedStateVersion: number; + expectedPreviewDigest: BillingMigrationDigest; + expectedCaseDigest: BillingMigrationDigest; + expectedPolicyDigest: BillingMigrationDigest; + expectedScopeDigest: BillingMigrationDigest; +}; + +export type BillingMigrationRedeliveryRequest = { + eventId: string; + destinationId: string; + expectedEventDigest: BillingMigrationDigest; + expectedStateVersion: number; + reason: string; +}; + +export type BillingMigrationCredentialRemovalRequest = { + expectedStateVersion: number; + reason: string; + irreversibleAcknowledged: true; +}; + +export type BillingMigrationLegalHoldProposalRequest = { + command: 'set' | 'release'; + reason: string; + externalComplianceReference: string; + expectedPreviousCommandDigest?: BillingMigrationDigest; + expiresAt: Timestamp; +}; + +export type BillingMigrationLegalHoldApprovalRequest = { + expectedProposalDigest: BillingMigrationDigest; +}; + +export type BillingMigrationCompletionRequest = { + expectedStateVersion: number; + expectedPolicyDigest: BillingMigrationDigest; + expectedAuthorityDigest: BillingMigrationDigest; + expectedStabilityEvidenceDigest: BillingMigrationDigest; +}; + +export type BillingMigrationDigest = string; + +export type BillingMigrationSourceManifest = { + programId: string; + stateVersion: number; + manifestId: string; + adapterVersion: string; + providerApiVersion: string; + schemaVersion: string; + recordCount: number; + currentAccessRecordCount: number; + objectChecksum: BillingMigrationDigest; + manifestDigest: BillingMigrationDigest; + capturedAt: Timestamp; +}; + +export type BillingMigrationSourceManifestListEnvelope = { + data: { + items: Array<{ + billingMigrationOperationsContractVersion: '1'; + recordType: 'sourceManifest'; + payload: BillingMigrationSourceManifest; + }>; + }; +}; + +export type BillingMigrationMappingSet = { + programId: string; + stateVersion: number; + mappingSetId: string; + version: number; + status: 'draft' | 'frozen'; + entries: Array; + mappingDigest: BillingMigrationDigest; +}; + +export type BillingMigrationMappingEntry = { + sourceKind: 'customer_id' | 'original_customer_id' | 'audited_alias' | 'product' | 'entitlement'; + sourceIdentifier: string; + targetId: string; + matchKind: 'exact' | 'audited_alias'; +}; + +export type BillingMigrationMappingSetEnvelope = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'mappingSet'; + payload: BillingMigrationMappingSet; + }; +}; + +export type BillingMigrationMappingSetListEnvelope = { + data: { + items: Array<{ + billingMigrationOperationsContractVersion: '1'; + recordType: 'mappingSet'; + payload: BillingMigrationMappingSet; + }>; + }; +}; + +export type BillingMigrationReadiness = { + programId: string; + stateVersion: number; + ready: boolean; + currentAccessMappingPercent: number; /** - * The raw identifier. It is digested server-side and is never stored, never logged, and never echoed back. + * Counts only provider-signed/provider-validated evidence or a future explicitly persisted scoped exception. */ - identifierValue: string; + currentAccessEvidencePercent: number; + unresolved: { + critical: number; + blocking: number; + warning: number; + informational: number; + }; + finalDeltaCompleted: boolean; + watermarksFresh: boolean; + supportedVersionsAuthorityAware: boolean; + readinessDigest: BillingMigrationDigest; }; -export type BillingCustomerLookupResult = { - found?: boolean; - customer?: BillingCustomerSummary; +export type BillingMigrationReadinessEnvelope = { + data: { + billingMigrationOperationsContractVersion: '1'; + recordType: 'readinessAssessment'; + payload: BillingMigrationReadiness; + }; +}; + +export type BillingMigrationImportBatch = { + programId: string; + stateVersion: number; + batchId: string; + idempotencyKey: string; + status: 'pending' | 'running' | 'completed' | 'failed'; + recordCount: number; + validatedCount: number; + quarantinedCount: number; +}; + +export type BillingMigrationImportBatchRecord = { + billingMigrationOperationsContractVersion: '1'; + recordType: 'importBatch'; + payload: BillingMigrationImportBatch; +}; + +export type BillingMigrationImportBatchEnvelope = { + data: BillingMigrationImportBatchRecord; +}; + +export type BillingMigrationImportBatchListEnvelope = { + data: { + items: Array; + }; +}; + +export type BillingMigrationRunJob = { + programId: string; + stateVersion: number; + runJobId: string; + runKind: 'dry_run' | 'shadow'; + status: 'pending' | 'running' | 'completed' | 'failed'; + resultRunId?: string; +}; + +export type BillingMigrationRunJobEnvelope = { + data: BillingMigrationRunJob; +}; + +export type BillingMigrationDivergence = { + programId: string; + stateVersion: number; + divergenceId: string; + classification: 'critical' | 'blocking' | 'warning' | 'informational'; + reason: string; + observedAt: Timestamp; + classificationRuleVersion: string; +}; + +export type BillingMigrationDivergenceRecord = { + billingMigrationOperationsContractVersion: '1'; + recordType: 'divergence'; + payload: BillingMigrationDivergence; +}; + +export type BillingMigrationDivergenceListEnvelope = { + data: { + items: Array; + }; +}; + +export type CreateBillingMigrationMappingSetRequest = { + expectedStateVersion: number; + version: number; + entries: Array<{ + sourceKind: 'customer_id' | 'original_customer_id' | 'audited_alias' | 'product' | 'entitlement'; + sourceIdentifier: string; + targetId: string; + matchKind: 'exact' | 'audited_alias'; + }>; +}; + +export type CreateBillingMigrationImportBatchRequest = { + expectedStateVersion: number; + manifestId: string; + mappingSetId: string; + recordCount: number; + cursorBefore: string; +}; + +export type QueueBillingMigrationRunRequest = { + expectedStateVersion: number; + manifestDigest: BillingMigrationDigest; + mappingDigest: BillingMigrationDigest; }; /** - * One accepted external identity bound to a customer, as a protected representation. There - * is no value field and no digest field: the alias id identifies the row for a revocation - * and reveals nothing about the person, whereas an alias digest is still a stable - * per-person identifier. + * The provider reference a validation is performed against. Raw receipts, signed payloads, + * JWS representations, purchase tokens, and service-account material are structurally + * impossible to carry here: an Apple value is at most 24 decimal digits and a Google value + * is exactly 64 lowercase hexadecimal characters. * */ -export type OperatorBillingCustomerAlias = { - aliasId?: string; - aliasType?: 'application_user_id' | 'installation_id' | 'apple_app_account_token' | 'google_obfuscated_account_id'; - sourceAuthority?: 'trusted_server' | 'sdk_installation' | 'provider_payload' | 'operator' | 'restore'; - verificationStatus?: 'asserted' | 'verified'; - active?: boolean; - effectiveStart?: string; - effectiveEnd?: string; +export type TransactionReference = { + referenceKind: 'app_store_transaction_id' | 'google_play_token_digest'; + value: string; }; /** - * One provider purchase chain. Supersession is an explicit edge; nothing is ever deleted. + * Optional Google Play order reference. A join handle only; never the identity of a Transaction Fact, because promotional purchases have none. */ -export type BillingPurchaseLineage = { - purchaseLineageId?: string; - environmentId?: string; - provider?: 'app_store' | 'google_play'; - storeEnvironment?: 'sandbox' | 'production'; - lineageType?: 'subscription' | 'one_time'; - /** - * Set while an identity conflict is open - the projector skips it and the last committed state is preserved. - */ - projectionFrozen?: boolean; - diagnosticStatus?: 'none' | 'identity_unresolved' | 'identity_conflict' | 'product_unresolved'; - supersededByLineageId?: string; - createdAt?: string; - updatedAt?: string; +export type ProviderOrderReference = { + referenceKind: 'google_play_order_id'; + value: string; }; /** - * Validated ownership of a non-consumable. Consumables are excluded from Mosaic Billing. + * SDK context. It carries no Organization, Project, Environment, or Application identity: tenant scope is derived from the authenticated key. */ -export type BillingOneTimePurchase = { - oneTimePurchaseInstanceId?: string; - purchaseLineageId?: string; - provider?: 'app_store' | 'google_play'; - mosaicProductId?: string; - providerProductIdentifier?: string; - acquiredAt?: string; - validityState?: 'owned' | 'refunded' | 'revoked' | 'unknown'; - refundEffectiveAt?: string; - revocationEffectiveAt?: string; +export type ObservationContext = { + platform: 'ios' | 'android'; + sdkFamily: 'flutter' | 'ios' | 'android'; + sdkVersion: string; + operatingSystemVersion?: string; + applicationVersion?: string; }; -export type BillingProjectionStatus = { - state?: 'current' | 'pending' | 'stale' | 'degraded' | 'failed'; - lastProjectedAt?: string; - pendingFactCount?: number; - diagnosticCode?: string; +/** + * Correlation to Analytics Event 1/2 through the existing opaque handles only. + */ +export type ObservationCorrelation = { + purchaseAttemptId?: string; + providerOperationId?: string; + providerUpdateId?: string; }; -export type BillingEntitlementSnapshotEntry = { - entitlementId?: string; - entitlementKey?: string; - /** - * unavailable is a read-time service state and is never persisted on a snapshot. - */ - state?: 'active' | 'inactive' | 'unknown'; - effectiveStart?: string; - effectiveEnd?: string; - endKnown?: boolean; - sourceCount?: number; - uncertaintyReason?: 'none' | 'provider_unavailable' | 'missing_fact' | 'identity_unresolved' | 'product_unresolved' | 'conflicting_facts' | 'projection_failed' | 'stale_validation' | 'unsupported_provider_state'; - isTestSource?: boolean; - explanationCode?: string; - sourceIds?: Array; +/** + * Server-derived Store Environment classification. A client never asserts it: no observation submitted by a client carries this property at all. + */ +export type StoreEnvironmentClassification = { + classification: 'sandbox' | 'production' | 'unclassified'; + basis: 'provider_asserted' | 'provider_endpoint' | 'signature_environment' | 'mosaic_environment_policy' | 'unknown'; }; /** - * One reason the customer holds, or may hold, an Entitlement. Source identity is - * (purchase lineage, Mosaic Product, grant version) and never a fact id, so multiple facts - * describing one purchase cannot double-grant. + * The Billing Ingestion Contract v1 clientTransactionObservation record, accepted verbatim + * so one platform-neutral document travels from four SDKs to one server. * */ -export type BillingEntitlementSource = { - sourceId?: string; - entitlementId?: string; - purchaseLineageId?: string; - mosaicProductId?: string; - grantVersionId?: string; - subscriptionInstanceId?: string; - oneTimePurchaseInstanceId?: string; - storePlatform?: string; - sourceType?: string; - sourceState?: string; - sourceStart?: string; - sourceEnd?: string; - endKnown?: boolean; - uncertaintyReason?: string; - isTestSource?: boolean; - explanationCode?: string; -}; - -export type BillingEntitlementSnapshot = { - snapshotId?: string; - /** - * Per-customer monotonic. It is the sole cache-monotonicity key; a no-change projection does not advance it. - */ - snapshotVersion?: number; - previousSnapshotVersion?: number; - projectionRuleVersion?: number; - computedAt?: string; - asOf?: string; - changeReason?: string; - entries?: Array; - sources?: Array; +export type ClientTransactionObservationRecord = { + billingIngestionContractVersion: '1'; + recordType: 'clientTransactionObservation'; + payload: ClientTransactionObservation; }; /** - * One projected Subscription Instance. Access and lifecycle are separate axes on purpose: - * a cancelled subscription keeps access until its validated period end, so cancellation - * moves renewal intent and nothing else. + * An untrusted claim that a transaction may exist. It carries no receipt, no signed + * payload, no purchase token, no credential, no price, no entitlement assertion, no tenant + * identity, and no Store Environment assertion. + * + * There is deliberately no storeEnvironmentClassification member, and additionalProperties + * is false, so a client-asserted Store Environment is rejected with `unknown_field` rather + * than ignored. Classification comes only from server-side validation. + * + * sourceAuthority must be `client_observation`: a public SDK key proves only that a client + * sent the document, and any higher authority claimed here is refused with + * `authority_not_allowed`. * */ -export type BillingSubscriptionSnapshot = { - subscriptionInstanceId?: string; - purchaseLineageId?: string; - billingCustomerId?: string; - environmentId?: string; - storePlatform?: string; - mosaicProductId?: string; - priorMosaicProductId?: string; +export type ClientTransactionObservation = { + observationId: string; /** - * unavailable is a read-time service state and is never persisted on a snapshot. + * Deterministic idempotency key computed by the SDK. Never derived from a timestamp, price, Product, or subject. */ - accessState?: 'active' | 'inactive' | 'unknown'; - lifecycleState?: 'trialing' | 'active' | 'grace_period' | 'billing_retry' | 'paused' | 'expired' | 'revoked' | 'refunded' | 'superseded' | 'unknown'; - renewalIntent?: string; - billingState?: string; - uncertaintyReason?: string; - projectionVersion?: number; - projectionRuleVersion?: number; - computedAt?: string; - asOf?: string; - periodStart?: string; - periodEnd?: string; - gracePeriodEnd?: string; - billingRetryStart?: string; - pauseEffectiveAt?: string; - pauseResumeAt?: string; - cancellationEffectiveAt?: string; - expirationEffectiveAt?: string; - revocationEffectiveAt?: string; - refundEffectiveAt?: string; - supersededBySubscriptionInstanceId?: string; - isTestSource?: boolean; - sourceFactCount?: number; - changeReason?: string; - explanationCode?: string; -}; - -export type BillingTimelineEntry = { - timelineEntryId?: string; - entryType?: string; - effectiveAt?: string; - observedAt?: string; - subscriptionInstanceId?: string; - mosaicProductId?: string; - priorMosaicProductId?: string; - explanationCode?: string; + submissionId: string; + providerId: string; + storePlatform: 'apple_app_store' | 'google_play'; + transactionReference: TransactionReference; + providerOrderReference?: ProviderOrderReference; /** - * Passed through the ledger guard function, so no provider token or raw payload fragment can appear here. + * UTC timestamp, RFC 3339 with a literal Z. */ - detail?: { - [key: string]: string; - }; + observedAt: string; + sourceAuthority: 'client_observation'; + context: ObservationContext; + correlation?: ObservationCorrelation; + /** + * A claim only. The server resolves the Mosaic Product independently; a mismatch is a diagnostic and never an override. + */ + claimedMosaicProductId?: string; }; -export type BillingCustomerDetail = { - customer?: BillingCustomerSummary; - aliases?: Array; - purchaseLineages?: Array; - subscriptions?: Array; - oneTimePurchases?: Array; - identityConflicts?: Array; - currentSnapshot?: BillingEntitlementSnapshot; - projectionStatus?: BillingProjectionStatus; +export type ServerTransactionObservationRecord = { + billingIngestionContractVersion: '1'; + recordType: 'serverTransactionObservation'; + payload: ServerTransactionObservation; }; /** - * One disputed association held open for operator resolution. Access is granted to neither - * candidate while it is open: the disputed subject is frozen and the last committed state is - * preserved. The disputed alias digest is never part of this shape. + * A trusted app-backend observation. It records how trust was established, never the + * credential that established it. Like the client record it carries no purchase token: the + * reference is the same digest a client would send. + * + * On this endpoint sourceAuthority must be `trusted_server_observation`. A Mosaic secret + * server key proves a trusted backend sent the document; it proves nothing about a provider + * having signed anything, so `provider_notification`, `reconciliation_discovery`, and + * `manual_revalidation` — authorities only Mosaic's own pipeline may author — are refused + * with `authority_not_allowed`. * */ -export type OperatorBillingIdentityConflict = { - conflictId?: string; - projectId?: string; - scope?: 'lineage' | 'alias'; - status?: 'open' | 'resolved'; - purchaseLineageId?: string; - aliasType?: string; +export type ServerTransactionObservation = { + observationId: string; + submissionId: string; + providerId: string; + storePlatform: 'apple_app_store' | 'google_play'; + transactionReference: TransactionReference; + providerOrderReference?: ProviderOrderReference; + sourceAuthority: 'trusted_server_observation'; + trustBasis: 'provider_signature_verified' | 'mutual_tls' | 'provider_server_api' | 'operator_initiated'; /** - * The incumbent. + * The full Google Play purchase token, permitted only here, only on a `google_play` + * record, and only under `trusted_server_observation` authority. The client record has + * no such member and rejects one as `unknown_field`. + * + * It is a transaction reference the buyer's own purchase produced, not a Mosaic provider + * credential; service-account keys, signing keys, and Authorization values remain + * forbidden everywhere. It is encrypted at rest on receipt, never logged, never returned + * on any read, and never relieves the record of full provider validation. + * + * When present it MUST SHA-256-digest to this record's own `transactionReference.value`. + * Without that binding a caller could file a real token under a different transaction's + * reference, and Mosaic would validate the token, get a genuine answer from Google, and + * record it as a fact about the transaction the reference named. A mismatch is rejected + * with `provider_reference_malformed`. + * + * It exists because a digest cannot be reversed: without a token a Google observation + * has nothing to validate against and can only wait for the notification. + * */ - firstCustomerId?: string; + purchaseToken?: string; + receivedAt: string; + providerReportedAt?: string; /** - * The challenger the evidence proposed. + * Opaque provider notification identifier. Never the notification body. */ - secondCustomerId?: string; - diagnosticCode?: 'multiple_customers_claim_lineage' | 'reassignment_requires_operator_resolution' | 'application_user_alias_claims_two_customers'; - openedAt?: string; - resolvedAt?: string; - resolutionAction?: 'keep_existing' | 'reassign_to_candidate' | 'operator_split'; - resolutionReason?: string; + providerNotificationReference?: string; + storeEnvironmentClassification?: StoreEnvironmentClassification; + correlation?: ObservationCorrelation; + /** + * Referencing a client observation never raises that observation's authority. + */ + originatingObservationId?: string; }; -export type OperatorBillingIdentityConflictDetail = { - conflict?: OperatorBillingIdentityConflict; - lineage?: BillingPurchaseLineage; +/** + * The Billing Ingestion Contract v1 observationSubmissionResult record, returned verbatim + * by both observation endpoints so every SDK decodes one platform-neutral shape. Validated + * against protocol/schema/billing-ingestion/v1/submission-response.schema.json. + * + */ +export type ObservationSubmissionResultRecord = { + billingIngestionContractVersion: '1'; + recordType: 'observationSubmissionResult'; + payload: ObservationSubmissionResult; }; -export type ResolveIdentityConflictRequest = { - action: 'keep_existing' | 'reassign_to_candidate' | 'operator_split'; +/** + * The status set contains no member named validated, verified, confirmed, or entitled. + * Acceptance means the observation is well formed and queued and asserts nothing about the + * transaction being real. A reader must never grant access, unlock content, or emit a + * provider-confirmed Analytics Event on accepted_for_validation. + * + */ +export type ObservationSubmissionResult = { + submissionId: string; /** - * Optional. When present it must name the party the action already implies. + * UTC timestamp, RFC 3339 with a literal Z and at most microsecond precision. */ - assignedBillingCustomerId?: string; + receivedAt: string; + status: 'accepted_for_validation' | 'duplicate' | 'permanently_rejected' | 'retryable_failure'; /** - * Required. Recorded on the conflict and on the audit event. + * Required for permanently_rejected and retryable_failure, absent otherwise. */ - reason: string; -}; - -export type OperatorBillingSyncRequest = { - billingCustomerId?: string; - projectId?: string; - environmentId?: string; + code?: 'observation_schema_invalid' | 'unsupported_contract_version' | 'unsupported_record_type' | 'unknown_field' | 'invalid_identifier' | 'invalid_timestamp' | 'observed_at_too_far_future' | 'observation_expired' | 'observation_too_large' | 'provider_reference_malformed' | 'provider_reference_too_long' | 'reference_kind_not_supported_for_platform' | 'credential_shaped_value_rejected' | 'sensitive_value_rejected' | 'tenant_field_forbidden' | 'authority_not_allowed' | 'unknown_provider' | 'billing_not_enabled_for_environment' | 'observation_id_conflict' | 'rate_limited' | 'storage_temporarily_unavailable' | 'service_temporarily_unavailable' | 'ingestion_timeout' | 'validation_backlog_saturated'; /** - * What the projection queue coalesces on. + * Advisory hint on retryable_failure only. */ - projectionScopeKey?: string; - triggerKind?: string; - requestedAt?: string; - status?: 'queued'; + retryAfterSeconds?: number; + /** + * Advisory hint on accepted_for_validation only. Says when validation is likely to run, never that it succeeded. + */ + estimatedValidationDelaySeconds?: number; +}; + +export type StoreServerCredentialApplication = { + applicationId?: string; + platform?: 'ios' | 'android'; + /** + * Apple bundle id or Google package name. The verified payload must match one of these. + */ + providerApplicationIdentifier?: string; }; /** - * One restore or sync job. Mosaic's authoritative `outcome` and the native `providerOutcome` - * are separate axes and are never merged: a completed native restore whose facts have not - * reached a snapshot is not restored access. `restored` is admissible only together with the - * accepted `snapshotVersion` that demonstrates it. + * Encrypted Apple or Google server credential. Secret material is never returned. + */ +export type StoreServerCredential = { + id?: string; + projectId?: string; + environmentId?: string; + provider?: 'app_store' | 'google_play'; + /** + * Store Environment + */ + storeEnvironment?: 'sandbox' | 'production'; + name?: string; + status?: 'active' | 'revoked'; + healthStatus?: 'untested' | 'healthy' | 'degraded' | 'unavailable' | 'revoked'; + appleIssuerId?: string; + appleKeyId?: string; + googleClientEmail?: string; + googlePubSubProjectId?: string; + googlePubSubSubscriptionId?: string; + applications?: Array; + lastErrorCode?: string; + lastTestedAt?: string; + createdAt?: string; + rotatedAt?: string; + revokedAt?: string; + updatedAt?: string; +}; + +export type StoreServerCredentialWithEndpoint = StoreServerCredential & { + /** + * Full Apple notification endpoint including the intake token. Returned only on + * create and rotate. The token is stored as SHA-256 only and cannot be recovered. + * + */ + notificationEndpointUrl?: string; +}; + +export type CreateStoreServerCredentialRequest = { + environmentId: string; + provider: 'app_store' | 'google_play'; + /** + * Must align with the Environment mode; sandbox and production never mix. + */ + storeEnvironment: 'sandbox' | 'production'; + name: string; + /** + * Write-only. Apple .p8 PEM or Google service-account JSON. Validated before persistence, never returned. + */ + secret: string; + appleIssuerId?: string; + appleKeyId?: string; + googleClientEmail?: string; + googlePubSubProjectId?: string; + googlePubSubSubscriptionId?: string; + applications: Array; +}; + +/** + * A normalized, provider-independent statement that a store confirmed something + * happened. Never a subscription, an entitlement, or an access grant. Carries no + * customer identity, price, or currency. * */ -export type BillingRestoreJob = { - restoreId?: string; +export type TransactionFact = { + id?: string; + projectId?: string; environmentId?: string; + applicationId?: string; + provider?: 'app_store' | 'google_play'; + storeEnvironment?: 'sandbox' | 'production'; + providerTransactionId?: string; + providerOriginalTransactionId?: string; + transactionType?: 'auto_renewable_subscription' | 'non_consumable'; + factKind?: 'initial_purchase' | 'renewal' | 'one_time_purchase' | 'plan_change' | 'offer_redeemed' | 'refund' | 'revocation' | 'expiration' | 'grace_period_start' | 'billing_retry_start' | 'cancellation_scheduled' | 'auto_renew_disabled' | 'auto_renew_enabled' | 'purchase_superseded' | 'paused' | 'resumed'; + occurredAt?: string; /** - * Empty until identity resolves - which is exactly the identity_unresolved outcome. + * Provider-stated validity only. Never interpreted as customer access. */ - billingCustomerId?: string; - storePlatform?: 'apple_app_store' | 'google_play'; - status?: 'queued' | 'leased' | 'completed' | 'failed'; - outcome?: 'restored' | 'no_additional_purchases' | 'validation_pending' | 'identity_unresolved' | 'product_unresolved' | 'provider_unavailable' | 'failed'; - providerOutcome?: 'completed' | 'no_purchases_found' | 'cancelled' | 'failed' | 'unsupported' | 'not_attempted'; - uncertaintyReason?: string; - observedTransactionCount?: number; - pendingValidationCount?: number; - baselineSnapshotVersion?: number; - snapshotVersion?: number; + periodStartAt?: string; + /** + * Provider-stated validity only. Never interpreted as customer access. + */ + periodEndAt?: string; + revokedAt?: string; + refundedAt?: string; + renewalExpected?: boolean; + isTestTransaction?: boolean; + providerProductIdentifier?: string; + providerBasePlanIdentifier?: string; + providerOfferIdentifier?: string; + resolutionState?: 'active_mapping' | 'archived_mapping' | 'replacement_chain' | 'unresolved'; + mosaicProductId?: string; + providerProductMappingId?: string; + /** + * Resolution Snapshot version + */ + resolvedMappingVersion?: number; + validatorVersion?: number; + factVersion?: number; + sourceRawInputId?: string; + validationAttemptId?: string; + recordedAt?: string; +}; + +/** + * One append-only record of one validation try. No provider response body is ever stored. + */ +export type ValidationAttempt = { + id?: string; + projectId?: string; + environmentId?: string; + rawInputId?: string; + credentialId?: string; + attemptNumber?: number; + validatorVersion?: number; + startedAt?: string; + completedAt?: string; + outcome?: 'validated' | 'recorded_no_fact' | 'quarantined' | 'retryable_failure' | 'permanently_failed'; + retryable?: boolean; + failureCategory?: 'transient' | 'rate_limited' | 'auth' | 'quota' | 'not_found_retryable' | 'not_found_terminal' | 'invalid' | 'signature' | 'resolution' | 'configuration'; + /** + * Mosaic-owned stable code. + */ + diagnosticCode?: string; + /** + * Provider machine code + */ + providerCode?: string; + providerHttpStatus?: number; + storeEnvironment?: 'sandbox' | 'production' | 'unclassified'; + latencyMs?: number; + replayOfAttemptId?: string; + correlationId?: string; +}; + +export type BillingLedgerEntry = { + id?: string; + projectId?: string; + environmentId?: string; + entryType?: 'input_received' | 'input_authenticated' | 'input_duplicate_detected' | 'validation_started' | 'validation_succeeded' | 'validation_failed' | 'product_resolved' | 'product_resolution_failed' | 'fact_recorded' | 'fact_deduplicated' | 'input_quarantined' | 'quarantine_closed' | 'reconciliation_started' | 'reconciliation_discovery' | 'reconciliation_completed' | 'replay_started' | 'replay_completed' | 'revalidation_completed' | 'credential_health_changed'; + rawInputId?: string; + validationAttemptId?: string; + transactionFactId?: string; + credentialId?: string; + correlationId?: string; + occurredAt?: string; +}; + +/** + * An input that cannot safely proceed. The only status that follows a successful + * revalidation is closed_after_success, and it always names the attempt that justified + * it. There is no status, field, or action meaning an operator declared the input valid. + * + */ +export type QuarantineRecord = { + id?: string; + projectId?: string; + environmentId?: string; + rawInputId?: string; + applicationId?: string; + provider?: 'app_store' | 'google_play'; + /** + * Store Environment of the quarantined input, always distinct from the Mosaic + * Environment. Never absent: an input whose environment was not classified before it + * quarantined reports `unclassified` explicitly, because a missing value on an operator + * surface reads as production to a careless eye. + * + */ + storeEnvironment?: 'sandbox' | 'production' | 'unclassified'; + /** + * The store Product the quarantined input named, carried from the input's most recent + * resolution attempt. For the common `product_unknown` case it is the single most + * actionable field on the record: it is exactly what the operator has to create a mapping + * for. + * + */ + providerProductIdentifier?: string; + reasonCode?: 'signature_invalid' | 'application_mismatch' | 'environment_mismatch' | 'store_environment_mismatch' | 'credential_unavailable' | 'credential_revoked' | 'missing_validation_credential' | 'product_unknown' | 'product_ambiguous' | 'cross_environment_mismatch' | 'unsupported_product_type' | 'unsupported_transaction_type' | 'malformed_reference' | 'input_content_conflict' | 'replay_conflict' | 'provider_permanently_failed' | 'validation_exhausted'; + severity?: 'warning' | 'error' | 'security'; + scopes?: Array; + status?: 'open' | 'retrying' | 'closed_after_success' | 'closed_superseded'; attemptCount?: number; - maxAttempts?: number; - requestedAt?: string; - updatedAt?: string; + firstSeenAt?: string; + lastAttemptAt?: string; + /** + * The successful attempt that justified closure. + */ + closingAttemptId?: string; + supersededByRecordId?: string; + closedAt?: string; + diagnosticCode?: string; +}; + +export type ReconciliationRun = { + id?: string; + projectId?: string; + environmentId?: string; + credentialId?: string; + provider?: 'app_store' | 'google_play'; + trigger?: 'scheduled' | 'manual'; + strategy?: 'apple_notification_history' | 'apple_transaction_history' | 'google_token_requery'; + status?: 'queued' | 'leased' | 'completed' | 'partial' | 'failed'; + windowStart?: string; + windowEnd?: string; + examinedCount?: number; + discoveredCount?: number; + duplicateCount?: number; + /** + * Discoveries that contradicted a fact already on record, as distinct from discoveries + * that were merely new. Gate 9A requires reconciliation to detect missing *or* + * conflicting state; without a separate counter the two are indistinguishable. Nothing is + * overwritten — both facts stand — and each conflict also opens a quarantine record. + * + */ + conflictCount?: number; + failureCount?: number; + lastErrorCode?: string; + createdAt?: string; + startedAt?: string; completedAt?: string; }; -export type CreateExperimentRequest = { - placementId: string; - name: string; - hypothesis?: string; +export type CreateReconciliationRunRequest = { + credentialId: string; + provider: 'app_store' | 'google_play'; + /** + * apple_transaction_history is deliberately absent: the worker has no run loop for it, so + * accepting it produced a 202 followed by a run that failed with `unsupported_strategy` + * and no explanation anywhere in the product. It remains in the stored enumeration for + * forward compatibility and is rejected at the API boundary until the loop exists. + * + */ + strategy: 'apple_notification_history' | 'google_token_requery'; + windowStart: string; + /** + * The window may not exceed 180 days + */ + windowEnd: string; +}; + +export type ReplayJob = { + id?: string; + projectId?: string; + environmentId?: string; + kind?: 'replay' | 'revalidation'; + rawInputId?: string; + windowStart?: string; + windowEnd?: string; + validatorVersion?: number; + status?: 'queued' | 'leased' | 'completed' | 'failed'; + comparisonResult?: 'identical' | 'new_facts' | 'conflicting' | 'still_failing'; + examinedCount?: number; + unchangedCount?: number; + newFactCount?: number; + conflictCount?: number; + lastErrorCode?: string; + createdAt?: string; + completedAt?: string; +}; + +/** + * Supply either a single rawInputId or a bounded window, never both. + */ +export type CreateReplayJobRequest = { + kind: 'replay' | 'revalidation'; + rawInputId?: string; + windowStart?: string; + windowEnd?: string; + validatorVersion?: number; +}; + +export type CustomerAccessTokenIssuanceRequest = { + customerAccessTokenContractVersion: '1'; + recordType: 'customerAccessTokenIssuanceRequest'; + payload: { + billingCustomerId: string; + /** + * Only sdk_sync is issued in Phase 9B. + */ + audience: 'sdk_sync' | 'server_check'; + scopes: Array<'entitlements.read' | 'entitlements.sync' | 'restore.request'>; + /** + * A request, not an instruction. Clamped to the contract maximum. + */ + requestedTtlSeconds?: number; + correlationId: string; + }; +}; + +export type CustomerAccessTokenIssuanceResult = { + customerAccessTokenContractVersion: '1'; + recordType: 'customerAccessTokenIssuanceResult'; + payload: { + /** + * The opaque credential. Returned exactly once; Mosaic stores only its SHA-256 digest and can never reproduce it. + */ + token: string; + metadata: CustomerAccessTokenMetadata; + correlationId: string; + }; +}; + +/** + * Everything Mosaic knows about a token, which is deliberately everything the token itself does not carry. + */ +export type CustomerAccessTokenMetadata = { + /** + * A stable public handle, safe to log and to name in an audit event. It is not the token and cannot be presented as one. + */ + tokenId: string; + projectId: string; + environmentId: string; + billingCustomerId: string; + audience: 'sdk_sync' | 'server_check'; + scopes: Array<'entitlements.read' | 'entitlements.sync' | 'restore.request'>; + issuer: string; + issuedAt: string; + expiresAt: string; + status: 'active' | 'expired' | 'revoked'; + revokedAt?: string; + revocationReason?: 'customer_signed_out' | 'identity_changed' | 'operator_revoked' | 'customer_deleted' | 'key_rotated' | 'suspected_compromise' | 'superseded_by_new_token'; + lastUsedAt?: string; + tokenPrefix: 'mcat_'; + digestAlgorithm: 'sha256'; +}; + +export type EntitlementSyncRequestRecord = { + authoritativeEntitlementContractVersion: '1'; + recordType: 'entitlementSyncRequest'; + payload: { + /** + * A hint only. Verified against the token; a mismatch is refused. + */ + billingCustomerId?: string; + knownSnapshotVersion?: number; + entityTag?: string; + supportedAuthoritativeEntitlementContracts: Array<'1'>; + requestedEntitlementKeys?: Array; + correlationId: string; + }; +}; + +export type AuthorityEntitlementSyncRequestRecord = { + authoritativeEntitlementContractVersion: '2'; + recordType: 'entitlementSyncRequest'; + payload: { + knownAuthorityEpoch?: number; + knownSnapshotVersion?: number; + knownSnapshotAuthorityDigest?: string; + request: { + applicationId: string; + platform: 'ios' | 'android'; + appVersion: string; + sdkVersion: string; + supportedContractVersions: Array<'1' | '2'>; + capabilities: Array<'authority_epoch' | 'authority_scope' | 'urgent_authority_sync' | 'mosaic_authoritative_targeting'>; + }; + }; +}; + +export type EntitlementSyncRequestUnion = ({ + authoritativeEntitlementContractVersion: '1'; +} & EntitlementSyncRequestRecord) | ({ + authoritativeEntitlementContractVersion: '2'; +} & AuthorityEntitlementSyncRequestRecord); + +export type BillingAuthorityScope = { + projectId: string; + environmentId: string; + applicationId: string; + platform: 'ios' | 'android'; +}; + +export type BillingAuthority = { + authorityEpoch: number; + authorityKind: 'source' | 'mosaic' | 'source_rollback'; + scope: BillingAuthorityScope; + transitionState: 'stable' | 'cutover_pending' | 'stabilizing' | 'rolled_back'; + cutoverAt?: string; +}; + +export type BillingAuthorityMinimumSupport = { + minimumContractVersion: '2'; + minimumSdkVersion: string; + supportedAppVersionWindow: { + minimumInclusive: string; + maximumInclusive?: string; + }; + requiredCapabilities: Array<'authority_epoch' | 'authority_scope' | 'urgent_authority_sync' | 'mosaic_authoritative_targeting'>; +}; + +export type AuthoritySnapshotPayloadV1 = { + snapshotId: string; + billingCustomerId: string; + projectId: string; + environmentId: string; + snapshotVersion: number; + previousSnapshotVersion?: number; + projectionRuleVersion: number; + issuedAt: string; + asOf: string; + refreshAfter: string; + validUntil: string; + staleGraceSeconds?: number; + entityTag: string; + contentDigest: string; + entries: Array; + sources: Array; + projectionStatus: ProjectionStatus; + changeReason: string; + correlationId: string; +}; + +export type AuthorityUnchangedPayloadV1 = { + billingCustomerId: string; + projectId: string; + environmentId: string; + snapshotVersion: number; + entityTag: string; + issuedAt: string; + asOf: string; + refreshAfter: string; + validUntil: string; + staleGraceSeconds?: number; + projectionStatus: ProjectionStatus; + correlationId: string; +}; + +export type AuthorityCustomerEntitlementSnapshotRecord = { + authoritativeEntitlementContractVersion: '2'; + recordType: 'customerEntitlementSnapshot'; + payload: { + authority: BillingAuthority; + snapshot: AuthoritySnapshotPayloadV1; + snapshotAuthorityDigest: string; + minimumSupport: BillingAuthorityMinimumSupport; + }; +}; + +export type AuthoritySnapshotUnchangedRecord = { + authoritativeEntitlementContractVersion: '2'; + recordType: 'snapshotUnchanged'; + payload: { + authority: BillingAuthority; + unchanged: AuthorityUnchangedPayloadV1; + snapshotAuthorityDigest: string; + minimumSupport: BillingAuthorityMinimumSupport; + }; +}; + +export type AuthorityUnavailableRecord = { + authoritativeEntitlementContractVersion: '2'; + recordType: 'authorityUnavailable'; + payload: unknown & { + scope: BillingAuthorityScope; + result: 'unavailable'; + reason: 'authority_unknown' | 'unsupported_contract' | 'unsupported_app_version' | 'scope_mismatch' | 'policy_unavailable'; + minimumSupport?: BillingAuthorityMinimumSupport; + }; +}; + +export type AuthoritativeEntitlementSyncResponse = CustomerEntitlementSnapshotRecord | AuthorityCustomerEntitlementSnapshotRecord | AuthoritySnapshotUnchangedRecord | AuthorityUnavailableRecord; + +/** + * The Authoritative Entitlement Contract v1 restoreRequest envelope. The normative shape is + * protocol/schema/authoritative-entitlement/v1/restore.schema.json; this declaration exists + * so the operation has a resolvable request schema and is deliberately not a second source + * of truth for the contract. + * + */ +export type RestoreRequestRecord = { + authoritativeEntitlementContractVersion: '1'; + recordType: 'restoreRequest'; + payload: { + storePlatform: 'apple_app_store' | 'google_play'; + /** + * What the native restore itself did. Recorded verbatim and never merged into Mosaic's own outcome. + */ + providerOutcome: 'completed' | 'no_purchases_found' | 'cancelled' | 'failed' | 'unsupported' | 'not_attempted'; + /** + * Observations already submitted for this restore. No provider transaction reference travels on this surface. + */ + observationSubmissionIds?: Array; + /** + * Honoured on the trusted surface only. The SDK surface drops it. + */ + billingCustomerId?: string; + correlationId: string; + }; +}; + +/** + * The Authoritative Entitlement Contract v1 restoreResult envelope. Normative shape: + * protocol/schema/authoritative-entitlement/v1/restore.schema.json. + * + */ +export type RestoreResultRecord = { + authoritativeEntitlementContractVersion: '1'; + recordType: 'restoreResult'; + payload: { + restoreId?: string; + /** + * Absent while identity is unresolved + */ + billingCustomerId?: string; + projectId?: string; + environmentId?: string; + storePlatform?: 'apple_app_store' | 'google_play'; + /** + * Mosaic's own answer. `restored` is only ever written together with the accepted snapshot version that demonstrates it. + */ + outcome?: 'restored' | 'no_additional_purchases' | 'validation_pending' | 'identity_unresolved' | 'product_unresolved' | 'provider_unavailable' | 'failed'; + providerOutcome?: 'completed' | 'no_purchases_found' | 'cancelled' | 'failed' | 'unsupported' | 'not_attempted'; + /** + * The accepted snapshot that reflects the restore. Present only for `restored` + */ + snapshotVersion?: number; + /** + * Inputs actually linked + */ + observedTransactionCount?: number; + pendingValidationCount?: number; + uncertainty?: { + reason?: 'none' | 'provider_unavailable' | 'missing_fact' | 'identity_unresolved' | 'product_unresolved' | 'conflicting_facts' | 'projection_failed' | 'stale_validation' | 'unsupported_provider_state'; + since?: string; + diagnosticCode?: string; + }; + requestedAt?: string; + completedAt?: string; + correlationId?: string; + }; +}; + +/** + * The Authoritative Entitlement Contract v1 customerEntitlementSnapshot envelope. The + * normative shape is protocol/schema/authoritative-entitlement/v1/snapshot.schema.json; + * this declaration exists so the operation has a resolvable response schema and is + * deliberately not a second source of truth for the contract. + * + */ +export type CustomerEntitlementSnapshotRecord = { + authoritativeEntitlementContractVersion: '1'; + recordType: 'customerEntitlementSnapshot' | 'snapshotUnchanged'; + payload: { + snapshotId?: string; + billingCustomerId?: string; + projectId?: string; + environmentId?: string; + /** + * Monotonic per (customer + */ + snapshotVersion?: number; + previousSnapshotVersion?: number; + projectionRuleVersion?: number; + issuedAt?: string; + asOf?: string; + refreshAfter?: string; + validUntil?: string; + staleGraceSeconds?: number; + entityTag?: string; + contentDigest?: string; + entries?: Array; + sources?: Array; + projectionStatus?: ProjectionStatus; + changeReason?: string; + correlationId?: string; + }; +}; + +/** + * Product and Subscription Instance identity are deliberately absent; they live on the contributing source summaries, which sourceIds resolves against. + */ +export type EntitlementEntry = { + entitlementId: string; + entitlementKey: string; + /** + * `unavailable` is deliberately absent: it describes Mosaic's ability to answer, never the customer's access. + */ + state: 'active' | 'inactive' | 'unknown'; + effectiveStart?: string; + effectiveEnd?: string; + /** + * False means an active source has an uncertain end + */ + endKnown: boolean; + sourceIds: Array; + sourceCount: number; + primaryExplanation: PrimaryExplanation; + uncertainty?: Uncertainty; +}; + +export type EntitlementSourceSummary = { + /** + * Derived from (purchase lineage + */ + sourceId: string; + sourceType: 'active_subscription' | 'trial' | 'grace_period' | 'billing_retry' | 'one_time_non_consumable' | 'family_shared'; + subscriptionInstanceId?: string; + oneTimePurchaseInstanceId?: string; + mosaicProductId: string; + grantVersionId: string; + sourceSnapshotId: string; + storePlatform?: 'apple_app_store' | 'google_play'; + start: string; + /** + * Absent means this source has no finite end Mosaic can state. + */ + end?: string; + sourceState: 'granting' | 'not_granting' | 'unknown'; + uncertainty: Uncertainty; + explanationCode: string; + /** + * True for an Apple sandbox transaction or a Google Play license-tester purchase + */ + isTestSource: boolean; +}; + +export type ProjectionStatus = { + state: 'current' | 'pending' | 'stale' | 'degraded' | 'failed'; + lastProjectedAt: string; + pendingFactCount?: number; + diagnosticCode?: string; +}; + +/** + * Why a state is not definitive. reason `none` means the state is definitive, and a definitive state carries no since instant. + */ +export type Uncertainty = { + reason: 'none' | 'provider_unavailable' | 'missing_fact' | 'identity_unresolved' | 'product_unresolved' | 'conflicting_facts' | 'projection_failed' | 'stale_validation' | 'unsupported_provider_state'; + since?: string; + expectedResolution?: 'automatic_retry' | 'next_provider_notification' | 'next_projection_run' | 'operator_action' | 'customer_action' | 'none_expected'; + diagnosticCode?: string; +}; + +export type PrimaryExplanation = { + /** + * A member of the contract's closed explanation vocabulary. A reader may render its own copy for a code but must never invent one. + */ + code: string; + sourceId?: string; + safeSummary?: string; +}; + +export type EntitlementCheckRequestRecord = { + authoritativeEntitlementContractVersion: '1'; + recordType: 'entitlementCheckRequest'; + payload: { + billingCustomerId: string; + entitlementKeys: Array; + expectedSnapshotVersion?: number; + supportedAuthoritativeEntitlementContracts: Array<'1'>; + correlationId: string; + }; +}; + +export type EntitlementCheckResultRecord = { + authoritativeEntitlementContractVersion: '1'; + recordType: 'entitlementCheckResult'; + payload: { + billingCustomerId: string; + projectId: string; + environmentId: string; + /** + * Absent only when no snapshot could be read at all + */ + snapshotVersion?: number; + projectionRuleVersion?: number; + issuedAt: string; + asOf?: string; + results: Array<{ + entitlementKey: string; + /** + * This is the only place `unavailable` is admissible for an Entitlement: it says Mosaic could not answer, not that the customer lacks access. + */ + state: 'active' | 'inactive' | 'unknown' | 'unavailable'; + effectiveStart?: string; + effectiveEnd?: string; + endKnown: boolean; + sourceCount: number; + sourceIds?: Array; + primaryExplanation: PrimaryExplanation; + uncertainty?: Uncertainty; + isTestSource?: boolean; + }>; + projectionStatus?: ProjectionStatus; + correlationId: string; + }; +}; + +/** + * The Authoritative Entitlement Contract v1 subscriptionSnapshot envelope. The normative + * shape is protocol/schema/authoritative-entitlement/v1/subscription.schema.json. + * + */ +export type SubscriptionSnapshotRecord = { + authoritativeEntitlementContractVersion: '1'; + recordType: 'subscriptionSnapshot'; + payload: { + subscriptionSnapshotId?: string; + subscriptionInstanceId?: string; + purchaseLineageId?: string; + billingCustomerId?: string; + projectId?: string; + environmentId?: string; + projectionVersion?: number; + projectionRuleVersion?: number; + computedAt?: string; + asOf?: string; + storePlatform?: 'apple_app_store' | 'google_play'; + mosaicProductId?: string; + priorMosaicProductId?: string; + accessState?: 'active' | 'inactive' | 'unknown' | 'unavailable'; + lifecycleState?: 'trialing' | 'active' | 'grace_period' | 'billing_retry' | 'paused' | 'expired' | 'revoked' | 'refunded' | 'superseded' | 'unknown'; + renewalIntent?: 'auto_renew_enabled' | 'auto_renew_disabled' | 'provider_managed' | 'paused' | 'unknown'; + billingState?: 'current' | 'retrying' | 'grace' | 'failed' | 'refunded' | 'revoked' | 'unknown'; + uncertainty?: Uncertainty; + periodStart?: string; + periodEnd?: string; + gracePeriodEnd?: string; + billingRetryStart?: string; + pauseEffectiveAt?: string; + pauseResumeAt?: string; + /** + * When the provider recorded the cancellation. It changes renewalIntent; it does not end access. + */ + cancellationEffectiveAt?: string; + expirationEffectiveAt?: string; + revocationEffectiveAt?: string; + refundEffectiveAt?: string; + supersededBySubscriptionInstanceId?: string; + isTestSource?: boolean; + sourceFactCount?: number; + checksum?: string; + changeReason?: string; + explanationCode?: string; + correlationId?: string; + }; +}; + +export type SubscriptionSummary = { + subscriptionInstanceId?: string; + subscriptionSnapshotId?: string; + projectionVersion?: number; + accessState?: 'active' | 'inactive' | 'unknown'; + lifecycleState?: string; + renewalIntent?: string; + billingState?: string; + isTestSource?: boolean; + asOf?: string; +}; + +export type SubscriptionTimelineEntry = { + timelineEntryId?: string; + entryType?: string; + effectiveAt?: string; + observedAt?: string; + explanationCode?: string; + mosaicProductId?: string; + detail?: { + [key: string]: string; + }; +}; + +/** + * A Billing Customer as the trusted-server API reports it. Alias values never appear: aliases are stored as digests. + */ +export type BillingCustomer = { + billingCustomerId?: string; + projectId?: string; + status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; + diagnosticsStatus?: 'none' | 'identity_conflict' | 'projection_stale' | 'projection_failed'; + currentProjectionVersion?: number; + lastProjectedAt?: string; + /** + * True when an application backend has named this customer. False means purchase-anchored but never identified. + */ + identified?: boolean; + createdAt?: string; + updatedAt?: string; +}; + +/** + * Per-Project Mosaic Billing configuration. Off by default. + */ +export type BillingSettings = { + projectId?: string; + billingEnabled?: boolean; + /** + * Active Store Server Credentials. Disabling is refused while this is above zero. + */ + activeCredentialCount?: number; + /** + * Whether a disable would be accepted right now. False while any Store Server Credential + * is active: revoking the credential is what actually stops the store delivering. + * + */ + canDisable?: boolean; + updatedAt?: string; +}; + +export type BillingHealth = { + environmentId?: string; + billingEnabled?: boolean; + credentialCount?: number; + unhealthyCredentials?: number; + queueDepth?: number; + oldestQueuedAgeSeconds?: number; + openQuarantineCount?: number; + factCount?: number; + lastFactRecordedAt?: string; + lastReconciliationAt?: string; +}; + +export type IdentifyBillingCustomerRequest = { + /** + * Your own identifier for the user. Stored only as a domain-separated SHA-256 digest. + */ + applicationUserId: string; +}; + +export type AttachBillingCustomerAliasRequest = { + applicationUserId: string; +}; + +export type BillingIdentityCustomer = { + billingCustomerId?: string; + projectId?: string; + status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; + diagnosticsStatus?: string; + currentProjectionVersion?: number; + createdAt?: string; + updatedAt?: string; + lastProjectedAt?: string; +}; + +/** + * An alias record. The alias digest is never a member of this shape. + */ +export type BillingCustomerAlias = { + aliasId?: string; + billingCustomerId?: string; + aliasType?: 'application_user_id' | 'installation_id' | 'apple_app_account_token' | 'google_obfuscated_account_id'; + sourceAuthority?: string; + verificationStatus?: string; + effectiveStart?: string; + effectiveEnd?: string; + createdAt?: string; +}; + +export type BillingIdentityConflict = { + conflictId?: string; + projectId?: string; + scope?: 'lineage' | 'alias'; + status?: 'open' | 'resolved'; + /** + * The incumbent. + */ + firstCustomerId?: string; + /** + * The challenger. + */ + secondCustomerId?: string; + /** + * Present only for a lineage-scoped conflict. + */ + purchaseLineageId?: string; + /** + * Present only for an alias-scoped conflict. The digest is never reported. + */ + aliasType?: string; + diagnosticCode?: string; + openedAt?: string; + resolvedAt?: string; + resolutionAction?: 'assigned_first' | 'assigned_second' | 'detached_both'; +}; + +export type BillingIdentityConflictDetail = { + conflict?: BillingIdentityConflict; + /** + * The disputed Purchase Lineage, for a lineage-scoped conflict. + */ + lineage?: { + purchaseLineageId?: string; + environmentId?: string; + provider?: 'app_store' | 'google_play'; + storeEnvironment?: 'sandbox' | 'production'; + lineageType?: 'subscription' | 'one_time'; + projectionFrozen?: boolean; + diagnosticStatus?: string; + }; +}; + +export type BillingSyncRequest = { + billingCustomerId?: string; + projectId?: string; + environmentId?: string; + projectionScopeKey?: string; + triggerKind?: 'manual_sync'; + requestedAt?: string; + status?: 'queued'; +}; + +/** + * Must be bounded by at least one of subscriptionInstanceId, billingCustomerId, or a complete window. + */ +export type CreateProjectionReplayRequest = { + subscriptionInstanceId?: string; + billingCustomerId?: string; + /** + * Bounds on facts rather than on lineage creation - a scope is in scope when it holds a fact whose effective or recorded time falls inside the window. + */ + windowStart?: string; + windowEnd?: string; + /** + * Zero selects the active version. A version this build does not derive under is refused rather than approximated. + */ + projectionRuleVersion?: number; + limit?: number; +}; + +export type ProjectionReplayResult = { + projectionRuleVersion?: number; + scopesReplayed?: number; + scopesChanged?: number; + outcomes?: Array<{ + projectionScopeKey?: string; + /** + * The whole point of a replay - proving determinism + */ + comparison?: 'unchanged' | 'changed'; + /** + * Whether a new snapshot was written. Materialization is changes-only + */ + materialized?: boolean; + changedEntitlementIds?: Array; + }>; +}; + +/** + * One immutable Product-to-Entitlement grant interval. Half-open - [effectiveStart, effectiveEnd). + */ +export type ProductEntitlementGrantVersion = { + grantVersionId?: string; + projectId?: string; + productId?: string; + productKey?: string; + entitlementId?: string; + entitlementKey?: string; + /** + * Monotonic per (Product + */ + version?: number; + /** + * The access-policy vocabulary this version was written under + */ + grantPolicyVersion?: number; + effectiveStart?: string; + /** + * Absent on the current version. Set once + */ + effectiveEnd?: string; + current?: boolean; + /** + * Derived from effectiveStart preceding createdAt - a version whose meaning began before it existed was backdated. + */ + retroactive?: boolean; + supportedPurchaseTypes?: Array<'auto_renewable_subscription' | 'non_consumable'>; + accessPolicy?: GrantAccessPolicy; + createdAt?: string; + createdByActorId?: string; + reason?: string; +}; + +/** + * Which subscription states this grant treats as granting access (plan policy version 1). + */ +export type GrantAccessPolicy = { + grantsInActive?: boolean; + grantsInTrial?: boolean; + /** + * Both providers grant access during grace + */ + grantsInGrace?: boolean; + /** + * Contradicts both providers' documentation. Closed by default and settable only by an organization owner. + */ + grantsInBillingRetry?: boolean; + /** + * Always false. Google's pause never grants access and the policy is not overridable. + */ + grantsInPaused?: boolean; + grantsInOneTimeOwnership?: boolean; +}; + +/** + * A proposed grant version. The same body is accepted by the impact preview and by publish, + * so what is previewed is what is published. Omitted policy flags take their documented + * defaults rather than false - a missing grantsInActive defaulting to false would publish a + * version granting nothing during an active subscription, the opposite of what an operator + * leaving the field out meant. + * + */ +export type PublishGrantVersionRequest = { + productId: string; + entitlementId: string; + /** + * Now or later unless retroactive is true. + */ + effectiveStart: string; + /** + * Backdate the change. Held to the additive-superset rule and confined to the currently open interval. + */ + retroactive?: boolean; + supportedPurchaseTypes?: Array<'auto_renewable_subscription' | 'non_consumable'>; + grantsInActive?: boolean; + grantsInTrial?: boolean; + grantsInGrace?: boolean; + grantsInBillingRetry?: boolean; + /** + * Accepted only so it can be refused with 422. + */ + grantsInPaused?: boolean; + grantsInOneTimeOwnership?: boolean; + /** + * Required to publish. It is what an investigation reads months later + */ + reason?: string; +}; + +/** + * Read-only counts of what a proposed grant change would touch, from current committed state. + */ +export type GrantVersionImpact = { + productId?: string; + entitlementId?: string; + /** + * Products a reprojection of the affected customers would re-derive. + */ + impactedProducts?: number; + /** + * Entitlements a reprojection of the affected customers would re-derive. + */ + impactedEntitlements?: number; + /** + * Billing Customers whose current snapshot cites this Product. + */ + impactedCustomers?: number; + /** + * How many of those citations are currently granting access - the number that answers how many people could lose access. + */ + impactedActiveSources?: number; + /** + * Purchase lineages resolved to this Product + */ + impactedLineages?: number; + retroactive?: boolean; + /** + * Whether the proposal passes the widen-only rule. Meaningful for a retroactive change; a preview reports it + */ + additiveSuperset?: boolean; + /** + * The first narrowing found when additiveSuperset is false + */ + narrowingCode?: string; + currentVersion?: ProductEntitlementGrantVersion; + observedAt?: string; +}; + +export type WebhookDestination = { + id?: string; + projectId?: string; + environmentId?: string; + /** + * HTTPS only. Screened against the resolved address at registration and again on every delivery attempt. + */ + url?: string; + status?: 'active' | 'paused' | 'disabled'; + /** + * Phase 9B emits one event type. The other nine the contract declares are reserved names. + */ + eventTypes?: Array<'customer.entitlements.changed'>; + description?: string; + createdAt?: string; + updatedAt?: string; + secretLastRotatedAt?: string; + /** + * What an operator wrote when they disabled it by hand. + */ + disabledReason?: string; + /** + * Exhausted deliveries in a row. Any success resets it + */ + consecutiveFailureCount?: number; + autoDisabledAt?: string; + /** + * Set only by the automatic path + */ + autoDisableReason?: 'consecutive_exhausted_deliveries' | 'destination_refused'; +}; + +export type WebhookDestinationWithSecret = { + destination?: WebhookDestination; + /** + * Displayed exactly once. Mosaic keeps only the sealed form + */ + secret?: string; + secretId?: string; + /** + * Set by a rotation. Until this instant the superseded secret still signs. Absent on a create + */ + previousSecretHonoredUntil?: string; +}; + +export type WebhookSigningSecretMetadata = { + id?: string; + status?: 'active' | 'retired'; + createdAt?: string; + retiredAt?: string; + /** + * A retired secret keeps signing until this instant + */ + honoredUntil?: string; +}; + +export type CreateWebhookDestinationRequest = { + url: string; + eventTypes?: Array<'customer.entitlements.changed'>; + description?: string; +}; + +export type UpdateWebhookDestinationRequest = { + url?: string; + eventTypes?: Array<'customer.entitlements.changed'>; + description?: string; +}; + +export type SetWebhookDestinationStatusRequest = { + status: 'active' | 'paused' | 'disabled'; + reason?: string; +}; + +export type WebhookDelivery = { + id?: string; + projectId?: string; + environmentId?: string; + /** + * Stable across every attempt and every manual replay. It is the consumer's deduplication key. + */ + eventId?: string; + destinationId?: string; + status?: 'pending' | 'succeeded' | 'failed' | 'exhausted' | 'skipped'; + skippedReason?: 'destination_disabled' | 'event_type_not_enabled' | 'destination_deleted' | 'tenant_suspended'; + attemptCount?: number; + maxAttempts?: number; + nextAttemptAt?: string; + createdAt?: string; + updatedAt?: string; + completedAt?: string; +}; + +/** + * One recorded try. Mirrors the Billing State Webhook Contract v1 webhookDeliveryAttempt + * record; the normative shape is protocol/schema/billing-state-webhook/v1/delivery.schema.json. + * + */ +export type WebhookDeliveryAttempt = { + id?: string; + deliveryId?: string; + eventId?: string; + destinationId?: string; + attempt?: number; + maxAttempts?: number; + outcome?: 'delivered' | 'retryable_failure' | 'permanent_failure' | 'exhausted' | 'skipped'; + responseStatusCode?: number; + /** + * Bounded and control-character-free. Never parsed + */ + responseExcerpt?: string; + errorCode?: string; + latencyMs?: number; + skippedReason?: 'destination_disabled' | 'event_type_not_enabled' | 'destination_deleted' | 'tenant_suspended'; + requestedAt?: string; + respondedAt?: string; + nextAttemptAt?: string; +}; + +export type BillingProjectionHealth = { + environmentId?: string; + billingEnabled?: boolean; + /** + * The rule version new projections are computed under. + */ + activeProjectionRuleVersion?: number; + /** + * A count above one with no replay in flight means a promotion was prepared and never run. + */ + projectionRuleVersionCount?: number; + projectionQueueDepth?: number; + /** + * The alerting signal. Depth alone cannot distinguish a busy queue from a stuck one. + */ + projectionOldestQueuedAgeSeconds?: number; + projectionFailedJobs?: number; + /** + * A rate signal the queue depth cannot give + */ + projectionFailuresLastHour?: number; + /** + * Customers whose committed state in this Environment is older than the staleness threshold. + */ + staleCustomers?: number; + neverProjectedCustomers?: number; + /** + * A spike here is a security-relevant signal + */ + openIdentityConflicts?: number; + frozenLineages?: number; + unresolvedLineages?: number; + /** + * Entries on current snapshots stating unknown - how often Mosaic is declining to answer. + */ + unknownEntitlementEntries?: number; + restoreBacklog?: number; + restoreFailedJobs?: number; + webhookDeliveryBacklog?: number; + webhookDeliveriesExhausted?: number; + activeWebhookDestinations?: number; + lastProjectionCommittedAt?: string; + observedAt?: string; +}; + +/** + * One Billing Customer as the operator list and the detail header report it. It carries no + * alias value and no alias digest, because Mosaic exposes neither on any surface. + * + */ +export type BillingCustomerSummary = { + billingCustomerId?: string; + projectId?: string; + environmentId?: string; + status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; + diagnosticsStatus?: 'none' | 'identity_conflict' | 'projection_stale' | 'projection_failed'; + /** + * An active application-user alias exists - a person is attached. + */ + identified?: boolean; + /** + * A purchase lineage exists in this Environment - revenue is attached. + */ + purchaseAnchored?: boolean; + hasOpenIdentityConflict?: boolean; + frozenLineageCount?: number; + currentProjectionVersion?: number; + lastProjectedAt?: string; + /** + * Absent when the customer has never been projected in this Environment, which is not the same as having no entitlements. + */ + snapshotVersion?: number; + snapshotUpdatedAt?: string; + createdAt?: string; + updatedAt?: string; +}; + +export type BillingCustomerLookupRequest = { + identifierType: 'billing_customer_id' | 'application_user_id' | 'installation_id'; + /** + * The raw identifier. It is digested server-side and is never stored, never logged, and never echoed back. + */ + identifierValue: string; +}; + +export type BillingCustomerLookupResult = { + found?: boolean; + customer?: BillingCustomerSummary; +}; + +/** + * One accepted external identity bound to a customer, as a protected representation. There + * is no value field and no digest field: the alias id identifies the row for a revocation + * and reveals nothing about the person, whereas an alias digest is still a stable + * per-person identifier. + * + */ +export type OperatorBillingCustomerAlias = { + aliasId?: string; + aliasType?: 'application_user_id' | 'installation_id' | 'apple_app_account_token' | 'google_obfuscated_account_id'; + sourceAuthority?: 'trusted_server' | 'sdk_installation' | 'provider_payload' | 'operator' | 'restore'; + verificationStatus?: 'asserted' | 'verified'; + active?: boolean; + effectiveStart?: string; + effectiveEnd?: string; +}; + +/** + * One provider purchase chain. Supersession is an explicit edge; nothing is ever deleted. + */ +export type BillingPurchaseLineage = { + purchaseLineageId?: string; + environmentId?: string; + provider?: 'app_store' | 'google_play'; + storeEnvironment?: 'sandbox' | 'production'; + lineageType?: 'subscription' | 'one_time'; + /** + * Set while an identity conflict is open - the projector skips it and the last committed state is preserved. + */ + projectionFrozen?: boolean; + diagnosticStatus?: 'none' | 'identity_unresolved' | 'identity_conflict' | 'product_unresolved'; + supersededByLineageId?: string; + createdAt?: string; + updatedAt?: string; +}; + +/** + * Validated ownership of a non-consumable. Consumables are excluded from Mosaic Billing. + */ +export type BillingOneTimePurchase = { + oneTimePurchaseInstanceId?: string; + purchaseLineageId?: string; + provider?: 'app_store' | 'google_play'; + mosaicProductId?: string; + providerProductIdentifier?: string; + acquiredAt?: string; + validityState?: 'owned' | 'refunded' | 'revoked' | 'unknown'; + refundEffectiveAt?: string; + revocationEffectiveAt?: string; +}; + +export type BillingProjectionStatus = { + state?: 'current' | 'pending' | 'stale' | 'degraded' | 'failed'; + lastProjectedAt?: string; + pendingFactCount?: number; + diagnosticCode?: string; +}; + +export type BillingEntitlementSnapshotEntry = { + entitlementId?: string; + entitlementKey?: string; + /** + * unavailable is a read-time service state and is never persisted on a snapshot. + */ + state?: 'active' | 'inactive' | 'unknown'; + effectiveStart?: string; + effectiveEnd?: string; + endKnown?: boolean; + sourceCount?: number; + uncertaintyReason?: 'none' | 'provider_unavailable' | 'missing_fact' | 'identity_unresolved' | 'product_unresolved' | 'conflicting_facts' | 'projection_failed' | 'stale_validation' | 'unsupported_provider_state'; + isTestSource?: boolean; + explanationCode?: string; + sourceIds?: Array; +}; + +/** + * One reason the customer holds, or may hold, an Entitlement. Source identity is + * (purchase lineage, Mosaic Product, grant version) and never a fact id, so multiple facts + * describing one purchase cannot double-grant. + * + */ +export type BillingEntitlementSource = { + sourceId?: string; + entitlementId?: string; + purchaseLineageId?: string; + mosaicProductId?: string; + grantVersionId?: string; + subscriptionInstanceId?: string; + oneTimePurchaseInstanceId?: string; + storePlatform?: string; + sourceType?: string; + sourceState?: string; + sourceStart?: string; + sourceEnd?: string; + endKnown?: boolean; + uncertaintyReason?: string; + isTestSource?: boolean; + explanationCode?: string; +}; + +export type BillingEntitlementSnapshot = { + snapshotId?: string; + /** + * Per-customer monotonic. It is the sole cache-monotonicity key; a no-change projection does not advance it. + */ + snapshotVersion?: number; + previousSnapshotVersion?: number; + projectionRuleVersion?: number; + computedAt?: string; + asOf?: string; + changeReason?: string; + entries?: Array; + sources?: Array; +}; + +/** + * One projected Subscription Instance. Access and lifecycle are separate axes on purpose: + * a cancelled subscription keeps access until its validated period end, so cancellation + * moves renewal intent and nothing else. + * + */ +export type BillingSubscriptionSnapshot = { + subscriptionInstanceId?: string; + purchaseLineageId?: string; + billingCustomerId?: string; + environmentId?: string; + storePlatform?: string; + mosaicProductId?: string; + priorMosaicProductId?: string; + /** + * unavailable is a read-time service state and is never persisted on a snapshot. + */ + accessState?: 'active' | 'inactive' | 'unknown'; + lifecycleState?: 'trialing' | 'active' | 'grace_period' | 'billing_retry' | 'paused' | 'expired' | 'revoked' | 'refunded' | 'superseded' | 'unknown'; + renewalIntent?: string; + billingState?: string; + uncertaintyReason?: string; + projectionVersion?: number; + projectionRuleVersion?: number; + computedAt?: string; + asOf?: string; + periodStart?: string; + periodEnd?: string; + gracePeriodEnd?: string; + billingRetryStart?: string; + pauseEffectiveAt?: string; + pauseResumeAt?: string; + cancellationEffectiveAt?: string; + expirationEffectiveAt?: string; + revocationEffectiveAt?: string; + refundEffectiveAt?: string; + supersededBySubscriptionInstanceId?: string; + isTestSource?: boolean; + sourceFactCount?: number; + changeReason?: string; + explanationCode?: string; +}; + +export type BillingTimelineEntry = { + timelineEntryId?: string; + entryType?: string; + effectiveAt?: string; + observedAt?: string; + subscriptionInstanceId?: string; + mosaicProductId?: string; + priorMosaicProductId?: string; + explanationCode?: string; + /** + * Passed through the ledger guard function, so no provider token or raw payload fragment can appear here. + */ + detail?: { + [key: string]: string; + }; +}; + +export type BillingCustomerDetail = { + customer?: BillingCustomerSummary; + aliases?: Array; + purchaseLineages?: Array; + subscriptions?: Array; + oneTimePurchases?: Array; + identityConflicts?: Array; + currentSnapshot?: BillingEntitlementSnapshot; + projectionStatus?: BillingProjectionStatus; +}; + +/** + * One disputed association held open for operator resolution. Access is granted to neither + * candidate while it is open: the disputed subject is frozen and the last committed state is + * preserved. The disputed alias digest is never part of this shape. + * + */ +export type OperatorBillingIdentityConflict = { + conflictId?: string; + projectId?: string; + scope?: 'lineage' | 'alias'; + status?: 'open' | 'resolved'; + purchaseLineageId?: string; + aliasType?: string; + /** + * The incumbent. + */ + firstCustomerId?: string; + /** + * The challenger the evidence proposed. + */ + secondCustomerId?: string; + diagnosticCode?: 'multiple_customers_claim_lineage' | 'reassignment_requires_operator_resolution' | 'application_user_alias_claims_two_customers'; + openedAt?: string; + resolvedAt?: string; + resolutionAction?: 'keep_existing' | 'reassign_to_candidate' | 'operator_split'; + resolutionReason?: string; +}; + +export type OperatorBillingIdentityConflictDetail = { + conflict?: OperatorBillingIdentityConflict; + lineage?: BillingPurchaseLineage; +}; + +export type ResolveIdentityConflictRequest = { + action: 'keep_existing' | 'reassign_to_candidate' | 'operator_split'; + /** + * Optional. When present it must name the party the action already implies. + */ + assignedBillingCustomerId?: string; + /** + * Required. Recorded on the conflict and on the audit event. + */ + reason: string; +}; + +export type OperatorBillingSyncRequest = { + billingCustomerId?: string; + projectId?: string; + environmentId?: string; + /** + * What the projection queue coalesces on. + */ + projectionScopeKey?: string; + triggerKind?: string; + requestedAt?: string; + status?: 'queued'; +}; + +/** + * One restore or sync job. Mosaic's authoritative `outcome` and the native `providerOutcome` + * are separate axes and are never merged: a completed native restore whose facts have not + * reached a snapshot is not restored access. `restored` is admissible only together with the + * accepted `snapshotVersion` that demonstrates it. + * + */ +export type BillingRestoreJob = { + restoreId?: string; + environmentId?: string; + /** + * Empty until identity resolves - which is exactly the identity_unresolved outcome. + */ + billingCustomerId?: string; + storePlatform?: 'apple_app_store' | 'google_play'; + status?: 'queued' | 'leased' | 'completed' | 'failed'; + outcome?: 'restored' | 'no_additional_purchases' | 'validation_pending' | 'identity_unresolved' | 'product_unresolved' | 'provider_unavailable' | 'failed'; + providerOutcome?: 'completed' | 'no_purchases_found' | 'cancelled' | 'failed' | 'unsupported' | 'not_attempted'; + uncertaintyReason?: string; + observedTransactionCount?: number; + pendingValidationCount?: number; + baselineSnapshotVersion?: number; + snapshotVersion?: number; + attemptCount?: number; + maxAttempts?: number; + requestedAt?: string; + updatedAt?: string; + completedAt?: string; +}; + +export type CreateExperimentRequest = { + placementId: string; + name: string; + hypothesis?: string; +}; + +export type ExperimentSchedule = { + startsAt: Timestamp; + endsAt?: Timestamp; +}; + +export type ExperimentVariantDraft = { + id?: string; + role: 'control' | 'treatment'; + name: string; + paywallId: string; + paywallVersionId: string; + allocationBasisPoints: number; +}; + +export type ExperimentDraftDocument = { + variants: Array; + assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; + primaryMetricVersionId: string; + guardrailMetricVersionIds: Array; + schedule: ExperimentSchedule; + mutualExclusionGroupVersionId?: string; + qaPolicy: { + enabled: boolean; + }; +}; + +export type UpdateExperimentDraftRequest = { + expectedRevision: number; + document: ExperimentDraftDocument; +}; + +export type PublishExperimentRequest = { + expectedRevision: number; +}; + +export type ExperimentValidationIssue = { + code: string; + severity: 'error' | 'warning' | 'info'; + message: string; + resourceId?: string; + recoveryAction: string; +}; + +export type ExperimentValidation = { + valid: boolean; + issues: Array; +}; + +export type ExperimentDraft = { + id: string; + revision: number; + status: 'active' | 'published' | 'superseded'; + document: ExperimentDraftDocument; + validation: ExperimentValidation; + updatedAt: Timestamp; +}; + +export type ExperimentVariantVersion = { + id: string; + role: 'control' | 'treatment'; + name: string; + paywallId: string; + paywallVersionId: string; + allocationStart: number; + allocationEnd: number; +}; + +export type ExperimentVersion = { + id: string; + experimentId: string; + placementId: string; + versionNumber: number; + sourceRevision: number; + assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; + bucketingAlgorithm: 'experiment_sha256_length_prefixed_v1'; + allocationVersion: string; + variants: Array; + primaryMetricVersionId: string; + guardrailMetricVersionIds: Array; + schedule: ExperimentSchedule; + mutualExclusionGroupVersionId?: string; + publishedAt: Timestamp; +}; + +export type Experiment = { + id: string; + projectId: string; + environmentId: string; + placementId: string; + name: string; + hypothesis?: string; + state: 'draft' | 'scheduled' | 'running' | 'paused' | 'stopped' | 'completed' | 'archived'; + currentDraft?: ExperimentDraft; + activeVersion?: ExperimentVersion; + role: 'owner' | 'admin' | 'member'; + permissions: Array<'read' | 'write' | 'publish' | 'lifecycle' | 'qa' | 'export'>; + createdAt: Timestamp; + updatedAt: Timestamp; + archivedAt?: Timestamp; +}; + +export type ExperimentMetricDefinition = { + id: string; + version: number; + name: string; + numeratorEvent: string; + denominatorEvent: string; + assignmentUnit: 'assignment_key'; + authority: string; + availability: 'available' | 'trusted_source_unavailable'; + eventFilter: { + 'payload.reason'?: 'provider_unavailable'; + }; + attributionWindowSeconds: number; + freshnessSeconds: number; + definition: string; + primaryEligible: boolean; + guardrailEligible: boolean; +}; + +export type ExperimentGroup = { + id: string; + name: string; + status: 'active' | 'archived'; + activeVersionId?: string; + createdAt: Timestamp; +}; + +export type ExperimentGroupMemberInput = { + /** + * Stable Experiment root identifier. + */ + experimentId: string; + allocationBasisPoints: number; +}; + +export type CreateExperimentGroupRequest = { + name: string; + assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; + members: Array; + holdoutBasisPoints: number; +}; + +export type CreateExperimentGroupVersionRequest = { + assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; + members: Array; + holdoutBasisPoints: number; +}; + +export type ExperimentGroupVersion = { + id: string; + groupId: string; + versionNumber: number; + assignmentKeyPolicy: string; + bucketingAlgorithm: string; + members: Array; + holdoutBasisPoints: number; + createdAt: Timestamp; +}; + +export type ExperimentGroupCreated = { + group: ExperimentGroup; + version: ExperimentGroupVersion; +}; + +export type ExperimentInterval = { + lower: number; + upper: number; +}; + +export type ExperimentVariantResult = { + variantId: string; + role: string; + allocationBasisPoints: number; + uniqueExposures: number; + uniqueConversions: number; + estimate: number; + wilson95: ExperimentInterval; + rawExposureEvents: number; + fallbackPresentations: number; +}; + +export type ExperimentLift = { + treatmentVariantId: string; + absoluteLift: number; + newcombe95: ExperimentInterval; + relativeLift?: number; +}; + +export type ExperimentSrm = { + status: 'insufficient_sample' | 'ok' | 'mismatch'; + severity: 'none' | 'warning' | 'critical'; + statistic: number; + degreesOfFreedom: number; + pValue: number; + cells: Array<{ + variantId: string; + observed: number; + expected: number; + observedShare: number; + expectedShare: number; + }>; + exclusions: Array; + explanation: string; + investigationSteps: Array; +}; + +export type ExperimentGuardrailVariantResult = { + variantId: string; + role: 'control' | 'treatment'; + denominatorCount: number; + numeratorCount: number; + rate: number; +}; + +export type ExperimentGuardrailMaturity = { + status: 'interim' | 'insufficient_sample' | 'mature'; + minimumVariantDenominator: number; + attributionWindowClosed: boolean; +}; + +/** + * Descriptive selected guardrail result. Warning means a mature Treatment rate is greater than Control; it never triggers an automatic action. + */ +export type ExperimentGuardrailResult = { + metricVersionId: string; + name: string; + status: 'insufficient_data' | 'stale' | 'healthy' | 'warning'; + denominatorCount: number; + numeratorCount: number; + rate: number; + maturity: ExperimentGuardrailMaturity; + freshness?: Timestamp; + variants: Array; +}; + +/** + * Descriptive Experiment results. A winner, significance badge, and automatic action are intentionally absent. + */ +export type ExperimentResults = { + experimentId: string; + experimentVersionId: string; + state: string; + interim: boolean; + variants: Array; + lifts: Array; + srm: ExperimentSrm; + freshness?: Timestamp; + warnings: Array; + guardrails: Array; +}; + +export type ExperimentHistory = { + id: string; + fromState: string; + toState: string; + reason?: string; + releaseId?: string; + actorId: string; + createdAt: Timestamp; +}; + +export type CreateExperimentQaOverrideRequest = { + experimentVersionId: string; + variantId: string; + identityType: 'installation' | 'identified_user'; + safeLabel: string; + expiresAt: Timestamp; +}; + +export type CreateExperimentExportRequest = { + format: 'ndjson' | 'csv'; + includeIdentity: boolean; +}; + +export type ExperimentQaOverride = { + id: string; + experimentVersionId: string; + variantId: string; + identityType: string; + safeLabel: string; + selectorDigest?: string; + status: 'active' | 'revoked' | 'expired'; + createdAt: Timestamp; + expiresAt: Timestamp; + revokedAt?: Timestamp; +}; + +export type ExperimentQaOverrideCreated = { + override: ExperimentQaOverride; + /** + * Returned once and never persisted in plaintext. + */ + readonly token: string; +}; + +export type ExperimentEnvelope = { + data: Experiment; +}; + +export type ExperimentDraftEnvelope = { + data: ExperimentDraft; +}; + +export type ExperimentValidationEnvelope = { + data: ExperimentValidation; +}; + +export type ExperimentVersionEnvelope = { + data: ExperimentVersion; +}; + +export type ExperimentResultsEnvelope = { + data: ExperimentResults; +}; + +export type ExperimentListEnvelope = { + data: { + items: Array; + }; +}; + +export type ExperimentVersionListEnvelope = { + data: { + items: Array; + }; +}; + +export type ExperimentHistoryListEnvelope = { + data: { + items: Array; + }; +}; + +export type ExperimentMetricListEnvelope = { + data: { + items: Array; + }; +}; + +export type ExperimentGroupListEnvelope = { + data: { + items: Array; + }; +}; + +export type ExperimentGroupCreatedEnvelope = { + data: ExperimentGroupCreated; +}; + +export type ExperimentGroupVersionEnvelope = { + data: ExperimentGroupVersion; +}; + +export type ExperimentGroupVersionListEnvelope = { + data: { + items: Array; + }; +}; + +export type ExperimentQaOverrideListEnvelope = { + data: { + items: Array; + }; +}; + +export type ExperimentQaOverrideCreatedEnvelope = { + data: ExperimentQaOverrideCreated; +}; + +export type AnalyticsEventBatch = { + /** + * Must exactly equal every enclosed eventSchemaVersion. + */ + analyticsEventContractVersion: '1' | '2'; + batchId: string; + sentAt: Timestamp; + events: Array<{ + [key: string]: unknown; + }>; +}; + +export type AnalyticsEventResult = { + eventId: string; + status: 'accepted' | 'duplicate' | 'permanently_rejected' | 'retryable'; + code?: string; +}; + +export type AnalyticsIngestionResult = { + analyticsEventContractVersion: '1' | '2'; + batchId: string; + receivedAt: Timestamp; + results: Array; +}; + +export type AnalyticsSettings = { + projectId: string; + environmentId: string; + collectionEnabled: boolean; + rawRetentionDays: number; + updatedAt: Timestamp; +}; + +export type UpdateAnalyticsSettingsRequest = { + collectionEnabled: boolean; + rawRetentionDays: number; +}; + +export type AnalyticsFreshness = { + latestReceivedAt?: Timestamp; + latestAggregatedAt?: Timestamp; + lateEventPolicy: string; +}; + +export type AnalyticsMetric = { + id: string; + value?: number; + numerator: number; + denominator?: number; + basis: 'event_count'; + authority: 'client_observed' | 'trusted_server' | 'provider_confirmed'; + attributionWindow: '24h'; + timezone: string; + definition: string; + warnings?: Array; + dimensions?: { + [key: string]: string; + }; +}; + +export type AnalyticsResult = { + metrics: Array; + freshness: AnalyticsFreshness; +}; + +export type CreateAnalyticsEventExportRequest = { + from: Timestamp; + to: Timestamp; + format: 'ndjson' | 'csv'; +}; + +export type AnalyticsIdentityRequest = { + kind: 'application_user' | 'installation'; + /** + * Opaque identifier. Application-user values accept non-control Unicode and punctuation but sensitive-shaped values are rejected. + */ + identity: string; +}; + +export type CreateAnalyticsPrivacyExportRequest = AnalyticsIdentityRequest & { + format: 'ndjson' | 'csv'; +}; + +export type CreateAnalyticsPrivacyDeletionRequest = AnalyticsIdentityRequest & { + requestDigest: string; + confirm: true; +}; + +export type AnalyticsPrivacyPreview = { + kind: 'application_user' | 'installation'; + affectedEvents: number; + affectedSessions: number; + affectedEnvironmentIds: Array; + requestDigest: string; +}; + +export type AnalyticsJob = { + id: string; + kind: 'events' | 'application_user' | 'installation'; + status: 'queued' | 'leased' | 'recomputing' | 'completed' | 'failed' | 'expired'; + format?: 'ndjson' | 'csv'; + rowCount?: number; + byteLength?: number; + affectedEventCount?: number; + affectedSessionCount?: number; + expiresAt?: Timestamp; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type AnalyticsSettingsEnvelope = { + data: AnalyticsSettings; +}; + +export type AnalyticsResultEnvelope = { + data: AnalyticsResult; +}; + +export type AnalyticsPrivacyPreviewEnvelope = { + data: AnalyticsPrivacyPreview; +}; + +export type AnalyticsJobEnvelope = { + data: AnalyticsJob; +}; + +export type PlacementDecisionDocumentRequest = { + document: PlacementDecisionDocument; +}; + +/** + * Canonical Placement Decision v1 envelope; see protocol/schema/placement-decision/v1/decision.schema.json. + */ +export type PlacementDecisionDocument = { + placementDecisionVersion: '1'; + ruleSet: { + [key: string]: unknown; + }; + [key: string]: unknown; +}; + +export type PlacementValidationIssue = { + severity: 'error' | 'warning'; + code: string; + ruleId?: string; + conditionPath?: string; + resourceType?: string; + resourceId?: string; + recoveryAction: string; +}; + +export type PlacementValidation = { + valid: boolean; + issues: Array; +}; + +export type PlacementRuleSet = { + id: string; + projectId: string; + environmentId: string; + placementId: string; + contractVersion: '1'; + status: 'active' | 'archived'; + currentDraftId?: string; + currentPublishedVersionId?: string; + createdByActorId: string; + createdAt: string; + updatedAt: string; + archivedAt?: string; +}; + +export type PlacementRuleSetDraft = { + id: string; + ruleSetId: string; + projectId: string; + environmentId: string; + status: 'active' | 'published' | 'superseded'; + revision: number; + sourceVersionId?: string; + createdByActorId: string; + updatedByActorId: string; + createdAt: string; + updatedAt: string; +}; + +export type PlacementRuleSetDraftResource = { + ruleSet: PlacementRuleSet; + draft: PlacementRuleSetDraft; + document: PlacementDecisionDocument; + validation: PlacementValidation; +}; + +export type PlacementRuleSetVersion = { + id: string; + ruleSetId: string; + projectId: string; + environmentId: string; + placementId: string; + versionNumber: number; + sourceDraftId: string; + sourceRevision: number; + contractVersion: '1'; + document: PlacementDecisionDocument; + documentHash: string; + validation: PlacementValidation; + publishedByActorId: string; + publishedAt: string; +}; + +export type PlacementAttribute = { + id: string; + projectId: string; + key: string; + type: 'string' | 'boolean' | 'number' | 'timestamp' | 'semantic_version' | 'string_list'; + description: string; + allowedOperators: Array; + sensitivity: 'standard' | 'sensitive'; + status: 'active' | 'archived'; + revision: number; + createdByActorId: string; + updatedByActorId: string; + createdAt: string; + updatedAt: string; + archivedAt?: string; +}; + +export type CreatePlacementAttributeRequest = { + key: string; + type: string; + description?: string; + allowedOperators: Array; + sensitivity: 'standard' | 'sensitive'; +}; + +export type PlacementAlias = { + id: string; + projectId: string; + placementId: string; + key: string; + status: 'active' | 'archived'; + createdByActorId: string; + createdAt: string; + archivedAt?: string; +}; + +export type PlacementUsage = { + ruleSetCount: number; + aliasCount: number; + publishedRuleCount: number; +}; + +export type PlacementOutcome = { + type: 'paywall' | 'no_paywall' | 'fallback' | 'unavailable'; + paywallVersionId?: string; + key?: string; + unavailableFallbackKey?: string; + reason?: string; +}; + +export type QaOverride = { + id: string; + projectId: string; + environmentId: string; + placementId: string; + safeLabel: string; + outcome: PlacementOutcome; + status: 'active' | 'revoked' | 'expired'; + createdByActorId: string; + createdAt: string; + expiresAt: string; + revokedAt?: string; +}; + +export type CreateQaOverrideRequest = { + safeLabel: string; + outcome: PlacementOutcome; + expiresAt: string; +}; + +export type QaOverrideCreated = { + override: QaOverride; + /** + * Returned once and never persisted in plaintext. + */ + token: string; +}; + +export type PlacementSimulationRequest = { + platform?: 'ios' | 'android'; + osVersion?: string; + applicationVersion?: string; + locale?: string; + country?: string; + entitlements?: { + [key: string]: unknown; + }; + productAvailability?: { + [key: string]: unknown; + }; + productReadiness?: { + [key: string]: unknown; + }; + providerCapabilities?: { + [key: string]: unknown; + }; +}; + +export type PlacementSimulationResult = { + winningRuleId?: string; + selectedOutcome: PlacementOutcome; + finalOutcome: PlacementOutcome; + fallbackPath: Array; + assignmentKeyType?: string; + rolloutBucket?: number; + trace: Array<{ + [key: string]: unknown; + }>; +}; + +export type PlacementRuleSetDraftEnvelope = { + data: PlacementRuleSetDraftResource; +}; + +export type PlacementValidationEnvelope = { + data: PlacementValidation; +}; + +export type PlacementRuleSetVersionEnvelope = { + data: PlacementRuleSetVersion; +}; + +export type PlacementRuleSetVersionListEnvelope = { + data: { + items: Array; + }; +}; + +export type PlacementAttributeEnvelope = { + data: PlacementAttribute; +}; + +export type PlacementAttributeListEnvelope = { + data: { + items: Array; + }; +}; + +export type PlacementAliasEnvelope = { + data: PlacementAlias; +}; + +export type PlacementAliasListEnvelope = { + data: { + items: Array; + }; +}; + +export type PlacementUsageEnvelope = { + data: PlacementUsage; +}; + +export type QaOverrideCreatedEnvelope = { + data: QaOverrideCreated; +}; + +export type QaOverrideListEnvelope = { + data: { + items: Array; + }; +}; + +export type PlacementSimulationEnvelope = { + data: PlacementSimulationResult; +}; + +export type Timestamp = string; + +export type Page = { + nextCursor?: string; +}; + +export type Role = 'owner' | 'admin' | 'member'; + +export type ProjectStatus = 'active' | 'archived'; + +export type Platform = 'ios' | 'android'; + +export type ApiKeyKind = 'public_sdk' | 'secret_server'; + +export type ProductType = 'subscription' | 'one_time_non_consumable'; + +export type ProductStatus = 'draft' | 'connected' | 'attention_required' | 'archived'; + +export type MetadataSource = 'mock' | 'provider'; + +export type ProviderKind = 'revenuecat' | 'app_store' | 'google_play' | 'custom'; + +export type ProviderActivationKind = 'provider_connection' | 'native_store'; + +export type ProviderConnectionKind = 'revenuecat' | 'custom'; + +export type ProviderIntegrationMode = 'server_connected' | 'sdk_only'; + +export type ProviderConnectionMode = 'sandbox' | 'production'; + +export type ProviderConnectionStatus = 'pending' | 'active' | 'revoked'; + +export type ProviderHealthStatus = 'untested' | 'healthy' | 'degraded' | 'unavailable' | 'revoked'; + +export type EnvironmentMode = 'development' | 'staging' | 'production'; + +export type ProviderMappingStatus = 'placeholder' | 'draft' | 'active' | 'attention_required' | 'archived'; + +export type ProviderAvailability = 'unknown' | 'available' | 'unavailable'; + +export type ProviderSyncState = 'never_synced' | 'current' | 'stale' | 'failed'; + +export type ProviderErrorCode = 'credentialInvalid' | 'credentialExpired' | 'permissionDenied' | 'connectionRevoked' | 'scopeMismatch' | 'modeMismatch' | 'rateLimited' | 'timeout' | 'providerUnavailable' | 'invalidResponse' | 'productNotFound' | 'productUnavailable' | 'mappingMissing' | 'mappingAmbiguous' | 'syncInProgress' | 'syncPartial' | 'syncFailed' | 'metadataStale' | 'basePlanMissing' | 'observationMissing' | 'observationStale' | 'idempotencyConflict'; + +export type SignUpRequest = { + email: string; + name: string; + password: string; +}; + +export type LoginRequest = { + email: string; + password: string; +}; + +export type CreatePaywallRequest = { + key: string; + name: string; +}; + +export type UpdatePaywallRequest = { + name: string; +}; + +export type CreateDraftRequest = { + environmentId: string; + sourceVersionId?: string; + document: { + [key: string]: unknown; + }; +}; + +export type UpdateDraftRequest = { + document: { + [key: string]: unknown; + }; +}; + +export type CreatePlacementRequest = { + key: string; + name: string; + description?: string; +}; + +export type UpdatePlacementRequest = { + name: string; + description?: string; +}; + +export type BindPlacementRequest = { + paywallId: string; +}; + +export type PublishRequest = { + draftId: string; + expectedRevision: number; + acknowledgeMockProducts: boolean; +}; + +export type CreateOrganizationRequest = { + name: string; +}; + +export type UpdateOrganizationRequest = CreateOrganizationRequest; + +export type UpdateNameRequest = CreateOrganizationRequest; + +export type AddMemberRequest = { + actorId: string; + role: Role; +}; + +export type UpdateMemberRequest = { + role: Role; +}; + +export type CreateProjectRequest = { + organizationId: string; + key: string; + name: string; +}; + +export type CreateApplicationRequest = { + name: string; + platform: Platform; + identifier: string; +}; + +export type CreateApiKeyRequest = { + kind: ApiKeyKind; + /** + * Required for public_sdk keys and forbidden for secret_server keys. + */ + applicationId?: string; +}; + +export type CreateCatalogResourceRequest = { + key: string; + name: string; + description?: string; +}; + +export type CreateProductRequest = { + key: string; + internalName: string; + description?: string; + type: ProductType; +}; + +export type ProductReferenceRequest = { + productId: string; +}; + +export type EntitlementReferenceRequest = { + entitlementId: string; +}; + +export type CreateProviderMappingRequest = { + applicationId: string; + provider: ProviderKind; + providerProductIdentifier: string; +}; + +export type SetEnvironmentModeRequest = { + mode: EnvironmentMode; +}; + +export type CreateProviderConnectionRequest = unknown & { + name: string; + provider: ProviderConnectionKind; + integrationMode: ProviderIntegrationMode; + mode: ProviderConnectionMode; + /** + * Required RevenueCat v2 Project resource ID. + */ + externalProjectId?: string; + environmentIds: Array; + applicationIds: Array; +}; + +export type ProviderCredentialRequest = { + credential: string; +}; + +export type ReplaceProviderMappingRequest = { + providerProductIdentifier: string; + /** + * RevenueCat v2 Package ID or SDK lookup key; persisted as the verified lookup key. + */ + providerPackageIdentifier?: string; + /** + * RevenueCat v2 Offering ID or SDK lookup key; persisted as the verified lookup key. + */ + providerOfferingIdentifier?: string; + /** + * Required exact Google base-plan ID for subscriptions. + */ + providerBasePlanIdentifier?: string; + /** + * Optional explicitly selected Google offer ID. Offer tokens are never persisted. + */ + providerOfferIdentifier?: string; +}; + +export type ProviderEntitlementImportRequest = { + providerIdentifier: string; + existingEntitlementId?: string; + key?: string; + name?: string; +}; + +export type ProviderProductImportItemRequest = { + providerProductIdentifier: string; + providerPackageIdentifier?: string; + providerOfferingIdentifier?: string; + existingProductId?: string; + key?: string; + internalName?: string; + environmentId: string; + applicationId: string; + entitlements: Array; +}; + +export type ProviderImportRequest = { + connectionId: string; + items: Array; +}; + +export type ReplaceProviderConnectionScopesRequest = { + environmentIds: Array; + applicationIds: Array; +}; + +export type SetProviderAssignmentRequest = { + provider?: ProviderKind; + activationKind?: ProviderActivationKind; + connectionId?: string; + /** + * Required when explicitly assigning a production connection outside a production Environment. + */ + acknowledgeProductionConnectionUse?: boolean; +}; + +/** + * Creates an unverified draft only. RevenueCat Product, Package, and Offering + * identifiers are server-side catalog references and never become the SDK + * providerProductReference until verified and normalized. + * + */ +export type CreateProviderMappingDraftRequest = { + connectionId?: string; + provider?: ProviderKind; + environmentId: string; + applicationId: string; + /** + * Opaque provider catalog resource identifier. + */ + providerProductIdentifier: string; + /** + * RevenueCat-only metadata; requires providerOfferingIdentifier. + */ + providerPackageIdentifier?: string; + /** + * RevenueCat-only metadata; requires providerPackageIdentifier. + */ + providerOfferingIdentifier?: string; + expectedStoreProductId?: string; + /** + * Exact Google base-plan ID; required by service validation for subscriptions. + */ + providerBasePlanIdentifier?: string; + /** + * Optional exact Google offer ID; requires a base plan. Runtime offer tokens are forbidden. + */ + providerOfferIdentifier?: string; +}; + +export type CreateProviderMappingObservationRequest = { + adapterVersion: string; + storeContext: 'storekitConfiguration' | 'appleSandbox' | 'googlePlayTest' | 'production' | 'unknown'; + result: 'available' | 'unavailable' | 'failed'; + /** + * Safe Mosaic code only; raw provider messages are forbidden. + */ + diagnosticCode?: string; + correlationId: string; + metadata?: ProviderMappingObservationMetadata; + observedAt: Timestamp; + expiresAt?: Timestamp; +}; + +export type Organization = { + id: string; + name: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type Membership = { + organizationId: string; + actorId: string; + role: Role; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type Project = { + id: string; + organizationId: string; + key: string; + name: string; + status: ProjectStatus; + archivedAt?: Timestamp; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type Application = { + id: string; + projectId: string; + name: string; + platform: Platform; + identifier: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type Environment = { + id: string; + projectId: string; + key: string; + name: string; + mode: EnvironmentMode; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +/** + * Contains no provider credential, token, secret, nonce, or ciphertext. + */ +export type ProviderConnection = { + id: string; + projectId: string; + name: string; + provider: ProviderConnectionKind; + integrationMode: ProviderIntegrationMode; + mode: ProviderConnectionMode; + status: ProviderConnectionStatus; + healthStatus: ProviderHealthStatus; + externalProjectId?: string; + environmentIds: Array; + applicationIds: Array; + lastSuccessfulTestAt?: Timestamp; + lastSuccessfulSyncAt?: Timestamp; + lastErrorCode?: ProviderErrorCode; + credential?: ProviderCredential; + revokedAt?: Timestamp; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +/** + * Safe credential metadata only; contains no plaintext, ciphertext, nonce, or authorization material. + */ +export type ProviderCredential = { + class: 'serverSecret'; + fingerprint: string; + keyId: string; + envelopeVersion: number; + createdAt: Timestamp; + rotatedAt?: Timestamp; + revokedAt?: Timestamp; +}; + +export type ProviderCapability = { + name: string; + support: 'supported' | 'conditional' | 'unsupported'; + reasonCode?: string; +}; + +export type ProviderConnectionHealth = { + connectionId: string; + status: ProviderHealthStatus; + lastSuccessfulAt?: Timestamp; + lastErrorCode?: ProviderErrorCode; + capabilities: Array; + requiredPermissions: Array; +}; + +export type ProviderConnectionCapabilities = { + connectionId: string; + capabilities: Array; + requiredPermissions: Array; +}; + +export type ProviderDiagnostic = { + id: string; + projectId: string; + connectionId: string; + operation: string; + code: ProviderErrorCode; + retryable: boolean; + retryAfterSeconds?: number; + correlationId: string; + occurredAt: Timestamp; +}; + +export type ProviderCatalogApplication = { + id: string; + name: string; + platform: 'app_store' | 'play_store'; + identifier?: string; +}; + +export type ProviderCatalogProduct = { + id: string; + applicationId: string; + storeIdentifier: string; + displayName?: string; + type: 'subscription' | 'non_consumable' | 'consumable' | 'unknown'; + state: string; + importable: boolean; +}; + +export type ProviderCatalogEntitlement = { + id: string; + lookupKey: string; + displayName: string; + state: string; +}; + +export type ProviderCatalogPackage = { + id: string; + lookupKey: string; + displayName: string; + productIds: Array; +}; + +export type ProviderCatalogOffering = { + id: string; + lookupKey: string; + displayName: string; + state: string; + isCurrent: boolean; + packages: Array; +}; + +export type ProviderCatalogPreview = { + connectionId: string; + observedAt: Timestamp; + applications: Array; + products: Array; + entitlements: Array; + offerings: Array; +}; + +export type ProviderImport = { + id: string; + projectId: string; + connectionId: string; + status: 'in_progress' | 'completed' | 'partial'; + createdByActorId: string; + createdAt: Timestamp; + completedAt?: Timestamp; +}; + +export type ProviderImportItem = { + importId: string; + projectId: string; + providerProductIdentifier: string; + mosaicProductId?: string; + mappingId?: string; + status: 'imported' | 'failed'; + errorCode?: ProviderErrorCode; + createdAt: Timestamp; +}; + +export type ProviderImportResult = { + import: ProviderImport; + items: Array; +}; + +export type ProviderSyncJob = { + id: string; + projectId: string; + connectionId: string; + status: 'queued' | 'leased' | 'completed' | 'failed'; + attemptCount: number; + maxAttempts: number; + availableAt: Timestamp; + requestedByActorId: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type ProviderSyncRun = { + id: string; + projectId: string; + connectionId: string; + jobId: string; + status: 'running' | 'completed' | 'partial' | 'failed'; + itemCount: number; + successCount: number; + failureCount: number; + startedAt: Timestamp; + completedAt?: Timestamp; + createdAt: Timestamp; +}; + +export type ActiveProviderAssignment = { + projectId: string; + environmentId: string; + applicationId: string; + platform: Platform; + provider: ProviderKind; + activationKind: ProviderActivationKind; + connectionId?: string; + productionConnectionUseAcknowledged: boolean; + createdByActorId: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type ApiKey = { + id: string; + environmentId: string; + /** + * Trusted Application binding for public SDK analytics ingestion. + */ + applicationId?: string; + kind: ApiKeyKind; + prefix: string; + createdByActorId: string; + createdAt: Timestamp; + rotatedAt?: Timestamp; + revokedAt?: Timestamp; + lastUsedAt?: Timestamp; +}; + +export type ApiKeySecretResult = { + apiKey: ApiKey; + /** + * Returned only by create and rotate operations and never available again. + */ + secret: string; +}; + +export type Plan = { + id: string; + projectId: string; + key: string; + name: string; + description?: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +/** + * Legacy catalog lifecycle summary embedded in Product responses; never sufficient for publication. + */ +export type ProductReadiness = { + ready: boolean; + reasons: Array; + metadataSource: MetadataSource; +}; + +export type Product = { + id: string; + projectId: string; + key: string; + internalName: string; + description?: string; + type: ProductType; + status: ProductStatus; + metadataSource: MetadataSource; + readiness: ProductReadiness; + replacementProductId?: string; + archivedAt?: Timestamp; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type Entitlement = { + id: string; + projectId: string; + key: string; + name: string; + description?: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type PlanProduct = { + planId: string; + productId: string; + createdAt: Timestamp; +}; + +export type ProductEntitlementGrant = { + productId: string; + entitlementId: string; + createdAt: Timestamp; +}; + +export type ProviderProductMapping = { + id: string; + projectId: string; + productId: string; + connectionId?: string; + environmentId?: string; + applicationId: string; + platform: Platform; + provider: ProviderKind; + /** + * RevenueCat v2 Product resource identifier used only by server-side catalog synchronization. + */ + providerProductIdentifier: string; + /** + * Optional RevenueCat Package metadata; present only with providerOfferingIdentifier. + */ + providerPackageIdentifier?: string; + /** + * Optional RevenueCat Offering metadata; present only with providerPackageIdentifier. + */ + providerOfferingIdentifier?: string; + expectedStoreProductId?: string; + providerBasePlanIdentifier?: string; + providerOfferIdentifier?: string; + replacesMappingId?: string; + status: ProviderMappingStatus; + availability: ProviderAvailability; + syncState: ProviderSyncState; + currentSnapshotId?: string; + lastErrorCode?: ProviderErrorCode; + archivedAt?: Timestamp; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +/** + * Immutable safe metadata evidence; raw provider responses and credentials are excluded. + */ +export type ProviderProductMetadataSnapshot = { + id: string; + projectId: string; + mappingId: string; + source: 'provider' | 'sdk_snapshot' | 'manual'; + digest: string; + availability: ProviderAvailability; + observedAt: Timestamp; + syncedAt: Timestamp; + staleAt: Timestamp; + expiresAt?: Timestamp; + lastErrorCode?: ProviderErrorCode; + /** + * Normalized safe metadata only; raw provider responses are never persisted. + */ + metadata: { + [key: string]: unknown; + }; + createdAt: Timestamp; +}; + +export type ProviderReadinessIssue = { + code: ProviderErrorCode; + resourceType: string; + resourceId: string; + recoveryAction: string; +}; + +export type ProviderReadiness = { + state: 'configured' | 'verifiedInTest' | 'attentionRequired' | 'unavailable' | 'archived'; + productId: string; + environmentId: string; + applicationId: string; + platform: Platform; + connectionId?: string; + provider?: ProviderKind; + mappingId?: string; + observation?: ProviderMappingObservation; + blockers: Array; + warnings: Array; + evaluatedAt: Timestamp; +}; + +export type ProviderMappingObservation = { + id: string; + projectId: string; + mappingId: string; + environmentId: string; + applicationId: string; + platform: Platform; + provider: ProviderKind; + adapterVersion: string; + storeContext: 'storekitConfiguration' | 'appleSandbox' | 'googlePlayTest' | 'production' | 'unknown'; + result: 'available' | 'unavailable' | 'failed'; + diagnosticCode?: string; + correlationId: string; + metadata: ProviderMappingObservationMetadata; + observedAt: Timestamp; + expiresAt?: Timestamp; + receivedAt: Timestamp; + createdByActorId: string; +}; + +/** + * Closed operational metadata. Unknown fields and receipt, token, credential, customer, account, authorization, or secret-like material are rejected. + */ +export type ProviderMappingObservationMetadata = { + clientPlatform?: 'ios' | 'android' | 'flutter'; + clientVersion?: string; + applicationVersion?: string; + osVersion?: string; + configurationSource?: 'bundled' | 'remote' | 'local' | 'unknown'; + storefrontCountryCode?: string; + testScenario?: 'productLoad' | 'configurationAcceptance' | 'purchasePresentation' | 'restore'; +}; + +export type ProviderMappingUsage = { + mapping: ProviderProductMapping; + product: Product; + usage: ProductUsage; +}; + +export type ProviderProfile = { + provider: ProviderKind; + displayName: string; + platform: Platform; + adapterVersion: string; + capabilities: Array; +}; + +export type ProductUsage = { + productId: string; + plans: Array; + entitlements: Array; + providerMappings: Array; + historicalReferences: Array; +}; + +export type AuditEvent = { + id: string; + actorId: string; + organizationId: string; + projectId?: string; + environmentId?: string; + action: string; + resourceType: string; + resourceId: string; + metadata: { + [key: string]: string; + }; + createdAt: Timestamp; +}; + +export type ValidationSummary = { + errors: Array; + warnings: Array; +}; + +export type Paywall = { + id: string; + projectId: string; + key: string; + name: string; + status: 'active' | 'archived'; + archivedAt?: Timestamp; + createdByActorId: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type Draft = { + id: string; + projectId: string; + paywallId: string; + environmentId: string; + status: 'active' | 'published' | 'archived'; + revision: number; + sourceVersionId?: string; + protocolVersion: '0.2'; + validationStatus: 'valid' | 'invalid'; + validation: ValidationSummary; + createdByActorId: string; + updatedByActorId: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type DraftResource = { + draft: Draft; + document: { + [key: string]: unknown; + }; +}; + +export type PaywallVersion = { + id: string; + projectId: string; + paywallId: string; + environmentId: string; + versionNumber: number; + sourceDraftId: string; + sourceRevision: number; + protocolVersion: '0.2'; + document: { + [key: string]: unknown; + }; + documentHash: string; + validation: ValidationSummary; + createdByActorId: string; + createdAt: Timestamp; + productIds: Array; +}; + +export type Placement = { + id: string; + projectId: string; + key: string; + name: string; + description?: string; + status: 'active' | 'archived'; + archivedAt?: Timestamp; + createdByActorId: string; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type PlacementBinding = { + projectId: string; + environmentId: string; + placementId: string; + paywallId: string; + updatedByActorId: string; + updatedAt: Timestamp; +}; + +export type User = { + id: string; + email: string; + name: string; + createdAt: Timestamp; +}; + +export type Asset = { + id: string; + projectId: string; + kind: 'image' | 'video'; + originalFilename: string; + mediaType: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/gif' | 'video/mp4'; + byteLength: number; + contentDigest: string; + url: string; + status: 'pending' | 'ready' | 'failed' | 'archived' | 'deleted'; + createdByActorId: string; + archivedAt?: Timestamp; + createdAt: Timestamp; + updatedAt: Timestamp; +}; + +export type AssetUsage = { + draftReferences: number; + versionReferences: number; + releaseReferences: number; +}; + +export type ConfigurationRelease = { + id: string; + projectId: string; + environmentId: string; + releaseNumber: number; + deliveryContractVersion: '1'; + contentHash: string; + sourceReleaseId?: string; + rollbackSourceReleaseId?: string; + publishedByActorId: string; + publishedAt: Timestamp; +}; + +export type PublishResult = { + release: ConfigurationRelease; + warnings: Array; +}; + +export type ErrorEnvelope = { + error: { + code: string; + message: string; + fields?: { + [key: string]: Array; + }; + details?: { + [key: string]: unknown; + }; + requestId?: string; + }; +}; + +export type HealthEnvelope = { + data: { + status: 'ok' | 'ready'; + version: string; + commit?: string; + built?: string; + }; +}; + +export type OrganizationEnvelope = { + data: Organization; +}; + +export type MembershipEnvelope = { + data: Membership; }; -export type ExperimentSchedule = { - startsAt: Timestamp; - endsAt?: Timestamp; +export type ProjectEnvelope = { + data: Project; }; -export type ExperimentVariantDraft = { - id?: string; - role: 'control' | 'treatment'; - name: string; - paywallId: string; - paywallVersionId: string; - allocationBasisPoints: number; +export type ApplicationEnvelope = { + data: Application; }; -export type ExperimentDraftDocument = { - variants: Array; - assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; - primaryMetricVersionId: string; - guardrailMetricVersionIds: Array; - schedule: ExperimentSchedule; - mutualExclusionGroupVersionId?: string; - qaPolicy: { - enabled: boolean; - }; +export type EnvironmentEnvelope = { + data: Environment; }; -export type UpdateExperimentDraftRequest = { - expectedRevision: number; - document: ExperimentDraftDocument; +export type ProviderConnectionEnvelope = { + data: ProviderConnection; }; -export type PublishExperimentRequest = { - expectedRevision: number; +export type ProviderAssignmentEnvelope = { + data: ActiveProviderAssignment; }; -export type ExperimentValidationIssue = { - code: string; - severity: 'error' | 'warning' | 'info'; - message: string; - resourceId?: string; - recoveryAction: string; +export type ProviderReadinessEnvelope = { + data: ProviderReadiness; }; -export type ExperimentValidation = { - valid: boolean; - issues: Array; +export type ProviderMappingUsageEnvelope = { + data: ProviderMappingUsage; }; -export type ExperimentDraft = { - id: string; - revision: number; - status: 'active' | 'published' | 'superseded'; - document: ExperimentDraftDocument; - validation: ExperimentValidation; - updatedAt: Timestamp; +export type ProviderMappingObservationEnvelope = { + data: ProviderMappingObservation; }; -export type ExperimentVariantVersion = { - id: string; - role: 'control' | 'treatment'; - name: string; - paywallId: string; - paywallVersionId: string; - allocationStart: number; - allocationEnd: number; +export type ProviderProfileEnvelope = { + data: ProviderProfile; }; -export type ExperimentVersion = { - id: string; - experimentId: string; - placementId: string; - versionNumber: number; - sourceRevision: number; - assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; - bucketingAlgorithm: 'experiment_sha256_length_prefixed_v1'; - allocationVersion: string; - variants: Array; - primaryMetricVersionId: string; - guardrailMetricVersionIds: Array; - schedule: ExperimentSchedule; - mutualExclusionGroupVersionId?: string; - publishedAt: Timestamp; +export type ApiKeyEnvelope = { + data: ApiKey; }; -export type Experiment = { - id: string; - projectId: string; - environmentId: string; - placementId: string; - name: string; - hypothesis?: string; - state: 'draft' | 'scheduled' | 'running' | 'paused' | 'stopped' | 'completed' | 'archived'; - currentDraft?: ExperimentDraft; - activeVersion?: ExperimentVersion; - role: 'owner' | 'admin' | 'member'; - permissions: Array<'read' | 'write' | 'publish' | 'lifecycle' | 'qa' | 'export'>; - createdAt: Timestamp; - updatedAt: Timestamp; - archivedAt?: Timestamp; +export type ApiKeySecretEnvelope = { + data: ApiKeySecretResult; }; -export type ExperimentMetricDefinition = { - id: string; - version: number; - name: string; - numeratorEvent: string; - denominatorEvent: string; - assignmentUnit: 'assignment_key'; - authority: string; - availability: 'available' | 'trusted_source_unavailable'; - eventFilter: { - 'payload.reason'?: 'provider_unavailable'; - }; - attributionWindowSeconds: number; - freshnessSeconds: number; - definition: string; - primaryEligible: boolean; - guardrailEligible: boolean; +export type PlanEnvelope = { + data: Plan; }; -export type ExperimentGroup = { - id: string; - name: string; - status: 'active' | 'archived'; - activeVersionId?: string; - createdAt: Timestamp; +export type ProductEnvelope = { + data: Product; }; -export type ExperimentGroupMemberInput = { - /** - * Stable Experiment root identifier. - */ - experimentId: string; - allocationBasisPoints: number; +export type ProductReadinessEnvelope = { + data: ProductReadiness; }; -export type CreateExperimentGroupRequest = { - name: string; - assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; - members: Array; - holdoutBasisPoints: number; +export type EntitlementEnvelope = { + data: Entitlement; }; -export type CreateExperimentGroupVersionRequest = { - assignmentKeyPolicy: 'installation' | 'identified_user' | 'identified_user_or_installation'; - members: Array; - holdoutBasisPoints: number; +export type PlanProductEnvelope = { + data: PlanProduct; }; -export type ExperimentGroupVersion = { - id: string; - groupId: string; - versionNumber: number; - assignmentKeyPolicy: string; - bucketingAlgorithm: string; - members: Array; - holdoutBasisPoints: number; - createdAt: Timestamp; +export type ProductEntitlementGrantEnvelope = { + data: ProductEntitlementGrant; }; -export type ExperimentGroupCreated = { - group: ExperimentGroup; - version: ExperimentGroupVersion; +export type ProviderMappingEnvelope = { + data: ProviderProductMapping; }; -export type ExperimentInterval = { - lower: number; - upper: number; +export type ProductUsageEnvelope = { + data: ProductUsage; }; -export type ExperimentVariantResult = { - variantId: string; - role: string; - allocationBasisPoints: number; - uniqueExposures: number; - uniqueConversions: number; - estimate: number; - wilson95: ExperimentInterval; - rawExposureEvents: number; - fallbackPresentations: number; +export type OrganizationListEnvelope = OrganizationList; + +export type MembershipListEnvelope = MembershipList; + +export type ProjectListEnvelope = ProjectList; + +export type ApplicationListEnvelope = ApplicationList; + +export type EnvironmentListEnvelope = EnvironmentList; + +export type ProviderConnectionListEnvelope = ProviderConnectionList; + +export type ApiKeyListEnvelope = ApiKeyList; + +export type PlanListEnvelope = PlanList; + +export type ProductListEnvelope = ProductList; + +export type EntitlementListEnvelope = EntitlementList; + +export type ProviderMappingListEnvelope = ProviderMappingList; + +export type AuditEventListEnvelope = AuditEventList; + +export type PaywallEnvelope = { + data: Paywall; }; -export type ExperimentLift = { - treatmentVariantId: string; - absoluteLift: number; - newcombe95: ExperimentInterval; - relativeLift?: number; +export type DraftEnvelope = { + data: DraftResource; }; -export type ExperimentSrm = { - status: 'insufficient_sample' | 'ok' | 'mismatch'; - severity: 'none' | 'warning' | 'critical'; - statistic: number; - degreesOfFreedom: number; - pValue: number; - cells: Array<{ - variantId: string; - observed: number; - expected: number; - observedShare: number; - expectedShare: number; - }>; - exclusions: Array; - explanation: string; - investigationSteps: Array; +export type ValidationSummaryEnvelope = { + data: ValidationSummary; }; -export type ExperimentGuardrailVariantResult = { - variantId: string; - role: 'control' | 'treatment'; - denominatorCount: number; - numeratorCount: number; - rate: number; +export type PaywallVersionEnvelope = { + data: PaywallVersion; }; -export type ExperimentGuardrailMaturity = { - status: 'interim' | 'insufficient_sample' | 'mature'; - minimumVariantDenominator: number; - attributionWindowClosed: boolean; +export type PlacementEnvelope = { + data: Placement; +}; + +export type PlacementBindingEnvelope = { + data: PlacementBinding; }; -/** - * Descriptive selected guardrail result. Warning means a mature Treatment rate is greater than Control; it never triggers an automatic action. - */ -export type ExperimentGuardrailResult = { - metricVersionId: string; - name: string; - status: 'insufficient_data' | 'stale' | 'healthy' | 'warning'; - denominatorCount: number; - numeratorCount: number; - rate: number; - maturity: ExperimentGuardrailMaturity; - freshness?: Timestamp; - variants: Array; +export type AssetEnvelope = { + data: Asset; }; -/** - * Descriptive Experiment results. A winner, significance badge, and automatic action are intentionally absent. - */ -export type ExperimentResults = { - experimentId: string; - experimentVersionId: string; - state: string; - interim: boolean; - variants: Array; - lifts: Array; - srm: ExperimentSrm; - freshness?: Timestamp; - warnings: Array; - guardrails: Array; +export type AssetUsageEnvelope = { + data: AssetUsage; }; -export type ExperimentHistory = { - id: string; - fromState: string; - toState: string; - reason?: string; - releaseId?: string; - actorId: string; - createdAt: Timestamp; +export type AssetListEnvelope = { + data: { + items: Array; + page: Page; + }; }; -export type CreateExperimentQaOverrideRequest = { - experimentVersionId: string; - variantId: string; - identityType: 'installation' | 'identified_user'; - safeLabel: string; - expiresAt: Timestamp; +export type UserEnvelope = { + data: User; }; -export type CreateExperimentExportRequest = { - format: 'ndjson' | 'csv'; - includeIdentity: boolean; +export type ReleaseEnvelope = { + data: ConfigurationRelease; }; -export type ExperimentQaOverride = { - id: string; - experimentVersionId: string; - variantId: string; - identityType: string; - safeLabel: string; - selectorDigest?: string; - status: 'active' | 'revoked' | 'expired'; - createdAt: Timestamp; - expiresAt: Timestamp; - revokedAt?: Timestamp; +export type PublishResultEnvelope = { + data: PublishResult; }; -export type ExperimentQaOverrideCreated = { - override: ExperimentQaOverride; - /** - * Returned once and never persisted in plaintext. - */ - readonly token: string; +export type PaywallListEnvelope = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentEnvelope = { - data: Experiment; +export type PaywallVersionListEnvelope = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentDraftEnvelope = { - data: ExperimentDraft; +export type PlacementListEnvelope = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentValidationEnvelope = { - data: ExperimentValidation; +export type ReleaseListEnvelope = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentVersionEnvelope = { - data: ExperimentVersion; +export type OrganizationList = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentResultsEnvelope = { - data: ExperimentResults; +export type MembershipList = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentListEnvelope = { +export type ProjectList = { data: { - items: Array; + items: Array; + page: Page; }; }; -export type ExperimentVersionListEnvelope = { +export type ApplicationList = { data: { - items: Array; + items: Array; + page: Page; }; }; -export type ExperimentHistoryListEnvelope = { +export type EnvironmentList = { data: { - items: Array; + items: Array; + page: Page; }; }; -export type ExperimentMetricListEnvelope = { +export type ProviderConnectionList = { data: { - items: Array; + items: Array; + page: Page; }; }; -export type ExperimentGroupListEnvelope = { +export type ApiKeyList = { data: { - items: Array; + items: Array; + page: Page; }; }; -export type ExperimentGroupCreatedEnvelope = { - data: ExperimentGroupCreated; +export type PlanList = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentGroupVersionEnvelope = { - data: ExperimentGroupVersion; +export type ProductList = { + data: { + items: Array; + page: Page; + }; }; -export type ExperimentGroupVersionListEnvelope = { +export type EntitlementList = { data: { - items: Array; + items: Array; + page: Page; }; }; -export type ExperimentQaOverrideListEnvelope = { +export type ProviderMappingList = { data: { - items: Array; + items: Array; + page: Page; }; }; -export type ExperimentQaOverrideCreatedEnvelope = { - data: ExperimentQaOverrideCreated; +export type AuditEventList = { + data: { + items: Array; + page: Page; + }; }; -export type AnalyticsEventBatch = { +export type BootstrapOrganization = { + organization: Organization; + role: Role; /** - * Must exactly equal every enclosed eventSchemaVersion. + * Active Projects only, ordered by id, capped at 25. */ - analyticsEventContractVersion: '1' | '2'; - batchId: string; - sentAt: Timestamp; - events: Array<{ - [key: string]: unknown; - }>; + projects: Array; + /** + * Active Projects in the Organization + */ + projectCount: number; + /** + * True when projects holds fewer entries than projectCount. + */ + projectsTruncated: boolean; }; -export type AnalyticsEventResult = { - eventId: string; - status: 'accepted' | 'duplicate' | 'permanently_rejected' | 'retryable'; - code?: string; +export type WorkspaceBootstrap = { + /** + * Organizations the caller belongs to, ordered by id. Empty when the caller belongs to none. + */ + organizations: Array; }; -export type AnalyticsIngestionResult = { - analyticsEventContractVersion: '1' | '2'; - batchId: string; - receivedAt: Timestamp; - results: Array; +export type WorkspaceBootstrapEnvelope = { + data: WorkspaceBootstrap; }; -export type AnalyticsSettings = { - projectId: string; +export type CreateBillingMigrationProgramRequestWritable = { environmentId: string; - collectionEnabled: boolean; - rawRetentionDays: number; - updatedAt: Timestamp; + applications: Array; + /** + * Preserved opaque RevenueCat project identifier. + */ + revenueCatProjectId: string; + /** + * Separately consented least-privilege migration key; never returned or logged. + */ + revenueCatApiKey: string; + stabilizationDays?: number; + rollbackWindowDays?: number; }; -export type UpdateAnalyticsSettingsRequest = { - collectionEnabled: boolean; - rawRetentionDays: number; +export type ExperimentQaOverrideCreatedWritable = { + override: ExperimentQaOverride; }; -export type AnalyticsFreshness = { - latestReceivedAt?: Timestamp; - latestAggregatedAt?: Timestamp; - lateEventPolicy: string; +export type ExperimentQaOverrideCreatedEnvelopeWritable = { + data: ExperimentQaOverrideCreatedWritable; }; -export type AnalyticsMetric = { - id: string; - value?: number; - numerator: number; - denominator?: number; - basis: 'event_count'; - authority: 'client_observed' | 'trusted_server' | 'provider_confirmed'; - attributionWindow: '24h'; - timezone: string; - definition: string; - warnings?: Array; - dimensions?: { - [key: string]: string; +export type CreateQaOverrideRequestWritable = { + safeLabel: string; + selector: string; + outcome: PlacementOutcome; + expiresAt: string; +}; + +export type PlacementSimulationRequestWritable = { + platform?: 'ios' | 'android'; + osVersion?: string; + applicationVersion?: string; + locale?: string; + country?: string; + installationId?: string; + userId?: string; + attributes?: { + [key: string]: unknown; + }; + entitlements?: { + [key: string]: unknown; + }; + productAvailability?: { + [key: string]: unknown; + }; + productReadiness?: { + [key: string]: unknown; + }; + providerCapabilities?: { + [key: string]: unknown; }; + overrideToken?: string; }; -export type AnalyticsResult = { - metrics: Array; - freshness: AnalyticsFreshness; -}; - -export type CreateAnalyticsEventExportRequest = { - from: Timestamp; - to: Timestamp; - format: 'ndjson' | 'csv'; -}; - -export type AnalyticsIdentityRequest = { - kind: 'application_user' | 'installation'; +export type CreateProviderConnectionRequestWritable = unknown & { + name: string; + provider: ProviderConnectionKind; + integrationMode: ProviderIntegrationMode; + mode: ProviderConnectionMode; /** - * Opaque identifier. Application-user values accept non-control Unicode and punctuation but sensitive-shaped values are rejected. + * Required RevenueCat v2 Project resource ID. */ - identity: string; + externalProjectId?: string; + /** + * One-time RevenueCat v2 least-privilege secret key. Never returned or logged. + */ + credential?: string; + environmentIds: Array; + applicationIds: Array; }; -export type CreateAnalyticsPrivacyExportRequest = AnalyticsIdentityRequest & { - format: 'ndjson' | 'csv'; -}; +/** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ +export type Cursor = string; -export type CreateAnalyticsPrivacyDeletionRequest = AnalyticsIdentityRequest & { - requestDigest: string; - confirm: true; -}; +export type Limit = number; -export type AnalyticsPrivacyPreview = { - kind: 'application_user' | 'installation'; - affectedEvents: number; - affectedSessions: number; - affectedEnvironmentIds: Array; - requestDigest: string; -}; +export type OrganizationId = string; -export type AnalyticsJob = { - id: string; - kind: 'events' | 'application_user' | 'installation'; - status: 'queued' | 'leased' | 'recomputing' | 'completed' | 'failed' | 'expired'; - format?: 'ndjson' | 'csv'; - rowCount?: number; - byteLength?: number; - affectedEventCount?: number; - affectedSessionCount?: number; - expiresAt?: Timestamp; - createdAt: Timestamp; - updatedAt: Timestamp; -}; +export type ActorId = string; -export type AnalyticsSettingsEnvelope = { - data: AnalyticsSettings; -}; +export type ProjectId = string; -export type AnalyticsResultEnvelope = { - data: AnalyticsResult; -}; +export type WebhookDestinationId = string; -export type AnalyticsPrivacyPreviewEnvelope = { - data: AnalyticsPrivacyPreview; -}; +export type WebhookDeliveryId = string; -export type AnalyticsJobEnvelope = { - data: AnalyticsJob; -}; +export type EnvironmentId = string; -export type PlacementDecisionDocumentRequest = { - document: PlacementDecisionDocument; -}; +export type BillingCustomerId = string; + +export type SubscriptionInstanceId = string; + +export type RestoreJobId = string; + +export type IdentityConflictId = string; + +export type ApplicationId = string; + +export type ProviderConnectionId = string; + +export type ProviderMappingId = string; /** - * Canonical Placement Decision v1 envelope; see protocol/schema/placement-decision/v1/decision.schema.json. + * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not + * construct or parse one — it encodes both the ordering timestamp and the row id, because + * billing identifiers are not time-ordered and an id alone cannot express a position in a + * timestamp ordering. + * + * A malformed or stale value starts from the first page rather than erroring, so a mangled + * cursor cannot silently truncate a list. Omit it for the first page; a response with no + * `nextCursor` is the last page. + * */ -export type PlacementDecisionDocument = { - placementDecisionVersion: '1'; - ruleSet: { - [key: string]: unknown; - }; - [key: string]: unknown; -}; +export type BillingCursor = string; -export type PlacementValidationIssue = { - severity: 'error' | 'warning'; - code: string; - ruleId?: string; - conditionPath?: string; - resourceType?: string; - resourceId?: string; - recoveryAction: string; -}; +export type StoreCredentialId = string; -export type PlacementValidation = { - valid: boolean; - issues: Array; -}; +export type QuarantineRecordId = string; -export type PlacementRuleSet = { - id: string; - projectId: string; - environmentId: string; - placementId: string; - contractVersion: '1'; - status: 'active' | 'archived'; - currentDraftId?: string; - currentPublishedVersionId?: string; - createdByActorId: string; - createdAt: string; - updatedAt: string; - archivedAt?: string; -}; +export type BillingProviderFilter = 'app_store' | 'google_play'; -export type PlacementRuleSetDraft = { - id: string; - ruleSetId: string; - projectId: string; - environmentId: string; - status: 'active' | 'published' | 'superseded'; - revision: number; - sourceVersionId?: string; - createdByActorId: string; - updatedByActorId: string; - createdAt: string; - updatedAt: string; -}; +export type BillingFrom = string; -export type PlacementRuleSetDraftResource = { - ruleSet: PlacementRuleSet; - draft: PlacementRuleSetDraft; - document: PlacementDecisionDocument; - validation: PlacementValidation; -}; +export type BillingTo = string; -export type PlacementRuleSetVersion = { - id: string; - ruleSetId: string; - projectId: string; - environmentId: string; - placementId: string; - versionNumber: number; - sourceDraftId: string; - sourceRevision: number; - contractVersion: '1'; - document: PlacementDecisionDocument; - documentHash: string; - validation: PlacementValidation; - publishedByActorId: string; - publishedAt: string; -}; +export type ApiKeyId = string; -export type PlacementAttribute = { - id: string; - projectId: string; - key: string; - type: 'string' | 'boolean' | 'number' | 'timestamp' | 'semantic_version' | 'string_list'; - description: string; - allowedOperators: Array; - sensitivity: 'standard' | 'sensitive'; - status: 'active' | 'archived'; - revision: number; - createdByActorId: string; - updatedByActorId: string; - createdAt: string; - updatedAt: string; - archivedAt?: string; -}; +export type PlanId = string; -export type CreatePlacementAttributeRequest = { - key: string; - type: string; - description?: string; - allowedOperators: Array; - sensitivity: 'standard' | 'sensitive'; -}; +export type ProductId = string; -export type PlacementAlias = { - id: string; - projectId: string; - placementId: string; - key: string; - status: 'active' | 'archived'; - createdByActorId: string; - createdAt: string; - archivedAt?: string; -}; +export type EntitlementId = string; -export type PlacementUsage = { - ruleSetCount: number; - aliasCount: number; - publishedRuleCount: number; -}; +export type PaywallId = string; -export type PlacementOutcome = { - type: 'paywall' | 'no_paywall' | 'fallback' | 'unavailable'; - paywallVersionId?: string; - key?: string; - unavailableFallbackKey?: string; - reason?: string; -}; +export type DraftId = string; -export type QaOverride = { - id: string; - projectId: string; - environmentId: string; - placementId: string; - safeLabel: string; - outcome: PlacementOutcome; - status: 'active' | 'revoked' | 'expired'; - createdByActorId: string; - createdAt: string; - expiresAt: string; - revokedAt?: string; -}; +export type VersionId = string; + +export type PlacementId = string; + +export type RuleSetId = string; + +export type ExperimentId = string; + +export type AssetId = string; + +export type ReleaseId = string; -export type CreateQaOverrideRequest = { - safeLabel: string; - outcome: PlacementOutcome; - expiresAt: string; -}; +export type IdempotencyKey = string; -export type QaOverrideCreated = { - override: QaOverride; - /** - * Returned once and never persisted in plaintext. - */ - token: string; -}; +export type BillingMigrationProgramId = string; -export type PlacementSimulationRequest = { - platform?: 'ios' | 'android'; - osVersion?: string; - applicationVersion?: string; - locale?: string; - country?: string; - entitlements?: { - [key: string]: unknown; - }; - productAvailability?: { - [key: string]: unknown; - }; - productReadiness?: { - [key: string]: unknown; - }; - providerCapabilities?: { - [key: string]: unknown; - }; -}; +export type BillingMigrationIdempotencyKey = string; -export type PlacementSimulationResult = { - winningRuleId?: string; - selectedOutcome: PlacementOutcome; - finalOutcome: PlacementOutcome; - fallbackPath: Array; - assignmentKeyType?: string; - rolloutBucket?: number; - trace: Array<{ - [key: string]: unknown; - }>; -}; +export type IfMatch = string; -export type PlacementRuleSetDraftEnvelope = { - data: PlacementRuleSetDraftResource; -}; +export type AnalyticsFrom = Timestamp; -export type PlacementValidationEnvelope = { - data: PlacementValidation; -}; +export type AnalyticsTo = Timestamp; -export type PlacementRuleSetVersionEnvelope = { - data: PlacementRuleSetVersion; -}; +export type AnalyticsTimezone = string; -export type PlacementRuleSetVersionListEnvelope = { - data: { - items: Array; - }; -}; +export type AnalyticsMetricBasis = 'event_count'; -export type PlacementAttributeEnvelope = { - data: PlacementAttribute; -}; +export type AnalyticsPlatform = 'ios' | 'android'; -export type PlacementAttributeListEnvelope = { - data: { - items: Array; - }; -}; +export type AnalyticsLocale = string; -export type PlacementAliasEnvelope = { - data: PlacementAlias; -}; +export type AnalyticsApplicationVersion = string; -export type PlacementAliasListEnvelope = { - data: { - items: Array; - }; +export type GetHealthData = { + body?: never; + path?: never; + query?: never; + url: '/health/live'; }; -export type PlacementUsageEnvelope = { - data: PlacementUsage; +export type GetHealthResponses = { + /** + * Process liveness. + */ + 200: HealthEnvelope; }; -export type QaOverrideCreatedEnvelope = { - data: QaOverrideCreated; +export type GetHealthResponse = GetHealthResponses[keyof GetHealthResponses]; + +export type GetReadinessData = { + body?: never; + path?: never; + query?: never; + url: '/health/ready'; }; -export type QaOverrideListEnvelope = { - data: { - items: Array; - }; +export type GetReadinessErrors = { + /** + * PostgreSQL is unavailable. + */ + 503: ErrorEnvelope; }; -export type PlacementSimulationEnvelope = { - data: PlacementSimulationResult; +export type GetReadinessError = GetReadinessErrors[keyof GetReadinessErrors]; + +export type GetReadinessResponses = { + /** + * PostgreSQL is reachable and the API is ready to serve traffic. + */ + 200: HealthEnvelope; }; -export type Timestamp = string; +export type GetReadinessResponse = GetReadinessResponses[keyof GetReadinessResponses]; -export type Page = { - nextCursor?: string; +export type SignUpData = { + body: SignUpRequest; + path?: never; + query?: never; + url: '/v1/auth/signup'; }; -export type Role = 'owner' | 'admin' | 'member'; +export type SignUpErrors = { + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; +}; -export type ProjectStatus = 'active' | 'archived'; +export type SignUpError = SignUpErrors[keyof SignUpErrors]; -export type Platform = 'ios' | 'android'; +export type SignUpResponses = { + /** + * Authenticated browser user. Login and signup also set the session cookie. + */ + 201: UserEnvelope; +}; -export type ApiKeyKind = 'public_sdk' | 'secret_server'; +export type SignUpResponse = SignUpResponses[keyof SignUpResponses]; -export type ProductType = 'subscription' | 'one_time_non_consumable'; +export type LoginData = { + body: LoginRequest; + path?: never; + query?: never; + url: '/v1/auth/login'; +}; -export type ProductStatus = 'draft' | 'connected' | 'attention_required' | 'archived'; +export type LoginErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; +}; -export type MetadataSource = 'mock' | 'provider'; +export type LoginError = LoginErrors[keyof LoginErrors]; -export type ProviderKind = 'revenuecat' | 'app_store' | 'google_play' | 'custom'; +export type LoginResponses = { + /** + * Authenticated browser user. Login and signup also set the session cookie. + */ + 200: UserEnvelope; +}; -export type ProviderActivationKind = 'provider_connection' | 'native_store'; +export type LoginResponse = LoginResponses[keyof LoginResponses]; -export type ProviderConnectionKind = 'revenuecat' | 'custom'; +export type LogoutData = { + body?: never; + path?: never; + query?: never; + url: '/v1/auth/logout'; +}; -export type ProviderIntegrationMode = 'server_connected' | 'sdk_only'; +export type LogoutResponses = { + /** + * Session revoked and cookie cleared. + */ + 204: void; +}; -export type ProviderConnectionMode = 'sandbox' | 'production'; +export type LogoutResponse = LogoutResponses[keyof LogoutResponses]; -export type ProviderConnectionStatus = 'pending' | 'active' | 'revoked'; +export type GetSessionData = { + body?: never; + path?: never; + query?: never; + url: '/v1/auth/session'; +}; -export type ProviderHealthStatus = 'untested' | 'healthy' | 'degraded' | 'unavailable' | 'revoked'; +export type GetSessionErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; +}; -export type EnvironmentMode = 'development' | 'staging' | 'production'; +export type GetSessionError = GetSessionErrors[keyof GetSessionErrors]; -export type ProviderMappingStatus = 'placeholder' | 'draft' | 'active' | 'attention_required' | 'archived'; +export type GetSessionResponses = { + /** + * Authenticated browser user. Login and signup also set the session cookie. + */ + 200: UserEnvelope; +}; -export type ProviderAvailability = 'unknown' | 'available' | 'unavailable'; +export type GetSessionResponse = GetSessionResponses[keyof GetSessionResponses]; -export type ProviderSyncState = 'never_synced' | 'current' | 'stale' | 'failed'; +export type ListOrganizationsData = { + body?: never; + path?: never; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/organizations'; +}; -export type ProviderErrorCode = 'credentialInvalid' | 'credentialExpired' | 'permissionDenied' | 'connectionRevoked' | 'scopeMismatch' | 'modeMismatch' | 'rateLimited' | 'timeout' | 'providerUnavailable' | 'invalidResponse' | 'productNotFound' | 'productUnavailable' | 'mappingMissing' | 'mappingAmbiguous' | 'syncInProgress' | 'syncPartial' | 'syncFailed' | 'metadataStale' | 'basePlanMissing' | 'observationMissing' | 'observationStale' | 'idempotencyConflict'; +export type ListOrganizationsErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; +}; -export type SignUpRequest = { - email: string; - name: string; - password: string; +export type ListOrganizationsError = ListOrganizationsErrors[keyof ListOrganizationsErrors]; + +export type ListOrganizationsResponses = { + /** + * Organizations + */ + 200: OrganizationList; }; -export type LoginRequest = { - email: string; - password: string; -}; +export type ListOrganizationsResponse = ListOrganizationsResponses[keyof ListOrganizationsResponses]; -export type CreatePaywallRequest = { - key: string; - name: string; +export type CreateOrganizationData = { + body: CreateOrganizationRequest; + path?: never; + query?: never; + url: '/v1/organizations'; }; -export type UpdatePaywallRequest = { - name: string; +export type CreateOrganizationErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type CreateDraftRequest = { - environmentId: string; - sourceVersionId?: string; - document: { - [key: string]: unknown; - }; +export type CreateOrganizationError = CreateOrganizationErrors[keyof CreateOrganizationErrors]; + +export type CreateOrganizationResponses = { + /** + * Organization + */ + 201: OrganizationEnvelope; }; -export type UpdateDraftRequest = { - document: { - [key: string]: unknown; +export type CreateOrganizationResponse = CreateOrganizationResponses[keyof CreateOrganizationResponses]; + +export type GetOrganizationData = { + body?: never; + path: { + organizationId: string; }; + query?: never; + url: '/v1/organizations/{organizationId}'; }; -export type CreatePlacementRequest = { - key: string; - name: string; - description?: string; +export type GetOrganizationErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type UpdatePlacementRequest = { - name: string; - description?: string; -}; +export type GetOrganizationError = GetOrganizationErrors[keyof GetOrganizationErrors]; -export type BindPlacementRequest = { - paywallId: string; +export type GetOrganizationResponses = { + /** + * Organization + */ + 200: OrganizationEnvelope; }; -export type PublishRequest = { - draftId: string; - expectedRevision: number; - acknowledgeMockProducts: boolean; -}; +export type GetOrganizationResponse = GetOrganizationResponses[keyof GetOrganizationResponses]; -export type CreateOrganizationRequest = { - name: string; +export type UpdateOrganizationData = { + body: CreateOrganizationRequest; + path: { + organizationId: string; + }; + query?: never; + url: '/v1/organizations/{organizationId}'; }; -export type UpdateOrganizationRequest = CreateOrganizationRequest; +export type UpdateOrganizationErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; +}; -export type UpdateNameRequest = CreateOrganizationRequest; +export type UpdateOrganizationError = UpdateOrganizationErrors[keyof UpdateOrganizationErrors]; -export type AddMemberRequest = { - actorId: string; - role: Role; +export type UpdateOrganizationResponses = { + /** + * Organization + */ + 200: OrganizationEnvelope; }; -export type UpdateMemberRequest = { - role: Role; -}; +export type UpdateOrganizationResponse = UpdateOrganizationResponses[keyof UpdateOrganizationResponses]; -export type CreateProjectRequest = { - organizationId: string; - key: string; - name: string; +export type ListMembersData = { + body?: never; + path: { + organizationId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/organizations/{organizationId}/members'; }; -export type CreateApplicationRequest = { - name: string; - platform: Platform; - identifier: string; +export type ListMembersErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type CreateApiKeyRequest = { - kind: ApiKeyKind; +export type ListMembersError = ListMembersErrors[keyof ListMembersErrors]; + +export type ListMembersResponses = { /** - * Required for public_sdk keys and forbidden for secret_server keys. + * Memberships */ - applicationId?: string; + 200: MembershipList; }; -export type CreateCatalogResourceRequest = { - key: string; - name: string; - description?: string; -}; +export type ListMembersResponse = ListMembersResponses[keyof ListMembersResponses]; -export type CreateProductRequest = { - key: string; - internalName: string; - description?: string; - type: ProductType; +export type AddMemberData = { + body: AddMemberRequest; + path: { + organizationId: string; + }; + query?: never; + url: '/v1/organizations/{organizationId}/members'; }; -export type ProductReferenceRequest = { - productId: string; +export type AddMemberErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type EntitlementReferenceRequest = { - entitlementId: string; +export type AddMemberError = AddMemberErrors[keyof AddMemberErrors]; + +export type AddMemberResponses = { + /** + * Membership + */ + 201: MembershipEnvelope; }; -export type CreateProviderMappingRequest = { - applicationId: string; - provider: ProviderKind; - providerProductIdentifier: string; +export type AddMemberResponse = AddMemberResponses[keyof AddMemberResponses]; + +export type RemoveMemberData = { + body?: never; + path: { + organizationId: string; + actorId: string; + }; + query?: never; + url: '/v1/organizations/{organizationId}/members/{actorId}'; }; -export type SetEnvironmentModeRequest = { - mode: EnvironmentMode; +export type RemoveMemberErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; }; -export type CreateProviderConnectionRequest = unknown & { - name: string; - provider: ProviderConnectionKind; - integrationMode: ProviderIntegrationMode; - mode: ProviderConnectionMode; +export type RemoveMemberError = RemoveMemberErrors[keyof RemoveMemberErrors]; + +export type RemoveMemberResponses = { /** - * Required RevenueCat v2 Project resource ID. + * Member removed. */ - externalProjectId?: string; - environmentIds: Array; - applicationIds: Array; + 204: void; }; -export type ProviderCredentialRequest = { - credential: string; +export type RemoveMemberResponse = RemoveMemberResponses[keyof RemoveMemberResponses]; + +export type UpdateMemberData = { + body: UpdateMemberRequest; + path: { + organizationId: string; + actorId: string; + }; + query?: never; + url: '/v1/organizations/{organizationId}/members/{actorId}'; }; -export type ReplaceProviderMappingRequest = { - providerProductIdentifier: string; +export type UpdateMemberErrors = { /** - * RevenueCat v2 Package ID or SDK lookup key; persisted as the verified lookup key. + * Stable machine-readable failure. */ - providerPackageIdentifier?: string; + 401: ErrorEnvelope; /** - * RevenueCat v2 Offering ID or SDK lookup key; persisted as the verified lookup key. + * Stable machine-readable failure. */ - providerOfferingIdentifier?: string; + 403: ErrorEnvelope; /** - * Required exact Google base-plan ID for subscriptions. + * Stable machine-readable failure. */ - providerBasePlanIdentifier?: string; + 404: ErrorEnvelope; /** - * Optional explicitly selected Google offer ID. Offer tokens are never persisted. + * Stable machine-readable failure. */ - providerOfferIdentifier?: string; + 409: ErrorEnvelope; }; -export type ProviderEntitlementImportRequest = { - providerIdentifier: string; - existingEntitlementId?: string; - key?: string; - name?: string; -}; +export type UpdateMemberError = UpdateMemberErrors[keyof UpdateMemberErrors]; -export type ProviderProductImportItemRequest = { - providerProductIdentifier: string; - providerPackageIdentifier?: string; - providerOfferingIdentifier?: string; - existingProductId?: string; - key?: string; - internalName?: string; - environmentId: string; - applicationId: string; - entitlements: Array; +export type UpdateMemberResponses = { + /** + * Membership + */ + 200: MembershipEnvelope; }; -export type ProviderImportRequest = { - connectionId: string; - items: Array; -}; +export type UpdateMemberResponse = UpdateMemberResponses[keyof UpdateMemberResponses]; -export type ReplaceProviderConnectionScopesRequest = { - environmentIds: Array; - applicationIds: Array; +export type ListAuditEventsData = { + body?: never; + path: { + organizationId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + projectId?: string; + action?: string; + }; + url: '/v1/organizations/{organizationId}/audit-events'; }; -export type SetProviderAssignmentRequest = { - provider?: ProviderKind; - activationKind?: ProviderActivationKind; - connectionId?: string; +export type ListAuditEventsErrors = { /** - * Required when explicitly assigning a production connection outside a production Environment. + * Stable machine-readable failure. */ - acknowledgeProductionConnectionUse?: boolean; -}; - -/** - * Creates an unverified draft only. RevenueCat Product, Package, and Offering - * identifiers are server-side catalog references and never become the SDK - * providerProductReference until verified and normalized. - * - */ -export type CreateProviderMappingDraftRequest = { - connectionId?: string; - provider?: ProviderKind; - environmentId: string; - applicationId: string; + 401: ErrorEnvelope; /** - * Opaque provider catalog resource identifier. + * Stable machine-readable failure. */ - providerProductIdentifier: string; + 403: ErrorEnvelope; /** - * RevenueCat-only metadata; requires providerOfferingIdentifier. + * Stable machine-readable failure. */ - providerPackageIdentifier?: string; + 422: ErrorEnvelope; +}; + +export type ListAuditEventsError = ListAuditEventsErrors[keyof ListAuditEventsErrors]; + +export type ListAuditEventsResponses = { /** - * RevenueCat-only metadata; requires providerPackageIdentifier. + * Audit events */ - providerOfferingIdentifier?: string; - expectedStoreProductId?: string; + 200: AuditEventList; +}; + +export type ListAuditEventsResponse = ListAuditEventsResponses[keyof ListAuditEventsResponses]; + +export type ListProjectsData = { + body?: never; + path?: never; + query: { + organizationId: string; + status?: ProjectStatus; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/projects'; +}; + +export type ListProjectsErrors = { /** - * Exact Google base-plan ID; required by service validation for subscriptions. + * Stable machine-readable failure. */ - providerBasePlanIdentifier?: string; + 401: ErrorEnvelope; /** - * Optional exact Google offer ID; requires a base plan. Runtime offer tokens are forbidden. + * Stable machine-readable failure. */ - providerOfferIdentifier?: string; -}; - -export type CreateProviderMappingObservationRequest = { - adapterVersion: string; - storeContext: 'storekitConfiguration' | 'appleSandbox' | 'googlePlayTest' | 'production' | 'unknown'; - result: 'available' | 'unavailable' | 'failed'; + 403: ErrorEnvelope; /** - * Safe Mosaic code only; raw provider messages are forbidden. + * Stable machine-readable failure. */ - diagnosticCode?: string; - correlationId: string; - metadata?: ProviderMappingObservationMetadata; - observedAt: Timestamp; - expiresAt?: Timestamp; + 422: ErrorEnvelope; }; -export type Organization = { - id: string; - name: string; - createdAt: Timestamp; - updatedAt: Timestamp; -}; +export type ListProjectsError = ListProjectsErrors[keyof ListProjectsErrors]; -export type Membership = { - organizationId: string; - actorId: string; - role: Role; - createdAt: Timestamp; - updatedAt: Timestamp; +export type ListProjectsResponses = { + /** + * Projects + */ + 200: ProjectList; }; -export type Project = { - id: string; - organizationId: string; - key: string; - name: string; - status: ProjectStatus; - archivedAt?: Timestamp; - createdAt: Timestamp; - updatedAt: Timestamp; -}; +export type ListProjectsResponse = ListProjectsResponses[keyof ListProjectsResponses]; -export type Application = { - id: string; - projectId: string; - name: string; - platform: Platform; - identifier: string; - createdAt: Timestamp; - updatedAt: Timestamp; +export type CreateProjectData = { + body: CreateProjectRequest; + path?: never; + query?: never; + url: '/v1/projects'; }; -export type Environment = { - id: string; - projectId: string; - key: string; - name: string; - mode: EnvironmentMode; - createdAt: Timestamp; - updatedAt: Timestamp; +export type CreateProjectErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -/** - * Contains no provider credential, token, secret, nonce, or ciphertext. - */ -export type ProviderConnection = { - id: string; - projectId: string; - name: string; - provider: ProviderConnectionKind; - integrationMode: ProviderIntegrationMode; - mode: ProviderConnectionMode; - status: ProviderConnectionStatus; - healthStatus: ProviderHealthStatus; - externalProjectId?: string; - environmentIds: Array; - applicationIds: Array; - lastSuccessfulTestAt?: Timestamp; - lastSuccessfulSyncAt?: Timestamp; - lastErrorCode?: ProviderErrorCode; - credential?: ProviderCredential; - revokedAt?: Timestamp; - createdAt: Timestamp; - updatedAt: Timestamp; +export type CreateProjectError = CreateProjectErrors[keyof CreateProjectErrors]; + +export type CreateProjectResponses = { + /** + * Project + */ + 201: ProjectEnvelope; }; -/** - * Safe credential metadata only; contains no plaintext, ciphertext, nonce, or authorization material. - */ -export type ProviderCredential = { - class: 'serverSecret'; - fingerprint: string; - keyId: string; - envelopeVersion: number; - createdAt: Timestamp; - rotatedAt?: Timestamp; - revokedAt?: Timestamp; -}; +export type CreateProjectResponse = CreateProjectResponses[keyof CreateProjectResponses]; -export type ProviderCapability = { - name: string; - support: 'supported' | 'conditional' | 'unsupported'; - reasonCode?: string; +export type GetProjectData = { + body?: never; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}'; }; -export type ProviderConnectionHealth = { - connectionId: string; - status: ProviderHealthStatus; - lastSuccessfulAt?: Timestamp; - lastErrorCode?: ProviderErrorCode; - capabilities: Array; - requiredPermissions: Array; +export type GetProjectErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type ProviderConnectionCapabilities = { - connectionId: string; - capabilities: Array; - requiredPermissions: Array; +export type GetProjectError = GetProjectErrors[keyof GetProjectErrors]; + +export type GetProjectResponses = { + /** + * Project + */ + 200: ProjectEnvelope; }; -export type ProviderDiagnostic = { - id: string; - projectId: string; - connectionId: string; - operation: string; - code: ProviderErrorCode; - retryable: boolean; - retryAfterSeconds?: number; - correlationId: string; - occurredAt: Timestamp; +export type GetProjectResponse = GetProjectResponses[keyof GetProjectResponses]; + +export type UpdateProjectData = { + body: CreateOrganizationRequest; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}'; }; -export type ProviderCatalogApplication = { - id: string; - name: string; - platform: 'app_store' | 'play_store'; - identifier?: string; +export type UpdateProjectErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProviderCatalogProduct = { - id: string; - applicationId: string; - storeIdentifier: string; - displayName?: string; - type: 'subscription' | 'non_consumable' | 'consumable' | 'unknown'; - state: string; - importable: boolean; +export type UpdateProjectError = UpdateProjectErrors[keyof UpdateProjectErrors]; + +export type UpdateProjectResponses = { + /** + * Project + */ + 200: ProjectEnvelope; }; -export type ProviderCatalogEntitlement = { - id: string; - lookupKey: string; - displayName: string; - state: string; +export type UpdateProjectResponse = UpdateProjectResponses[keyof UpdateProjectResponses]; + +export type ArchiveProjectData = { + body?: never; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/archive'; }; -export type ProviderCatalogPackage = { - id: string; - lookupKey: string; - displayName: string; - productIds: Array; +export type ArchiveProjectErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProviderCatalogOffering = { - id: string; - lookupKey: string; - displayName: string; - state: string; - isCurrent: boolean; - packages: Array; +export type ArchiveProjectError = ArchiveProjectErrors[keyof ArchiveProjectErrors]; + +export type ArchiveProjectResponses = { + /** + * Project + */ + 200: ProjectEnvelope; }; -export type ProviderCatalogPreview = { - connectionId: string; - observedAt: Timestamp; - applications: Array; - products: Array; - entitlements: Array; - offerings: Array; +export type ArchiveProjectResponse = ArchiveProjectResponses[keyof ArchiveProjectResponses]; + +export type RestoreProjectData = { + body?: never; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/restore'; }; -export type ProviderImport = { - id: string; - projectId: string; - connectionId: string; - status: 'in_progress' | 'completed' | 'partial'; - createdByActorId: string; - createdAt: Timestamp; - completedAt?: Timestamp; +export type RestoreProjectErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProviderImportItem = { - importId: string; - projectId: string; - providerProductIdentifier: string; - mosaicProductId?: string; - mappingId?: string; - status: 'imported' | 'failed'; - errorCode?: ProviderErrorCode; - createdAt: Timestamp; +export type RestoreProjectError = RestoreProjectErrors[keyof RestoreProjectErrors]; + +export type RestoreProjectResponses = { + /** + * Project + */ + 200: ProjectEnvelope; }; -export type ProviderImportResult = { - import: ProviderImport; - items: Array; +export type RestoreProjectResponse = RestoreProjectResponses[keyof RestoreProjectResponses]; + +export type ListApplicationsData = { + body?: never; + path: { + projectId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/projects/{projectId}/applications'; }; -export type ProviderSyncJob = { - id: string; - projectId: string; - connectionId: string; - status: 'queued' | 'leased' | 'completed' | 'failed'; - attemptCount: number; - maxAttempts: number; - availableAt: Timestamp; - requestedByActorId: string; - createdAt: Timestamp; - updatedAt: Timestamp; +export type ListApplicationsErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProviderSyncRun = { - id: string; - projectId: string; - connectionId: string; - jobId: string; - status: 'running' | 'completed' | 'partial' | 'failed'; - itemCount: number; - successCount: number; - failureCount: number; - startedAt: Timestamp; - completedAt?: Timestamp; - createdAt: Timestamp; +export type ListApplicationsError = ListApplicationsErrors[keyof ListApplicationsErrors]; + +export type ListApplicationsResponses = { + /** + * Applications + */ + 200: ApplicationList; }; -export type ActiveProviderAssignment = { - projectId: string; - environmentId: string; - applicationId: string; - platform: Platform; - provider: ProviderKind; - activationKind: ProviderActivationKind; - connectionId?: string; - productionConnectionUseAcknowledged: boolean; - createdByActorId: string; - createdAt: Timestamp; - updatedAt: Timestamp; +export type ListApplicationsResponse = ListApplicationsResponses[keyof ListApplicationsResponses]; + +export type CreateApplicationData = { + body: CreateApplicationRequest; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/applications'; }; -export type ApiKey = { - id: string; - environmentId: string; +export type CreateApplicationErrors = { /** - * Trusted Application binding for public SDK analytics ingestion. + * Stable machine-readable failure. */ - applicationId?: string; - kind: ApiKeyKind; - prefix: string; - createdByActorId: string; - createdAt: Timestamp; - rotatedAt?: Timestamp; - revokedAt?: Timestamp; - lastUsedAt?: Timestamp; + default: ErrorEnvelope; }; -export type ApiKeySecretResult = { - apiKey: ApiKey; +export type CreateApplicationError = CreateApplicationErrors[keyof CreateApplicationErrors]; + +export type CreateApplicationResponses = { /** - * Returned only by create and rotate operations and never available again. + * Application */ - secret: string; + 201: ApplicationEnvelope; }; -export type Plan = { - id: string; - projectId: string; - key: string; - name: string; - description?: string; - createdAt: Timestamp; - updatedAt: Timestamp; +export type CreateApplicationResponse = CreateApplicationResponses[keyof CreateApplicationResponses]; + +export type ListEnvironmentsData = { + body?: never; + path: { + projectId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/projects/{projectId}/environments'; }; -/** - * Legacy catalog lifecycle summary embedded in Product responses; never sufficient for publication. - */ -export type ProductReadiness = { - ready: boolean; - reasons: Array; - metadataSource: MetadataSource; +export type ListEnvironmentsErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type Product = { - id: string; - projectId: string; - key: string; - internalName: string; - description?: string; - type: ProductType; - status: ProductStatus; - metadataSource: MetadataSource; - readiness: ProductReadiness; - replacementProductId?: string; - archivedAt?: Timestamp; - createdAt: Timestamp; - updatedAt: Timestamp; +export type ListEnvironmentsError = ListEnvironmentsErrors[keyof ListEnvironmentsErrors]; + +export type ListEnvironmentsResponses = { + /** + * Environments + */ + 200: EnvironmentList; }; -export type Entitlement = { - id: string; - projectId: string; - key: string; - name: string; - description?: string; - createdAt: Timestamp; - updatedAt: Timestamp; +export type ListEnvironmentsResponse = ListEnvironmentsResponses[keyof ListEnvironmentsResponses]; + +export type ListProviderConnectionsData = { + body?: never; + path: { + projectId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/projects/{projectId}/provider-connections'; }; -export type PlanProduct = { - planId: string; - productId: string; - createdAt: Timestamp; +export type ListProviderConnectionsErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProductEntitlementGrant = { - productId: string; - entitlementId: string; - createdAt: Timestamp; +export type ListProviderConnectionsError = ListProviderConnectionsErrors[keyof ListProviderConnectionsErrors]; + +export type ListProviderConnectionsResponses = { + /** + * Non-secret Provider Connections + */ + 200: ProviderConnectionList; }; -export type ProviderProductMapping = { - id: string; - projectId: string; - productId: string; - connectionId?: string; - environmentId?: string; - applicationId: string; - platform: Platform; - provider: ProviderKind; +export type ListProviderConnectionsResponse = ListProviderConnectionsResponses[keyof ListProviderConnectionsResponses]; + +export type CreateProviderConnectionData = { + body: CreateProviderConnectionRequestWritable; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/provider-connections'; +}; + +export type CreateProviderConnectionErrors = { /** - * RevenueCat v2 Product resource identifier used only by server-side catalog synchronization. + * Stable machine-readable failure. */ - providerProductIdentifier: string; + default: ErrorEnvelope; +}; + +export type CreateProviderConnectionError = CreateProviderConnectionErrors[keyof CreateProviderConnectionErrors]; + +export type CreateProviderConnectionResponses = { /** - * Optional RevenueCat Package metadata; present only with providerOfferingIdentifier. + * Non-secret Provider Connection metadata */ - providerPackageIdentifier?: string; + 201: ProviderConnectionEnvelope; +}; + +export type CreateProviderConnectionResponse = CreateProviderConnectionResponses[keyof CreateProviderConnectionResponses]; + +export type ImportProviderProductsData = { + body: ProviderImportRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/provider-imports'; +}; + +export type ImportProviderProductsErrors = { /** - * Optional RevenueCat Offering metadata; present only with providerPackageIdentifier. + * Stable machine-readable failure. */ - providerOfferingIdentifier?: string; - expectedStoreProductId?: string; - providerBasePlanIdentifier?: string; - providerOfferIdentifier?: string; - replacesMappingId?: string; - status: ProviderMappingStatus; - availability: ProviderAvailability; - syncState: ProviderSyncState; - currentSnapshotId?: string; - lastErrorCode?: ProviderErrorCode; - archivedAt?: Timestamp; - createdAt: Timestamp; - updatedAt: Timestamp; + default: ErrorEnvelope; }; -/** - * Immutable safe metadata evidence; raw provider responses and credentials are excluded. - */ -export type ProviderProductMetadataSnapshot = { - id: string; - projectId: string; - mappingId: string; - source: 'provider' | 'sdk_snapshot' | 'manual'; - digest: string; - availability: ProviderAvailability; - observedAt: Timestamp; - syncedAt: Timestamp; - staleAt: Timestamp; - expiresAt?: Timestamp; - lastErrorCode?: ProviderErrorCode; +export type ImportProviderProductsError = ImportProviderProductsErrors[keyof ImportProviderProductsErrors]; + +export type ImportProviderProductsResponses = { /** - * Normalized safe metadata only; raw provider responses are never persisted. + * Idempotent selected-import result with item-level outcomes. */ - metadata: { - [key: string]: unknown; + 200: { + data: ProviderImportResult; }; - createdAt: Timestamp; }; -export type ProviderReadinessIssue = { - code: ProviderErrorCode; - resourceType: string; - resourceId: string; - recoveryAction: string; +export type ImportProviderProductsResponse = ImportProviderProductsResponses[keyof ImportProviderProductsResponses]; + +export type ListApiKeysData = { + body?: never; + path: { + environmentId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + kind?: ApiKeyKind; + state?: 'active' | 'revoked'; + }; + url: '/v1/environments/{environmentId}/api-keys'; }; -export type ProviderReadiness = { - state: 'configured' | 'verifiedInTest' | 'attentionRequired' | 'unavailable' | 'archived'; - productId: string; - environmentId: string; - applicationId: string; - platform: Platform; - connectionId?: string; - provider?: ProviderKind; - mappingId?: string; - observation?: ProviderMappingObservation; - blockers: Array; - warnings: Array; - evaluatedAt: Timestamp; +export type ListApiKeysErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProviderMappingObservation = { - id: string; - projectId: string; - mappingId: string; - environmentId: string; - applicationId: string; - platform: Platform; - provider: ProviderKind; - adapterVersion: string; - storeContext: 'storekitConfiguration' | 'appleSandbox' | 'googlePlayTest' | 'production' | 'unknown'; - result: 'available' | 'unavailable' | 'failed'; - diagnosticCode?: string; - correlationId: string; - metadata: ProviderMappingObservationMetadata; - observedAt: Timestamp; - expiresAt?: Timestamp; - receivedAt: Timestamp; - createdByActorId: string; -}; +export type ListApiKeysError = ListApiKeysErrors[keyof ListApiKeysErrors]; -/** - * Closed operational metadata. Unknown fields and receipt, token, credential, customer, account, authorization, or secret-like material are rejected. - */ -export type ProviderMappingObservationMetadata = { - clientPlatform?: 'ios' | 'android' | 'flutter'; - clientVersion?: string; - applicationVersion?: string; - osVersion?: string; - configurationSource?: 'bundled' | 'remote' | 'local' | 'unknown'; - storefrontCountryCode?: string; - testScenario?: 'productLoad' | 'configurationAcceptance' | 'purchasePresentation' | 'restore'; +export type ListApiKeysResponses = { + /** + * API keys without secrets + */ + 200: ApiKeyList; }; -export type ProviderMappingUsage = { - mapping: ProviderProductMapping; - product: Product; - usage: ProductUsage; +export type ListApiKeysResponse = ListApiKeysResponses[keyof ListApiKeysResponses]; + +export type CreateApiKeyData = { + body: CreateApiKeyRequest; + path: { + environmentId: string; + }; + query?: never; + url: '/v1/environments/{environmentId}/api-keys'; }; -export type ProviderProfile = { - provider: ProviderKind; - displayName: string; - platform: Platform; - adapterVersion: string; - capabilities: Array; +export type CreateApiKeyErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProductUsage = { - productId: string; - plans: Array; - entitlements: Array; - providerMappings: Array; - historicalReferences: Array; +export type CreateApiKeyError = CreateApiKeyErrors[keyof CreateApiKeyErrors]; + +export type CreateApiKeyResponses = { + /** + * One-time secret result + */ + 201: ApiKeySecretEnvelope; }; -export type AuditEvent = { - id: string; - actorId: string; - organizationId: string; - projectId?: string; - environmentId?: string; - action: string; - resourceType: string; - resourceId: string; - metadata: { - [key: string]: string; +export type CreateApiKeyResponse = CreateApiKeyResponses[keyof CreateApiKeyResponses]; + +export type UpdateEnvironmentData = { + body: CreateOrganizationRequest; + path: { + environmentId: string; }; - createdAt: Timestamp; + query?: never; + url: '/v1/environments/{environmentId}'; }; -export type ValidationSummary = { - errors: Array; - warnings: Array; +export type UpdateEnvironmentErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type Paywall = { - id: string; - projectId: string; - key: string; - name: string; - status: 'active' | 'archived'; - archivedAt?: Timestamp; - createdByActorId: string; - createdAt: Timestamp; - updatedAt: Timestamp; -}; +export type UpdateEnvironmentError = UpdateEnvironmentErrors[keyof UpdateEnvironmentErrors]; -export type Draft = { - id: string; - projectId: string; - paywallId: string; - environmentId: string; - status: 'active' | 'published' | 'archived'; - revision: number; - sourceVersionId?: string; - protocolVersion: '0.2'; - validationStatus: 'valid' | 'invalid'; - validation: ValidationSummary; - createdByActorId: string; - updatedByActorId: string; - createdAt: Timestamp; - updatedAt: Timestamp; +export type UpdateEnvironmentResponses = { + /** + * Environment + */ + 200: EnvironmentEnvelope; }; -export type DraftResource = { - draft: Draft; - document: { - [key: string]: unknown; - }; -}; +export type UpdateEnvironmentResponse = UpdateEnvironmentResponses[keyof UpdateEnvironmentResponses]; -export type PaywallVersion = { - id: string; - projectId: string; - paywallId: string; - environmentId: string; - versionNumber: number; - sourceDraftId: string; - sourceRevision: number; - protocolVersion: '0.2'; - document: { - [key: string]: unknown; +export type SetEnvironmentModeData = { + body: SetEnvironmentModeRequest; + path: { + environmentId: string; }; - documentHash: string; - validation: ValidationSummary; - createdByActorId: string; - createdAt: Timestamp; - productIds: Array; + query?: never; + url: '/v1/environments/{environmentId}/mode'; }; -export type Placement = { - id: string; - projectId: string; - key: string; - name: string; - description?: string; - status: 'active' | 'archived'; - archivedAt?: Timestamp; - createdByActorId: string; - createdAt: Timestamp; - updatedAt: Timestamp; +export type SetEnvironmentModeErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type PlacementBinding = { - projectId: string; - environmentId: string; - placementId: string; - paywallId: string; - updatedByActorId: string; - updatedAt: Timestamp; +export type SetEnvironmentModeError = SetEnvironmentModeErrors[keyof SetEnvironmentModeErrors]; + +export type SetEnvironmentModeResponses = { + /** + * Environment + */ + 200: EnvironmentEnvelope; }; -export type User = { - id: string; - email: string; - name: string; - createdAt: Timestamp; +export type SetEnvironmentModeResponse = SetEnvironmentModeResponses[keyof SetEnvironmentModeResponses]; + +export type ClearActiveProviderAssignmentData = { + body?: never; + path: { + environmentId: string; + applicationId: string; + }; + query?: never; + url: '/v1/environments/{environmentId}/applications/{applicationId}/active-provider'; }; -export type Asset = { - id: string; - projectId: string; - kind: 'image' | 'video'; - originalFilename: string; - mediaType: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/gif' | 'video/mp4'; - byteLength: number; - contentDigest: string; - url: string; - status: 'pending' | 'ready' | 'failed' | 'archived' | 'deleted'; - createdByActorId: string; - archivedAt?: Timestamp; - createdAt: Timestamp; - updatedAt: Timestamp; +export type ClearActiveProviderAssignmentErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type AssetUsage = { - draftReferences: number; - versionReferences: number; - releaseReferences: number; +export type ClearActiveProviderAssignmentError = ClearActiveProviderAssignmentErrors[keyof ClearActiveProviderAssignmentErrors]; + +export type ClearActiveProviderAssignmentResponses = { + /** + * The current assignment was cleared; its audit history remains immutable. + */ + 204: void; }; -export type ConfigurationRelease = { - id: string; - projectId: string; - environmentId: string; - releaseNumber: number; - deliveryContractVersion: '1'; - contentHash: string; - sourceReleaseId?: string; - rollbackSourceReleaseId?: string; - publishedByActorId: string; - publishedAt: Timestamp; +export type ClearActiveProviderAssignmentResponse = ClearActiveProviderAssignmentResponses[keyof ClearActiveProviderAssignmentResponses]; + +export type GetActiveProviderAssignmentData = { + body?: never; + path: { + environmentId: string; + applicationId: string; + }; + query?: never; + url: '/v1/environments/{environmentId}/applications/{applicationId}/active-provider'; }; -export type PublishResult = { - release: ConfigurationRelease; - warnings: Array; +export type GetActiveProviderAssignmentErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ErrorEnvelope = { - error: { - code: string; - message: string; - fields?: { - [key: string]: Array; - }; - details?: { - [key: string]: unknown; - }; - requestId?: string; - }; +export type GetActiveProviderAssignmentError = GetActiveProviderAssignmentErrors[keyof GetActiveProviderAssignmentErrors]; + +export type GetActiveProviderAssignmentResponses = { + /** + * Active provider assignment + */ + 200: ProviderAssignmentEnvelope; }; -export type HealthEnvelope = { - data: { - status: 'ok' | 'ready'; - version: string; - commit?: string; - built?: string; +export type GetActiveProviderAssignmentResponse = GetActiveProviderAssignmentResponses[keyof GetActiveProviderAssignmentResponses]; + +export type SetActiveProviderAssignmentData = { + body: SetProviderAssignmentRequest; + path: { + environmentId: string; + applicationId: string; }; + query?: never; + url: '/v1/environments/{environmentId}/applications/{applicationId}/active-provider'; }; -export type OrganizationEnvelope = { - data: Organization; +export type SetActiveProviderAssignmentErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type MembershipEnvelope = { - data: Membership; -}; +export type SetActiveProviderAssignmentError = SetActiveProviderAssignmentErrors[keyof SetActiveProviderAssignmentErrors]; -export type ProjectEnvelope = { - data: Project; +export type SetActiveProviderAssignmentResponses = { + /** + * Active provider assignment + */ + 200: ProviderAssignmentEnvelope; }; -export type ApplicationEnvelope = { - data: Application; -}; +export type SetActiveProviderAssignmentResponse = SetActiveProviderAssignmentResponses[keyof SetActiveProviderAssignmentResponses]; -export type EnvironmentEnvelope = { - data: Environment; +export type GetProviderConnectionData = { + body?: never; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}'; }; -export type ProviderConnectionEnvelope = { - data: ProviderConnection; +export type GetProviderConnectionErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProviderAssignmentEnvelope = { - data: ActiveProviderAssignment; -}; +export type GetProviderConnectionError = GetProviderConnectionErrors[keyof GetProviderConnectionErrors]; -export type ProviderReadinessEnvelope = { - data: ProviderReadiness; +export type GetProviderConnectionResponses = { + /** + * Non-secret Provider Connection metadata + */ + 200: ProviderConnectionEnvelope; }; -export type ProviderMappingUsageEnvelope = { - data: ProviderMappingUsage; -}; +export type GetProviderConnectionResponse = GetProviderConnectionResponses[keyof GetProviderConnectionResponses]; -export type ProviderMappingObservationEnvelope = { - data: ProviderMappingObservation; +export type ReplaceProviderConnectionScopesData = { + body: ReplaceProviderConnectionScopesRequest; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/scopes'; }; -export type ProviderProfileEnvelope = { - data: ProviderProfile; +export type ReplaceProviderConnectionScopesErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ApiKeyEnvelope = { - data: ApiKey; -}; +export type ReplaceProviderConnectionScopesError = ReplaceProviderConnectionScopesErrors[keyof ReplaceProviderConnectionScopesErrors]; -export type ApiKeySecretEnvelope = { - data: ApiKeySecretResult; +export type ReplaceProviderConnectionScopesResponses = { + /** + * Non-secret Provider Connection metadata + */ + 200: ProviderConnectionEnvelope; }; -export type PlanEnvelope = { - data: Plan; +export type ReplaceProviderConnectionScopesResponse = ReplaceProviderConnectionScopesResponses[keyof ReplaceProviderConnectionScopesResponses]; + +export type RevokeProviderConnectionData = { + body?: never; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/revoke'; }; -export type ProductEnvelope = { - data: Product; +export type RevokeProviderConnectionErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProductReadinessEnvelope = { - data: ProductReadiness; +export type RevokeProviderConnectionError = RevokeProviderConnectionErrors[keyof RevokeProviderConnectionErrors]; + +export type RevokeProviderConnectionResponses = { + /** + * Non-secret Provider Connection metadata + */ + 200: ProviderConnectionEnvelope; }; -export type EntitlementEnvelope = { - data: Entitlement; +export type RevokeProviderConnectionResponse = RevokeProviderConnectionResponses[keyof RevokeProviderConnectionResponses]; + +export type TestProviderConnectionData = { + body?: never; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/test'; }; -export type PlanProductEnvelope = { - data: PlanProduct; +export type TestProviderConnectionErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProductEntitlementGrantEnvelope = { - data: ProductEntitlementGrant; +export type TestProviderConnectionError = TestProviderConnectionErrors[keyof TestProviderConnectionErrors]; + +export type TestProviderConnectionResponses = { + /** + * Safe connection health and capability summary. + */ + 200: { + data: ProviderConnectionHealth; + }; }; -export type ProviderMappingEnvelope = { - data: ProviderProductMapping; +export type TestProviderConnectionResponse = TestProviderConnectionResponses[keyof TestProviderConnectionResponses]; + +export type GetProviderConnectionHealthData = { + body?: never; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/health'; }; -export type ProductUsageEnvelope = { - data: ProductUsage; +export type GetProviderConnectionHealthErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type OrganizationListEnvelope = OrganizationList; +export type GetProviderConnectionHealthError = GetProviderConnectionHealthErrors[keyof GetProviderConnectionHealthErrors]; -export type MembershipListEnvelope = MembershipList; +export type GetProviderConnectionHealthResponses = { + /** + * Safe connection health and capability summary. + */ + 200: { + data: ProviderConnectionHealth; + }; +}; -export type ProjectListEnvelope = ProjectList; +export type GetProviderConnectionHealthResponse = GetProviderConnectionHealthResponses[keyof GetProviderConnectionHealthResponses]; -export type ApplicationListEnvelope = ApplicationList; +export type GetProviderConnectionCapabilitiesData = { + body?: never; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/capabilities'; +}; -export type EnvironmentListEnvelope = EnvironmentList; +export type GetProviderConnectionCapabilitiesErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type ProviderConnectionListEnvelope = ProviderConnectionList; +export type GetProviderConnectionCapabilitiesError = GetProviderConnectionCapabilitiesErrors[keyof GetProviderConnectionCapabilitiesErrors]; -export type ApiKeyListEnvelope = ApiKeyList; +export type GetProviderConnectionCapabilitiesResponses = { + /** + * Closed provider capability matrix and required least-privilege permissions. + */ + 200: { + data: ProviderConnectionCapabilities; + }; +}; -export type PlanListEnvelope = PlanList; +export type GetProviderConnectionCapabilitiesResponse = GetProviderConnectionCapabilitiesResponses[keyof GetProviderConnectionCapabilitiesResponses]; -export type ProductListEnvelope = ProductList; +export type ListProviderConnectionDiagnosticsData = { + body?: never; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/diagnostics'; +}; + +export type ListProviderConnectionDiagnosticsErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type EntitlementListEnvelope = EntitlementList; +export type ListProviderConnectionDiagnosticsError = ListProviderConnectionDiagnosticsErrors[keyof ListProviderConnectionDiagnosticsErrors]; -export type ProviderMappingListEnvelope = ProviderMappingList; +export type ListProviderConnectionDiagnosticsResponses = { + /** + * Safe provider diagnostics without provider response bodies or secrets. + */ + 200: { + data: { + items: Array; + }; + }; +}; -export type AuditEventListEnvelope = AuditEventList; +export type ListProviderConnectionDiagnosticsResponse = ListProviderConnectionDiagnosticsResponses[keyof ListProviderConnectionDiagnosticsResponses]; -export type PaywallEnvelope = { - data: Paywall; +export type PreviewProviderCatalogData = { + body?: never; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/catalog-preview'; }; -export type DraftEnvelope = { - data: DraftResource; +export type PreviewProviderCatalogErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ValidationSummaryEnvelope = { - data: ValidationSummary; -}; +export type PreviewProviderCatalogError = PreviewProviderCatalogErrors[keyof PreviewProviderCatalogErrors]; -export type PaywallVersionEnvelope = { - data: PaywallVersion; +export type PreviewProviderCatalogResponses = { + /** + * Normalized live provider catalog preview. + */ + 200: { + data: ProviderCatalogPreview; + }; }; -export type PlacementEnvelope = { - data: Placement; -}; +export type PreviewProviderCatalogResponse = PreviewProviderCatalogResponses[keyof PreviewProviderCatalogResponses]; -export type PlacementBindingEnvelope = { - data: PlacementBinding; +export type RotateProviderCredentialData = { + body: ProviderCredentialRequest; + path: { + connectionId: string; + }; + query?: never; + url: '/v1/provider-connections/{connectionId}/rotate-credential'; }; -export type AssetEnvelope = { - data: Asset; +export type RotateProviderCredentialErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type AssetUsageEnvelope = { - data: AssetUsage; +export type RotateProviderCredentialError = RotateProviderCredentialErrors[keyof RotateProviderCredentialErrors]; + +export type RotateProviderCredentialResponses = { + /** + * Non-secret Provider Connection metadata + */ + 200: ProviderConnectionEnvelope; }; -export type AssetListEnvelope = { - data: { - items: Array; - page: Page; +export type RotateProviderCredentialResponse = RotateProviderCredentialResponses[keyof RotateProviderCredentialResponses]; + +export type ReconnectProviderConnectionData = { + body: ProviderCredentialRequest; + path: { + connectionId: string; }; + query?: never; + url: '/v1/provider-connections/{connectionId}/reconnect'; }; -export type UserEnvelope = { - data: User; +export type ReconnectProviderConnectionErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ReleaseEnvelope = { - data: ConfigurationRelease; -}; +export type ReconnectProviderConnectionError = ReconnectProviderConnectionErrors[keyof ReconnectProviderConnectionErrors]; -export type PublishResultEnvelope = { - data: PublishResult; +export type ReconnectProviderConnectionResponses = { + /** + * Non-secret Provider Connection metadata + */ + 200: ProviderConnectionEnvelope; }; -export type PaywallListEnvelope = { - data: { - items: Array; - page: Page; +export type ReconnectProviderConnectionResponse = ReconnectProviderConnectionResponses[keyof ReconnectProviderConnectionResponses]; + +export type EnqueueProviderSyncData = { + body?: never; + path: { + connectionId: string; }; + query?: never; + url: '/v1/provider-connections/{connectionId}/sync'; }; -export type PaywallVersionListEnvelope = { - data: { - items: Array; - page: Page; - }; +export type EnqueueProviderSyncErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type PlacementListEnvelope = { - data: { - items: Array; - page: Page; +export type EnqueueProviderSyncError = EnqueueProviderSyncErrors[keyof EnqueueProviderSyncErrors]; + +export type EnqueueProviderSyncResponses = { + /** + * Accepted provider synchronization job. + */ + 202: { + data: ProviderSyncJob; }; }; -export type ReleaseListEnvelope = { - data: { - items: Array; - page: Page; +export type EnqueueProviderSyncResponse = EnqueueProviderSyncResponses[keyof EnqueueProviderSyncResponses]; + +export type ListProviderSyncRunsData = { + body?: never; + path: { + connectionId: string; }; + query?: never; + url: '/v1/provider-connections/{connectionId}/sync-runs'; }; -export type OrganizationList = { - data: { - items: Array; - page: Page; - }; +export type ListProviderSyncRunsErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type MembershipList = { - data: { - items: Array; - page: Page; +export type ListProviderSyncRunsError = ListProviderSyncRunsErrors[keyof ListProviderSyncRunsErrors]; + +export type ListProviderSyncRunsResponses = { + /** + * Provider synchronization run history. + */ + 200: { + data: { + items: Array; + }; }; }; -export type ProjectList = { - data: { - items: Array; - page: Page; +export type ListProviderSyncRunsResponse = ListProviderSyncRunsResponses[keyof ListProviderSyncRunsResponses]; + +export type RotateApiKeyData = { + body?: never; + path: { + apiKeyId: string; }; + query?: never; + url: '/v1/api-keys/{apiKeyId}/rotate'; }; -export type ApplicationList = { - data: { - items: Array; - page: Page; - }; +export type RotateApiKeyErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type EnvironmentList = { - data: { - items: Array; - page: Page; - }; +export type RotateApiKeyError = RotateApiKeyErrors[keyof RotateApiKeyErrors]; + +export type RotateApiKeyResponses = { + /** + * One-time secret result + */ + 200: ApiKeySecretEnvelope; }; -export type ProviderConnectionList = { - data: { - items: Array; - page: Page; +export type RotateApiKeyResponse = RotateApiKeyResponses[keyof RotateApiKeyResponses]; + +export type RevokeApiKeyData = { + body?: never; + path: { + apiKeyId: string; }; + query?: never; + url: '/v1/api-keys/{apiKeyId}/revoke'; }; -export type ApiKeyList = { - data: { - items: Array; - page: Page; - }; +export type RevokeApiKeyErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type PlanList = { - data: { - items: Array; - page: Page; - }; +export type RevokeApiKeyError = RevokeApiKeyErrors[keyof RevokeApiKeyErrors]; + +export type RevokeApiKeyResponses = { + /** + * API key metadata + */ + 200: ApiKeyEnvelope; }; -export type ProductList = { - data: { - items: Array; - page: Page; +export type RevokeApiKeyResponse = RevokeApiKeyResponses[keyof RevokeApiKeyResponses]; + +export type ListPlansData = { + body?: never; + path: { + projectId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; }; + url: '/v1/projects/{projectId}/plans'; }; -export type EntitlementList = { - data: { - items: Array; - page: Page; - }; +export type ListPlansErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ProviderMappingList = { - data: { - items: Array; - page: Page; - }; +export type ListPlansError = ListPlansErrors[keyof ListPlansErrors]; + +export type ListPlansResponses = { + /** + * Plans + */ + 200: PlanList; }; -export type AuditEventList = { - data: { - items: Array; - page: Page; +export type ListPlansResponse = ListPlansResponses[keyof ListPlansResponses]; + +export type CreatePlanData = { + body: CreateCatalogResourceRequest; + path: { + projectId: string; }; + query?: never; + url: '/v1/projects/{projectId}/plans'; }; -export type ExperimentQaOverrideCreatedWritable = { - override: ExperimentQaOverride; +export type CreatePlanErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; }; -export type ExperimentQaOverrideCreatedEnvelopeWritable = { - data: ExperimentQaOverrideCreatedWritable; -}; +export type CreatePlanError = CreatePlanErrors[keyof CreatePlanErrors]; -export type CreateQaOverrideRequestWritable = { - safeLabel: string; - selector: string; - outcome: PlacementOutcome; - expiresAt: string; +export type CreatePlanResponses = { + /** + * Plan + */ + 201: PlanEnvelope; }; -export type PlacementSimulationRequestWritable = { - platform?: 'ios' | 'android'; - osVersion?: string; - applicationVersion?: string; - locale?: string; - country?: string; - installationId?: string; - userId?: string; - attributes?: { - [key: string]: unknown; - }; - entitlements?: { - [key: string]: unknown; - }; - productAvailability?: { - [key: string]: unknown; - }; - productReadiness?: { - [key: string]: unknown; - }; - providerCapabilities?: { - [key: string]: unknown; +export type CreatePlanResponse = CreatePlanResponses[keyof CreatePlanResponses]; + +export type GetPlanData = { + body?: never; + path: { + planId: string; }; - overrideToken?: string; + query?: never; + url: '/v1/plans/{planId}'; }; -export type CreateProviderConnectionRequestWritable = unknown & { - name: string; - provider: ProviderConnectionKind; - integrationMode: ProviderIntegrationMode; - mode: ProviderConnectionMode; +export type GetPlanErrors = { /** - * Required RevenueCat v2 Project resource ID. + * Stable machine-readable failure. */ - externalProjectId?: string; + default: ErrorEnvelope; +}; + +export type GetPlanError = GetPlanErrors[keyof GetPlanErrors]; + +export type GetPlanResponses = { /** - * One-time RevenueCat v2 least-privilege secret key. Never returned or logged. + * Plan */ - credential?: string; - environmentIds: Array; - applicationIds: Array; + 200: PlanEnvelope; }; -/** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ -export type Cursor = string; - -export type Limit = number; +export type GetPlanResponse = GetPlanResponses[keyof GetPlanResponses]; -export type OrganizationId = string; +export type UpdatePlanData = { + body: CreateCatalogResourceRequest; + path: { + planId: string; + }; + query?: never; + url: '/v1/plans/{planId}'; +}; -export type ActorId = string; +export type UpdatePlanErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type ProjectId = string; +export type UpdatePlanError = UpdatePlanErrors[keyof UpdatePlanErrors]; -export type WebhookDestinationId = string; +export type UpdatePlanResponses = { + /** + * Plan + */ + 200: PlanEnvelope; +}; -export type WebhookDeliveryId = string; +export type UpdatePlanResponse = UpdatePlanResponses[keyof UpdatePlanResponses]; -export type EnvironmentId = string; +export type ListPlanProductsData = { + body?: never; + path: { + planId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/plans/{planId}/products'; +}; -export type BillingCustomerId = string; +export type ListPlanProductsErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type SubscriptionInstanceId = string; +export type ListPlanProductsError = ListPlanProductsErrors[keyof ListPlanProductsErrors]; -export type RestoreJobId = string; +export type ListPlanProductsResponses = { + /** + * Products + */ + 200: ProductList; +}; -export type IdentityConflictId = string; +export type ListPlanProductsResponse = ListPlanProductsResponses[keyof ListPlanProductsResponses]; -export type ApplicationId = string; +export type AddPlanProductData = { + body: ProductReferenceRequest; + path: { + planId: string; + }; + query?: never; + url: '/v1/plans/{planId}/products'; +}; -export type ProviderConnectionId = string; +export type AddPlanProductErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type ProviderMappingId = string; +export type AddPlanProductError = AddPlanProductErrors[keyof AddPlanProductErrors]; -/** - * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not - * construct or parse one — it encodes both the ordering timestamp and the row id, because - * billing identifiers are not time-ordered and an id alone cannot express a position in a - * timestamp ordering. - * - * A malformed or stale value starts from the first page rather than erroring, so a mangled - * cursor cannot silently truncate a list. Omit it for the first page; a response with no - * `nextCursor` is the last page. - * - */ -export type BillingCursor = string; +export type AddPlanProductResponses = { + /** + * Plan membership + */ + 201: PlanProductEnvelope; +}; -export type StoreCredentialId = string; +export type AddPlanProductResponse = AddPlanProductResponses[keyof AddPlanProductResponses]; -export type QuarantineRecordId = string; +export type RemovePlanProductData = { + body?: never; + path: { + planId: string; + productId: string; + }; + query?: never; + url: '/v1/plans/{planId}/products/{productId}'; +}; -export type BillingProviderFilter = 'app_store' | 'google_play'; +export type RemovePlanProductErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type BillingFrom = string; +export type RemovePlanProductError = RemovePlanProductErrors[keyof RemovePlanProductErrors]; -export type BillingTo = string; +export type RemovePlanProductResponses = { + /** + * Product removed from Plan. + */ + 204: void; +}; -export type ApiKeyId = string; +export type RemovePlanProductResponse = RemovePlanProductResponses[keyof RemovePlanProductResponses]; -export type PlanId = string; +export type ListProductsData = { + body?: never; + path: { + projectId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + status?: ProductStatus; + type?: ProductType; + search?: string; + }; + url: '/v1/projects/{projectId}/products'; +}; -export type ProductId = string; +export type ListProductsErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type EntitlementId = string; +export type ListProductsError = ListProductsErrors[keyof ListProductsErrors]; -export type PaywallId = string; +export type ListProductsResponses = { + /** + * Products + */ + 200: ProductList; +}; -export type DraftId = string; +export type ListProductsResponse = ListProductsResponses[keyof ListProductsResponses]; -export type VersionId = string; +export type CreateProductData = { + body: CreateProductRequest; + path: { + projectId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/products'; +}; -export type PlacementId = string; +export type CreateProductErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type RuleSetId = string; +export type CreateProductError = CreateProductErrors[keyof CreateProductErrors]; -export type ExperimentId = string; +export type CreateProductResponses = { + /** + * Product + */ + 201: ProductEnvelope; +}; -export type AssetId = string; +export type CreateProductResponse = CreateProductResponses[keyof CreateProductResponses]; -export type ReleaseId = string; +export type DeleteProductData = { + body?: never; + path: { + productId: string; + }; + query?: never; + url: '/v1/products/{productId}'; +}; -export type IdempotencyKey = string; +export type DeleteProductErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type IfMatch = string; +export type DeleteProductError = DeleteProductErrors[keyof DeleteProductErrors]; -export type AnalyticsFrom = Timestamp; +export type DeleteProductResponses = { + /** + * Unreferenced Product deleted. + */ + 204: void; +}; -export type AnalyticsTo = Timestamp; +export type DeleteProductResponse = DeleteProductResponses[keyof DeleteProductResponses]; -export type AnalyticsTimezone = string; +export type GetProductData = { + body?: never; + path: { + productId: string; + }; + query?: never; + url: '/v1/products/{productId}'; +}; -export type AnalyticsMetricBasis = 'event_count'; +export type GetProductErrors = { + /** + * Stable machine-readable failure. + */ + default: ErrorEnvelope; +}; -export type AnalyticsPlatform = 'ios' | 'android'; +export type GetProductError = GetProductErrors[keyof GetProductErrors]; -export type AnalyticsLocale = string; +export type GetProductResponses = { + /** + * Product + */ + 200: ProductEnvelope; +}; -export type AnalyticsApplicationVersion = string; +export type GetProductResponse = GetProductResponses[keyof GetProductResponses]; -export type GetHealthData = { - body?: never; - path?: never; +export type UpdateProductData = { + body: CreateProductRequest; + path: { + productId: string; + }; query?: never; - url: '/health/live'; + url: '/v1/products/{productId}'; }; -export type GetHealthResponses = { +export type UpdateProductErrors = { /** - * Process liveness. + * Stable machine-readable failure. */ - 200: HealthEnvelope; + default: ErrorEnvelope; }; -export type GetHealthResponse = GetHealthResponses[keyof GetHealthResponses]; +export type UpdateProductError = UpdateProductErrors[keyof UpdateProductErrors]; -export type GetReadinessData = { +export type UpdateProductResponses = { + /** + * Product + */ + 200: ProductEnvelope; +}; + +export type UpdateProductResponse = UpdateProductResponses[keyof UpdateProductResponses]; + +export type ArchiveProductData = { body?: never; - path?: never; + path: { + productId: string; + }; query?: never; - url: '/health/ready'; + url: '/v1/products/{productId}/archive'; }; -export type GetReadinessErrors = { +export type ArchiveProductErrors = { /** - * PostgreSQL is unavailable. + * Stable machine-readable failure. */ - 503: ErrorEnvelope; + default: ErrorEnvelope; }; -export type GetReadinessError = GetReadinessErrors[keyof GetReadinessErrors]; +export type ArchiveProductError = ArchiveProductErrors[keyof ArchiveProductErrors]; -export type GetReadinessResponses = { +export type ArchiveProductResponses = { /** - * PostgreSQL is reachable and the API is ready to serve traffic. + * Product */ - 200: HealthEnvelope; + 200: ProductEnvelope; }; -export type GetReadinessResponse = GetReadinessResponses[keyof GetReadinessResponses]; +export type ArchiveProductResponse = ArchiveProductResponses[keyof ArchiveProductResponses]; -export type SignUpData = { - body: SignUpRequest; - path?: never; +export type RestoreProductData = { + body?: never; + path: { + productId: string; + }; query?: never; - url: '/v1/auth/signup'; + url: '/v1/products/{productId}/restore'; }; -export type SignUpErrors = { - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; +export type RestoreProductErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type SignUpError = SignUpErrors[keyof SignUpErrors]; +export type RestoreProductError = RestoreProductErrors[keyof RestoreProductErrors]; -export type SignUpResponses = { +export type RestoreProductResponses = { /** - * Authenticated browser user. Login and signup also set the session cookie. + * Product */ - 201: UserEnvelope; + 200: ProductEnvelope; }; -export type SignUpResponse = SignUpResponses[keyof SignUpResponses]; +export type RestoreProductResponse = RestoreProductResponses[keyof RestoreProductResponses]; -export type LoginData = { - body: LoginRequest; - path?: never; +export type SetProductReplacementData = { + body: ProductReferenceRequest; + path: { + productId: string; + }; query?: never; - url: '/v1/auth/login'; + url: '/v1/products/{productId}/replacement'; }; -export type LoginErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type SetProductReplacementErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type LoginError = LoginErrors[keyof LoginErrors]; +export type SetProductReplacementError = SetProductReplacementErrors[keyof SetProductReplacementErrors]; -export type LoginResponses = { +export type SetProductReplacementResponses = { /** - * Authenticated browser user. Login and signup also set the session cookie. + * Product */ - 200: UserEnvelope; + 200: ProductEnvelope; }; -export type LoginResponse = LoginResponses[keyof LoginResponses]; +export type SetProductReplacementResponse = SetProductReplacementResponses[keyof SetProductReplacementResponses]; -export type LogoutData = { +export type GetProductUsageData = { body?: never; - path?: never; + path: { + productId: string; + }; query?: never; - url: '/v1/auth/logout'; + url: '/v1/products/{productId}/usage'; }; -export type LogoutResponses = { +export type GetProductUsageErrors = { /** - * Session revoked and cookie cleared. + * Stable machine-readable failure. */ - 204: void; + default: ErrorEnvelope; }; -export type LogoutResponse = LogoutResponses[keyof LogoutResponses]; +export type GetProductUsageError = GetProductUsageErrors[keyof GetProductUsageErrors]; -export type GetSessionData = { +export type GetProductUsageResponses = { + /** + * Product usage + */ + 200: ProductUsageEnvelope; +}; + +export type GetProductUsageResponse = GetProductUsageResponses[keyof GetProductUsageResponses]; + +export type GetProductReadinessData = { body?: never; - path?: never; - query?: never; - url: '/v1/auth/session'; + path: { + productId: string; + }; + query: { + environmentId: string; + applicationId: string; + }; + url: '/v1/products/{productId}/readiness'; }; -export type GetSessionErrors = { +export type GetProductReadinessErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + default: ErrorEnvelope; }; -export type GetSessionError = GetSessionErrors[keyof GetSessionErrors]; +export type GetProductReadinessError = GetProductReadinessErrors[keyof GetProductReadinessErrors]; -export type GetSessionResponses = { +export type GetProductReadinessResponses = { /** - * Authenticated browser user. Login and signup also set the session cookie. + * Scoped provider readiness and stable recovery codes */ - 200: UserEnvelope; + 200: ProviderReadinessEnvelope; }; -export type GetSessionResponse = GetSessionResponses[keyof GetSessionResponses]; +export type GetProductReadinessResponse = GetProductReadinessResponses[keyof GetProductReadinessResponses]; -export type ListOrganizationsData = { +export type ListProviderMappingsData = { body?: never; - path?: never; + path: { + productId: string; + }; query?: { /** * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. @@ -3876,2353 +6966,2539 @@ export type ListOrganizationsData = { cursor?: string; limit?: number; }; - url: '/v1/organizations'; + url: '/v1/products/{productId}/provider-mappings'; }; -export type ListOrganizationsErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type ListProviderMappingsErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type ListOrganizationsError = ListOrganizationsErrors[keyof ListOrganizationsErrors]; +export type ListProviderMappingsError = ListProviderMappingsErrors[keyof ListProviderMappingsErrors]; -export type ListOrganizationsResponses = { +export type ListProviderMappingsResponses = { /** - * Organizations + * Placeholder mappings */ - 200: OrganizationList; + 200: ProviderMappingList; }; -export type ListOrganizationsResponse = ListOrganizationsResponses[keyof ListOrganizationsResponses]; +export type ListProviderMappingsResponse = ListProviderMappingsResponses[keyof ListProviderMappingsResponses]; -export type CreateOrganizationData = { - body: CreateOrganizationRequest; - path?: never; +export type CreateProviderMappingData = { + body: CreateProviderMappingRequest; + path: { + productId: string; + }; query?: never; - url: '/v1/organizations'; + url: '/v1/products/{productId}/provider-mappings'; }; -export type CreateOrganizationErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type CreateProviderMappingErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type CreateOrganizationError = CreateOrganizationErrors[keyof CreateOrganizationErrors]; +export type CreateProviderMappingError = CreateProviderMappingErrors[keyof CreateProviderMappingErrors]; -export type CreateOrganizationResponses = { +export type CreateProviderMappingResponses = { /** - * Organization + * Placeholder mapping */ - 201: OrganizationEnvelope; + 201: ProviderMappingEnvelope; }; -export type CreateOrganizationResponse = CreateOrganizationResponses[keyof CreateOrganizationResponses]; +export type CreateProviderMappingResponse = CreateProviderMappingResponses[keyof CreateProviderMappingResponses]; -export type GetOrganizationData = { - body?: never; +export type CreateProviderMappingDraftData = { + body: CreateProviderMappingDraftRequest; path: { - organizationId: string; + productId: string; }; query?: never; - url: '/v1/organizations/{organizationId}'; + url: '/v1/products/{productId}/provider-mapping-drafts'; }; -export type GetOrganizationErrors = { +export type CreateProviderMappingDraftErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + default: ErrorEnvelope; +}; + +export type CreateProviderMappingDraftError = CreateProviderMappingDraftErrors[keyof CreateProviderMappingDraftErrors]; + +export type CreateProviderMappingDraftResponses = { /** - * Stable machine-readable failure. + * Placeholder mapping */ - 403: ErrorEnvelope; + 201: ProviderMappingEnvelope; +}; + +export type CreateProviderMappingDraftResponse = CreateProviderMappingDraftResponses[keyof CreateProviderMappingDraftResponses]; + +export type GetProviderReadinessData = { + body?: never; + path: { + productId: string; + }; + query: { + environmentId: string; + applicationId: string; + }; + url: '/v1/products/{productId}/provider-readiness'; +}; + +export type GetProviderReadinessErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + default: ErrorEnvelope; }; -export type GetOrganizationError = GetOrganizationErrors[keyof GetOrganizationErrors]; +export type GetProviderReadinessError = GetProviderReadinessErrors[keyof GetProviderReadinessErrors]; -export type GetOrganizationResponses = { +export type GetProviderReadinessResponses = { /** - * Organization + * Scoped provider readiness and stable recovery codes */ - 200: OrganizationEnvelope; + 200: ProviderReadinessEnvelope; }; -export type GetOrganizationResponse = GetOrganizationResponses[keyof GetOrganizationResponses]; +export type GetProviderReadinessResponse = GetProviderReadinessResponses[keyof GetProviderReadinessResponses]; -export type UpdateOrganizationData = { - body: CreateOrganizationRequest; +export type ArchiveProviderMappingData = { + body?: never; path: { - organizationId: string; + mappingId: string; }; query?: never; - url: '/v1/organizations/{organizationId}'; + url: '/v1/provider-mappings/{mappingId}/archive'; }; -export type UpdateOrganizationErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type ArchiveProviderMappingErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + default: ErrorEnvelope; +}; + +export type ArchiveProviderMappingError = ArchiveProviderMappingErrors[keyof ArchiveProviderMappingErrors]; + +export type ArchiveProviderMappingResponses = { /** - * Stable machine-readable failure. + * Placeholder mapping */ - 404: ErrorEnvelope; + 200: ProviderMappingEnvelope; +}; + +export type ArchiveProviderMappingResponse = ArchiveProviderMappingResponses[keyof ArchiveProviderMappingResponses]; + +export type ReplaceProviderMappingData = { + body: ReplaceProviderMappingRequest; + path: { + mappingId: string; + }; + query?: never; + url: '/v1/provider-mappings/{mappingId}/replace'; +}; + +export type ReplaceProviderMappingErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type UpdateOrganizationError = UpdateOrganizationErrors[keyof UpdateOrganizationErrors]; +export type ReplaceProviderMappingError = ReplaceProviderMappingErrors[keyof ReplaceProviderMappingErrors]; -export type UpdateOrganizationResponses = { +export type ReplaceProviderMappingResponses = { /** - * Organization + * Placeholder mapping */ - 200: OrganizationEnvelope; + 201: ProviderMappingEnvelope; }; -export type UpdateOrganizationResponse = UpdateOrganizationResponses[keyof UpdateOrganizationResponses]; +export type ReplaceProviderMappingResponse = ReplaceProviderMappingResponses[keyof ReplaceProviderMappingResponses]; -export type ListMembersData = { +export type GetProviderMappingMetadataData = { body?: never; path: { - organizationId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; + mappingId: string; }; - url: '/v1/organizations/{organizationId}/members'; + query?: never; + url: '/v1/provider-mappings/{mappingId}/metadata'; }; -export type ListMembersErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetProviderMappingMetadataErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type ListMembersError = ListMembersErrors[keyof ListMembersErrors]; +export type GetProviderMappingMetadataError = GetProviderMappingMetadataErrors[keyof GetProviderMappingMetadataErrors]; -export type ListMembersResponses = { +export type GetProviderMappingMetadataResponses = { /** - * Memberships + * Current immutable normalized provider metadata and freshness evidence. */ - 200: MembershipList; + 200: { + data: ProviderProductMetadataSnapshot; + }; }; -export type ListMembersResponse = ListMembersResponses[keyof ListMembersResponses]; +export type GetProviderMappingMetadataResponse = GetProviderMappingMetadataResponses[keyof GetProviderMappingMetadataResponses]; -export type AddMemberData = { - body: AddMemberRequest; +export type GetProviderMappingUsageData = { + body?: never; path: { - organizationId: string; + mappingId: string; }; query?: never; - url: '/v1/organizations/{organizationId}/members'; + url: '/v1/provider-mappings/{mappingId}/usage'; }; -export type AddMemberErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; +export type GetProviderMappingUsageErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type AddMemberError = AddMemberErrors[keyof AddMemberErrors]; +export type GetProviderMappingUsageError = GetProviderMappingUsageErrors[keyof GetProviderMappingUsageErrors]; -export type AddMemberResponses = { +export type GetProviderMappingUsageResponses = { /** - * Membership + * Mapping-specific replacement impact */ - 201: MembershipEnvelope; + 200: ProviderMappingUsageEnvelope; }; -export type AddMemberResponse = AddMemberResponses[keyof AddMemberResponses]; +export type GetProviderMappingUsageResponse = GetProviderMappingUsageResponses[keyof GetProviderMappingUsageResponses]; -export type RemoveMemberData = { +export type ListProviderMappingObservationsData = { body?: never; path: { - organizationId: string; - actorId: string; + mappingId: string; }; query?: never; - url: '/v1/organizations/{organizationId}/members/{actorId}'; + url: '/v1/provider-mappings/{mappingId}/observations'; }; -export type RemoveMemberErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; +export type ListProviderMappingObservationsErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + default: ErrorEnvelope; }; -export type RemoveMemberError = RemoveMemberErrors[keyof RemoveMemberErrors]; +export type ListProviderMappingObservationsError = ListProviderMappingObservationsErrors[keyof ListProviderMappingObservationsErrors]; -export type RemoveMemberResponses = { +export type ListProviderMappingObservationsResponses = { /** - * Member removed. + * Immutable native-store observation history. */ - 204: void; + 200: { + data: Array; + }; }; -export type RemoveMemberResponse = RemoveMemberResponses[keyof RemoveMemberResponses]; +export type ListProviderMappingObservationsResponse = ListProviderMappingObservationsResponses[keyof ListProviderMappingObservationsResponses]; -export type UpdateMemberData = { - body: UpdateMemberRequest; +export type CreateProviderMappingObservationData = { + body: CreateProviderMappingObservationRequest; path: { - organizationId: string; - actorId: string; + mappingId: string; }; query?: never; - url: '/v1/organizations/{organizationId}/members/{actorId}'; + url: '/v1/provider-mappings/{mappingId}/observations'; }; -export type UpdateMemberErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; +export type CreateProviderMappingObservationErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + default: ErrorEnvelope; }; -export type UpdateMemberError = UpdateMemberErrors[keyof UpdateMemberErrors]; +export type CreateProviderMappingObservationError = CreateProviderMappingObservationErrors[keyof CreateProviderMappingObservationErrors]; -export type UpdateMemberResponses = { +export type CreateProviderMappingObservationResponses = { /** - * Membership + * Accepted immutable native-store test observation */ - 200: MembershipEnvelope; + 201: ProviderMappingObservationEnvelope; }; -export type UpdateMemberResponse = UpdateMemberResponses[keyof UpdateMemberResponses]; +export type CreateProviderMappingObservationResponse = CreateProviderMappingObservationResponses[keyof CreateProviderMappingObservationResponses]; -export type ListAuditEventsData = { +export type GetNativeProviderProfileData = { body?: never; path: { - organizationId: string; + provider: 'app_store' | 'google_play'; }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; - projectId?: string; - action?: string; + query: { + platform: Platform; }; - url: '/v1/organizations/{organizationId}/audit-events'; + url: '/v1/native-providers/{provider}/profile'; }; -export type ListAuditEventsErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetNativeProviderProfileErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type ListAuditEventsError = ListAuditEventsErrors[keyof ListAuditEventsErrors]; +export type GetNativeProviderProfileError = GetNativeProviderProfileErrors[keyof GetNativeProviderProfileErrors]; -export type ListAuditEventsResponses = { +export type GetNativeProviderProfileResponses = { /** - * Audit events + * Credential-free native provider capability profile */ - 200: AuditEventList; + 200: ProviderProfileEnvelope; }; -export type ListAuditEventsResponse = ListAuditEventsResponses[keyof ListAuditEventsResponses]; +export type GetNativeProviderProfileResponse = GetNativeProviderProfileResponses[keyof GetNativeProviderProfileResponses]; -export type ListProjectsData = { +export type ListEntitlementsData = { body?: never; - path?: never; - query: { - organizationId: string; - status?: ProjectStatus; + path: { + projectId: string; + }; + query?: { /** * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ cursor?: string; limit?: number; }; - url: '/v1/projects'; + url: '/v1/projects/{projectId}/entitlements'; }; -export type ListProjectsErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type ListEntitlementsErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type ListProjectsError = ListProjectsErrors[keyof ListProjectsErrors]; +export type ListEntitlementsError = ListEntitlementsErrors[keyof ListEntitlementsErrors]; -export type ListProjectsResponses = { +export type ListEntitlementsResponses = { /** - * Projects + * Entitlement definitions */ - 200: ProjectList; + 200: EntitlementList; }; -export type ListProjectsResponse = ListProjectsResponses[keyof ListProjectsResponses]; +export type ListEntitlementsResponse = ListEntitlementsResponses[keyof ListEntitlementsResponses]; -export type CreateProjectData = { - body: CreateProjectRequest; - path?: never; +export type CreateEntitlementData = { + body: CreateCatalogResourceRequest; + path: { + projectId: string; + }; query?: never; - url: '/v1/projects'; + url: '/v1/projects/{projectId}/entitlements'; }; -export type CreateProjectErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; +export type CreateEntitlementErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + default: ErrorEnvelope; }; -export type CreateProjectError = CreateProjectErrors[keyof CreateProjectErrors]; +export type CreateEntitlementError = CreateEntitlementErrors[keyof CreateEntitlementErrors]; -export type CreateProjectResponses = { +export type CreateEntitlementResponses = { /** - * Project + * Entitlement definition */ - 201: ProjectEnvelope; + 201: EntitlementEnvelope; }; -export type CreateProjectResponse = CreateProjectResponses[keyof CreateProjectResponses]; +export type CreateEntitlementResponse = CreateEntitlementResponses[keyof CreateEntitlementResponses]; -export type GetProjectData = { +export type GetEntitlementData = { body?: never; path: { - projectId: string; + entitlementId: string; }; query?: never; - url: '/v1/projects/{projectId}'; + url: '/v1/entitlements/{entitlementId}'; }; -export type GetProjectErrors = { +export type GetEntitlementErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + default: ErrorEnvelope; +}; + +export type GetEntitlementError = GetEntitlementErrors[keyof GetEntitlementErrors]; + +export type GetEntitlementResponses = { /** - * Stable machine-readable failure. + * Entitlement definition */ - 403: ErrorEnvelope; + 200: EntitlementEnvelope; +}; + +export type GetEntitlementResponse = GetEntitlementResponses[keyof GetEntitlementResponses]; + +export type UpdateEntitlementData = { + body: CreateCatalogResourceRequest; + path: { + entitlementId: string; + }; + query?: never; + url: '/v1/entitlements/{entitlementId}'; +}; + +export type UpdateEntitlementErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + default: ErrorEnvelope; }; -export type GetProjectError = GetProjectErrors[keyof GetProjectErrors]; +export type UpdateEntitlementError = UpdateEntitlementErrors[keyof UpdateEntitlementErrors]; -export type GetProjectResponses = { +export type UpdateEntitlementResponses = { /** - * Project + * Entitlement definition */ - 200: ProjectEnvelope; + 200: EntitlementEnvelope; }; -export type GetProjectResponse = GetProjectResponses[keyof GetProjectResponses]; +export type UpdateEntitlementResponse = UpdateEntitlementResponses[keyof UpdateEntitlementResponses]; -export type UpdateProjectData = { - body: CreateOrganizationRequest; +export type ListProductEntitlementsData = { + body?: never; path: { - projectId: string; + productId: string; }; - query?: never; - url: '/v1/projects/{projectId}'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + limit?: number; + }; + url: '/v1/products/{productId}/entitlements'; }; -export type UpdateProjectErrors = { +export type ListProductEntitlementsErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type UpdateProjectError = UpdateProjectErrors[keyof UpdateProjectErrors]; +export type ListProductEntitlementsError = ListProductEntitlementsErrors[keyof ListProductEntitlementsErrors]; -export type UpdateProjectResponses = { +export type ListProductEntitlementsResponses = { /** - * Project + * Entitlement definitions */ - 200: ProjectEnvelope; + 200: EntitlementList; }; -export type UpdateProjectResponse = UpdateProjectResponses[keyof UpdateProjectResponses]; +export type ListProductEntitlementsResponse = ListProductEntitlementsResponses[keyof ListProductEntitlementsResponses]; -export type ArchiveProjectData = { - body?: never; +export type AddProductEntitlementData = { + body: EntitlementReferenceRequest; path: { - projectId: string; + productId: string; }; query?: never; - url: '/v1/projects/{projectId}/archive'; + url: '/v1/products/{productId}/entitlements'; }; -export type ArchiveProjectErrors = { +export type AddProductEntitlementErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ArchiveProjectError = ArchiveProjectErrors[keyof ArchiveProjectErrors]; +export type AddProductEntitlementError = AddProductEntitlementErrors[keyof AddProductEntitlementErrors]; -export type ArchiveProjectResponses = { +export type AddProductEntitlementResponses = { /** - * Project + * Product grant */ - 200: ProjectEnvelope; + 201: ProductEntitlementGrantEnvelope; }; -export type ArchiveProjectResponse = ArchiveProjectResponses[keyof ArchiveProjectResponses]; +export type AddProductEntitlementResponse = AddProductEntitlementResponses[keyof AddProductEntitlementResponses]; -export type RestoreProjectData = { +export type RemoveProductEntitlementData = { body?: never; path: { - projectId: string; + productId: string; + entitlementId: string; }; query?: never; - url: '/v1/projects/{projectId}/restore'; + url: '/v1/products/{productId}/entitlements/{entitlementId}'; }; -export type RestoreProjectErrors = { +export type RemoveProductEntitlementErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type RestoreProjectError = RestoreProjectErrors[keyof RestoreProjectErrors]; +export type RemoveProductEntitlementError = RemoveProductEntitlementErrors[keyof RemoveProductEntitlementErrors]; -export type RestoreProjectResponses = { +export type RemoveProductEntitlementResponses = { /** - * Project + * Entitlement grant removed. */ - 200: ProjectEnvelope; + 204: void; }; -export type RestoreProjectResponse = RestoreProjectResponses[keyof RestoreProjectResponses]; +export type RemoveProductEntitlementResponse = RemoveProductEntitlementResponses[keyof RemoveProductEntitlementResponses]; -export type ListApplicationsData = { +export type ListAssetsData = { body?: never; path: { projectId: string; }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; - }; - url: '/v1/projects/{projectId}/applications'; + query?: never; + url: '/v1/projects/{projectId}/assets'; }; -export type ListApplicationsErrors = { +export type ListAssetsErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ListApplicationsError = ListApplicationsErrors[keyof ListApplicationsErrors]; +export type ListAssetsError = ListAssetsErrors[keyof ListAssetsErrors]; -export type ListApplicationsResponses = { +export type ListAssetsResponses = { /** - * Applications + * Hosted Assets */ - 200: ApplicationList; + 200: AssetListEnvelope; }; -export type ListApplicationsResponse = ListApplicationsResponses[keyof ListApplicationsResponses]; +export type ListAssetsResponse = ListAssetsResponses[keyof ListAssetsResponses]; -export type CreateApplicationData = { - body: CreateApplicationRequest; +export type UploadAssetData = { + body: { + file: Blob | File; + }; path: { projectId: string; }; query?: never; - url: '/v1/projects/{projectId}/applications'; + url: '/v1/projects/{projectId}/assets'; }; -export type CreateApplicationErrors = { +export type UploadAssetErrors = { + /** + * Stable machine-readable failure. + */ + 413: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type CreateApplicationError = CreateApplicationErrors[keyof CreateApplicationErrors]; +export type UploadAssetError = UploadAssetErrors[keyof UploadAssetErrors]; -export type CreateApplicationResponses = { +export type UploadAssetResponses = { /** - * Application + * Hosted Asset metadata */ - 201: ApplicationEnvelope; + 201: AssetEnvelope; }; -export type CreateApplicationResponse = CreateApplicationResponses[keyof CreateApplicationResponses]; +export type UploadAssetResponse = UploadAssetResponses[keyof UploadAssetResponses]; -export type ListEnvironmentsData = { +export type ArchiveAssetData = { body?: never; path: { projectId: string; + assetId: string; }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; - }; - url: '/v1/projects/{projectId}/environments'; + query?: never; + url: '/v1/projects/{projectId}/assets/{assetId}'; }; -export type ListEnvironmentsErrors = { +export type ArchiveAssetErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ListEnvironmentsError = ListEnvironmentsErrors[keyof ListEnvironmentsErrors]; +export type ArchiveAssetError = ArchiveAssetErrors[keyof ArchiveAssetErrors]; -export type ListEnvironmentsResponses = { +export type ArchiveAssetResponses = { /** - * Environments + * Hosted Asset metadata */ - 200: EnvironmentList; + 200: AssetEnvelope; }; -export type ListEnvironmentsResponse = ListEnvironmentsResponses[keyof ListEnvironmentsResponses]; +export type ArchiveAssetResponse = ArchiveAssetResponses[keyof ArchiveAssetResponses]; -export type ListProviderConnectionsData = { +export type GetAssetData = { body?: never; path: { projectId: string; + assetId: string; }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; - }; - url: '/v1/projects/{projectId}/provider-connections'; + query?: never; + url: '/v1/projects/{projectId}/assets/{assetId}'; }; -export type ListProviderConnectionsErrors = { +export type GetAssetErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ListProviderConnectionsError = ListProviderConnectionsErrors[keyof ListProviderConnectionsErrors]; +export type GetAssetError = GetAssetErrors[keyof GetAssetErrors]; -export type ListProviderConnectionsResponses = { +export type GetAssetResponses = { /** - * Non-secret Provider Connections + * Hosted Asset metadata */ - 200: ProviderConnectionList; + 200: AssetEnvelope; }; -export type ListProviderConnectionsResponse = ListProviderConnectionsResponses[keyof ListProviderConnectionsResponses]; +export type GetAssetResponse = GetAssetResponses[keyof GetAssetResponses]; -export type CreateProviderConnectionData = { - body: CreateProviderConnectionRequestWritable; +export type GetAssetUsageData = { + body?: never; path: { projectId: string; + assetId: string; }; query?: never; - url: '/v1/projects/{projectId}/provider-connections'; + url: '/v1/projects/{projectId}/assets/{assetId}/usage'; }; -export type CreateProviderConnectionErrors = { +export type GetAssetUsageErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type CreateProviderConnectionError = CreateProviderConnectionErrors[keyof CreateProviderConnectionErrors]; +export type GetAssetUsageError = GetAssetUsageErrors[keyof GetAssetUsageErrors]; -export type CreateProviderConnectionResponses = { +export type GetAssetUsageResponses = { /** - * Non-secret Provider Connection metadata + * Hosted Asset reference counts */ - 201: ProviderConnectionEnvelope; + 200: AssetUsageEnvelope; }; -export type CreateProviderConnectionResponse = CreateProviderConnectionResponses[keyof CreateProviderConnectionResponses]; +export type GetAssetUsageResponse = GetAssetUsageResponses[keyof GetAssetUsageResponses]; -export type ImportProviderProductsData = { - body: ProviderImportRequest; - headers: { - 'Idempotency-Key': string; - }; +export type ListPaywallsData = { + body?: never; path: { projectId: string; }; query?: never; - url: '/v1/projects/{projectId}/provider-imports'; + url: '/v1/projects/{projectId}/paywalls'; }; -export type ImportProviderProductsErrors = { +export type ListPaywallsErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ImportProviderProductsError = ImportProviderProductsErrors[keyof ImportProviderProductsErrors]; +export type ListPaywallsError = ListPaywallsErrors[keyof ListPaywallsErrors]; -export type ImportProviderProductsResponses = { +export type ListPaywallsResponses = { /** - * Idempotent selected-import result with item-level outcomes. + * Paywalls */ - 200: { - data: ProviderImportResult; - }; + 200: PaywallListEnvelope; }; -export type ImportProviderProductsResponse = ImportProviderProductsResponses[keyof ImportProviderProductsResponses]; +export type ListPaywallsResponse = ListPaywallsResponses[keyof ListPaywallsResponses]; -export type ListApiKeysData = { - body?: never; +export type CreatePaywallData = { + body: CreatePaywallRequest; path: { - environmentId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; - kind?: ApiKeyKind; - state?: 'active' | 'revoked'; + projectId: string; }; - url: '/v1/environments/{environmentId}/api-keys'; + query?: never; + url: '/v1/projects/{projectId}/paywalls'; }; -export type ListApiKeysErrors = { +export type CreatePaywallErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ListApiKeysError = ListApiKeysErrors[keyof ListApiKeysErrors]; +export type CreatePaywallError = CreatePaywallErrors[keyof CreatePaywallErrors]; -export type ListApiKeysResponses = { +export type CreatePaywallResponses = { /** - * API keys without secrets + * Paywall */ - 200: ApiKeyList; + 201: PaywallEnvelope; }; -export type ListApiKeysResponse = ListApiKeysResponses[keyof ListApiKeysResponses]; +export type CreatePaywallResponse = CreatePaywallResponses[keyof CreatePaywallResponses]; -export type CreateApiKeyData = { - body: CreateApiKeyRequest; +export type GetPaywallData = { + body?: never; path: { - environmentId: string; + projectId: string; + paywallId: string; }; query?: never; - url: '/v1/environments/{environmentId}/api-keys'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}'; }; -export type CreateApiKeyErrors = { +export type GetPaywallErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type CreateApiKeyError = CreateApiKeyErrors[keyof CreateApiKeyErrors]; +export type GetPaywallError = GetPaywallErrors[keyof GetPaywallErrors]; -export type CreateApiKeyResponses = { +export type GetPaywallResponses = { /** - * One-time secret result + * Paywall */ - 201: ApiKeySecretEnvelope; + 200: PaywallEnvelope; }; -export type CreateApiKeyResponse = CreateApiKeyResponses[keyof CreateApiKeyResponses]; +export type GetPaywallResponse = GetPaywallResponses[keyof GetPaywallResponses]; -export type UpdateEnvironmentData = { - body: CreateOrganizationRequest; +export type UpdatePaywallData = { + body: UpdatePaywallRequest; path: { - environmentId: string; + projectId: string; + paywallId: string; }; query?: never; - url: '/v1/environments/{environmentId}'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}'; }; -export type UpdateEnvironmentErrors = { +export type UpdatePaywallErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type UpdateEnvironmentError = UpdateEnvironmentErrors[keyof UpdateEnvironmentErrors]; +export type UpdatePaywallError = UpdatePaywallErrors[keyof UpdatePaywallErrors]; -export type UpdateEnvironmentResponses = { +export type UpdatePaywallResponses = { /** - * Environment + * Paywall */ - 200: EnvironmentEnvelope; + 200: PaywallEnvelope; }; -export type UpdateEnvironmentResponse = UpdateEnvironmentResponses[keyof UpdateEnvironmentResponses]; +export type UpdatePaywallResponse = UpdatePaywallResponses[keyof UpdatePaywallResponses]; -export type SetEnvironmentModeData = { - body: SetEnvironmentModeRequest; +export type CreatePaywallDraftData = { + body: CreateDraftRequest; + headers: { + 'Idempotency-Key': string; + }; path: { - environmentId: string; + projectId: string; + paywallId: string; }; query?: never; - url: '/v1/environments/{environmentId}/mode'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts'; }; -export type SetEnvironmentModeErrors = { +export type CreatePaywallDraftErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type SetEnvironmentModeError = SetEnvironmentModeErrors[keyof SetEnvironmentModeErrors]; +export type CreatePaywallDraftError = CreatePaywallDraftErrors[keyof CreatePaywallDraftErrors]; -export type SetEnvironmentModeResponses = { +export type CreatePaywallDraftResponses = { /** - * Environment + * Hosted Draft and current immutable revision document. */ - 200: EnvironmentEnvelope; + 201: DraftEnvelope; }; -export type SetEnvironmentModeResponse = SetEnvironmentModeResponses[keyof SetEnvironmentModeResponses]; +export type CreatePaywallDraftResponse = CreatePaywallDraftResponses[keyof CreatePaywallDraftResponses]; -export type ClearActiveProviderAssignmentData = { +export type GetActivePaywallDraftData = { body?: never; path: { + projectId: string; + paywallId: string; + }; + query: { environmentId: string; - applicationId: string; }; - query?: never; - url: '/v1/environments/{environmentId}/applications/{applicationId}/active-provider'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/active'; }; -export type ClearActiveProviderAssignmentErrors = { +export type GetActivePaywallDraftErrors = { + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ClearActiveProviderAssignmentError = ClearActiveProviderAssignmentErrors[keyof ClearActiveProviderAssignmentErrors]; +export type GetActivePaywallDraftError = GetActivePaywallDraftErrors[keyof GetActivePaywallDraftErrors]; -export type ClearActiveProviderAssignmentResponses = { +export type GetActivePaywallDraftResponses = { /** - * The current assignment was cleared; its audit history remains immutable. + * Hosted Draft and current immutable revision document. */ - 204: void; + 200: DraftEnvelope; }; -export type ClearActiveProviderAssignmentResponse = ClearActiveProviderAssignmentResponses[keyof ClearActiveProviderAssignmentResponses]; +export type GetActivePaywallDraftResponse = GetActivePaywallDraftResponses[keyof GetActivePaywallDraftResponses]; -export type GetActiveProviderAssignmentData = { +export type GetPaywallDraftData = { body?: never; path: { - environmentId: string; - applicationId: string; + projectId: string; + paywallId: string; + draftId: string; }; query?: never; - url: '/v1/environments/{environmentId}/applications/{applicationId}/active-provider'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/{draftId}'; }; -export type GetActiveProviderAssignmentErrors = { +export type GetPaywallDraftErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type GetActiveProviderAssignmentError = GetActiveProviderAssignmentErrors[keyof GetActiveProviderAssignmentErrors]; +export type GetPaywallDraftError = GetPaywallDraftErrors[keyof GetPaywallDraftErrors]; -export type GetActiveProviderAssignmentResponses = { +export type GetPaywallDraftResponses = { /** - * Active provider assignment + * Hosted Draft and current immutable revision document. */ - 200: ProviderAssignmentEnvelope; + 200: DraftEnvelope; }; -export type GetActiveProviderAssignmentResponse = GetActiveProviderAssignmentResponses[keyof GetActiveProviderAssignmentResponses]; +export type GetPaywallDraftResponse = GetPaywallDraftResponses[keyof GetPaywallDraftResponses]; -export type SetActiveProviderAssignmentData = { - body: SetProviderAssignmentRequest; +export type UpdatePaywallDraftData = { + body: UpdateDraftRequest; + headers: { + 'If-Match': string; + 'Idempotency-Key': string; + }; path: { - environmentId: string; - applicationId: string; + projectId: string; + paywallId: string; + draftId: string; }; query?: never; - url: '/v1/environments/{environmentId}/applications/{applicationId}/active-provider'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/{draftId}'; }; -export type SetActiveProviderAssignmentErrors = { +export type UpdatePaywallDraftErrors = { + /** + * Stable machine-readable failure. + */ + 412: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 428: ErrorEnvelope; /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type SetActiveProviderAssignmentError = SetActiveProviderAssignmentErrors[keyof SetActiveProviderAssignmentErrors]; +export type UpdatePaywallDraftError = UpdatePaywallDraftErrors[keyof UpdatePaywallDraftErrors]; -export type SetActiveProviderAssignmentResponses = { +export type UpdatePaywallDraftResponses = { /** - * Active provider assignment + * Hosted Draft and current immutable revision document. */ - 200: ProviderAssignmentEnvelope; + 200: DraftEnvelope; }; -export type SetActiveProviderAssignmentResponse = SetActiveProviderAssignmentResponses[keyof SetActiveProviderAssignmentResponses]; +export type UpdatePaywallDraftResponse = UpdatePaywallDraftResponses[keyof UpdatePaywallDraftResponses]; -export type GetProviderConnectionData = { +export type ValidatePaywallDraftData = { body?: never; path: { - connectionId: string; + projectId: string; + paywallId: string; + draftId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/{draftId}/validate'; }; -export type GetProviderConnectionErrors = { +export type ValidatePaywallDraftErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type GetProviderConnectionError = GetProviderConnectionErrors[keyof GetProviderConnectionErrors]; +export type ValidatePaywallDraftError = ValidatePaywallDraftErrors[keyof ValidatePaywallDraftErrors]; -export type GetProviderConnectionResponses = { +export type ValidatePaywallDraftResponses = { /** - * Non-secret Provider Connection metadata + * Draft validation */ - 200: ProviderConnectionEnvelope; + 200: ValidationSummaryEnvelope; }; -export type GetProviderConnectionResponse = GetProviderConnectionResponses[keyof GetProviderConnectionResponses]; +export type ValidatePaywallDraftResponse = ValidatePaywallDraftResponses[keyof ValidatePaywallDraftResponses]; -export type ReplaceProviderConnectionScopesData = { - body: ReplaceProviderConnectionScopesRequest; +export type ListPaywallVersionsData = { + body?: never; path: { - connectionId: string; + projectId: string; + paywallId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/scopes'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/versions'; }; -export type ReplaceProviderConnectionScopesErrors = { +export type ListPaywallVersionsErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ReplaceProviderConnectionScopesError = ReplaceProviderConnectionScopesErrors[keyof ReplaceProviderConnectionScopesErrors]; +export type ListPaywallVersionsError = ListPaywallVersionsErrors[keyof ListPaywallVersionsErrors]; -export type ReplaceProviderConnectionScopesResponses = { +export type ListPaywallVersionsResponses = { /** - * Non-secret Provider Connection metadata + * Immutable Paywall Versions */ - 200: ProviderConnectionEnvelope; + 200: PaywallVersionListEnvelope; }; -export type ReplaceProviderConnectionScopesResponse = ReplaceProviderConnectionScopesResponses[keyof ReplaceProviderConnectionScopesResponses]; +export type ListPaywallVersionsResponse = ListPaywallVersionsResponses[keyof ListPaywallVersionsResponses]; -export type RevokeProviderConnectionData = { +export type GetPaywallVersionData = { body?: never; path: { - connectionId: string; + projectId: string; + paywallId: string; + versionId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/revoke'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/versions/{versionId}'; }; -export type RevokeProviderConnectionErrors = { +export type GetPaywallVersionErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type RevokeProviderConnectionError = RevokeProviderConnectionErrors[keyof RevokeProviderConnectionErrors]; +export type GetPaywallVersionError = GetPaywallVersionErrors[keyof GetPaywallVersionErrors]; -export type RevokeProviderConnectionResponses = { +export type GetPaywallVersionResponses = { /** - * Non-secret Provider Connection metadata + * Immutable Paywall Version */ - 200: ProviderConnectionEnvelope; + 200: PaywallVersionEnvelope; }; -export type RevokeProviderConnectionResponse = RevokeProviderConnectionResponses[keyof RevokeProviderConnectionResponses]; +export type GetPaywallVersionResponse = GetPaywallVersionResponses[keyof GetPaywallVersionResponses]; -export type TestProviderConnectionData = { +export type ClonePaywallVersionToDraftData = { body?: never; + headers: { + 'Idempotency-Key': string; + }; path: { - connectionId: string; + projectId: string; + paywallId: string; + versionId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/test'; + url: '/v1/projects/{projectId}/paywalls/{paywallId}/versions/{versionId}/drafts'; }; -export type TestProviderConnectionErrors = { +export type ClonePaywallVersionToDraftErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type TestProviderConnectionError = TestProviderConnectionErrors[keyof TestProviderConnectionErrors]; +export type ClonePaywallVersionToDraftError = ClonePaywallVersionToDraftErrors[keyof ClonePaywallVersionToDraftErrors]; -export type TestProviderConnectionResponses = { +export type ClonePaywallVersionToDraftResponses = { /** - * Safe connection health and capability summary. + * Hosted Draft and current immutable revision document. */ - 200: { - data: ProviderConnectionHealth; - }; + 201: DraftEnvelope; }; -export type TestProviderConnectionResponse = TestProviderConnectionResponses[keyof TestProviderConnectionResponses]; +export type ClonePaywallVersionToDraftResponse = ClonePaywallVersionToDraftResponses[keyof ClonePaywallVersionToDraftResponses]; -export type GetProviderConnectionHealthData = { +export type ListPlacementsData = { body?: never; path: { - connectionId: string; + projectId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/health'; + url: '/v1/projects/{projectId}/placements'; }; -export type GetProviderConnectionHealthErrors = { +export type ListPlacementsErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type GetProviderConnectionHealthError = GetProviderConnectionHealthErrors[keyof GetProviderConnectionHealthErrors]; +export type ListPlacementsError = ListPlacementsErrors[keyof ListPlacementsErrors]; -export type GetProviderConnectionHealthResponses = { +export type ListPlacementsResponses = { /** - * Safe connection health and capability summary. + * Placements */ - 200: { - data: ProviderConnectionHealth; - }; + 200: PlacementListEnvelope; }; -export type GetProviderConnectionHealthResponse = GetProviderConnectionHealthResponses[keyof GetProviderConnectionHealthResponses]; +export type ListPlacementsResponse = ListPlacementsResponses[keyof ListPlacementsResponses]; -export type GetProviderConnectionCapabilitiesData = { - body?: never; +export type CreatePlacementData = { + body: CreatePlacementRequest; path: { - connectionId: string; + projectId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/capabilities'; + url: '/v1/projects/{projectId}/placements'; }; -export type GetProviderConnectionCapabilitiesErrors = { +export type CreatePlacementErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type GetProviderConnectionCapabilitiesError = GetProviderConnectionCapabilitiesErrors[keyof GetProviderConnectionCapabilitiesErrors]; +export type CreatePlacementError = CreatePlacementErrors[keyof CreatePlacementErrors]; -export type GetProviderConnectionCapabilitiesResponses = { +export type CreatePlacementResponses = { /** - * Closed provider capability matrix and required least-privilege permissions. + * Placement */ - 200: { - data: ProviderConnectionCapabilities; - }; + 201: PlacementEnvelope; }; -export type GetProviderConnectionCapabilitiesResponse = GetProviderConnectionCapabilitiesResponses[keyof GetProviderConnectionCapabilitiesResponses]; +export type CreatePlacementResponse = CreatePlacementResponses[keyof CreatePlacementResponses]; -export type ListProviderConnectionDiagnosticsData = { - body?: never; +export type UpdatePlacementData = { + body: UpdatePlacementRequest; path: { - connectionId: string; + projectId: string; + placementId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/diagnostics'; + url: '/v1/projects/{projectId}/placements/{placementId}'; }; -export type ListProviderConnectionDiagnosticsErrors = { +export type UpdatePlacementErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ListProviderConnectionDiagnosticsError = ListProviderConnectionDiagnosticsErrors[keyof ListProviderConnectionDiagnosticsErrors]; +export type UpdatePlacementError = UpdatePlacementErrors[keyof UpdatePlacementErrors]; -export type ListProviderConnectionDiagnosticsResponses = { +export type UpdatePlacementResponses = { /** - * Safe provider diagnostics without provider response bodies or secrets. + * Placement */ - 200: { - data: { - items: Array; - }; - }; + 200: PlacementEnvelope; }; -export type ListProviderConnectionDiagnosticsResponse = ListProviderConnectionDiagnosticsResponses[keyof ListProviderConnectionDiagnosticsResponses]; +export type UpdatePlacementResponse = UpdatePlacementResponses[keyof UpdatePlacementResponses]; -export type PreviewProviderCatalogData = { +export type GetPlacementBindingData = { body?: never; path: { - connectionId: string; + projectId: string; + environmentId: string; + placementId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/catalog-preview'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/binding'; }; -export type PreviewProviderCatalogErrors = { +export type GetPlacementBindingErrors = { + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type PreviewProviderCatalogError = PreviewProviderCatalogErrors[keyof PreviewProviderCatalogErrors]; +export type GetPlacementBindingError = GetPlacementBindingErrors[keyof GetPlacementBindingErrors]; -export type PreviewProviderCatalogResponses = { +export type GetPlacementBindingResponses = { /** - * Normalized live provider catalog preview. - */ - 200: { - data: ProviderCatalogPreview; - }; + * Environment Placement binding + */ + 200: PlacementBindingEnvelope; }; -export type PreviewProviderCatalogResponse = PreviewProviderCatalogResponses[keyof PreviewProviderCatalogResponses]; +export type GetPlacementBindingResponse = GetPlacementBindingResponses[keyof GetPlacementBindingResponses]; -export type RotateProviderCredentialData = { - body: ProviderCredentialRequest; +export type BindPlacementData = { + body: BindPlacementRequest; path: { - connectionId: string; + projectId: string; + environmentId: string; + placementId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/rotate-credential'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/binding'; }; -export type RotateProviderCredentialErrors = { +export type BindPlacementErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type RotateProviderCredentialError = RotateProviderCredentialErrors[keyof RotateProviderCredentialErrors]; +export type BindPlacementError = BindPlacementErrors[keyof BindPlacementErrors]; -export type RotateProviderCredentialResponses = { +export type BindPlacementResponses = { /** - * Non-secret Provider Connection metadata + * Environment Placement binding */ - 200: ProviderConnectionEnvelope; + 200: PlacementBindingEnvelope; }; -export type RotateProviderCredentialResponse = RotateProviderCredentialResponses[keyof RotateProviderCredentialResponses]; +export type BindPlacementResponse = BindPlacementResponses[keyof BindPlacementResponses]; -export type ReconnectProviderConnectionData = { - body: ProviderCredentialRequest; +export type PublishConfigurationData = { + body: PublishRequest; + headers: { + 'Idempotency-Key': string; + }; path: { - connectionId: string; + projectId: string; + environmentId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/reconnect'; + url: '/v1/projects/{projectId}/environments/{environmentId}/publish'; }; -export type ReconnectProviderConnectionErrors = { +export type PublishConfigurationErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ReconnectProviderConnectionError = ReconnectProviderConnectionErrors[keyof ReconnectProviderConnectionErrors]; +export type PublishConfigurationError = PublishConfigurationErrors[keyof PublishConfigurationErrors]; -export type ReconnectProviderConnectionResponses = { +export type PublishConfigurationResponses = { /** - * Non-secret Provider Connection metadata + * Publication result and nonblocking warnings */ - 200: ProviderConnectionEnvelope; + 201: PublishResultEnvelope; }; -export type ReconnectProviderConnectionResponse = ReconnectProviderConnectionResponses[keyof ReconnectProviderConnectionResponses]; +export type PublishConfigurationResponse = PublishConfigurationResponses[keyof PublishConfigurationResponses]; -export type EnqueueProviderSyncData = { +export type ListConfigurationReleasesData = { body?: never; path: { - connectionId: string; + projectId: string; + environmentId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/sync'; + url: '/v1/projects/{projectId}/environments/{environmentId}/releases'; }; -export type EnqueueProviderSyncErrors = { +export type ListConfigurationReleasesErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type EnqueueProviderSyncError = EnqueueProviderSyncErrors[keyof EnqueueProviderSyncErrors]; +export type ListConfigurationReleasesError = ListConfigurationReleasesErrors[keyof ListConfigurationReleasesErrors]; -export type EnqueueProviderSyncResponses = { +export type ListConfigurationReleasesResponses = { /** - * Accepted provider synchronization job. + * Configuration Release history */ - 202: { - data: ProviderSyncJob; - }; + 200: ReleaseListEnvelope; }; -export type EnqueueProviderSyncResponse = EnqueueProviderSyncResponses[keyof EnqueueProviderSyncResponses]; +export type ListConfigurationReleasesResponse = ListConfigurationReleasesResponses[keyof ListConfigurationReleasesResponses]; -export type ListProviderSyncRunsData = { +export type RollbackConfigurationReleaseData = { body?: never; + headers: { + 'Idempotency-Key': string; + }; path: { - connectionId: string; + projectId: string; + environmentId: string; + releaseId: string; }; query?: never; - url: '/v1/provider-connections/{connectionId}/sync-runs'; + url: '/v1/projects/{projectId}/environments/{environmentId}/releases/{releaseId}/rollback'; }; -export type ListProviderSyncRunsErrors = { +export type RollbackConfigurationReleaseErrors = { /** * Stable machine-readable failure. */ default: ErrorEnvelope; }; -export type ListProviderSyncRunsError = ListProviderSyncRunsErrors[keyof ListProviderSyncRunsErrors]; +export type RollbackConfigurationReleaseError = RollbackConfigurationReleaseErrors[keyof RollbackConfigurationReleaseErrors]; -export type ListProviderSyncRunsResponses = { +export type RollbackConfigurationReleaseResponses = { /** - * Provider synchronization run history. + * Immutable Configuration Release metadata */ - 200: { - data: { - items: Array; - }; - }; + 201: ReleaseEnvelope; }; -export type ListProviderSyncRunsResponse = ListProviderSyncRunsResponses[keyof ListProviderSyncRunsResponses]; +export type RollbackConfigurationReleaseResponse = RollbackConfigurationReleaseResponses[keyof RollbackConfigurationReleaseResponses]; -export type RotateApiKeyData = { +export type GetSdkConfigurationData = { body?: never; - path: { - apiKeyId: string; + headers: { + 'Mosaic-SDK-Platform': 'flutter' | 'ios' | 'android'; + 'Mosaic-SDK-Version': string; + 'Mosaic-Configuration-Versions': string; + 'Mosaic-Paywall-Protocol-Versions': '0.2'; + /** + * Comma-separated unique exact Protocol capability pairs (`name@0.2`), bounded to 128 pairs. The selected Release is returned only when every required pair is reported. + */ + 'Mosaic-Paywall-Capabilities': string; + 'Mosaic-Placement-Decision-Versions'?: string; + /** + * Comma-separated exact Placement Decision v1 feature identifiers. + */ + 'Mosaic-Decision-Features'?: string; + 'Mosaic-Bucketing-Algorithms'?: 'sha256_length_prefixed_v1'; + /** + * Required when Delivery v3 is requested. Comma-separated unique Experiment Assignment contract versions. + */ + 'Mosaic-Experiment-Assignment-Versions'?: string; + /** + * Required when Delivery v3 is requested. Comma-separated unique exact Experiment Assignment v1 feature identifiers. + */ + 'Mosaic-Experiment-Features'?: string; + /** + * Required when Delivery v3 is requested. Comma-separated unique canonical Variant and Group bucketing algorithms. + */ + 'Mosaic-Experiment-Bucketing-Algorithms'?: string; + /** + * Required when Delivery v3 is requested. Comma-separated unique trusted-time schedule policies. + */ + 'Mosaic-Experiment-Schedule-Policies'?: string; + 'Mosaic-App-Version'?: string; + 'If-None-Match'?: string; }; + path?: never; query?: never; - url: '/v1/api-keys/{apiKeyId}/rotate'; + url: '/v1/sdk/configuration'; }; -export type RotateApiKeyErrors = { +export type GetSdkConfigurationErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 406: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 429: ErrorEnvelope; }; -export type RotateApiKeyError = RotateApiKeyErrors[keyof RotateApiKeyErrors]; +export type GetSdkConfigurationError = GetSdkConfigurationErrors[keyof GetSdkConfigurationErrors]; -export type RotateApiKeyResponses = { +export type GetSdkConfigurationResponses = { /** - * One-time secret result + * Highest mutually supported representation actually available for the current immutable Release. A v3-capable SDK falls back to an available v2 or safe v1 representation when that Release predates v3. Delivery v3 requires complete Experiment capability headers. A v1 candidate is withheld when an advanced Placement lacks an explicit Paywall default. */ - 200: ApiKeySecretEnvelope; + 200: { + [key: string]: unknown; + }; }; -export type RotateApiKeyResponse = RotateApiKeyResponses[keyof RotateApiKeyResponses]; +export type GetSdkConfigurationResponse = GetSdkConfigurationResponses[keyof GetSdkConfigurationResponses]; -export type RevokeApiKeyData = { +export type GetSdkCommerceConfigurationData = { body?: never; - path: { - apiKeyId: string; + headers: { + /** + * Comma-separated accepted Commerce media types. Supported versions are application/vnd.mosaic.commerce-configuration+json;version=1 and version=2. + */ + Accept: string; + 'Mosaic-SDK-Platform': 'flutter' | 'ios' | 'android'; + 'Mosaic-SDK-Version': string; + 'Mosaic-Commerce-Configuration-Versions': string; + 'Mosaic-Commerce-Provider-Contract-Versions': string; + 'If-None-Match'?: string; }; - query?: never; - url: '/v1/api-keys/{apiKeyId}/revoke'; + path?: never; + query: { + applicationId: string; + }; + url: '/v1/sdk/commerce-configuration'; }; -export type RevokeApiKeyErrors = { +export type GetSdkCommerceConfigurationErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 406: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 429: ErrorEnvelope; }; -export type RevokeApiKeyError = RevokeApiKeyErrors[keyof RevokeApiKeyErrors]; +export type GetSdkCommerceConfigurationError = GetSdkCommerceConfigurationErrors[keyof GetSdkCommerceConfigurationErrors]; -export type RevokeApiKeyResponses = { +export type GetSdkCommerceConfigurationResponses = { /** - * API key metadata + * Immutable version-negotiated Commerce Configuration sidecar associated with the current Configuration Release and requested Application. */ - 200: ApiKeyEnvelope; + 200: { + [key: string]: unknown; + }; }; -export type RevokeApiKeyResponse = RevokeApiKeyResponses[keyof RevokeApiKeyResponses]; +export type GetSdkCommerceConfigurationResponse = GetSdkCommerceConfigurationResponses[keyof GetSdkCommerceConfigurationResponses]; -export type ListPlansData = { +export type GetAssetContentData = { body?: never; path: { - projectId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; + assetId: string; + contentDigest: string; }; - url: '/v1/projects/{projectId}/plans'; + query?: never; + url: '/v1/sdk/assets/{assetId}/{contentDigest}'; }; -export type ListPlansErrors = { +export type GetAssetContentErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type ListPlansError = ListPlansErrors[keyof ListPlansErrors]; +export type GetAssetContentError = GetAssetContentErrors[keyof GetAssetContentErrors]; -export type ListPlansResponses = { +export type GetAssetContentResponses = { /** - * Plans + * Immutable Asset content. */ - 200: PlanList; + 200: Blob | File; }; -export type ListPlansResponse = ListPlansResponses[keyof ListPlansResponses]; +export type GetAssetContentResponse = GetAssetContentResponses[keyof GetAssetContentResponses]; -export type CreatePlanData = { - body: CreateCatalogResourceRequest; +export type ListPlacementAttributesData = { + body?: never; path: { projectId: string; }; query?: never; - url: '/v1/projects/{projectId}/plans'; + url: '/v1/projects/{projectId}/placement-attributes'; }; -export type CreatePlanErrors = { +export type ListPlacementAttributesErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; }; -export type CreatePlanError = CreatePlanErrors[keyof CreatePlanErrors]; +export type ListPlacementAttributesError = ListPlacementAttributesErrors[keyof ListPlacementAttributesErrors]; -export type CreatePlanResponses = { +export type ListPlacementAttributesResponses = { /** - * Plan + * Project attribute allow-list. */ - 201: PlanEnvelope; + 200: PlacementAttributeListEnvelope; }; -export type CreatePlanResponse = CreatePlanResponses[keyof CreatePlanResponses]; +export type ListPlacementAttributesResponse = ListPlacementAttributesResponses[keyof ListPlacementAttributesResponses]; -export type GetPlanData = { - body?: never; +export type CreatePlacementAttributeData = { + body: CreatePlacementAttributeRequest; path: { - planId: string; + projectId: string; }; query?: never; - url: '/v1/plans/{planId}'; + url: '/v1/projects/{projectId}/placement-attributes'; }; -export type GetPlanErrors = { +export type CreatePlacementAttributeErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetPlanError = GetPlanErrors[keyof GetPlanErrors]; +export type CreatePlacementAttributeError = CreatePlacementAttributeErrors[keyof CreatePlacementAttributeErrors]; -export type GetPlanResponses = { +export type CreatePlacementAttributeResponses = { /** - * Plan + * Attribute definition created. */ - 200: PlanEnvelope; + 201: PlacementAttributeEnvelope; }; -export type GetPlanResponse = GetPlanResponses[keyof GetPlanResponses]; +export type CreatePlacementAttributeResponse = CreatePlacementAttributeResponses[keyof CreatePlacementAttributeResponses]; -export type UpdatePlanData = { - body: CreateCatalogResourceRequest; +export type ArchivePlacementAttributeData = { + body?: never; path: { - planId: string; + projectId: string; + attributeId: string; }; query?: never; - url: '/v1/plans/{planId}'; + url: '/v1/projects/{projectId}/placement-attributes/{attributeId}'; }; -export type UpdatePlanErrors = { +export type ArchivePlacementAttributeErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; }; -export type UpdatePlanError = UpdatePlanErrors[keyof UpdatePlanErrors]; +export type ArchivePlacementAttributeError = ArchivePlacementAttributeErrors[keyof ArchivePlacementAttributeErrors]; -export type UpdatePlanResponses = { +export type ArchivePlacementAttributeResponses = { /** - * Plan + * Attribute definition archived. */ - 200: PlanEnvelope; + 204: void; }; -export type UpdatePlanResponse = UpdatePlanResponses[keyof UpdatePlanResponses]; +export type ArchivePlacementAttributeResponse = ArchivePlacementAttributeResponses[keyof ArchivePlacementAttributeResponses]; -export type ListPlanProductsData = { +export type GetPlacementUsageData = { body?: never; path: { - planId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; + projectId: string; + placementId: string; }; - url: '/v1/plans/{planId}/products'; + query?: never; + url: '/v1/projects/{projectId}/placements/{placementId}/usage'; }; -export type ListPlanProductsErrors = { +export type GetPlacementUsageErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type ListPlanProductsError = ListPlanProductsErrors[keyof ListPlanProductsErrors]; +export type GetPlacementUsageError = GetPlacementUsageErrors[keyof GetPlacementUsageErrors]; -export type ListPlanProductsResponses = { +export type GetPlacementUsageResponses = { /** - * Products + * Placement usage. */ - 200: ProductList; + 200: PlacementUsageEnvelope; }; -export type ListPlanProductsResponse = ListPlanProductsResponses[keyof ListPlanProductsResponses]; +export type GetPlacementUsageResponse = GetPlacementUsageResponses[keyof GetPlacementUsageResponses]; -export type AddPlanProductData = { - body: ProductReferenceRequest; +export type ListPlacementAliasesData = { + body?: never; path: { - planId: string; + projectId: string; + placementId: string; }; query?: never; - url: '/v1/plans/{planId}/products'; -}; - -export type AddPlanProductErrors = { - /** - * Stable machine-readable failure. - */ - default: ErrorEnvelope; + url: '/v1/projects/{projectId}/placements/{placementId}/aliases'; }; -export type AddPlanProductError = AddPlanProductErrors[keyof AddPlanProductErrors]; - -export type AddPlanProductResponses = { +export type ListPlacementAliasesResponses = { /** - * Plan membership + * Placement aliases. */ - 201: PlanProductEnvelope; + 200: PlacementAliasListEnvelope; }; -export type AddPlanProductResponse = AddPlanProductResponses[keyof AddPlanProductResponses]; +export type ListPlacementAliasesResponse = ListPlacementAliasesResponses[keyof ListPlacementAliasesResponses]; -export type RemovePlanProductData = { - body?: never; +export type CreatePlacementAliasData = { + body: { + key: string; + }; path: { - planId: string; - productId: string; + projectId: string; + placementId: string; }; query?: never; - url: '/v1/plans/{planId}/products/{productId}'; + url: '/v1/projects/{projectId}/placements/{placementId}/aliases'; }; -export type RemovePlanProductErrors = { +export type CreatePlacementAliasErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type RemovePlanProductError = RemovePlanProductErrors[keyof RemovePlanProductErrors]; +export type CreatePlacementAliasError = CreatePlacementAliasErrors[keyof CreatePlacementAliasErrors]; -export type RemovePlanProductResponses = { +export type CreatePlacementAliasResponses = { /** - * Product removed from Plan. + * Alias created. */ - 204: void; + 201: PlacementAliasEnvelope; }; -export type RemovePlanProductResponse = RemovePlanProductResponses[keyof RemovePlanProductResponses]; +export type CreatePlacementAliasResponse = CreatePlacementAliasResponses[keyof CreatePlacementAliasResponses]; -export type ListProductsData = { +export type ArchivePlacementWithUsageCheckData = { body?: never; path: { projectId: string; + placementId: string; }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; - status?: ProductStatus; - type?: ProductType; - search?: string; - }; - url: '/v1/projects/{projectId}/products'; + query?: never; + url: '/v1/projects/{projectId}/placements/{placementId}/archive'; }; -export type ListProductsErrors = { +export type ArchivePlacementWithUsageCheckErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type ListProductsError = ListProductsErrors[keyof ListProductsErrors]; +export type ArchivePlacementWithUsageCheckError = ArchivePlacementWithUsageCheckErrors[keyof ArchivePlacementWithUsageCheckErrors]; -export type ListProductsResponses = { +export type ArchivePlacementWithUsageCheckResponses = { /** - * Products + * Placement archived. */ - 200: ProductList; + 204: void; }; -export type ListProductsResponse = ListProductsResponses[keyof ListProductsResponses]; +export type ArchivePlacementWithUsageCheckResponse = ArchivePlacementWithUsageCheckResponses[keyof ArchivePlacementWithUsageCheckResponses]; -export type CreateProductData = { - body: CreateProductRequest; +export type GetPlacementDecisionData = { + body?: never; path: { projectId: string; + environmentId: string; + placementId: string; }; query?: never; - url: '/v1/projects/{projectId}/products'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-set'; }; -export type CreateProductErrors = { +export type GetPlacementDecisionErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type CreateProductError = CreateProductErrors[keyof CreateProductErrors]; +export type GetPlacementDecisionError = GetPlacementDecisionErrors[keyof GetPlacementDecisionErrors]; -export type CreateProductResponses = { +export type GetPlacementDecisionResponses = { /** - * Product + * Combined Rule Set and current Draft detail. */ - 201: ProductEnvelope; + 200: PlacementRuleSetDraftEnvelope; }; -export type CreateProductResponse = CreateProductResponses[keyof CreateProductResponses]; +export type GetPlacementDecisionResponse = GetPlacementDecisionResponses[keyof GetPlacementDecisionResponses]; -export type DeleteProductData = { - body?: never; +export type CreatePlacementRuleSetData = { + body: PlacementDecisionDocumentRequest; + headers: { + 'Idempotency-Key': string; + }; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; }; query?: never; - url: '/v1/products/{productId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-set'; }; -export type DeleteProductErrors = { +export type CreatePlacementRuleSetErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type DeleteProductError = DeleteProductErrors[keyof DeleteProductErrors]; +export type CreatePlacementRuleSetError = CreatePlacementRuleSetErrors[keyof CreatePlacementRuleSetErrors]; -export type DeleteProductResponses = { +export type CreatePlacementRuleSetResponses = { /** - * Unreferenced Product deleted. + * Rule Set and Draft created. */ - 204: void; + 201: PlacementRuleSetDraftEnvelope; }; -export type DeleteProductResponse = DeleteProductResponses[keyof DeleteProductResponses]; +export type CreatePlacementRuleSetResponse = CreatePlacementRuleSetResponses[keyof CreatePlacementRuleSetResponses]; -export type GetProductData = { - body?: never; +export type UpdatePlacementRuleSetDraftData = { + body: PlacementDecisionDocumentRequest; + headers: { + 'If-Match': string; + 'Idempotency-Key': string; + }; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; + ruleSetId: string; }; query?: never; - url: '/v1/products/{productId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/draft'; }; -export type GetProductErrors = { +export type UpdatePlacementRuleSetDraftErrors = { + /** + * Stable machine-readable failure. + */ + 412: ErrorEnvelope; /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 428: ErrorEnvelope; }; -export type GetProductError = GetProductErrors[keyof GetProductErrors]; +export type UpdatePlacementRuleSetDraftError = UpdatePlacementRuleSetDraftErrors[keyof UpdatePlacementRuleSetDraftErrors]; -export type GetProductResponses = { +export type UpdatePlacementRuleSetDraftResponses = { /** - * Product + * New immutable Draft revision. */ - 200: ProductEnvelope; + 200: PlacementRuleSetDraftEnvelope; }; -export type GetProductResponse = GetProductResponses[keyof GetProductResponses]; +export type UpdatePlacementRuleSetDraftResponse = UpdatePlacementRuleSetDraftResponses[keyof UpdatePlacementRuleSetDraftResponses]; -export type UpdateProductData = { - body: CreateProductRequest; +export type ValidatePlacementRuleSetData = { + body?: never; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; + ruleSetId: string; }; query?: never; - url: '/v1/products/{productId}'; -}; - -export type UpdateProductErrors = { - /** - * Stable machine-readable failure. - */ - default: ErrorEnvelope; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/validate'; }; -export type UpdateProductError = UpdateProductErrors[keyof UpdateProductErrors]; - -export type UpdateProductResponses = { +export type ValidatePlacementRuleSetResponses = { /** - * Product + * Semantic validation result. */ - 200: ProductEnvelope; + 200: PlacementValidationEnvelope; }; -export type UpdateProductResponse = UpdateProductResponses[keyof UpdateProductResponses]; +export type ValidatePlacementRuleSetResponse = ValidatePlacementRuleSetResponses[keyof ValidatePlacementRuleSetResponses]; -export type ArchiveProductData = { - body?: never; +export type PublishPlacementRuleSetData = { + body: { + expectedRevision: number; + }; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; + ruleSetId: string; }; query?: never; - url: '/v1/products/{productId}/archive'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/publish'; }; -export type ArchiveProductErrors = { +export type PublishPlacementRuleSetErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 412: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ArchiveProductError = ArchiveProductErrors[keyof ArchiveProductErrors]; +export type PublishPlacementRuleSetError = PublishPlacementRuleSetErrors[keyof PublishPlacementRuleSetErrors]; -export type ArchiveProductResponses = { +export type PublishPlacementRuleSetResponses = { /** - * Product + * Immutable Rule Set Version. */ - 200: ProductEnvelope; + 201: PlacementRuleSetVersionEnvelope; }; -export type ArchiveProductResponse = ArchiveProductResponses[keyof ArchiveProductResponses]; +export type PublishPlacementRuleSetResponse = PublishPlacementRuleSetResponses[keyof PublishPlacementRuleSetResponses]; -export type RestoreProductData = { +export type ArchivePlacementRuleSetData = { body?: never; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; + ruleSetId: string; }; query?: never; - url: '/v1/products/{productId}/restore'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/archive'; }; -export type RestoreProductErrors = { +export type ArchivePlacementRuleSetErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Rule Set is already archived or conflicts with current state. + */ + 409: ErrorEnvelope; }; -export type RestoreProductError = RestoreProductErrors[keyof RestoreProductErrors]; +export type ArchivePlacementRuleSetError = ArchivePlacementRuleSetErrors[keyof ArchivePlacementRuleSetErrors]; -export type RestoreProductResponses = { +export type ArchivePlacementRuleSetResponses = { /** - * Product + * Rule Set archived; immutable version history is retained. */ - 200: ProductEnvelope; + 204: void; }; -export type RestoreProductResponse = RestoreProductResponses[keyof RestoreProductResponses]; +export type ArchivePlacementRuleSetResponse = ArchivePlacementRuleSetResponses[keyof ArchivePlacementRuleSetResponses]; -export type SetProductReplacementData = { - body: ProductReferenceRequest; +export type ListPlacementRuleSetVersionsData = { + body?: never; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; + ruleSetId: string; }; query?: never; - url: '/v1/products/{productId}/replacement'; -}; - -export type SetProductReplacementErrors = { - /** - * Stable machine-readable failure. - */ - default: ErrorEnvelope; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/versions'; }; -export type SetProductReplacementError = SetProductReplacementErrors[keyof SetProductReplacementErrors]; - -export type SetProductReplacementResponses = { +export type ListPlacementRuleSetVersionsResponses = { /** - * Product + * Immutable version history. */ - 200: ProductEnvelope; + 200: PlacementRuleSetVersionListEnvelope; }; -export type SetProductReplacementResponse = SetProductReplacementResponses[keyof SetProductReplacementResponses]; +export type ListPlacementRuleSetVersionsResponse = ListPlacementRuleSetVersionsResponses[keyof ListPlacementRuleSetVersionsResponses]; -export type GetProductUsageData = { +export type ClonePlacementRuleSetVersionData = { body?: never; + headers: { + 'Idempotency-Key': string; + }; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; + ruleSetId: string; + versionId: string; }; query?: never; - url: '/v1/products/{productId}/usage'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/versions/{versionId}/draft'; }; -export type GetProductUsageErrors = { +export type ClonePlacementRuleSetVersionErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type GetProductUsageError = GetProductUsageErrors[keyof GetProductUsageErrors]; +export type ClonePlacementRuleSetVersionError = ClonePlacementRuleSetVersionErrors[keyof ClonePlacementRuleSetVersionErrors]; -export type GetProductUsageResponses = { +export type ClonePlacementRuleSetVersionResponses = { /** - * Product usage + * Active Draft cloned from the immutable version. */ - 200: ProductUsageEnvelope; + 201: PlacementRuleSetDraftEnvelope; }; -export type GetProductUsageResponse = GetProductUsageResponses[keyof GetProductUsageResponses]; +export type ClonePlacementRuleSetVersionResponse = ClonePlacementRuleSetVersionResponses[keyof ClonePlacementRuleSetVersionResponses]; -export type GetProductReadinessData = { - body?: never; +export type SimulatePlacementDecisionData = { + body: PlacementSimulationRequestWritable; path: { - productId: string; - }; - query: { + projectId: string; environmentId: string; - applicationId: string; + placementId: string; + ruleSetId: string; }; - url: '/v1/products/{productId}/readiness'; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/simulate'; }; -export type GetProductReadinessErrors = { +export type SimulatePlacementDecisionErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetProductReadinessError = GetProductReadinessErrors[keyof GetProductReadinessErrors]; +export type SimulatePlacementDecisionError = SimulatePlacementDecisionErrors[keyof SimulatePlacementDecisionErrors]; -export type GetProductReadinessResponses = { +export type SimulatePlacementDecisionResponses = { /** - * Scoped provider readiness and stable recovery codes + * Ephemeral bounded decision trace; request inputs are neither logged nor persisted. */ - 200: ProviderReadinessEnvelope; + 200: PlacementSimulationEnvelope; }; -export type GetProductReadinessResponse = GetProductReadinessResponses[keyof GetProductReadinessResponses]; +export type SimulatePlacementDecisionResponse = SimulatePlacementDecisionResponses[keyof SimulatePlacementDecisionResponses]; -export type ListProviderMappingsData = { +export type ListPlacementQaOverridesData = { body?: never; path: { - productId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; + projectId: string; + environmentId: string; + placementId: string; }; - url: '/v1/products/{productId}/provider-mappings'; -}; - -export type ListProviderMappingsErrors = { - /** - * Stable machine-readable failure. - */ - default: ErrorEnvelope; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/qa-overrides'; }; -export type ListProviderMappingsError = ListProviderMappingsErrors[keyof ListProviderMappingsErrors]; - -export type ListProviderMappingsResponses = { +export type ListPlacementQaOverridesResponses = { /** - * Placeholder mappings + * Active non-production QA overrides. */ - 200: ProviderMappingList; + 200: QaOverrideListEnvelope; }; -export type ListProviderMappingsResponse = ListProviderMappingsResponses[keyof ListProviderMappingsResponses]; +export type ListPlacementQaOverridesResponse = ListPlacementQaOverridesResponses[keyof ListPlacementQaOverridesResponses]; -export type CreateProviderMappingData = { - body: CreateProviderMappingRequest; +export type CreatePlacementQaOverrideData = { + body: CreateQaOverrideRequestWritable; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; }; query?: never; - url: '/v1/products/{productId}/provider-mappings'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/qa-overrides'; }; -export type CreateProviderMappingErrors = { +export type CreatePlacementQaOverrideErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type CreateProviderMappingError = CreateProviderMappingErrors[keyof CreateProviderMappingErrors]; +export type CreatePlacementQaOverrideError = CreatePlacementQaOverrideErrors[keyof CreatePlacementQaOverrideErrors]; -export type CreateProviderMappingResponses = { +export type CreatePlacementQaOverrideResponses = { /** - * Placeholder mapping + * Override created; opaque token is returned once. */ - 201: ProviderMappingEnvelope; + 201: QaOverrideCreatedEnvelope; }; -export type CreateProviderMappingResponse = CreateProviderMappingResponses[keyof CreateProviderMappingResponses]; +export type CreatePlacementQaOverrideResponse = CreatePlacementQaOverrideResponses[keyof CreatePlacementQaOverrideResponses]; -export type CreateProviderMappingDraftData = { - body: CreateProviderMappingDraftRequest; +export type RevokePlacementQaOverrideData = { + body?: never; path: { - productId: string; + projectId: string; + environmentId: string; + placementId: string; + overrideId: string; }; query?: never; - url: '/v1/products/{productId}/provider-mapping-drafts'; + url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/qa-overrides/{overrideId}'; }; -export type CreateProviderMappingDraftErrors = { +export type RevokePlacementQaOverrideErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type CreateProviderMappingDraftError = CreateProviderMappingDraftErrors[keyof CreateProviderMappingDraftErrors]; +export type RevokePlacementQaOverrideError = RevokePlacementQaOverrideErrors[keyof RevokePlacementQaOverrideErrors]; -export type CreateProviderMappingDraftResponses = { +export type RevokePlacementQaOverrideResponses = { /** - * Placeholder mapping + * Override revoked; the next immutable release omits it. */ - 201: ProviderMappingEnvelope; + 204: void; }; -export type CreateProviderMappingDraftResponse = CreateProviderMappingDraftResponses[keyof CreateProviderMappingDraftResponses]; +export type RevokePlacementQaOverrideResponse = RevokePlacementQaOverrideResponses[keyof RevokePlacementQaOverrideResponses]; -export type GetProviderReadinessData = { +export type ListExperimentsData = { body?: never; path: { - productId: string; + projectId: string; + environmentId: string; }; - query: { + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments'; +}; + +export type ListExperimentsResponses = { + /** + * Environment-scoped Experiments. + */ + 200: ExperimentListEnvelope; +}; + +export type ListExperimentsResponse = ListExperimentsResponses[keyof ListExperimentsResponses]; + +export type CreateExperimentData = { + body: CreateExperimentRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; environmentId: string; - applicationId: string; }; - url: '/v1/products/{productId}/provider-readiness'; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments'; }; -export type GetProviderReadinessErrors = { +export type CreateExperimentErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetProviderReadinessError = GetProviderReadinessErrors[keyof GetProviderReadinessErrors]; +export type CreateExperimentError = CreateExperimentErrors[keyof CreateExperimentErrors]; -export type GetProviderReadinessResponses = { +export type CreateExperimentResponses = { /** - * Scoped provider readiness and stable recovery codes + * Experiment and first Draft revision. */ - 200: ProviderReadinessEnvelope; + 201: ExperimentEnvelope; }; -export type GetProviderReadinessResponse = GetProviderReadinessResponses[keyof GetProviderReadinessResponses]; +export type CreateExperimentResponse = CreateExperimentResponses[keyof CreateExperimentResponses]; -export type ArchiveProviderMappingData = { +export type ListExperimentMetricDefinitionsData = { body?: never; path: { - mappingId: string; + projectId: string; + environmentId: string; }; query?: never; - url: '/v1/provider-mappings/{mappingId}/archive'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/metrics'; }; -export type ArchiveProviderMappingErrors = { +export type ListExperimentMetricDefinitionsResponses = { /** - * Stable machine-readable failure. + * Immutable Experiment metric definitions. */ - default: ErrorEnvelope; + 200: ExperimentMetricListEnvelope; }; -export type ArchiveProviderMappingError = ArchiveProviderMappingErrors[keyof ArchiveProviderMappingErrors]; +export type ListExperimentMetricDefinitionsResponse = ListExperimentMetricDefinitionsResponses[keyof ListExperimentMetricDefinitionsResponses]; -export type ArchiveProviderMappingResponses = { +export type ListExperimentGroupsData = { + body?: never; + path: { + projectId: string; + environmentId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups'; +}; + +export type ListExperimentGroupsResponses = { /** - * Placeholder mapping + * Mutual-exclusion groups. */ - 200: ProviderMappingEnvelope; + 200: ExperimentGroupListEnvelope; }; -export type ArchiveProviderMappingResponse = ArchiveProviderMappingResponses[keyof ArchiveProviderMappingResponses]; +export type ListExperimentGroupsResponse = ListExperimentGroupsResponses[keyof ListExperimentGroupsResponses]; -export type ReplaceProviderMappingData = { - body: ReplaceProviderMappingRequest; +export type CreateExperimentGroupVersionData = { + body: CreateExperimentGroupRequest; path: { - mappingId: string; + projectId: string; + environmentId: string; }; query?: never; - url: '/v1/provider-mappings/{mappingId}/replace'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups'; }; -export type ReplaceProviderMappingErrors = { +export type CreateExperimentGroupVersionErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ReplaceProviderMappingError = ReplaceProviderMappingErrors[keyof ReplaceProviderMappingErrors]; +export type CreateExperimentGroupVersionError = CreateExperimentGroupVersionErrors[keyof CreateExperimentGroupVersionErrors]; -export type ReplaceProviderMappingResponses = { +export type CreateExperimentGroupVersionResponses = { /** - * Placeholder mapping + * Group and immutable Version. */ - 201: ProviderMappingEnvelope; + 201: ExperimentGroupCreatedEnvelope; }; -export type ReplaceProviderMappingResponse = ReplaceProviderMappingResponses[keyof ReplaceProviderMappingResponses]; +export type CreateExperimentGroupVersionResponse = CreateExperimentGroupVersionResponses[keyof CreateExperimentGroupVersionResponses]; -export type GetProviderMappingMetadataData = { +export type ListExperimentMutualExclusionGroupVersionsData = { body?: never; path: { - mappingId: string; + projectId: string; + environmentId: string; + groupId: string; }; query?: never; - url: '/v1/provider-mappings/{mappingId}/metadata'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups/{groupId}/versions'; }; -export type GetProviderMappingMetadataErrors = { +export type ListExperimentMutualExclusionGroupVersionsErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type GetProviderMappingMetadataError = GetProviderMappingMetadataErrors[keyof GetProviderMappingMetadataErrors]; +export type ListExperimentMutualExclusionGroupVersionsError = ListExperimentMutualExclusionGroupVersionsErrors[keyof ListExperimentMutualExclusionGroupVersionsErrors]; -export type GetProviderMappingMetadataResponses = { +export type ListExperimentMutualExclusionGroupVersionsResponses = { /** - * Current immutable normalized provider metadata and freshness evidence. + * Immutable group Version history. */ - 200: { - data: ProviderProductMetadataSnapshot; - }; + 200: ExperimentGroupVersionListEnvelope; }; -export type GetProviderMappingMetadataResponse = GetProviderMappingMetadataResponses[keyof GetProviderMappingMetadataResponses]; +export type ListExperimentMutualExclusionGroupVersionsResponse = ListExperimentMutualExclusionGroupVersionsResponses[keyof ListExperimentMutualExclusionGroupVersionsResponses]; -export type GetProviderMappingUsageData = { - body?: never; +export type CreateExperimentMutualExclusionGroupVersionData = { + body: CreateExperimentGroupVersionRequest; path: { - mappingId: string; + projectId: string; + environmentId: string; + groupId: string; }; query?: never; - url: '/v1/provider-mappings/{mappingId}/usage'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups/{groupId}/versions'; }; -export type GetProviderMappingUsageErrors = { +export type CreateExperimentMutualExclusionGroupVersionErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetProviderMappingUsageError = GetProviderMappingUsageErrors[keyof GetProviderMappingUsageErrors]; +export type CreateExperimentMutualExclusionGroupVersionError = CreateExperimentMutualExclusionGroupVersionErrors[keyof CreateExperimentMutualExclusionGroupVersionErrors]; -export type GetProviderMappingUsageResponses = { +export type CreateExperimentMutualExclusionGroupVersionResponses = { /** - * Mapping-specific replacement impact + * New immutable group Version. */ - 200: ProviderMappingUsageEnvelope; + 201: ExperimentGroupVersionEnvelope; }; -export type GetProviderMappingUsageResponse = GetProviderMappingUsageResponses[keyof GetProviderMappingUsageResponses]; +export type CreateExperimentMutualExclusionGroupVersionResponse = CreateExperimentMutualExclusionGroupVersionResponses[keyof CreateExperimentMutualExclusionGroupVersionResponses]; -export type ListProviderMappingObservationsData = { +export type GetExperimentData = { body?: never; path: { - mappingId: string; + projectId: string; + environmentId: string; + experimentId: string; }; query?: never; - url: '/v1/provider-mappings/{mappingId}/observations'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}'; }; -export type ListProviderMappingObservationsErrors = { +export type GetExperimentErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type ListProviderMappingObservationsError = ListProviderMappingObservationsErrors[keyof ListProviderMappingObservationsErrors]; +export type GetExperimentError = GetExperimentErrors[keyof GetExperimentErrors]; -export type ListProviderMappingObservationsResponses = { +export type GetExperimentResponses = { /** - * Immutable native-store observation history. + * Experiment root */ - 200: { - data: Array; - }; + 200: ExperimentEnvelope; }; -export type ListProviderMappingObservationsResponse = ListProviderMappingObservationsResponses[keyof ListProviderMappingObservationsResponses]; +export type GetExperimentResponse = GetExperimentResponses[keyof GetExperimentResponses]; -export type CreateProviderMappingObservationData = { - body: CreateProviderMappingObservationRequest; +export type UpdateExperimentDraftData = { + body: UpdateExperimentDraftRequest; + headers: { + 'If-Match': string; + 'Idempotency-Key': string; + }; path: { - mappingId: string; + projectId: string; + environmentId: string; + experimentId: string; }; query?: never; - url: '/v1/provider-mappings/{mappingId}/observations'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/draft'; }; -export type CreateProviderMappingObservationErrors = { +export type UpdateExperimentDraftErrors = { + /** + * Stale Draft revision with currentRevision and ETag recovery details. + */ + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 428: ErrorEnvelope; }; -export type CreateProviderMappingObservationError = CreateProviderMappingObservationErrors[keyof CreateProviderMappingObservationErrors]; +export type UpdateExperimentDraftError = UpdateExperimentDraftErrors[keyof UpdateExperimentDraftErrors]; -export type CreateProviderMappingObservationResponses = { +export type UpdateExperimentDraftResponses = { /** - * Accepted immutable native-store test observation + * New immutable Draft revision. */ - 201: ProviderMappingObservationEnvelope; + 200: ExperimentDraftEnvelope; }; -export type CreateProviderMappingObservationResponse = CreateProviderMappingObservationResponses[keyof CreateProviderMappingObservationResponses]; +export type UpdateExperimentDraftResponse = UpdateExperimentDraftResponses[keyof UpdateExperimentDraftResponses]; -export type GetNativeProviderProfileData = { +export type ValidateExperimentDraftData = { body?: never; path: { - provider: 'app_store' | 'google_play'; - }; - query: { - platform: Platform; - }; - url: '/v1/native-providers/{provider}/profile'; -}; - -export type GetNativeProviderProfileErrors = { - /** - * Stable machine-readable failure. - */ - default: ErrorEnvelope; + projectId: string; + environmentId: string; + experimentId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/validate'; }; -export type GetNativeProviderProfileError = GetNativeProviderProfileErrors[keyof GetNativeProviderProfileErrors]; - -export type GetNativeProviderProfileResponses = { +export type ValidateExperimentDraftResponses = { /** - * Credential-free native provider capability profile + * Scientific */ - 200: ProviderProfileEnvelope; + 200: ExperimentValidationEnvelope; }; -export type GetNativeProviderProfileResponse = GetNativeProviderProfileResponses[keyof GetNativeProviderProfileResponses]; +export type ValidateExperimentDraftResponse = ValidateExperimentDraftResponses[keyof ValidateExperimentDraftResponses]; -export type ListEntitlementsData = { - body?: never; +export type PublishExperimentData = { + body: PublishExperimentRequest; path: { projectId: string; + environmentId: string; + experimentId: string; }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; - }; - url: '/v1/projects/{projectId}/entitlements'; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/publish'; }; -export type ListEntitlementsErrors = { +export type PublishExperimentErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ListEntitlementsError = ListEntitlementsErrors[keyof ListEntitlementsErrors]; +export type PublishExperimentError = PublishExperimentErrors[keyof PublishExperimentErrors]; -export type ListEntitlementsResponses = { +export type PublishExperimentResponses = { /** - * Entitlement definitions + * Immutable Experiment Version and atomic Configuration Delivery v3 release. */ - 200: EntitlementList; + 201: ExperimentVersionEnvelope; }; -export type ListEntitlementsResponse = ListEntitlementsResponses[keyof ListEntitlementsResponses]; +export type PublishExperimentResponse = PublishExperimentResponses[keyof PublishExperimentResponses]; -export type CreateEntitlementData = { - body: CreateCatalogResourceRequest; +export type ListExperimentVersionsData = { + body?: never; path: { projectId: string; + environmentId: string; + experimentId: string; }; query?: never; - url: '/v1/projects/{projectId}/entitlements'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/versions'; }; -export type CreateEntitlementErrors = { +export type ListExperimentVersionsResponses = { /** - * Stable machine-readable failure. + * Immutable Experiment Version history. */ - default: ErrorEnvelope; + 200: ExperimentVersionListEnvelope; }; -export type CreateEntitlementError = CreateEntitlementErrors[keyof CreateEntitlementErrors]; +export type ListExperimentVersionsResponse = ListExperimentVersionsResponses[keyof ListExperimentVersionsResponses]; -export type CreateEntitlementResponses = { +export type ListExperimentHistoryData = { + body?: never; + path: { + projectId: string; + environmentId: string; + experimentId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/history'; +}; + +export type ListExperimentHistoryResponses = { /** - * Entitlement definition + * Audited lifecycle and publication history. */ - 201: EntitlementEnvelope; + 200: ExperimentHistoryListEnvelope; }; -export type CreateEntitlementResponse = CreateEntitlementResponses[keyof CreateEntitlementResponses]; +export type ListExperimentHistoryResponse = ListExperimentHistoryResponses[keyof ListExperimentHistoryResponses]; -export type GetEntitlementData = { +export type GetExperimentResultsData = { body?: never; path: { - entitlementId: string; + projectId: string; + environmentId: string; + experimentId: string; }; query?: never; - url: '/v1/entitlements/{entitlementId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/results'; }; -export type GetEntitlementErrors = { +export type GetExperimentResultsResponses = { /** - * Stable machine-readable failure. + * Unique-unit conversion */ - default: ErrorEnvelope; + 200: ExperimentResultsEnvelope; }; -export type GetEntitlementError = GetEntitlementErrors[keyof GetEntitlementErrors]; +export type GetExperimentResultsResponse = GetExperimentResultsResponses[keyof GetExperimentResultsResponses]; -export type GetEntitlementResponses = { +export type GetExperimentSampleRatioMismatchData = { + body?: never; + path: { + projectId: string; + environmentId: string; + experimentId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/srm'; +}; + +export type GetExperimentSampleRatioMismatchResponses = { /** - * Entitlement definition + * Descriptive Pearson chi-square SRM diagnostic. */ - 200: EntitlementEnvelope; + 200: { + data: ExperimentSrm; + }; }; -export type GetEntitlementResponse = GetEntitlementResponses[keyof GetEntitlementResponses]; +export type GetExperimentSampleRatioMismatchResponse = GetExperimentSampleRatioMismatchResponses[keyof GetExperimentSampleRatioMismatchResponses]; -export type UpdateEntitlementData = { - body: CreateCatalogResourceRequest; +export type TransitionExperimentLifecycleData = { + body?: { + reason?: string; + }; path: { - entitlementId: string; + projectId: string; + environmentId: string; + experimentId: string; + lifecycleAction: 'schedule' | 'start' | 'pause' | 'resume' | 'stop' | 'complete' | 'archive' | 'emergency-stop'; }; query?: never; - url: '/v1/entitlements/{entitlementId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/{lifecycleAction}'; }; -export type UpdateEntitlementErrors = { +export type TransitionExperimentLifecycleErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type UpdateEntitlementError = UpdateEntitlementErrors[keyof UpdateEntitlementErrors]; +export type TransitionExperimentLifecycleError = TransitionExperimentLifecycleErrors[keyof TransitionExperimentLifecycleErrors]; -export type UpdateEntitlementResponses = { +export type TransitionExperimentLifecycleResponses = { /** - * Entitlement definition + * Updated Experiment root. */ - 200: EntitlementEnvelope; + 200: ExperimentEnvelope; }; -export type UpdateEntitlementResponse = UpdateEntitlementResponses[keyof UpdateEntitlementResponses]; +export type TransitionExperimentLifecycleResponse = TransitionExperimentLifecycleResponses[keyof TransitionExperimentLifecycleResponses]; -export type ListProductEntitlementsData = { +export type ListExperimentQaOverridesData = { body?: never; path: { - productId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; - limit?: number; + projectId: string; + environmentId: string; + experimentId: string; }; - url: '/v1/products/{productId}/entitlements'; -}; - -export type ListProductEntitlementsErrors = { - /** - * Stable machine-readable failure. - */ - default: ErrorEnvelope; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/qa-overrides'; }; -export type ListProductEntitlementsError = ListProductEntitlementsErrors[keyof ListProductEntitlementsErrors]; - -export type ListProductEntitlementsResponses = { +export type ListExperimentQaOverridesResponses = { /** - * Entitlement definitions + * Safe QA override metadata without tokens. */ - 200: EntitlementList; + 200: ExperimentQaOverrideListEnvelope; }; -export type ListProductEntitlementsResponse = ListProductEntitlementsResponses[keyof ListProductEntitlementsResponses]; +export type ListExperimentQaOverridesResponse = ListExperimentQaOverridesResponses[keyof ListExperimentQaOverridesResponses]; -export type AddProductEntitlementData = { - body: EntitlementReferenceRequest; +export type CreateExperimentQaOverrideData = { + body: CreateExperimentQaOverrideRequest; path: { - productId: string; + projectId: string; + environmentId: string; + experimentId: string; }; query?: never; - url: '/v1/products/{productId}/entitlements'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/qa-overrides'; }; -export type AddProductEntitlementErrors = { +export type CreateExperimentQaOverrideErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type AddProductEntitlementError = AddProductEntitlementErrors[keyof AddProductEntitlementErrors]; +export type CreateExperimentQaOverrideError = CreateExperimentQaOverrideErrors[keyof CreateExperimentQaOverrideErrors]; -export type AddProductEntitlementResponses = { +export type CreateExperimentQaOverrideResponses = { /** - * Product grant + * Non-production override; raw token returned once and never delivered as creator identity. */ - 201: ProductEntitlementGrantEnvelope; + 201: ExperimentQaOverrideCreatedEnvelope; }; -export type AddProductEntitlementResponse = AddProductEntitlementResponses[keyof AddProductEntitlementResponses]; +export type CreateExperimentQaOverrideResponse = CreateExperimentQaOverrideResponses[keyof CreateExperimentQaOverrideResponses]; -export type RemoveProductEntitlementData = { +export type RevokeExperimentQaOverrideData = { body?: never; path: { - productId: string; - entitlementId: string; + projectId: string; + environmentId: string; + experimentId: string; + overrideId: string; }; query?: never; - url: '/v1/products/{productId}/entitlements/{entitlementId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/qa-overrides/{overrideId}'; }; -export type RemoveProductEntitlementErrors = { +export type RevokeExperimentQaOverrideErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type RemoveProductEntitlementError = RemoveProductEntitlementErrors[keyof RemoveProductEntitlementErrors]; +export type RevokeExperimentQaOverrideError = RevokeExperimentQaOverrideErrors[keyof RevokeExperimentQaOverrideErrors]; -export type RemoveProductEntitlementResponses = { +export type RevokeExperimentQaOverrideResponses = { /** - * Entitlement grant removed. + * Override revoked. */ 204: void; }; -export type RemoveProductEntitlementResponse = RemoveProductEntitlementResponses[keyof RemoveProductEntitlementResponses]; +export type RevokeExperimentQaOverrideResponse = RevokeExperimentQaOverrideResponses[keyof RevokeExperimentQaOverrideResponses]; -export type ListAssetsData = { - body?: never; +export type CreateExperimentRawExportData = { + body: CreateExperimentExportRequest; path: { projectId: string; + environmentId: string; + experimentId: string; }; query?: never; - url: '/v1/projects/{projectId}/assets'; + url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/exports'; }; -export type ListAssetsErrors = { +export type CreateExperimentRawExportErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ListAssetsError = ListAssetsErrors[keyof ListAssetsErrors]; +export type CreateExperimentRawExportError = CreateExperimentRawExportErrors[keyof CreateExperimentRawExportErrors]; -export type ListAssetsResponses = { +export type CreateExperimentRawExportResponses = { /** - * Hosted Assets + * Asynchronous analytics job. */ - 200: AssetListEnvelope; + 202: AnalyticsJobEnvelope; }; -export type ListAssetsResponse = ListAssetsResponses[keyof ListAssetsResponses]; +export type CreateExperimentRawExportResponse = CreateExperimentRawExportResponses[keyof CreateExperimentRawExportResponses]; -export type UploadAssetData = { - body: { - file: Blob | File; - }; - path: { - projectId: string; - }; +export type IngestAnalyticsEventBatchData = { + body: AnalyticsEventBatch; + path?: never; query?: never; - url: '/v1/projects/{projectId}/assets'; + url: '/v1/sdk/events/batch'; }; -export type UploadAssetErrors = { +export type IngestAnalyticsEventBatchErrors = { /** * Stable machine-readable failure. */ - 413: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -6230,744 +9506,906 @@ export type UploadAssetErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 429: ErrorEnvelope; }; -export type UploadAssetError = UploadAssetErrors[keyof UploadAssetErrors]; +export type IngestAnalyticsEventBatchError = IngestAnalyticsEventBatchErrors[keyof IngestAnalyticsEventBatchErrors]; -export type UploadAssetResponses = { +export type IngestAnalyticsEventBatchResponses = { /** - * Hosted Asset metadata + * Per-event ingestion outcomes. */ - 201: AssetEnvelope; + 200: AnalyticsIngestionResult; }; -export type UploadAssetResponse = UploadAssetResponses[keyof UploadAssetResponses]; +export type IngestAnalyticsEventBatchResponse = IngestAnalyticsEventBatchResponses[keyof IngestAnalyticsEventBatchResponses]; -export type ArchiveAssetData = { +export type GetAnalyticsSettingsData = { body?: never; path: { projectId: string; - assetId: string; + environmentId: string; }; query?: never; - url: '/v1/projects/{projectId}/assets/{assetId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/settings'; }; -export type ArchiveAssetErrors = { +export type GetAnalyticsSettingsErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type ArchiveAssetError = ArchiveAssetErrors[keyof ArchiveAssetErrors]; +export type GetAnalyticsSettingsError = GetAnalyticsSettingsErrors[keyof GetAnalyticsSettingsErrors]; -export type ArchiveAssetResponses = { +export type GetAnalyticsSettingsResponses = { /** - * Hosted Asset metadata + * Analytics settings. */ - 200: AssetEnvelope; + 200: AnalyticsSettingsEnvelope; }; -export type ArchiveAssetResponse = ArchiveAssetResponses[keyof ArchiveAssetResponses]; +export type GetAnalyticsSettingsResponse = GetAnalyticsSettingsResponses[keyof GetAnalyticsSettingsResponses]; -export type GetAssetData = { - body?: never; +export type UpdateAnalyticsSettingsData = { + body: UpdateAnalyticsSettingsRequest; path: { projectId: string; - assetId: string; + environmentId: string; }; query?: never; - url: '/v1/projects/{projectId}/assets/{assetId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/settings'; }; -export type GetAssetErrors = { +export type UpdateAnalyticsSettingsErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetAssetError = GetAssetErrors[keyof GetAssetErrors]; +export type UpdateAnalyticsSettingsError = UpdateAnalyticsSettingsErrors[keyof UpdateAnalyticsSettingsErrors]; -export type GetAssetResponses = { +export type UpdateAnalyticsSettingsResponses = { /** - * Hosted Asset metadata + * Updated analytics settings. */ - 200: AssetEnvelope; + 200: AnalyticsSettingsEnvelope; }; -export type GetAssetResponse = GetAssetResponses[keyof GetAssetResponses]; +export type UpdateAnalyticsSettingsResponse = UpdateAnalyticsSettingsResponses[keyof UpdateAnalyticsSettingsResponses]; -export type GetAssetUsageData = { +export type GetAnalyticsOverviewData = { body?: never; path: { projectId: string; - assetId: string; + environmentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/assets/{assetId}/usage'; + query: { + from: Timestamp; + to: Timestamp; + timezone: string; + metricBasis: 'event_count'; + platform?: 'ios' | 'android'; + locale?: string; + applicationVersion?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/overview'; }; -export type GetAssetUsageErrors = { +export type GetAnalyticsOverviewErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetAssetUsageError = GetAssetUsageErrors[keyof GetAssetUsageErrors]; +export type GetAnalyticsOverviewError = GetAnalyticsOverviewErrors[keyof GetAnalyticsOverviewErrors]; -export type GetAssetUsageResponses = { +export type GetAnalyticsOverviewResponses = { /** - * Hosted Asset reference counts + * Event-count analytics result. */ - 200: AssetUsageEnvelope; + 200: AnalyticsResultEnvelope; }; -export type GetAssetUsageResponse = GetAssetUsageResponses[keyof GetAssetUsageResponses]; +export type GetAnalyticsOverviewResponse = GetAnalyticsOverviewResponses[keyof GetAnalyticsOverviewResponses]; -export type ListPaywallsData = { +export type GetAnalyticsFunnelData = { body?: never; path: { projectId: string; + environmentId: string; + funnel: 'placements' | 'paywalls' | 'products' | 'purchases'; }; - query?: never; - url: '/v1/projects/{projectId}/paywalls'; + query: { + from: Timestamp; + to: Timestamp; + timezone: string; + metricBasis: 'event_count'; + platform?: 'ios' | 'android'; + locale?: string; + applicationVersion?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/funnels/{funnel}'; }; -export type ListPaywallsErrors = { +export type GetAnalyticsFunnelErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ListPaywallsError = ListPaywallsErrors[keyof ListPaywallsErrors]; +export type GetAnalyticsFunnelError = GetAnalyticsFunnelErrors[keyof GetAnalyticsFunnelErrors]; -export type ListPaywallsResponses = { +export type GetAnalyticsFunnelResponses = { /** - * Paywalls + * Event-count analytics result. */ - 200: PaywallListEnvelope; + 200: AnalyticsResultEnvelope; }; -export type ListPaywallsResponse = ListPaywallsResponses[keyof ListPaywallsResponses]; +export type GetAnalyticsFunnelResponse = GetAnalyticsFunnelResponses[keyof GetAnalyticsFunnelResponses]; -export type CreatePaywallData = { - body: CreatePaywallRequest; +export type CompareAnalyticsPaywallVersionsData = { + body?: never; path: { projectId: string; + environmentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/paywalls'; + query: { + from: Timestamp; + to: Timestamp; + timezone: string; + metricBasis: 'event_count'; + platform?: 'ios' | 'android'; + locale?: string; + applicationVersion?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/paywall-version-comparison'; }; -export type CreatePaywallErrors = { +export type CompareAnalyticsPaywallVersionsErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type CreatePaywallError = CreatePaywallErrors[keyof CreatePaywallErrors]; +export type CompareAnalyticsPaywallVersionsError = CompareAnalyticsPaywallVersionsErrors[keyof CompareAnalyticsPaywallVersionsErrors]; -export type CreatePaywallResponses = { +export type CompareAnalyticsPaywallVersionsResponses = { /** - * Paywall + * Event-count analytics result. */ - 201: PaywallEnvelope; + 200: AnalyticsResultEnvelope; }; -export type CreatePaywallResponse = CreatePaywallResponses[keyof CreatePaywallResponses]; +export type CompareAnalyticsPaywallVersionsResponse = CompareAnalyticsPaywallVersionsResponses[keyof CompareAnalyticsPaywallVersionsResponses]; -export type GetPaywallData = { +export type GetAnalyticsProviderErrorsData = { body?: never; path: { projectId: string; - paywallId: string; + environmentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}'; + query: { + from: Timestamp; + to: Timestamp; + timezone: string; + metricBasis: 'event_count'; + platform?: 'ios' | 'android'; + locale?: string; + applicationVersion?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/provider-errors'; }; -export type GetPaywallErrors = { +export type GetAnalyticsProviderErrorsErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetPaywallError = GetPaywallErrors[keyof GetPaywallErrors]; +export type GetAnalyticsProviderErrorsError = GetAnalyticsProviderErrorsErrors[keyof GetAnalyticsProviderErrorsErrors]; -export type GetPaywallResponses = { +export type GetAnalyticsProviderErrorsResponses = { /** - * Paywall + * Event-count analytics result. */ - 200: PaywallEnvelope; + 200: AnalyticsResultEnvelope; }; -export type GetPaywallResponse = GetPaywallResponses[keyof GetPaywallResponses]; +export type GetAnalyticsProviderErrorsResponse = GetAnalyticsProviderErrorsResponses[keyof GetAnalyticsProviderErrorsResponses]; -export type UpdatePaywallData = { - body: UpdatePaywallRequest; +export type GetAnalyticsProductAvailabilityFailuresData = { + body?: never; path: { projectId: string; - paywallId: string; + environmentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}'; + query: { + from: Timestamp; + to: Timestamp; + timezone: string; + metricBasis: 'event_count'; + platform?: 'ios' | 'android'; + locale?: string; + applicationVersion?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/product-availability-failures'; }; -export type UpdatePaywallErrors = { +export type GetAnalyticsProductAvailabilityFailuresErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type UpdatePaywallError = UpdatePaywallErrors[keyof UpdatePaywallErrors]; +export type GetAnalyticsProductAvailabilityFailuresError = GetAnalyticsProductAvailabilityFailuresErrors[keyof GetAnalyticsProductAvailabilityFailuresErrors]; -export type UpdatePaywallResponses = { +export type GetAnalyticsProductAvailabilityFailuresResponses = { /** - * Paywall + * Event-count analytics result. */ - 200: PaywallEnvelope; + 200: AnalyticsResultEnvelope; }; -export type UpdatePaywallResponse = UpdatePaywallResponses[keyof UpdatePaywallResponses]; +export type GetAnalyticsProductAvailabilityFailuresResponse = GetAnalyticsProductAvailabilityFailuresResponses[keyof GetAnalyticsProductAvailabilityFailuresResponses]; -export type CreatePaywallDraftData = { - body: CreateDraftRequest; - headers: { - 'Idempotency-Key': string; - }; +export type GetAnalyticsBreakdownData = { + body?: never; path: { projectId: string; - paywallId: string; + environmentId: string; + dimension: 'platforms' | 'locales'; }; - query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts'; + query: { + from: Timestamp; + to: Timestamp; + timezone: string; + metricBasis: 'event_count'; + platform?: 'ios' | 'android'; + locale?: string; + applicationVersion?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/breakdowns/{dimension}'; }; -export type CreatePaywallDraftErrors = { +export type GetAnalyticsBreakdownErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type CreatePaywallDraftError = CreatePaywallDraftErrors[keyof CreatePaywallDraftErrors]; +export type GetAnalyticsBreakdownError = GetAnalyticsBreakdownErrors[keyof GetAnalyticsBreakdownErrors]; -export type CreatePaywallDraftResponses = { +export type GetAnalyticsBreakdownResponses = { /** - * Hosted Draft and current immutable revision document. + * Event-count analytics result. */ - 201: DraftEnvelope; + 200: AnalyticsResultEnvelope; }; -export type CreatePaywallDraftResponse = CreatePaywallDraftResponses[keyof CreatePaywallDraftResponses]; +export type GetAnalyticsBreakdownResponse = GetAnalyticsBreakdownResponses[keyof GetAnalyticsBreakdownResponses]; -export type GetActivePaywallDraftData = { +export type GetAnalyticsFreshnessData = { body?: never; path: { projectId: string; - paywallId: string; + environmentId: string; }; query: { - environmentId: string; + from: Timestamp; + to: Timestamp; + timezone: string; + metricBasis: 'event_count'; }; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/active'; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/freshness'; }; -export type GetActivePaywallDraftErrors = { - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; +export type GetAnalyticsFreshnessErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetActivePaywallDraftError = GetActivePaywallDraftErrors[keyof GetActivePaywallDraftErrors]; +export type GetAnalyticsFreshnessError = GetAnalyticsFreshnessErrors[keyof GetAnalyticsFreshnessErrors]; -export type GetActivePaywallDraftResponses = { +export type GetAnalyticsFreshnessResponses = { /** - * Hosted Draft and current immutable revision document. + * Event-count analytics result. */ - 200: DraftEnvelope; + 200: AnalyticsResultEnvelope; }; -export type GetActivePaywallDraftResponse = GetActivePaywallDraftResponses[keyof GetActivePaywallDraftResponses]; +export type GetAnalyticsFreshnessResponse = GetAnalyticsFreshnessResponses[keyof GetAnalyticsFreshnessResponses]; -export type GetPaywallDraftData = { - body?: never; +export type CreateAnalyticsEventExportData = { + body: CreateAnalyticsEventExportRequest; path: { projectId: string; - paywallId: string; - draftId: string; + environmentId: string; }; query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/{draftId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/exports'; }; -export type GetPaywallDraftErrors = { +export type CreateAnalyticsEventExportErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetPaywallDraftError = GetPaywallDraftErrors[keyof GetPaywallDraftErrors]; +export type CreateAnalyticsEventExportError = CreateAnalyticsEventExportErrors[keyof CreateAnalyticsEventExportErrors]; -export type GetPaywallDraftResponses = { +export type CreateAnalyticsEventExportResponses = { /** - * Hosted Draft and current immutable revision document. + * Asynchronous analytics job. */ - 200: DraftEnvelope; + 202: AnalyticsJobEnvelope; }; -export type GetPaywallDraftResponse = GetPaywallDraftResponses[keyof GetPaywallDraftResponses]; +export type CreateAnalyticsEventExportResponse = CreateAnalyticsEventExportResponses[keyof CreateAnalyticsEventExportResponses]; -export type UpdatePaywallDraftData = { - body: UpdateDraftRequest; - headers: { - 'If-Match': string; - 'Idempotency-Key': string; - }; +export type PreviewAnalyticsPrivacyRequestData = { + body: AnalyticsIdentityRequest; path: { projectId: string; - paywallId: string; - draftId: string; }; query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/{draftId}'; + url: '/v1/projects/{projectId}/analytics/privacy/preview'; }; -export type UpdatePaywallDraftErrors = { - /** - * Stable machine-readable failure. - */ - 412: ErrorEnvelope; +export type PreviewAnalyticsPrivacyRequestErrors = { /** * Stable machine-readable failure. */ - 428: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type UpdatePaywallDraftError = UpdatePaywallDraftErrors[keyof UpdatePaywallDraftErrors]; +export type PreviewAnalyticsPrivacyRequestError = PreviewAnalyticsPrivacyRequestErrors[keyof PreviewAnalyticsPrivacyRequestErrors]; -export type UpdatePaywallDraftResponses = { +export type PreviewAnalyticsPrivacyRequestResponses = { /** - * Hosted Draft and current immutable revision document. + * Privacy impact preview. */ - 200: DraftEnvelope; + 200: AnalyticsPrivacyPreviewEnvelope; }; -export type UpdatePaywallDraftResponse = UpdatePaywallDraftResponses[keyof UpdatePaywallDraftResponses]; +export type PreviewAnalyticsPrivacyRequestResponse = PreviewAnalyticsPrivacyRequestResponses[keyof PreviewAnalyticsPrivacyRequestResponses]; -export type ValidatePaywallDraftData = { - body?: never; +export type CreateAnalyticsPrivacyExportData = { + body: CreateAnalyticsPrivacyExportRequest; path: { projectId: string; - paywallId: string; - draftId: string; }; query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/drafts/{draftId}/validate'; + url: '/v1/projects/{projectId}/analytics/privacy/exports'; }; -export type ValidatePaywallDraftErrors = { +export type CreateAnalyticsPrivacyExportErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ValidatePaywallDraftError = ValidatePaywallDraftErrors[keyof ValidatePaywallDraftErrors]; +export type CreateAnalyticsPrivacyExportError = CreateAnalyticsPrivacyExportErrors[keyof CreateAnalyticsPrivacyExportErrors]; -export type ValidatePaywallDraftResponses = { +export type CreateAnalyticsPrivacyExportResponses = { /** - * Draft validation + * Asynchronous analytics job. */ - 200: ValidationSummaryEnvelope; + 202: AnalyticsJobEnvelope; }; -export type ValidatePaywallDraftResponse = ValidatePaywallDraftResponses[keyof ValidatePaywallDraftResponses]; +export type CreateAnalyticsPrivacyExportResponse = CreateAnalyticsPrivacyExportResponses[keyof CreateAnalyticsPrivacyExportResponses]; -export type ListPaywallVersionsData = { - body?: never; +export type CreateAnalyticsPrivacyDeletionData = { + body: CreateAnalyticsPrivacyDeletionRequest; path: { projectId: string; - paywallId: string; }; query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/versions'; + url: '/v1/projects/{projectId}/analytics/privacy/deletions'; }; -export type ListPaywallVersionsErrors = { +export type CreateAnalyticsPrivacyDeletionErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ListPaywallVersionsError = ListPaywallVersionsErrors[keyof ListPaywallVersionsErrors]; +export type CreateAnalyticsPrivacyDeletionError = CreateAnalyticsPrivacyDeletionErrors[keyof CreateAnalyticsPrivacyDeletionErrors]; -export type ListPaywallVersionsResponses = { +export type CreateAnalyticsPrivacyDeletionResponses = { /** - * Immutable Paywall Versions + * Asynchronous analytics job. */ - 200: PaywallVersionListEnvelope; + 202: AnalyticsJobEnvelope; }; -export type ListPaywallVersionsResponse = ListPaywallVersionsResponses[keyof ListPaywallVersionsResponses]; +export type CreateAnalyticsPrivacyDeletionResponse = CreateAnalyticsPrivacyDeletionResponses[keyof CreateAnalyticsPrivacyDeletionResponses]; -export type GetPaywallVersionData = { +export type GetAnalyticsJobData = { body?: never; path: { projectId: string; - paywallId: string; - versionId: string; + jobId: string; }; query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/versions/{versionId}'; + url: '/v1/projects/{projectId}/analytics/jobs/{jobId}'; }; -export type GetPaywallVersionErrors = { +export type GetAnalyticsJobErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type GetPaywallVersionError = GetPaywallVersionErrors[keyof GetPaywallVersionErrors]; +export type GetAnalyticsJobError = GetAnalyticsJobErrors[keyof GetAnalyticsJobErrors]; -export type GetPaywallVersionResponses = { +export type GetAnalyticsJobResponses = { /** - * Immutable Paywall Version + * Asynchronous analytics job. */ - 200: PaywallVersionEnvelope; + 200: AnalyticsJobEnvelope; }; -export type GetPaywallVersionResponse = GetPaywallVersionResponses[keyof GetPaywallVersionResponses]; +export type GetAnalyticsJobResponse = GetAnalyticsJobResponses[keyof GetAnalyticsJobResponses]; -export type ClonePaywallVersionToDraftData = { +export type DownloadAnalyticsJobData = { body?: never; - headers: { - 'Idempotency-Key': string; - }; path: { projectId: string; - paywallId: string; - versionId: string; + jobId: string; }; query?: never; - url: '/v1/projects/{projectId}/paywalls/{paywallId}/versions/{versionId}/drafts'; + url: '/v1/projects/{projectId}/analytics/jobs/{jobId}/download'; }; -export type ClonePaywallVersionToDraftErrors = { +export type DownloadAnalyticsJobErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type ClonePaywallVersionToDraftError = ClonePaywallVersionToDraftErrors[keyof ClonePaywallVersionToDraftErrors]; +export type DownloadAnalyticsJobError = DownloadAnalyticsJobErrors[keyof DownloadAnalyticsJobErrors]; -export type ClonePaywallVersionToDraftResponses = { +export type DownloadAnalyticsJobResponses = { /** - * Hosted Draft and current immutable revision document. + * Private export artifact. */ - 201: DraftEnvelope; + 200: Blob | File; }; -export type ClonePaywallVersionToDraftResponse = ClonePaywallVersionToDraftResponses[keyof ClonePaywallVersionToDraftResponses]; +export type DownloadAnalyticsJobResponse = DownloadAnalyticsJobResponses[keyof DownloadAnalyticsJobResponses]; -export type ListPlacementsData = { - body?: never; +export type ReceiveAppleStoreNotificationData = { + body: { + /** + * Apple JWS notification payload. Never logged or echoed. + */ + signedPayload: string; + }; path: { - projectId: string; + /** + * One-time intake token issued with the credential. + */ + intakeToken: string; }; query?: never; - url: '/v1/projects/{projectId}/placements'; + url: '/v1/billing/apple/notifications/{intakeToken}'; }; -export type ListPlacementsErrors = { +export type ReceiveAppleStoreNotificationErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type ListPlacementsError = ListPlacementsErrors[keyof ListPlacementsErrors]; +export type ReceiveAppleStoreNotificationError = ReceiveAppleStoreNotificationErrors[keyof ReceiveAppleStoreNotificationErrors]; -export type ListPlacementsResponses = { +export type ReceiveAppleStoreNotificationResponses = { /** - * Placements + * The notification was durably recorded and queued for validation. */ - 200: PlacementListEnvelope; + 202: { + data?: { + status?: 'accepted'; + }; + }; }; -export type ListPlacementsResponse = ListPlacementsResponses[keyof ListPlacementsResponses]; +export type ReceiveAppleStoreNotificationResponse = ReceiveAppleStoreNotificationResponses[keyof ReceiveAppleStoreNotificationResponses]; -export type CreatePlacementData = { - body: CreatePlacementRequest; - path: { - projectId: string; +export type SubmitTransactionObservationData = { + body: ClientTransactionObservationRecord; + headers?: { + /** + * Optional Customer Access Token binding the observation to its customer as token-bound public-client evidence. It can attach an unowned lineage but cannot reassign or freeze an attached lineage. + */ + 'Mosaic-Customer-Token'?: string; }; + path?: never; query?: never; - url: '/v1/projects/{projectId}/placements'; + url: '/v1/sdk/billing/observations'; }; -export type CreatePlacementErrors = { +export type SubmitTransactionObservationErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Resubmitting the identical document cannot succeed. The SDK queue should drop it. + */ + 422: ObservationSubmissionResultRecord; + /** + * Transient. Resubmit the identical document later; Retry-After carries the hint. + */ + 429: ObservationSubmissionResultRecord; + /** + * Transient. Resubmit the identical document later; Retry-After carries the hint. + */ + 503: ObservationSubmissionResultRecord; }; -export type CreatePlacementError = CreatePlacementErrors[keyof CreatePlacementErrors]; +export type SubmitTransactionObservationError = SubmitTransactionObservationErrors[keyof SubmitTransactionObservationErrors]; -export type CreatePlacementResponses = { +export type SubmitTransactionObservationResponses = { /** - * Placement + * The submission was already recorded. A duplicate is idempotent, not an error. */ - 201: PlacementEnvelope; + 200: ObservationSubmissionResultRecord; + /** + * The observation is well formed and queued for validation. Nothing more: the store has not been consulted when this response is written. + */ + 202: ObservationSubmissionResultRecord; }; -export type CreatePlacementResponse = CreatePlacementResponses[keyof CreatePlacementResponses]; +export type SubmitTransactionObservationResponse = SubmitTransactionObservationResponses[keyof SubmitTransactionObservationResponses]; -export type UpdatePlacementData = { - body: UpdatePlacementRequest; - path: { - projectId: string; - placementId: string; +export type SubmitServerTransactionObservationData = { + body: ServerTransactionObservationRecord; + headers?: { + /** + * Optional Customer Access Token naming the customer. Because this request is authenticated by the secret server key + */ + 'Mosaic-Customer-Token'?: string; }; + path?: never; query?: never; - url: '/v1/projects/{projectId}/placements/{placementId}'; + url: '/v1/billing/server/observations'; }; -export type UpdatePlacementErrors = { +export type SubmitServerTransactionObservationErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Resubmitting the identical document cannot succeed. The SDK queue should drop it. + */ + 422: ObservationSubmissionResultRecord; + /** + * Transient. Resubmit the identical document later; Retry-After carries the hint. + */ + 429: ObservationSubmissionResultRecord; + /** + * Transient. Resubmit the identical document later; Retry-After carries the hint. + */ + 503: ObservationSubmissionResultRecord; }; -export type UpdatePlacementError = UpdatePlacementErrors[keyof UpdatePlacementErrors]; +export type SubmitServerTransactionObservationError = SubmitServerTransactionObservationErrors[keyof SubmitServerTransactionObservationErrors]; -export type UpdatePlacementResponses = { +export type SubmitServerTransactionObservationResponses = { /** - * Placement + * The submission was already recorded. A duplicate is idempotent, not an error. */ - 200: PlacementEnvelope; + 200: ObservationSubmissionResultRecord; + /** + * The observation is well formed and queued for validation. Nothing more: the store has not been consulted when this response is written. + */ + 202: ObservationSubmissionResultRecord; }; -export type UpdatePlacementResponse = UpdatePlacementResponses[keyof UpdatePlacementResponses]; +export type SubmitServerTransactionObservationResponse = SubmitServerTransactionObservationResponses[keyof SubmitServerTransactionObservationResponses]; -export type GetPlacementBindingData = { +export type SyncCustomerEntitlementsData = { body?: never; - path: { - projectId: string; - environmentId: string; - placementId: string; + headers: { + /** + * Public SDK key identifying the Environment and Application. + */ + 'Mosaic-SDK-Key': string; }; + path?: never; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/binding'; + url: '/v1/sdk/billing/entitlements'; }; -export type GetPlacementBindingErrors = { +export type SyncCustomerEntitlementsErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 429: ErrorEnvelope; }; -export type GetPlacementBindingError = GetPlacementBindingErrors[keyof GetPlacementBindingErrors]; +export type SyncCustomerEntitlementsError = SyncCustomerEntitlementsErrors[keyof SyncCustomerEntitlementsErrors]; -export type GetPlacementBindingResponses = { +export type SyncCustomerEntitlementsResponses = { /** - * Environment Placement binding + * The current Customer Entitlement Snapshot. */ - 200: PlacementBindingEnvelope; + 200: CustomerEntitlementSnapshotRecord; }; -export type GetPlacementBindingResponse = GetPlacementBindingResponses[keyof GetPlacementBindingResponses]; +export type SyncCustomerEntitlementsResponse = SyncCustomerEntitlementsResponses[keyof SyncCustomerEntitlementsResponses]; -export type BindPlacementData = { - body: BindPlacementRequest; - path: { - projectId: string; - environmentId: string; - placementId: string; +export type SyncCustomerEntitlementsWithNegotiationData = { + body: EntitlementSyncRequestUnion; + headers: { + 'Mosaic-SDK-Key': string; }; + path?: never; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/binding'; + url: '/v1/sdk/billing/entitlements'; }; -export type BindPlacementErrors = { +export type SyncCustomerEntitlementsWithNegotiationErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 406: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 429: ErrorEnvelope; }; -export type BindPlacementError = BindPlacementErrors[keyof BindPlacementErrors]; +export type SyncCustomerEntitlementsWithNegotiationError = SyncCustomerEntitlementsWithNegotiationErrors[keyof SyncCustomerEntitlementsWithNegotiationErrors]; -export type BindPlacementResponses = { +export type SyncCustomerEntitlementsWithNegotiationResponses = { /** - * Environment Placement binding + * The current Customer Entitlement Snapshot; the canonical snapshotUnchanged record + * when the v1 known version and entity tag match or the v2 epoch, version, and previously + * observed authority digest all match; or a v2 authorityUnavailable record. This form + * never answers 304: the unchanged record keeps freshness inside the versioned body. + * */ - 200: PlacementBindingEnvelope; + 200: AuthoritativeEntitlementSyncResponse; }; -export type BindPlacementResponse = BindPlacementResponses[keyof BindPlacementResponses]; +export type SyncCustomerEntitlementsWithNegotiationResponse = SyncCustomerEntitlementsWithNegotiationResponses[keyof SyncCustomerEntitlementsWithNegotiationResponses]; -export type PublishConfigurationData = { - body: PublishRequest; - headers: { - 'Idempotency-Key': string; - }; - path: { - projectId: string; - environmentId: string; +export type ListCustomerAccessTokensData = { + body?: never; + path?: never; + query: { + billingCustomerId: string; + limit?: number; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/publish'; + url: '/v1/billing/server/customer-tokens'; }; -export type PublishConfigurationErrors = { +export type ListCustomerAccessTokensErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type PublishConfigurationError = PublishConfigurationErrors[keyof PublishConfigurationErrors]; +export type ListCustomerAccessTokensError = ListCustomerAccessTokensErrors[keyof ListCustomerAccessTokensErrors]; -export type PublishConfigurationResponses = { +export type ListCustomerAccessTokensResponses = { /** - * Publication result and nonblocking warnings + * Token metadata. */ - 201: PublishResultEnvelope; + 200: { + data?: { + items?: Array; + }; + }; }; -export type PublishConfigurationResponse = PublishConfigurationResponses[keyof PublishConfigurationResponses]; +export type ListCustomerAccessTokensResponse = ListCustomerAccessTokensResponses[keyof ListCustomerAccessTokensResponses]; -export type ListConfigurationReleasesData = { - body?: never; - path: { - projectId: string; - environmentId: string; - }; +export type IssueCustomerAccessTokenData = { + body: CustomerAccessTokenIssuanceRequest; + path?: never; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/releases'; + url: '/v1/billing/server/customer-tokens'; }; -export type ListConfigurationReleasesErrors = { +export type IssueCustomerAccessTokenErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ListConfigurationReleasesError = ListConfigurationReleasesErrors[keyof ListConfigurationReleasesErrors]; +export type IssueCustomerAccessTokenError = IssueCustomerAccessTokenErrors[keyof IssueCustomerAccessTokenErrors]; -export type ListConfigurationReleasesResponses = { +export type IssueCustomerAccessTokenResponses = { /** - * Configuration Release history + * The token and its metadata. The token value appears here and nowhere else. */ - 200: ReleaseListEnvelope; + 201: CustomerAccessTokenIssuanceResult; }; -export type ListConfigurationReleasesResponse = ListConfigurationReleasesResponses[keyof ListConfigurationReleasesResponses]; +export type IssueCustomerAccessTokenResponse = IssueCustomerAccessTokenResponses[keyof IssueCustomerAccessTokenResponses]; -export type RollbackConfigurationReleaseData = { - body?: never; - headers: { - 'Idempotency-Key': string; +export type RevokeCustomerAccessTokenData = { + body: { + revocationReason: 'customer_signed_out' | 'identity_changed' | 'operator_revoked' | 'customer_deleted' | 'key_rotated' | 'suspected_compromise' | 'superseded_by_new_token'; }; path: { - projectId: string; - environmentId: string; - releaseId: string; + tokenId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/releases/{releaseId}/rollback'; + url: '/v1/billing/server/customer-tokens/{tokenId}/revoke'; }; -export type RollbackConfigurationReleaseErrors = { +export type RevokeCustomerAccessTokenErrors = { /** * Stable machine-readable failure. */ - default: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type RollbackConfigurationReleaseError = RollbackConfigurationReleaseErrors[keyof RollbackConfigurationReleaseErrors]; +export type RevokeCustomerAccessTokenError = RevokeCustomerAccessTokenErrors[keyof RevokeCustomerAccessTokenErrors]; -export type RollbackConfigurationReleaseResponses = { +export type RevokeCustomerAccessTokenResponses = { /** - * Immutable Configuration Release metadata + * The revoked token's metadata. */ - 201: ReleaseEnvelope; + 200: { + data?: CustomerAccessTokenMetadata; + }; }; -export type RollbackConfigurationReleaseResponse = RollbackConfigurationReleaseResponses[keyof RollbackConfigurationReleaseResponses]; +export type RevokeCustomerAccessTokenResponse = RevokeCustomerAccessTokenResponses[keyof RevokeCustomerAccessTokenResponses]; -export type GetSdkConfigurationData = { - body?: never; +export type SubmitSdkRestoreData = { + body: RestoreRequestRecord; headers: { - 'Mosaic-SDK-Platform': 'flutter' | 'ios' | 'android'; - 'Mosaic-SDK-Version': string; - 'Mosaic-Configuration-Versions': string; - 'Mosaic-Paywall-Protocol-Versions': '0.2'; - /** - * Comma-separated unique exact Protocol capability pairs (`name@0.2`), bounded to 128 pairs. The selected Release is returned only when every required pair is reported. - */ - 'Mosaic-Paywall-Capabilities': string; - 'Mosaic-Placement-Decision-Versions'?: string; - /** - * Comma-separated exact Placement Decision v1 feature identifiers. - */ - 'Mosaic-Decision-Features'?: string; - 'Mosaic-Bucketing-Algorithms'?: 'sha256_length_prefixed_v1'; - /** - * Required when Delivery v3 is requested. Comma-separated unique Experiment Assignment contract versions. - */ - 'Mosaic-Experiment-Assignment-Versions'?: string; - /** - * Required when Delivery v3 is requested. Comma-separated unique exact Experiment Assignment v1 feature identifiers. - */ - 'Mosaic-Experiment-Features'?: string; - /** - * Required when Delivery v3 is requested. Comma-separated unique canonical Variant and Group bucketing algorithms. - */ - 'Mosaic-Experiment-Bucketing-Algorithms'?: string; /** - * Required when Delivery v3 is requested. Comma-separated unique trusted-time schedule policies. + * Public SDK key identifying the Environment and Application. It proves which Environment is asking and can never select a customer. */ - 'Mosaic-Experiment-Schedule-Policies'?: string; - 'Mosaic-App-Version'?: string; - 'If-None-Match'?: string; + 'Mosaic-SDK-Key': string; }; path?: never; query?: never; - url: '/v1/sdk/configuration'; + url: '/v1/sdk/billing/restores'; }; -export type GetSdkConfigurationErrors = { +export type SubmitSdkRestoreErrors = { /** * Stable machine-readable failure. */ @@ -6975,51 +10413,41 @@ export type GetSdkConfigurationErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 406: ErrorEnvelope; + 422: ErrorEnvelope; /** * Stable machine-readable failure. */ 429: ErrorEnvelope; }; -export type GetSdkConfigurationError = GetSdkConfigurationErrors[keyof GetSdkConfigurationErrors]; +export type SubmitSdkRestoreError = SubmitSdkRestoreErrors[keyof SubmitSdkRestoreErrors]; -export type GetSdkConfigurationResponses = { +export type SubmitSdkRestoreResponses = { /** - * Highest mutually supported representation actually available for the current immutable Release. A v3-capable SDK falls back to an available v2 or safe v1 representation when that Release predates v3. Delivery v3 requires complete Experiment capability headers. A v1 candidate is withheld when an advanced Placement lacks an explicit Paywall default. + * The restore was recorded and the chain is running. */ - 200: { - [key: string]: unknown; - }; + 202: RestoreResultRecord; }; -export type GetSdkConfigurationResponse = GetSdkConfigurationResponses[keyof GetSdkConfigurationResponses]; +export type SubmitSdkRestoreResponse = SubmitSdkRestoreResponses[keyof SubmitSdkRestoreResponses]; -export type GetSdkCommerceConfigurationData = { +export type GetSdkRestoreData = { body?: never; headers: { - /** - * Comma-separated accepted Commerce media types. Supported versions are application/vnd.mosaic.commerce-configuration+json;version=1 and version=2. - */ - Accept: string; - 'Mosaic-SDK-Platform': 'flutter' | 'ios' | 'android'; - 'Mosaic-SDK-Version': string; - 'Mosaic-Commerce-Configuration-Versions': string; - 'Mosaic-Commerce-Provider-Contract-Versions': string; - 'If-None-Match'?: string; + 'Mosaic-SDK-Key': string; }; - path?: never; - query: { - applicationId: string; + path: { + restoreId: string; }; - url: '/v1/sdk/commerce-configuration'; + query?: never; + url: '/v1/sdk/billing/restores/{restoreId}'; }; -export type GetSdkCommerceConfigurationErrors = { +export type GetSdkRestoreErrors = { /** * Stable machine-readable failure. */ @@ -7028,75 +10456,91 @@ export type GetSdkCommerceConfigurationErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; +}; + +export type GetSdkRestoreError = GetSdkRestoreErrors[keyof GetSdkRestoreErrors]; + +export type GetSdkRestoreResponses = { + /** + * The restore record. + */ + 200: RestoreResultRecord; +}; + +export type GetSdkRestoreResponse = GetSdkRestoreResponses[keyof GetSdkRestoreResponses]; + +export type SubmitServerRestoreData = { + body: RestoreRequestRecord; + path?: never; + query?: never; + url: '/v1/billing/server/restores'; +}; + +export type SubmitServerRestoreErrors = { /** * Stable machine-readable failure. */ - 406: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 429: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetSdkCommerceConfigurationError = GetSdkCommerceConfigurationErrors[keyof GetSdkCommerceConfigurationErrors]; +export type SubmitServerRestoreError = SubmitServerRestoreErrors[keyof SubmitServerRestoreErrors]; -export type GetSdkCommerceConfigurationResponses = { +export type SubmitServerRestoreResponses = { /** - * Immutable version-negotiated Commerce Configuration sidecar associated with the current Configuration Release and requested Application. + * The restore was recorded and the chain is running. */ - 200: { - [key: string]: unknown; - }; + 202: RestoreResultRecord; }; -export type GetSdkCommerceConfigurationResponse = GetSdkCommerceConfigurationResponses[keyof GetSdkCommerceConfigurationResponses]; +export type SubmitServerRestoreResponse = SubmitServerRestoreResponses[keyof SubmitServerRestoreResponses]; -export type GetAssetContentData = { +export type GetServerRestoreData = { body?: never; path: { - assetId: string; - contentDigest: string; + restoreId: string; }; query?: never; - url: '/v1/sdk/assets/{assetId}/{contentDigest}'; + url: '/v1/billing/server/restores/{restoreId}'; }; -export type GetAssetContentErrors = { +export type GetServerRestoreErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type GetAssetContentError = GetAssetContentErrors[keyof GetAssetContentErrors]; +export type GetServerRestoreError = GetServerRestoreErrors[keyof GetServerRestoreErrors]; -export type GetAssetContentResponses = { +export type GetServerRestoreResponses = { /** - * Immutable Asset content. + * The restore record. */ - 200: Blob | File; + 200: RestoreResultRecord; }; -export type GetAssetContentResponse = GetAssetContentResponses[keyof GetAssetContentResponses]; +export type GetServerRestoreResponse = GetServerRestoreResponses[keyof GetServerRestoreResponses]; -export type ListPlacementAttributesData = { - body?: never; - path: { - projectId: string; - }; +export type IdentifyBillingCustomerData = { + body: IdentifyBillingCustomerRequest; + path?: never; query?: never; - url: '/v1/projects/{projectId}/placement-attributes'; + url: '/v1/billing/identity/customers'; }; -export type ListPlacementAttributesErrors = { +export type IdentifyBillingCustomerErrors = { /** * Stable machine-readable failure. */ @@ -7104,62 +10548,81 @@ export type ListPlacementAttributesErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ListPlacementAttributesError = ListPlacementAttributesErrors[keyof ListPlacementAttributesErrors]; +export type IdentifyBillingCustomerError = IdentifyBillingCustomerErrors[keyof IdentifyBillingCustomerErrors]; -export type ListPlacementAttributesResponses = { +export type IdentifyBillingCustomerResponses = { /** - * Project attribute allow-list. + * The existing Billing Customer. */ - 200: PlacementAttributeListEnvelope; + 200: { + data?: BillingIdentityCustomer; + }; + /** + * A Billing Customer was created. + */ + 201: { + data?: BillingIdentityCustomer; + }; }; -export type ListPlacementAttributesResponse = ListPlacementAttributesResponses[keyof ListPlacementAttributesResponses]; +export type IdentifyBillingCustomerResponse = IdentifyBillingCustomerResponses[keyof IdentifyBillingCustomerResponses]; -export type CreatePlacementAttributeData = { - body: CreatePlacementAttributeRequest; +export type ListBillingCustomerAliasesData = { + body?: never; path: { - projectId: string; + customerId: string; }; query?: never; - url: '/v1/projects/{projectId}/placement-attributes'; + url: '/v1/billing/identity/customers/{customerId}/aliases'; }; -export type CreatePlacementAttributeErrors = { +export type ListBillingCustomerAliasesErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type CreatePlacementAttributeError = CreatePlacementAttributeErrors[keyof CreatePlacementAttributeErrors]; +export type ListBillingCustomerAliasesError = ListBillingCustomerAliasesErrors[keyof ListBillingCustomerAliasesErrors]; -export type CreatePlacementAttributeResponses = { +export type ListBillingCustomerAliasesResponses = { /** - * Attribute definition created. + * Aliases. */ - 201: PlacementAttributeEnvelope; + 200: { + data?: { + items?: Array; + }; + }; }; -export type CreatePlacementAttributeResponse = CreatePlacementAttributeResponses[keyof CreatePlacementAttributeResponses]; +export type ListBillingCustomerAliasesResponse = ListBillingCustomerAliasesResponses[keyof ListBillingCustomerAliasesResponses]; -export type ArchivePlacementAttributeData = { - body?: never; +export type AttachBillingCustomerAliasData = { + body: AttachBillingCustomerAliasRequest; path: { - projectId: string; - attributeId: string; + customerId: string; }; query?: never; - url: '/v1/projects/{projectId}/placement-attributes/{attributeId}'; + url: '/v1/billing/identity/customers/{customerId}/aliases'; }; -export type ArchivePlacementAttributeErrors = { +export type AttachBillingCustomerAliasErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -7168,301 +10631,254 @@ export type ArchivePlacementAttributeErrors = { * Stable machine-readable failure. */ 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type ArchivePlacementAttributeError = ArchivePlacementAttributeErrors[keyof ArchivePlacementAttributeErrors]; +export type AttachBillingCustomerAliasError = AttachBillingCustomerAliasErrors[keyof AttachBillingCustomerAliasErrors]; -export type ArchivePlacementAttributeResponses = { +export type AttachBillingCustomerAliasResponses = { /** - * Attribute definition archived. + * The attached alias. */ - 204: void; + 201: { + data?: BillingCustomerAlias; + }; }; -export type ArchivePlacementAttributeResponse = ArchivePlacementAttributeResponses[keyof ArchivePlacementAttributeResponses]; +export type AttachBillingCustomerAliasResponse = AttachBillingCustomerAliasResponses[keyof AttachBillingCustomerAliasResponses]; -export type GetPlacementUsageData = { +export type RevokeBillingCustomerAliasData = { body?: never; path: { - projectId: string; - placementId: string; + aliasId: string; }; query?: never; - url: '/v1/projects/{projectId}/placements/{placementId}/usage'; + url: '/v1/billing/identity/aliases/{aliasId}/revoke'; }; -export type GetPlacementUsageErrors = { +export type RevokeBillingCustomerAliasErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; -}; - -export type GetPlacementUsageError = GetPlacementUsageErrors[keyof GetPlacementUsageErrors]; - -export type GetPlacementUsageResponses = { + 401: ErrorEnvelope; /** - * Placement usage. + * Stable machine-readable failure. */ - 200: PlacementUsageEnvelope; + 404: ErrorEnvelope; }; -export type GetPlacementUsageResponse = GetPlacementUsageResponses[keyof GetPlacementUsageResponses]; - -export type ListPlacementAliasesData = { - body?: never; - path: { - projectId: string; - placementId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/placements/{placementId}/aliases'; -}; +export type RevokeBillingCustomerAliasError = RevokeBillingCustomerAliasErrors[keyof RevokeBillingCustomerAliasErrors]; -export type ListPlacementAliasesResponses = { +export type RevokeBillingCustomerAliasResponses = { /** - * Placement aliases. + * The alias was revoked. */ - 200: PlacementAliasListEnvelope; + 204: void; }; -export type ListPlacementAliasesResponse = ListPlacementAliasesResponses[keyof ListPlacementAliasesResponses]; +export type RevokeBillingCustomerAliasResponse = RevokeBillingCustomerAliasResponses[keyof RevokeBillingCustomerAliasResponses]; -export type CreatePlacementAliasData = { - body: { - key: string; - }; +export type RequestBillingCustomerSyncData = { + body?: never; path: { - projectId: string; - placementId: string; + customerId: string; }; query?: never; - url: '/v1/projects/{projectId}/placements/{placementId}/aliases'; + url: '/v1/billing/identity/customers/{customerId}/sync-requests'; }; -export type CreatePlacementAliasErrors = { +export type RequestBillingCustomerSyncErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; -}; - -export type CreatePlacementAliasError = CreatePlacementAliasErrors[keyof CreatePlacementAliasErrors]; - -export type CreatePlacementAliasResponses = { + 401: ErrorEnvelope; /** - * Alias created. + * Stable machine-readable failure. */ - 201: PlacementAliasEnvelope; -}; - -export type CreatePlacementAliasResponse = CreatePlacementAliasResponses[keyof CreatePlacementAliasResponses]; - -export type ArchivePlacementWithUsageCheckData = { - body?: never; - path: { - projectId: string; - placementId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/placements/{placementId}/archive'; -}; - -export type ArchivePlacementWithUsageCheckErrors = { + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ 409: ErrorEnvelope; }; -export type ArchivePlacementWithUsageCheckError = ArchivePlacementWithUsageCheckErrors[keyof ArchivePlacementWithUsageCheckErrors]; +export type RequestBillingCustomerSyncError = RequestBillingCustomerSyncErrors[keyof RequestBillingCustomerSyncErrors]; -export type ArchivePlacementWithUsageCheckResponses = { +export type RequestBillingCustomerSyncResponses = { /** - * Placement archived. + * A projection was queued. */ - 204: void; + 202: { + data?: BillingSyncRequest; + }; }; -export type ArchivePlacementWithUsageCheckResponse = ArchivePlacementWithUsageCheckResponses[keyof ArchivePlacementWithUsageCheckResponses]; +export type RequestBillingCustomerSyncResponse = RequestBillingCustomerSyncResponses[keyof RequestBillingCustomerSyncResponses]; -export type GetPlacementDecisionData = { +export type ListBillingIdentityConflictsData = { body?: never; - path: { - projectId: string; - environmentId: string; - placementId: string; + path?: never; + query?: { + status?: 'open' | 'resolved'; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-set'; + url: '/v1/billing/identity/conflicts'; }; -export type GetPlacementDecisionErrors = { +export type ListBillingIdentityConflictsErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 401: ErrorEnvelope; }; -export type GetPlacementDecisionError = GetPlacementDecisionErrors[keyof GetPlacementDecisionErrors]; +export type ListBillingIdentityConflictsError = ListBillingIdentityConflictsErrors[keyof ListBillingIdentityConflictsErrors]; -export type GetPlacementDecisionResponses = { +export type ListBillingIdentityConflictsResponses = { /** - * Combined Rule Set and current Draft detail. + * Identity conflicts. */ - 200: PlacementRuleSetDraftEnvelope; + 200: { + data?: { + items?: Array; + }; + }; }; -export type GetPlacementDecisionResponse = GetPlacementDecisionResponses[keyof GetPlacementDecisionResponses]; +export type ListBillingIdentityConflictsResponse = ListBillingIdentityConflictsResponses[keyof ListBillingIdentityConflictsResponses]; -export type CreatePlacementRuleSetData = { - body: PlacementDecisionDocumentRequest; - headers: { - 'Idempotency-Key': string; - }; +export type GetBillingIdentityConflictData = { + body?: never; path: { - projectId: string; - environmentId: string; - placementId: string; + conflictId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-set'; + url: '/v1/billing/identity/conflicts/{conflictId}'; }; -export type CreatePlacementRuleSetErrors = { +export type GetBillingIdentityConflictErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type CreatePlacementRuleSetError = CreatePlacementRuleSetErrors[keyof CreatePlacementRuleSetErrors]; +export type GetBillingIdentityConflictError = GetBillingIdentityConflictErrors[keyof GetBillingIdentityConflictErrors]; -export type CreatePlacementRuleSetResponses = { +export type GetBillingIdentityConflictResponses = { /** - * Rule Set and Draft created. + * The conflict. */ - 201: PlacementRuleSetDraftEnvelope; + 200: { + data?: BillingIdentityConflictDetail; + }; }; -export type CreatePlacementRuleSetResponse = CreatePlacementRuleSetResponses[keyof CreatePlacementRuleSetResponses]; +export type GetBillingIdentityConflictResponse = GetBillingIdentityConflictResponses[keyof GetBillingIdentityConflictResponses]; -export type UpdatePlacementRuleSetDraftData = { - body: PlacementDecisionDocumentRequest; - headers: { - 'If-Match': string; - 'Idempotency-Key': string; - }; +export type GetBillingCustomerData = { + body?: never; path: { - projectId: string; - environmentId: string; - placementId: string; - ruleSetId: string; + customerId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/draft'; + url: '/v1/billing/server/customers/{customerId}'; }; -export type UpdatePlacementRuleSetDraftErrors = { +export type GetBillingCustomerErrors = { /** * Stable machine-readable failure. */ - 412: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ - 428: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type UpdatePlacementRuleSetDraftError = UpdatePlacementRuleSetDraftErrors[keyof UpdatePlacementRuleSetDraftErrors]; +export type GetBillingCustomerError = GetBillingCustomerErrors[keyof GetBillingCustomerErrors]; -export type UpdatePlacementRuleSetDraftResponses = { +export type GetBillingCustomerResponses = { /** - * New immutable Draft revision. + * The Billing Customer. */ - 200: PlacementRuleSetDraftEnvelope; + 200: { + data?: BillingCustomer; + }; }; -export type UpdatePlacementRuleSetDraftResponse = UpdatePlacementRuleSetDraftResponses[keyof UpdatePlacementRuleSetDraftResponses]; +export type GetBillingCustomerResponse = GetBillingCustomerResponses[keyof GetBillingCustomerResponses]; -export type ValidatePlacementRuleSetData = { +export type GetCustomerEntitlementSnapshotData = { body?: never; path: { - projectId: string; - environmentId: string; - placementId: string; - ruleSetId: string; + customerId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/validate'; + query?: { + /** + * Defaults to the Environment the secret key belongs to. Any other value is 403. + */ + environmentId?: string; + }; + url: '/v1/billing/server/customers/{customerId}/entitlements'; }; -export type ValidatePlacementRuleSetResponses = { +export type GetCustomerEntitlementSnapshotErrors = { /** - * Semantic validation result. + * Stable machine-readable failure. */ - 200: PlacementValidationEnvelope; -}; - -export type ValidatePlacementRuleSetResponse = ValidatePlacementRuleSetResponses[keyof ValidatePlacementRuleSetResponses]; - -export type PublishPlacementRuleSetData = { - body: { - expectedRevision: number; - }; - path: { - projectId: string; - environmentId: string; - placementId: string; - ruleSetId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/publish'; -}; - -export type PublishPlacementRuleSetErrors = { + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 412: ErrorEnvelope; + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type PublishPlacementRuleSetError = PublishPlacementRuleSetErrors[keyof PublishPlacementRuleSetErrors]; +export type GetCustomerEntitlementSnapshotError = GetCustomerEntitlementSnapshotErrors[keyof GetCustomerEntitlementSnapshotErrors]; -export type PublishPlacementRuleSetResponses = { +export type GetCustomerEntitlementSnapshotResponses = { /** - * Immutable Rule Set Version. + * The current snapshot. */ - 201: PlacementRuleSetVersionEnvelope; + 200: CustomerEntitlementSnapshotRecord; }; -export type PublishPlacementRuleSetResponse = PublishPlacementRuleSetResponses[keyof PublishPlacementRuleSetResponses]; +export type GetCustomerEntitlementSnapshotResponse = GetCustomerEntitlementSnapshotResponses[keyof GetCustomerEntitlementSnapshotResponses]; -export type ArchivePlacementRuleSetData = { - body?: never; +export type CheckCustomerEntitlementsData = { + body: EntitlementCheckRequestRecord; path: { - projectId: string; - environmentId: string; - placementId: string; - ruleSetId: string; + customerId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/archive'; + query?: { + environmentId?: string; + }; + url: '/v1/billing/server/customers/{customerId}/entitlement-checks'; }; -export type ArchivePlacementRuleSetErrors = { +export type CheckCustomerEntitlementsErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -7472,351 +10888,350 @@ export type ArchivePlacementRuleSetErrors = { */ 404: ErrorEnvelope; /** - * Rule Set is already archived or conflicts with current state. + * Stable machine-readable failure. */ - 409: ErrorEnvelope; -}; - -export type ArchivePlacementRuleSetError = ArchivePlacementRuleSetErrors[keyof ArchivePlacementRuleSetErrors]; - -export type ArchivePlacementRuleSetResponses = { + 406: ErrorEnvelope; /** - * Rule Set archived; immutable version history is retained. + * Stable machine-readable failure. */ - 204: void; + 422: ErrorEnvelope; }; -export type ArchivePlacementRuleSetResponse = ArchivePlacementRuleSetResponses[keyof ArchivePlacementRuleSetResponses]; - -export type ListPlacementRuleSetVersionsData = { - body?: never; - path: { - projectId: string; - environmentId: string; - placementId: string; - ruleSetId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/versions'; -}; +export type CheckCustomerEntitlementsError = CheckCustomerEntitlementsErrors[keyof CheckCustomerEntitlementsErrors]; -export type ListPlacementRuleSetVersionsResponses = { +export type CheckCustomerEntitlementsResponses = { /** - * Immutable version history. + * The check result. */ - 200: PlacementRuleSetVersionListEnvelope; + 200: EntitlementCheckResultRecord; }; -export type ListPlacementRuleSetVersionsResponse = ListPlacementRuleSetVersionsResponses[keyof ListPlacementRuleSetVersionsResponses]; +export type CheckCustomerEntitlementsResponse = CheckCustomerEntitlementsResponses[keyof CheckCustomerEntitlementsResponses]; -export type ClonePlacementRuleSetVersionData = { +export type ListCustomerSubscriptionsData = { body?: never; - headers: { - 'Idempotency-Key': string; - }; path: { - projectId: string; - environmentId: string; - placementId: string; - ruleSetId: string; - versionId: string; + customerId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/versions/{versionId}/draft'; + query?: { + environmentId?: string; + limit?: number; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/billing/server/customers/{customerId}/subscriptions'; }; -export type ClonePlacementRuleSetVersionErrors = { +export type ListCustomerSubscriptionsErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 409: ErrorEnvelope; }; -export type ClonePlacementRuleSetVersionError = ClonePlacementRuleSetVersionErrors[keyof ClonePlacementRuleSetVersionErrors]; +export type ListCustomerSubscriptionsError = ListCustomerSubscriptionsErrors[keyof ListCustomerSubscriptionsErrors]; -export type ClonePlacementRuleSetVersionResponses = { +export type ListCustomerSubscriptionsResponses = { /** - * Active Draft cloned from the immutable version. + * Projected subscriptions. */ - 201: PlacementRuleSetDraftEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type ClonePlacementRuleSetVersionResponse = ClonePlacementRuleSetVersionResponses[keyof ClonePlacementRuleSetVersionResponses]; +export type ListCustomerSubscriptionsResponse = ListCustomerSubscriptionsResponses[keyof ListCustomerSubscriptionsResponses]; -export type SimulatePlacementDecisionData = { - body: PlacementSimulationRequestWritable; +export type GetSubscriptionSnapshotData = { + body?: never; path: { - projectId: string; - environmentId: string; - placementId: string; - ruleSetId: string; + instanceId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/rule-sets/{ruleSetId}/simulate'; + url: '/v1/billing/server/subscriptions/{instanceId}'; }; -export type SimulatePlacementDecisionErrors = { +export type GetSubscriptionSnapshotErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; }; -export type SimulatePlacementDecisionError = SimulatePlacementDecisionErrors[keyof SimulatePlacementDecisionErrors]; +export type GetSubscriptionSnapshotError = GetSubscriptionSnapshotErrors[keyof GetSubscriptionSnapshotErrors]; -export type SimulatePlacementDecisionResponses = { +export type GetSubscriptionSnapshotResponses = { /** - * Ephemeral bounded decision trace; request inputs are neither logged nor persisted. + * The subscription snapshot. */ - 200: PlacementSimulationEnvelope; + 200: SubscriptionSnapshotRecord; }; -export type SimulatePlacementDecisionResponse = SimulatePlacementDecisionResponses[keyof SimulatePlacementDecisionResponses]; +export type GetSubscriptionSnapshotResponse = GetSubscriptionSnapshotResponses[keyof GetSubscriptionSnapshotResponses]; -export type ListPlacementQaOverridesData = { +export type ListSubscriptionTimelineData = { body?: never; path: { - projectId: string; - environmentId: string; - placementId: string; + instanceId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/qa-overrides'; + query?: { + limit?: number; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/billing/server/subscriptions/{instanceId}/timeline'; }; -export type ListPlacementQaOverridesResponses = { +export type ListSubscriptionTimelineErrors = { /** - * Active non-production QA overrides. + * Stable machine-readable failure. */ - 200: QaOverrideListEnvelope; -}; - -export type ListPlacementQaOverridesResponse = ListPlacementQaOverridesResponses[keyof ListPlacementQaOverridesResponses]; - -export type CreatePlacementQaOverrideData = { - body: CreateQaOverrideRequestWritable; - path: { - projectId: string; - environmentId: string; - placementId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/qa-overrides'; -}; - -export type CreatePlacementQaOverrideErrors = { + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; }; -export type CreatePlacementQaOverrideError = CreatePlacementQaOverrideErrors[keyof CreatePlacementQaOverrideErrors]; +export type ListSubscriptionTimelineError = ListSubscriptionTimelineErrors[keyof ListSubscriptionTimelineErrors]; -export type CreatePlacementQaOverrideResponses = { +export type ListSubscriptionTimelineResponses = { /** - * Override created; opaque token is returned once. + * Timeline entries. */ - 201: QaOverrideCreatedEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type CreatePlacementQaOverrideResponse = CreatePlacementQaOverrideResponses[keyof CreatePlacementQaOverrideResponses]; +export type ListSubscriptionTimelineResponse = ListSubscriptionTimelineResponses[keyof ListSubscriptionTimelineResponses]; -export type RevokePlacementQaOverrideData = { +export type GetBillingSettingsData = { body?: never; path: { projectId: string; - environmentId: string; - placementId: string; - overrideId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/placements/{placementId}/qa-overrides/{overrideId}'; + url: '/v1/projects/{projectId}/billing/settings'; }; -export type RevokePlacementQaOverrideErrors = { +export type GetBillingSettingsErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; -}; - -export type RevokePlacementQaOverrideError = RevokePlacementQaOverrideErrors[keyof RevokePlacementQaOverrideErrors]; - -export type RevokePlacementQaOverrideResponses = { + 403: ErrorEnvelope; /** - * Override revoked; the next immutable release omits it. + * Stable machine-readable failure. */ - 204: void; + 404: ErrorEnvelope; }; -export type RevokePlacementQaOverrideResponse = RevokePlacementQaOverrideResponses[keyof RevokePlacementQaOverrideResponses]; - -export type ListExperimentsData = { - body?: never; - path: { - projectId: string; - environmentId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments'; -}; +export type GetBillingSettingsError = GetBillingSettingsErrors[keyof GetBillingSettingsErrors]; -export type ListExperimentsResponses = { +export type GetBillingSettingsResponses = { /** - * Environment-scoped Experiments. + * Billing settings. */ - 200: ExperimentListEnvelope; + 200: { + data?: BillingSettings; + }; }; -export type ListExperimentsResponse = ListExperimentsResponses[keyof ListExperimentsResponses]; - -export type CreateExperimentData = { - body: CreateExperimentRequest; - headers: { - 'Idempotency-Key': string; +export type GetBillingSettingsResponse = GetBillingSettingsResponses[keyof GetBillingSettingsResponses]; + +export type UpdateBillingSettingsData = { + body: { + billingEnabled: boolean; }; path: { projectId: string; - environmentId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments'; + url: '/v1/projects/{projectId}/billing/settings'; }; -export type CreateExperimentErrors = { +export type UpdateBillingSettingsErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; }; -export type CreateExperimentError = CreateExperimentErrors[keyof CreateExperimentErrors]; +export type UpdateBillingSettingsError = UpdateBillingSettingsErrors[keyof UpdateBillingSettingsErrors]; -export type CreateExperimentResponses = { +export type UpdateBillingSettingsResponses = { /** - * Experiment and first Draft revision. + * Updated settings. */ - 201: ExperimentEnvelope; + 200: { + data?: { + billingEnabled?: boolean; + }; + }; }; -export type CreateExperimentResponse = CreateExperimentResponses[keyof CreateExperimentResponses]; +export type UpdateBillingSettingsResponse = UpdateBillingSettingsResponses[keyof UpdateBillingSettingsResponses]; -export type ListExperimentMetricDefinitionsData = { +export type ListStoreServerCredentialsData = { body?: never; path: { projectId: string; - environmentId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/metrics'; + url: '/v1/projects/{projectId}/billing/store-credentials'; }; -export type ListExperimentMetricDefinitionsResponses = { +export type ListStoreServerCredentialsErrors = { /** - * Immutable Experiment metric definitions. + * Stable machine-readable failure. */ - 200: ExperimentMetricListEnvelope; + 403: ErrorEnvelope; }; -export type ListExperimentMetricDefinitionsResponse = ListExperimentMetricDefinitionsResponses[keyof ListExperimentMetricDefinitionsResponses]; - -export type ListExperimentGroupsData = { - body?: never; - path: { - projectId: string; - environmentId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups'; -}; +export type ListStoreServerCredentialsError = ListStoreServerCredentialsErrors[keyof ListStoreServerCredentialsErrors]; -export type ListExperimentGroupsResponses = { +export type ListStoreServerCredentialsResponses = { /** - * Mutual-exclusion groups. + * Store Server Credentials. */ - 200: ExperimentGroupListEnvelope; + 200: { + data?: { + items?: Array; + }; + }; }; -export type ListExperimentGroupsResponse = ListExperimentGroupsResponses[keyof ListExperimentGroupsResponses]; +export type ListStoreServerCredentialsResponse = ListStoreServerCredentialsResponses[keyof ListStoreServerCredentialsResponses]; -export type CreateExperimentGroupVersionData = { - body: CreateExperimentGroupRequest; +export type CreateStoreServerCredentialData = { + body: CreateStoreServerCredentialRequest; path: { projectId: string; - environmentId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups'; + url: '/v1/projects/{projectId}/billing/store-credentials'; }; -export type CreateExperimentGroupVersionErrors = { +export type CreateStoreServerCredentialErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ 422: ErrorEnvelope; }; -export type CreateExperimentGroupVersionError = CreateExperimentGroupVersionErrors[keyof CreateExperimentGroupVersionErrors]; +export type CreateStoreServerCredentialError = CreateStoreServerCredentialErrors[keyof CreateStoreServerCredentialErrors]; -export type CreateExperimentGroupVersionResponses = { +export type CreateStoreServerCredentialResponses = { /** - * Group and immutable Version. + * Store Server Credential including the one-time notification endpoint URL. This is the only response that ever carries it. */ - 201: ExperimentGroupCreatedEnvelope; + 201: { + data?: StoreServerCredentialWithEndpoint; + }; }; -export type CreateExperimentGroupVersionResponse = CreateExperimentGroupVersionResponses[keyof CreateExperimentGroupVersionResponses]; +export type CreateStoreServerCredentialResponse = CreateStoreServerCredentialResponses[keyof CreateStoreServerCredentialResponses]; -export type ListExperimentMutualExclusionGroupVersionsData = { +export type GetStoreServerCredentialData = { body?: never; path: { projectId: string; - environmentId: string; - groupId: string; + credentialId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups/{groupId}/versions'; + url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}'; }; -export type ListExperimentMutualExclusionGroupVersionsErrors = { +export type GetStoreServerCredentialErrors = { + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type ListExperimentMutualExclusionGroupVersionsError = ListExperimentMutualExclusionGroupVersionsErrors[keyof ListExperimentMutualExclusionGroupVersionsErrors]; +export type GetStoreServerCredentialError = GetStoreServerCredentialErrors[keyof GetStoreServerCredentialErrors]; -export type ListExperimentMutualExclusionGroupVersionsResponses = { +export type GetStoreServerCredentialResponses = { /** - * Immutable group Version history. + * Store Server Credential without secret material or endpoint URL. */ - 200: ExperimentGroupVersionListEnvelope; + 200: { + data?: StoreServerCredential; + }; }; -export type ListExperimentMutualExclusionGroupVersionsResponse = ListExperimentMutualExclusionGroupVersionsResponses[keyof ListExperimentMutualExclusionGroupVersionsResponses]; +export type GetStoreServerCredentialResponse = GetStoreServerCredentialResponses[keyof GetStoreServerCredentialResponses]; -export type CreateExperimentMutualExclusionGroupVersionData = { - body: CreateExperimentGroupVersionRequest; +export type RotateStoreServerCredentialData = { + body: { + /** + * Write-only. Never returned. + */ + secret: string; + }; path: { projectId: string; - environmentId: string; - groupId: string; + credentialId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/groups/{groupId}/versions'; + url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}/rotate'; }; -export type CreateExperimentMutualExclusionGroupVersionErrors = { +export type RotateStoreServerCredentialErrors = { + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -7824,352 +11239,463 @@ export type CreateExperimentMutualExclusionGroupVersionErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type CreateExperimentMutualExclusionGroupVersionError = CreateExperimentMutualExclusionGroupVersionErrors[keyof CreateExperimentMutualExclusionGroupVersionErrors]; +export type RotateStoreServerCredentialError = RotateStoreServerCredentialErrors[keyof RotateStoreServerCredentialErrors]; -export type CreateExperimentMutualExclusionGroupVersionResponses = { +export type RotateStoreServerCredentialResponses = { /** - * New immutable group Version. + * Store Server Credential including the one-time notification endpoint URL. This is the only response that ever carries it. */ - 201: ExperimentGroupVersionEnvelope; + 200: { + data?: StoreServerCredentialWithEndpoint; + }; }; -export type CreateExperimentMutualExclusionGroupVersionResponse = CreateExperimentMutualExclusionGroupVersionResponses[keyof CreateExperimentMutualExclusionGroupVersionResponses]; +export type RotateStoreServerCredentialResponse = RotateStoreServerCredentialResponses[keyof RotateStoreServerCredentialResponses]; -export type GetExperimentData = { +export type RevokeStoreServerCredentialData = { body?: never; path: { projectId: string; - environmentId: string; - experimentId: string; + credentialId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}'; + url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}/revoke'; }; -export type GetExperimentErrors = { +export type RevokeStoreServerCredentialErrors = { + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type GetExperimentError = GetExperimentErrors[keyof GetExperimentErrors]; +export type RevokeStoreServerCredentialError = RevokeStoreServerCredentialErrors[keyof RevokeStoreServerCredentialErrors]; -export type GetExperimentResponses = { +export type RevokeStoreServerCredentialResponses = { /** - * Experiment root + * Store Server Credential without secret material or endpoint URL. */ - 200: ExperimentEnvelope; + 200: { + data?: StoreServerCredential; + }; }; -export type GetExperimentResponse = GetExperimentResponses[keyof GetExperimentResponses]; +export type RevokeStoreServerCredentialResponse = RevokeStoreServerCredentialResponses[keyof RevokeStoreServerCredentialResponses]; -export type UpdateExperimentDraftData = { - body: UpdateExperimentDraftRequest; - headers: { - 'If-Match': string; - 'Idempotency-Key': string; - }; +export type TestStoreServerCredentialData = { + body?: never; path: { projectId: string; - environmentId: string; - experimentId: string; + credentialId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/draft'; + url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}/test'; }; -export type UpdateExperimentDraftErrors = { +export type TestStoreServerCredentialErrors = { /** - * Stale Draft revision with currentRevision and ETag recovery details. + * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ - 428: ErrorEnvelope; + 429: ErrorEnvelope; }; -export type UpdateExperimentDraftError = UpdateExperimentDraftErrors[keyof UpdateExperimentDraftErrors]; +export type TestStoreServerCredentialError = TestStoreServerCredentialErrors[keyof TestStoreServerCredentialErrors]; -export type UpdateExperimentDraftResponses = { +export type TestStoreServerCredentialResponses = { /** - * New immutable Draft revision. + * Store Server Credential without secret material or endpoint URL. */ - 200: ExperimentDraftEnvelope; + 200: { + data?: StoreServerCredential; + }; }; -export type UpdateExperimentDraftResponse = UpdateExperimentDraftResponses[keyof UpdateExperimentDraftResponses]; +export type TestStoreServerCredentialResponse = TestStoreServerCredentialResponses[keyof TestStoreServerCredentialResponses]; -export type ValidateExperimentDraftData = { +export type ListTransactionFactsData = { body?: never; path: { projectId: string; environmentId: string; - experimentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/validate'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not + * construct or parse one — it encodes both the ordering timestamp and the row id, because + * billing identifiers are not time-ordered and an id alone cannot express a position in a + * timestamp ordering. + * + * A malformed or stale value starts from the first page rather than erroring, so a mangled + * cursor cannot silently truncate a list. Omit it for the first page; a response with no + * `nextCursor` is the last page. + * + */ + cursor?: string; + limit?: number; + provider?: 'app_store' | 'google_play'; + from?: string; + to?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/facts'; }; -export type ValidateExperimentDraftResponses = { +export type ListTransactionFactsErrors = { /** - * Scientific + * Stable machine-readable failure. */ - 200: ExperimentValidationEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; +}; + +export type ListTransactionFactsError = ListTransactionFactsErrors[keyof ListTransactionFactsErrors]; + +export type ListTransactionFactsResponses = { + /** + * Transaction Facts. + */ + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type ValidateExperimentDraftResponse = ValidateExperimentDraftResponses[keyof ValidateExperimentDraftResponses]; +export type ListTransactionFactsResponse = ListTransactionFactsResponses[keyof ListTransactionFactsResponses]; -export type PublishExperimentData = { - body: PublishExperimentRequest; +export type ListValidationAttemptsData = { + body?: never; path: { projectId: string; environmentId: string; - experimentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/publish'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not + * construct or parse one — it encodes both the ordering timestamp and the row id, because + * billing identifiers are not time-ordered and an id alone cannot express a position in a + * timestamp ordering. + * + * A malformed or stale value starts from the first page rather than erroring, so a mangled + * cursor cannot silently truncate a list. Omit it for the first page; a response with no + * `nextCursor` is the last page. + * + */ + cursor?: string; + limit?: number; + status?: 'validated' | 'recorded_no_fact' | 'quarantined' | 'retryable_failure' | 'permanently_failed'; + /** + * Narrow the list to one input's attempt history. + */ + rawInputId?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/validation-attempts'; }; -export type PublishExperimentErrors = { +export type ListValidationAttemptsErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type PublishExperimentError = PublishExperimentErrors[keyof PublishExperimentErrors]; +export type ListValidationAttemptsError = ListValidationAttemptsErrors[keyof ListValidationAttemptsErrors]; -export type PublishExperimentResponses = { +export type ListValidationAttemptsResponses = { /** - * Immutable Experiment Version and atomic Configuration Delivery v3 release. + * Validation Attempts. */ - 201: ExperimentVersionEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type PublishExperimentResponse = PublishExperimentResponses[keyof PublishExperimentResponses]; +export type ListValidationAttemptsResponse = ListValidationAttemptsResponses[keyof ListValidationAttemptsResponses]; -export type ListExperimentVersionsData = { +export type ListBillingLedgerData = { body?: never; path: { projectId: string; environmentId: string; - experimentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/versions'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not + * construct or parse one — it encodes both the ordering timestamp and the row id, because + * billing identifiers are not time-ordered and an id alone cannot express a position in a + * timestamp ordering. + * + * A malformed or stale value starts from the first page rather than erroring, so a mangled + * cursor cannot silently truncate a list. Omit it for the first page; a response with no + * `nextCursor` is the last page. + * + */ + cursor?: string; + limit?: number; + from?: string; + to?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/ledger'; }; -export type ListExperimentVersionsResponses = { +export type ListBillingLedgerErrors = { /** - * Immutable Experiment Version history. + * Stable machine-readable failure. */ - 200: ExperimentVersionListEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type ListExperimentVersionsResponse = ListExperimentVersionsResponses[keyof ListExperimentVersionsResponses]; - -export type ListExperimentHistoryData = { - body?: never; - path: { - projectId: string; - environmentId: string; - experimentId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/history'; -}; +export type ListBillingLedgerError = ListBillingLedgerErrors[keyof ListBillingLedgerErrors]; -export type ListExperimentHistoryResponses = { +export type ListBillingLedgerResponses = { /** - * Audited lifecycle and publication history. + * Billing Ledger Entries. */ - 200: ExperimentHistoryListEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type ListExperimentHistoryResponse = ListExperimentHistoryResponses[keyof ListExperimentHistoryResponses]; +export type ListBillingLedgerResponse = ListBillingLedgerResponses[keyof ListBillingLedgerResponses]; -export type GetExperimentResultsData = { +export type ListBillingQuarantineData = { body?: never; path: { projectId: string; environmentId: string; - experimentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/results'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not + * construct or parse one — it encodes both the ordering timestamp and the row id, because + * billing identifiers are not time-ordered and an id alone cannot express a position in a + * timestamp ordering. + * + * A malformed or stale value starts from the first page rather than erroring, so a mangled + * cursor cannot silently truncate a list. Omit it for the first page; a response with no + * `nextCursor` is the last page. + * + */ + cursor?: string; + limit?: number; + status?: 'open' | 'retrying' | 'closed_after_success' | 'closed_superseded'; + reasonCode?: string; + provider?: 'app_store' | 'google_play'; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/quarantine'; }; -export type GetExperimentResultsResponses = { +export type ListBillingQuarantineErrors = { /** - * Unique-unit conversion + * Stable machine-readable failure. */ - 200: ExperimentResultsEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type GetExperimentResultsResponse = GetExperimentResultsResponses[keyof GetExperimentResultsResponses]; - -export type GetExperimentSampleRatioMismatchData = { - body?: never; - path: { - projectId: string; - environmentId: string; - experimentId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/srm'; -}; +export type ListBillingQuarantineError = ListBillingQuarantineErrors[keyof ListBillingQuarantineErrors]; -export type GetExperimentSampleRatioMismatchResponses = { +export type ListBillingQuarantineResponses = { /** - * Descriptive Pearson chi-square SRM diagnostic. + * Quarantine Records. */ 200: { - data: ExperimentSrm; + data?: { + items?: Array; + nextCursor?: string; + }; }; }; -export type GetExperimentSampleRatioMismatchResponse = GetExperimentSampleRatioMismatchResponses[keyof GetExperimentSampleRatioMismatchResponses]; +export type ListBillingQuarantineResponse = ListBillingQuarantineResponses[keyof ListBillingQuarantineResponses]; -export type TransitionExperimentLifecycleData = { - body?: { - reason?: string; - }; +export type GetBillingHealthData = { + body?: never; path: { projectId: string; environmentId: string; - experimentId: string; - lifecycleAction: 'schedule' | 'start' | 'pause' | 'resume' | 'stop' | 'complete' | 'archive' | 'emergency-stop'; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/{lifecycleAction}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/health'; }; -export type TransitionExperimentLifecycleErrors = { +export type GetBillingHealthErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type TransitionExperimentLifecycleError = TransitionExperimentLifecycleErrors[keyof TransitionExperimentLifecycleErrors]; +export type GetBillingHealthError = GetBillingHealthErrors[keyof GetBillingHealthErrors]; -export type TransitionExperimentLifecycleResponses = { +export type GetBillingHealthResponses = { /** - * Updated Experiment root. + * Billing health summary. */ - 200: ExperimentEnvelope; + 200: { + data?: BillingHealth; + }; }; -export type TransitionExperimentLifecycleResponse = TransitionExperimentLifecycleResponses[keyof TransitionExperimentLifecycleResponses]; +export type GetBillingHealthResponse = GetBillingHealthResponses[keyof GetBillingHealthResponses]; -export type ListExperimentQaOverridesData = { +export type GetBillingProjectionHealthData = { body?: never; path: { projectId: string; environmentId: string; - experimentId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/qa-overrides'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/projection-health'; }; -export type ListExperimentQaOverridesResponses = { +export type GetBillingProjectionHealthErrors = { /** - * Safe QA override metadata without tokens. + * Stable machine-readable failure. */ - 200: ExperimentQaOverrideListEnvelope; -}; - -export type ListExperimentQaOverridesResponse = ListExperimentQaOverridesResponses[keyof ListExperimentQaOverridesResponses]; - -export type CreateExperimentQaOverrideData = { - body: CreateExperimentQaOverrideRequest; - path: { - projectId: string; - environmentId: string; - experimentId: string; - }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/qa-overrides'; -}; - -export type CreateExperimentQaOverrideErrors = { + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type CreateExperimentQaOverrideError = CreateExperimentQaOverrideErrors[keyof CreateExperimentQaOverrideErrors]; +export type GetBillingProjectionHealthError = GetBillingProjectionHealthErrors[keyof GetBillingProjectionHealthErrors]; -export type CreateExperimentQaOverrideResponses = { +export type GetBillingProjectionHealthResponses = { /** - * Non-production override; raw token returned once and never delivered as creator identity. + * Projection health summary. */ - 201: ExperimentQaOverrideCreatedEnvelope; + 200: { + data?: BillingProjectionHealth; + }; }; -export type CreateExperimentQaOverrideResponse = CreateExperimentQaOverrideResponses[keyof CreateExperimentQaOverrideResponses]; +export type GetBillingProjectionHealthResponse = GetBillingProjectionHealthResponses[keyof GetBillingProjectionHealthResponses]; -export type RevokeExperimentQaOverrideData = { - body?: never; +export type CreateBillingProjectionReplayData = { + body: CreateProjectionReplayRequest; path: { projectId: string; - environmentId: string; - experimentId: string; - overrideId: string; + environmentId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/qa-overrides/{overrideId}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/projection-replays'; }; -export type RevokeExperimentQaOverrideErrors = { +export type CreateBillingProjectionReplayErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type RevokeExperimentQaOverrideError = RevokeExperimentQaOverrideErrors[keyof RevokeExperimentQaOverrideErrors]; +export type CreateBillingProjectionReplayError = CreateBillingProjectionReplayErrors[keyof CreateBillingProjectionReplayErrors]; -export type RevokeExperimentQaOverrideResponses = { +export type CreateBillingProjectionReplayResponses = { /** - * Override revoked. + * The replay ran. */ - 204: void; + 200: { + data?: ProjectionReplayResult; + }; }; -export type RevokeExperimentQaOverrideResponse = RevokeExperimentQaOverrideResponses[keyof RevokeExperimentQaOverrideResponses]; +export type CreateBillingProjectionReplayResponse = CreateBillingProjectionReplayResponses[keyof CreateBillingProjectionReplayResponses]; -export type CreateExperimentRawExportData = { - body: CreateExperimentExportRequest; +export type ListBillingCustomersData = { + body?: never; path: { projectId: string; environmentId: string; - experimentId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/experiments/{experimentId}/exports'; + query?: { + status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; + /** + * Restrict to identified or to purchase-anchored-only customers. + */ + identified?: boolean; + /** + * Only customers party to an open identity conflict. + */ + conflictedOnly?: boolean; + limit?: number; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers'; }; -export type CreateExperimentRawExportErrors = { +export type ListBillingCustomersErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8181,32 +11707,52 @@ export type CreateExperimentRawExportErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type CreateExperimentRawExportError = CreateExperimentRawExportErrors[keyof CreateExperimentRawExportErrors]; +export type ListBillingCustomersError = ListBillingCustomersErrors[keyof ListBillingCustomersErrors]; -export type CreateExperimentRawExportResponses = { +export type ListBillingCustomersResponses = { /** - * Asynchronous analytics job. + * Billing Customers. */ - 202: AnalyticsJobEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type CreateExperimentRawExportResponse = CreateExperimentRawExportResponses[keyof CreateExperimentRawExportResponses]; +export type ListBillingCustomersResponse = ListBillingCustomersResponses[keyof ListBillingCustomersResponses]; -export type IngestAnalyticsEventBatchData = { - body: AnalyticsEventBatch; - path?: never; +export type LookupBillingCustomerData = { + body: BillingCustomerLookupRequest; + path: { + projectId: string; + environmentId: string; + }; query?: never; - url: '/v1/sdk/events/batch'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customer-lookups'; }; -export type IngestAnalyticsEventBatchErrors = { +export type LookupBillingCustomerErrors = { /** * Stable machine-readable failure. */ 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8221,28 +11767,35 @@ export type IngestAnalyticsEventBatchErrors = { 429: ErrorEnvelope; }; -export type IngestAnalyticsEventBatchError = IngestAnalyticsEventBatchErrors[keyof IngestAnalyticsEventBatchErrors]; +export type LookupBillingCustomerError = LookupBillingCustomerErrors[keyof LookupBillingCustomerErrors]; -export type IngestAnalyticsEventBatchResponses = { +export type LookupBillingCustomerResponses = { /** - * Per-event ingestion outcomes. + * The lookup result. */ - 200: AnalyticsIngestionResult; + 200: { + data?: BillingCustomerLookupResult; + }; }; -export type IngestAnalyticsEventBatchResponse = IngestAnalyticsEventBatchResponses[keyof IngestAnalyticsEventBatchResponses]; +export type LookupBillingCustomerResponse = LookupBillingCustomerResponses[keyof LookupBillingCustomerResponses]; -export type GetAnalyticsSettingsData = { +export type GetOperatorBillingCustomerData = { body?: never; path: { projectId: string; environmentId: string; + customerId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/settings'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}'; }; -export type GetAnalyticsSettingsErrors = { +export type GetOperatorBillingCustomerErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8251,30 +11804,45 @@ export type GetAnalyticsSettingsErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type GetAnalyticsSettingsError = GetAnalyticsSettingsErrors[keyof GetAnalyticsSettingsErrors]; +export type GetOperatorBillingCustomerError = GetOperatorBillingCustomerErrors[keyof GetOperatorBillingCustomerErrors]; -export type GetAnalyticsSettingsResponses = { +export type GetOperatorBillingCustomerResponses = { /** - * Analytics settings. + * The Billing Customer. */ - 200: AnalyticsSettingsEnvelope; + 200: { + data?: BillingCustomerDetail; + }; }; -export type GetAnalyticsSettingsResponse = GetAnalyticsSettingsResponses[keyof GetAnalyticsSettingsResponses]; +export type GetOperatorBillingCustomerResponse = GetOperatorBillingCustomerResponses[keyof GetOperatorBillingCustomerResponses]; -export type UpdateAnalyticsSettingsData = { - body: UpdateAnalyticsSettingsRequest; +export type GetBillingCustomerEntitlementSnapshotData = { + body?: never; path: { projectId: string; environmentId: string; + customerId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/settings'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}/entitlements'; }; -export type UpdateAnalyticsSettingsErrors = { +export type GetBillingCustomerEntitlementSnapshotErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8282,39 +11850,55 @@ export type UpdateAnalyticsSettingsErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type UpdateAnalyticsSettingsError = UpdateAnalyticsSettingsErrors[keyof UpdateAnalyticsSettingsErrors]; +export type GetBillingCustomerEntitlementSnapshotError = GetBillingCustomerEntitlementSnapshotErrors[keyof GetBillingCustomerEntitlementSnapshotErrors]; -export type UpdateAnalyticsSettingsResponses = { +export type GetBillingCustomerEntitlementSnapshotResponses = { /** - * Updated analytics settings. + * The current snapshot. */ - 200: AnalyticsSettingsEnvelope; + 200: { + data?: { + snapshot?: BillingEntitlementSnapshot; + projectionStatus?: BillingProjectionStatus; + }; + }; }; -export type UpdateAnalyticsSettingsResponse = UpdateAnalyticsSettingsResponses[keyof UpdateAnalyticsSettingsResponses]; +export type GetBillingCustomerEntitlementSnapshotResponse = GetBillingCustomerEntitlementSnapshotResponses[keyof GetBillingCustomerEntitlementSnapshotResponses]; -export type GetAnalyticsOverviewData = { +export type ListBillingCustomerSubscriptionsData = { body?: never; path: { projectId: string; environmentId: string; + customerId: string; }; - query: { - from: Timestamp; - to: Timestamp; - timezone: string; - metricBasis: 'event_count'; - platform?: 'ios' | 'android'; - locale?: string; - applicationVersion?: string; + query?: { + limit?: number; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/overview'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}/subscriptions'; }; -export type GetAnalyticsOverviewErrors = { +export type ListBillingCustomerSubscriptionsErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8322,40 +11906,53 @@ export type GetAnalyticsOverviewErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type GetAnalyticsOverviewError = GetAnalyticsOverviewErrors[keyof GetAnalyticsOverviewErrors]; +export type ListBillingCustomerSubscriptionsError = ListBillingCustomerSubscriptionsErrors[keyof ListBillingCustomerSubscriptionsErrors]; -export type GetAnalyticsOverviewResponses = { +export type ListBillingCustomerSubscriptionsResponses = { /** - * Event-count analytics result. + * Subscriptions. */ - 200: AnalyticsResultEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type GetAnalyticsOverviewResponse = GetAnalyticsOverviewResponses[keyof GetAnalyticsOverviewResponses]; +export type ListBillingCustomerSubscriptionsResponse = ListBillingCustomerSubscriptionsResponses[keyof ListBillingCustomerSubscriptionsResponses]; -export type GetAnalyticsFunnelData = { +export type CreateBillingCustomerSyncRequestData = { body?: never; path: { projectId: string; environmentId: string; - funnel: 'placements' | 'paywalls' | 'products' | 'purchases'; - }; - query: { - from: Timestamp; - to: Timestamp; - timezone: string; - metricBasis: 'event_count'; - platform?: 'ios' | 'android'; - locale?: string; - applicationVersion?: string; + customerId: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/funnels/{funnel}'; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}/sync-requests'; }; -export type GetAnalyticsFunnelErrors = { +export type CreateBillingCustomerSyncRequestErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8363,148 +11960,209 @@ export type GetAnalyticsFunnelErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 429: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type GetAnalyticsFunnelError = GetAnalyticsFunnelErrors[keyof GetAnalyticsFunnelErrors]; +export type CreateBillingCustomerSyncRequestError = CreateBillingCustomerSyncRequestErrors[keyof CreateBillingCustomerSyncRequestErrors]; -export type GetAnalyticsFunnelResponses = { +export type CreateBillingCustomerSyncRequestResponses = { /** - * Event-count analytics result. + * The projection was queued. */ - 200: AnalyticsResultEnvelope; + 202: { + data?: OperatorBillingSyncRequest; + }; }; -export type GetAnalyticsFunnelResponse = GetAnalyticsFunnelResponses[keyof GetAnalyticsFunnelResponses]; +export type CreateBillingCustomerSyncRequestResponse = CreateBillingCustomerSyncRequestResponses[keyof CreateBillingCustomerSyncRequestResponses]; -export type CompareAnalyticsPaywallVersionsData = { +export type GetBillingSubscriptionData = { body?: never; path: { projectId: string; environmentId: string; + instanceId: string; }; - query: { - from: Timestamp; - to: Timestamp; - timezone: string; - metricBasis: 'event_count'; - platform?: 'ios' | 'android'; - locale?: string; - applicationVersion?: string; - }; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/paywall-version-comparison'; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/subscriptions/{instanceId}'; }; -export type CompareAnalyticsPaywallVersionsErrors = { +export type GetBillingSubscriptionErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type CompareAnalyticsPaywallVersionsError = CompareAnalyticsPaywallVersionsErrors[keyof CompareAnalyticsPaywallVersionsErrors]; +export type GetBillingSubscriptionError = GetBillingSubscriptionErrors[keyof GetBillingSubscriptionErrors]; -export type CompareAnalyticsPaywallVersionsResponses = { +export type GetBillingSubscriptionResponses = { /** - * Event-count analytics result. + * The Subscription Instance. */ - 200: AnalyticsResultEnvelope; + 200: { + data?: BillingSubscriptionSnapshot; + }; }; -export type CompareAnalyticsPaywallVersionsResponse = CompareAnalyticsPaywallVersionsResponses[keyof CompareAnalyticsPaywallVersionsResponses]; +export type GetBillingSubscriptionResponse = GetBillingSubscriptionResponses[keyof GetBillingSubscriptionResponses]; -export type GetAnalyticsProviderErrorsData = { +export type ListBillingSubscriptionTimelineData = { body?: never; path: { projectId: string; environmentId: string; + instanceId: string; }; - query: { - from: Timestamp; - to: Timestamp; - timezone: string; - metricBasis: 'event_count'; - platform?: 'ios' | 'android'; - locale?: string; - applicationVersion?: string; + query?: { + limit?: number; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/provider-errors'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/subscriptions/{instanceId}/timeline'; }; -export type GetAnalyticsProviderErrorsErrors = { +export type ListBillingSubscriptionTimelineErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type GetAnalyticsProviderErrorsError = GetAnalyticsProviderErrorsErrors[keyof GetAnalyticsProviderErrorsErrors]; +export type ListBillingSubscriptionTimelineError = ListBillingSubscriptionTimelineErrors[keyof ListBillingSubscriptionTimelineErrors]; -export type GetAnalyticsProviderErrorsResponses = { +export type ListBillingSubscriptionTimelineResponses = { /** - * Event-count analytics result. + * Timeline entries. */ - 200: AnalyticsResultEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type GetAnalyticsProviderErrorsResponse = GetAnalyticsProviderErrorsResponses[keyof GetAnalyticsProviderErrorsResponses]; +export type ListBillingSubscriptionTimelineResponse = ListBillingSubscriptionTimelineResponses[keyof ListBillingSubscriptionTimelineResponses]; -export type GetAnalyticsProductAvailabilityFailuresData = { +export type ListBillingRestoreJobsData = { body?: never; path: { projectId: string; environmentId: string; }; - query: { - from: Timestamp; - to: Timestamp; - timezone: string; - metricBasis: 'event_count'; - platform?: 'ios' | 'android'; - locale?: string; - applicationVersion?: string; + query?: { + billingCustomerId?: string; + limit?: number; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/product-availability-failures'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/restore-jobs'; }; -export type GetAnalyticsProductAvailabilityFailuresErrors = { +export type ListBillingRestoreJobsErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type GetAnalyticsProductAvailabilityFailuresError = GetAnalyticsProductAvailabilityFailuresErrors[keyof GetAnalyticsProductAvailabilityFailuresErrors]; +export type ListBillingRestoreJobsError = ListBillingRestoreJobsErrors[keyof ListBillingRestoreJobsErrors]; -export type GetAnalyticsProductAvailabilityFailuresResponses = { +export type ListBillingRestoreJobsResponses = { /** - * Event-count analytics result. + * Restore jobs. */ - 200: AnalyticsResultEnvelope; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type GetAnalyticsProductAvailabilityFailuresResponse = GetAnalyticsProductAvailabilityFailuresResponses[keyof GetAnalyticsProductAvailabilityFailuresResponses]; +export type ListBillingRestoreJobsResponse = ListBillingRestoreJobsResponses[keyof ListBillingRestoreJobsResponses]; -export type GetAnalyticsBreakdownData = { +export type GetBillingRestoreJobData = { body?: never; path: { projectId: string; environmentId: string; - dimension: 'platforms' | 'locales'; - }; - query: { - from: Timestamp; - to: Timestamp; - timezone: string; - metricBasis: 'event_count'; - platform?: 'ios' | 'android'; - locale?: string; - applicationVersion?: string; + restoreId: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/breakdowns/{dimension}'; + query?: never; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/restore-jobs/{restoreId}'; }; -export type GetAnalyticsBreakdownErrors = { +export type GetBillingRestoreJobErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8512,64 +12170,94 @@ export type GetAnalyticsBreakdownErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type GetAnalyticsBreakdownError = GetAnalyticsBreakdownErrors[keyof GetAnalyticsBreakdownErrors]; +export type GetBillingRestoreJobError = GetBillingRestoreJobErrors[keyof GetBillingRestoreJobErrors]; -export type GetAnalyticsBreakdownResponses = { +export type GetBillingRestoreJobResponses = { /** - * Event-count analytics result. + * The restore job. */ - 200: AnalyticsResultEnvelope; + 200: { + data?: BillingRestoreJob; + }; }; -export type GetAnalyticsBreakdownResponse = GetAnalyticsBreakdownResponses[keyof GetAnalyticsBreakdownResponses]; +export type GetBillingRestoreJobResponse = GetBillingRestoreJobResponses[keyof GetBillingRestoreJobResponses]; -export type GetAnalyticsFreshnessData = { +export type ListOperatorBillingIdentityConflictsData = { body?: never; path: { projectId: string; - environmentId: string; }; - query: { - from: Timestamp; - to: Timestamp; - timezone: string; - metricBasis: 'event_count'; + query?: { + status?: 'open' | 'resolved'; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/freshness'; + url: '/v1/projects/{projectId}/billing/identity-conflicts'; }; -export type GetAnalyticsFreshnessErrors = { +export type ListOperatorBillingIdentityConflictsErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type GetAnalyticsFreshnessError = GetAnalyticsFreshnessErrors[keyof GetAnalyticsFreshnessErrors]; +export type ListOperatorBillingIdentityConflictsError = ListOperatorBillingIdentityConflictsErrors[keyof ListOperatorBillingIdentityConflictsErrors]; -export type GetAnalyticsFreshnessResponses = { +export type ListOperatorBillingIdentityConflictsResponses = { /** - * Event-count analytics result. + * Identity conflicts. */ - 200: AnalyticsResultEnvelope; + 200: { + data?: { + items?: Array; + }; + }; }; -export type GetAnalyticsFreshnessResponse = GetAnalyticsFreshnessResponses[keyof GetAnalyticsFreshnessResponses]; +export type ListOperatorBillingIdentityConflictsResponse = ListOperatorBillingIdentityConflictsResponses[keyof ListOperatorBillingIdentityConflictsResponses]; -export type CreateAnalyticsEventExportData = { - body: CreateAnalyticsEventExportRequest; +export type GetOperatorBillingIdentityConflictData = { + body?: never; path: { projectId: string; - environmentId: string; + conflictId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/analytics/exports'; + url: '/v1/projects/{projectId}/billing/identity-conflicts/{conflictId}'; }; -export type CreateAnalyticsEventExportErrors = { +export type GetOperatorBillingIdentityConflictErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8577,30 +12265,45 @@ export type CreateAnalyticsEventExportErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type CreateAnalyticsEventExportError = CreateAnalyticsEventExportErrors[keyof CreateAnalyticsEventExportErrors]; +export type GetOperatorBillingIdentityConflictError = GetOperatorBillingIdentityConflictErrors[keyof GetOperatorBillingIdentityConflictErrors]; -export type CreateAnalyticsEventExportResponses = { +export type GetOperatorBillingIdentityConflictResponses = { /** - * Asynchronous analytics job. + * The conflict. */ - 202: AnalyticsJobEnvelope; + 200: { + data?: OperatorBillingIdentityConflictDetail; + }; }; -export type CreateAnalyticsEventExportResponse = CreateAnalyticsEventExportResponses[keyof CreateAnalyticsEventExportResponses]; +export type GetOperatorBillingIdentityConflictResponse = GetOperatorBillingIdentityConflictResponses[keyof GetOperatorBillingIdentityConflictResponses]; -export type PreviewAnalyticsPrivacyRequestData = { - body: AnalyticsIdentityRequest; +export type ResolveBillingIdentityConflictData = { + body: ResolveIdentityConflictRequest; path: { projectId: string; + conflictId: string; }; query?: never; - url: '/v1/projects/{projectId}/analytics/privacy/preview'; + url: '/v1/projects/{projectId}/billing/identity-conflicts/{conflictId}/resolution'; }; -export type PreviewAnalyticsPrivacyRequestErrors = { +export type ResolveBillingIdentityConflictErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8609,29 +12312,62 @@ export type PreviewAnalyticsPrivacyRequestErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 429: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type PreviewAnalyticsPrivacyRequestError = PreviewAnalyticsPrivacyRequestErrors[keyof PreviewAnalyticsPrivacyRequestErrors]; +export type ResolveBillingIdentityConflictError = ResolveBillingIdentityConflictErrors[keyof ResolveBillingIdentityConflictErrors]; -export type PreviewAnalyticsPrivacyRequestResponses = { +export type ResolveBillingIdentityConflictResponses = { /** - * Privacy impact preview. + * The resolved conflict. */ - 200: AnalyticsPrivacyPreviewEnvelope; + 200: { + data?: OperatorBillingIdentityConflict; + }; }; -export type PreviewAnalyticsPrivacyRequestResponse = PreviewAnalyticsPrivacyRequestResponses[keyof PreviewAnalyticsPrivacyRequestResponses]; +export type ResolveBillingIdentityConflictResponse = ResolveBillingIdentityConflictResponses[keyof ResolveBillingIdentityConflictResponses]; -export type CreateAnalyticsPrivacyExportData = { - body: CreateAnalyticsPrivacyExportRequest; +export type ListProductEntitlementGrantVersionsData = { + body?: never; path: { projectId: string; }; - query?: never; - url: '/v1/projects/{projectId}/analytics/privacy/exports'; + query: { + productId: string; + /** + * Restricts the history to one (Product + */ + entitlementId?: string; + /** + * Return only the open-ended version in force now. + */ + currentOnly?: boolean; + limit?: number; + }; + url: '/v1/projects/{projectId}/billing/grant-versions'; }; -export type CreateAnalyticsPrivacyExportErrors = { +export type ListProductEntitlementGrantVersionsErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8639,34 +12375,54 @@ export type CreateAnalyticsPrivacyExportErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type CreateAnalyticsPrivacyExportError = CreateAnalyticsPrivacyExportErrors[keyof CreateAnalyticsPrivacyExportErrors]; +export type ListProductEntitlementGrantVersionsError = ListProductEntitlementGrantVersionsErrors[keyof ListProductEntitlementGrantVersionsErrors]; -export type CreateAnalyticsPrivacyExportResponses = { +export type ListProductEntitlementGrantVersionsResponses = { /** - * Asynchronous analytics job. + * Grant versions. */ - 202: AnalyticsJobEnvelope; + 200: { + data?: { + items?: Array; + }; + }; }; -export type CreateAnalyticsPrivacyExportResponse = CreateAnalyticsPrivacyExportResponses[keyof CreateAnalyticsPrivacyExportResponses]; +export type ListProductEntitlementGrantVersionsResponse = ListProductEntitlementGrantVersionsResponses[keyof ListProductEntitlementGrantVersionsResponses]; -export type CreateAnalyticsPrivacyDeletionData = { - body: CreateAnalyticsPrivacyDeletionRequest; +export type PublishProductEntitlementGrantVersionData = { + body: PublishGrantVersionRequest; path: { projectId: string; }; query?: never; - url: '/v1/projects/{projectId}/analytics/privacy/deletions'; + url: '/v1/projects/{projectId}/billing/grant-versions'; }; -export type CreateAnalyticsPrivacyDeletionErrors = { +export type PublishProductEntitlementGrantVersionErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8675,30 +12431,39 @@ export type CreateAnalyticsPrivacyDeletionErrors = { * Stable machine-readable failure. */ 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type CreateAnalyticsPrivacyDeletionError = CreateAnalyticsPrivacyDeletionErrors[keyof CreateAnalyticsPrivacyDeletionErrors]; +export type PublishProductEntitlementGrantVersionError = PublishProductEntitlementGrantVersionErrors[keyof PublishProductEntitlementGrantVersionErrors]; -export type CreateAnalyticsPrivacyDeletionResponses = { +export type PublishProductEntitlementGrantVersionResponses = { /** - * Asynchronous analytics job. + * The published grant version. */ - 202: AnalyticsJobEnvelope; + 201: { + data?: ProductEntitlementGrantVersion; + }; }; -export type CreateAnalyticsPrivacyDeletionResponse = CreateAnalyticsPrivacyDeletionResponses[keyof CreateAnalyticsPrivacyDeletionResponses]; +export type PublishProductEntitlementGrantVersionResponse = PublishProductEntitlementGrantVersionResponses[keyof PublishProductEntitlementGrantVersionResponses]; -export type GetAnalyticsJobData = { - body?: never; +export type PreviewProductEntitlementGrantImpactData = { + body: PublishGrantVersionRequest; path: { projectId: string; - jobId: string; }; query?: never; - url: '/v1/projects/{projectId}/analytics/jobs/{jobId}'; + url: '/v1/projects/{projectId}/billing/grant-versions/impact-preview'; }; -export type GetAnalyticsJobErrors = { +export type PreviewProductEntitlementGrantImpactErrors = { + /** + * Stable machine-readable failure. + */ + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -8707,202 +12472,205 @@ export type GetAnalyticsJobErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetAnalyticsJobError = GetAnalyticsJobErrors[keyof GetAnalyticsJobErrors]; +export type PreviewProductEntitlementGrantImpactError = PreviewProductEntitlementGrantImpactErrors[keyof PreviewProductEntitlementGrantImpactErrors]; -export type GetAnalyticsJobResponses = { +export type PreviewProductEntitlementGrantImpactResponses = { /** - * Asynchronous analytics job. + * The impact preview. */ - 200: AnalyticsJobEnvelope; + 200: { + data?: GrantVersionImpact; + }; }; -export type GetAnalyticsJobResponse = GetAnalyticsJobResponses[keyof GetAnalyticsJobResponses]; +export type PreviewProductEntitlementGrantImpactResponse = PreviewProductEntitlementGrantImpactResponses[keyof PreviewProductEntitlementGrantImpactResponses]; -export type DownloadAnalyticsJobData = { +export type GetProductEntitlementGrantVersionData = { body?: never; path: { projectId: string; - jobId: string; + grantVersionId: string; }; query?: never; - url: '/v1/projects/{projectId}/analytics/jobs/{jobId}/download'; + url: '/v1/projects/{projectId}/billing/grant-versions/{grantVersionId}'; }; -export type DownloadAnalyticsJobErrors = { +export type GetProductEntitlementGrantVersionErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type DownloadAnalyticsJobError = DownloadAnalyticsJobErrors[keyof DownloadAnalyticsJobErrors]; +export type GetProductEntitlementGrantVersionError = GetProductEntitlementGrantVersionErrors[keyof GetProductEntitlementGrantVersionErrors]; -export type DownloadAnalyticsJobResponses = { +export type GetProductEntitlementGrantVersionResponses = { /** - * Private export artifact. + * The grant version. */ - 200: Blob | File; + 200: { + data?: ProductEntitlementGrantVersion; + }; }; -export type DownloadAnalyticsJobResponse = DownloadAnalyticsJobResponses[keyof DownloadAnalyticsJobResponses]; +export type GetProductEntitlementGrantVersionResponse = GetProductEntitlementGrantVersionResponses[keyof GetProductEntitlementGrantVersionResponses]; -export type ReceiveAppleStoreNotificationData = { - body: { - /** - * Apple JWS notification payload. Never logged or echoed. - */ - signedPayload: string; +export type UpdateProductEntitlementGrantVersionData = { + body?: never; + path: { + projectId: string; + grantVersionId: string; }; + query?: never; + url: '/v1/projects/{projectId}/billing/grant-versions/{grantVersionId}'; +}; + +export type UpdateProductEntitlementGrantVersionErrors = { + /** + * Stable machine-readable failure. + */ + 409: ErrorEnvelope; +}; + +export type UpdateProductEntitlementGrantVersionError = UpdateProductEntitlementGrantVersionErrors[keyof UpdateProductEntitlementGrantVersionErrors]; + +export type ListWebhookDestinationsData = { + body?: never; path: { - /** - * One-time intake token issued with the credential. - */ - intakeToken: string; + projectId: string; + environmentId: string; }; query?: never; - url: '/v1/billing/apple/notifications/{intakeToken}'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/webhook-destinations'; }; -export type ReceiveAppleStoreNotificationErrors = { +export type ListWebhookDestinationsErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 401: ErrorEnvelope; /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type ReceiveAppleStoreNotificationError = ReceiveAppleStoreNotificationErrors[keyof ReceiveAppleStoreNotificationErrors]; +export type ListWebhookDestinationsError = ListWebhookDestinationsErrors[keyof ListWebhookDestinationsErrors]; -export type ReceiveAppleStoreNotificationResponses = { +export type ListWebhookDestinationsResponses = { /** - * The notification was durably recorded and queued for validation. + * Destinations. */ - 202: { - data?: { - status?: 'accepted'; - }; + 200: { + data?: Array; }; }; -export type ReceiveAppleStoreNotificationResponse = ReceiveAppleStoreNotificationResponses[keyof ReceiveAppleStoreNotificationResponses]; +export type ListWebhookDestinationsResponse = ListWebhookDestinationsResponses[keyof ListWebhookDestinationsResponses]; -export type SubmitTransactionObservationData = { - body: ClientTransactionObservationRecord; - headers?: { - /** - * Optional Customer Access Token binding the observation to its customer as token-bound public-client evidence. It can attach an unowned lineage but cannot reassign or freeze an attached lineage. - */ - 'Mosaic-Customer-Token'?: string; +export type CreateWebhookDestinationData = { + body: CreateWebhookDestinationRequest; + path: { + projectId: string; + environmentId: string; }; - path?: never; query?: never; - url: '/v1/sdk/billing/observations'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/webhook-destinations'; }; -export type SubmitTransactionObservationErrors = { +export type CreateWebhookDestinationErrors = { /** * Stable machine-readable failure. */ 401: ErrorEnvelope; /** - * Resubmitting the identical document cannot succeed. The SDK queue should drop it. - */ - 422: ObservationSubmissionResultRecord; - /** - * Transient. Resubmit the identical document later; Retry-After carries the hint. + * Stable machine-readable failure. */ - 429: ObservationSubmissionResultRecord; + 403: ErrorEnvelope; /** - * Transient. Resubmit the identical document later; Retry-After carries the hint. + * Stable machine-readable failure. */ - 503: ObservationSubmissionResultRecord; + 422: ErrorEnvelope; }; -export type SubmitTransactionObservationError = SubmitTransactionObservationErrors[keyof SubmitTransactionObservationErrors]; +export type CreateWebhookDestinationError = CreateWebhookDestinationErrors[keyof CreateWebhookDestinationErrors]; -export type SubmitTransactionObservationResponses = { - /** - * The submission was already recorded. A duplicate is idempotent, not an error. - */ - 200: ObservationSubmissionResultRecord; +export type CreateWebhookDestinationResponses = { /** - * The observation is well formed and queued for validation. Nothing more: the store has not been consulted when this response is written. + * The destination and its one-time signing secret. */ - 202: ObservationSubmissionResultRecord; + 201: { + data?: WebhookDestinationWithSecret; + }; }; -export type SubmitTransactionObservationResponse = SubmitTransactionObservationResponses[keyof SubmitTransactionObservationResponses]; +export type CreateWebhookDestinationResponse = CreateWebhookDestinationResponses[keyof CreateWebhookDestinationResponses]; -export type SubmitServerTransactionObservationData = { - body: ServerTransactionObservationRecord; - headers?: { - /** - * Optional Customer Access Token naming the customer. Because this request is authenticated by the secret server key - */ - 'Mosaic-Customer-Token'?: string; +export type DeleteWebhookDestinationData = { + body?: never; + path: { + projectId: string; + destinationId: string; }; - path?: never; query?: never; - url: '/v1/billing/server/observations'; + url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}'; }; -export type SubmitServerTransactionObservationErrors = { +export type DeleteWebhookDestinationErrors = { /** * Stable machine-readable failure. */ 401: ErrorEnvelope; /** - * Resubmitting the identical document cannot succeed. The SDK queue should drop it. - */ - 422: ObservationSubmissionResultRecord; - /** - * Transient. Resubmit the identical document later; Retry-After carries the hint. + * Stable machine-readable failure. */ - 429: ObservationSubmissionResultRecord; + 404: ErrorEnvelope; /** - * Transient. Resubmit the identical document later; Retry-After carries the hint. + * Stable machine-readable failure. */ - 503: ObservationSubmissionResultRecord; + 409: ErrorEnvelope; }; -export type SubmitServerTransactionObservationError = SubmitServerTransactionObservationErrors[keyof SubmitServerTransactionObservationErrors]; +export type DeleteWebhookDestinationError = DeleteWebhookDestinationErrors[keyof DeleteWebhookDestinationErrors]; -export type SubmitServerTransactionObservationResponses = { - /** - * The submission was already recorded. A duplicate is idempotent, not an error. - */ - 200: ObservationSubmissionResultRecord; +export type DeleteWebhookDestinationResponses = { /** - * The observation is well formed and queued for validation. Nothing more: the store has not been consulted when this response is written. + * The destination was removed. */ - 202: ObservationSubmissionResultRecord; + 204: void; }; -export type SubmitServerTransactionObservationResponse = SubmitServerTransactionObservationResponses[keyof SubmitServerTransactionObservationResponses]; +export type DeleteWebhookDestinationResponse = DeleteWebhookDestinationResponses[keyof DeleteWebhookDestinationResponses]; -export type SyncCustomerEntitlementsData = { +export type GetWebhookDestinationData = { body?: never; - headers: { - /** - * Public SDK key identifying the Environment and Application. - */ - 'Mosaic-SDK-Key': string; + path: { + projectId: string; + destinationId: string; }; - path?: never; query?: never; - url: '/v1/sdk/billing/entitlements'; + url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}'; }; -export type SyncCustomerEntitlementsErrors = { +export type GetWebhookDestinationErrors = { /** * Stable machine-readable failure. */ @@ -8910,39 +12678,33 @@ export type SyncCustomerEntitlementsErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 429: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type SyncCustomerEntitlementsError = SyncCustomerEntitlementsErrors[keyof SyncCustomerEntitlementsErrors]; +export type GetWebhookDestinationError = GetWebhookDestinationErrors[keyof GetWebhookDestinationErrors]; -export type SyncCustomerEntitlementsResponses = { +export type GetWebhookDestinationResponses = { /** - * The current Customer Entitlement Snapshot. + * The destination. */ - 200: CustomerEntitlementSnapshotRecord; + 200: { + data?: WebhookDestination; + }; }; -export type SyncCustomerEntitlementsResponse = SyncCustomerEntitlementsResponses[keyof SyncCustomerEntitlementsResponses]; +export type GetWebhookDestinationResponse = GetWebhookDestinationResponses[keyof GetWebhookDestinationResponses]; -export type SyncCustomerEntitlementsWithNegotiationData = { - body: EntitlementSyncRequestRecord; - headers: { - 'Mosaic-SDK-Key': string; +export type UpdateWebhookDestinationData = { + body: UpdateWebhookDestinationRequest; + path: { + projectId: string; + destinationId: string; }; - path?: never; query?: never; - url: '/v1/sdk/billing/entitlements'; + url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}'; }; -export type SyncCustomerEntitlementsWithNegotiationErrors = { +export type UpdateWebhookDestinationErrors = { /** * Stable machine-readable failure. */ @@ -8950,52 +12712,37 @@ export type SyncCustomerEntitlementsWithNegotiationErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 406: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ 422: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 429: ErrorEnvelope; }; -export type SyncCustomerEntitlementsWithNegotiationError = SyncCustomerEntitlementsWithNegotiationErrors[keyof SyncCustomerEntitlementsWithNegotiationErrors]; +export type UpdateWebhookDestinationError = UpdateWebhookDestinationErrors[keyof UpdateWebhookDestinationErrors]; -export type SyncCustomerEntitlementsWithNegotiationResponses = { +export type UpdateWebhookDestinationResponses = { /** - * The current Customer Entitlement Snapshot, or — when the stated knownSnapshotVersion - * and entity tag both match — the canonical snapshotUnchanged record. This form never - * answers 304: a bodyless response would force freshness into header names no frozen - * schema defines, so the unchanged record carries refreshAfter, validUntil, and - * staleGraceSeconds in the body instead, recomputed at the instant it was answered. - * + * The updated destination. */ - 200: CustomerEntitlementSnapshotRecord; + 200: { + data?: WebhookDestination; + }; }; -export type SyncCustomerEntitlementsWithNegotiationResponse = SyncCustomerEntitlementsWithNegotiationResponses[keyof SyncCustomerEntitlementsWithNegotiationResponses]; +export type UpdateWebhookDestinationResponse = UpdateWebhookDestinationResponses[keyof UpdateWebhookDestinationResponses]; -export type ListCustomerAccessTokensData = { - body?: never; - path?: never; - query: { - billingCustomerId: string; - limit?: number; +export type SetWebhookDestinationStatusData = { + body: SetWebhookDestinationStatusRequest; + path: { + projectId: string; + destinationId: string; }; - url: '/v1/billing/server/customer-tokens'; + query?: never; + url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/status'; }; -export type ListCustomerAccessTokensErrors = { +export type SetWebhookDestinationStatusErrors = { /** * Stable machine-readable failure. */ @@ -9003,36 +12750,37 @@ export type ListCustomerAccessTokensErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ 422: ErrorEnvelope; }; -export type ListCustomerAccessTokensError = ListCustomerAccessTokensErrors[keyof ListCustomerAccessTokensErrors]; +export type SetWebhookDestinationStatusError = SetWebhookDestinationStatusErrors[keyof SetWebhookDestinationStatusErrors]; -export type ListCustomerAccessTokensResponses = { +export type SetWebhookDestinationStatusResponses = { /** - * Token metadata. + * The updated destination. */ 200: { - data?: { - items?: Array; - }; + data?: WebhookDestination; }; }; -export type ListCustomerAccessTokensResponse = ListCustomerAccessTokensResponses[keyof ListCustomerAccessTokensResponses]; +export type SetWebhookDestinationStatusResponse = SetWebhookDestinationStatusResponses[keyof SetWebhookDestinationStatusResponses]; -export type IssueCustomerAccessTokenData = { - body: CustomerAccessTokenIssuanceRequest; - path?: never; +export type ListWebhookSigningSecretsData = { + body?: never; + path: { + projectId: string; + destinationId: string; + }; query?: never; - url: '/v1/billing/server/customer-tokens'; + url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/secrets'; }; -export type IssueCustomerAccessTokenErrors = { +export type ListWebhookSigningSecretsErrors = { /** * Stable machine-readable failure. */ @@ -9041,39 +12789,32 @@ export type IssueCustomerAccessTokenErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; }; -export type IssueCustomerAccessTokenError = IssueCustomerAccessTokenErrors[keyof IssueCustomerAccessTokenErrors]; +export type ListWebhookSigningSecretsError = ListWebhookSigningSecretsErrors[keyof ListWebhookSigningSecretsErrors]; -export type IssueCustomerAccessTokenResponses = { +export type ListWebhookSigningSecretsResponses = { /** - * The token and its metadata. The token value appears here and nowhere else. + * Secret metadata. */ - 201: CustomerAccessTokenIssuanceResult; + 200: { + data?: Array; + }; }; -export type IssueCustomerAccessTokenResponse = IssueCustomerAccessTokenResponses[keyof IssueCustomerAccessTokenResponses]; +export type ListWebhookSigningSecretsResponse = ListWebhookSigningSecretsResponses[keyof ListWebhookSigningSecretsResponses]; -export type RevokeCustomerAccessTokenData = { - body: { - revocationReason: 'customer_signed_out' | 'identity_changed' | 'operator_revoked' | 'customer_deleted' | 'key_rotated' | 'suspected_compromise' | 'superseded_by_new_token'; - }; +export type RotateWebhookSigningSecretData = { + body?: never; path: { - tokenId: string; + projectId: string; + destinationId: string; }; query?: never; - url: '/v1/billing/server/customer-tokens/{tokenId}/revoke'; + url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/secrets/rotate'; }; -export type RevokeCustomerAccessTokenErrors = { +export type RotateWebhookSigningSecretErrors = { /** * Stable machine-readable failure. */ @@ -9082,43 +12823,33 @@ export type RevokeCustomerAccessTokenErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; }; -export type RevokeCustomerAccessTokenError = RevokeCustomerAccessTokenErrors[keyof RevokeCustomerAccessTokenErrors]; +export type RotateWebhookSigningSecretError = RotateWebhookSigningSecretErrors[keyof RotateWebhookSigningSecretErrors]; -export type RevokeCustomerAccessTokenResponses = { +export type RotateWebhookSigningSecretResponses = { /** - * The revoked token's metadata. + * The new secret and the overlap deadline. */ - 200: { - data?: CustomerAccessTokenMetadata; + 201: { + data?: WebhookDestinationWithSecret; }; }; -export type RevokeCustomerAccessTokenResponse = RevokeCustomerAccessTokenResponses[keyof RevokeCustomerAccessTokenResponses]; +export type RotateWebhookSigningSecretResponse = RotateWebhookSigningSecretResponses[keyof RotateWebhookSigningSecretResponses]; -export type SubmitSdkRestoreData = { - body: RestoreRequestRecord; - headers: { - /** - * Public SDK key identifying the Environment and Application. It proves which Environment is asking and can never select a customer. - */ - 'Mosaic-SDK-Key': string; +export type RetireWebhookSigningSecretData = { + body?: never; + path: { + projectId: string; + destinationId: string; + secretId: string; }; - path?: never; query?: never; - url: '/v1/sdk/billing/restores'; + url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/secrets/{secretId}/retire'; }; -export type SubmitSdkRestoreErrors = { +export type RetireWebhookSigningSecretErrors = { /** * Stable machine-readable failure. */ @@ -9126,41 +12857,42 @@ export type SubmitSdkRestoreErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ - 429: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type SubmitSdkRestoreError = SubmitSdkRestoreErrors[keyof SubmitSdkRestoreErrors]; +export type RetireWebhookSigningSecretError = RetireWebhookSigningSecretErrors[keyof RetireWebhookSigningSecretErrors]; -export type SubmitSdkRestoreResponses = { +export type RetireWebhookSigningSecretResponses = { /** - * The restore was recorded and the chain is running. + * The retired secret's metadata. */ - 202: RestoreResultRecord; + 200: { + data?: WebhookSigningSecretMetadata; + }; }; -export type SubmitSdkRestoreResponse = SubmitSdkRestoreResponses[keyof SubmitSdkRestoreResponses]; +export type RetireWebhookSigningSecretResponse = RetireWebhookSigningSecretResponses[keyof RetireWebhookSigningSecretResponses]; -export type GetSdkRestoreData = { +export type ListWebhookDeliveriesData = { body?: never; - headers: { - 'Mosaic-SDK-Key': string; - }; path: { - restoreId: string; + projectId: string; }; - query?: never; - url: '/v1/sdk/billing/restores/{restoreId}'; + query?: { + environmentId?: string; + eventId?: string; + destinationId?: string; + status?: 'pending' | 'succeeded' | 'failed' | 'exhausted' | 'skipped'; + limit?: number; + }; + url: '/v1/projects/{projectId}/billing/webhook-deliveries'; }; -export type GetSdkRestoreErrors = { +export type ListWebhookDeliveriesErrors = { /** * Stable machine-readable failure. */ @@ -9171,25 +12903,30 @@ export type GetSdkRestoreErrors = { 404: ErrorEnvelope; }; -export type GetSdkRestoreError = GetSdkRestoreErrors[keyof GetSdkRestoreErrors]; +export type ListWebhookDeliveriesError = ListWebhookDeliveriesErrors[keyof ListWebhookDeliveriesErrors]; -export type GetSdkRestoreResponses = { +export type ListWebhookDeliveriesResponses = { /** - * The restore record. + * Deliveries. */ - 200: RestoreResultRecord; + 200: { + data?: Array; + }; }; -export type GetSdkRestoreResponse = GetSdkRestoreResponses[keyof GetSdkRestoreResponses]; +export type ListWebhookDeliveriesResponse = ListWebhookDeliveriesResponses[keyof ListWebhookDeliveriesResponses]; -export type SubmitServerRestoreData = { - body: RestoreRequestRecord; - path?: never; +export type GetWebhookDeliveryData = { + body?: never; + path: { + projectId: string; + deliveryId: string; + }; query?: never; - url: '/v1/billing/server/restores'; + url: '/v1/projects/{projectId}/billing/webhook-deliveries/{deliveryId}'; }; -export type SubmitServerRestoreErrors = { +export type GetWebhookDeliveryErrors = { /** * Stable machine-readable failure. */ @@ -9197,34 +12934,33 @@ export type SubmitServerRestoreErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type SubmitServerRestoreError = SubmitServerRestoreErrors[keyof SubmitServerRestoreErrors]; +export type GetWebhookDeliveryError = GetWebhookDeliveryErrors[keyof GetWebhookDeliveryErrors]; -export type SubmitServerRestoreResponses = { +export type GetWebhookDeliveryResponses = { /** - * The restore was recorded and the chain is running. + * The delivery. */ - 202: RestoreResultRecord; + 200: { + data?: WebhookDelivery; + }; }; -export type SubmitServerRestoreResponse = SubmitServerRestoreResponses[keyof SubmitServerRestoreResponses]; +export type GetWebhookDeliveryResponse = GetWebhookDeliveryResponses[keyof GetWebhookDeliveryResponses]; -export type GetServerRestoreData = { +export type ListWebhookDeliveryAttemptsData = { body?: never; path: { - restoreId: string; + projectId: string; + deliveryId: string; }; query?: never; - url: '/v1/billing/server/restores/{restoreId}'; + url: '/v1/projects/{projectId}/billing/webhook-deliveries/{deliveryId}/attempts'; }; -export type GetServerRestoreErrors = { +export type ListWebhookDeliveryAttemptsErrors = { /** * Stable machine-readable failure. */ @@ -9235,25 +12971,30 @@ export type GetServerRestoreErrors = { 404: ErrorEnvelope; }; -export type GetServerRestoreError = GetServerRestoreErrors[keyof GetServerRestoreErrors]; +export type ListWebhookDeliveryAttemptsError = ListWebhookDeliveryAttemptsErrors[keyof ListWebhookDeliveryAttemptsErrors]; -export type GetServerRestoreResponses = { +export type ListWebhookDeliveryAttemptsResponses = { /** - * The restore record. + * Attempts. */ - 200: RestoreResultRecord; + 200: { + data?: Array; + }; }; -export type GetServerRestoreResponse = GetServerRestoreResponses[keyof GetServerRestoreResponses]; +export type ListWebhookDeliveryAttemptsResponse = ListWebhookDeliveryAttemptsResponses[keyof ListWebhookDeliveryAttemptsResponses]; -export type IdentifyBillingCustomerData = { - body: IdentifyBillingCustomerRequest; - path?: never; +export type ReplayWebhookDeliveryData = { + body?: never; + path: { + projectId: string; + deliveryId: string; + }; query?: never; - url: '/v1/billing/identity/customers'; + url: '/v1/projects/{projectId}/billing/webhook-deliveries/{deliveryId}/replay'; }; -export type IdentifyBillingCustomerErrors = { +export type ReplayWebhookDeliveryErrors = { /** * Stable machine-readable failure. */ @@ -9261,85 +13002,93 @@ export type IdentifyBillingCustomerErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 404: ErrorEnvelope; /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type IdentifyBillingCustomerError = IdentifyBillingCustomerErrors[keyof IdentifyBillingCustomerErrors]; +export type ReplayWebhookDeliveryError = ReplayWebhookDeliveryErrors[keyof ReplayWebhookDeliveryErrors]; -export type IdentifyBillingCustomerResponses = { - /** - * The existing Billing Customer. - */ - 200: { - data?: BillingIdentityCustomer; - }; +export type ReplayWebhookDeliveryResponses = { /** - * A Billing Customer was created. + * The delivery was queued. */ - 201: { - data?: BillingIdentityCustomer; + 202: { + data?: WebhookDelivery; }; }; -export type IdentifyBillingCustomerResponse = IdentifyBillingCustomerResponses[keyof IdentifyBillingCustomerResponses]; +export type ReplayWebhookDeliveryResponse = ReplayWebhookDeliveryResponses[keyof ReplayWebhookDeliveryResponses]; -export type ListBillingCustomerAliasesData = { +export type ListReconciliationRunsData = { body?: never; path: { - customerId: string; + projectId: string; + environmentId: string; }; - query?: never; - url: '/v1/billing/identity/customers/{customerId}/aliases'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not + * construct or parse one — it encodes both the ordering timestamp and the row id, because + * billing identifiers are not time-ordered and an id alone cannot express a position in a + * timestamp ordering. + * + * A malformed or stale value starts from the first page rather than erroring, so a mangled + * cursor cannot silently truncate a list. Omit it for the first page; a response with no + * `nextCursor` is the last page. + * + */ + cursor?: string; + limit?: number; + status?: 'queued' | 'leased' | 'completed' | 'partial' | 'failed'; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/reconciliation-runs'; }; -export type ListBillingCustomerAliasesErrors = { +export type ListReconciliationRunsErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type ListBillingCustomerAliasesError = ListBillingCustomerAliasesErrors[keyof ListBillingCustomerAliasesErrors]; +export type ListReconciliationRunsError = ListReconciliationRunsErrors[keyof ListReconciliationRunsErrors]; -export type ListBillingCustomerAliasesResponses = { +export type ListReconciliationRunsResponses = { /** - * Aliases. + * Reconciliation runs. */ 200: { data?: { - items?: Array; + items?: Array; + nextCursor?: string; }; }; }; -export type ListBillingCustomerAliasesResponse = ListBillingCustomerAliasesResponses[keyof ListBillingCustomerAliasesResponses]; +export type ListReconciliationRunsResponse = ListReconciliationRunsResponses[keyof ListReconciliationRunsResponses]; -export type AttachBillingCustomerAliasData = { - body: AttachBillingCustomerAliasRequest; +export type CreateReconciliationRunData = { + body: CreateReconciliationRunRequest; path: { - customerId: string; + projectId: string; + environmentId: string; }; query?: never; - url: '/v1/billing/identity/customers/{customerId}/aliases'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/reconciliation-runs'; }; -export type AttachBillingCustomerAliasErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type CreateReconciliationRunErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -9350,244 +13099,255 @@ export type AttachBillingCustomerAliasErrors = { 422: ErrorEnvelope; }; -export type AttachBillingCustomerAliasError = AttachBillingCustomerAliasErrors[keyof AttachBillingCustomerAliasErrors]; +export type CreateReconciliationRunError = CreateReconciliationRunErrors[keyof CreateReconciliationRunErrors]; -export type AttachBillingCustomerAliasResponses = { +export type CreateReconciliationRunResponses = { /** - * The attached alias. + * Reconciliation queued. */ - 201: { - data?: BillingCustomerAlias; + 202: { + data?: ReconciliationRun; }; }; -export type AttachBillingCustomerAliasResponse = AttachBillingCustomerAliasResponses[keyof AttachBillingCustomerAliasResponses]; +export type CreateReconciliationRunResponse = CreateReconciliationRunResponses[keyof CreateReconciliationRunResponses]; -export type RevokeBillingCustomerAliasData = { +export type ListReplayJobsData = { body?: never; path: { - aliasId: string; + projectId: string; + environmentId: string; }; - query?: never; - url: '/v1/billing/identity/aliases/{aliasId}/revoke'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not + * construct or parse one — it encodes both the ordering timestamp and the row id, because + * billing identifiers are not time-ordered and an id alone cannot express a position in a + * timestamp ordering. + * + * A malformed or stale value starts from the first page rather than erroring, so a mangled + * cursor cannot silently truncate a list. Omit it for the first page; a response with no + * `nextCursor` is the last page. + * + */ + cursor?: string; + limit?: number; + status?: 'queued' | 'leased' | 'completed' | 'failed'; + }; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/replay-jobs'; }; -export type RevokeBillingCustomerAliasErrors = { +export type ListReplayJobsErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type RevokeBillingCustomerAliasError = RevokeBillingCustomerAliasErrors[keyof RevokeBillingCustomerAliasErrors]; +export type ListReplayJobsError = ListReplayJobsErrors[keyof ListReplayJobsErrors]; -export type RevokeBillingCustomerAliasResponses = { +export type ListReplayJobsResponses = { /** - * The alias was revoked. + * Replay jobs. */ - 204: void; + 200: { + data?: { + items?: Array; + nextCursor?: string; + }; + }; }; -export type RevokeBillingCustomerAliasResponse = RevokeBillingCustomerAliasResponses[keyof RevokeBillingCustomerAliasResponses]; +export type ListReplayJobsResponse = ListReplayJobsResponses[keyof ListReplayJobsResponses]; -export type RequestBillingCustomerSyncData = { - body?: never; +export type CreateReplayJobData = { + body: CreateReplayJobRequest; path: { - customerId: string; + projectId: string; + environmentId: string; }; query?: never; - url: '/v1/billing/identity/customers/{customerId}/sync-requests'; + url: '/v1/projects/{projectId}/environments/{environmentId}/billing/replay-jobs'; }; -export type RequestBillingCustomerSyncErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type CreateReplayJobErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type RequestBillingCustomerSyncError = RequestBillingCustomerSyncErrors[keyof RequestBillingCustomerSyncErrors]; +export type CreateReplayJobError = CreateReplayJobErrors[keyof CreateReplayJobErrors]; -export type RequestBillingCustomerSyncResponses = { +export type CreateReplayJobResponses = { /** - * A projection was queued. + * Replay queued. */ 202: { - data?: BillingSyncRequest; + data?: ReplayJob; }; }; -export type RequestBillingCustomerSyncResponse = RequestBillingCustomerSyncResponses[keyof RequestBillingCustomerSyncResponses]; +export type CreateReplayJobResponse = CreateReplayJobResponses[keyof CreateReplayJobResponses]; -export type ListBillingIdentityConflictsData = { +export type GetQuarantineRecordData = { body?: never; - path?: never; - query?: { - status?: 'open' | 'resolved'; + path: { + projectId: string; + recordId: string; }; - url: '/v1/billing/identity/conflicts'; + query?: never; + url: '/v1/projects/{projectId}/billing/quarantine/{recordId}'; }; -export type ListBillingIdentityConflictsErrors = { +export type GetQuarantineRecordErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 403: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 404: ErrorEnvelope; }; -export type ListBillingIdentityConflictsError = ListBillingIdentityConflictsErrors[keyof ListBillingIdentityConflictsErrors]; +export type GetQuarantineRecordError = GetQuarantineRecordErrors[keyof GetQuarantineRecordErrors]; -export type ListBillingIdentityConflictsResponses = { +export type GetQuarantineRecordResponses = { /** - * Identity conflicts. + * Quarantine Record. */ 200: { - data?: { - items?: Array; - }; + data?: QuarantineRecord; }; }; -export type ListBillingIdentityConflictsResponse = ListBillingIdentityConflictsResponses[keyof ListBillingIdentityConflictsResponses]; +export type GetQuarantineRecordResponse = GetQuarantineRecordResponses[keyof GetQuarantineRecordResponses]; -export type GetBillingIdentityConflictData = { +export type RetryQuarantinedInputData = { body?: never; path: { - conflictId: string; + projectId: string; + recordId: string; }; query?: never; - url: '/v1/billing/identity/conflicts/{conflictId}'; + url: '/v1/projects/{projectId}/billing/quarantine/{recordId}/retry'; }; -export type GetBillingIdentityConflictErrors = { +export type RetryQuarantinedInputErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type GetBillingIdentityConflictError = GetBillingIdentityConflictErrors[keyof GetBillingIdentityConflictErrors]; +export type RetryQuarantinedInputError = RetryQuarantinedInputErrors[keyof RetryQuarantinedInputErrors]; -export type GetBillingIdentityConflictResponses = { +export type RetryQuarantinedInputResponses = { /** - * The conflict. + * Quarantine Record. */ - 200: { - data?: BillingIdentityConflictDetail; + 202: { + data?: QuarantineRecord; }; }; -export type GetBillingIdentityConflictResponse = GetBillingIdentityConflictResponses[keyof GetBillingIdentityConflictResponses]; +export type RetryQuarantinedInputResponse = RetryQuarantinedInputResponses[keyof RetryQuarantinedInputResponses]; -export type GetBillingCustomerData = { - body?: never; +export type CloseQuarantineRecordSupersededData = { + body: { + supersededByRecordId: string; + }; path: { - customerId: string; + projectId: string; + recordId: string; }; query?: never; - url: '/v1/billing/server/customers/{customerId}'; + url: '/v1/projects/{projectId}/billing/quarantine/{recordId}/close-superseded'; }; -export type GetBillingCustomerErrors = { +export type CloseQuarantineRecordSupersededErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; }; -export type GetBillingCustomerError = GetBillingCustomerErrors[keyof GetBillingCustomerErrors]; +export type CloseQuarantineRecordSupersededError = CloseQuarantineRecordSupersededErrors[keyof CloseQuarantineRecordSupersededErrors]; -export type GetBillingCustomerResponses = { +export type CloseQuarantineRecordSupersededResponses = { /** - * The Billing Customer. + * Quarantine Record. */ 200: { - data?: BillingCustomer; + data?: QuarantineRecord; }; }; -export type GetBillingCustomerResponse = GetBillingCustomerResponses[keyof GetBillingCustomerResponses]; +export type CloseQuarantineRecordSupersededResponse = CloseQuarantineRecordSupersededResponses[keyof CloseQuarantineRecordSupersededResponses]; -export type GetCustomerEntitlementSnapshotData = { +export type ListBillingMigrationProgramsData = { body?: never; path: { - customerId: string; + projectId: string; }; query?: { - /** - * Defaults to the Environment the secret key belongs to. Any other value is 403. - */ - environmentId?: string; + limit?: number; }; - url: '/v1/billing/server/customers/{customerId}/entitlements'; + url: '/v1/projects/{projectId}/billing/migration-programs'; }; -export type GetCustomerEntitlementSnapshotErrors = { +export type ListBillingMigrationProgramsErrors = { /** * Stable machine-readable failure. */ 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; }; -export type GetCustomerEntitlementSnapshotError = GetCustomerEntitlementSnapshotErrors[keyof GetCustomerEntitlementSnapshotErrors]; +export type ListBillingMigrationProgramsError = ListBillingMigrationProgramsErrors[keyof ListBillingMigrationProgramsErrors]; -export type GetCustomerEntitlementSnapshotResponses = { +export type ListBillingMigrationProgramsResponses = { /** - * The current snapshot. + * Migration programs. */ - 200: CustomerEntitlementSnapshotRecord; + 200: BillingMigrationProgramListEnvelope; }; -export type GetCustomerEntitlementSnapshotResponse = GetCustomerEntitlementSnapshotResponses[keyof GetCustomerEntitlementSnapshotResponses]; +export type ListBillingMigrationProgramsResponse = ListBillingMigrationProgramsResponses[keyof ListBillingMigrationProgramsResponses]; -export type CheckCustomerEntitlementsData = { - body: EntitlementCheckRequestRecord; - path: { - customerId: string; +export type CreateBillingMigrationProgramData = { + body: CreateBillingMigrationProgramRequestWritable; + headers: { + 'Idempotency-Key': string; }; - query?: { - environmentId?: string; + path: { + projectId: string; }; - url: '/v1/billing/server/customers/{customerId}/entitlement-checks'; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs'; }; -export type CheckCustomerEntitlementsErrors = { +export type CreateBillingMigrationProgramErrors = { /** * Stable machine-readable failure. */ @@ -9603,41 +13363,43 @@ export type CheckCustomerEntitlementsErrors = { /** * Stable machine-readable failure. */ - 406: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type CheckCustomerEntitlementsError = CheckCustomerEntitlementsErrors[keyof CheckCustomerEntitlementsErrors]; +export type CreateBillingMigrationProgramError = CreateBillingMigrationProgramErrors[keyof CreateBillingMigrationProgramErrors]; -export type CheckCustomerEntitlementsResponses = { +export type CreateBillingMigrationProgramResponses = { /** - * The check result. + * Original program returned for an identical idempotent replay. */ - 200: EntitlementCheckResultRecord; + 200: BillingMigrationProgramEnvelope; + /** + * Migration program created. + */ + 201: BillingMigrationProgramEnvelope; }; -export type CheckCustomerEntitlementsResponse = CheckCustomerEntitlementsResponses[keyof CheckCustomerEntitlementsResponses]; +export type CreateBillingMigrationProgramResponse = CreateBillingMigrationProgramResponses[keyof CreateBillingMigrationProgramResponses]; -export type ListCustomerSubscriptionsData = { +export type GetBillingMigrationProgramData = { body?: never; path: { - customerId: string; - }; - query?: { - environmentId?: string; - limit?: number; - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; + projectId: string; + programId: string; }; - url: '/v1/billing/server/customers/{customerId}/subscriptions'; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}'; }; -export type ListCustomerSubscriptionsErrors = { +export type GetBillingMigrationProgramErrors = { /** * Stable machine-readable failure. */ @@ -9645,39 +13407,33 @@ export type ListCustomerSubscriptionsErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type ListCustomerSubscriptionsError = ListCustomerSubscriptionsErrors[keyof ListCustomerSubscriptionsErrors]; +export type GetBillingMigrationProgramError = GetBillingMigrationProgramErrors[keyof GetBillingMigrationProgramErrors]; -export type ListCustomerSubscriptionsResponses = { +export type GetBillingMigrationProgramResponses = { /** - * Projected subscriptions. + * Migration program. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationProgramEnvelope; }; -export type ListCustomerSubscriptionsResponse = ListCustomerSubscriptionsResponses[keyof ListCustomerSubscriptionsResponses]; +export type GetBillingMigrationProgramResponse = GetBillingMigrationProgramResponses[keyof GetBillingMigrationProgramResponses]; -export type GetSubscriptionSnapshotData = { +export type ListBillingMigrationSourceManifestsData = { body?: never; path: { - instanceId: string; + projectId: string; + programId: string; }; - query?: never; - url: '/v1/billing/server/subscriptions/{instanceId}'; + query?: { + limit?: number; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/manifests'; }; -export type GetSubscriptionSnapshotErrors = { +export type ListBillingMigrationSourceManifestsErrors = { /** * Stable machine-readable failure. */ @@ -9686,39 +13442,32 @@ export type GetSubscriptionSnapshotErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; }; -export type GetSubscriptionSnapshotError = GetSubscriptionSnapshotErrors[keyof GetSubscriptionSnapshotErrors]; +export type ListBillingMigrationSourceManifestsError = ListBillingMigrationSourceManifestsErrors[keyof ListBillingMigrationSourceManifestsErrors]; -export type GetSubscriptionSnapshotResponses = { +export type ListBillingMigrationSourceManifestsResponses = { /** - * The subscription snapshot. + * Strict Billing Migration Operations v1 sourceManifest records. */ - 200: SubscriptionSnapshotRecord; + 200: BillingMigrationSourceManifestListEnvelope; }; -export type GetSubscriptionSnapshotResponse = GetSubscriptionSnapshotResponses[keyof GetSubscriptionSnapshotResponses]; +export type ListBillingMigrationSourceManifestsResponse = ListBillingMigrationSourceManifestsResponses[keyof ListBillingMigrationSourceManifestsResponses]; -export type ListSubscriptionTimelineData = { +export type ListBillingMigrationMappingSetsData = { body?: never; path: { - instanceId: string; + projectId: string; + programId: string; }; query?: { limit?: number; - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; }; - url: '/v1/billing/server/subscriptions/{instanceId}/timeline'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/mapping-sets'; }; -export type ListSubscriptionTimelineErrors = { +export type ListBillingMigrationMappingSetsErrors = { /** * Stable machine-readable failure. */ @@ -9727,38 +13476,30 @@ export type ListSubscriptionTimelineErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; }; -export type ListSubscriptionTimelineError = ListSubscriptionTimelineErrors[keyof ListSubscriptionTimelineErrors]; +export type ListBillingMigrationMappingSetsError = ListBillingMigrationMappingSetsErrors[keyof ListBillingMigrationMappingSetsErrors]; -export type ListSubscriptionTimelineResponses = { +export type ListBillingMigrationMappingSetsResponses = { /** - * Timeline entries. + * Versioned mapping sets. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationMappingSetListEnvelope; }; -export type ListSubscriptionTimelineResponse = ListSubscriptionTimelineResponses[keyof ListSubscriptionTimelineResponses]; +export type ListBillingMigrationMappingSetsResponse = ListBillingMigrationMappingSetsResponses[keyof ListBillingMigrationMappingSetsResponses]; -export type GetBillingSettingsData = { - body?: never; +export type CreateBillingMigrationMappingSetData = { + body: CreateBillingMigrationMappingSetRequest; path: { projectId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/settings'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/mapping-sets'; }; -export type GetBillingSettingsErrors = { +export type CreateBillingMigrationMappingSetErrors = { /** * Stable machine-readable failure. */ @@ -9766,104 +13507,99 @@ export type GetBillingSettingsErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; }; -export type GetBillingSettingsError = GetBillingSettingsErrors[keyof GetBillingSettingsErrors]; +export type CreateBillingMigrationMappingSetError = CreateBillingMigrationMappingSetErrors[keyof CreateBillingMigrationMappingSetErrors]; -export type GetBillingSettingsResponses = { +export type CreateBillingMigrationMappingSetResponses = { /** - * Billing settings. + * Draft mapping set created. */ - 200: { - data?: BillingSettings; - }; + 201: BillingMigrationMappingSetEnvelope; }; -export type GetBillingSettingsResponse = GetBillingSettingsResponses[keyof GetBillingSettingsResponses]; +export type CreateBillingMigrationMappingSetResponse = CreateBillingMigrationMappingSetResponses[keyof CreateBillingMigrationMappingSetResponses]; -export type UpdateBillingSettingsData = { - body: { - billingEnabled: boolean; - }; +export type FreezeBillingMigrationMappingSetData = { + body: BillingMigrationStateVersionRequest; path: { projectId: string; + programId: string; + mappingSetId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/settings'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/mapping-sets/{mappingSetId}/freeze'; }; -export type UpdateBillingSettingsErrors = { +export type FreezeBillingMigrationMappingSetErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ 409: ErrorEnvelope; }; -export type UpdateBillingSettingsError = UpdateBillingSettingsErrors[keyof UpdateBillingSettingsErrors]; +export type FreezeBillingMigrationMappingSetError = FreezeBillingMigrationMappingSetErrors[keyof FreezeBillingMigrationMappingSetErrors]; -export type UpdateBillingSettingsResponses = { +export type FreezeBillingMigrationMappingSetResponses = { /** - * Updated settings. + * Mapping set frozen. */ - 200: { - data?: { - billingEnabled?: boolean; - }; - }; + 200: unknown; }; -export type UpdateBillingSettingsResponse = UpdateBillingSettingsResponses[keyof UpdateBillingSettingsResponses]; - -export type ListStoreServerCredentialsData = { +export type ListBillingMigrationImportBatchesData = { body?: never; path: { projectId: string; + programId: string; }; - query?: never; - url: '/v1/projects/{projectId}/billing/store-credentials'; + query?: { + limit?: number; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/import-batches'; }; -export type ListStoreServerCredentialsErrors = { +export type ListBillingMigrationImportBatchesErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 401: ErrorEnvelope; }; -export type ListStoreServerCredentialsError = ListStoreServerCredentialsErrors[keyof ListStoreServerCredentialsErrors]; +export type ListBillingMigrationImportBatchesError = ListBillingMigrationImportBatchesErrors[keyof ListBillingMigrationImportBatchesErrors]; -export type ListStoreServerCredentialsResponses = { +export type ListBillingMigrationImportBatchesResponses = { /** - * Store Server Credentials. + * Bounded import batches. */ - 200: { - data?: { - items?: Array; - }; - }; + 200: BillingMigrationImportBatchListEnvelope; }; -export type ListStoreServerCredentialsResponse = ListStoreServerCredentialsResponses[keyof ListStoreServerCredentialsResponses]; +export type ListBillingMigrationImportBatchesResponse = ListBillingMigrationImportBatchesResponses[keyof ListBillingMigrationImportBatchesResponses]; -export type CreateStoreServerCredentialData = { - body: CreateStoreServerCredentialRequest; +export type CreateBillingMigrationImportBatchData = { + body: CreateBillingMigrationImportBatchRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/store-credentials'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/import-batches'; }; -export type CreateStoreServerCredentialErrors = { +export type CreateBillingMigrationImportBatchErrors = { /** * Stable machine-readable failure. */ @@ -9878,196 +13614,190 @@ export type CreateStoreServerCredentialErrors = { 422: ErrorEnvelope; }; -export type CreateStoreServerCredentialError = CreateStoreServerCredentialErrors[keyof CreateStoreServerCredentialErrors]; +export type CreateBillingMigrationImportBatchError = CreateBillingMigrationImportBatchErrors[keyof CreateBillingMigrationImportBatchErrors]; -export type CreateStoreServerCredentialResponses = { +export type CreateBillingMigrationImportBatchResponses = { /** - * Store Server Credential including the one-time notification endpoint URL. This is the only response that ever carries it. + * Original batch returned for an identical replay. */ - 201: { - data?: StoreServerCredentialWithEndpoint; - }; + 200: BillingMigrationImportBatchEnvelope; + /** + * Import batch queued. + */ + 202: BillingMigrationImportBatchEnvelope; }; -export type CreateStoreServerCredentialResponse = CreateStoreServerCredentialResponses[keyof CreateStoreServerCredentialResponses]; +export type CreateBillingMigrationImportBatchResponse = CreateBillingMigrationImportBatchResponses[keyof CreateBillingMigrationImportBatchResponses]; -export type GetStoreServerCredentialData = { +export type GetBillingMigrationImportBatchData = { body?: never; path: { projectId: string; - credentialId: string; + programId: string; + batchId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/import-batches/{batchId}'; }; -export type GetStoreServerCredentialErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetBillingMigrationImportBatchErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type GetStoreServerCredentialError = GetStoreServerCredentialErrors[keyof GetStoreServerCredentialErrors]; +export type GetBillingMigrationImportBatchError = GetBillingMigrationImportBatchErrors[keyof GetBillingMigrationImportBatchErrors]; -export type GetStoreServerCredentialResponses = { +export type GetBillingMigrationImportBatchResponses = { /** - * Store Server Credential without secret material or endpoint URL. + * Import batch. */ - 200: { - data?: StoreServerCredential; - }; + 200: BillingMigrationImportBatchEnvelope; }; -export type GetStoreServerCredentialResponse = GetStoreServerCredentialResponses[keyof GetStoreServerCredentialResponses]; +export type GetBillingMigrationImportBatchResponse = GetBillingMigrationImportBatchResponses[keyof GetBillingMigrationImportBatchResponses]; -export type RotateStoreServerCredentialData = { - body: { - /** - * Write-only. Never returned. - */ - secret: string; +export type QueueBillingMigrationDryRunData = { + body: QueueBillingMigrationRunRequest; + headers: { + 'Idempotency-Key': string; }; path: { projectId: string; - credentialId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}/rotate'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/dry-runs'; }; -export type RotateStoreServerCredentialErrors = { +export type QueueBillingMigrationDryRunErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 409: ErrorEnvelope; +}; + +export type QueueBillingMigrationDryRunError = QueueBillingMigrationDryRunErrors[keyof QueueBillingMigrationDryRunErrors]; + +export type QueueBillingMigrationDryRunResponses = { /** - * Stable machine-readable failure. + * Original job returned for an identical replay. */ - 404: ErrorEnvelope; + 200: BillingMigrationRunJobEnvelope; + /** + * Dry-run job queued. + */ + 202: BillingMigrationRunJobEnvelope; +}; + +export type QueueBillingMigrationDryRunResponse = QueueBillingMigrationDryRunResponses[keyof QueueBillingMigrationDryRunResponses]; + +export type QueueBillingMigrationShadowRunData = { + body: QueueBillingMigrationRunRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/shadow-runs'; +}; + +export type QueueBillingMigrationShadowRunErrors = { /** * Stable machine-readable failure. */ 409: ErrorEnvelope; }; -export type RotateStoreServerCredentialError = RotateStoreServerCredentialErrors[keyof RotateStoreServerCredentialErrors]; +export type QueueBillingMigrationShadowRunError = QueueBillingMigrationShadowRunErrors[keyof QueueBillingMigrationShadowRunErrors]; -export type RotateStoreServerCredentialResponses = { +export type QueueBillingMigrationShadowRunResponses = { /** - * Store Server Credential including the one-time notification endpoint URL. This is the only response that ever carries it. + * Original job returned for an identical replay. */ - 200: { - data?: StoreServerCredentialWithEndpoint; - }; + 200: BillingMigrationRunJobEnvelope; + /** + * Shadow-run job queued. + */ + 202: BillingMigrationRunJobEnvelope; }; -export type RotateStoreServerCredentialResponse = RotateStoreServerCredentialResponses[keyof RotateStoreServerCredentialResponses]; +export type QueueBillingMigrationShadowRunResponse = QueueBillingMigrationShadowRunResponses[keyof QueueBillingMigrationShadowRunResponses]; -export type RevokeStoreServerCredentialData = { +export type GetBillingMigrationRunJobData = { body?: never; path: { projectId: string; - credentialId: string; + programId: string; + runJobId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}/revoke'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/runs/{runJobId}'; }; -export type RevokeStoreServerCredentialErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetBillingMigrationRunJobErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type RevokeStoreServerCredentialError = RevokeStoreServerCredentialErrors[keyof RevokeStoreServerCredentialErrors]; +export type GetBillingMigrationRunJobError = GetBillingMigrationRunJobErrors[keyof GetBillingMigrationRunJobErrors]; -export type RevokeStoreServerCredentialResponses = { +export type GetBillingMigrationRunJobResponses = { /** - * Store Server Credential without secret material or endpoint URL. + * Durable run-job status. */ - 200: { - data?: StoreServerCredential; - }; + 200: BillingMigrationRunJobEnvelope; }; -export type RevokeStoreServerCredentialResponse = RevokeStoreServerCredentialResponses[keyof RevokeStoreServerCredentialResponses]; +export type GetBillingMigrationRunJobResponse = GetBillingMigrationRunJobResponses[keyof GetBillingMigrationRunJobResponses]; -export type TestStoreServerCredentialData = { +export type ListBillingMigrationDivergencesData = { body?: never; path: { projectId: string; - credentialId: string; + programId: string; }; - query?: never; - url: '/v1/projects/{projectId}/billing/store-credentials/{credentialId}/test'; + query?: { + limit?: number; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/divergences'; }; -export type TestStoreServerCredentialErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type ListBillingMigrationDivergencesErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 429: ErrorEnvelope; }; -export type TestStoreServerCredentialError = TestStoreServerCredentialErrors[keyof TestStoreServerCredentialErrors]; +export type ListBillingMigrationDivergencesError = ListBillingMigrationDivergencesErrors[keyof ListBillingMigrationDivergencesErrors]; -export type TestStoreServerCredentialResponses = { +export type ListBillingMigrationDivergencesResponses = { /** - * Store Server Credential without secret material or endpoint URL. + * Immutable divergence records. */ - 200: { - data?: StoreServerCredential; - }; + 200: BillingMigrationDivergenceListEnvelope; }; -export type TestStoreServerCredentialResponse = TestStoreServerCredentialResponses[keyof TestStoreServerCredentialResponses]; +export type ListBillingMigrationDivergencesResponse = ListBillingMigrationDivergencesResponses[keyof ListBillingMigrationDivergencesResponses]; -export type ListTransactionFactsData = { - body?: never; +export type AssessBillingMigrationReadinessData = { + body: BillingMigrationStateVersionRequest; path: { projectId: string; - environmentId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not - * construct or parse one — it encodes both the ordering timestamp and the row id, because - * billing identifiers are not time-ordered and an id alone cannot express a position in a - * timestamp ordering. - * - * A malformed or stale value starts from the first page rather than erroring, so a mangled - * cursor cannot silently truncate a list. Omit it for the first page; a response with no - * `nextCursor` is the last page. - * - */ - cursor?: string; - limit?: number; - provider?: 'app_store' | 'google_play'; - from?: string; - to?: string; + programId: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/facts'; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/readiness-assessments'; }; -export type ListTransactionFactsErrors = { +export type AssessBillingMigrationReadinessErrors = { /** * Stable machine-readable failure. */ @@ -10075,162 +13805,96 @@ export type ListTransactionFactsErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 409: ErrorEnvelope; }; -export type ListTransactionFactsError = ListTransactionFactsErrors[keyof ListTransactionFactsErrors]; +export type AssessBillingMigrationReadinessError = AssessBillingMigrationReadinessErrors[keyof AssessBillingMigrationReadinessErrors]; -export type ListTransactionFactsResponses = { +export type AssessBillingMigrationReadinessResponses = { /** - * Transaction Facts. + * Immutable readiness assessment. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 201: BillingMigrationReadinessEnvelope; }; -export type ListTransactionFactsResponse = ListTransactionFactsResponses[keyof ListTransactionFactsResponses]; +export type AssessBillingMigrationReadinessResponse = AssessBillingMigrationReadinessResponses[keyof AssessBillingMigrationReadinessResponses]; -export type ListValidationAttemptsData = { +export type GetLatestBillingMigrationReadinessData = { body?: never; path: { projectId: string; - environmentId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not - * construct or parse one — it encodes both the ordering timestamp and the row id, because - * billing identifiers are not time-ordered and an id alone cannot express a position in a - * timestamp ordering. - * - * A malformed or stale value starts from the first page rather than erroring, so a mangled - * cursor cannot silently truncate a list. Omit it for the first page; a response with no - * `nextCursor` is the last page. - * - */ - cursor?: string; - limit?: number; - status?: 'validated' | 'recorded_no_fact' | 'quarantined' | 'retryable_failure' | 'permanently_failed'; - /** - * Narrow the list to one input's attempt history. - */ - rawInputId?: string; + programId: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/validation-attempts'; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/readiness-assessments/latest'; }; - -export type ListValidationAttemptsErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; + +export type GetLatestBillingMigrationReadinessErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type ListValidationAttemptsError = ListValidationAttemptsErrors[keyof ListValidationAttemptsErrors]; +export type GetLatestBillingMigrationReadinessError = GetLatestBillingMigrationReadinessErrors[keyof GetLatestBillingMigrationReadinessErrors]; -export type ListValidationAttemptsResponses = { +export type GetLatestBillingMigrationReadinessResponses = { /** - * Validation Attempts. + * Latest immutable readiness assessment. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationReadinessEnvelope; }; -export type ListValidationAttemptsResponse = ListValidationAttemptsResponses[keyof ListValidationAttemptsResponses]; +export type GetLatestBillingMigrationReadinessResponse = GetLatestBillingMigrationReadinessResponses[keyof GetLatestBillingMigrationReadinessResponses]; -export type ListBillingLedgerData = { +export type ListBillingMigrationSourcePullsData = { body?: never; path: { projectId: string; - environmentId: string; + programId: string; }; query?: { /** - * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not - * construct or parse one — it encodes both the ordering timestamp and the row id, because - * billing identifiers are not time-ordered and an id alone cannot express a position in a - * timestamp ordering. - * - * A malformed or stale value starts from the first page rather than erroring, so a mangled - * cursor cannot silently truncate a list. Omit it for the first page; a response with no - * `nextCursor` is the last page. - * + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ cursor?: string; limit?: number; - from?: string; - to?: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/ledger'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/source-pulls'; }; -export type ListBillingLedgerErrors = { +export type ListBillingMigrationSourcePullsErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; }; -export type ListBillingLedgerError = ListBillingLedgerErrors[keyof ListBillingLedgerErrors]; +export type ListBillingMigrationSourcePullsError = ListBillingMigrationSourcePullsErrors[keyof ListBillingMigrationSourcePullsErrors]; -export type ListBillingLedgerResponses = { +export type ListBillingMigrationSourcePullsResponses = { /** - * Billing Ledger Entries. + * Source-pull jobs. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationSourcePullPageEnvelope; }; -export type ListBillingLedgerResponse = ListBillingLedgerResponses[keyof ListBillingLedgerResponses]; +export type ListBillingMigrationSourcePullsResponse = ListBillingMigrationSourcePullsResponses[keyof ListBillingMigrationSourcePullsResponses]; -export type ListBillingQuarantineData = { - body?: never; +export type QueueBillingMigrationSourcePullData = { + body: BillingMigrationSourcePullRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - environmentId: string; - }; - query?: { - /** - * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not - * construct or parse one — it encodes both the ordering timestamp and the row id, because - * billing identifiers are not time-ordered and an id alone cannot express a position in a - * timestamp ordering. - * - * A malformed or stale value starts from the first page rather than erroring, so a mangled - * cursor cannot silently truncate a list. Omit it for the first page; a response with no - * `nextCursor` is the last page. - * - */ - cursor?: string; - limit?: number; - status?: 'open' | 'retrying' | 'closed_after_success' | 'closed_superseded'; - reasonCode?: string; - provider?: 'app_store' | 'google_play'; + programId: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/quarantine'; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/source-pulls'; }; -export type ListBillingQuarantineErrors = { +export type QueueBillingMigrationSourcePullErrors = { /** * Stable machine-readable failure. */ @@ -10238,36 +13902,44 @@ export type ListBillingQuarantineErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 409: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 422: ErrorEnvelope; + /** + * Stable machine-readable failure. + */ + 503: ErrorEnvelope; }; -export type ListBillingQuarantineError = ListBillingQuarantineErrors[keyof ListBillingQuarantineErrors]; +export type QueueBillingMigrationSourcePullError = QueueBillingMigrationSourcePullErrors[keyof QueueBillingMigrationSourcePullErrors]; -export type ListBillingQuarantineResponses = { +export type QueueBillingMigrationSourcePullResponses = { /** - * Quarantine Records. + * Original source-pull job returned for an identical replay. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 202: BillingMigrationOperationEnvelope; }; -export type ListBillingQuarantineResponse = ListBillingQuarantineResponses[keyof ListBillingQuarantineResponses]; +export type QueueBillingMigrationSourcePullResponse = QueueBillingMigrationSourcePullResponses[keyof QueueBillingMigrationSourcePullResponses]; -export type GetBillingHealthData = { +export type GetBillingMigrationSourcePullData = { body?: never; path: { projectId: string; - environmentId: string; + programId: string; + sourcePullId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/health'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/source-pulls/{sourcePullId}'; }; -export type GetBillingHealthErrors = { +export type GetBillingMigrationSourcePullErrors = { /** * Stable machine-readable failure. */ @@ -10278,34 +13950,28 @@ export type GetBillingHealthErrors = { 404: ErrorEnvelope; }; -export type GetBillingHealthError = GetBillingHealthErrors[keyof GetBillingHealthErrors]; +export type GetBillingMigrationSourcePullError = GetBillingMigrationSourcePullErrors[keyof GetBillingMigrationSourcePullErrors]; -export type GetBillingHealthResponses = { +export type GetBillingMigrationSourcePullResponses = { /** - * Billing health summary. + * Source-pull job. */ - 200: { - data?: BillingHealth; - }; + 200: BillingMigrationSourcePullRecord; }; -export type GetBillingHealthResponse = GetBillingHealthResponses[keyof GetBillingHealthResponses]; +export type GetBillingMigrationSourcePullResponse = GetBillingMigrationSourcePullResponses[keyof GetBillingMigrationSourcePullResponses]; -export type GetBillingProjectionHealthData = { - body?: never; +export type PromoteBillingMigrationReadyData = { + body: BillingMigrationStateVersionRequest; path: { projectId: string; - environmentId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/projection-health'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/promote-ready'; }; -export type GetBillingProjectionHealthErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type PromoteBillingMigrationReadyErrors = { /** * Stable machine-readable failure. */ @@ -10313,49 +13979,85 @@ export type GetBillingProjectionHealthErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetBillingProjectionHealthError = GetBillingProjectionHealthErrors[keyof GetBillingProjectionHealthErrors]; +export type PromoteBillingMigrationReadyError = PromoteBillingMigrationReadyErrors[keyof PromoteBillingMigrationReadyErrors]; -export type GetBillingProjectionHealthResponses = { +export type PromoteBillingMigrationReadyResponses = { /** - * Projection health summary. + * Billing migration operation result. */ - 200: { - data?: BillingProjectionHealth; - }; + 200: BillingMigrationOperationEnvelope; }; -export type GetBillingProjectionHealthResponse = GetBillingProjectionHealthResponses[keyof GetBillingProjectionHealthResponses]; +export type PromoteBillingMigrationReadyResponse = PromoteBillingMigrationReadyResponses[keyof PromoteBillingMigrationReadyResponses]; -export type CreateBillingProjectionReplayData = { - body: CreateProjectionReplayRequest; +export type ProposeBillingMigrationCutoverData = { + body: BillingMigrationCutoverProposalRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - environmentId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/projection-replays'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cutover-proposals'; }; -export type CreateBillingProjectionReplayErrors = { +export type ProposeBillingMigrationCutoverErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 422: ErrorEnvelope; +}; + +export type ProposeBillingMigrationCutoverError = ProposeBillingMigrationCutoverErrors[keyof ProposeBillingMigrationCutoverErrors]; + +export type ProposeBillingMigrationCutoverResponses = { + /** + * Original result returned for an identical idempotent replay. + */ + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; +}; + +export type ProposeBillingMigrationCutoverResponse = ProposeBillingMigrationCutoverResponses[keyof ProposeBillingMigrationCutoverResponses]; + +export type ProposeBillingMigrationRollbackData = { + body: BillingMigrationRollbackProposalRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-proposals'; +}; + +export type ProposeBillingMigrationRollbackErrors = { + /** + * Stable machine-readable failure. + */ + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10366,106 +14068,107 @@ export type CreateBillingProjectionReplayErrors = { 422: ErrorEnvelope; }; -export type CreateBillingProjectionReplayError = CreateBillingProjectionReplayErrors[keyof CreateBillingProjectionReplayErrors]; +export type ProposeBillingMigrationRollbackError = ProposeBillingMigrationRollbackErrors[keyof ProposeBillingMigrationRollbackErrors]; -export type CreateBillingProjectionReplayResponses = { +export type ProposeBillingMigrationRollbackResponses = { /** - * The replay ran. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: ProjectionReplayResult; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type CreateBillingProjectionReplayResponse = CreateBillingProjectionReplayResponses[keyof CreateBillingProjectionReplayResponses]; +export type ProposeBillingMigrationRollbackResponse = ProposeBillingMigrationRollbackResponses[keyof ProposeBillingMigrationRollbackResponses]; -export type ListBillingCustomersData = { - body?: never; +export type ApproveBillingMigrationProposalData = { + body: BillingMigrationStateVersionRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - environmentId: string; - }; - query?: { - status?: 'active' | 'frozen' | 'anonymized' | 'absorbed'; - /** - * Restrict to identified or to purchase-anchored-only customers. - */ - identified?: boolean; - /** - * Only customers party to an open identity conflict. - */ - conflictedOnly?: boolean; - limit?: number; - /** - * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. - */ - cursor?: string; + programId: string; + proposalId: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers'; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/proposals/{proposalId}/approvals'; }; -export type ListBillingCustomersErrors = { +export type ApproveBillingMigrationProposalErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 422: ErrorEnvelope; +}; + +export type ApproveBillingMigrationProposalError = ApproveBillingMigrationProposalErrors[keyof ApproveBillingMigrationProposalErrors]; + +export type ApproveBillingMigrationProposalResponses = { /** - * Stable machine-readable failure. + * Original result returned for an identical idempotent replay. */ - 409: ErrorEnvelope; + 200: BillingMigrationOperationEnvelope; /** - * Stable machine-readable failure. + * Billing migration operation result. */ - 503: ErrorEnvelope; + 201: BillingMigrationOperationEnvelope; }; -export type ListBillingCustomersError = ListBillingCustomersErrors[keyof ListBillingCustomersErrors]; +export type ApproveBillingMigrationProposalResponse = ApproveBillingMigrationProposalResponses[keyof ApproveBillingMigrationProposalResponses]; -export type ListBillingCustomersResponses = { +export type ListBillingMigrationCheckpointsData = { + body?: never; + path: { + projectId: string; + programId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints'; +}; + +export type ListBillingMigrationCheckpointsResponses = { /** - * Billing Customers. + * Authority checkpoints. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationCheckpointPageEnvelope; }; -export type ListBillingCustomersResponse = ListBillingCustomersResponses[keyof ListBillingCustomersResponses]; +export type ListBillingMigrationCheckpointsResponse = ListBillingMigrationCheckpointsResponses[keyof ListBillingMigrationCheckpointsResponses]; -export type LookupBillingCustomerData = { - body: BillingCustomerLookupRequest; +export type CreateBillingMigrationCheckpointData = { + body: BillingMigrationCheckpointRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - environmentId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customer-lookups'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints'; }; -export type LookupBillingCustomerErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type CreateBillingMigrationCheckpointErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10474,49 +14177,41 @@ export type LookupBillingCustomerErrors = { * Stable machine-readable failure. */ 422: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 429: ErrorEnvelope; }; -export type LookupBillingCustomerError = LookupBillingCustomerErrors[keyof LookupBillingCustomerErrors]; +export type CreateBillingMigrationCheckpointError = CreateBillingMigrationCheckpointErrors[keyof CreateBillingMigrationCheckpointErrors]; -export type LookupBillingCustomerResponses = { +export type CreateBillingMigrationCheckpointResponses = { /** - * The lookup result. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: BillingCustomerLookupResult; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type LookupBillingCustomerResponse = LookupBillingCustomerResponses[keyof LookupBillingCustomerResponses]; +export type CreateBillingMigrationCheckpointResponse = CreateBillingMigrationCheckpointResponses[keyof CreateBillingMigrationCheckpointResponses]; -export type GetOperatorBillingCustomerData = { - body?: never; +export type ExecuteBillingMigrationCutoverData = { + body: BillingMigrationCutoverExecutionRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - environmentId: string; - customerId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cutover-executions'; }; -export type GetOperatorBillingCustomerErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type ExecuteBillingMigrationCutoverErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10524,46 +14219,42 @@ export type GetOperatorBillingCustomerErrors = { /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetOperatorBillingCustomerError = GetOperatorBillingCustomerErrors[keyof GetOperatorBillingCustomerErrors]; +export type ExecuteBillingMigrationCutoverError = ExecuteBillingMigrationCutoverErrors[keyof ExecuteBillingMigrationCutoverErrors]; -export type GetOperatorBillingCustomerResponses = { +export type ExecuteBillingMigrationCutoverResponses = { /** - * The Billing Customer. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: BillingCustomerDetail; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type GetOperatorBillingCustomerResponse = GetOperatorBillingCustomerResponses[keyof GetOperatorBillingCustomerResponses]; +export type ExecuteBillingMigrationCutoverResponse = ExecuteBillingMigrationCutoverResponses[keyof ExecuteBillingMigrationCutoverResponses]; -export type GetBillingCustomerEntitlementSnapshotData = { - body?: never; +export type ExecuteBillingMigrationRollbackData = { + body: BillingMigrationRollbackExecutionRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - environmentId: string; - customerId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}/entitlements'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-executions'; }; -export type GetBillingCustomerEntitlementSnapshotErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type ExecuteBillingMigrationRollbackErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10571,55 +14262,66 @@ export type GetBillingCustomerEntitlementSnapshotErrors = { /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetBillingCustomerEntitlementSnapshotError = GetBillingCustomerEntitlementSnapshotErrors[keyof GetBillingCustomerEntitlementSnapshotErrors]; +export type ExecuteBillingMigrationRollbackError = ExecuteBillingMigrationRollbackErrors[keyof ExecuteBillingMigrationRollbackErrors]; -export type GetBillingCustomerEntitlementSnapshotResponses = { +export type ExecuteBillingMigrationRollbackResponses = { /** - * The current snapshot. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: { - snapshot?: BillingEntitlementSnapshot; - projectionStatus?: BillingProjectionStatus; - }; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type GetBillingCustomerEntitlementSnapshotResponse = GetBillingCustomerEntitlementSnapshotResponses[keyof GetBillingCustomerEntitlementSnapshotResponses]; +export type ExecuteBillingMigrationRollbackResponse = ExecuteBillingMigrationRollbackResponses[keyof ExecuteBillingMigrationRollbackResponses]; -export type ListBillingCustomerSubscriptionsData = { +export type ListBillingMigrationCasesData = { body?: never; path: { projectId: string; - environmentId: string; - customerId: string; + programId: string; }; query?: { - limit?: number; /** * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ cursor?: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}/subscriptions'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases'; }; -export type ListBillingCustomerSubscriptionsErrors = { +export type ListBillingMigrationCasesResponses = { /** - * Stable machine-readable failure. + * Migration cases. */ - 401: ErrorEnvelope; + 200: BillingMigrationCasePageEnvelope; +}; + +export type ListBillingMigrationCasesResponse = ListBillingMigrationCasesResponses[keyof ListBillingMigrationCasesResponses]; + +export type CreateBillingMigrationCaseData = { + body: BillingMigrationCaseRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases'; +}; + +export type CreateBillingMigrationCaseErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10627,49 +14329,40 @@ export type ListBillingCustomerSubscriptionsErrors = { /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type ListBillingCustomerSubscriptionsError = ListBillingCustomerSubscriptionsErrors[keyof ListBillingCustomerSubscriptionsErrors]; +export type CreateBillingMigrationCaseError = CreateBillingMigrationCaseErrors[keyof CreateBillingMigrationCaseErrors]; -export type ListBillingCustomerSubscriptionsResponses = { +export type CreateBillingMigrationCaseResponses = { /** - * Subscriptions. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type ListBillingCustomerSubscriptionsResponse = ListBillingCustomerSubscriptionsResponses[keyof ListBillingCustomerSubscriptionsResponses]; +export type CreateBillingMigrationCaseResponse = CreateBillingMigrationCaseResponses[keyof CreateBillingMigrationCaseResponses]; -export type CreateBillingCustomerSyncRequestData = { - body?: never; +export type TransitionBillingMigrationCaseData = { + body: BillingMigrationCaseTransitionRequest; path: { projectId: string; - environmentId: string; - customerId: string; + programId: string; + caseId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/customers/{customerId}/sync-requests'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases/{caseId}/transitions'; }; -export type CreateBillingCustomerSyncRequestErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type TransitionBillingMigrationCaseErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10677,151 +14370,188 @@ export type CreateBillingCustomerSyncRequestErrors = { /** * Stable machine-readable failure. */ - 429: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type CreateBillingCustomerSyncRequestError = CreateBillingCustomerSyncRequestErrors[keyof CreateBillingCustomerSyncRequestErrors]; +export type TransitionBillingMigrationCaseError = TransitionBillingMigrationCaseErrors[keyof TransitionBillingMigrationCaseErrors]; -export type CreateBillingCustomerSyncRequestResponses = { +export type TransitionBillingMigrationCaseResponses = { /** - * The projection was queued. + * Billing migration operation result. */ - 202: { - data?: OperatorBillingSyncRequest; - }; + 200: BillingMigrationOperationEnvelope; }; -export type CreateBillingCustomerSyncRequestResponse = CreateBillingCustomerSyncRequestResponses[keyof CreateBillingCustomerSyncRequestResponses]; +export type TransitionBillingMigrationCaseResponse = TransitionBillingMigrationCaseResponses[keyof TransitionBillingMigrationCaseResponses]; -export type GetBillingSubscriptionData = { +export type ListBillingMigrationRepairPreviewsData = { body?: never; path: { projectId: string; - environmentId: string; - instanceId: string; + programId: string; }; - query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/subscriptions/{instanceId}'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-previews'; }; -export type GetBillingSubscriptionErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type ListBillingMigrationRepairPreviewsResponses = { /** - * Stable machine-readable failure. + * Repair previews. */ - 404: ErrorEnvelope; + 200: BillingMigrationRepairPreviewPageEnvelope; +}; + +export type ListBillingMigrationRepairPreviewsResponse = ListBillingMigrationRepairPreviewsResponses[keyof ListBillingMigrationRepairPreviewsResponses]; + +export type PreviewBillingMigrationRepairData = { + body: BillingMigrationRepairPreviewRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-previews'; +}; + +export type PreviewBillingMigrationRepairErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 422: ErrorEnvelope; /** * Stable machine-readable failure. */ 503: ErrorEnvelope; }; -export type GetBillingSubscriptionError = GetBillingSubscriptionErrors[keyof GetBillingSubscriptionErrors]; +export type PreviewBillingMigrationRepairError = PreviewBillingMigrationRepairErrors[keyof PreviewBillingMigrationRepairErrors]; -export type GetBillingSubscriptionResponses = { +export type PreviewBillingMigrationRepairResponses = { /** - * The Subscription Instance. + * Existing preview returned for an identical idempotent replay. */ - 200: { - data?: BillingSubscriptionSnapshot; - }; + 200: BillingMigrationRepairPreviewCommandEnvelope; + /** + * Newly created bounded repair preview. + */ + 201: BillingMigrationRepairPreviewCommandEnvelope; }; -export type GetBillingSubscriptionResponse = GetBillingSubscriptionResponses[keyof GetBillingSubscriptionResponses]; +export type PreviewBillingMigrationRepairResponse = PreviewBillingMigrationRepairResponses[keyof PreviewBillingMigrationRepairResponses]; -export type ListBillingSubscriptionTimelineData = { +export type ListBillingMigrationRepairExecutionsData = { body?: never; path: { projectId: string; - environmentId: string; - instanceId: string; + programId: string; }; query?: { - limit?: number; /** * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ cursor?: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/subscriptions/{instanceId}/timeline'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-executions'; }; -export type ListBillingSubscriptionTimelineErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type ListBillingMigrationRepairExecutionsResponses = { /** - * Stable machine-readable failure. + * Repair executions. */ - 404: ErrorEnvelope; + 200: BillingMigrationRepairExecutionPageEnvelope; +}; + +export type ListBillingMigrationRepairExecutionsResponse = ListBillingMigrationRepairExecutionsResponses[keyof ListBillingMigrationRepairExecutionsResponses]; + +export type ExecuteBillingMigrationRepairData = { + body: BillingMigrationRepairExecutionRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-executions'; +}; + +export type ExecuteBillingMigrationRepairErrors = { /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 422: ErrorEnvelope; /** * Stable machine-readable failure. */ 503: ErrorEnvelope; }; -export type ListBillingSubscriptionTimelineError = ListBillingSubscriptionTimelineErrors[keyof ListBillingSubscriptionTimelineErrors]; +export type ExecuteBillingMigrationRepairError = ExecuteBillingMigrationRepairErrors[keyof ExecuteBillingMigrationRepairErrors]; -export type ListBillingSubscriptionTimelineResponses = { +export type ExecuteBillingMigrationRepairResponses = { /** - * Timeline entries. + * Settled terminal execution returned for an identical replay. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationRepairExecutionCommandTerminalEnvelope; + /** + * Newly completed repair execution. + */ + 201: BillingMigrationRepairExecutionCommandTerminalEnvelope; + /** + * New or replayed execution remains pending. + */ + 202: BillingMigrationRepairExecutionCommandPendingEnvelope; }; -export type ListBillingSubscriptionTimelineResponse = ListBillingSubscriptionTimelineResponses[keyof ListBillingSubscriptionTimelineResponses]; +export type ExecuteBillingMigrationRepairResponse = ExecuteBillingMigrationRepairResponses[keyof ExecuteBillingMigrationRepairResponses]; -export type ListBillingRestoreJobsData = { +export type ListBillingMigrationWebhookRedeliveriesData = { body?: never; path: { projectId: string; - environmentId: string; + programId: string; }; query?: { - billingCustomerId?: string; - limit?: number; /** * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ cursor?: string; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/restore-jobs'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/webhook-redeliveries'; }; -export type ListBillingRestoreJobsErrors = { +export type ListBillingMigrationWebhookRedeliveriesResponses = { /** - * Stable machine-readable failure. + * Webhook redeliveries. */ - 401: ErrorEnvelope; + 200: BillingMigrationWebhookRedeliveryPageEnvelope; +}; + +export type ListBillingMigrationWebhookRedeliveriesResponse = ListBillingMigrationWebhookRedeliveriesResponses[keyof ListBillingMigrationWebhookRedeliveriesResponses]; + +export type RedeliverBillingMigrationWebhookData = { + body: BillingMigrationRedeliveryRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/webhook-redeliveries'; +}; + +export type RedeliverBillingMigrationWebhookErrors = { /** * Stable machine-readable failure. */ @@ -10829,57 +14559,70 @@ export type ListBillingRestoreJobsErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 409: ErrorEnvelope; + 422: ErrorEnvelope; +}; + +export type RedeliverBillingMigrationWebhookError = RedeliverBillingMigrationWebhookErrors[keyof RedeliverBillingMigrationWebhookErrors]; + +export type RedeliverBillingMigrationWebhookResponses = { /** - * Stable machine-readable failure. + * Original result returned for an identical idempotent replay. */ - 503: ErrorEnvelope; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 202: BillingMigrationOperationEnvelope; }; -export type ListBillingRestoreJobsError = ListBillingRestoreJobsErrors[keyof ListBillingRestoreJobsErrors]; +export type RedeliverBillingMigrationWebhookResponse = RedeliverBillingMigrationWebhookResponses[keyof RedeliverBillingMigrationWebhookResponses]; + +export type ListBillingMigrationCredentialRemovalsData = { + body?: never; + path: { + projectId: string; + programId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals'; +}; -export type ListBillingRestoreJobsResponses = { +export type ListBillingMigrationCredentialRemovalsResponses = { /** - * Restore jobs. + * Credential removals. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationCredentialRemovalPageEnvelope; }; -export type ListBillingRestoreJobsResponse = ListBillingRestoreJobsResponses[keyof ListBillingRestoreJobsResponses]; +export type ListBillingMigrationCredentialRemovalsResponse = ListBillingMigrationCredentialRemovalsResponses[keyof ListBillingMigrationCredentialRemovalsResponses]; -export type GetBillingRestoreJobData = { - body?: never; +export type RemoveBillingMigrationCredentialData = { + body: BillingMigrationCredentialRemovalRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - environmentId: string; - restoreId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/restore-jobs/{restoreId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals'; }; -export type GetBillingRestoreJobErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type RemoveBillingMigrationCredentialErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10887,46 +14630,66 @@ export type GetBillingRestoreJobErrors = { /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetBillingRestoreJobError = GetBillingRestoreJobErrors[keyof GetBillingRestoreJobErrors]; +export type RemoveBillingMigrationCredentialError = RemoveBillingMigrationCredentialErrors[keyof RemoveBillingMigrationCredentialErrors]; -export type GetBillingRestoreJobResponses = { +export type RemoveBillingMigrationCredentialResponses = { /** - * The restore job. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: BillingRestoreJob; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type GetBillingRestoreJobResponse = GetBillingRestoreJobResponses[keyof GetBillingRestoreJobResponses]; +export type RemoveBillingMigrationCredentialResponse = RemoveBillingMigrationCredentialResponses[keyof RemoveBillingMigrationCredentialResponses]; -export type ListOperatorBillingIdentityConflictsData = { +export type ListBillingMigrationLegalHoldProposalsData = { body?: never; path: { projectId: string; + programId: string; }; query?: { - status?: 'open' | 'resolved'; + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; }; - url: '/v1/projects/{projectId}/billing/identity-conflicts'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals'; }; -export type ListOperatorBillingIdentityConflictsErrors = { +export type ListBillingMigrationLegalHoldProposalsResponses = { /** - * Stable machine-readable failure. + * Legal-hold proposals. */ - 401: ErrorEnvelope; + 200: BillingMigrationLegalHoldProposalPageEnvelope; +}; + +export type ListBillingMigrationLegalHoldProposalsResponse = ListBillingMigrationLegalHoldProposalsResponses[keyof ListBillingMigrationLegalHoldProposalsResponses]; + +export type ProposeBillingMigrationLegalHoldData = { + body: BillingMigrationLegalHoldProposalRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals'; +}; + +export type ProposeBillingMigrationLegalHoldErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10935,50 +14698,42 @@ export type ListOperatorBillingIdentityConflictsErrors = { * Stable machine-readable failure. */ 422: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 503: ErrorEnvelope; }; -export type ListOperatorBillingIdentityConflictsError = ListOperatorBillingIdentityConflictsErrors[keyof ListOperatorBillingIdentityConflictsErrors]; +export type ProposeBillingMigrationLegalHoldError = ProposeBillingMigrationLegalHoldErrors[keyof ProposeBillingMigrationLegalHoldErrors]; -export type ListOperatorBillingIdentityConflictsResponses = { +export type ProposeBillingMigrationLegalHoldResponses = { /** - * Identity conflicts. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: { - items?: Array; - }; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type ListOperatorBillingIdentityConflictsResponse = ListOperatorBillingIdentityConflictsResponses[keyof ListOperatorBillingIdentityConflictsResponses]; +export type ProposeBillingMigrationLegalHoldResponse = ProposeBillingMigrationLegalHoldResponses[keyof ProposeBillingMigrationLegalHoldResponses]; -export type GetOperatorBillingIdentityConflictData = { - body?: never; +export type ApproveBillingMigrationLegalHoldData = { + body: BillingMigrationLegalHoldApprovalRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - conflictId: string; + programId: string; + proposalId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/identity-conflicts/{conflictId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals/{proposalId}/approvals'; }; -export type GetOperatorBillingIdentityConflictErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type ApproveBillingMigrationLegalHoldErrors = { /** * Stable machine-readable failure. */ 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 404: ErrorEnvelope; /** * Stable machine-readable failure. */ @@ -10986,37 +14741,35 @@ export type GetOperatorBillingIdentityConflictErrors = { /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type GetOperatorBillingIdentityConflictError = GetOperatorBillingIdentityConflictErrors[keyof GetOperatorBillingIdentityConflictErrors]; +export type ApproveBillingMigrationLegalHoldError = ApproveBillingMigrationLegalHoldErrors[keyof ApproveBillingMigrationLegalHoldErrors]; -export type GetOperatorBillingIdentityConflictResponses = { +export type ApproveBillingMigrationLegalHoldResponses = { /** - * The conflict. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: OperatorBillingIdentityConflictDetail; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type GetOperatorBillingIdentityConflictResponse = GetOperatorBillingIdentityConflictResponses[keyof GetOperatorBillingIdentityConflictResponses]; +export type ApproveBillingMigrationLegalHoldResponse = ApproveBillingMigrationLegalHoldResponses[keyof ApproveBillingMigrationLegalHoldResponses]; -export type ResolveBillingIdentityConflictData = { - body: ResolveIdentityConflictRequest; +export type InspectBillingMigrationCompletionData = { + body?: never; path: { projectId: string; - conflictId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/identity-conflicts/{conflictId}/resolution'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion'; }; -export type ResolveBillingIdentityConflictErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type InspectBillingMigrationCompletionErrors = { /** * Stable machine-readable failure. */ @@ -11025,992 +14778,937 @@ export type ResolveBillingIdentityConflictErrors = { * Stable machine-readable failure. */ 404: ErrorEnvelope; +}; + +export type InspectBillingMigrationCompletionError = InspectBillingMigrationCompletionErrors[keyof InspectBillingMigrationCompletionErrors]; + +export type InspectBillingMigrationCompletionResponses = { /** - * Stable machine-readable failure. + * Server-derived completion prerequisites. */ - 409: ErrorEnvelope; + 200: BillingMigrationCompletionPrerequisitesRecord; +}; + +export type InspectBillingMigrationCompletionResponse = InspectBillingMigrationCompletionResponses[keyof InspectBillingMigrationCompletionResponses]; + +export type CompleteBillingMigrationData = { + body: BillingMigrationCompletionRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion'; +}; + +export type CompleteBillingMigrationErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 403: ErrorEnvelope; /** * Stable machine-readable failure. */ - 429: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 503: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type ResolveBillingIdentityConflictError = ResolveBillingIdentityConflictErrors[keyof ResolveBillingIdentityConflictErrors]; +export type CompleteBillingMigrationError = CompleteBillingMigrationErrors[keyof CompleteBillingMigrationErrors]; -export type ResolveBillingIdentityConflictResponses = { +export type CompleteBillingMigrationResponses = { /** - * The resolved conflict. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: OperatorBillingIdentityConflict; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type ResolveBillingIdentityConflictResponse = ResolveBillingIdentityConflictResponses[keyof ResolveBillingIdentityConflictResponses]; +export type CompleteBillingMigrationResponse = CompleteBillingMigrationResponses[keyof CompleteBillingMigrationResponses]; -export type ListProductEntitlementGrantVersionsData = { +export type ListBillingMigrationProposalsData = { body?: never; path: { projectId: string; + programId: string; }; - query: { - productId: string; - /** - * Restricts the history to one (Product - */ - entitlementId?: string; + query?: { /** - * Return only the open-ended version in force now. + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ - currentOnly?: boolean; - limit?: number; + cursor?: string; }; - url: '/v1/projects/{projectId}/billing/grant-versions'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/proposals'; }; -export type ListProductEntitlementGrantVersionsErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type ListBillingMigrationProposalsResponses = { /** - * Stable machine-readable failure. + * Cutover and rollback proposals. */ - 403: ErrorEnvelope; + 200: BillingMigrationProposalPageEnvelope; +}; + +export type ListBillingMigrationProposalsResponse = ListBillingMigrationProposalsResponses[keyof ListBillingMigrationProposalsResponses]; + +export type GetBillingMigrationProposalData = { + body?: never; + path: { + projectId: string; + programId: string; + proposalId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/proposals/{proposalId}'; +}; + +export type GetBillingMigrationProposalErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; +}; + +export type GetBillingMigrationProposalError = GetBillingMigrationProposalErrors[keyof GetBillingMigrationProposalErrors]; + +export type GetBillingMigrationProposalResponses = { /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. + * Migration proposal. */ - 503: ErrorEnvelope; + 200: BillingMigrationProposalRecord; }; -export type ListProductEntitlementGrantVersionsError = ListProductEntitlementGrantVersionsErrors[keyof ListProductEntitlementGrantVersionsErrors]; +export type GetBillingMigrationProposalResponse = GetBillingMigrationProposalResponses[keyof GetBillingMigrationProposalResponses]; -export type ListProductEntitlementGrantVersionsResponses = { +export type ListBillingMigrationApprovalsData = { + body?: never; + path: { + projectId: string; + programId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/approvals'; +}; + +export type ListBillingMigrationApprovalsResponses = { /** - * Grant versions. + * Migration approvals. */ - 200: { - data?: { - items?: Array; - }; - }; + 200: BillingMigrationApprovalPageEnvelope; }; -export type ListProductEntitlementGrantVersionsResponse = ListProductEntitlementGrantVersionsResponses[keyof ListProductEntitlementGrantVersionsResponses]; +export type ListBillingMigrationApprovalsResponse = ListBillingMigrationApprovalsResponses[keyof ListBillingMigrationApprovalsResponses]; -export type PublishProductEntitlementGrantVersionData = { - body: PublishGrantVersionRequest; +export type GetBillingMigrationApprovalData = { + body?: never; path: { projectId: string; + programId: string; + approvalId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/grant-versions'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/approvals/{approvalId}'; }; -export type PublishProductEntitlementGrantVersionErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetBillingMigrationApprovalErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 503: ErrorEnvelope; }; -export type PublishProductEntitlementGrantVersionError = PublishProductEntitlementGrantVersionErrors[keyof PublishProductEntitlementGrantVersionErrors]; +export type GetBillingMigrationApprovalError = GetBillingMigrationApprovalErrors[keyof GetBillingMigrationApprovalErrors]; -export type PublishProductEntitlementGrantVersionResponses = { +export type GetBillingMigrationApprovalResponses = { /** - * The published grant version. + * Migration approval. */ - 201: { - data?: ProductEntitlementGrantVersion; - }; + 200: BillingMigrationApprovalRecord; }; -export type PublishProductEntitlementGrantVersionResponse = PublishProductEntitlementGrantVersionResponses[keyof PublishProductEntitlementGrantVersionResponses]; +export type GetBillingMigrationApprovalResponse = GetBillingMigrationApprovalResponses[keyof GetBillingMigrationApprovalResponses]; -export type PreviewProductEntitlementGrantImpactData = { - body: PublishGrantVersionRequest; +export type GetLatestBillingMigrationCheckpointData = { + body?: never; path: { projectId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/grant-versions/impact-preview'; -}; - -export type PreviewProductEntitlementGrantImpactErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints/latest'; +}; + +export type GetLatestBillingMigrationCheckpointErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; }; -export type PreviewProductEntitlementGrantImpactError = PreviewProductEntitlementGrantImpactErrors[keyof PreviewProductEntitlementGrantImpactErrors]; +export type GetLatestBillingMigrationCheckpointError = GetLatestBillingMigrationCheckpointErrors[keyof GetLatestBillingMigrationCheckpointErrors]; -export type PreviewProductEntitlementGrantImpactResponses = { +export type GetLatestBillingMigrationCheckpointResponses = { /** - * The impact preview. + * Migration checkpoint. */ - 200: { - data?: GrantVersionImpact; - }; + 200: BillingMigrationCheckpointRecord; }; -export type PreviewProductEntitlementGrantImpactResponse = PreviewProductEntitlementGrantImpactResponses[keyof PreviewProductEntitlementGrantImpactResponses]; +export type GetLatestBillingMigrationCheckpointResponse = GetLatestBillingMigrationCheckpointResponses[keyof GetLatestBillingMigrationCheckpointResponses]; -export type GetProductEntitlementGrantVersionData = { +export type GetBillingMigrationCheckpointData = { body?: never; path: { projectId: string; - grantVersionId: string; + programId: string; + checkpointId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/grant-versions/{grantVersionId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/checkpoints/{checkpointId}'; }; -export type GetProductEntitlementGrantVersionErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetBillingMigrationCheckpointErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type GetProductEntitlementGrantVersionError = GetProductEntitlementGrantVersionErrors[keyof GetProductEntitlementGrantVersionErrors]; +export type GetBillingMigrationCheckpointError = GetBillingMigrationCheckpointErrors[keyof GetBillingMigrationCheckpointErrors]; -export type GetProductEntitlementGrantVersionResponses = { +export type GetBillingMigrationCheckpointResponses = { /** - * The grant version. + * Migration checkpoint. */ - 200: { - data?: ProductEntitlementGrantVersion; - }; + 200: BillingMigrationCheckpointRecord; }; -export type GetProductEntitlementGrantVersionResponse = GetProductEntitlementGrantVersionResponses[keyof GetProductEntitlementGrantVersionResponses]; +export type GetBillingMigrationCheckpointResponse = GetBillingMigrationCheckpointResponses[keyof GetBillingMigrationCheckpointResponses]; -export type UpdateProductEntitlementGrantVersionData = { +export type ListBillingMigrationAuthorityExecutionsData = { body?: never; path: { projectId: string; - grantVersionId: string; + programId: string; }; - query?: never; - url: '/v1/projects/{projectId}/billing/grant-versions/{grantVersionId}'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/authority-executions'; }; -export type UpdateProductEntitlementGrantVersionErrors = { +export type ListBillingMigrationAuthorityExecutionsResponses = { /** - * Stable machine-readable failure. + * Authority executions. */ - 409: ErrorEnvelope; + 200: BillingMigrationAuthorityExecutionPageEnvelope; }; -export type UpdateProductEntitlementGrantVersionError = UpdateProductEntitlementGrantVersionErrors[keyof UpdateProductEntitlementGrantVersionErrors]; +export type ListBillingMigrationAuthorityExecutionsResponse = ListBillingMigrationAuthorityExecutionsResponses[keyof ListBillingMigrationAuthorityExecutionsResponses]; -export type ListWebhookDestinationsData = { +export type GetBillingMigrationAuthorityExecutionData = { body?: never; path: { projectId: string; - environmentId: string; + programId: string; + executionId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/webhook-destinations'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/authority-executions/{executionId}'; }; -export type ListWebhookDestinationsErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetBillingMigrationAuthorityExecutionErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type ListWebhookDestinationsError = ListWebhookDestinationsErrors[keyof ListWebhookDestinationsErrors]; +export type GetBillingMigrationAuthorityExecutionError = GetBillingMigrationAuthorityExecutionErrors[keyof GetBillingMigrationAuthorityExecutionErrors]; -export type ListWebhookDestinationsResponses = { +export type GetBillingMigrationAuthorityExecutionResponses = { /** - * Destinations. + * Authority execution. */ - 200: { - data?: Array; - }; + 200: BillingMigrationAuthorityExecutionRecord; }; -export type ListWebhookDestinationsResponse = ListWebhookDestinationsResponses[keyof ListWebhookDestinationsResponses]; +export type GetBillingMigrationAuthorityExecutionResponse = GetBillingMigrationAuthorityExecutionResponses[keyof GetBillingMigrationAuthorityExecutionResponses]; -export type CreateWebhookDestinationData = { - body: CreateWebhookDestinationRequest; +export type GetBillingMigrationCaseData = { + body?: never; path: { projectId: string; - environmentId: string; + programId: string; + caseId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/webhook-destinations'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases/{caseId}'; }; -export type CreateWebhookDestinationErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetBillingMigrationCaseErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 404: ErrorEnvelope; +}; + +export type GetBillingMigrationCaseError = GetBillingMigrationCaseErrors[keyof GetBillingMigrationCaseErrors]; + +export type GetBillingMigrationCaseResponses = { /** - * Stable machine-readable failure. + * Migration case. */ - 422: ErrorEnvelope; + 200: BillingMigrationCaseRecord; }; -export type CreateWebhookDestinationError = CreateWebhookDestinationErrors[keyof CreateWebhookDestinationErrors]; +export type GetBillingMigrationCaseResponse = GetBillingMigrationCaseResponses[keyof GetBillingMigrationCaseResponses]; -export type CreateWebhookDestinationResponses = { +export type ListBillingMigrationCaseActionsData = { + body?: never; + path: { + projectId: string; + programId: string; + caseId: string; + }; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/cases/{caseId}/actions'; +}; + +export type ListBillingMigrationCaseActionsResponses = { /** - * The destination and its one-time signing secret. + * Immutable case actions. */ - 201: { - data?: WebhookDestinationWithSecret; - }; + 200: BillingMigrationCaseActionPageEnvelope; }; -export type CreateWebhookDestinationResponse = CreateWebhookDestinationResponses[keyof CreateWebhookDestinationResponses]; +export type ListBillingMigrationCaseActionsResponse = ListBillingMigrationCaseActionsResponses[keyof ListBillingMigrationCaseActionsResponses]; -export type DeleteWebhookDestinationData = { +export type GetBillingMigrationRepairPreviewData = { body?: never; path: { projectId: string; - destinationId: string; + programId: string; + previewId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-previews/{previewId}'; }; -export type DeleteWebhookDestinationErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetBillingMigrationRepairPreviewErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; }; -export type DeleteWebhookDestinationError = DeleteWebhookDestinationErrors[keyof DeleteWebhookDestinationErrors]; +export type GetBillingMigrationRepairPreviewError = GetBillingMigrationRepairPreviewErrors[keyof GetBillingMigrationRepairPreviewErrors]; -export type DeleteWebhookDestinationResponses = { +export type GetBillingMigrationRepairPreviewResponses = { /** - * The destination was removed. + * Repair preview. */ - 204: void; + 200: BillingMigrationRepairPreviewRecord; }; -export type DeleteWebhookDestinationResponse = DeleteWebhookDestinationResponses[keyof DeleteWebhookDestinationResponses]; +export type GetBillingMigrationRepairPreviewResponse = GetBillingMigrationRepairPreviewResponses[keyof GetBillingMigrationRepairPreviewResponses]; -export type GetWebhookDestinationData = { +export type GetBillingMigrationRepairExecutionData = { body?: never; path: { projectId: string; - destinationId: string; + programId: string; + executionId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/repair-executions/{executionId}'; }; -export type GetWebhookDestinationErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetBillingMigrationRepairExecutionErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type GetWebhookDestinationError = GetWebhookDestinationErrors[keyof GetWebhookDestinationErrors]; +export type GetBillingMigrationRepairExecutionError = GetBillingMigrationRepairExecutionErrors[keyof GetBillingMigrationRepairExecutionErrors]; -export type GetWebhookDestinationResponses = { +export type GetBillingMigrationRepairExecutionResponses = { /** - * The destination. + * Repair execution. */ - 200: { - data?: WebhookDestination; - }; + 200: BillingMigrationRepairExecutionRecord; }; -export type GetWebhookDestinationResponse = GetWebhookDestinationResponses[keyof GetWebhookDestinationResponses]; +export type GetBillingMigrationRepairExecutionResponse = GetBillingMigrationRepairExecutionResponses[keyof GetBillingMigrationRepairExecutionResponses]; -export type UpdateWebhookDestinationData = { - body: UpdateWebhookDestinationRequest; +export type GetBillingMigrationWebhookRedeliveryData = { + body?: never; path: { projectId: string; - destinationId: string; + programId: string; + redeliveryId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/webhook-redeliveries/{redeliveryId}'; }; -export type UpdateWebhookDestinationErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetBillingMigrationWebhookRedeliveryErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; }; -export type UpdateWebhookDestinationError = UpdateWebhookDestinationErrors[keyof UpdateWebhookDestinationErrors]; +export type GetBillingMigrationWebhookRedeliveryError = GetBillingMigrationWebhookRedeliveryErrors[keyof GetBillingMigrationWebhookRedeliveryErrors]; -export type UpdateWebhookDestinationResponses = { +export type GetBillingMigrationWebhookRedeliveryResponses = { /** - * The updated destination. + * Webhook redelivery. */ - 200: { - data?: WebhookDestination; - }; + 200: BillingMigrationWebhookRedeliveryRecord; }; -export type UpdateWebhookDestinationResponse = UpdateWebhookDestinationResponses[keyof UpdateWebhookDestinationResponses]; +export type GetBillingMigrationWebhookRedeliveryResponse = GetBillingMigrationWebhookRedeliveryResponses[keyof GetBillingMigrationWebhookRedeliveryResponses]; -export type SetWebhookDestinationStatusData = { - body: SetWebhookDestinationStatusRequest; +export type GetCurrentBillingMigrationCredentialRemovalData = { + body?: never; path: { projectId: string; - destinationId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/status'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals/current'; }; -export type SetWebhookDestinationStatusErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetCurrentBillingMigrationCredentialRemovalErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 422: ErrorEnvelope; }; -export type SetWebhookDestinationStatusError = SetWebhookDestinationStatusErrors[keyof SetWebhookDestinationStatusErrors]; +export type GetCurrentBillingMigrationCredentialRemovalError = GetCurrentBillingMigrationCredentialRemovalErrors[keyof GetCurrentBillingMigrationCredentialRemovalErrors]; -export type SetWebhookDestinationStatusResponses = { +export type GetCurrentBillingMigrationCredentialRemovalResponses = { /** - * The updated destination. + * Credential removal. */ - 200: { - data?: WebhookDestination; - }; + 200: BillingMigrationCredentialRemovalRecord; }; -export type SetWebhookDestinationStatusResponse = SetWebhookDestinationStatusResponses[keyof SetWebhookDestinationStatusResponses]; +export type GetCurrentBillingMigrationCredentialRemovalResponse = GetCurrentBillingMigrationCredentialRemovalResponses[keyof GetCurrentBillingMigrationCredentialRemovalResponses]; -export type ListWebhookSigningSecretsData = { +export type GetBillingMigrationCredentialRemovalData = { body?: never; path: { projectId: string; - destinationId: string; + programId: string; + removalId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/secrets'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/credential-removals/{removalId}'; }; -export type ListWebhookSigningSecretsErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetBillingMigrationCredentialRemovalErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type ListWebhookSigningSecretsError = ListWebhookSigningSecretsErrors[keyof ListWebhookSigningSecretsErrors]; +export type GetBillingMigrationCredentialRemovalError = GetBillingMigrationCredentialRemovalErrors[keyof GetBillingMigrationCredentialRemovalErrors]; -export type ListWebhookSigningSecretsResponses = { +export type GetBillingMigrationCredentialRemovalResponses = { /** - * Secret metadata. + * Credential removal. */ - 200: { - data?: Array; - }; + 200: BillingMigrationCredentialRemovalRecord; }; -export type ListWebhookSigningSecretsResponse = ListWebhookSigningSecretsResponses[keyof ListWebhookSigningSecretsResponses]; +export type GetBillingMigrationCredentialRemovalResponse = GetBillingMigrationCredentialRemovalResponses[keyof GetBillingMigrationCredentialRemovalResponses]; -export type RotateWebhookSigningSecretData = { +export type GetBillingMigrationLegalHoldProposalData = { body?: never; path: { projectId: string; - destinationId: string; + programId: string; + proposalId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/secrets/rotate'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-hold-proposals/{proposalId}'; }; -export type RotateWebhookSigningSecretErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetBillingMigrationLegalHoldProposalErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type RotateWebhookSigningSecretError = RotateWebhookSigningSecretErrors[keyof RotateWebhookSigningSecretErrors]; +export type GetBillingMigrationLegalHoldProposalError = GetBillingMigrationLegalHoldProposalErrors[keyof GetBillingMigrationLegalHoldProposalErrors]; -export type RotateWebhookSigningSecretResponses = { +export type GetBillingMigrationLegalHoldProposalResponses = { /** - * The new secret and the overlap deadline. + * Legal-hold proposal. */ - 201: { - data?: WebhookDestinationWithSecret; - }; + 200: BillingMigrationLegalHoldProposalRecord; }; -export type RotateWebhookSigningSecretResponse = RotateWebhookSigningSecretResponses[keyof RotateWebhookSigningSecretResponses]; +export type GetBillingMigrationLegalHoldProposalResponse = GetBillingMigrationLegalHoldProposalResponses[keyof GetBillingMigrationLegalHoldProposalResponses]; -export type RetireWebhookSigningSecretData = { +export type ListBillingMigrationLegalHoldsData = { body?: never; path: { projectId: string; - destinationId: string; - secretId: string; + programId: string; }; - query?: never; - url: '/v1/projects/{projectId}/billing/webhook-destinations/{destinationId}/secrets/{secretId}/retire'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-holds'; }; -export type RetireWebhookSigningSecretErrors = { +export type ListBillingMigrationLegalHoldsResponses = { /** - * Stable machine-readable failure. + * Legal-hold commands. */ - 401: ErrorEnvelope; + 200: BillingMigrationLegalHoldPageEnvelope; +}; + +export type ListBillingMigrationLegalHoldsResponse = ListBillingMigrationLegalHoldsResponses[keyof ListBillingMigrationLegalHoldsResponses]; + +export type GetCurrentBillingMigrationLegalHoldData = { + body?: never; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-holds/current'; +}; + +export type GetCurrentBillingMigrationLegalHoldErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; }; -export type RetireWebhookSigningSecretError = RetireWebhookSigningSecretErrors[keyof RetireWebhookSigningSecretErrors]; +export type GetCurrentBillingMigrationLegalHoldError = GetCurrentBillingMigrationLegalHoldErrors[keyof GetCurrentBillingMigrationLegalHoldErrors]; -export type RetireWebhookSigningSecretResponses = { +export type GetCurrentBillingMigrationLegalHoldResponses = { /** - * The retired secret's metadata. + * Legal-hold command. */ - 200: { - data?: WebhookSigningSecretMetadata; - }; + 200: BillingMigrationLegalHoldRecord; }; -export type RetireWebhookSigningSecretResponse = RetireWebhookSigningSecretResponses[keyof RetireWebhookSigningSecretResponses]; +export type GetCurrentBillingMigrationLegalHoldResponse = GetCurrentBillingMigrationLegalHoldResponses[keyof GetCurrentBillingMigrationLegalHoldResponses]; -export type ListWebhookDeliveriesData = { +export type GetBillingMigrationLegalHoldData = { body?: never; path: { projectId: string; + programId: string; + holdId: string; }; - query?: { - environmentId?: string; - eventId?: string; - destinationId?: string; - status?: 'pending' | 'succeeded' | 'failed' | 'exhausted' | 'skipped'; - limit?: number; - }; - url: '/v1/projects/{projectId}/billing/webhook-deliveries'; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/legal-holds/{holdId}'; }; - -export type ListWebhookDeliveriesErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; + +export type GetBillingMigrationLegalHoldErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type ListWebhookDeliveriesError = ListWebhookDeliveriesErrors[keyof ListWebhookDeliveriesErrors]; +export type GetBillingMigrationLegalHoldError = GetBillingMigrationLegalHoldErrors[keyof GetBillingMigrationLegalHoldErrors]; -export type ListWebhookDeliveriesResponses = { +export type GetBillingMigrationLegalHoldResponses = { /** - * Deliveries. + * Legal-hold command. */ - 200: { - data?: Array; - }; + 200: BillingMigrationLegalHoldRecord; }; -export type ListWebhookDeliveriesResponse = ListWebhookDeliveriesResponses[keyof ListWebhookDeliveriesResponses]; +export type GetBillingMigrationLegalHoldResponse = GetBillingMigrationLegalHoldResponses[keyof GetBillingMigrationLegalHoldResponses]; -export type GetWebhookDeliveryData = { +export type ListBillingMigrationCompletionHistoryData = { body?: never; path: { projectId: string; - deliveryId: string; + programId: string; }; - query?: never; - url: '/v1/projects/{projectId}/billing/webhook-deliveries/{deliveryId}'; + query?: { + /** + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. + */ + cursor?: string; + }; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion-history'; }; -export type GetWebhookDeliveryErrors = { +export type ListBillingMigrationCompletionHistoryResponses = { /** - * Stable machine-readable failure. + * Completion reports. */ - 401: ErrorEnvelope; + 200: BillingMigrationCompletionReportPageEnvelope; +}; + +export type ListBillingMigrationCompletionHistoryResponse = ListBillingMigrationCompletionHistoryResponses[keyof ListBillingMigrationCompletionHistoryResponses]; + +export type GetBillingMigrationCompletionReportData = { + body?: never; + path: { + projectId: string; + programId: string; + reportId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/completion-history/{reportId}'; +}; + +export type GetBillingMigrationCompletionReportErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type GetWebhookDeliveryError = GetWebhookDeliveryErrors[keyof GetWebhookDeliveryErrors]; +export type GetBillingMigrationCompletionReportError = GetBillingMigrationCompletionReportErrors[keyof GetBillingMigrationCompletionReportErrors]; -export type GetWebhookDeliveryResponses = { +export type GetBillingMigrationCompletionReportResponses = { /** - * The delivery. + * Completion report. */ - 200: { - data?: WebhookDelivery; - }; + 200: BillingMigrationCompletionReportRecord; }; -export type GetWebhookDeliveryResponse = GetWebhookDeliveryResponses[keyof GetWebhookDeliveryResponses]; +export type GetBillingMigrationCompletionReportResponse = GetBillingMigrationCompletionReportResponses[keyof GetBillingMigrationCompletionReportResponses]; -export type ListWebhookDeliveryAttemptsData = { - body?: never; +export type FreezeBillingMigrationStabilizationPolicyData = { + body: BillingMigrationFreezeStabilizationPolicyRequest; + headers: { + 'Idempotency-Key': string; + }; path: { projectId: string; - deliveryId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-deliveries/{deliveryId}/attempts'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-policy'; }; -export type ListWebhookDeliveryAttemptsErrors = { +export type FreezeBillingMigrationStabilizationPolicyErrors = { /** * Stable machine-readable failure. */ - 401: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type ListWebhookDeliveryAttemptsError = ListWebhookDeliveryAttemptsErrors[keyof ListWebhookDeliveryAttemptsErrors]; +export type FreezeBillingMigrationStabilizationPolicyError = FreezeBillingMigrationStabilizationPolicyErrors[keyof FreezeBillingMigrationStabilizationPolicyErrors]; -export type ListWebhookDeliveryAttemptsResponses = { +export type FreezeBillingMigrationStabilizationPolicyResponses = { /** - * Attempts. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: Array; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type ListWebhookDeliveryAttemptsResponse = ListWebhookDeliveryAttemptsResponses[keyof ListWebhookDeliveryAttemptsResponses]; +export type FreezeBillingMigrationStabilizationPolicyResponse = FreezeBillingMigrationStabilizationPolicyResponses[keyof FreezeBillingMigrationStabilizationPolicyResponses]; -export type ReplayWebhookDeliveryData = { +export type GetCurrentBillingMigrationStabilizationPolicyData = { body?: never; path: { projectId: string; - deliveryId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/webhook-deliveries/{deliveryId}/replay'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-policy/current'; }; -export type ReplayWebhookDeliveryErrors = { - /** - * Stable machine-readable failure. - */ - 401: ErrorEnvelope; +export type GetCurrentBillingMigrationStabilizationPolicyErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; }; -export type ReplayWebhookDeliveryError = ReplayWebhookDeliveryErrors[keyof ReplayWebhookDeliveryErrors]; +export type GetCurrentBillingMigrationStabilizationPolicyError = GetCurrentBillingMigrationStabilizationPolicyErrors[keyof GetCurrentBillingMigrationStabilizationPolicyErrors]; -export type ReplayWebhookDeliveryResponses = { +export type GetCurrentBillingMigrationStabilizationPolicyResponses = { /** - * The delivery was queued. + * Stabilization policy. */ - 202: { - data?: WebhookDelivery; - }; + 200: BillingMigrationStabilizationPolicyRecord; }; -export type ReplayWebhookDeliveryResponse = ReplayWebhookDeliveryResponses[keyof ReplayWebhookDeliveryResponses]; +export type GetCurrentBillingMigrationStabilizationPolicyResponse = GetCurrentBillingMigrationStabilizationPolicyResponses[keyof GetCurrentBillingMigrationStabilizationPolicyResponses]; -export type ListReconciliationRunsData = { +export type ListBillingMigrationStabilizationObservationsData = { body?: never; path: { projectId: string; - environmentId: string; + programId: string; }; query?: { /** - * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not - * construct or parse one — it encodes both the ordering timestamp and the row id, because - * billing identifiers are not time-ordered and an id alone cannot express a position in a - * timestamp ordering. - * - * A malformed or stale value starts from the first page rather than erroring, so a mangled - * cursor cannot silently truncate a list. Omit it for the first page; a response with no - * `nextCursor` is the last page. - * + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ cursor?: string; - limit?: number; - status?: 'queued' | 'leased' | 'completed' | 'partial' | 'failed'; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/reconciliation-runs'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-observations'; }; -export type ListReconciliationRunsErrors = { +export type ListBillingMigrationStabilizationObservationsResponses = { + /** + * Stabilization observations. + */ + 200: BillingMigrationStabilizationObservationPageEnvelope; +}; + +export type ListBillingMigrationStabilizationObservationsResponse = ListBillingMigrationStabilizationObservationsResponses[keyof ListBillingMigrationStabilizationObservationsResponses]; + +export type ObserveBillingMigrationStabilizationData = { + body: BillingMigrationObserveStabilizationRequest; + headers: { + 'Idempotency-Key': string; + }; + path: { + projectId: string; + programId: string; + }; + query?: never; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-observations'; +}; + +export type ObserveBillingMigrationStabilizationErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type ListReconciliationRunsError = ListReconciliationRunsErrors[keyof ListReconciliationRunsErrors]; +export type ObserveBillingMigrationStabilizationError = ObserveBillingMigrationStabilizationErrors[keyof ObserveBillingMigrationStabilizationErrors]; -export type ListReconciliationRunsResponses = { +export type ObserveBillingMigrationStabilizationResponses = { /** - * Reconciliation runs. + * Original result returned for an identical idempotent replay. */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type ListReconciliationRunsResponse = ListReconciliationRunsResponses[keyof ListReconciliationRunsResponses]; +export type ObserveBillingMigrationStabilizationResponse = ObserveBillingMigrationStabilizationResponses[keyof ObserveBillingMigrationStabilizationResponses]; -export type CreateReconciliationRunData = { - body: CreateReconciliationRunRequest; +export type GetLatestBillingMigrationStabilizationObservationData = { + body?: never; path: { projectId: string; - environmentId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/reconciliation-runs'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/stabilization-observations/latest'; }; -export type CreateReconciliationRunErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; - /** - * Stable machine-readable failure. - */ - 409: ErrorEnvelope; +export type GetLatestBillingMigrationStabilizationObservationErrors = { /** * Stable machine-readable failure. */ - 422: ErrorEnvelope; + 404: ErrorEnvelope; }; -export type CreateReconciliationRunError = CreateReconciliationRunErrors[keyof CreateReconciliationRunErrors]; +export type GetLatestBillingMigrationStabilizationObservationError = GetLatestBillingMigrationStabilizationObservationErrors[keyof GetLatestBillingMigrationStabilizationObservationErrors]; -export type CreateReconciliationRunResponses = { +export type GetLatestBillingMigrationStabilizationObservationResponses = { /** - * Reconciliation queued. + * Stabilization observation. */ - 202: { - data?: ReconciliationRun; - }; + 200: BillingMigrationStabilizationObservationRecord; }; -export type CreateReconciliationRunResponse = CreateReconciliationRunResponses[keyof CreateReconciliationRunResponses]; +export type GetLatestBillingMigrationStabilizationObservationResponse = GetLatestBillingMigrationStabilizationObservationResponses[keyof GetLatestBillingMigrationStabilizationObservationResponses]; -export type ListReplayJobsData = { +export type ListBillingMigrationRollbackReadinessAssessmentsData = { body?: never; path: { projectId: string; - environmentId: string; + programId: string; }; query?: { /** - * Opaque cursor from the immediately preceding list response. Forward it unchanged; do not - * construct or parse one — it encodes both the ordering timestamp and the row id, because - * billing identifiers are not time-ordered and an id alone cannot express a position in a - * timestamp ordering. - * - * A malformed or stale value starts from the first page rather than erroring, so a mangled - * cursor cannot silently truncate a list. Omit it for the first page; a response with no - * `nextCursor` is the last page. - * + * Opaque cursor from the immediately preceding list response. Malformed or stale values return validation_failed. */ cursor?: string; - limit?: number; - status?: 'queued' | 'leased' | 'completed' | 'failed'; }; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/replay-jobs'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-assessments'; }; -export type ListReplayJobsErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type ListBillingMigrationRollbackReadinessAssessmentsResponses = { /** - * Stable machine-readable failure. + * Rollback-readiness assessments. */ - 404: ErrorEnvelope; + 200: BillingMigrationRollbackReadinessAssessmentPageEnvelope; }; -export type ListReplayJobsError = ListReplayJobsErrors[keyof ListReplayJobsErrors]; +export type ListBillingMigrationRollbackReadinessAssessmentsResponse = ListBillingMigrationRollbackReadinessAssessmentsResponses[keyof ListBillingMigrationRollbackReadinessAssessmentsResponses]; -export type ListReplayJobsResponses = { - /** - * Replay jobs. - */ - 200: { - data?: { - items?: Array; - nextCursor?: string; - }; +export type AssessBillingMigrationRollbackReadinessData = { + body: BillingMigrationAssessRollbackReadinessRequest; + headers: { + 'Idempotency-Key': string; }; -}; - -export type ListReplayJobsResponse = ListReplayJobsResponses[keyof ListReplayJobsResponses]; - -export type CreateReplayJobData = { - body: CreateReplayJobRequest; path: { projectId: string; - environmentId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/environments/{environmentId}/billing/replay-jobs'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-assessments'; }; -export type CreateReplayJobErrors = { +export type AssessBillingMigrationRollbackReadinessErrors = { /** * Stable machine-readable failure. */ - 403: ErrorEnvelope; + 409: ErrorEnvelope; /** * Stable machine-readable failure. */ 422: ErrorEnvelope; }; -export type CreateReplayJobError = CreateReplayJobErrors[keyof CreateReplayJobErrors]; +export type AssessBillingMigrationRollbackReadinessError = AssessBillingMigrationRollbackReadinessErrors[keyof AssessBillingMigrationRollbackReadinessErrors]; -export type CreateReplayJobResponses = { +export type AssessBillingMigrationRollbackReadinessResponses = { /** - * Replay queued. + * Original result returned for an identical idempotent replay. */ - 202: { - data?: ReplayJob; - }; + 200: BillingMigrationOperationEnvelope; + /** + * Billing migration operation result. + */ + 201: BillingMigrationOperationEnvelope; }; -export type CreateReplayJobResponse = CreateReplayJobResponses[keyof CreateReplayJobResponses]; +export type AssessBillingMigrationRollbackReadinessResponse = AssessBillingMigrationRollbackReadinessResponses[keyof AssessBillingMigrationRollbackReadinessResponses]; -export type GetQuarantineRecordData = { +export type GetLatestBillingMigrationRollbackReadinessAssessmentData = { body?: never; path: { projectId: string; - recordId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/quarantine/{recordId}'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-assessments/latest'; }; -export type GetQuarantineRecordErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetLatestBillingMigrationRollbackReadinessAssessmentErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type GetQuarantineRecordError = GetQuarantineRecordErrors[keyof GetQuarantineRecordErrors]; +export type GetLatestBillingMigrationRollbackReadinessAssessmentError = GetLatestBillingMigrationRollbackReadinessAssessmentErrors[keyof GetLatestBillingMigrationRollbackReadinessAssessmentErrors]; -export type GetQuarantineRecordResponses = { +export type GetLatestBillingMigrationRollbackReadinessAssessmentResponses = { /** - * Quarantine Record. + * Rollback-readiness assessment. */ - 200: { - data?: QuarantineRecord; - }; + 200: BillingMigrationRollbackReadinessAssessmentRecord; }; -export type GetQuarantineRecordResponse = GetQuarantineRecordResponses[keyof GetQuarantineRecordResponses]; +export type GetLatestBillingMigrationRollbackReadinessAssessmentResponse = GetLatestBillingMigrationRollbackReadinessAssessmentResponses[keyof GetLatestBillingMigrationRollbackReadinessAssessmentResponses]; -export type RetryQuarantinedInputData = { +export type GetLatestBillingMigrationRollbackReadinessCheckpointData = { body?: never; path: { projectId: string; - recordId: string; + programId: string; }; query?: never; - url: '/v1/projects/{projectId}/billing/quarantine/{recordId}/retry'; + url: '/v1/projects/{projectId}/billing/migration-programs/{programId}/rollback-readiness-checkpoints/latest'; }; -export type RetryQuarantinedInputErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetLatestBillingMigrationRollbackReadinessCheckpointErrors = { /** * Stable machine-readable failure. */ 404: ErrorEnvelope; }; -export type RetryQuarantinedInputError = RetryQuarantinedInputErrors[keyof RetryQuarantinedInputErrors]; +export type GetLatestBillingMigrationRollbackReadinessCheckpointError = GetLatestBillingMigrationRollbackReadinessCheckpointErrors[keyof GetLatestBillingMigrationRollbackReadinessCheckpointErrors]; -export type RetryQuarantinedInputResponses = { +export type GetLatestBillingMigrationRollbackReadinessCheckpointResponses = { /** - * Quarantine Record. + * Rollback-readiness checkpoint. */ - 202: { - data?: QuarantineRecord; - }; + 200: BillingMigrationRollbackReadinessCheckpointRecord; }; -export type RetryQuarantinedInputResponse = RetryQuarantinedInputResponses[keyof RetryQuarantinedInputResponses]; +export type GetLatestBillingMigrationRollbackReadinessCheckpointResponse = GetLatestBillingMigrationRollbackReadinessCheckpointResponses[keyof GetLatestBillingMigrationRollbackReadinessCheckpointResponses]; -export type CloseQuarantineRecordSupersededData = { - body: { - supersededByRecordId: string; - }; - path: { - projectId: string; - recordId: string; - }; +export type GetWorkspaceBootstrapData = { + body?: never; + path?: never; query?: never; - url: '/v1/projects/{projectId}/billing/quarantine/{recordId}/close-superseded'; + url: '/v1/workspace/bootstrap'; }; -export type CloseQuarantineRecordSupersededErrors = { - /** - * Stable machine-readable failure. - */ - 403: ErrorEnvelope; +export type GetWorkspaceBootstrapErrors = { /** * Stable machine-readable failure. */ - 404: ErrorEnvelope; + 401: ErrorEnvelope; }; -export type CloseQuarantineRecordSupersededError = CloseQuarantineRecordSupersededErrors[keyof CloseQuarantineRecordSupersededErrors]; +export type GetWorkspaceBootstrapError = GetWorkspaceBootstrapErrors[keyof GetWorkspaceBootstrapErrors]; -export type CloseQuarantineRecordSupersededResponses = { +export type GetWorkspaceBootstrapResponses = { /** - * Quarantine Record. + * Workspace entry snapshot */ - 200: { - data?: QuarantineRecord; - }; + 200: WorkspaceBootstrapEnvelope; }; -export type CloseQuarantineRecordSupersededResponse = CloseQuarantineRecordSupersededResponses[keyof CloseQuarantineRecordSupersededResponses]; +export type GetWorkspaceBootstrapResponse = GetWorkspaceBootstrapResponses[keyof GetWorkspaceBootstrapResponses]; diff --git a/apps/dashboard/src/routeTree.gen.ts b/apps/dashboard/src/routeTree.gen.ts index c35c2725..2e116abb 100644 --- a/apps/dashboard/src/routeTree.gen.ts +++ b/apps/dashboard/src/routeTree.gen.ts @@ -15,52 +15,54 @@ import { Route as DiagnosticsRouteImport } from './routes/diagnostics' import { Route as Studio_layoutRouteImport } from './routes/_studio_layout' import { Route as HostedRouteImport } from './routes/_hosted' import { Route as IndexRouteImport } from './routes/index' -import { Route as Studio_layoutStudioRouteImport } from './routes/_studio_layout/studio' import { Route as HostedWorkspaceRouteImport } from './routes/_hosted/workspace' -import { Route as HostedOrganizationsNewRouteImport } from './routes/_hosted/organizations/new' -import { Route as HostedOrganizationsOrganizationIdIndexRouteImport } from './routes/_hosted/organizations/$organizationId/index' -import { Route as HostedOrganizationsOrganizationIdMembersRouteImport } from './routes/_hosted/organizations/$organizationId/members' -import { Route as HostedOrganizationsOrganizationIdProjectsNewRouteImport } from './routes/_hosted/organizations/$organizationId/projects/new' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdAppsRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/apps' -import { Route as Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRouteImport } from './routes/_studio_layout/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/settings/environments' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/settings/api-keys' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/grant-versions' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/$productId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/$planId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/index' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId' -import { Route as HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRouteImport } from './routes/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId' +import { Route as Studio_layoutStudioIndexRouteImport } from './routes/_studio_layout/studio/index' +import { Route as HostedOrgsNewRouteImport } from './routes/_hosted/orgs/new' +import { Route as HostedOrgsOrganizationIdIndexRouteImport } from './routes/_hosted/orgs/$organizationId/index' +import { Route as HostedOrgsOrganizationIdMembersRouteImport } from './routes/_hosted/orgs/$organizationId/members' +import { Route as HostedOrgsOrganizationIdProjectsNewRouteImport } from './routes/_hosted/orgs/$organizationId/projects/new' +import { Route as Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRouteImport } from './routes/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId' +import { Route as HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRouteImport } from './routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId' const SignupRoute = SignupRouteImport.update({ id: '/signup', @@ -90,357 +92,374 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const Studio_layoutStudioRoute = Studio_layoutStudioRouteImport.update({ - id: '/studio', - path: '/studio', - getParentRoute: () => Studio_layoutRoute, -} as any) const HostedWorkspaceRoute = HostedWorkspaceRouteImport.update({ id: '/workspace', path: '/workspace', getParentRoute: () => HostedRoute, } as any) -const HostedOrganizationsNewRoute = HostedOrganizationsNewRouteImport.update({ - id: '/organizations/new', - path: '/organizations/new', +const Studio_layoutStudioIndexRoute = + Studio_layoutStudioIndexRouteImport.update({ + id: '/studio/', + path: '/studio/', + getParentRoute: () => Studio_layoutRoute, + } as any) +const HostedOrgsNewRoute = HostedOrgsNewRouteImport.update({ + id: '/orgs/new', + path: '/orgs/new', getParentRoute: () => HostedRoute, } as any) -const HostedOrganizationsOrganizationIdIndexRoute = - HostedOrganizationsOrganizationIdIndexRouteImport.update({ - id: '/organizations/$organizationId/', - path: '/organizations/$organizationId/', - getParentRoute: () => HostedRoute, - } as any) -const HostedOrganizationsOrganizationIdMembersRoute = - HostedOrganizationsOrganizationIdMembersRouteImport.update({ - id: '/organizations/$organizationId/members', - path: '/organizations/$organizationId/members', - getParentRoute: () => HostedRoute, - } as any) -const HostedOrganizationsOrganizationIdProjectsNewRoute = - HostedOrganizationsOrganizationIdProjectsNewRouteImport.update({ - id: '/organizations/$organizationId/projects/new', - path: '/organizations/$organizationId/projects/new', +const HostedOrgsOrganizationIdIndexRoute = + HostedOrgsOrganizationIdIndexRouteImport.update({ + id: '/orgs/$organizationId/', + path: '/orgs/$organizationId/', getParentRoute: () => HostedRoute, } as any) -const HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdIndexRouteImport.update({ - id: '/organizations/$organizationId/projects/$projectId/', - path: '/organizations/$organizationId/projects/$projectId/', +const HostedOrgsOrganizationIdMembersRoute = + HostedOrgsOrganizationIdMembersRouteImport.update({ + id: '/orgs/$organizationId/members', + path: '/orgs/$organizationId/members', getParentRoute: () => HostedRoute, } as any) -const HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdAppsRouteImport.update({ - id: '/organizations/$organizationId/projects/$projectId/apps', - path: '/organizations/$organizationId/projects/$projectId/apps', +const HostedOrgsOrganizationIdProjectsNewRoute = + HostedOrgsOrganizationIdProjectsNewRouteImport.update({ + id: '/orgs/$organizationId/projects/new', + path: '/orgs/$organizationId/projects/new', getParentRoute: () => HostedRoute, } as any) -const Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute = - Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRouteImport.update( +const Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute = + Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRouteImport.update( { - id: '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId', - path: '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId', + id: '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId', + path: '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId', getParentRoute: () => Studio_layoutRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/settings/environments', - path: '/organizations/$organizationId/projects/$projectId/settings/environments', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/settings/api-keys', - path: '/organizations/$organizationId/projects/$projectId/settings/api-keys', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/providers', - path: '/organizations/$organizationId/projects/$projectId/catalog/providers', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/grant-versions', - path: '/organizations/$organizationId/projects/$projectId/catalog/grant-versions', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/products/', - path: '/organizations/$organizationId/projects/$projectId/catalog/products/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/plans/', - path: '/organizations/$organizationId/projects/$projectId/catalog/plans/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/entitlements/', - path: '/organizations/$organizationId/projects/$projectId/catalog/entitlements/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/connections/', - path: '/organizations/$organizationId/projects/$projectId/billing/connections/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases', - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements', - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls', - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments', - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets', - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRouteImport.update( { - id: '/$connectionId', - path: '/$connectionId', - getParentRoute: () => - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRoute, + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/', + getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/products/$productId', - path: '/organizations/$organizationId/projects/$projectId/catalog/products/$productId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId', - path: '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId', - path: '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId', - path: '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface', - path: '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRouteImport.update( { - id: '/$placementId', - path: '/$placementId', - getParentRoute: () => - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRoute, + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new', + getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRouteImport.update( { - id: '/$paywallId', - path: '/$paywallId', - getParentRoute: () => - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRoute, + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId', + getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRouteImport.update( { - id: '/new', - path: '/new', + id: '/$connectionId', + path: '/$connectionId', getParentRoute: () => - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRouteImport.update( { - id: '/$experimentId', - path: '/$experimentId', - getParentRoute: () => - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute, + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId', + getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId', getParentRoute: () => HostedRoute, } as any, ) -const HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute = - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRouteImport.update( +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRouteImport.update( { - id: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId', - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId', + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId', + getParentRoute: () => HostedRoute, + } as any, + ) +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRouteImport.update( + { + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId', + getParentRoute: () => HostedRoute, + } as any, + ) +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRouteImport.update( + { + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId', + getParentRoute: () => HostedRoute, + } as any, + ) +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRouteImport.update( + { + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId', + getParentRoute: () => HostedRoute, + } as any, + ) +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRouteImport.update( + { + id: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId', + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId', getParentRoute: () => HostedRoute, } as any, ) @@ -451,51 +470,53 @@ export interface FileRoutesByFullPath { '/login': typeof LoginRoute '/signup': typeof SignupRoute '/workspace': typeof HostedWorkspaceRoute - '/studio': typeof Studio_layoutStudioRoute - '/organizations/new': typeof HostedOrganizationsNewRoute - '/organizations/$organizationId/members': typeof HostedOrganizationsOrganizationIdMembersRoute - '/organizations/$organizationId/': typeof HostedOrganizationsOrganizationIdIndexRoute - '/organizations/$organizationId/projects/new': typeof HostedOrganizationsOrganizationIdProjectsNewRoute - '/organizations/$organizationId/projects/$projectId/apps': typeof HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute - '/organizations/$organizationId/projects/$projectId/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/grant-versions': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute - '/organizations/$organizationId/projects/$projectId/catalog/providers': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteWithChildren - '/organizations/$organizationId/projects/$projectId/settings/api-keys': typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute - '/organizations/$organizationId/projects/$projectId/settings/environments': typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute - '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId': typeof Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute - '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface': typeof HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute - '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/products/$productId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteWithChildren - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteWithChildren - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteWithChildren - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute - '/organizations/$organizationId/projects/$projectId/billing/connections/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/entitlements/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/plans/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/products/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute + '/orgs/new': typeof HostedOrgsNewRoute + '/studio/': typeof Studio_layoutStudioIndexRoute + '/orgs/$organizationId/members': typeof HostedOrgsOrganizationIdMembersRoute + '/orgs/$organizationId/': typeof HostedOrgsOrganizationIdIndexRoute + '/orgs/$organizationId/projects/new': typeof HostedOrgsOrganizationIdProjectsNewRoute + '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId': typeof Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteWithChildren + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -503,51 +524,53 @@ export interface FileRoutesByTo { '/login': typeof LoginRoute '/signup': typeof SignupRoute '/workspace': typeof HostedWorkspaceRoute - '/studio': typeof Studio_layoutStudioRoute - '/organizations/new': typeof HostedOrganizationsNewRoute - '/organizations/$organizationId/members': typeof HostedOrganizationsOrganizationIdMembersRoute - '/organizations/$organizationId': typeof HostedOrganizationsOrganizationIdIndexRoute - '/organizations/$organizationId/projects/new': typeof HostedOrganizationsOrganizationIdProjectsNewRoute - '/organizations/$organizationId/projects/$projectId/apps': typeof HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute - '/organizations/$organizationId/projects/$projectId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/grant-versions': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute - '/organizations/$organizationId/projects/$projectId/catalog/providers': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteWithChildren - '/organizations/$organizationId/projects/$projectId/settings/api-keys': typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute - '/organizations/$organizationId/projects/$projectId/settings/environments': typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute - '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId': typeof Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute - '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface': typeof HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute - '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/products/$productId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute - '/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteWithChildren - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteWithChildren - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteWithChildren - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute - '/organizations/$organizationId/projects/$projectId/billing/connections': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/entitlements': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/plans': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute - '/organizations/$organizationId/projects/$projectId/catalog/products': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute - '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute - '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute + '/orgs/new': typeof HostedOrgsNewRoute + '/studio': typeof Studio_layoutStudioIndexRoute + '/orgs/$organizationId/members': typeof HostedOrgsOrganizationIdMembersRoute + '/orgs/$organizationId': typeof HostedOrgsOrganizationIdIndexRoute + '/orgs/$organizationId/projects/new': typeof HostedOrgsOrganizationIdProjectsNewRoute + '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId': typeof Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteWithChildren + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute + '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -558,51 +581,53 @@ export interface FileRoutesById { '/login': typeof LoginRoute '/signup': typeof SignupRoute '/_hosted/workspace': typeof HostedWorkspaceRoute - '/_studio_layout/studio': typeof Studio_layoutStudioRoute - '/_hosted/organizations/new': typeof HostedOrganizationsNewRoute - '/_hosted/organizations/$organizationId/members': typeof HostedOrganizationsOrganizationIdMembersRoute - '/_hosted/organizations/$organizationId/': typeof HostedOrganizationsOrganizationIdIndexRoute - '/_hosted/organizations/$organizationId/projects/new': typeof HostedOrganizationsOrganizationIdProjectsNewRoute - '/_hosted/organizations/$organizationId/projects/$projectId/apps': typeof HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute - '/_hosted/organizations/$organizationId/projects/$projectId/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/grant-versions': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteWithChildren - '/_hosted/organizations/$organizationId/projects/$projectId/settings/api-keys': typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute - '/_hosted/organizations/$organizationId/projects/$projectId/settings/environments': typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute - '/_studio_layout/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId': typeof Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface': typeof HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/$planId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/$productId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteWithChildren - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteWithChildren - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteWithChildren - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId': typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/': typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute + '/_hosted/orgs/new': typeof HostedOrgsNewRoute + '/_studio_layout/studio/': typeof Studio_layoutStudioIndexRoute + '/_hosted/orgs/$organizationId/members': typeof HostedOrgsOrganizationIdMembersRoute + '/_hosted/orgs/$organizationId/': typeof HostedOrgsOrganizationIdIndexRoute + '/_hosted/orgs/$organizationId/projects/new': typeof HostedOrgsOrganizationIdProjectsNewRoute + '/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId': typeof Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteWithChildren + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/': typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -612,51 +637,53 @@ export interface FileRouteTypes { | '/login' | '/signup' | '/workspace' - | '/studio' - | '/organizations/new' - | '/organizations/$organizationId/members' - | '/organizations/$organizationId/' - | '/organizations/$organizationId/projects/new' - | '/organizations/$organizationId/projects/$projectId/apps' - | '/organizations/$organizationId/projects/$projectId/' - | '/organizations/$organizationId/projects/$projectId/catalog/grant-versions' - | '/organizations/$organizationId/projects/$projectId/catalog/providers' - | '/organizations/$organizationId/projects/$projectId/settings/api-keys' - | '/organizations/$organizationId/projects/$projectId/settings/environments' - | '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId' - | '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores' - | '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId' - | '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId' - | '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId' - | '/organizations/$organizationId/projects/$projectId/catalog/products/$productId' - | '/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases' - | '/organizations/$organizationId/projects/$projectId/billing/connections/' - | '/organizations/$organizationId/projects/$projectId/catalog/entitlements/' - | '/organizations/$organizationId/projects/$projectId/catalog/plans/' - | '/organizations/$organizationId/projects/$projectId/catalog/products/' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/' + | '/orgs/new' + | '/studio/' + | '/orgs/$organizationId/members' + | '/orgs/$organizationId/' + | '/orgs/$organizationId/projects/new' + | '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -664,51 +691,53 @@ export interface FileRouteTypes { | '/login' | '/signup' | '/workspace' + | '/orgs/new' | '/studio' - | '/organizations/new' - | '/organizations/$organizationId/members' - | '/organizations/$organizationId' - | '/organizations/$organizationId/projects/new' - | '/organizations/$organizationId/projects/$projectId/apps' - | '/organizations/$organizationId/projects/$projectId' - | '/organizations/$organizationId/projects/$projectId/catalog/grant-versions' - | '/organizations/$organizationId/projects/$projectId/catalog/providers' - | '/organizations/$organizationId/projects/$projectId/settings/api-keys' - | '/organizations/$organizationId/projects/$projectId/settings/environments' - | '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId' - | '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores' - | '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId' - | '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId' - | '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId' - | '/organizations/$organizationId/projects/$projectId/catalog/products/$productId' - | '/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases' - | '/organizations/$organizationId/projects/$projectId/billing/connections' - | '/organizations/$organizationId/projects/$projectId/catalog/entitlements' - | '/organizations/$organizationId/projects/$projectId/catalog/plans' - | '/organizations/$organizationId/projects/$projectId/catalog/products' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId' - | '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation' - | '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions' + | '/orgs/$organizationId/members' + | '/orgs/$organizationId' + | '/orgs/$organizationId/projects/new' + | '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls' + | '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements' id: | '__root__' | '/' @@ -718,51 +747,53 @@ export interface FileRouteTypes { | '/login' | '/signup' | '/_hosted/workspace' - | '/_studio_layout/studio' - | '/_hosted/organizations/new' - | '/_hosted/organizations/$organizationId/members' - | '/_hosted/organizations/$organizationId/' - | '/_hosted/organizations/$organizationId/projects/new' - | '/_hosted/organizations/$organizationId/projects/$projectId/apps' - | '/_hosted/organizations/$organizationId/projects/$projectId/' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/grant-versions' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers' - | '/_hosted/organizations/$organizationId/projects/$projectId/settings/api-keys' - | '/_hosted/organizations/$organizationId/projects/$projectId/settings/environments' - | '/_studio_layout/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId' - | '/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/$planId' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/$productId' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/' - | '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId' - | '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/' - | '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/' + | '/_hosted/orgs/new' + | '/_studio_layout/studio/' + | '/_hosted/orgs/$organizationId/members' + | '/_hosted/orgs/$organizationId/' + | '/_hosted/orgs/$organizationId/projects/new' + | '/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/' + | '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -818,13 +849,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/_studio_layout/studio': { - id: '/_studio_layout/studio' - path: '/studio' - fullPath: '/studio' - preLoaderRoute: typeof Studio_layoutStudioRouteImport - parentRoute: typeof Studio_layoutRoute - } '/_hosted/workspace': { id: '/_hosted/workspace' path: '/workspace' @@ -832,513 +856,502 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HostedWorkspaceRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/new': { - id: '/_hosted/organizations/new' - path: '/organizations/new' - fullPath: '/organizations/new' - preLoaderRoute: typeof HostedOrganizationsNewRouteImport + '/_studio_layout/studio/': { + id: '/_studio_layout/studio/' + path: '/studio' + fullPath: '/studio/' + preLoaderRoute: typeof Studio_layoutStudioIndexRouteImport + parentRoute: typeof Studio_layoutRoute + } + '/_hosted/orgs/new': { + id: '/_hosted/orgs/new' + path: '/orgs/new' + fullPath: '/orgs/new' + preLoaderRoute: typeof HostedOrgsNewRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/': { - id: '/_hosted/organizations/$organizationId/' - path: '/organizations/$organizationId' - fullPath: '/organizations/$organizationId/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdIndexRouteImport + '/_hosted/orgs/$organizationId/': { + id: '/_hosted/orgs/$organizationId/' + path: '/orgs/$organizationId' + fullPath: '/orgs/$organizationId/' + preLoaderRoute: typeof HostedOrgsOrganizationIdIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/members': { - id: '/_hosted/organizations/$organizationId/members' - path: '/organizations/$organizationId/members' - fullPath: '/organizations/$organizationId/members' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdMembersRouteImport + '/_hosted/orgs/$organizationId/members': { + id: '/_hosted/orgs/$organizationId/members' + path: '/orgs/$organizationId/members' + fullPath: '/orgs/$organizationId/members' + preLoaderRoute: typeof HostedOrgsOrganizationIdMembersRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/new': { - id: '/_hosted/organizations/$organizationId/projects/new' - path: '/organizations/$organizationId/projects/new' - fullPath: '/organizations/$organizationId/projects/new' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsNewRouteImport + '/_hosted/orgs/$organizationId/projects/new': { + id: '/_hosted/orgs/$organizationId/projects/new' + path: '/orgs/$organizationId/projects/new' + fullPath: '/orgs/$organizationId/projects/new' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsNewRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/' - path: '/organizations/$organizationId/projects/$projectId' - fullPath: '/organizations/$organizationId/projects/$projectId/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdIndexRouteImport + '/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId': { + id: '/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId' + path: '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId' + fullPath: '/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId' + preLoaderRoute: typeof Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRouteImport + parentRoute: typeof Studio_layoutRoute + } + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/apps': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/apps' - path: '/organizations/$organizationId/projects/$projectId/apps' - fullPath: '/organizations/$organizationId/projects/$projectId/apps' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdAppsRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRouteImport parentRoute: typeof HostedRoute } - '/_studio_layout/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId': { - id: '/_studio_layout/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId' - path: '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId' - fullPath: '/studio-hosted/$organizationId/$projectId/$environmentId/$paywallId/$draftId' - preLoaderRoute: typeof Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRouteImport - parentRoute: typeof Studio_layoutRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRouteImport + parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/settings/environments': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/settings/environments' - path: '/organizations/$organizationId/projects/$projectId/settings/environments' - fullPath: '/organizations/$organizationId/projects/$projectId/settings/environments' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/settings/api-keys': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/settings/api-keys' - path: '/organizations/$organizationId/projects/$projectId/settings/api-keys' - fullPath: '/organizations/$organizationId/projects/$projectId/settings/api-keys' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers' - path: '/organizations/$organizationId/projects/$projectId/catalog/providers' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/providers' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/grant-versions': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/grant-versions' - path: '/organizations/$organizationId/projects/$projectId/catalog/grant-versions' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/grant-versions' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/' - path: '/organizations/$organizationId/projects/$projectId/catalog/products' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/products/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/' - path: '/organizations/$organizationId/projects/$projectId/catalog/plans' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/plans/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/restores' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/' - path: '/organizations/$organizationId/projects/$projectId/catalog/entitlements' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/entitlements/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/' - path: '/organizations/$organizationId/projects/$projectId/billing/connections' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/connections/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/health' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases' - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/releases' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements' - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls' - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments' - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets' - path: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/assets' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId' - path: '/$connectionId' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/providers/$connectionId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRouteImport - parentRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRouteImport + parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/$productId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/products/$productId' - path: '/organizations/$organizationId/projects/$projectId/catalog/products/$productId' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/products/$productId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/$planId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/plans/$planId' - path: '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/plans/$planId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId' - path: '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId' - fullPath: '/organizations/$organizationId/projects/$projectId/catalog/entitlements/$entitlementId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId' - path: '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/connections/$credentialId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/restores' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/projection-health' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/health' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/health' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface' - path: '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface' - fullPath: '/organizations/$organizationId/projects/$projectId/analytics/$environmentId/$surface' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId' + path: '/$connectionId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRouteImport + parentRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRoute + } + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId' - path: '/$placementId' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/placements/$placementId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRouteImport - parentRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRouteImport + parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId' - path: '/$paywallId' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/paywalls/$paywallId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRouteImport - parentRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRouteImport + parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new' - path: '/new' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/new' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRouteImport - parentRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/$factId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRouteImport + parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId' - path: '/$experimentId' - fullPath: '/organizations/$organizationId/projects/$projectId/monetization/$environmentId/experiments/$experimentId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRouteImport - parentRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/subscriptions/$instanceId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRouteImport + parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/transactions/$factId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/$runId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/subscriptions/$instanceId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/reconciliation/$runId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/quarantine/$recordId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/identity-conflicts/$conflictId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRouteImport parentRoute: typeof HostedRoute } - '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId': { - id: '/_hosted/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId' - path: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId' - fullPath: '/organizations/$organizationId/projects/$projectId/billing/$environmentId/customers/$customerId' - preLoaderRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRouteImport + '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId': { + id: '/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId' + path: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId' + fullPath: '/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId' + preLoaderRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRouteImport parentRoute: typeof HostedRoute } } } -interface HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteChildren { - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute -} - -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteChildren: HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteChildren = - { - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersConnectionIdRoute, - } - -const HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteWithChildren = - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRoute._addFileChildren( - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteChildren, - ) - -interface HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteChildren { - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute -} - -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteChildren: HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteChildren = - { - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsExperimentIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsNewRoute, - } - -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteWithChildren = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute._addFileChildren( - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteChildren, - ) - -interface HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteChildren { - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute -} - -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteChildren: HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteChildren = - { - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsPaywallIdRoute, - } - -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteWithChildren = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRoute._addFileChildren( - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteChildren, - ) - -interface HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteChildren { - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute +interface HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteChildren { + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute } -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteChildren: HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteChildren = +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteChildren: HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteChildren = { - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsPlacementIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersConnectionIdRoute, } -const HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteWithChildren = - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRoute._addFileChildren( - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteChildren, +const HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteWithChildren = + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRoute._addFileChildren( + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteChildren, ) interface HostedRouteChildren { HostedWorkspaceRoute: typeof HostedWorkspaceRoute - HostedOrganizationsNewRoute: typeof HostedOrganizationsNewRoute - HostedOrganizationsOrganizationIdMembersRoute: typeof HostedOrganizationsOrganizationIdMembersRoute - HostedOrganizationsOrganizationIdIndexRoute: typeof HostedOrganizationsOrganizationIdIndexRoute - HostedOrganizationsOrganizationIdProjectsNewRoute: typeof HostedOrganizationsOrganizationIdProjectsNewRoute - HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute - HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteWithChildren - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute - HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteWithChildren - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteWithChildren - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteWithChildren - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute: typeof HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute + HostedOrgsNewRoute: typeof HostedOrgsNewRoute + HostedOrgsOrganizationIdMembersRoute: typeof HostedOrgsOrganizationIdMembersRoute + HostedOrgsOrganizationIdIndexRoute: typeof HostedOrgsOrganizationIdIndexRoute + HostedOrgsOrganizationIdProjectsNewRoute: typeof HostedOrgsOrganizationIdProjectsNewRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteWithChildren + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute: typeof HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute } const HostedRouteChildren: HostedRouteChildren = { HostedWorkspaceRoute: HostedWorkspaceRoute, - HostedOrganizationsNewRoute: HostedOrganizationsNewRoute, - HostedOrganizationsOrganizationIdMembersRoute: - HostedOrganizationsOrganizationIdMembersRoute, - HostedOrganizationsOrganizationIdIndexRoute: - HostedOrganizationsOrganizationIdIndexRoute, - HostedOrganizationsOrganizationIdProjectsNewRoute: - HostedOrganizationsOrganizationIdProjectsNewRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdAppsRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogGrantVersionsRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProvidersRouteWithChildren, - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsApiKeysRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdSettingsEnvironmentsRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdAnalyticsEnvironmentIdSurfaceRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdHealthRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdProjectionHealthRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdRestoresRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsCredentialIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsEntitlementIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansPlanIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsProductIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdAssetsRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdExperimentsRouteWithChildren, - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPaywallsRouteWithChildren, - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdPlacementsRouteWithChildren, - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdMonetizationEnvironmentIdReleasesRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingConnectionsIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogEntitlementsIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogPlansIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdCatalogProductsIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersCustomerIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsConflictIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineRecordIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationRunIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdSubscriptionsInstanceIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsFactIdRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdCustomersIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdIdentityConflictsIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdQuarantineIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdReconciliationIndexRoute, - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute: - HostedOrganizationsOrganizationIdProjectsProjectIdBillingEnvironmentIdTransactionsIndexRoute, + HostedOrgsNewRoute: HostedOrgsNewRoute, + HostedOrgsOrganizationIdMembersRoute: HostedOrgsOrganizationIdMembersRoute, + HostedOrgsOrganizationIdIndexRoute: HostedOrgsOrganizationIdIndexRoute, + HostedOrgsOrganizationIdProjectsNewRoute: + HostedOrgsOrganizationIdProjectsNewRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAppsRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyAnalyticsSurfaceRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingHealthRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingProjectionHealthRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingRestoresRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogGrantVersionsRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProvidersRouteWithChildren, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationAssetsRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationReleasesRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsApiKeysRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeySettingsEnvironmentsRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsCredentialIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersCustomerIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsConflictIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsProgramIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineRecordIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationRunIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingSubscriptionsInstanceIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsFactIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsEntitlementIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansPlanIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsProductIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsExperimentIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsNewRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsPaywallIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsPlacementIdRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingConnectionsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingCustomersIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingIdentityConflictsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingMigrationsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingQuarantineIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingReconciliationIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyBillingTransactionsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogEntitlementsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogPlansIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyCatalogProductsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationExperimentsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPaywallsIndexRoute, + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute: + HostedOrgsOrganizationIdProjectsProjectIdEnvEnvironmentKeyMonetizationPlacementsIndexRoute, } const HostedRouteWithChildren = HostedRoute._addFileChildren(HostedRouteChildren) interface Studio_layoutRouteChildren { - Studio_layoutStudioRoute: typeof Studio_layoutStudioRoute - Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute: typeof Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute + Studio_layoutStudioIndexRoute: typeof Studio_layoutStudioIndexRoute + Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute: typeof Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute } const Studio_layoutRouteChildren: Studio_layoutRouteChildren = { - Studio_layoutStudioRoute: Studio_layoutStudioRoute, - Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute: - Studio_layoutStudioHostedOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute, + Studio_layoutStudioIndexRoute: Studio_layoutStudioIndexRoute, + Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute: + Studio_layoutStudioOrganizationIdProjectIdEnvironmentIdPaywallIdDraftIdRoute, } const Studio_layoutRouteWithChildren = Studio_layoutRoute._addFileChildren( diff --git a/apps/dashboard/src/routes/_hosted.tsx b/apps/dashboard/src/routes/_hosted.tsx index 7e4557e1..e4917c0d 100644 --- a/apps/dashboard/src/routes/_hosted.tsx +++ b/apps/dashboard/src/routes/_hosted.tsx @@ -2,7 +2,7 @@ import { createFileRoute, Outlet, redirect, useRouterState } from "@tanstack/rea import { useQuery } from "@tanstack/react-query" import { RouteErrorState, RoutePendingState } from "@/components/feedback/route-feedback" -import { CloudWorkspaceShell } from "@/features/organizations/components/cloud-workspace-shell" +import { CloudWorkspaceShell } from "@/features/orgs/components/cloud-workspace-shell" import { HostedAccessBanner } from "@/features/auth/components/hosted-access-banner" import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" diff --git a/apps/dashboard/src/routes/_hosted/workspace.tsx b/apps/dashboard/src/routes/_hosted/workspace.tsx index 2d68b77f..f8dc7101 100644 --- a/apps/dashboard/src/routes/_hosted/workspace.tsx +++ b/apps/dashboard/src/routes/_hosted/workspace.tsx @@ -2,9 +2,12 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" -import { WorkspaceHome } from "@/features/organizations/components/workspace-home" +import { WorkspaceEntryRedirect } from "@/features/orgs/components/workspace-entry-redirect" export const Route = createFileRoute("/_hosted/workspace")({ - component: WorkspaceHome, + // The entry decision deliberately lives in the component, not in beforeLoad: + // see WorkspaceEntryRedirect for why a client-only beforeLoad never runs on a + // hard load. + component: WorkspaceEntryRedirect, pendingComponent: RoutePendingState, }) From 216c1c26a56d869b0aef33cfd5204c3de72d3545 Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Thu, 30 Jul 2026 19:12:39 +0100 Subject: [PATCH 09/25] feat(dashboard): add shared route head util and per-page titles Add routeHead() in src/lib/routing/route-head.ts so each file route only supplies its own title (and an optional description) instead of repeating the product-name suffix and meta shape. Wire it into the root route and every page route. --- apps/dashboard/src/lib/routing/route-head.ts | 58 +++++++++++++++++++ apps/dashboard/src/routes/__root.tsx | 7 +-- .../_hosted/orgs/$organizationId/index.tsx | 2 + .../_hosted/orgs/$organizationId/members.tsx | 2 + .../$environmentKey/analytics/$surface.tsx | 2 + .../$projectId/env/$environmentKey/apps.tsx | 2 + .../billing/connections/$credentialId.tsx | 2 + .../billing/connections/index.tsx | 2 + .../billing/customers/$customerId.tsx | 2 + .../billing/customers/index.tsx | 3 +- .../env/$environmentKey/billing/health.tsx | 2 + .../identity-conflicts/$conflictId.tsx | 2 + .../billing/identity-conflicts/index.tsx | 3 +- .../billing/migrations/$programId.tsx | 2 + .../billing/migrations/index.tsx | 2 + .../billing/projection-health.tsx | 2 + .../billing/quarantine/$recordId.tsx | 2 + .../billing/quarantine/index.tsx | 3 +- .../billing/reconciliation/$runId.tsx | 2 + .../billing/reconciliation/index.tsx | 3 +- .../env/$environmentKey/billing/restores.tsx | 3 +- .../billing/subscriptions/$instanceId.tsx | 3 +- .../billing/transactions/$factId.tsx | 2 + .../billing/transactions/index.tsx | 3 +- .../catalog/entitlements/$entitlementId.tsx | 2 + .../catalog/entitlements/index.tsx | 2 + .../catalog/grant-versions.tsx | 2 + .../$environmentKey/catalog/plans/$planId.tsx | 2 + .../$environmentKey/catalog/plans/index.tsx | 2 + .../catalog/products/$productId.tsx | 2 + .../catalog/products/index.tsx | 2 + .../env/$environmentKey/catalog/providers.tsx | 2 + .../catalog/providers/$connectionId.tsx | 2 + .../$projectId/env/$environmentKey/index.tsx | 14 +++-- .../$environmentKey/monetization/assets.tsx | 2 + .../experiments/$experimentId.tsx | 6 +- .../monetization/experiments/index.tsx | 7 ++- .../monetization/experiments/new.tsx | 6 +- .../monetization/paywalls/$paywallId.tsx | 2 + .../monetization/paywalls/index.tsx | 2 + .../monetization/placements/$placementId.tsx | 6 +- .../monetization/placements/index.tsx | 2 + .../$environmentKey/monetization/releases.tsx | 2 + .../env/$environmentKey/settings/api-keys.tsx | 2 + .../$environmentKey/settings/environments.tsx | 2 + .../orgs/$organizationId/projects/new.tsx | 2 + .../dashboard/src/routes/_hosted/orgs/new.tsx | 2 + .../$environmentId/$paywallId/$draftId.tsx | 3 + .../routes/_studio_layout/studio/index.tsx | 3 + apps/dashboard/src/routes/diagnostics.tsx | 6 ++ apps/dashboard/src/routes/login.tsx | 6 ++ apps/dashboard/src/routes/signup.tsx | 6 ++ 52 files changed, 193 insertions(+), 22 deletions(-) create mode 100644 apps/dashboard/src/lib/routing/route-head.ts diff --git a/apps/dashboard/src/lib/routing/route-head.ts b/apps/dashboard/src/lib/routing/route-head.ts new file mode 100644 index 00000000..db86d8c7 --- /dev/null +++ b/apps/dashboard/src/lib/routing/route-head.ts @@ -0,0 +1,58 @@ +/** + * Shared document head construction for file routes. + * + * Every route repeats the same title suffix and description shape, so the + * boilerplate is centralised here and each route supplies only what makes it + * distinct. TanStack merges head output from the root down the matched route + * tree, deduping `title` and same-`name` meta tags, so a leaf route's values + * replace the root defaults without the route having to restate them. + */ + +import type { AnyRouteMatch } from "@tanstack/react-router" + +/** Product name appended to every page title. */ +export const APP_NAME = "Mosaic Studio" + +/** Description used when a route does not describe itself. */ +export const APP_DESCRIPTION = "Build and operate native monetization experiences with Mosaic." + +/** Separator between the page title and the product name. */ +const TITLE_SEPARATOR = " · " + +export interface RouteHeadOptions { + /** Overrides the inherited description. Omit to keep the parent's. */ + description?: string + /** + * Page title without the product suffix, for example `"Paywalls"`. Omit to + * fall back to the bare product name. + */ + title?: string +} + +export interface RouteHead { + meta: NonNullable +} + +/** + * Builds the `head` payload for a route. + * + * ```ts + * export const Route = createFileRoute("/login")({ + * head: () => routeHead({ title: "Sign in" }), + * }) + * ``` + */ +export function routeHead({ description, title }: RouteHeadOptions = {}): RouteHead { + const meta: NonNullable = [{ title: pageTitle(title) }] + + if (description) { + meta.push({ content: description, name: "description" }) + } + + return { meta } +} + +/** Suffixes a page title with the product name. */ +export function pageTitle(title?: string): string { + return title ? `${title}${TITLE_SEPARATOR}${APP_NAME}` : APP_NAME +} diff --git a/apps/dashboard/src/routes/__root.tsx b/apps/dashboard/src/routes/__root.tsx index 30de3f2d..dbec8427 100644 --- a/apps/dashboard/src/routes/__root.tsx +++ b/apps/dashboard/src/routes/__root.tsx @@ -6,6 +6,7 @@ import { RootErrorComponent } from "@/components/feedback/root-error-component" import { RouteNotFoundState } from "@/components/feedback/route-feedback" import { RootDocument } from "@/components/layout/root-document" import { dashboardBuildInfo } from "@/config/environment" +import { APP_DESCRIPTION, routeHead } from "@/lib/routing/route-head" import { AppProviders } from "@/providers/app-providers" import type { RouterContext } from "@/router-context" import globalStyles from "@/styles/globals.css?url" @@ -18,11 +19,7 @@ export const Route = createRootRouteWithContext()({ meta: [ { charSet: "utf-8" }, { content: "width=device-width, initial-scale=1", name: "viewport" }, - { title: "Mosaic Studio" }, - { - content: "Build and operate native monetization experiences with Mosaic.", - name: "description", - }, + ...routeHead({ description: APP_DESCRIPTION }).meta, // Lets an operator identify the exact bundle a browser loaded without // opening a console. { diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/index.tsx index d9233696..9098c5f2 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/index.tsx @@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { OrganizationOverviewPage } from "@/features/orgs/components/organization-overview-page" +import { routeHead } from "@/lib/routing/route-head" interface OrganizationSearch { projectStatus?: "archived" @@ -10,6 +11,7 @@ interface OrganizationSearch { export const Route = createFileRoute("/_hosted/orgs/$organizationId/")({ component: OrganizationRoute, + head: () => routeHead({ title: "Organization" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): OrganizationSearch => ({ projectStatus: search.projectStatus === "archived" ? "archived" : undefined, diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/members.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/members.tsx index 3aac7a57..681521f9 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/members.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/members.tsx @@ -3,9 +3,11 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { MembersPage } from "@/features/members/components/members-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute("/_hosted/orgs/$organizationId/members")({ component: OrganizationMembersRoute, + head: () => routeHead({ title: "Members" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface.tsx index 335f72be..bedca714 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface.tsx @@ -5,10 +5,12 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { AnalyticsWorkspace } from "@/features/analytics/components/analytics-workspace" import { analyticsSurfaces, type AnalyticsSurface } from "@/features/analytics/types/analytics" import { parseAnalyticsFilters } from "@/features/analytics/types/analytics-filters" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/analytics/$surface", )({ + head: () => routeHead({ title: "Analytics" }), validateSearch: parseAnalyticsFilters, component: RouteComponent, pendingComponent: RoutePendingState, diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps.tsx index aa0492e1..37a6c5b1 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps.tsx @@ -3,11 +3,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { ApplicationsPage } from "@/features/projects/components/applications-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/apps", )({ component: ProjectAppsRoute, + head: () => routeHead({ title: "Applications" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId.tsx index 1c1a723b..12da7e92 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId.tsx @@ -3,11 +3,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { StoreConnectionDetailPage } from "@/features/store-connections/components/store-connection-detail-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/$credentialId", )({ component: BillingConnectionDetailRoute, + head: () => routeHead({ title: "Billing connection" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index.tsx index f5a855a6..aed05734 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/index.tsx @@ -3,11 +3,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { StoreConnectionsPage } from "@/features/store-connections/components/store-connections-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/connections/", )({ component: BillingConnectionsRoute, + head: () => routeHead({ title: "Billing connections" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId.tsx index 6fcfb264..81715572 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId.tsx @@ -4,11 +4,13 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { CustomerDetailPage } from "@/features/billing-customers/components/customer-detail-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/$customerId", )({ component: CustomerDetailRoute, + head: () => routeHead({ title: "Customer" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/index.tsx index 46d9b1ac..f82e908a 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/index.tsx @@ -4,6 +4,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { BillingCustomersPage } from "@/features/billing-customers/components/billing-customers-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" interface CustomersSearch { conflictedOnly?: boolean @@ -14,6 +15,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/customers/", )({ component: BillingCustomersRoute, + head: () => routeHead({ title: "Customers" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): CustomersSearch => ({ conflictedOnly: @@ -32,7 +34,6 @@ function BillingCustomersRoute() { const navigate = Route.useNavigate() if (!environmentId) return fallback - return ( routeHead({ title: "Billing health" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId.tsx index ceed7652..bc3beb4a 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId.tsx @@ -4,11 +4,13 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { IdentityConflictDetailPage } from "@/features/billing-customers/components/identity-conflict-detail-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/$conflictId", )({ component: IdentityConflictDetailRoute, + head: () => routeHead({ title: "Identity conflict" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/index.tsx index d8e35c17..e3f068f4 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/index.tsx @@ -4,6 +4,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { IdentityConflictsPage } from "@/features/billing-customers/components/identity-conflicts-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" interface ConflictsSearch { status?: "open" | "resolved" @@ -13,6 +14,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/identity-conflicts/", )({ component: IdentityConflictsRoute, + head: () => routeHead({ title: "Identity conflicts" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): ConflictsSearch => ({ status: search.status === "resolved" ? "resolved" : undefined, @@ -26,7 +28,6 @@ function IdentityConflictsRoute() { const navigate = Route.useNavigate() if (!environmentId) return fallback - return ( routeHead({ title: "Migration program" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): MigrationSearch => ({ batchId: typeof search.batchId === "string" ? search.batchId : undefined, diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx index 240dea58..5fde4008 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/index.tsx @@ -2,11 +2,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { MigrationProgramsPage } from "@/features/billing-migrations/components/migration-programs-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/", )({ component: MigrationProgramsRoute, + head: () => routeHead({ title: "Migrations" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health.tsx index 59a5b059..f821a5a2 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health.tsx @@ -4,11 +4,13 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { ProjectionHealthPage } from "@/features/billing-projection/components/projection-health-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/projection-health", )({ component: ProjectionHealthRoute, + head: () => routeHead({ title: "Projection health" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId.tsx index f2cd3dc2..b573eeba 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId.tsx @@ -4,11 +4,13 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { QuarantineDetailPage } from "@/features/billing-operations/components/quarantine-detail-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/$recordId", )({ component: BillingQuarantineDetailRoute, + head: () => routeHead({ title: "Quarantined record" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/index.tsx index 626e6ada..47e13497 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/index.tsx @@ -5,6 +5,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { QuarantinePage } from "@/features/billing-operations/components/quarantine-page" import type { QuarantineListFilters } from "@/features/billing-operations/queries/quarantine-queries" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" const STATUSES = ["open", "retrying", "closed_after_success", "closed_superseded"] as const const PROVIDERS = ["app_store", "google_play"] as const @@ -14,6 +15,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/quarantine/", )({ component: BillingQuarantineRoute, + head: () => routeHead({ title: "Quarantine" }), pendingComponent: RoutePendingState, // Unknown values fall back to "no filter" rather than throwing: a stale link // should still render the page it names. @@ -38,7 +40,6 @@ function BillingQuarantineRoute() { const navigate = Route.useNavigate() if (!environmentId) return fallback - return ( routeHead({ title: "Reconciliation run" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/index.tsx index 09e2b5f7..8f6f4d6b 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/index.tsx @@ -4,6 +4,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { ReconciliationPage } from "@/features/billing-operations/components/reconciliation-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" interface ReconciliationSearch { cursor?: string @@ -13,6 +14,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/reconciliation/", )({ component: BillingReconciliationRoute, + head: () => routeHead({ title: "Reconciliation" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): ReconciliationSearch => ({ cursor: @@ -29,7 +31,6 @@ function BillingReconciliationRoute() { const navigate = Route.useNavigate() if (!environmentId) return fallback - return ( routeHead({ title: "Restore jobs" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): RestoresSearch => ({ cursor: @@ -29,7 +31,6 @@ function RestoreJobsRoute() { const navigate = Route.useNavigate() if (!environmentId) return fallback - return ( routeHead({ title: "Subscription" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): SubscriptionSearch => ({ cursor: @@ -29,7 +31,6 @@ function SubscriptionDetailRoute() { const navigate = Route.useNavigate() if (!environmentId) return fallback - return ( routeHead({ title: "Transaction" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/index.tsx index dc889386..9840a85a 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/index.tsx @@ -8,11 +8,13 @@ import { serializeTransactionFilters, } from "@/features/billing-ledger/types/transaction-filters" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/transactions/", )({ component: BillingTransactionsRoute, + head: () => routeHead({ title: "Transactions" }), pendingComponent: RoutePendingState, // The Mosaic Environment is a path parameter and is deliberately never read // from the search string, so a crafted URL cannot retarget the view. @@ -26,7 +28,6 @@ function BillingTransactionsRoute() { const navigate = Route.useNavigate() if (!environmentId) return fallback - return ( routeHead({ title: "Entitlement" }), }) function CatalogEntitlementRoute() { diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/index.tsx index 12043378..640d7d2e 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/index.tsx @@ -3,11 +3,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { EntitlementsPage } from "@/features/catalog/components/entitlements-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/", )({ component: CatalogEntitlementsRoute, + head: () => routeHead({ title: "Entitlements" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions.tsx index edb510bd..c4528d1d 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions.tsx @@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { GrantVersionsPage } from "@/features/entitlement-grants/components/grant-versions-page" +import { routeHead } from "@/lib/routing/route-head" interface GrantVersionsSearch { entitlementId?: string @@ -17,6 +18,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/grant-versions", )({ component: GrantVersionsRoute, + head: () => routeHead({ title: "Grant versions" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): GrantVersionsSearch => ({ entitlementId: readIdentifier(search.entitlementId), diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId.tsx index e3641cae..e780dd13 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId.tsx @@ -1,11 +1,13 @@ import { createFileRoute } from "@tanstack/react-router" import { PlanDetailPage } from "@/features/catalog/components/plan-detail-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId", )({ component: CatalogPlanRoute, + head: () => routeHead({ title: "Plan" }), }) function CatalogPlanRoute() { diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/index.tsx index 594c6d6a..99332d47 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/index.tsx @@ -3,11 +3,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { PlansPage } from "@/features/catalog/components/plans-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/", )({ component: CatalogPlansRoute, + head: () => routeHead({ title: "Plans" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId.tsx index 5128808b..659f6d72 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId.tsx @@ -2,6 +2,7 @@ import { createFileRoute } from "@tanstack/react-router" import { ProductDetailPage } from "@/features/catalog/components/product-detail-page" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" +import { routeHead } from "@/lib/routing/route-head" interface ProductReadinessSearch { applicationId?: string @@ -13,6 +14,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId", )({ component: CatalogProductRoute, + head: () => routeHead({ title: "Product" }), validateSearch: (search: Record): ProductReadinessSearch => { const returnTo = safeInternalReturnTo(search.returnTo, "") return { diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index.tsx index 18c2c89f..15a343df 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/index.tsx @@ -5,6 +5,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" import { ProductsPage } from "@/features/catalog/components/products-page" import type { ProductFilters } from "@/features/catalog/queries/catalog-query" +import { routeHead } from "@/lib/routing/route-head" const productStatuses = ["draft", "connected", "attention_required", "archived"] as const const productTypes = ["subscription", "one_time_non_consumable"] as const @@ -25,6 +26,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/", )({ component: CatalogProductsRoute, + head: () => routeHead({ title: "Products" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): ProductsSearch => { // Bounded to same-origin paths, so a recovery round trip can never be diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers.tsx index 34871287..23d35f89 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers.tsx @@ -4,6 +4,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { ProviderConnectionsPage } from "@/features/provider-connections/components/provider-connections-page" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" +import { routeHead } from "@/lib/routing/route-head" interface ProviderConnectionsSearch { environmentId?: string @@ -14,6 +15,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers", )({ component: ProjectProviderConnectionsRoute, + head: () => routeHead({ title: "Provider connections" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): ProviderConnectionsSearch => { const returnTo = safeInternalReturnTo(search.returnTo, "") diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId.tsx index ed4ef035..d05ffe0b 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId.tsx @@ -1,11 +1,13 @@ import { createFileRoute } from "@tanstack/react-router" import { ProviderConnectionDetailPage } from "@/features/provider-connections/components/provider-connection-detail-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers/$connectionId", )({ component: ProjectProviderConnectionDetailRoute, + head: () => routeHead({ title: "Provider connection" }), }) function ProjectProviderConnectionDetailRoute() { diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index.tsx index b22fef9b..075a8221 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/index.tsx @@ -3,13 +3,15 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { ProjectOverviewPage } from "@/features/projects/components/project-overview-page" +import { routeHead } from "@/lib/routing/route-head" -export const Route = createFileRoute("/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/")( - { - component: ProjectRoute, - pendingComponent: RoutePendingState, - }, -) +export const Route = createFileRoute( + "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/", +)({ + component: ProjectRoute, + head: () => routeHead({ title: "Project overview" }), + pendingComponent: RoutePendingState, +}) function ProjectRoute() { const { organizationId, projectId } = Route.useParams() diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets.tsx index 5d589435..4ecefb1e 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets.tsx @@ -5,6 +5,7 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { AssetsPage } from "@/features/assets/components/assets-page" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" interface AssetsRouteSearch { returnTo?: string @@ -14,6 +15,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/assets", )({ component: RouteComponent, + head: () => routeHead({ title: "Assets" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): AssetsRouteSearch => { const returnTo = safeInternalReturnTo(search.returnTo, "") diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId.tsx index 538c82f4..679ca30d 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId.tsx @@ -1,10 +1,14 @@ import { createFileRoute } from "@tanstack/react-router" import { ExperimentWorkspace } from "@/features/experiments/components/experiment-workspace" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/$experimentId", -)({ component: RouteComponent }) +)({ + component: RouteComponent, + head: () => routeHead({ title: "Experiment" }), +}) function RouteComponent() { return diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/index.tsx index fe583657..4adfc01e 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/index.tsx @@ -3,10 +3,15 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { ExperimentsPage } from "@/features/experiments/components/experiments-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/", -)({ component: RouteComponent, pendingComponent: RoutePendingState }) +)({ + component: RouteComponent, + head: () => routeHead({ title: "Experiments" }), + pendingComponent: RoutePendingState, +}) function RouteComponent() { return diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new.tsx index 89d88a12..d6b67962 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new.tsx @@ -1,10 +1,14 @@ import { createFileRoute } from "@tanstack/react-router" import { NewExperimentPage } from "@/features/experiments/components/new-experiment-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new", -)({ component: RouteComponent }) +)({ + component: RouteComponent, + head: () => routeHead({ title: "New experiment" }), +}) function RouteComponent() { return diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId.tsx index 57f99532..0ee8050d 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId.tsx @@ -2,11 +2,13 @@ import { createFileRoute } from "@tanstack/react-router" import { PaywallDetailPage } from "@/features/paywalls/components/paywall-detail-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId", )({ component: RouteComponent, + head: () => routeHead({ title: "Paywall" }), }) function RouteComponent() { diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/index.tsx index e0455009..2c10e652 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/index.tsx @@ -4,11 +4,13 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { PaywallsPage } from "@/features/paywalls/components/paywalls-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/", )({ component: RouteComponent, + head: () => routeHead({ title: "Paywalls" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId.tsx index c5b68e61..2eb1fe1a 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId.tsx @@ -1,10 +1,14 @@ import { createFileRoute } from "@tanstack/react-router" import { PlacementDecisionPage } from "@/features/placement-decisions/components/placement-decision-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/$placementId", -)({ component: PlacementDecisionRoute }) +)({ + component: PlacementDecisionRoute, + head: () => routeHead({ title: "Placement" }), +}) function PlacementDecisionRoute() { const params = Route.useParams() diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/index.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/index.tsx index 03f2829d..a254906f 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/index.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/index.tsx @@ -4,11 +4,13 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { PlacementsPage } from "@/features/placements/components/placements-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/placements/", )({ component: RouteComponent, + head: () => routeHead({ title: "Placements" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases.tsx index 99748256..04094725 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases.tsx @@ -4,11 +4,13 @@ import { RoutePendingState } from "@/components/feedback/route-feedback" import { ReleaseHistoryPage } from "@/features/releases/components/release-history-page" import { useRouteEnvironment } from "@/features/environments/hooks/use-route-environment" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/releases", )({ component: RouteComponent, + head: () => routeHead({ title: "Releases" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys.tsx index 1ba61ec8..8cf82ee5 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys.tsx @@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { ApiKeysPage } from "@/features/api-keys/components/api-keys-page" +import { routeHead } from "@/lib/routing/route-head" interface ApiKeysSearch { environmentId?: string @@ -12,6 +13,7 @@ export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/api-keys", )({ component: ProjectApiKeysRoute, + head: () => routeHead({ title: "API keys" }), pendingComponent: RoutePendingState, validateSearch: (search: Record): ApiKeysSearch => ({ environmentId: diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments.tsx index 9746db61..66cfcae4 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments.tsx @@ -3,11 +3,13 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { EnvironmentsPage } from "@/features/environments/components/environments-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute( "/_hosted/orgs/$organizationId/projects/$projectId/env/$environmentKey/settings/environments", )({ component: ProjectEnvironmentsRoute, + head: () => routeHead({ title: "Environments" }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/new.tsx b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/new.tsx index d37e1acc..099ca856 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/new.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/$organizationId/projects/new.tsx @@ -1,9 +1,11 @@ import { createFileRoute } from "@tanstack/react-router" import { CreateProjectPage } from "@/features/projects/components/create-project-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute("/_hosted/orgs/$organizationId/projects/new")({ component: NewProjectRoute, + head: () => routeHead({ title: "New project" }), }) function NewProjectRoute() { diff --git a/apps/dashboard/src/routes/_hosted/orgs/new.tsx b/apps/dashboard/src/routes/_hosted/orgs/new.tsx index e81adfba..49ef5494 100644 --- a/apps/dashboard/src/routes/_hosted/orgs/new.tsx +++ b/apps/dashboard/src/routes/_hosted/orgs/new.tsx @@ -1,7 +1,9 @@ import { createFileRoute } from "@tanstack/react-router" import { CreateOrganizationPage } from "@/features/orgs/components/create-organization-page" +import { routeHead } from "@/lib/routing/route-head" export const Route = createFileRoute("/_hosted/orgs/new")({ component: CreateOrganizationPage, + head: () => routeHead({ title: "New organization" }), }) diff --git a/apps/dashboard/src/routes/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId.tsx b/apps/dashboard/src/routes/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId.tsx index 42f2e982..1559dfbd 100644 --- a/apps/dashboard/src/routes/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId.tsx +++ b/apps/dashboard/src/routes/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId.tsx @@ -1,5 +1,7 @@ import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router" +import { routeHead } from "@/lib/routing/route-head" + interface HostedStudioSearch { review?: "publish" } @@ -13,6 +15,7 @@ export const Route = createFileRoute( "/_studio_layout/studio/$organizationId/$projectId/$environmentId/$paywallId/$draftId", )({ component: RouteComponent, + head: () => routeHead({ title: "Paywall editor" }), validateSearch: (search: Record): HostedStudioSearch => ({ review: search.review === "publish" ? "publish" : undefined, }), diff --git a/apps/dashboard/src/routes/_studio_layout/studio/index.tsx b/apps/dashboard/src/routes/_studio_layout/studio/index.tsx index 9472dcbe..02e3e3ea 100644 --- a/apps/dashboard/src/routes/_studio_layout/studio/index.tsx +++ b/apps/dashboard/src/routes/_studio_layout/studio/index.tsx @@ -1,5 +1,7 @@ import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router" +import { routeHead } from "@/lib/routing/route-head" + const PaywallEditorWorkspace = lazyRouteComponent( () => import("@/features/paywall-editor/components/paywall-editor-workspace"), "PaywallEditorWorkspace", @@ -7,4 +9,5 @@ const PaywallEditorWorkspace = lazyRouteComponent( export const Route = createFileRoute("/_studio_layout/studio/")({ component: PaywallEditorWorkspace, + head: () => routeHead({ title: "Paywall editor" }), }) diff --git a/apps/dashboard/src/routes/diagnostics.tsx b/apps/dashboard/src/routes/diagnostics.tsx index 5840ccf7..073bb594 100644 --- a/apps/dashboard/src/routes/diagnostics.tsx +++ b/apps/dashboard/src/routes/diagnostics.tsx @@ -2,6 +2,7 @@ import { createFileRoute } from "@tanstack/react-router" import { RoutePendingState } from "@/components/feedback/route-feedback" import { DiagnosticsPage } from "@/features/diagnostics/components/diagnostics-page" +import { routeHead } from "@/lib/routing/route-head" /** * Deliberately outside `_hosted`: this page holds no tenant data and is most @@ -9,5 +10,10 @@ import { DiagnosticsPage } from "@/features/diagnostics/components/diagnostics-p */ export const Route = createFileRoute("/diagnostics")({ component: DiagnosticsPage, + head: () => + routeHead({ + description: "Inspect dashboard build, session, and API connectivity.", + title: "Diagnostics", + }), pendingComponent: RoutePendingState, }) diff --git a/apps/dashboard/src/routes/login.tsx b/apps/dashboard/src/routes/login.tsx index e105467b..b4b027dc 100644 --- a/apps/dashboard/src/routes/login.tsx +++ b/apps/dashboard/src/routes/login.tsx @@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import { AuthPageShell } from "@/features/auth/components/auth-page-shell" import { LoginForm } from "@/features/auth/components/login-form" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" +import { routeHead } from "@/lib/routing/route-head" interface AuthRouteSearch { returnTo?: string @@ -10,6 +11,11 @@ interface AuthRouteSearch { export const Route = createFileRoute("/login")({ component: LoginRoute, + head: () => + routeHead({ + description: "Sign in to your Mosaic Studio workspace.", + title: "Sign in", + }), validateSearch: (search: Record): AuthRouteSearch => { const returnTo = safeInternalReturnTo(search.returnTo, "") return returnTo ? { returnTo } : {} diff --git a/apps/dashboard/src/routes/signup.tsx b/apps/dashboard/src/routes/signup.tsx index d26e91cb..94d4ec33 100644 --- a/apps/dashboard/src/routes/signup.tsx +++ b/apps/dashboard/src/routes/signup.tsx @@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router" import { AuthPageShell } from "@/features/auth/components/auth-page-shell" import { SignupForm } from "@/features/auth/components/signup-form" import { safeInternalReturnTo } from "@/features/auth/types/hosted-access" +import { routeHead } from "@/lib/routing/route-head" interface AuthRouteSearch { returnTo?: string @@ -10,6 +11,11 @@ interface AuthRouteSearch { export const Route = createFileRoute("/signup")({ component: SignupRoute, + head: () => + routeHead({ + description: "Create your Mosaic Studio account.", + title: "Sign up", + }), validateSearch: (search: Record): AuthRouteSearch => { const returnTo = safeInternalReturnTo(search.returnTo, "") return returnTo ? { returnTo } : {} From 0b640637464268d4195515d3483ea173cdae2993 Mon Sep 17 00:00:00 2001 From: Muhideen Mujeeb Adeoye Date: Fri, 31 Jul 2026 09:58:40 +0100 Subject: [PATCH 10/25] feat(dashboard): move create flows into dialogs and add a Select component Register application, create Plan/Product/Entitlement/Placement/Paywall, create Migration Program, add member, and the grant version wizard now open from a header action in a dialog instead of a form panel below the list. Each closes on success and resets its form and mutation error on close; the grant wizard keeps its three steps and moves from a Sheet to the dialog. Purchase setup no longer carries its own Environment picker or environmentId search param. It reads the Environment the address names, so the workspace switcher is the single place that scope changes. Replaces all 90 native selects with a Base UI Select wrapper. Two things the primitive imposes are handled once rather than at every call site: the trigger label resolves from `items`, so callers build one options list and pass it to both, and `onValueChange` is narrowed to a non-null value because nothing here uses a null-valued item. The paywall editor inspector derives its items from the SelectItem children instead, keeping its sixty-odd fields unchanged. Tests drive the new control through `chooseSelectOption`. An option commits on pointer release, so a bare click leaves the value untouched, and options only exist in the DOM while the list is open. --- apps/dashboard/src/components/ui/dialog.tsx | 125 +++ apps/dashboard/src/components/ui/select.tsx | 180 ++++ .../components/analytics-filters.tsx | 71 +- .../components/data-privacy-panel.tsx | 74 +- .../api-keys/components/api-keys-page.tsx | 79 +- .../components/customer-search-form.tsx | 41 +- .../components/transaction-ledger-filters.tsx | 232 +++-- .../create-migration-program-form.test.tsx | 101 +++ .../create-migration-program-form.tsx | 212 +++++ .../components/guided-mapping-form.tsx | 225 +++++ .../migration-lifecycle-operations.tsx | 826 ++++++++++++++++++ .../migration-program-detail-page.tsx | 768 ++++++++++++++++ .../components/migration-programs-page.tsx | 181 ++++ .../create-reconciliation-run-sheet.tsx | 75 +- .../components/quarantine-page.tsx | 93 +- .../catalog/components/entitlements-page.tsx | 136 +-- .../catalog/components/plans-page.tsx | 133 +-- .../components/product-detail-page.tsx | 145 +-- .../catalog/components/products-page.tsx | 304 ++++--- .../components/grant-versions-page.tsx | 97 +- .../publish-grant-version-wizard.tsx | 109 ++- .../components/experiment-builder.tsx | 189 ++-- .../components/experiment-workspace.tsx | 78 +- .../mutual-exclusion-group-manager.tsx | 41 +- .../members/components/members-page.tsx | 146 +++- .../components/design-system-controls.tsx | 137 ++- .../components/design-system-panel.tsx | 65 +- .../components/mock-commerce-panel.test.tsx | 23 +- .../components/mock-commerce-panel.tsx | 161 ++-- .../components/preview-canvas.test.tsx | 26 +- .../components/preview-controls.test.tsx | 7 +- .../components/preview-controls.tsx | 172 ++-- .../property-inspector-accessibility.tsx | 5 +- .../property-inspector-background.tsx | 31 +- .../property-inspector-basic-nodes.tsx | 19 +- .../property-inspector-controls.tsx | 37 +- .../components/property-inspector-fields.tsx | 58 +- .../components/property-inspector-layout.tsx | 80 +- .../property-inspector-product-styles.tsx | 13 +- .../property-inspector-products.tsx | 17 +- .../property-inspector.navigation.test.tsx | 14 +- .../components/property-inspector.test.tsx | 22 +- .../studio-automated-workflow.test.tsx | 22 +- .../components/studio-tool-panel.test.tsx | 5 +- .../components/studio-tool-panel.tsx | 91 +- .../components/create-paywall-draft-form.tsx | 12 +- .../paywalls/components/paywalls-page.tsx | 64 +- .../components/attribute-definitions.tsx | 75 +- .../components/condition-editor.tsx | 145 +-- .../components/decision-simulator.tsx | 161 ++-- .../components/outcome-editor.tsx | 136 ++- .../components/placement-decision-page.tsx | 49 +- .../placement-decisions-risk.test.tsx | 25 +- .../components/qa-overrides.tsx | 65 +- .../placements/components/placements-page.tsx | 285 +++--- .../projects/components/applications-page.tsx | 161 ++-- .../components/active-provider-matrix.tsx | 50 +- .../components/connect-revenuecat-sheet.tsx | 35 +- .../components/provider-catalog-import.tsx | 249 ++++-- .../components/provider-connections-page.tsx | 81 +- .../types/provider-connection-view.test.ts | 19 - .../types/provider-connection-view.ts | 8 - .../connect-store-credential-sheet.tsx | 105 ++- .../env/$environmentKey/catalog/providers.tsx | 12 +- apps/dashboard/src/test/select.ts | 21 + 65 files changed, 5661 insertions(+), 1763 deletions(-) create mode 100644 apps/dashboard/src/components/ui/dialog.tsx create mode 100644 apps/dashboard/src/components/ui/select.tsx create mode 100644 apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.test.tsx create mode 100644 apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.tsx create mode 100644 apps/dashboard/src/features/billing-migrations/components/guided-mapping-form.tsx create mode 100644 apps/dashboard/src/features/billing-migrations/components/migration-lifecycle-operations.tsx create mode 100644 apps/dashboard/src/features/billing-migrations/components/migration-program-detail-page.tsx create mode 100644 apps/dashboard/src/features/billing-migrations/components/migration-programs-page.tsx create mode 100644 apps/dashboard/src/test/select.ts diff --git a/apps/dashboard/src/components/ui/dialog.tsx b/apps/dashboard/src/components/ui/dialog.tsx new file mode 100644 index 00000000..5b301d85 --- /dev/null +++ b/apps/dashboard/src/components/ui/dialog.tsx @@ -0,0 +1,125 @@ +"use client" + +import * as React from "react" +import { Dialog as DialogPrimitive } from "@base-ui/react/dialog" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "@phosphor-icons/react/dist/ssr/X" + +function Dialog({ ...props }: DialogPrimitive.Root.Props) { + return +} + +function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) { + return +} + +function DialogClose({ ...props }: DialogPrimitive.Close.Props) { + return +} + +function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) { + return +} + +function DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: DialogPrimitive.Popup.Props & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + } + > + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { + return ( + + ) +} + +function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) { + return ( + + ) +} + +export { + Dialog, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/apps/dashboard/src/components/ui/select.tsx b/apps/dashboard/src/components/ui/select.tsx new file mode 100644 index 00000000..b5556da3 --- /dev/null +++ b/apps/dashboard/src/components/ui/select.tsx @@ -0,0 +1,180 @@ +"use client" + +import * as React from "react" +import { Select as SelectPrimitive } from "@base-ui/react/select" + +import { cn } from "@/lib/utils" +import { CaretUpDownIcon } from "@phosphor-icons/react/dist/ssr/CaretUpDown" +import { CheckIcon } from "@phosphor-icons/react/dist/ssr/Check" + +/** + * Base UI resolves the trigger's label from `items`, not from the rendered + * `SelectItem` children, so a caller whose option text differs from its value + * builds the list once and passes it to both. + */ +export interface SelectOption { + label: string + value: Value +} + +type SelectChangeDetails = Parameters< + NonNullable["onValueChange"]> +>[1] + +/** + * Base UI reports `null` only when an item whose own value is `null` is chosen, + * which is how it models a clearable select. Mosaic spells "nothing chosen" as + * an empty-string item instead, so the callback is narrowed here rather than at + * every call site. Introducing a null-valued item means widening this again. + */ +export type SelectRootProps = Omit< + SelectPrimitive.Root.Props, + "onValueChange" +> & { + onValueChange?: ( + value: Multiple extends true ? Value[] : Value, + eventDetails: SelectChangeDetails, + ) => void +} + +function Select({ + onValueChange, + ...props +}: SelectRootProps) { + return ( + ["onValueChange"]} + {...props} + /> + ) +} + +function SelectGroup({ ...props }: SelectPrimitive.Group.Props) { + return +} + +function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { + return ( + + ) +} + +function SelectTrigger({ + className, + children, + size = "default", + ...props +}: SelectPrimitive.Trigger.Props & { size?: "default" | "sm" }) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + sideOffset = 4, + ...props +}: SelectPrimitive.Popup.Props & { + sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"] +}) { + return ( + + + + {children} + + + + ) +} + +function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) { + return ( + + ) +} + +function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) { + return ( + + + {children} + + + + + + ) +} + +function SelectSeparator({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/apps/dashboard/src/features/analytics/components/analytics-filters.tsx b/apps/dashboard/src/features/analytics/components/analytics-filters.tsx index 7fb7b343..679f9d18 100644 --- a/apps/dashboard/src/features/analytics/components/analytics-filters.tsx +++ b/apps/dashboard/src/features/analytics/components/analytics-filters.tsx @@ -1,6 +1,28 @@ import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import type { AnalyticsFilters as Filters } from "../types/analytics" +const TIMEZONE_OPTIONS = [ + { label: "UTC", value: "UTC" }, + { label: "America/Los Angeles", value: "America/Los_Angeles" }, + { label: "America/New York", value: "America/New_York" }, + { label: "Europe/London", value: "Europe/London" }, + { label: "Africa/Lagos", value: "Africa/Lagos" }, + { label: "Asia/Tokyo", value: "Asia/Tokyo" }, +] + +const PLATFORM_OPTIONS = [ + { label: "All platforms", value: "" }, + { label: "iOS", value: "ios" }, + { label: "Android", value: "android" }, +] + interface Props { filters: Filters onChange: (filters: Filters) => void @@ -29,25 +51,36 @@ export function AnalyticsFilters({ filters, onChange }: Props) { > - - +
+ + +
+
+ + +
({ + label: `${days} days${days === 180 ? " (default)" : ""}`, + value: String(days), +})) + +const IDENTITY_SCOPE_OPTIONS = [ + { label: "Application user", value: "application_user" }, + { label: "Installation", value: "installation" }, +] + export function DataPrivacyPanel({ adapter, role, @@ -88,21 +105,26 @@ function CollectionSettingsPanel({ - + + + + + {RETENTION_OPTIONS.map((option) => ( + + {option.label} + + ))} + + +

Exact aggregates and privacy audit metadata are retained for 24 months. Retention changes apply to raw events received after the setting is saved. @@ -240,19 +262,25 @@ function IdentityOperationsPanel({ > {(field) => ( - + + + + + {IDENTITY_SCOPE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + +

)} diff --git a/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx b/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx index 73ab0aeb..de27f32e 100644 --- a/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx +++ b/apps/dashboard/src/features/api-keys/components/api-keys-page.tsx @@ -4,6 +4,13 @@ import { useNavigate } from "@tanstack/react-router" import { useState } from "react" import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { Sheet, SheetContent, @@ -57,6 +64,17 @@ export function ApiKeysPage({ environmentId, organizationId, projectId }: ApiKey environments.data?.items.find((item) => item.id === environmentId) ?? environments.data?.items.find((item) => item.key === "development") ?? environments.data?.items[0] + const environmentOptions = (environments.data?.items ?? []).map((environment) => ({ + label: environment.name, + value: environment.id, + })) + const applicationOptions = [ + { label: "Select an Application", value: "" }, + ...(applications.data?.items ?? []).map((application) => ({ + label: `${application.name} · ${application.platform}`, + value: application.id, + })), + ] const keys = useQuery({ ...apiKeysQueryOptions(selectedEnvironment?.id ?? ""), enabled: scopeReady && Boolean(selectedEnvironment), @@ -129,28 +147,33 @@ export function ApiKeysPage({ environmentId, organizationId, projectId }: ApiKey title="API keys" > - + + + + + {environmentOptions.map((option) => ( + + {option.label} + + ))} + + +
{revealed ? : null} @@ -276,25 +299,29 @@ export function ApiKeysPage({ environmentId, organizationId, projectId }: ApiKey title="Create API key" >
- +
- + + + + + {applicationOptions.map((option) => ( + + {option.label} + + ))} + + +
- + + + + + {productOptions.map((option) => ( + + {option.label} + + ))} + + +
- + + + + + {resolutionOptions.map((option) => ( + + {option.label} + + ))} + + + - + + + + + {pageSizeOptions.map((option) => ( + + {option.label} + + ))} + + +
diff --git a/apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.test.tsx b/apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.test.tsx new file mode 100644 index 00000000..903cebba --- /dev/null +++ b/apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.test.tsx @@ -0,0 +1,101 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react" +import { describe, expect, it, vi } from "vitest" + +import { CreateMigrationProgramForm } from "@/features/billing-migrations/components/create-migration-program-form" +import { chooseSelectOption } from "@/test/select" +import type { Application, BillingMigrationProgram, Environment } from "@/generated/api" + +const application: Application = { + createdAt: "2026-07-29T00:00:00Z", + id: "app_ios", + identifier: "dev.mosaic.app", + name: "Mosaic iOS", + platform: "ios", + projectId: "project_1", + updatedAt: "2026-07-29T00:00:00Z", +} +const environment: Environment = { + createdAt: "2026-07-29T00:00:00Z", + id: "env_1", + key: "production", + mode: "production", + name: "Production", + projectId: "project_1", + updatedAt: "2026-07-29T00:00:00Z", +} +const program: BillingMigrationProgram = { + authorityEpochBefore: 0, + programId: "program_1", + rollbackWindowDays: 7, + scope: { + applications: [{ applicationId: application.id, platform: "ios" }], + environmentId: environment.id, + projectId: "project_1", + }, + source: { adapter: "revenuecat", adapterVersion: "v2", credentialReference: "credential_1" }, + stabilizationDays: 7, + state: "mapping", + stateVersion: 1, +} + +async function fillRequiredFields() { + await chooseSelectOption(screen.getByLabelText("Environment"), environment.name) + fireEvent.change(screen.getByLabelText("RevenueCat Project ID"), { + target: { value: "rc_project" }, + }) + fireEvent.change(screen.getByLabelText("RevenueCat migration API key"), { + target: { value: "rc_secret" }, + }) + fireEvent.click(screen.getByLabelText("Mosaic iOS · ios")) +} + +describe("CreateMigrationProgramForm", () => { + it("clears the secret after success and suppresses a double submission with one command key", async () => { + let resolve!: (value: BillingMigrationProgram) => void + const commandKeys: string[] = [] + const onCreate = vi.fn((command: { idempotencyKey: string }) => { + commandKeys.push(command.idempotencyKey) + return new Promise((done) => { + resolve = done + }) + }) + render( + , + ) + await fillRequiredFields() + fireEvent.click(screen.getByRole("button", { name: "Create and check source" })) + fireEvent.click(screen.getByRole("button", { name: "Create and check source" })) + await waitFor(() => expect(onCreate).toHaveBeenCalledOnce()) + expect(commandKeys).toHaveLength(1) + expect(commandKeys[0]).toBeTruthy() + resolve(program) + await waitFor(() => + expect(screen.getByLabelText("RevenueCat migration API key")).toHaveValue(""), + ) + }) + + it("clears the secret after failure and renders Mosaic-owned recovery copy", async () => { + render( + Promise.reject(new Error("raw provider stack"))} + onCreated={vi.fn()} + resetMutation={vi.fn()} + />, + ) + await fillRequiredFields() + fireEvent.click(screen.getByRole("button", { name: "Create and check source" })) + expect(await screen.findByRole("alert")).toHaveTextContent("Mosaic could not check this source") + expect(screen.getByRole("alert")).not.toHaveTextContent("raw provider stack") + expect(screen.getByLabelText("RevenueCat migration API key")).toHaveValue("") + }) +}) diff --git a/apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.tsx b/apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.tsx new file mode 100644 index 00000000..e1312477 --- /dev/null +++ b/apps/dashboard/src/features/billing-migrations/components/create-migration-program-form.tsx @@ -0,0 +1,212 @@ +import { useForm } from "@tanstack/react-form" +import { useRef, useState } from "react" + +import { Button } from "@/components/ui/button" +import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field" +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { createMigrationCommandKey } from "@/features/billing-migrations/mutations/migration-mutations" +import { migrationErrorCopy } from "@/features/billing-migrations/types/migration-operations" +import type { + Application, + BillingMigrationProgram, + CreateBillingMigrationProgramRequestWritable, + Environment, +} from "@/generated/api" + +interface Props { + applications: readonly Application[] + environments: readonly Environment[] + isPending: boolean + onCreate: (command: { + body: CreateBillingMigrationProgramRequestWritable + idempotencyKey: string + }) => Promise + onCreated: (program: BillingMigrationProgram) => void + resetMutation: () => void +} + +export function CreateMigrationProgramForm({ + applications, + environments, + isPending, + onCreate, + onCreated, + resetMutation, +}: Props) { + const environmentOptions = [ + { label: "Choose an Environment", value: "" }, + ...environments.map((item) => ({ label: item.name, value: item.id })), + ] + const submitting = useRef(false) + const [submitError, setSubmitError] = useState(null) + const form = useForm({ + defaultValues: { + applicationIds: [] as string[], + environmentId: "", + revenueCatApiKey: "", + revenueCatProjectId: "", + rollbackWindowDays: 7, + stabilizationDays: 7, + }, + onSubmit: async ({ value }) => { + if (submitting.current) return + submitting.current = true + setSubmitError(null) + try { + const program = await onCreate({ + body: { + applications: value.applicationIds.map((applicationId) => { + const application = applications.find((item) => item.id === applicationId) + if (!application) throw new Error("invalid_scope") + return { applicationId, platform: application.platform } + }), + environmentId: value.environmentId, + revenueCatApiKey: value.revenueCatApiKey, + revenueCatProjectId: value.revenueCatProjectId.trim(), + rollbackWindowDays: value.rollbackWindowDays, + stabilizationDays: value.stabilizationDays, + }, + idempotencyKey: createMigrationCommandKey(), + }) + form.reset() + onCreated(program) + } catch (error) { + setSubmitError(migrationErrorCopy(error, "create")) + } finally { + form.setFieldValue("revenueCatApiKey", "") + resetMutation() + submitting.current = false + } + }, + }) + + return ( +
{ + event.preventDefault() + void form.handleSubmit() + }} + > + (value ? undefined : "Choose an Environment.") }} + > + {(field) => ( + 0}> + Environment + + ({ message }))} /> + + )} + + (value.trim() ? undefined : "Enter the RevenueCat Project ID."), + }} + > + {(field) => ( + 0}> + RevenueCat Project ID + field.handleChange(event.target.value)} + value={field.state.value} + /> + ({ message }))} /> + + )} + + (value ? undefined : "Enter a least-privilege migration key."), + }} + > + {(field) => ( + 0}> + RevenueCat migration API key + field.handleChange(event.target.value)} + type="password" + value={field.state.value} + /> + Cleared after every submission and never read back. + ({ message }))} /> + + )} + + + value.length ? undefined : "Select at least one Application/platform scope.", + }} + > + {(field) => ( + 0}> + Application/platform scope + + Choose every Application and platform explicitly. Mosaic does not infer or wildcard + this scope. + +
+ {applications.map((application) => ( + + ))} +
+ ({ message }))} /> +
+ )} +
+ {submitError ? ( +

+ {submitError} +

+ ) : null} +
+ +
+
+ ) +} diff --git a/apps/dashboard/src/features/billing-migrations/components/guided-mapping-form.tsx b/apps/dashboard/src/features/billing-migrations/components/guided-mapping-form.tsx new file mode 100644 index 00000000..d6613929 --- /dev/null +++ b/apps/dashboard/src/features/billing-migrations/components/guided-mapping-form.tsx @@ -0,0 +1,225 @@ +import { useMemo, useState } from "react" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import type { BillingMigrationMappingEntry } from "@/generated/api" + +type MappingEntry = BillingMigrationMappingEntry +const sourceKinds: MappingEntry["sourceKind"][] = [ + "customer_id", + "original_customer_id", + "audited_alias", + "product", + "entitlement", +] +const sourceKindOptions = sourceKinds.map((kind) => ({ + label: kind.replaceAll("_", " "), + value: kind, +})) + +const MATCH_KIND_OPTIONS = [ + { label: "Exact", value: "exact" }, + { label: "Audited alias", value: "audited_alias" }, +] +const emptyRow = (): MappingEntry => ({ + matchKind: "exact", + sourceIdentifier: "", + sourceKind: "product", + targetId: "", +}) + +interface Props { + isPending: boolean + onCreate: (entries: MappingEntry[]) => Promise +} + +export function GuidedMappingForm({ isPending, onCreate }: Props) { + const [entries, setEntries] = useState([emptyRow()]) + const [reviewing, setReviewing] = useState(false) + const errors = useMemo( + () => + entries.map((entry) => ({ + source: entry.sourceIdentifier.trim() ? "" : "Enter the exact source identifier.", + target: entry.targetId.trim() ? "" : "Enter the Mosaic target ID.", + })), + [entries], + ) + const valid = errors.every((entry) => !entry.source && !entry.target) + + function update(index: number, patch: Partial) { + setReviewing(false) + setEntries((current) => + current.map((entry, entryIndex) => (entryIndex === index ? { ...entry, ...patch } : entry)), + ) + } + + return ( +
+

+ Enter source identifiers from normalized, assessed source evidence only. Source-row browsing + and Mosaic target pickers are deferred until verified manifest ingestion and list APIs are + available; do not infer or transform identifiers here. +

+
+ {entries.map((entry, index) => ( +
+ Mapping {index + 1} +
+
+ Source kind + +
+ +
+ Match + +
+ +
+ {entries.length > 1 ? ( + + ) : null} +
+ ))} +
+
+ + +
+ {reviewing ? ( +
+

+ Review before creating +

+

+ This draft contains {entries.length} explicit mapping(s). Review every source and target + before creating an immutable version later. +

+
    + {entries.map((entry, index) => ( +
  • + {entry.sourceKind.replaceAll("_", " ")} {entry.sourceIdentifier} →{" "} + {entry.targetId} ({entry.matchKind.replaceAll("_", " ")}) +
  • + ))} +
+ +
+ ) : null} +
+ ) +} diff --git a/apps/dashboard/src/features/billing-migrations/components/migration-lifecycle-operations.tsx b/apps/dashboard/src/features/billing-migrations/components/migration-lifecycle-operations.tsx new file mode 100644 index 00000000..df3a4931 --- /dev/null +++ b/apps/dashboard/src/features/billing-migrations/components/migration-lifecycle-operations.tsx @@ -0,0 +1,826 @@ +import { useMutation, type QueryClient } from "@tanstack/react-query" +import { useState } from "react" + +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { StatusPill } from "@/features/billing-ledger/components/billing-chrome" +import { MigrationImpactReviewAction } from "@/features/billing-migrations/components/migration-impact-review-action" +import { + createMigrationCommandKey, + migrationLifecycleMutationOptions, +} from "@/features/billing-migrations/mutations/migration-mutations" +import { + canRunMigrationCommand, + migrationCompletionBlockers, + type MigrationCommandCapability, + type MigrationProgramView, +} from "@/features/billing-migrations/types/migration-operations" +import type { + BillingMigrationApproval, + BillingMigrationAuthorityExecution, + BillingMigrationCase, + BillingMigrationCheckpoint, + BillingMigrationCompletionPrerequisites, + BillingMigrationCompletionReport, + BillingMigrationCredentialRemoval, + BillingMigrationDigestSet, + BillingMigrationLegalHold, + BillingMigrationLegalHoldProposal, + BillingMigrationProposal, + BillingMigrationRepairExecution, + BillingMigrationRepairPreview, + BillingMigrationRepairPreviewRequest, + BillingMigrationRollbackReadinessAssessment, + BillingMigrationStabilizationObservation, + BillingMigrationWebhookRedelivery, +} from "@/generated/api" + +type LifecycleRecord = + | BillingMigrationApproval + | BillingMigrationAuthorityExecution + | BillingMigrationCase + | BillingMigrationCheckpoint + | BillingMigrationCompletionReport + | BillingMigrationCredentialRemoval + | BillingMigrationLegalHold + | BillingMigrationLegalHoldProposal + | BillingMigrationProposal + | BillingMigrationRepairExecution + | BillingMigrationRepairPreview + | BillingMigrationRollbackReadinessAssessment + | BillingMigrationStabilizationObservation + | BillingMigrationWebhookRedelivery +type RepairKind = BillingMigrationRepairPreviewRequest["repairKind"] + +interface LifecycleData { + approvals: BillingMigrationApproval[] + cases: BillingMigrationCase[] + checkpoints: BillingMigrationCheckpoint[] + completion: BillingMigrationCompletionPrerequisites | undefined + executions: BillingMigrationAuthorityExecution[] + holdProposals: BillingMigrationLegalHoldProposal[] + holds: BillingMigrationLegalHold[] + observations: BillingMigrationStabilizationObservation[] + proposals: BillingMigrationProposal[] + removals: BillingMigrationCredentialRemoval[] + repairExecutions: BillingMigrationRepairExecution[] + repairPreviews: BillingMigrationRepairPreview[] + reports: BillingMigrationCompletionReport[] + rollbackAssessments: BillingMigrationRollbackReadinessAssessment[] + webhookRedeliveries: BillingMigrationWebhookRedelivery[] +} +type CommandName = + | "propose_cutover" + | "approve_cutover" + | "checkpoint" + | "execute_cutover" + | "observe_stabilization" + | "propose_rollback" + | "approve_rollback" + | "execute_rollback" + | "preview_repair" + | "execute_repair" + | "redeliver_webhook" + | "remove_credential" + | "propose_legal_hold" + | "approve_legal_hold" + | "complete" + +const commandCapability: Record = { + approve_cutover: "approve-cutover", + approve_legal_hold: "manage-legal-hold", + approve_rollback: "execute-rollback", + checkpoint: "execute-cutover", + complete: "complete-migration", + execute_cutover: "execute-cutover", + execute_repair: "execute-repair", + execute_rollback: "execute-rollback", + observe_stabilization: "execute-rollback", + preview_repair: "execute-repair", + propose_cutover: "propose-cutover", + propose_legal_hold: "manage-legal-hold", + propose_rollback: "execute-rollback", + redeliver_webhook: "execute-repair", + remove_credential: "remove-credential", +} + +const digestNames = [ + "scope", + "manifest", + "mapping", + "policy", + "evidence", + "readiness", + "finalWatermark", + "applicationVersion", +] as const + +const emptyDigests = Object.fromEntries( + digestNames.map((name) => [name, ""]), +) as BillingMigrationDigestSet + +const impact: Record = { + approve_cutover: + "Attests to another human's production proposal; it does not change authority by itself.", + approve_legal_hold: + "Applies an approved retention hold command and changes deletion eligibility.", + approve_rollback: + "Attests to another human's rollback proposal; execution creates a newer source-authority epoch.", + checkpoint: "Freezes the approved cohort and rollback baseline; stale evidence invalidates it.", + complete: "Closes stabilization only when the server confirms every completion prerequisite.", + execute_cutover: + "Atomically changes access authority from the source to Mosaic and increments the authority epoch.", + execute_repair: + "Executes only the allowlisted, bounded repair represented by the unexpired preview.", + execute_rollback: + "Creates a newer source-rollback authority epoch; it never deletes Mosaic evidence.", + observe_stabilization: + "Records server-derived health evidence; caller-supplied metrics are not accepted.", + preview_repair: + "Creates a bounded impact preview. It cannot edit facts, snapshots, or access directly.", + propose_cutover: + "Creates an expiring proposal bound to the exact evidence digests; authority is unchanged.", + propose_legal_hold: + "Proposes changing evidence retention; production requires a distinct approver.", + propose_rollback: + "Creates an expiring rollback proposal bound to checkpoint and prerequisite digests.", + redeliver_webhook: + "Redelivers one stored event to one stored destination without accepting payloads or secrets.", + remove_credential: + "Irreversibly destroys migration credential material and may make rollback unhealthy.", +} + +function repairKind(value: string): RepairKind | undefined { + const values: RepairKind[] = [ + "provider_revalidate", + "projection_replay", + "attach_proven_alias", + "replace_mapping_set", + "retry_quarantined_record", + ] + return values.find((candidate) => candidate === value) +} + +function requireRepairKind(value: string): RepairKind { + const parsed = repairKind(value) + if (!parsed) throw new Error("Select a supported repair kind.") + return parsed +} + +function timelineRecord(record: LifecycleRecord) { + if ("approvalId" in record) + return { id: record.approvalId, title: `${record.command} approval`, time: record.approvedAt } + if ("classification" in record) + return { + id: record.caseId, + reason: record.reason, + status: record.status, + title: `${record.classification} reconciliation case`, + time: record.updatedAt, + } + if ("checkpointId" in record) + return { id: record.checkpointId, title: "migration checkpoint", time: record.createdAt } + if ("executionId" in record && "command" in record) + return { + id: record.executionId, + status: record.state, + title: `${record.command} authority execution`, + time: record.executedAt, + } + if ("holdId" in record) + return { + id: record.holdId, + reason: record.reason, + title: `${record.command} legal hold`, + time: record.commandedAt, + } + if ("observationId" in record && "metrics" in record) + return { + id: record.observationId, + status: record.healthy ? "healthy" : "breached", + title: "stabilization observation", + time: record.observedAt, + } + if ("removalId" in record) + return { + id: record.removalId, + reason: record.reason, + title: "credential removal", + time: record.removedAt, + } + if ("previewId" in record && "repairKind" in record) + return { + id: record.previewId, + reason: record.reason, + title: `${record.repairKind.replaceAll("_", " ")} preview`, + time: record.createdAt, + } + if ("executionId" in record) + return { + id: record.executionId, + status: record.executionStatus === "completed" ? record.result : "pending", + title: "repair execution", + time: record.executionStatus === "completed" ? record.executedAt : record.reservedAt, + } + if ("reportId" in record) + return { id: record.reportId, title: "migration completion", time: record.completedAt } + if ("assessmentId" in record) + return { + id: record.assessmentId, + status: record.ready ? "ready" : "blocked", + title: "rollback readiness assessment", + time: record.assessedAt, + } + if ("redeliveryId" in record) + return { + id: record.redeliveryId, + reason: record.reason, + title: "webhook redelivery", + time: record.createdAt, + } + if ("externalComplianceReference" in record) + return { + id: record.proposalId, + reason: record.reason, + status: record.status, + title: `${record.command} legal hold proposal`, + time: record.proposedAt, + } + return { + id: record.proposalId, + reason: record.reason, + status: record.status, + title: `${record.command} proposal`, + time: record.proposedAt, + } +} + +function Timeline({ records }: { records: LifecycleRecord[] }) { + if (records.length === 0) return

No records yet.

+ return ( +
    + {records.map((record) => { + const item = timelineRecord(record) + return ( +
  • +
    +

    {item.title}

    +

    + {item.id} · {new Date(item.time).toLocaleString()} +

    + {item.reason ?

    {item.reason}

    : null} +
    + +
  • + ) + })} +
+ ) +} + +export function MigrationLifecycleOperations({ + data, + detail, + organizationRole, + projectId, + programId, + queryClient, +}: { + data: LifecycleData | undefined + detail: MigrationProgramView + organizationRole?: string + projectId: string + programId: string + queryClient: QueryClient +}) { + const program = detail.program + const mutation = useMutation(migrationLifecycleMutationOptions(projectId, programId, queryClient)) + const [name, setName] = useState("propose_cutover") + const [reason, setReason] = useState("") + const [referenceId, setReferenceId] = useState("") + const [secondaryId, setSecondaryId] = useState("") + const [expectedDigest, setExpectedDigest] = useState("") + const [authorityDigest, setAuthorityDigest] = useState("") + const [prerequisiteDigest, setPrerequisiteDigest] = useState("") + const [approvalDigest, setApprovalDigest] = useState("") + const [caseDigest, setCaseDigest] = useState("") + const [scopeKind, setScopeKind] = useState("case") + const [digests, setDigests] = useState(emptyDigests) + const [acknowledged, setAcknowledged] = useState(false) + const [expiresAt, setExpiresAt] = useState("") + const commandOptions = Object.keys(commandCapability).map((item) => ({ + label: item.replaceAll("_", " "), + value: item, + })) + const capability = commandCapability[name] + const completionBlockers = migrationCompletionBlockers(data?.completion) + const granted = canRunMigrationCommand(detail, capability) + const allDigestsPresent = digestNames.every((digestName) => digests[digestName].trim()) + const boundInputsPresent = (() => { + switch (name) { + case "propose_cutover": + return allDigestsPresent + case "approve_cutover": + case "approve_rollback": + return Boolean(referenceId) + case "checkpoint": + return Boolean(referenceId && expectedDigest && approvalDigest && allDigestsPresent) + case "execute_cutover": + return Boolean(referenceId && secondaryId && approvalDigest && allDigestsPresent) + case "observe_stabilization": + return Boolean(digests.policy) + case "propose_rollback": + return Boolean(referenceId && expectedDigest && authorityDigest && prerequisiteDigest) + case "execute_rollback": + return Boolean( + referenceId && + secondaryId && + expectedDigest && + authorityDigest && + prerequisiteDigest && + approvalDigest, + ) + case "preview_repair": + return Boolean( + referenceId && + secondaryId && + expectedDigest && + caseDigest && + digests.policy && + digests.scope, + ) + case "execute_repair": + return Boolean( + referenceId && expectedDigest && caseDigest && digests.policy && digests.scope, + ) + case "redeliver_webhook": + return Boolean(referenceId && secondaryId && expectedDigest) + case "remove_credential": + return true + case "propose_legal_hold": + return Boolean(referenceId) + case "approve_legal_hold": + return Boolean(referenceId && expectedDigest) + case "complete": + return Boolean(authorityDigest && digests.policy && expectedDigest) + } + })() + const needsReason = ![ + "approve_cutover", + "approve_rollback", + "checkpoint", + "execute_repair", + "approve_legal_hold", + "complete", + "observe_stabilization", + ].includes(name) + const disabledReason = !granted + ? `The server has not granted ${capability}. Organization role ${organizationRole ?? "unknown"} is explanatory only.` + : needsReason && reason.trim().length < 8 + ? "Enter a specific operational reason of at least 8 characters." + : !boundInputsPresent + ? "Enter every reference and expected digest required to bind this command to reviewed server state." + : name === "complete" && completionBlockers.length > 0 + ? `Completion is blocked: ${completionBlockers.join(", ")}` + : [ + "propose_cutover", + "propose_rollback", + "preview_repair", + "propose_legal_hold", + ].includes(name) && !expiresAt + ? "Enter the explicit approval or preview expiry time." + : name === "remove_credential" && !acknowledged + ? "Acknowledge that credential removal is irreversible and can prevent rollback." + : null + + async function submit() { + const expectedStateVersion = program.stateVersion + const expiration = expiresAt ? new Date(expiresAt).toISOString() : "" + const scope = { + applications: program.scope.applications, + environmentId: program.scope.environmentId, + } + const approvalCommand = { + body: { expectedStateVersion }, + kind: "approve_proposal" as const, + proposalId: referenceId, + } + const selectedRepairKind = + name === "preview_repair" ? requireRepairKind(secondaryId) : "provider_revalidate" + const command = (() => { + switch (name) { + case "propose_cutover": + return { + kind: name, + body: { + expectedDigests: digests, + expectedStateVersion, + expiresAt: expiration, + reason: reason.trim(), + }, + } + case "approve_cutover": + case "approve_rollback": + return approvalCommand + case "checkpoint": + return { + kind: "create_checkpoint" as const, + body: { + approvalDigest, + approvalId: referenceId, + cohortDigest: expectedDigest, + expectedDigests: digests, + expectedStateVersion, + }, + } + case "execute_cutover": + return { + kind: name, + body: { + approvalDigest, + approvalId: secondaryId, + checkpointId: referenceId, + expectedAuthorityEpoch: program.authorityEpochBefore, + expectedDigests: digests, + expectedStateVersion, + reason: reason.trim(), + scope, + }, + } + case "observe_stabilization": + return { + kind: name, + body: { + expectedAuthorityEpoch: program.authorityEpochAfter ?? program.authorityEpochBefore, + expectedPolicyDigest: digests.policy, + expectedStateVersion, + }, + } + case "propose_rollback": + return { + kind: name, + body: { + checkpointId: referenceId, + expectedAuthorityDigest: authorityDigest, + expectedCheckpointDigest: expectedDigest, + expectedRollbackPrerequisitesDigest: prerequisiteDigest, + expectedStateVersion, + expiresAt: expiration, + reason: reason.trim(), + }, + } + case "execute_rollback": + return { + kind: name, + body: { + approvalId: secondaryId, + checkpointId: referenceId, + expectedApprovalDigest: approvalDigest, + expectedAuthorityDigest: authorityDigest, + expectedAuthorityEpoch: program.authorityEpochAfter ?? program.authorityEpochBefore, + expectedCheckpointDigest: expectedDigest, + expectedRollbackPrerequisitesDigest: prerequisiteDigest, + expectedStateVersion, + reason: reason.trim(), + scope, + }, + } + case "preview_repair": + return { + kind: name, + body: { + caseId: referenceId, + expectedCaseDigest: caseDigest, + expectedPolicyDigest: digests.policy, + expectedScopeDigest: digests.scope, + expectedStateVersion, + expiresAt: expiration, + reason: reason.trim(), + repairKind: selectedRepairKind, + scopeKind, + scopeReferences: expectedDigest + .split(",") + .map((item) => item.trim()) + .filter(Boolean), + }, + } + case "execute_repair": + return { + kind: name, + body: { + expectedCaseDigest: caseDigest, + expectedPolicyDigest: digests.policy, + expectedPreviewDigest: expectedDigest, + expectedScopeDigest: digests.scope, + expectedStateVersion, + previewId: referenceId, + }, + } + case "redeliver_webhook": + return { + kind: name, + body: { + destinationId: secondaryId, + eventId: referenceId, + expectedEventDigest: expectedDigest, + expectedStateVersion, + reason: reason.trim(), + }, + } + case "remove_credential": + return { + kind: name, + body: { + expectedStateVersion, + irreversibleAcknowledged: true as const, + reason: reason.trim(), + }, + } + case "propose_legal_hold": + return { + kind: name, + body: { + command: secondaryId === "release" ? ("release" as const) : ("set" as const), + expiresAt: expiration, + externalComplianceReference: referenceId, + reason: reason.trim(), + }, + } + case "approve_legal_hold": + return { + kind: name, + proposalId: referenceId, + body: { expectedProposalDigest: expectedDigest }, + } + case "complete": + return { + kind: name, + body: { + expectedAuthorityDigest: authorityDigest, + expectedPolicyDigest: digests.policy, + expectedStabilityEvidenceDigest: expectedDigest, + expectedStateVersion, + }, + } + } + })() + await mutation.mutateAsync({ command, idempotencyKey: createMigrationCommandKey() }) + setReason("") + setAcknowledged(false) + } + + const facts = [ + { label: "Program state", value: `${program.state} · version ${program.stateVersion}` }, + { + label: "Authority epoch", + value: String(program.authorityEpochAfter ?? program.authorityEpochBefore), + }, + { label: "Primary reference", value: referenceId || "Not supplied" }, + { label: "Expected digest", value: expectedDigest || "See bound digest fields" }, + { label: "Authority consequence", value: impact[name] }, + ] + + return ( +
+
+

Lifecycle command builder

+

+ Commands are compare-and-swap operations. A 409 refreshes Program and operational state + and requires a new review. Production proposal and approval must be performed by distinct + humans; a proposer cannot approve their own command. +

+
+
+ + +
+ + + + + + + + + {digestNames.map((digestName) => ( + + ))} + + +
+ {name === "remove_credential" ? ( + + ) : null} +
+ void submit()} + pendingLabel="Submitting command…" + title="Review dangerous command" + /> +
+ {mutation.isError ? ( +

+ The command failed. If state or evidence was stale, Mosaic refreshed it; review every + value before retrying. +

+ ) : null} +
+ +
+
+

Proposals, approvals, and checkpoints

+

+ Approval records remain separate from proposals so two-person production control is + visible. +

+ +
+
+

Authority executions

+

+ Cutover and rollback always create monotonic authority epochs. +

+ +
+
+

Stabilization and rollback readiness

+

+ The rollback window is {program.rollbackWindowDays} days. Completion stays blocked until + the window and server-derived health policy are satisfied. +

+ +
+
+

Reconciliation cases and evidence

+ +
+
+

Approved repair previews and executions

+ +
+
+

Webhook redelivery

+ +
+
+

Credential removal and legal hold

+

+ Only redacted metadata is returned. Secrets are never redisplayed. +

+ +
+
+

Completion reports and audit history

+ {data?.completion ? ( +
+ {completionBlockers.length ? ( + <> +

Completion blocked

+
    + {completionBlockers.map((blocker) => ( +
  • {blocker}
  • + ))} +
+ + ) : ( +

+ Server-derived completion prerequisites are satisfied. +

+ )} +
+ ) : ( +

+ Completion inspection is unavailable or has not produced a report. +

+ )} + +
+
+
+ ) +} diff --git a/apps/dashboard/src/features/billing-migrations/components/migration-program-detail-page.tsx b/apps/dashboard/src/features/billing-migrations/components/migration-program-detail-page.tsx new file mode 100644 index 00000000..38405e34 --- /dev/null +++ b/apps/dashboard/src/features/billing-migrations/components/migration-program-detail-page.tsx @@ -0,0 +1,768 @@ +import { useMutation, useQueries, useQueryClient } from "@tanstack/react-query" +import { useCallback, useMemo, useState } from "react" + +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" +import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" +import { StatusPill } from "@/features/billing-ledger/components/billing-chrome" +import { FreezeMappingAction } from "@/features/billing-migrations/components/freeze-mapping-action" +import { GuidedMappingForm } from "@/features/billing-migrations/components/guided-mapping-form" +import { MigrationImpactReviewAction } from "@/features/billing-migrations/components/migration-impact-review-action" +import { MigrationJourneyCockpit } from "@/features/billing-migrations/components/migration-journey-cockpit" +import { MigrationLifecycleOperations } from "@/features/billing-migrations/components/migration-lifecycle-operations" +import { useMigrationCommand } from "@/features/billing-migrations/hooks/use-migration-command" +import { + assessMigrationReadinessMutationOptions, + createMigrationCommandKey, + createMigrationBatchMutationOptions, + createMigrationMappingMutationOptions, + freezeMigrationMappingMutationOptions, + queueMigrationRunMutationOptions, +} from "@/features/billing-migrations/mutations/migration-mutations" +import { + migrationBatchQueryOptions, + migrationBatchesQueryOptions, + migrationDivergencesQueryOptions, + migrationManifestsQueryOptions, + migrationMappingsQueryOptions, + migrationKeys, + migrationLifecycleQueryOptions, + migrationProgramQueryOptions, + migrationReadinessQueryOptions, + migrationRunQueryOptions, +} from "@/features/billing-migrations/queries/migration-queries" +import { + canRunMigrationCommand, + dryRunAuthorityNotice, + laterLifecycleNotice, + migrationCommandJourney, +} from "@/features/billing-migrations/types/migration-operations" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" +import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" +import { useOrganizationAccess } from "@/hooks/use-organization-access" +import { ApiError } from "@/lib/api/errors" + +type DetailTab = + "overview" | "evidence" | "mappings" | "imports" | "compare" | "readiness" | "lifecycle" +const tabs: DetailTab[] = [ + "overview", + "evidence", + "mappings", + "imports", + "compare", + "readiness", + "lifecycle", +] +const CLASSIFICATION_OPTIONS = [ + { label: "All", value: "all" }, + { label: "Critical", value: "critical" }, + { label: "Blocking", value: "blocking" }, + { label: "Warning", value: "warning" }, + { label: "Informational", value: "informational" }, +] + +interface Props { + batchId?: string + classification: string + onSearchChange: (next: { + batchId?: string + classification?: string + runJobId?: string + tab?: DetailTab + }) => void + organizationId: string + programId: string + projectId: string + runJobId?: string + tab: DetailTab +} + +export function MigrationProgramDetailPage({ + batchId = "", + classification, + onSearchChange, + organizationId, + programId, + projectId, + runJobId = "", + tab, +}: Props) { + const queryClient = useQueryClient() + const access = useOrganizationAccess(organizationId) + const { project, scopeMismatch, scopeReady } = useValidatedProjectScope(organizationId, projectId) + const [importCount, setImportCount] = useState(1000) + const [cursorBefore, setCursorBefore] = useState("") + + const [program, manifests, mappings, batches, divergences, readiness, batch, run, lifecycle] = + useQueries({ + queries: [ + { ...migrationProgramQueryOptions(projectId, programId), enabled: scopeReady }, + { ...migrationManifestsQueryOptions(projectId, programId), enabled: scopeReady }, + { ...migrationMappingsQueryOptions(projectId, programId), enabled: scopeReady }, + { ...migrationBatchesQueryOptions(projectId, programId), enabled: scopeReady }, + { + ...migrationDivergencesQueryOptions(projectId, programId, classification), + enabled: scopeReady, + }, + { ...migrationReadinessQueryOptions(projectId, programId), enabled: scopeReady }, + { + ...migrationBatchQueryOptions(projectId, programId, batchId), + enabled: scopeReady && batchId.length > 0, + }, + { + ...migrationRunQueryOptions(projectId, programId, runJobId), + enabled: scopeReady && runJobId.length > 0, + }, + { ...migrationLifecycleQueryOptions(projectId, programId), enabled: scopeReady }, + ], + }) + const refetchProgram = useCallback( + () => queryClient.refetchQueries({ queryKey: migrationKeys.program(projectId, programId) }), + [programId, projectId, queryClient], + ) + const command = useMigrationCommand(refetchProgram) + + const createMapping = useMutation( + createMigrationMappingMutationOptions(projectId, programId, queryClient), + ) + const freezeMapping = useMutation( + freezeMigrationMappingMutationOptions(projectId, programId, queryClient), + ) + const createBatch = useMutation( + createMigrationBatchMutationOptions(projectId, programId, queryClient), + ) + const dryRun = useMutation( + queueMigrationRunMutationOptions(projectId, programId, "dry_run", queryClient), + ) + const shadowRun = useMutation( + queueMigrationRunMutationOptions(projectId, programId, "shadow", queryClient), + ) + const assess = useMutation( + assessMigrationReadinessMutationOptions(projectId, programId, queryClient), + ) + + const detail = program.data + const current = detail?.program + const latestManifest = manifests.data?.at(0) + const latestMapping = mappings.data?.at(0) + const divergenceCounts = useMemo( + () => + (divergences.data ?? []).reduce( + (counts, item) => ({ ...counts, [item.classification]: counts[item.classification] + 1 }), + { blocking: 0, critical: 0, informational: 0, warning: 0 }, + ), + [divergences.data], + ) + const journey = current && detail ? migrationCommandJourney(current.state, detail) : null + const anyCommandPending = + createMapping.isPending || + freezeMapping.isPending || + createBatch.isPending || + dryRun.isPending || + shadowRun.isPending || + assess.isPending + const importDisabledReason = !canRunMigrationCommand(detail, "run-import") + ? "The server has not granted the run-import command. Organization role is not command authority." + : !current || current.state !== "importing" + ? "Freeze a reviewed mapping set before importing." + : !latestManifest + ? "A source manifest is required before importing." + : !latestMapping || latestMapping.status !== "frozen" + ? "Choose and freeze a mapping set before importing." + : importCount < 0 || importCount > 1000 + ? "Record count must be between 0 and 1,000." + : null + const dryRunDisabledReason = !canRunMigrationCommand(detail, "run-import") + ? "The server has not granted the run-import command. Organization role is not command authority." + : !current || !journey?.canQueueDryRun + ? "Complete a bounded import before starting the dry run." + : !latestManifest || !latestMapping + ? "A source manifest and mapping set are required for comparison." + : null + const shadowDisabledReason = !canRunMigrationCommand(detail, "run-import") + ? "The server has not granted the run-import command. Organization role is not command authority." + : !current || !journey?.canQueueShadow + ? "Complete the dry run before starting shadow comparison." + : !latestManifest || !latestMapping + ? "A source manifest and mapping set are required for comparison." + : null + const readinessDisabledReason = !canRunMigrationCommand(detail, "assess-readiness") + ? "The server has not granted the assess-readiness command. Organization role is explanatory only." + : !current || !journey?.canAssess + ? "Complete shadow comparison before assessing readiness." + : null + const commonError = + project.error ?? + access.error ?? + program.error ?? + manifests.error ?? + mappings.error ?? + batches.error ?? + divergences.error ?? + lifecycle.error + const readinessMissing = readiness.error instanceof ApiError && readiness.error.status === 404 + const state = resolveHostedQueryState({ + error: commonError, + isEmpty: false, + isPending: + project.isPending || + access.isPending || + (scopeReady && + (program.isPending || + manifests.isPending || + mappings.isPending || + batches.isPending || + divergences.isPending || + lifecycle.isPending)), + loadingDescription: "Loading migration scope and operational evidence in parallel.", + onRetry: () => { + void program.refetch() + void manifests.refetch() + void mappings.refetch() + void batches.refetch() + void divergences.refetch() + void lifecycle.refetch() + }, + permissionDescription: "Project membership is required to view this Migration Program.", + scope: { organizationId, projectId }, + }) + + const programBase = `/orgs/${encodeURIComponent(organizationId)}/projects/${encodeURIComponent(projectId)}/billing/migrations/${encodeURIComponent(programId)}` + const scopeRows = useMemo(() => current?.scope.applications ?? [], [current?.scope.applications]) + + if (scopeMismatch) + return ( + + + + ) + + return ( + + {current ? : null} + + {command.error ? ( +

+ {command.error} +

+ ) : null} + + {current ? ( +

+ {laterLifecycleNotice(current)} No cutover, rollback, or repair command is implied by + this page. +

+ ) : null} + + {tab === "overview" && current ? ( + +
+
+
State
+
+ {current.state} · version {current.stateVersion} +
+
+
+
Source
+
RevenueCat · {current.source.adapterVersion}
+
+
+
Rollback window
+
{current.rollbackWindowDays} days
+
+
+
    + {scopeRows.map((item) => ( +
  • + {item.applicationId} · {item.platform} +
  • + ))} +
+
+

Server assessments

+

+ Source reads:{" "} + {detail?.sourceCapabilityAssessment?.capabilities.join(", ") || "Not assessed"}. + These provider permissions do not authorize operator commands. +

+

+ Operator commands:{" "} + {detail?.commandCapabilities.size + ? [...detail.commandCapabilities].join(", ") + : "None granted (fail closed)"} + . Organization role {access.role ?? "unknown"} is shown only to explain membership + context. +

+
+
+ ) : null} + + {tab === "evidence" ? ( + + {manifests.data?.length ? ( +
    + {manifests.data.map((item) => ( +
  • + {item.manifestId} + + {item.recordCount} records · {item.currentAccessRecordCount} current access + + + {item.manifestDigest} + +
  • + ))} +
+ ) : ( +

+ No immutable source manifest has been recorded. +

+ )} +
+ ) : null} + + {tab === "mappings" ? ( +
+ {journey?.canCreateMapping && current ? ( + + + command.run( + () => + createMapping.mutateAsync({ + entries, + expectedStateVersion: current.stateVersion, + version: + Math.max(0, ...(mappings.data ?? []).map((item) => item.version)) + 1, + }), + "mapping", + ) + } + /> + + ) : null} + + {mappings.data?.length ? ( +
    + {mappings.data.map((item) => ( +
  • + Version {item.version} + + {item.entries.length} entries + {item.status === "draft" && journey?.canFreezeMapping && current ? ( + + command.run( + () => + freezeMapping.mutateAsync({ + expectedStateVersion: current.stateVersion, + mappingSetId, + }), + "freeze", + ) + } + /> + ) : null} +
  • + ))} +
+ ) : ( +

No mapping set has been created.

+ )} +
+
+ ) : null} + + {tab === "imports" ? ( +
+ {current ? ( + +
+ + +
+
+ + void command.run(async () => { + const created = await createBatch.mutateAsync({ + body: { + cursorBefore, + expectedStateVersion: current.stateVersion, + manifestId: latestManifest?.manifestId ?? "", + mappingSetId: latestMapping?.mappingSetId ?? "", + recordCount: importCount, + }, + idempotencyKey: createMigrationCommandKey(), + }) + onSearchChange({ batchId: created.batchId, tab: "imports" }) + }, "import") + } + pendingLabel="Queueing import…" + title="Review import impact" + /> +
+
+ ) : null} + + {batches.data?.length ? ( +
    + {batches.data.map((item) => ( +
  • + + {item.batchId} + + + + {item.validatedCount}/{item.recordCount} validated · {item.quarantinedCount}{" "} + quarantined + +
  • + ))} +
+ ) : ( +

No import batch has been queued.

+ )} + {batch.data ? ( +

+ Selected batch {batch.data.batchId}: {batch.data.status},{" "} + {batch.data.validatedCount} validated and {batch.data.quarantinedCount}{" "} + quarantined. +

+ ) : null} +
+
+ ) : null} + + {tab === "compare" ? ( +
+ +

+ {dryRunAuthorityNotice(current?.state === "shadowing" ? "shadow" : "dry_run")} +

+
+ + void command.run(async () => { + const job = await dryRun.mutateAsync({ + body: { + expectedStateVersion: current?.stateVersion ?? 0, + manifestDigest: latestManifest?.manifestDigest ?? "", + mappingDigest: latestMapping?.mappingDigest ?? "", + }, + idempotencyKey: createMigrationCommandKey(), + }) + onSearchChange({ runJobId: job.runJobId, tab: "compare" }) + }, "run") + } + pendingLabel="Queueing dry run…" + title="Review dry-run impact" + /> + + void command.run(async () => { + const job = await shadowRun.mutateAsync({ + body: { + expectedStateVersion: current?.stateVersion ?? 0, + manifestDigest: latestManifest?.manifestDigest ?? "", + mappingDigest: latestMapping?.mappingDigest ?? "", + }, + idempotencyKey: createMigrationCommandKey(), + }) + onSearchChange({ runJobId: job.runJobId, tab: "compare" }) + }, "run") + } + pendingLabel="Queueing shadow comparison…" + title="Review shadow-run impact" + variant="outline" + /> +
+ {run.data ? ( +
+ {run.data.runKind.replaceAll("_", " ")} · {run.data.status}.{" "} + {dryRunAuthorityNotice(run.data.runKind)} +
+ ) : null} +
+ + + + {divergences.data?.length ? ( +
    + {divergences.data.map((item) => ( +
  • + +

    {item.reason}

    +

    + Rule {item.classificationRuleVersion} ·{" "} + {new Date(item.observedAt).toLocaleString()} +

    +
  • + ))} +
+ ) : ( +

+ No divergence matches this filter. +

+ )} +
+
+ ) : null} + + {tab === "readiness" ? ( + + {current ? ( + + void command.run( + () => assess.mutateAsync({ expectedStateVersion: current.stateVersion }), + "readiness", + ) + } + pendingLabel="Assessing readiness…" + title="Review readiness impact" + /> + ) : null} + {readiness.data ? ( +
+
+
Ready
+
{readiness.data.ready ? "Yes" : "No"}
+
+
+
Current-access mapping
+
{readiness.data.currentAccessMappingPercent}%
+
+
+
Validated evidence
+
{readiness.data.currentAccessEvidencePercent}%
+
+
+
Critical / blocking
+
+ {readiness.data.unresolved.critical} / {readiness.data.unresolved.blocking} +
+
+
+
Final delta
+
{readiness.data.finalDeltaCompleted ? "Complete" : "Pending"}
+
+
+
Fresh watermarks / aware versions
+
+ {readiness.data.watermarksFresh ? "Fresh" : "Pending"} /{" "} + {readiness.data.supportedVersionsAuthorityAware ? "Ready" : "Pending"} +
+
+
+ ) : readinessMissing ? ( +

+ No readiness assessment exists yet. +

+ ) : readiness.error ? ( +

+ Mosaic could not load the latest readiness assessment. Refresh this view and try + again. +

+ ) : null} +
+ ) : null} + {tab === "lifecycle" && detail ? ( + + ) : null} +
+
+ ) +} diff --git a/apps/dashboard/src/features/billing-migrations/components/migration-programs-page.tsx b/apps/dashboard/src/features/billing-migrations/components/migration-programs-page.tsx new file mode 100644 index 00000000..c1a75e4f --- /dev/null +++ b/apps/dashboard/src/features/billing-migrations/components/migration-programs-page.tsx @@ -0,0 +1,181 @@ +import { useState } from "react" +import { useMutation, useQueries, useQueryClient } from "@tanstack/react-query" +import { useNavigate } from "@tanstack/react-router" + +import { EmptyState } from "@/components/feedback/empty-state" +import { Button } from "@/components/ui/button" +import { buttonVariants } from "@/components/ui/button-variants" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" +import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" +import { StatusPill } from "@/features/billing-ledger/components/billing-chrome" +import { CreateMigrationProgramForm } from "@/features/billing-migrations/components/create-migration-program-form" +import { createMigrationProgramMutationOptions } from "@/features/billing-migrations/mutations/migration-mutations" +import { migrationProgramsQueryOptions } from "@/features/billing-migrations/queries/migration-queries" +import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" +import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" +import { applicationsQueryOptions } from "@/features/projects/queries/projects-query" +import { useOrganizationAccess } from "@/hooks/use-organization-access" + +export function MigrationProgramsPage({ + organizationId, + projectId, +}: { + organizationId: string + projectId: string +}) { + const queryClient = useQueryClient() + const navigate = useNavigate() + const access = useOrganizationAccess(organizationId) + const { project, scopeMismatch, scopeReady } = useValidatedProjectScope(organizationId, projectId) + const [programs, environments, applications] = useQueries({ + queries: [ + { ...migrationProgramsQueryOptions(projectId), enabled: scopeReady }, + { ...environmentsQueryOptions(projectId), enabled: scopeReady }, + { ...applicationsQueryOptions(projectId), enabled: scopeReady }, + ], + }) + const create = useMutation(createMigrationProgramMutationOptions(projectId, queryClient)) + const [createOpen, setCreateOpen] = useState(false) + + if (scopeMismatch) { + return ( + + + + ) + } + + const items = programs.data ?? [] + const error = + project.error ?? access.error ?? programs.error ?? environments.error ?? applications.error + const state = resolveHostedQueryState({ + error, + isEmpty: false, + isPending: + project.isPending || + access.isPending || + (scopeReady && (programs.isPending || environments.isPending || applications.isPending)), + loadingDescription: "Loading Project migration programs and their available scope.", + onRetry: () => { + void programs.refetch() + void environments.refetch() + void applications.refetch() + }, + permissionDescription: "Project membership is required to view Migration Programs.", + scope: { organizationId, projectId }, + }) + const base = `/orgs/${encodeURIComponent(organizationId)}/projects/${encodeURIComponent(projectId)}/billing/migrations` + const createDialog = ( + { + setCreateOpen(open) + if (!open) create.reset() + }} + open={createOpen} + > + }>Create program + + + Create Migration Program + + {`Creating a program performs a bounded, read-only source capability assessment. The server authorizes the command; Organization role ${access.role ?? "unknown"} is explanatory only.`} + + +
+ create.mutateAsync(command)} + onCreated={(program) => { + setCreateOpen(false) + void navigate({ + params: (prev) => ({ ...prev, programId: program.programId }), + search: { classification: "all", tab: "overview" }, + to: "/orgs/$organizationId/projects/$projectId/env/$environmentKey/billing/migrations/$programId", + }) + }} + resetMutation={() => create.reset()} + /> +
+
+
+ ) + + return ( + + + {items.length === 0 ? ( + + Review authorized operators + + } + description={ + "No RevenueCat migration has been scoped for this Project. Submit a scoped request from Create program; the server will authorize or deny it." + } + title="No Migration Programs yet" + /> + ) : ( + +
    + {items.map((detail) => { + const program = detail.program + return ( +
  • +
    + + RevenueCat · {program.scope.environmentId} + +

    + {program.scope.applications.length} explicit Application/platform scope(s) · + state version {program.stateVersion} +

    +
    + +
  • + ) + })} +
+
+ )} +
+
+ ) +} diff --git a/apps/dashboard/src/features/billing-operations/components/create-reconciliation-run-sheet.tsx b/apps/dashboard/src/features/billing-operations/components/create-reconciliation-run-sheet.tsx index 1af9bacf..c0f20c19 100644 --- a/apps/dashboard/src/features/billing-operations/components/create-reconciliation-run-sheet.tsx +++ b/apps/dashboard/src/features/billing-operations/components/create-reconciliation-run-sheet.tsx @@ -4,6 +4,13 @@ import { useState } from "react" import { Button } from "@/components/ui/button" import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { Sheet, SheetContent, @@ -27,9 +34,6 @@ import { } from "@/features/billing-operations/types/reconciliation-range" import type { CreateReconciliationRunRequest, StoreServerCredential } from "@/generated/api" -const fieldClass = - "border-input bg-background focus-visible:border-ring focus-visible:ring-ring/40 h-9 w-full rounded border px-3 text-sm outline-none focus-visible:ring-3" - /** * The strategies the API accepts today. * @@ -106,6 +110,17 @@ export function CreateReconciliationRunSheet({ const credentialId = useStore(form.store, (state) => state.values.credentialId) const selected = usable.find((item) => item.id === credentialId) + const credentialOptions = [ + { label: "Select a credential", value: "" }, + ...usable.map((credential) => ({ + label: `${credential.name} · ${providerLabel(credential.provider)} · ${storeEnvironmentLabel(credential.storeEnvironment)}`, + value: credential.id, + })), + ] + const strategyOptions = strategiesFor(selected?.provider).map((strategy) => ({ + label: reconciliationStrategyLabel(strategy), + value: strategy, + })) return ( Store Server Credential - { + field.handleChange(value) + const next = usable.find((item) => item.id === value) form.setFieldValue("strategy", strategiesFor(next?.provider)[0] as Strategy) }} value={field.state.value} > - - {usable.map((credential) => ( - - ))} - + + + + + {credentialOptions.map((option) => ( + + {option.label} + + ))} + + The credential fixes both the store and the Store Environment. The Mosaic Environment is {environmentName} and comes from the address. @@ -191,18 +208,22 @@ export function CreateReconciliationRunSheet({ {(field) => ( Strategy - field.handleChange(value as Strategy)} value={field.state.value} > - {strategiesFor(selected?.provider).map((strategy) => ( - - ))} - + + + + + {strategyOptions.map((option) => ( + + {option.label} + + ))} + + )} diff --git a/apps/dashboard/src/features/billing-operations/components/quarantine-page.tsx b/apps/dashboard/src/features/billing-operations/components/quarantine-page.tsx index e6e639a0..e87a2e19 100644 --- a/apps/dashboard/src/features/billing-operations/components/quarantine-page.tsx +++ b/apps/dashboard/src/features/billing-operations/components/quarantine-page.tsx @@ -1,6 +1,13 @@ import { useQuery } from "@tanstack/react-query" import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { buttonVariants } from "@/components/ui/button-variants" import { EmptyState } from "@/components/feedback/empty-state" import { @@ -31,8 +38,8 @@ import { type QuarantineListFilters, } from "@/features/billing-operations/queries/quarantine-queries" import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" -import { ScopeMismatchRecovery } from "@/features/organizations/components/scope-mismatch-recovery" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" const fieldClass = @@ -46,6 +53,20 @@ interface QuarantinePageProps { projectId: string } +const QUARANTINE_STATUS_OPTIONS = [ + { label: "Any status", value: "" }, + { label: "Open", value: "open" }, + { label: "Retrying", value: "retrying" }, + { label: "Closed after a successful attempt", value: "closed_after_success" }, + { label: "Closed as superseded", value: "closed_superseded" }, +] + +const quarantineProviderOptions = [ + { label: "Any store", value: "" }, + { label: providerLabel("app_store"), value: "app_store" }, + { label: providerLabel("google_play"), value: "google_play" }, +] + export function QuarantinePage({ environmentId, filters, @@ -103,7 +124,7 @@ export function QuarantinePage({ ) } - const base = `/organizations/${encodeURIComponent(organizationId)}/projects/${encodeURIComponent(projectId)}/billing/${encodeURIComponent(environmentId)}` + const base = `/orgs/${encodeURIComponent(organizationId)}/projects/${encodeURIComponent(projectId)}/billing/${encodeURIComponent(environmentId)}` return (
- -
+
+ + - + + + + + {quarantineProviderOptions.map((option) => ( + + {option.label} + + ))} + + +
{/* A reason-code filter can arrive from a health or ledger recovery link. Without a visible control it would filter invisibly. */} {filters.reasonCode ? ( diff --git a/apps/dashboard/src/features/catalog/components/entitlements-page.tsx b/apps/dashboard/src/features/catalog/components/entitlements-page.tsx index 62177067..0c0ad62e 100644 --- a/apps/dashboard/src/features/catalog/components/entitlements-page.tsx +++ b/apps/dashboard/src/features/catalog/components/entitlements-page.tsx @@ -1,16 +1,27 @@ +import { useState } from "react" import { useForm } from "@tanstack/react-form" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { Link } from "@tanstack/react-router" import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" import { Field, FieldDescription, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { createEntitlementMutationOptions } from "@/features/catalog/mutations/catalog-mutations" import { entitlementsQueryOptions } from "@/features/catalog/queries/catalog-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" -import { ScopeMismatchRecovery } from "@/features/organizations/components/scope-mismatch-recovery" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" interface EntitlementsPageProps { @@ -24,6 +35,7 @@ export function EntitlementsPage({ organizationId, projectId }: EntitlementsPage const entitlements = useQuery({ ...entitlementsQueryOptions(projectId), enabled: scopeReady }) const mutation = useMutation(createEntitlementMutationOptions(projectId, queryClient)) const items = entitlements.data?.items ?? [] + const [createOpen, setCreateOpen] = useState(false) const form = useForm({ defaultValues: { description: "", key: "", name: "" }, onSubmit: async ({ value }) => { @@ -33,6 +45,7 @@ export function EntitlementsPage({ organizationId, projectId }: EntitlementsPage name: value.name.trim(), }) form.reset() + setCreateOpen(false) }, }) const state = resolveHostedQueryState({ @@ -64,45 +77,34 @@ export function EntitlementsPage({ organizationId, projectId }: EntitlementsPage ) } - return ( - { + setCreateOpen(open) + if (!open) { + form.reset() + mutation.reset() + } + }} + open={createOpen} > - - -
    - {items.map((entitlement) => ( -
  • - - {entitlement.name} - - {entitlement.key} - - - - View definition - -
  • - ))} -
-
-
- {canManageEntitlements ? ( - -
{ - event.preventDefault() - event.stopPropagation() - void form.handleSubmit() - }} - > + }>Create Entitlement + + { + event.preventDefault() + event.stopPropagation() + void form.handleSubmit() + }} + > + + Create Entitlement + + An Entitlement is a named capability that Products unlock. This defines it only; it + does not grant customer access. + + +
{(field) => ( @@ -142,17 +144,53 @@ export function EntitlementsPage({ organizationId, projectId }: EntitlementsPage )} -
+ + }>Cancel + - - {mutation.error ? ( -

- {mutation.error.message} -

- ) : null} +
+ +
+ + ) + + return ( + + + +
    + {items.map((entitlement) => ( +
  • + + {entitlement.name} + + {entitlement.key} + + + ({ ...prev, entitlementId: entitlement.id })} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/entitlements/$entitlementId" + > + View definition + +
  • + ))} +
- ) : null} +
) } diff --git a/apps/dashboard/src/features/catalog/components/plans-page.tsx b/apps/dashboard/src/features/catalog/components/plans-page.tsx index 28c0ddc8..2a1f96b8 100644 --- a/apps/dashboard/src/features/catalog/components/plans-page.tsx +++ b/apps/dashboard/src/features/catalog/components/plans-page.tsx @@ -1,16 +1,27 @@ +import { useState } from "react" import { useForm } from "@tanstack/react-form" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { Link } from "@tanstack/react-router" import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" import { Field, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { createPlanMutationOptions } from "@/features/catalog/mutations/catalog-mutations" import { plansQueryOptions } from "@/features/catalog/queries/catalog-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" -import { ScopeMismatchRecovery } from "@/features/organizations/components/scope-mismatch-recovery" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" interface PlansPageProps { @@ -24,6 +35,7 @@ export function PlansPage({ organizationId, projectId }: PlansPageProps) { const plans = useQuery({ ...plansQueryOptions(projectId), enabled: scopeReady }) const mutation = useMutation(createPlanMutationOptions(projectId, queryClient)) const items = plans.data?.items ?? [] + const [createOpen, setCreateOpen] = useState(false) const form = useForm({ defaultValues: { description: "", key: "", name: "" }, onSubmit: async ({ value }) => { @@ -33,6 +45,7 @@ export function PlansPage({ organizationId, projectId }: PlansPageProps) { name: value.name.trim(), }) form.reset() + setCreateOpen(false) }, }) const state = resolveHostedQueryState({ @@ -64,47 +77,34 @@ export function PlansPage({ organizationId, projectId }: PlansPageProps) { ) } - return ( - { + setCreateOpen(open) + if (!open) { + form.reset() + mutation.reset() + } + }} + open={createOpen} > - - -
    - {items.map((plan) => ( -
  • -

    {plan.name}

    -

    {plan.key}

    -

    - {plan.description ?? "No description"} -

    - - Manage Products - -
  • - ))} -
-
-
- {canManagePlans ? ( - }>Create Plan + +
{ + event.preventDefault() + event.stopPropagation() + void form.handleSubmit() + }} > - { - event.preventDefault() - event.stopPropagation() - void form.handleSubmit() - }} - > + + Create Plan + + A Plan is what you sell; Products are its monthly, yearly, or lifetime purchase + options. + + +
{(field) => ( @@ -144,17 +144,52 @@ export function PlansPage({ organizationId, projectId }: PlansPageProps) { )} -
+ + }>Cancel + - - {mutation.error ? ( -

- {mutation.error.message} -

- ) : null} +
+ +
+ + ) + + return ( + + + +
    + {items.map((plan) => ( +
  • +

    {plan.name}

    +

    {plan.key}

    +

    + {plan.description ?? "No description"} +

    + ({ ...prev, planId: plan.id })} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/plans/$planId" + > + Manage Products + +
  • + ))} +
- ) : null} +
) } diff --git a/apps/dashboard/src/features/catalog/components/product-detail-page.tsx b/apps/dashboard/src/features/catalog/components/product-detail-page.tsx index 251f91e7..73e57e9e 100644 --- a/apps/dashboard/src/features/catalog/components/product-detail-page.tsx +++ b/apps/dashboard/src/features/catalog/components/product-detail-page.tsx @@ -6,6 +6,13 @@ import { useState } from "react" import { Button } from "@/components/ui/button" import { buttonVariants } from "@/components/ui/button-variants" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { ProductReadinessPanel } from "@/features/catalog/components/product-readiness-panel" @@ -41,9 +48,9 @@ import { readinessStateLabel, } from "@/features/catalog/types/connected-product-view" import { environmentsQueryOptions } from "@/features/environments/queries/environments-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" -import { ScopeMismatchRecovery } from "@/features/organizations/components/scope-mismatch-recovery" -import { detectNestedScopeMismatch } from "@/features/organizations/types/nested-scope" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" +import { detectNestedScopeMismatch } from "@/features/orgs/types/nested-scope" import { providerConnectionsQueryOptions } from "@/features/provider-connections/queries/provider-connection-queries" import { applicationsQueryOptions, @@ -187,8 +194,8 @@ export function ProductDetailPage({ permissionAction: ( prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey" > Return to Project @@ -203,6 +210,24 @@ export function ProductDetailPage({ productId, product.data?.type, ) + const replacementSelectOptions = replacementOptions.map((item) => ({ + label: item.internalName, + value: item.id, + })) + const readinessEnvironmentOptions = [ + { label: "Select Environment", value: "" }, + ...(environments.data?.items ?? []).map((environment) => ({ + label: `${environment.name} · ${environment.mode}`, + value: environment.id, + })), + ] + const readinessApplicationOptions = [ + { label: "Select Application", value: "" }, + ...(applications.data?.items ?? []).map((application) => ({ + label: `${application.name} · ${application.platform.toUpperCase()}`, + value: application.id, + })), + ] const effectiveReplacementId = selectedReplacementId ?? product.data?.replacementProductId const replacementNeedsSave = Boolean( selectedReplacementId && selectedReplacementId !== product.data?.replacementProductId, @@ -230,7 +255,7 @@ export function ProductDetailPage({ : undefined, } }) ?? [] - const manageProvidersHref = `/organizations/${encodeURIComponent(organizationId)}/projects/${encodeURIComponent(projectId)}/catalog/providers` + const manageProvidersHref = `/orgs/${encodeURIComponent(organizationId)}/projects/${encodeURIComponent(projectId)}/catalog/providers` async function confirmArchive() { try { @@ -320,46 +345,54 @@ export function ProductDetailPage({ title="Readiness scope" >
- -
+
+ + - + + + + + {readinessApplicationOptions.map((option) => ( + + {option.label} + + ))} + + +
{!hasExplicitReadinessScope ? (

@@ -385,7 +418,7 @@ export function ProductDetailPage({ {connectedReadiness ? ( Ask an Owner or Admin to change Access grants @@ -487,7 +520,7 @@ export function ProductDetailPage({ queryClient.fetchQuery(providerMappingUsageQueryOptions(mappingId)) } manageProvidersHref={manageProvidersHref} - membersHref={`/organizations/${encodeURIComponent(organizationId)}/members`} + membersHref={`/orgs/${encodeURIComponent(organizationId)}/members`} mappings={mappingViews} onArchive={async (mappingId) => { await archiveMapping.mutateAsync(mappingId) @@ -535,7 +568,7 @@ export function ProductDetailPage({ {!access.canManage ? ( Ask an Owner or Admin to change Product lifecycle @@ -564,23 +597,25 @@ export function ProductDetailPage({ : "This Product has no known usage and can be archived safely."}

{usageCount > 0 ? ( - + + + + + {replacementSelectOptions.map((option) => ( + + {option.label} + + ))} + + + ) : null} {usageCount > 0 && product.data?.replacementProductId ? (

@@ -592,8 +627,8 @@ export function ProductDetailPage({

prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products" > Create Replacement Product diff --git a/apps/dashboard/src/features/catalog/components/products-page.tsx b/apps/dashboard/src/features/catalog/components/products-page.tsx index f10a9375..5651e13c 100644 --- a/apps/dashboard/src/features/catalog/components/products-page.tsx +++ b/apps/dashboard/src/features/catalog/components/products-page.tsx @@ -1,20 +1,57 @@ +import { useState } from "react" import { useForm } from "@tanstack/react-form" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { Link } from "@tanstack/react-router" import { Button } from "@/components/ui/button" import { buttonVariants } from "@/components/ui/button-variants" +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" import { Field, FieldDescription, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { createProductMutationOptions } from "@/features/catalog/mutations/catalog-mutations" import { productsQueryOptions, type ProductFilters } from "@/features/catalog/queries/catalog-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" -import { ScopeMismatchRecovery } from "@/features/organizations/components/scope-mismatch-recovery" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" import { describeReturnDestination } from "@/lib/routing/workspace-hrefs" +const PRODUCT_TYPE_OPTIONS = [ + { label: "Subscription", value: "subscription" }, + { label: "One-time non-consumable", value: "one_time_non_consumable" }, +] + +const STATUS_FILTER_OPTIONS = [ + { label: "All statuses", value: "" }, + { label: "Draft", value: "draft" }, + { label: "Connected", value: "connected" }, + { label: "Attention required", value: "attention_required" }, + { label: "Archived", value: "archived" }, +] + +const TYPE_FILTER_OPTIONS = [ + { label: "All types", value: "" }, + { label: "Subscription", value: "subscription" }, + { label: "One-time", value: "one_time_non_consumable" }, +] + interface ProductsPageProps { filters: ProductFilters onFiltersChange: (filters: ProductFilters) => void @@ -40,6 +77,7 @@ export function ProductsPage({ const products = useQuery({ ...productsQueryOptions(projectId, filters), enabled: scopeReady }) const mutation = useMutation(createProductMutationOptions(projectId, queryClient)) const items = products.data?.items ?? [] + const [createOpen, setCreateOpen] = useState(false) const form = useForm({ defaultValues: { description: "", @@ -55,6 +93,7 @@ export function ProductsPage({ type: value.type, }) form.reset() + setCreateOpen(false) }, }) const state = resolveHostedQueryState({ @@ -90,8 +129,118 @@ export function ProductsPage({ ) } + const createDialog = ( + { + setCreateOpen(open) + if (!open) { + form.reset() + mutation.reset() + } + }} + open={createOpen} + > + }>Create Product + +
{ + event.preventDefault() + event.stopPropagation() + void form.handleSubmit() + }} + > + + Create Product + + Create a provider-neutral Product manually or import synchronized provider metadata + from Purchase setup. + + +
+ + {(field) => ( + + Internal name + field.handleChange(event.target.value)} + placeholder="Monthly" + value={field.state.value} + /> + + )} + + + {(field) => ( + + Key + field.handleChange(event.target.value.toLowerCase())} + placeholder="monthly" + value={field.state.value} + /> + + )} + + + {(field) => ( + + Type + + + )} + + + {(field) => ( + + Description + field.handleChange(event.target.value)} + value={field.state.value} + /> + Optional Mosaic-owned metadata. + + )} + + {mutation.error ? ( +

+ {mutation.error.message} +

+ ) : null} +
+ + }>Cancel + + +
+
+
+ ) + return ( prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/providers" > Review Purchase setup @@ -132,42 +281,54 @@ export function ProductsPage({ value={filters.search ?? ""} /> - -
+
+ + - + + + + + {TYPE_FILTER_OPTIONS.map((option) => ( + + {option.label} + + ))} + + +
@@ -185,9 +346,9 @@ export function ProductsPage({
({ ...prev, productId: product.id })} search={returnTo ? { returnTo } : {}} - to="/organizations/$organizationId/projects/$projectId/catalog/products/$productId" + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/catalog/products/$productId" > {returnTo ? "Open mappings" : "View usage"} @@ -196,89 +357,6 @@ export function ProductsPage({ - {canManageProducts ? ( - -
{ - event.preventDefault() - event.stopPropagation() - void form.handleSubmit() - }} - > - - {(field) => ( - - Internal name - field.handleChange(event.target.value)} - placeholder="Monthly" - value={field.state.value} - /> - - )} - - - {(field) => ( - - Key - field.handleChange(event.target.value.toLowerCase())} - placeholder="monthly" - value={field.state.value} - /> - - )} - - - {(field) => ( - - Type - - - )} - - - {(field) => ( - - Description - field.handleChange(event.target.value)} - value={field.state.value} - /> - Optional Mosaic-owned metadata. - - )} - - -
- {mutation.error ? ( -

- {mutation.error.message} -

- ) : null} -
- ) : null}
) } diff --git a/apps/dashboard/src/features/entitlement-grants/components/grant-versions-page.tsx b/apps/dashboard/src/features/entitlement-grants/components/grant-versions-page.tsx index dd29ed2a..313358b3 100644 --- a/apps/dashboard/src/features/entitlement-grants/components/grant-versions-page.tsx +++ b/apps/dashboard/src/features/entitlement-grants/components/grant-versions-page.tsx @@ -2,6 +2,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { EmptyState } from "@/components/feedback/empty-state" import { buttonVariants } from "@/components/ui/button-variants" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { DefinitionRow, StatusPill } from "@/features/billing-ledger/components/billing-chrome" @@ -20,8 +27,8 @@ import { grantPolicyFields, grantPolicyLabel, } from "@/features/entitlement-grants/types/grant-version-view" -import { ScopeMismatchRecovery } from "@/features/organizations/components/scope-mismatch-recovery" -import { WorkflowPanel, WorkspacePage } from "@/features/organizations/components/workspace-page" +import { ScopeMismatchRecovery } from "@/features/orgs/components/scope-mismatch-recovery" +import { WorkflowPanel, WorkspacePage } from "@/features/orgs/components/workspace-page" import { useValidatedProjectScope } from "@/features/projects/hooks/use-validated-project-scope" import { useOrganizationAccess } from "@/hooks/use-organization-access" import { catalogProductsHref } from "@/lib/routing/workspace-hrefs" @@ -98,6 +105,17 @@ export function GrantVersionsPage({ } const productsHref = catalogProductsHref({ organizationId, projectId }) ?? "#" + const productOptions = productItems.map((product) => ({ + label: product.internalName, + value: product.id, + })) + const entitlementOptions = [ + { label: "All Entitlements", value: "" }, + ...(entitlements.data?.items ?? []).map((entitlement) => ({ + label: entitlement.name, + value: entitlement.id, + })), + ] const grouped = groupByEntitlement(versions.data ?? []) return ( @@ -128,42 +146,59 @@ export function GrantVersionsPage({ <>
- -
+
+ + - + + + + + {entitlementOptions.map((option) => ( + + {option.label} + + ))} + + +
Ask an Owner or Admin to change what this Product grants diff --git a/apps/dashboard/src/features/entitlement-grants/components/publish-grant-version-wizard.tsx b/apps/dashboard/src/features/entitlement-grants/components/publish-grant-version-wizard.tsx index d7c09719..347a0229 100644 --- a/apps/dashboard/src/features/entitlement-grants/components/publish-grant-version-wizard.tsx +++ b/apps/dashboard/src/features/entitlement-grants/components/publish-grant-version-wizard.tsx @@ -4,14 +4,21 @@ import { Button } from "@/components/ui/button" import { Field, FieldDescription, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { - Sheet, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet" + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" import { StatusPill } from "@/features/billing-ledger/components/billing-chrome" import { evaluatePublishGate, @@ -26,9 +33,6 @@ import { } from "@/features/entitlement-grants/types/grant-version-view" import type { Entitlement, GrantVersionImpact, Product } from "@/generated/api" -const fieldClass = - "border-input bg-background focus-visible:border-ring focus-visible:ring-ring/40 h-9 w-full rounded border px-3 text-sm outline-none focus-visible:ring-3" - type PurchaseType = "auto_renewable_subscription" | "non_consumable" interface PublishGrantVersionWizardProps { @@ -102,6 +106,15 @@ export function PublishGrantVersionWizard({ }) } + const productOptions = products.map((product) => ({ + label: product.internalName, + value: product.id, + })) + const entitlementOptions = entitlements.map((entitlement) => ({ + label: entitlement.name, + value: entitlement.id, + })) + const gate = evaluatePublishGate({ canManage, impact, @@ -148,24 +161,24 @@ export function PublishGrantVersionWizard({ } return ( - { setOpen(nextOpen) if (!nextOpen) reset() }} open={open} > - }> + }> {triggerLabel} - - - - Create a new grant version - + + + + Create a new grant version + Published versions are never edited. This creates the next version and closes the current one at exactly its start, so the two intervals abut with no gap and no overlap. - - + +
    @@ -178,34 +191,42 @@ export function PublishGrantVersionWizard({ <> Product - update({ productId: value })} value={proposal.productId} > - {products.map((product) => ( - - ))} - + + + + + {productOptions.map((option) => ( + + {option.label} + + ))} + + Entitlement - update({ entitlementId: value })} value={proposal.entitlementId} > - {entitlements.map((entitlement) => ( - - ))} - + + + + + {entitlementOptions.map((option) => ( + + {option.label} + + ))} + + One version history belongs to one (Product, Entitlement) pair. @@ -368,7 +389,7 @@ export function PublishGrantVersionWizard({ ) : null}
- +
{step === "shape" ? (
({ ...prev, paywallId: variant.paywallId })} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/paywalls/$paywallId" > Open Paywall @@ -222,8 +224,8 @@ function ImmutableActiveDefinition({

prev} + to="/orgs/$organizationId/projects/$projectId/env/$environmentKey/monetization/experiments/new" > Create new Experiment @@ -569,6 +571,11 @@ function HistoryPanel({ ) } +const QA_IDENTITY_OPTIONS = [ + { label: "Identified user", value: "identified_user" }, + { label: "Installation", value: "installation" }, +] + function QaPanel({ activeVersionId, canManage, @@ -606,6 +613,10 @@ function QaPanel({ formApi.reset() }, }) + const variantOptions = variants.map((variant) => ({ + label: variant.name, + value: variant.id ?? "", + })) if (qa.isPending) return if (qa.error) return void qa.refetch()} /> @@ -646,18 +657,22 @@ function QaPanel({ {(field) => ( Forced Variant - + + + + + {variantOptions.map((option) => ( + + {option.label} + + ))} + + )} @@ -665,17 +680,22 @@ function QaPanel({ {(field) => ( Identity type - + + + + + {QA_IDENTITY_OPTIONS.map((option) => ( + + {option.label} + + ))} + + )} diff --git a/apps/dashboard/src/features/experiments/components/mutual-exclusion-group-manager.tsx b/apps/dashboard/src/features/experiments/components/mutual-exclusion-group-manager.tsx index f47225a9..39bd1920 100644 --- a/apps/dashboard/src/features/experiments/components/mutual-exclusion-group-manager.tsx +++ b/apps/dashboard/src/features/experiments/components/mutual-exclusion-group-manager.tsx @@ -5,7 +5,14 @@ import { useState } from "react" import { Button } from "@/components/ui/button" import { Field, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" -import { WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { WorkflowPanel } from "@/features/orgs/components/workspace-page" import { useExperimentAdapter } from "../api/use-experiment-adapter" import { createExperimentGroupMutationOptions, @@ -18,6 +25,12 @@ import { } from "../queries/experiment-queries" import { validateMutualExclusionAllocation, type ExperimentScope } from "../types/experiment" +const GROUP_IDENTITY_OPTIONS = [ + { label: "Identified user", value: "identified_user" }, + { label: "Identified user, otherwise installation", value: "identified_user_or_installation" }, + { label: "Installation", value: "installation" }, +] + export function MutualExclusionGroupManager({ scope }: { scope: ExperimentScope }) { const adapter = useExperimentAdapter() const queryClient = useQueryClient() @@ -181,20 +194,22 @@ export function MutualExclusionGroupManager({ scope }: { scope: ExperimentScope {(field) => ( Group assignment identity - + + + + + {GROUP_IDENTITY_OPTIONS.map((option) => ( + + {option.label} + + ))} + + )} diff --git a/apps/dashboard/src/features/members/components/members-page.tsx b/apps/dashboard/src/features/members/components/members-page.tsx index 0c6dee0e..81acc00b 100644 --- a/apps/dashboard/src/features/members/components/members-page.tsx +++ b/apps/dashboard/src/features/members/components/members-page.tsx @@ -1,25 +1,50 @@ +import { useState } from "react" import { useForm } from "@tanstack/react-form" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" import { Field, FieldDescription, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { HostedResourceBoundary } from "@/features/auth/components/hosted-resource-boundary" import { resolveHostedQueryState } from "@/features/auth/types/hosted-query-state" import { addMemberMutationOptions } from "@/features/members/mutations/member-mutations" import { membersQueryOptions } from "@/features/members/queries/members-query" -import { WorkspacePage, WorkflowPanel } from "@/features/organizations/components/workspace-page" +import { WorkspacePage, WorkflowPanel } from "@/features/orgs/components/workspace-page" + +const ROLE_OPTIONS = [ + { label: "Member", value: "member" }, + { label: "Admin", value: "admin" }, +] export function MembersPage({ organizationId }: { organizationId: string }) { const queryClient = useQueryClient() const members = useQuery(membersQueryOptions(organizationId)) const mutation = useMutation(addMemberMutationOptions(organizationId, queryClient)) const items = members.data?.items ?? [] + const [addOpen, setAddOpen] = useState(false) const form = useForm({ defaultValues: { actorId: "", role: "member" as "admin" | "member" }, onSubmit: async ({ value }) => { await mutation.mutateAsync({ actorId: value.actorId.trim(), role: value.role }) form.reset() + setAddOpen(false) }, }) const state = resolveHostedQueryState({ @@ -35,38 +60,34 @@ export function MembersPage({ organizationId }: { organizationId: string }) { }) const canManageMembers = state.kind === "empty" || state.kind === "ready" - return ( - { + setAddOpen(open) + if (!open) { + form.reset() + mutation.reset() + } + }} + open={addOpen} > - - -
    - {items.map((membership) => ( -
  • - {membership.actorId} - - {membership.role} - -
  • - ))} -
-
-
- {canManageMembers ? ( - }>Add member + +
{ + event.preventDefault() + event.stopPropagation() + void form.handleSubmit() + }} > - { - event.preventDefault() - event.stopPropagation() - void form.handleSubmit() - }} - > + + Add member + + Invitations and email delivery remain deferred; this accepts an existing Actor ID + only. + + +
{(field) => ( @@ -85,31 +106,62 @@ export function MembersPage({ organizationId }: { organizationId: string }) { {(field) => ( Role - field.handleChange(value as "admin" | "member")} value={field.state.value} > - - - + + + + + {ROLE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + )} + {mutation.error ? ( +

+ {mutation.error.message} +

+ ) : null} +
+ + }>Cancel - - {mutation.error ? ( -

- {mutation.error.message} -

- ) : null} +
+ +
+ + ) + + return ( + + + +
    + {items.map((membership) => ( +
  • + {membership.actorId} + + {membership.role} + +
  • + ))} +
- ) : null} +
) } diff --git a/apps/dashboard/src/features/paywall-editor/components/design-system-controls.tsx b/apps/dashboard/src/features/paywall-editor/components/design-system-controls.tsx index 8684a7e5..1543e80d 100644 --- a/apps/dashboard/src/features/paywall-editor/components/design-system-controls.tsx +++ b/apps/dashboard/src/features/paywall-editor/components/design-system-controls.tsx @@ -7,6 +7,13 @@ import { PlusIcon } from "@phosphor-icons/react/dist/ssr/Plus" import { TrashIcon } from "@phosphor-icons/react/dist/ssr/Trash" import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { InspectorColorControl } from "@/features/paywall-editor/components/inspector-color-control" import { useEditorActions } from "@/features/paywall-editor/stores/editor-store-context" import type { @@ -245,6 +252,19 @@ export function ColorControl({ ) } +const BACKGROUND_KIND_OPTIONS = [ + { label: "Colour", value: "color" }, + { label: "Linear gradient", value: "linearGradient" }, + { label: "Radial gradient", value: "radialGradient" }, + { label: "Image", value: "image" }, + { label: "Video", value: "video" }, +] + +const CONTENT_MODE_OPTIONS = [ + { label: "Fit", value: "fit" }, + { label: "Fill", value: "fill" }, +] + export function BackgroundEditor({ document, id, @@ -287,28 +307,43 @@ export function BackgroundEditor({ if (videos[0]) onChange(defaultMediaBackground(type, videos[0].id)) } } + const assetOptions = (value.type === "image" ? images : videos).map((asset) => ({ + label: asset.id, + value: asset.id, + })) + const posterOptions = [ + { label: "No poster", value: "" }, + ...images.map((asset) => ({ label: asset.id, value: asset.id })), + ] const selectedMediaExists = value.type === "image" || value.type === "video" ? document.assets.some((asset) => asset.type === value.type && asset.id === value.assetId) : true return (
- changeType(kind as ProtocolBackground["type"])} value={value.type} > - - - - - - + + + + + {BACKGROUND_KIND_OPTIONS.map((option) => ( + + {option.label} + + ))} + + {images.length === 0 || videos.length === 0 || !selectedMediaExists ? (
{!selectedMediaExists ? ( @@ -456,45 +491,59 @@ export function BackgroundEditor({ ) : null} {value.type === "image" || value.type === "video" ? ( <> - onChange({ ...value, assetId })} value={value.assetId} > - {(value.type === "image" ? images : videos).map((asset) => ( - - ))} - - + + + + + + {CONTENT_MODE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + {value.type === "video" ? ( - + onChange({ ...value, posterAssetId: posterAssetId || undefined }) } value={value.posterAssetId ?? ""} > - - {images.map((asset) => ( - - ))} - + + + + + {posterOptions.map((option) => ( + + {option.label} + + ))} + + ) : null} + isSafeTokenReplacement(designSystem, pendingDelete.category, pendingDelete.id, token.id), + ) + .map((token) => ({ label: `Replace usages with ${token.name}`, value: token.id })), + ] +} + export function DesignSystemPanel() { const { document } = useEditorStore() const editor = useEditorActions() @@ -321,30 +347,25 @@ export function DesignSystemPanel() { tone="warning" >

This style is in use.

- + + + + + {tokenReplacementOptions(designSystem, pendingDelete).map((option) => ( + + {option.label} + + ))} + + +
{document.products.map((reference) => { const selected = products.find((product) => product.id === reference.productId) const label = resolveLocalizedText(document, reference.label, currentLocale) return ( -
@@ -403,7 +446,7 @@ function ConnectedProductBindingContext({ : `${providerLabel ?? "Provider"} · ${mapping.availability} · ${mapping.syncState.replaceAll("_", " ")} · readiness ${readiness.data?.state ?? "unavailable"}` const diagnosticsHref = source.kind === "hosted" - ? `/organizations/${encodeURIComponent(source.organizationId)}/projects/${encodeURIComponent(source.projectId)}/catalog/products/${encodeURIComponent(productId)}?environmentId=${encodeURIComponent(environmentId)}&applicationId=${encodeURIComponent(applicationId)}&returnTo=${encodeURIComponent(hostedStudioHref(source))}` + ? `/orgs/${encodeURIComponent(source.organizationId)}/projects/${encodeURIComponent(source.projectId)}/catalog/products/${encodeURIComponent(productId)}?environmentId=${encodeURIComponent(environmentId)}&applicationId=${encodeURIComponent(applicationId)}&returnTo=${encodeURIComponent(hostedStudioHref(source))}` : undefined return ( @@ -488,18 +531,22 @@ export function MockCommercePanel({ > Preview state - + + + + + {MOCK_PURCHASE_STATES.map((state) => ( + + {state.label} + + ))} + +

Mock product bindings

diff --git a/apps/dashboard/src/features/paywall-editor/components/preview-canvas.test.tsx b/apps/dashboard/src/features/paywall-editor/components/preview-canvas.test.tsx index ff035d48..f56a61b2 100644 --- a/apps/dashboard/src/features/paywall-editor/components/preview-canvas.test.tsx +++ b/apps/dashboard/src/features/paywall-editor/components/preview-canvas.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react" +import { chooseSelectOption } from "@/test/select" import { useLayoutEffect } from "react" import { describe, expect, it } from "vitest" @@ -413,22 +414,25 @@ describe("PreviewCanvas layer metadata", () => { expect(screen.getByTestId("device-status-bar")).toHaveTextContent("82") expect(screen.getByTestId("device-status-bar")).toHaveStyle({ paddingInline: "35px" }) - fireEvent.change(screen.getByRole("combobox", { name: "Preview device" }), { - target: { value: "iphone-17-pro-max" }, - }) + await chooseSelectOption( + screen.getByRole("combobox", { name: "Preview device" }), + "iPhone 17 Pro Max", + ) expect(screen.getByTestId("device-status-bar")).toHaveStyle({ paddingInline: "40px" }) - fireEvent.change(screen.getByRole("combobox", { name: "Preview device" }), { - target: { value: "iphone-17-pro" }, - }) + await chooseSelectOption( + screen.getByRole("combobox", { name: "Preview device" }), + "iPhone 17 Pro", + ) fireEvent.click(screen.getByRole("button", { name: "Use landscape orientation" })) device = region.querySelector("[data-device-width]") expect(device).toHaveAttribute("data-device-width", "874") expect(device).toHaveAttribute("data-device-height", "402") - fireEvent.change(screen.getByRole("combobox", { name: "Preview device" }), { - target: { value: "pixel-10-pro" }, - }) + await chooseSelectOption( + screen.getByRole("combobox", { name: "Preview device" }), + "Pixel 10 Pro", + ) device = region.querySelector("[data-device-width]") expect(device).toHaveAttribute("data-device-width", "920") expect(device).toHaveAttribute("data-device-height", "412") @@ -445,9 +449,7 @@ describe("PreviewCanvas layer metadata", () => { expect(device).toHaveAttribute("data-canvas-fit-mode", "fit") fireEvent.click(screen.getByRole("button", { name: "Open preview settings" })) - fireEvent.change(screen.getByRole("combobox", { name: "Preview locale" }), { - target: { value: "ar" }, - }) + await chooseSelectOption(screen.getByRole("combobox", { name: "Preview locale" }), /^ar · /) await waitFor(() => expect(screen.getByTestId("preview-locale")).toHaveTextContent("ar")) fireEvent.change(screen.getByRole("slider", { name: /Preview text scale/i }), { target: { value: "1.5" }, diff --git a/apps/dashboard/src/features/paywall-editor/components/preview-controls.test.tsx b/apps/dashboard/src/features/paywall-editor/components/preview-controls.test.tsx index 4aca0af2..20d65d56 100644 --- a/apps/dashboard/src/features/paywall-editor/components/preview-controls.test.tsx +++ b/apps/dashboard/src/features/paywall-editor/components/preview-controls.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from "@testing-library/react" +import { chooseSelectOption } from "@/test/select" import { useEffect } from "react" import { describe, expect, it } from "vitest" @@ -57,7 +58,7 @@ describe("preview controls", () => { expect(screen.getByText("150%")).toBeInTheDocument() }) - it("changes the portable default locale in one history step without corrupting defaults", () => { + it("changes the portable default locale in one history step without corrupting defaults", async () => { render( @@ -66,9 +67,7 @@ describe("preview controls", () => { , ) - fireEvent.change(screen.getByRole("combobox", { name: "Default locale" }), { - target: { value: "de" }, - }) + await chooseSelectOption(screen.getByRole("combobox", { name: "Default locale" }), "de") expect(screen.getByTestId("document-default-locale")).toHaveTextContent("de") expect(screen.getByTestId("headline-default")).toHaveTextContent( "Erstelle eine Bezahlschranke, die Menschen sofort verstehen", diff --git a/apps/dashboard/src/features/paywall-editor/components/preview-controls.tsx b/apps/dashboard/src/features/paywall-editor/components/preview-controls.tsx index bf1a9299..bc1ec876 100644 --- a/apps/dashboard/src/features/paywall-editor/components/preview-controls.tsx +++ b/apps/dashboard/src/features/paywall-editor/components/preview-controls.tsx @@ -12,6 +12,15 @@ import { useReactFlow, useViewport } from "@xyflow/react" import type { ReactNode } from "react" import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { Popover, PopoverContent, @@ -81,6 +90,14 @@ function IconControl({ ) } +const DEVICE_ITEM_GROUPS = CANVAS_DEVICE_GROUPS.map((group) => ({ + items: CANVAS_DEVICE_PRESETS.filter((entry) => entry.group === group).map((entry) => ({ + label: entry.label, + value: entry.id, + })), + label: group, +})) + function DeviceSelect({ toolbar }: { toolbar: boolean }) { const canvas = useStudioWorkspaceSelector(selectCanvasPreferences) const workspace = useStudioWorkspaceActions() @@ -104,29 +121,34 @@ function DeviceSelect({ toolbar }: { toolbar: boolean }) { className="text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2" /> )} - + workspace.setCanvasPreference("device", value as StudioCanvasDevice) } value={canvas.device} > - {CANVAS_DEVICE_GROUPS.map((group) => ( - - {CANVAS_DEVICE_PRESETS.flatMap((entry) => - entry.group === group ? ( - - ) : ( - [] - ), - )} - - ))} - + + + + + {DEVICE_ITEM_GROUPS.map((group) => ( + + {group.label} + {group.items.map((option) => ( + + {option.label} + + ))} + + ))} + + ) @@ -150,27 +172,38 @@ function SecondaryPreviewSettings({ toolbar }: { toolbar: boolean }) { if (next) setPreference("countdownPreviewAt", next) } + const previewLocaleOptions = Object.entries(document.localization.locales).map( + ([locale, catalog]) => ({ + label: `${locale} · ${catalog.direction.toUpperCase()}`, + value: locale, + }), + ) + const countdownFieldId = toolbar ? "canvas-toolbar-countdown-preview-at" : "preview-panel-countdown-preview-at" return (
-
+
+ + - + + + + + {localeOptions.map((option) => ( + + {option.label} + + ))} + + +
) diff --git a/apps/dashboard/src/features/paywall-editor/components/property-inspector-accessibility.tsx b/apps/dashboard/src/features/paywall-editor/components/property-inspector-accessibility.tsx index 3b2e90da..b235f069 100644 --- a/apps/dashboard/src/features/paywall-editor/components/property-inspector-accessibility.tsx +++ b/apps/dashboard/src/features/paywall-editor/components/property-inspector-accessibility.tsx @@ -27,6 +27,7 @@ import { NumberField, SelectField, } from "@/features/paywall-editor/components/property-inspector-fields" +import { SelectItem } from "@/components/ui/select" export function seedOptionalLocalizedText(options: { defaultValue: string @@ -141,8 +142,8 @@ export function TextAccessibilitySection({ } value={node.accessibility.role} > - - + Text + Heading {node.accessibility.role === "heading" ? ( {document.designSystem.backgrounds.map((token) => ( - + ))} ) : null} @@ -443,9 +444,9 @@ export function DocumentBackgroundEditor({ value={value.assetId} > {(value.type === "image" ? imageAssets : videoAssets).map((asset) => ( - + ))} - - + Fit + Fill {value.type === "video" ? ( - + No poster {document.assets.flatMap((asset) => asset.type === "image" ? ( - + ) : ( [] ), @@ -558,11 +559,11 @@ export function ShadowSection({ node }: { node: ProtocolNode }) { } value={shadow?.type ?? "none"} > - - - + {shadow?.type === "shadowToken" ? ( {document.designSystem.shadows.map((token) => ( - + ))} ) : null} diff --git a/apps/dashboard/src/features/paywall-editor/components/property-inspector-basic-nodes.tsx b/apps/dashboard/src/features/paywall-editor/components/property-inspector-basic-nodes.tsx index 02433af1..28c9829b 100644 --- a/apps/dashboard/src/features/paywall-editor/components/property-inspector-basic-nodes.tsx +++ b/apps/dashboard/src/features/paywall-editor/components/property-inspector-basic-nodes.tsx @@ -57,6 +57,7 @@ import { TypographySection, VisibilitySection, } from "@/features/paywall-editor/components/property-inspector-layout" +import { SelectItem } from "@/components/ui/select" export function ScrollContainerInspector({ layout }: { layout: ScrollContainer }) { const { document } = useInspectorContext() @@ -90,10 +91,10 @@ export function ScrollContainerInspector({ layout }: { layout: ScrollContainer } }} value={presentation} > - - + {screen && !isInitial && presentation === "screen" ? ( - + + + + + {ASSET_SOURCE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + +