Skip to content

Prototype C: Prototype A with retry removed#120

Open
kyleve wants to merge 2 commits into
typed-lifecycle-vanillafrom
vanilla-no-retry
Open

Prototype C: Prototype A with retry removed#120
kyleve wants to merge 2 commits into
typed-lifecycle-vanillafrom
vanilla-no-retry

Conversation

@kyleve

@kyleve kyleve commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Problem

A prototype for comparison, branched off Prototype A (#117). It answers the question from the design discussion: now that "invalid states are unrepresentable" (the store-open race that retry once caught is fixed structurally by injection in #99/#116), how much does removing retry simplify the async-function engine?

Changes

Retry is gone, and because it was the only thing that ever resumed a drive, removing it deletes a cluster of machinery — not just a method:

  • Runner: deleted retry(), the FailedSite tracking (its only job was picking which function to re-run), the RetainedTeardown struct + retained-teardown var, the separate teardownMemo, and the injectFailureForTesting SPI. Teardown now runs inline, once, through a throwaway memo — no retain, so the release-on-success dance for the captured session is gone too.
  • The launch memo stays. It's promotion's requirement (an enterForeground() re-run must skip completed work), not retry's — the point I'd flagged in the design chat, now demonstrated: stripping retry doesn't touch it.
  • .failed is terminal. LifecycleFailureView loses its button (and the failure.launch.retry string); the container's failure closure drops its retry action; LifecycleProxy and the LifecycleDriving seam drop retry(). An unregistered gate still fails onto that terminal surface.
  • Teardown-failure safety is preserved without retry: a thrown erase never reaches the session drop, so state stays intact and relaunching the app returns to the working app rather than a half-erased one — same terminal contract as Prototype B, reached here without deleting the engine.

What it buys, precisely

Prototype A (#117) This branch
Swift LOC baseline −253 (96(+) / 349(−))
Runner state launchMemo + teardownMemo + RetainedTeardown + failedSite just launchMemo
Teardown retained, re-runnable, released-on-success inline, fire-once
Failure retryable surface + proxy/seam plumbing terminal surface
Promotion (memo, ID discipline, executed-ID recording) unchanged unchanged
Fuzz suites 200 model-parity + 120 retry-drain 200 model-parity

The honest read: it's a clean, real simplification (a quarter of the engine's bookkeeping was retry-only), but it confirms rather than overturns the earlier analysis — the memo, the one-ID-one-call-site discipline, and the promotion re-run machinery are all promotion's, so the conceptual weight of the engine is barely reduced. The visible win is the terminal failure surface and the fire-once teardown; the fuzz coverage drops by the retry-drain suite.

Testing

Dropped the retry/resume unit tests, the injected-failure test, and the 120-seed retry-drain fuzz suite; kept the 200-seed model-parity fuzz. Added a terminal-failure test (a second run() after a thrown step doesn't re-drive) and reshaped the promotion run-once test off retry. UI/proxy tests updated for the terminal failure surface and enterForeground forwarding. Full Stuff-iOS-Tests scheme green (against a pre-booted iPhone 17 / iOS 26.2 sim — the shared runner's "Application failed preflight checks" flake needed booting the exact target by UDID); ./swiftformat --lint clean.

Recommendation context

With two more apps planned, my standing recommendation is still Prototype A's reusable engine over B (Where-only). This branch is the sharper form of that recommendation: A with retry removed — same reusable async-function engine, terminal failure, ~250 fewer lines. If you want a launch-level "Try Again" back for a specific app later, it's re-addable; the point of this branch is that nothing in the current app needs it.

kyleve added 2 commits July 22, 2026 15:01
Explores what removing retry buys, branched off Prototype A (#117).
Retry's original customer — the fresh-install store-open race — was
fixed structurally by injection (#99/#116), so on-device local work
that fails is either terminal or belongs to a layer that retries
internally; a launch-level Try Again is redundant.

Removing it is more than deleting a method, because retry was the only
thing that *resumed* a drive:
- Deleted from the runner: retry(), the FailedSite tracking (its sole
  purpose was picking which function to re-run), the RetainedTeardown
  struct + retained-teardown var, the separate teardownMemo, and the
  injectFailureForTesting SPI. Teardown now runs inline once through a
  throwaway memo — no retain, so the release-on-success dance for the
  captured session is gone too. The launch memo stays: it's promotion's
  requirement (an enterForeground re-run must skip completed work), not
  retry's.
- .failed is now terminal. LifecycleFailureView drops its button (and
  the failure.launch.retry string); the container's failure closure
  loses its retry action; LifecycleProxy and the LifecycleDriving seam
  drop retry(). An unregistered-gate still fails onto that terminal
  surface.
- Teardown-failure safety is preserved without retry: a thrown erase
  never reaches the session drop, so state stays intact and relaunching
  the app returns to the working app rather than a half-erased one.

Tests: dropped the retry/resume unit tests, the injected-failure test,
and the 120-seed retry-drain fuzz suite; the 200-seed model-parity fuzz
stays. Added a terminal-failure test (a second run() doesn't re-drive)
and reshaped the promotion run-once test to not lean on retry. UI/proxy
tests updated for the terminal failure surface + enterForeground
forwarding. Full Stuff-iOS-Tests scheme green; swiftformat --lint
clean.
Plan step: noretry-docs-pr (docs half).

LifecycleKit README/AGENTS: retry removed from the API and lifecycle;
the memo is reframed as promotion's requirement (not retry's); failure
(launch and teardown) is terminal; teardown runs once through a
throwaway store. LifecycleKitUI README/AGENTS: the failure surface is
terminal, LifecycleFailureView loses its button, the proxy forwards
only enterForeground/teardown. ./sync-agents run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant