Stable releases previously required a byte-identical tree with the
promoted beta (only CHANGELOG.md could differ) and local releases had
to run exactly at the origin/main tip with an atomic main+tag push.
Together these froze main for the whole beta-to-stable window.
Relax both gates while keeping the beta soak mandatory:
- stable still requires an explicit delivered, non-withdrawn beta whose
commit is an ancestor of the sealed release commit; the tree-identity
drift check is removed
- local releases accept any clean sealed commit contained in
origin/main history (any branch or detached HEAD) and push only the
release tag; command-compatibility checks compare the sealed HEAD,
matching CI
Ported from 342d44efe (backup/event-token-lazy-resolution-pre-rewrite) and
adapted to the current in-place single 401 refresh+retry design:
- portal source: classify ticket/dial/read/ack failures via portalStageError
and reconnect with backoff on retryable stages only (DisableReconnect for
tests and one-shot callers); stage errors never leak response bodies
- personal/portal: transient token provider or refresh failures (network,
408/429/5xx) go through the reconnect loop instead of killing the source;
terminal failures (400/401/403) remain fatal
- personalRetryLogError: token resolution/refresh errors log only the
structured HTTP status, never provider error details
Unlike the original commit, a rejected token is still retried once in place
after a successful refresh, and a second 401 stays fatal (single-refresh
guard agreed in review).
Restored from the pre-rewrite branch head 342d44efe (backed up as
backup/event-token-lazy-resolution-pre-rewrite); the auth-layer changes
apply verbatim on the rebased branch.
- Add ClassifyRefreshFailure with structured HTTPStatusError so refresh
failures split into transient (network, timeout, 408/429/5xx) and
terminal (400/401/403) classes; unknown errors stay fatal.
- GetTokenSnapshot no longer marks a profile expired on transient
refresh failures, so long-running sources can retry after backoff.
- postJSON returns HTTPStatusError keeping the response body out of the
error string; the OAuth callback page HTML-escapes the sanitized
exchange error instead of echoing raw server output.
- isInvalidGrantError also matches the preserved response body.