sporades 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGES.md +16 -3
  2. package/README.md +39 -3
  3. package/bin/sporades-host-helper.js +49 -14
  4. package/bin/sporades.js +3348 -276
  5. package/dist/cli/cli-version.d.ts +1 -1
  6. package/dist/cli/cli-version.js +1 -1
  7. package/dist/cli/sporades-host-helper.js +31 -14
  8. package/dist/cli/sporades-host-helper.js.map +1 -1
  9. package/dist/cli/sporades.js +17 -2
  10. package/dist/cli/sporades.js.map +1 -1
  11. package/dist/client.d.ts +1 -0
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/client.js +1 -0
  14. package/dist/client.js.map +1 -1
  15. package/dist/database-runtime.d.ts.map +1 -1
  16. package/dist/database-runtime.js +4 -0
  17. package/dist/database-runtime.js.map +1 -1
  18. package/dist/generated-source-manifest.json +38 -2
  19. package/dist/jobs-runtime.d.ts +21 -0
  20. package/dist/jobs-runtime.d.ts.map +1 -1
  21. package/dist/jobs-runtime.js +375 -14
  22. package/dist/jobs-runtime.js.map +1 -1
  23. package/dist/server-runtime-source.d.ts +4 -1
  24. package/dist/server-runtime-source.d.ts.map +1 -1
  25. package/dist/server-runtime-source.js +571 -20
  26. package/dist/server-runtime-source.js.map +1 -1
  27. package/dist/server.d.ts +13 -1
  28. package/dist/server.d.ts.map +1 -1
  29. package/dist/server.js +23 -8
  30. package/dist/server.js.map +1 -1
  31. package/dist/stripe-team-billing-provider.d.ts +33 -0
  32. package/dist/stripe-team-billing-provider.d.ts.map +1 -0
  33. package/dist/stripe-team-billing-provider.js +564 -0
  34. package/dist/stripe-team-billing-provider.js.map +1 -0
  35. package/dist/team-billing-convergence.d.ts +7 -0
  36. package/dist/team-billing-convergence.d.ts.map +1 -0
  37. package/dist/team-billing-convergence.js +359 -0
  38. package/dist/team-billing-convergence.js.map +1 -0
  39. package/dist/team-billing-erasure.d.ts +29 -0
  40. package/dist/team-billing-erasure.d.ts.map +1 -0
  41. package/dist/team-billing-erasure.js +310 -0
  42. package/dist/team-billing-erasure.js.map +1 -0
  43. package/dist/team-billing-import.d.ts +43 -0
  44. package/dist/team-billing-import.d.ts.map +1 -0
  45. package/dist/team-billing-import.js +191 -0
  46. package/dist/team-billing-import.js.map +1 -0
  47. package/dist/team-billing-management.d.ts +48 -0
  48. package/dist/team-billing-management.d.ts.map +1 -0
  49. package/dist/team-billing-management.js +545 -0
  50. package/dist/team-billing-management.js.map +1 -0
  51. package/dist/team-billing-runtime.d.ts +153 -0
  52. package/dist/team-billing-runtime.d.ts.map +1 -0
  53. package/dist/team-billing-runtime.js +1004 -0
  54. package/dist/team-billing-runtime.js.map +1 -0
  55. package/dist/team-billing-subscription-semantics.d.ts +12 -0
  56. package/dist/team-billing-subscription-semantics.d.ts.map +1 -0
  57. package/dist/team-billing-subscription-semantics.js +22 -0
  58. package/dist/team-billing-subscription-semantics.js.map +1 -0
  59. package/dist/teams-runtime.d.ts +3 -0
  60. package/dist/teams-runtime.d.ts.map +1 -1
  61. package/dist/teams-runtime.js +28 -5
  62. package/dist/teams-runtime.js.map +1 -1
  63. package/dist/templates/client-runtime-template.d.ts.map +1 -1
  64. package/dist/templates/client-runtime-template.js +23 -0
  65. package/dist/templates/client-runtime-template.js.map +1 -1
  66. package/dist/templates/scaffold-template.js +7 -2
  67. package/dist/templates/scaffold-template.js.map +1 -1
  68. package/dist/templates/server-bundle-entry.d.ts.map +1 -1
  69. package/dist/templates/server-bundle-entry.js +2 -0
  70. package/dist/templates/server-bundle-entry.js.map +1 -1
  71. package/package.json +5 -1
  72. package/src/types/client.d.ts +47 -0
  73. package/src/types/server.d.ts +110 -4
  74. package/src/types/team-billing-import.d.ts +54 -0
package/CHANGES.md CHANGED
@@ -1,10 +1,23 @@
1
1
  # Changes
2
2
 
3
- ## Unreleased - 2026-08-24
3
+ - Expose the verified provider-free Team Billing projection to active Capsule
4
+ query and mutation contexts and to exact-Team Join admission. Current members
5
+ may be authorized for safe reads while all customer-directed commands remain
6
+ current-Team-admin operations; retained context handles are revoked.
7
+ - Add an offline `sporades/server/team-billing-import` seam for exact,
8
+ provider-free import of verified legacy Subscription snapshots and replay
9
+ guards. Incomplete or conflicting evidence fails closed, including drift in
10
+ durable Event ordering, terminal-latch safety fields, and contradictory
11
+ Subscription Event/state tuples. The public adapter type now declares the
12
+ complete named-dialect, execution, prepared-statement, and transaction seam.
4
13
 
5
- Changes since v0.9.0.
14
+ ## Unreleased - 2026-08-25
6
15
 
7
- No changes detected.
16
+ Changes since v0.9.2.
17
+
18
+ ### 🐛 Bug Fixes
19
+
20
+ - Wait for hosted capsule readiness (81c23604).
8
21
 
9
22
  ## 0.8.5 - 2026-08-15
10
23
 
package/README.md CHANGED
@@ -58,7 +58,32 @@ sporades host push --restart
58
58
  email-bound Join links, membership application roles, and explicit Team ACL
59
59
  decisions—without a hidden current Team or automatic data partitioning.
60
60
  Trusted `teams.admitJoin` policies may make atomic seat decisions from
61
- read-only app state hidden by the joining user's ordinary row ACLs.
61
+ read-only app state hidden by the joining user's ordinary row ACLs and the
62
+ exact Team's provider-free verified billing projection.
63
+ - **Headless Team Billing, app-rendered**: an optional `teamBilling` declaration
64
+ binds stable product keys to exact sandbox/live Stripe Prices and a fixed or
65
+ accepted-Team-member quantity policy. Sporades rechecks current Team-admin
66
+ authority plus the Capsule's transaction-bound policy on every operation and
67
+ exposes the closed, provider-free `teamBilling.get(teamId)` projection plus
68
+ durable `teamBilling.startCheckout(...)`, `teamBilling.openPortal(...)`, and
69
+ `teamBilling.requestPlanTransition(...)`. Portal owns compatible same-policy
70
+ switches; Sporades manages fixed-to-Team-counted transitions and keeps a
71
+ Team-counted Subscription converged after membership commits.
72
+ `teamBilling.prepareErasure(...)` durably expires or safely resolves every
73
+ issued Checkout and immediately cancels known or newly discovered live
74
+ Subscriptions before a transaction-bound server admission permits the app's
75
+ separate local deletion. Provider-free identity tombstones reject late
76
+ entitlement recreation; the Stripe Customer is not deleted.
77
+ Those commands may return only validated, short-lived Stripe-hosted URL
78
+ capabilities. Verified Checkout, Subscription, and failed-invoice evidence
79
+ converges through runtime-owned semantic ratchets and terminal latches;
80
+ malformed or ambiguous evidence fails closed without exposing provider
81
+ identifiers. The Capsule owns every component, route, message, and
82
+ entitlement decision.
83
+ Offline migration tools can import complete verified legacy snapshots and
84
+ provider-free replay guards through `sporades/server/team-billing-import`;
85
+ the seam performs no provider I/O and rejects incomplete or changed tuples,
86
+ including corrupted durable Event ordering or terminal-latch state.
62
87
  - **Realtime everything**: configuration free - automatic notifications, even for Postgress and AWS-S3
63
88
 
64
89
  ## Documentation
@@ -68,7 +93,8 @@ sporades host push --restart
68
93
  - [Feature reference](https://mgscox.github.io/sporades/guide/reference): exhaustive contracts and
69
94
  command lookup, split into focused topic pages.
70
95
  - [Built-in Teams](https://mgscox.github.io/sporades/reference/teams): collaboration model, Join
71
- links, application roles, and explicit Team ACL decisions.
96
+ links, application roles, explicit Team ACL decisions, and the headless Team
97
+ Billing declaration and safe projection.
72
98
  - [Access-key server and operator reference](https://mgscox.github.io/sporades/reference/server-runtime#gate-handlers-with-requireauth):
73
99
  scoped Bearer admission, user-owned lifecycle, Credential provenance, and
74
100
  the bounded operator CLI.
@@ -169,9 +195,19 @@ Sporades currently includes:
169
195
  retries converge and no Capsule billing state changes automatically. The Job
170
196
  delivers one Verified Stripe event to the blank Capsule's single
171
197
  `stripeEvents: stripeEvent(...)` policy seam under the userless Privileged
172
- audit lifecycle. Generated policy ignores unknown events and tells authors to
198
+ audit lifecycle. Capsules that need one serialized, all-or-nothing database
199
+ consequence can opt into `stripeEvent({ consequence: "atomic" }, handler)`;
200
+ the legacy declaration remains available for cooperative long-lived handlers.
201
+ Generated policy ignores unknown events and tells authors to
173
202
  make deliberate consequences idempotent and order-independent, reject stale
174
203
  later-arriving observations, and avoid logging or retaining raw provider data.
204
+ A successful reserved Stripe Event Job retains its verified payload for 30
205
+ days, then bounded runtime cleanup leaves only the digest-backed terminal
206
+ replay tombstone. Unresolved deliveries retain their retry/repair payload;
207
+ this includes legacy successes with absent or malformed completion timestamps,
208
+ plus canonical near-ceiling settlements whose exact deadline is
209
+ unrepresentable. Safe operator inspection distinguishes those cases without
210
+ exposing provider data.
175
211
  The catalogue owns each Price mode; browser input owns neither mode nor Stripe
176
212
  identity. Existing and non-blank Capsules remain unchanged.
177
213
  - Local Dev sessions with rebuilds, WebSocket reconnects, SQLite persistence,
@@ -177,8 +177,24 @@ var ACCESS_KEY_CLIENT_ADDRESS_HEADER = "x-sporades-client-address";
177
177
  // src/access-keys-runtime.ts
178
178
  var UNKNOWN_ACCESS_KEY_DIGEST = Buffer.from("4f7c77f7b9231094754542ed50fdfd62a2cf24a5e961b61f899b85b6fe33c72b", "hex");
179
179
 
180
+ // src/team-billing-management.ts
181
+ var CLAIM_TTL_MS = 5 * 60 * 1e3;
182
+
183
+ // src/team-billing-convergence.ts
184
+ var RANK = Object.freeze({ active: 20, cancelling: 30, "past-due": 40, cancelled: 50 });
185
+
186
+ // src/team-billing-runtime.ts
187
+ var CHECKOUT_CONTINUATION_TTL_DEFAULT_SECONDS = 10 * 60;
188
+ var CHECKOUT_CONTINUATION_TTL_MAX_SECONDS = 30 * 60;
189
+
190
+ // src/team-billing-erasure.ts
191
+ var CLAIM_TTL_MS2 = 5 * 6e4;
192
+
180
193
  // src/jobs-runtime.ts
181
194
  var nodeCryptoModule = process.getBuiltinModule("node:crypto");
195
+ var STRIPE_EVENT_PAYLOAD_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
196
+ var REDACTED_STRIPE_EVENT_PAYLOAD = JSON.stringify({ kind: "stripe-event", retained: false });
197
+ var STRIPE_EVENT_PAYLOAD_SENTINEL_RECHECK_MS = 24 * 60 * 60 * 1e3;
182
198
  var JOB_AUTH_SNAPSHOT_MAX_BYTES = 8 * 1024;
183
199
  var MAX_JOB_TIMESTAMP_MS = Date.parse("9999-12-31T23:59:59.999Z");
184
200
  var MIN_JOB_TIMESTAMP_MS = Date.parse("0000-01-01T00:00:00.000Z");
@@ -440,6 +456,8 @@ var transactionBeforeCommitChecks2 = Symbol.for("sporades.database.transactionBe
440
456
  // src/server-runtime-source.ts
441
457
  var trustedReadTransactionAdapter = Symbol("sporades.trustedReadTransactionAdapter");
442
458
  var runtimeOwnedJobEnqueueHandler = Symbol("sporades.runtimeOwnedJobEnqueueHandler");
459
+ var atomicStripeEventDefinitionBrand = Symbol.for("sporades.stripeEvent.atomicDefinition");
460
+ var atomicStripeFenceContention = Symbol("sporades.atomicStripeFenceContention");
443
461
  function logPayloadMaxBytes(config = {}) {
444
462
  const configured = Number(config.logs?.payloadMaxBytes ?? config.logging?.payloadMaxBytes);
445
463
  return Number.isInteger(configured) && configured > 0 ? configured : 4096;
@@ -595,7 +613,7 @@ function writeEnvelope(result, failed = false) {
595
613
  }
596
614
 
597
615
  // src/cli/cli-version.ts
598
- var CLI_VERSION = "0.9.1";
616
+ var CLI_VERSION = "0.9.3";
599
617
 
600
618
  // src/cli/schedule-inspection-envelope.ts
601
619
  var SCHEDULE_DIAGNOSTIC_FIELDS = /* @__PURE__ */ new Set([
@@ -1955,25 +1973,42 @@ async function verifyInstalledRelease(request, release, installData, previousCur
1955
1973
  }
1956
1974
  async function verifyInstalledPublicTree(request, timeoutMs) {
1957
1975
  const url = new URL("/", `${request.host.scheme ?? "https"}://${request.capsule.subname}.${request.host.domain}`).toString();
1958
- try {
1959
- const response = await fetch(url, { headers: { accept: "text/html" }, signal: AbortSignal.timeout(timeoutMs) });
1960
- const contentType = response.headers.get("content-type") ?? "";
1961
- if (!response.ok || !contentType.toLowerCase().startsWith("text/html")) {
1962
- return {
1976
+ const deadline = Date.now() + timeoutMs;
1977
+ let lastFailure = {
1978
+ ok: false,
1979
+ data: { url, path: "/", responding: false, statusCode: null, html: false },
1980
+ error: { message: "Hosted Capsule installed public tree did not respond." }
1981
+ };
1982
+ while (Date.now() < deadline) {
1983
+ try {
1984
+ const response = await fetch(url, {
1985
+ headers: { accept: "text/html" },
1986
+ signal: AbortSignal.timeout(Math.max(1, deadline - Date.now()))
1987
+ });
1988
+ const contentType = response.headers.get("content-type") ?? "";
1989
+ if (response.ok && contentType.toLowerCase().startsWith("text/html")) {
1990
+ await response.body?.cancel();
1991
+ return { ok: true, data: { url, path: "/", responding: true, statusCode: response.status, html: true } };
1992
+ }
1993
+ await response.body?.cancel();
1994
+ lastFailure = {
1963
1995
  ok: false,
1964
1996
  data: { url, path: "/", responding: response.ok, statusCode: response.status, html: false },
1965
1997
  error: { message: "Hosted Capsule installed public tree did not serve its HTML entry." }
1966
1998
  };
1999
+ } catch {
2000
+ lastFailure = {
2001
+ ok: false,
2002
+ data: { url, path: "/", responding: false, statusCode: null, html: false },
2003
+ error: { message: "Hosted Capsule installed public tree did not respond." }
2004
+ };
2005
+ }
2006
+ const remainingMs = deadline - Date.now();
2007
+ if (remainingMs > 0) {
2008
+ await delay(Math.min(100, remainingMs));
1967
2009
  }
1968
- await response.body?.cancel();
1969
- return { ok: true, data: { url, path: "/", responding: true, statusCode: response.status, html: true } };
1970
- } catch {
1971
- return {
1972
- ok: false,
1973
- data: { url, path: "/", responding: false, statusCode: null, html: false },
1974
- error: { message: "Hosted Capsule installed public tree did not respond." }
1975
- };
1976
2010
  }
2011
+ return lastFailure;
1977
2012
  }
1978
2013
  function readVerificationHealthTimeoutMs(request) {
1979
2014
  const value = Number(request.verification?.healthTimeoutMs ?? 1e4);