platform-types 1.10.705 → 1.10.707

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -472,7 +472,7 @@ declare global {
472
472
  remaining: number | null;
473
473
  }
474
474
  /**
475
- * Full subscription snapshot. Three producers emit it:
475
+ * Full subscription snapshot. Four producers emit it:
476
476
  *
477
477
  * - `GET /subscription` — the full `usage[]`, including the row-counted
478
478
  * `webhooks` / `maxWarehouses` meters. The two are present together or
@@ -481,7 +481,12 @@ declare global {
481
481
  * - the `GET /store` subscription embed — WITHOUT those two meters (monthly
482
482
  * and lifetime only) and without `provider`;
483
483
  * - the reply of the MANAGER `PUT /platform/stores/{storeId}/subscription` —
484
- * built by the same assembly as `GET /subscription`, meters included.
484
+ * built by the same assembly as `GET /subscription`, meters included, read
485
+ * strongly consistent after the write;
486
+ * - the `data` of a `POST /subscription/cancel` that ended a trial with no
487
+ * payment method — the same assembly again, read after the write; `data` is
488
+ * absent from that 200 only when the read itself failed after the trial had
489
+ * already ended. A cancel that reached a provider answers no `data`.
485
490
  *
486
491
  * The WebSocket only nudges a refetch on subscription/entitlement changes; it
487
492
  * never carries this payload.
@@ -500,6 +505,27 @@ declare global {
500
505
  * (`externalCustomerId`/`externalSubscriptionId`) are never exposed.
501
506
  */
502
507
  provider?: SubscriptionProvider;
508
+ /**
509
+ * Whether the row holds a live provider subscription that `POST /subscription/cancel`
510
+ * can act on: a provider subscription id of its rail's kind (Stripe `sub_…`; a
511
+ * MercadoPago PreApproval id or manual-mode anchor) on a row that is not `canceled`.
512
+ * Unlike `provider`, never a fallback. Carried by every producer, the `GET /store`
513
+ * embed included.
514
+ *
515
+ * `true` does not promise the portal or a payment-method update — those also depend
516
+ * on the rail: MercadoPago has no hosted portal (`501 BILLING_NOT_CONFIGURED`), a
517
+ * manual-mode anchor has no payment method to update (`501`), and a Stripe portal
518
+ * needs the `cus_` Customer (a MANAGER-set `sub_` row without one is `404`).
519
+ *
520
+ * When `false`, those three routes answer `404 NO_SUBSCRIPTION` without calling a
521
+ * provider — no row, a `cus_`-only Stripe checkout, a canceled row, an id from the
522
+ * other rail, and any provider-less row — with exactly two cancel exceptions, both
523
+ * on a provider-less row (no `provider`, provider subscription or customer id):
524
+ * a `trialing` row that is not the platform store, has a `trialEndsAt` and is not
525
+ * covered by a courtesy gift (`freeUntil`) has its trial ended (200); and a row no
526
+ * longer `trialing` that carries `trialEndedFromTier` answers 200 with no write.
527
+ */
528
+ hasProviderSubscription: boolean;
503
529
  billingCycle: BillingCycle | null;
504
530
  currentPeriodStart: number | null;
505
531
  currentPeriodEnd: number | null;
@@ -518,13 +544,15 @@ declare global {
518
544
  */
519
545
  canceledFromTier?: PlanTier | null;
520
546
  /**
521
- * The paid tier a trial ran on when it lapsed with no payment method —
522
- * set only by the grace sweep when it moves a provider-less `trialing`
523
- * row (no provider, no provider subscription or customer id) whose
524
- * `trialEndsAt` has passed to `basico`/`active`. Cleared (`null`) when
525
- * the store re-subscribes to a paid tier or is canceled — a cancel
526
- * records its own lost plan in `canceledFromTier`. `null` when absent —
527
- * every row that never expired that way, or has moved on since.
547
+ * The paid tier a trial ran on when it ended with no payment method —
548
+ * set only when a provider-less `trialing` row (no provider, no provider
549
+ * subscription or customer id) moves to `basico`/`active`: by the grace
550
+ * sweep once its `trialEndsAt` has passed, or by `POST /subscription/cancel`
551
+ * ending it early (that cancel sets this, never `canceledFromTier`).
552
+ * Cleared (`null`) when the store re-subscribes to a paid tier or a provider
553
+ * subscription is canceled — that cancel records its own lost plan in
554
+ * `canceledFromTier`. `null` when absent — every row that never ended a
555
+ * trial that way, or has moved on since.
528
556
  */
529
557
  trialEndedFromTier?: PlanTier | null;
530
558
  entitlements: SubscriptionEntitlementEntry[];
@@ -633,7 +661,11 @@ declare global {
633
661
  * it together with `provider`.
634
662
  */
635
663
  mode?: 'preapproval' | 'manual';
636
- /** The paid tier an expired trial dropped from; `null` when a write cleared it. */
664
+ /**
665
+ * The paid tier a trial with no payment method dropped from — one that expired
666
+ * (the grace sweep) or one the tenant ended through `POST /subscription/cancel`;
667
+ * `null` when a write cleared it.
668
+ */
637
669
  trialEndedFromTier?: PlanTier | null;
638
670
  };
639
671
  /**
@@ -648,7 +680,9 @@ declare global {
648
680
  * `fullName: ''`), `before` holds the trial's `status`/`planTier`/
649
681
  * `trialEndsAt`, and `after` holds `status: 'active'`,
650
682
  * `planTier: 'basico'`, `trialEndsAt: null` and `trialEndedFromTier`
651
- * (the trial's tier).
683
+ * (the trial's tier). `subscription.trial_canceled` rows have the same
684
+ * `before`/`after` and name the tenant user who ended the trial through
685
+ * `POST /subscription/cancel` as `actor`.
652
686
  *
653
687
  * ⚠️ The same partition also carries entitlement-override and advancedPricing
654
688
  * rows whose `before`/`after` hold different keys, so consumers must read
@@ -726,9 +760,10 @@ declare global {
726
760
  }
727
761
  /**
728
762
  * One tier's `SK: PLAN` template row, as assessed by
729
- * `report-plan-catalog-drift` (`POST /platform/operations`). Plan features
730
- * are code (`planFeatureMatrix.ts`) and cannot drift; the template row is
731
- * the only catalogue state a seeder/admin write can still leave stale.
763
+ * `report-plan-catalog-drift` (`POST /platform/operations`). Plan feature
764
+ * DEFAULTS are code (`planFeatureMatrix.ts`) and cannot drift; the template
765
+ * row — its live `grants` overlay included — is the only catalogue state a
766
+ * seeder/admin write can still leave stale.
732
767
  */
733
768
  interface TierCatalogDrift {
734
769
  tier: PlanTier;
@@ -742,6 +777,19 @@ declare global {
742
777
  * place to echo a catalogue's payment-provider IDs.
743
778
  */
744
779
  missingProviderIds: string[];
780
+ /**
781
+ * Keys in the template row's `grants` overlay that the api does not
782
+ * declare (retired or misspelt), sorted. Inert — every reader ignores them
783
+ * and the code default applies — but stored, so they are drift: a `null`
784
+ * for that key on `PATCH /platform/billing/plans/grants` removes them.
785
+ */
786
+ undeclaredGrantKeys: string[];
787
+ /**
788
+ * Declared keys whose overlay value has the wrong shape for their type
789
+ * (a `limit` on a boolean key, a fraction, `-2`), sorted. Ignored on read
790
+ * exactly like an undeclared key, and reported for the same reason.
791
+ */
792
+ malformedGrantKeys: string[];
745
793
  }
746
794
  /** One store holding at least one `OVERRIDE#` row under a key the api no longer declares. */
747
795
  interface OrphanOverrideStore {
@@ -788,7 +836,9 @@ declare global {
788
836
  interface PlanCatalogDriftReport {
789
837
  /**
790
838
  * True when ANY tier lacks a template row, a paid one is missing a provider
791
- * identifier, or any store holds an override under an undeclared key.
839
+ * identifier, a tier's `grants` overlay holds an undeclared key
840
+ * (`undeclaredGrantKeys`) or a wrong-shape value (`malformedGrantKeys`), or
841
+ * any store holds an override under an undeclared key.
792
842
  *
793
843
  * ⚠️ An UNBUILT roster (`orphanOverrides.rosterBuiltAt === null`) does not
794
844
  * flip this. A stage whose scan has never completed would otherwise answer
@@ -804,6 +854,112 @@ declare global {
804
854
  tiers: TierCatalogDrift[];
805
855
  orphanOverrides: OrphanOverrideDrift;
806
856
  }
857
+ /**
858
+ * One feature's grant on one tier, as `PATCH /platform/billing/plans/grants`
859
+ * takes and reports it: `{ enabled }` for a boolean key, `{ limit }` for a
860
+ * numeric or metered one (`-1` = unlimited).
861
+ */
862
+ type PlanGrantValue = {
863
+ enabled: boolean;
864
+ } | {
865
+ limit: number;
866
+ };
867
+ /**
868
+ * Body of `PATCH /platform/billing/plans/grants` (MANAGER). One request sets
869
+ * one or more keys on any tiers, atomically: `grants[key][tier]` is the new
870
+ * grant, or `null` to drop that tier's overlay entry (the code default
871
+ * applies again). A value equal to the code default is stored as no entry,
872
+ * so the overlay stays sparse. The final ladder of every changed key must
873
+ * rise with `displayOrder` (`FEATURE_LADDER_NON_MONOTONIC` otherwise).
874
+ */
875
+ interface PlanGrantsWriteRequest {
876
+ /**
877
+ * Keyed by feature key. A `string` key rather than `FeatureKey` because a
878
+ * `null` may name an undeclared key, to clear an inert overlay entry; a
879
+ * non-null value for one is refused (`PLAN_GRANT_UNKNOWN_KEY`).
880
+ */
881
+ grants: Record<string, Partial<Record<PlanTier, PlanGrantValue | null>>>;
882
+ /** Screened operator reason (10-500 characters, no personal data); lands in each tier's audit entry. */
883
+ reason: string;
884
+ /**
885
+ * Required on a live write: the comma-joined sorted keys the request
886
+ * changes — the string a dry run of the same body returns. It names the
887
+ * keys, not the tiers or values, so it proves the caller meant those keys;
888
+ * the dry run is where the tiers and the impact are reviewed.
889
+ */
890
+ confirm?: string;
891
+ /** `true` writes nothing and returns the impact plus the `confirm` string. */
892
+ dryRun?: boolean;
893
+ }
894
+ /** One key's effective grant on one tier, before and after the write. */
895
+ interface PlanGrantChange {
896
+ key: string;
897
+ /** `null` for an undeclared key — it has no effective grant on either side. */
898
+ before: PlanGrantValue | null;
899
+ after: PlanGrantValue | null;
900
+ }
901
+ /**
902
+ * Stores on a tier that a reduction would bite, for one key: they lose a
903
+ * boolean feature, or their current usage exceeds a lowered limit. Measured
904
+ * per store with its own overrides applied, so a store whose override keeps
905
+ * the old grant is not listed. A `readonly` / `canceled` store is never
906
+ * examined — its plan already lapsed, so the reduction takes nothing more.
907
+ */
908
+ interface PlanGrantImpact {
909
+ key: string;
910
+ storeIds: string[];
911
+ /**
912
+ * `false` for a lowered limit with no usage counter to compare against
913
+ * (`priceListsMax`): `storeIds` is then
914
+ * empty because nothing was measured, never because nothing is over.
915
+ */
916
+ measured: boolean;
917
+ }
918
+ /** One affected tier in a `PATCH /platform/billing/plans/grants` result. */
919
+ interface PlanGrantsTierResult {
920
+ tier: PlanTier;
921
+ changes: PlanGrantChange[];
922
+ /**
923
+ * Stores on this tier, any status: those whose SUBSCRIPTION row is on it
924
+ * (`planTier-status`) plus, on `profesional` — the tier a store with no
925
+ * SUBSCRIPTION row resolves to — every store with no row. A lower bound
926
+ * when `truncated`.
927
+ */
928
+ subscribedStores: number;
929
+ /** Dry run only: per reduced key, the stores it bites. Empty when nothing on this tier is reduced. */
930
+ impact?: PlanGrantImpact[];
931
+ /**
932
+ * Dry run: `impact` is a lower bound — more live stores than the walk
933
+ * examines (100), the request's time budget ran out, one store could not
934
+ * be read, or the tier's store list itself is short. Live write (present
935
+ * only when `true`): the follow-up did not reach every store — the budget
936
+ * ran out, a frame or grace reconcile failed, or the store list is short;
937
+ * a `PLAN_GRANTS_FANOUT_INCOMPLETE` error row carries the counts. On a
938
+ * `storefront` / `customDomain` change, every listed store it did not
939
+ * settle has a due-now grace entry the hourly storefront grace sweep
940
+ * reconciles (unless the row says some were not scheduled).
941
+ */
942
+ truncated?: boolean;
943
+ /** Live write only: how many stores the `subscription` socket frame was sent to. */
944
+ notifiedStores?: number;
945
+ /**
946
+ * Live write only, and only when the tier's `storefront` or `customDomain`
947
+ * changed: how many stores' shop grace a reconcile settled — written
948
+ * (a lost grant starts the 14-day grace; a regained one clears it) or
949
+ * already in line. A failed reconcile, a store gone, or a store the budget
950
+ * did not reach is not counted.
951
+ */
952
+ reconciledStores?: number;
953
+ }
954
+ /** `data` of `PATCH /platform/billing/plans/grants`. */
955
+ interface PlanGrantsWriteResult {
956
+ dryRun: boolean;
957
+ /** `false` on a dry run and on an identical replay (no change, no write, no audit). */
958
+ written: boolean;
959
+ /** The comma-joined sorted changed keys — what a live run must send as `confirm`. Empty when nothing changes. */
960
+ confirm: string;
961
+ tiers: PlanGrantsTierResult[];
962
+ }
807
963
  }
808
964
  export type BillingProviderName = SubscriptionProvider;
809
965
  export type InvoiceSummary = globalThis.InvoiceSummary;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platform-types",
3
- "version": "1.10.705",
3
+ "version": "1.10.707",
4
4
  "description": "Shared wire and entity contracts for the platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",