fix(trial): align retry-after and hosted signup flows

This commit is contained in:
rcourtman
2026-03-27 12:04:44 +00:00
parent 35bae3e62b
commit 57cc71fa0b
18 changed files with 481 additions and 234 deletions
@@ -681,6 +681,11 @@ continuity, but `session_store.go` and `csrf_store.go` must immediately
rewrite hashed canonical persistence during load instead of leaving raw-token
files on the primary runtime path until a later save side effect happens to
run.
That same shared `internal/api/` dependency also assumes local commercial-trial
handoff remains human-usable: lifecycle-adjacent trial CTAs may allow a short
burst of retries, but the backend contract must return the real remaining
backoff through `Retry-After` plus `details.retry_after_seconds` so setup and
install-adjacent surfaces do not drift into generic “try again later” behavior.
That same shared `internal/api/` dependency also assumes session-carried OIDC
refresh tokens stay fail-closed at rest: `session_store.go` may only persist
or recover those tokens through encrypted-at-rest session payloads, and any
@@ -227,6 +227,14 @@ Own canonical runtime payload shapes between backend and frontend.
install-state surface must describe that prepared token path consistently
with the live runtime behavior rather than directing the operator to create
another install token manually.
21. Keep local trial-start transport explicit on the shared commercial API
boundary: `/api/license/trial/start` must preserve the hosted-signup
redirect contract during the allowed retry burst, then return the actual
remaining backoff in both `Retry-After` and
`details.retry_after_seconds` once the burst is exceeded. Hosted
self-serve verification failures may render owned HTML, but they must
preserve originating Pulse context instead of collapsing into generic
control-plane failures.
## Current State
@@ -1700,6 +1708,19 @@ both sides instead of relying only on broad settings-surface coverage on the
security side: token settings changes must continue to carry the direct
`api-token-management-surface` API-contract proof together with the
security-side surface proof.
That same shared commercial API boundary now also owns the local trial-start
transport contract. `/api/license/trial/start` may allow a short human-scale
burst of retries while the hosted redirect handoff remains canonical, but once
that burst is exceeded it must return the actual remaining backoff in both the
`Retry-After` header and the JSON `details.retry_after_seconds` payload instead
of a fixed window guess or a text-only error. `internal/api/contract_test.go`
must pin both the hosted-signup redirect response and the rate-limited response
in the same slice as any handler change.
That same shared commercial API boundary also owns hosted self-serve failure
transport semantics. Hosted trial request and verification failures may render
owned HTML pages, but they must preserve the originating Pulse instance and
customer form context instead of collapsing into generic control-plane failures
or dead-end text with no route back to the originating runtime.
That same boundary must also keep token scope presets lazily derived from the
canonical scope constants: `apiTokenManagerModel.ts` may expose
`getAPITokenScopePresets()`, but it must not publish an eagerly evaluated
@@ -267,13 +267,18 @@ The trial-start rate-limit contract is part of that same boundary. Local
`/api/license/trial/start` retries are allowed as a short human-scale burst so
operators can revisit the hosted handoff without getting locked out for a day,
and both the local app server and hosted trial signup limiters must return the
actual remaining backoff through `Retry-After` rather than a coarse full-window
guess. Shared trial-start presentation must treat that backoff as canonical and
surface it consistently instead of flattening every `429` into generic copy.
actual remaining backoff through `Retry-After` and
`details.retry_after_seconds` rather than a coarse full-window guess. Shared
trial-start presentation must treat that backoff as canonical and surface it
consistently instead of flattening every `429` into generic copy.
For the hosted self-serve flow, that also means the public trial pages and form
posts must render the owned Pulse trial experience with preserved instance/form
state when rate limited, rather than dropping users onto a generic control-plane
`Too Many Requests` response.
The same owned hosted-trial failure experience applies to invalid or expired
verification links. The customer must stay inside the Pulse-owned retry path
with the originating-instance context preserved, rather than landing on a
generic hosted error page that reads like a detached SaaS funnel.
The hosted trial handoff page is part of that same boundary as well. It may
still use a secure hosted Stripe-backed session internally, but the customer
copy must present the flow as starting a trial for the originating Pulse
@@ -642,6 +642,11 @@ raw-token `sessions.json` and `csrf_tokens.json` files may load for upgrade
continuity, but `session_store.go` and `csrf_store.go` must immediately
rewrite hashed canonical persistence on load so adjacent storage and recovery
transport does not keep running against primary-path raw-token files.
That same shared `internal/api/` dependency also assumes customer-visible
commercial retry guidance stays canonical when storage- or recovery-adjacent
surfaces invoke trial or billing handoffs: the backend must return the real
remaining backoff through `Retry-After` and `details.retry_after_seconds`
instead of leaving neighboring surfaces to guess or hardcode retry windows.
That same shared `internal/api/` dependency now also assumes adjacent
commercial helper surfaces speak in monitored-system terms: recovery- or
storage-adjacent API wiring may consume the canonical monitored-system ledger