Skip to content

feat(ios): extract an atomic host-readiness state machine core (slice 1) - #313

Open
CompleteDotTech wants to merge 6 commits into
OpenCoven:mainfrom
CompleteDotTech:agent/issue-241-extract-an-atomic-ios-host-readiness
Open

feat(ios): extract an atomic host-readiness state machine core (slice 1)#313
CompleteDotTech wants to merge 6 commits into
OpenCoven:mainfrom
CompleteDotTech:agent/issue-241-extract-an-atomic-ios-host-readiness

Conversation

@CompleteDotTech

Copy link
Copy Markdown
Member

Summary

Extracts the first focused iOS replacement slice from PR #193: an atomic host-readiness state machine in PsycheCore, implementing the exact ten-state model required by issue #241unknown, discovering, pairing, authenticating, host_committed, synchronizing, ready, degraded, reconnecting, revoked (raw values are the contract names; case names follow repository Swift style).

What the machine enforces:

  • Commit-before-apply, structurally. commitHostIdentity(_:for:) persists the authoritative paired-host identity and selected-host state through a secure-store adapter before any snapshot can be validated or applied. ready is reachable only through the spine authenticating → host_committed → synchronizing → ready, and only synchronizeWorkspace can set it, after decode/revision validation and workspace acceptance both succeeded. There is no code path where a secure-store failure leaves the new workspace visible while readiness reports failure — the apply adapter cannot run after a failed commit.
  • Deterministic rollback per boundary. Transport, authentication, secure-store, decode/revision, and workspace-apply failures (plus revocation) each map to exactly one destination in a pure transition table (HostReadinessTransitions). No failure path labels newly fetched state as ready; no failure leaves a success-shaped workspace.
  • Stale vs live is a type, not a flag. HostWorkspacePresentation is .noState, .live(hostID:confirmedAt:), or .stale(hostID:confirmedAt:). A previously authoritative host's workspace stays on screen labeled stale (previous confirmedAt preserved); a failed first host leaves no state at all. Beginning any readiness flow relabels a live workspace stale immediately — the single transport is about to be retargeted, so the visible workspace cannot vouch for itself.
  • Fail closed on revocation and wrong-host identity. A flow that commits an identity contradicting the one it named is revoked without ever reaching the secure store and without overwriting committed authority.
  • Single-flight concurrency guard. One active flow owns the machine; a second beginPairing/beginReconnection is refused, and a superseded flow cannot commit or apply anything (every boundary call is flow-bound).
  • Narrow adapters only. HostReadinessAdapters (commit / validate / apply) is the whole integration surface — no Bonjour discovery, no selection UI, no physical-device logic in this slice, exactly as issue Extract an atomic iOS host-readiness state machine from PR #193 #241 requires.

Slice 1 of the #241 decomposition (per the 2026-08-24 disposition on PR #193): the readiness core + its contract + deterministic tests. Remaining for later slices: composition into ConnectionManager's readiness finalization path (replacing the pre-#241 ordering), rebasing PR #193's Bonjour/manual/re-pair UI onto this contract, lifecycle coverage (restart, host restart, foreground/background, Wi-Fi interruption, credential expiry), and physical same-LAN acceptance.

Scope note: file placement and size

New types live in PairedHost.swift (Pairing domain, where PairedHost and PairingStatus already live) and their tests in PairedHostStoreTests.swift. This keeps the generated Xcode project untouched: adding a new .swift file requires regenerating Psyche.xcodeproj with the pinned XcodeGen 2.45.4, which only runs on a macOS host — and hand-editing generated output is not allowed by repository policy. Re-homing into HostReadiness.swift is a mechanical move for a slice that next regenerates the project.

The change is 1,366 added lines (569 source + 797 tests), above the ~800-line split heuristic. The test file carries the issue's explicit requirement that tests cover every allowed transition and every rollback boundary (23 new tests: the full transition table, all five failure boundaries × with/without prior authority, stale-state preservation, wrong-host/revocation fail-closed, supersession, and concurrency). Splitting the tests from the table they verify would make both harder to review; splitting them into a second PR would land the untested machine.

Issue

Refs #241
Closes #241

Risk class

  • R3 — authority, security, persistence, recovery (paired-host authority persistence and readiness reporting semantics; no existing behavior changed in this slice)

Test plan

  • pnpm typecheck — passed locally (Node 24.16.0, pnpm 10.34.5, --ignore-scripts install).
  • pnpm test — 4,791 passed / 57 failed / 11 skipped. All 57 failures reproduce on a pristine checkout of main (verified by stashing this change and re-running) — they are environmental on this Linux host (no tmux, sandboxed process groups; e.g. gitMutationSupervisor.test.ts fails 4/4 with the change stashed). None touch iOS or the changed files.
  • pnpm vitest --run __tests__/repositoryMapContract.test.ts — 4 passed (repository-map contract unchanged).
  • iOS Core (xcodebuild test -scheme PsycheCore) — deferred to CI; no macOS/Xcode host available for this change. This is where the 23 new machine tests run.
  • iOS App build + tests — deferred to CI (app surface untouched; compiles against the extended framework).
  • pnpm ios:project:check — expected clean: no generated file changed, file set unchanged.

Validation and evidence

Command: pnpm typecheck
Result: pass (exit 0); generated-agents-doc.ts unchanged after generate:hooks-docs (deterministic)

Command: pnpm test
Result: 4791 passed / 57 failed / 11 skipped; all 57 failures reproduce identically
        with the change stashed on a pristine tree (no tmux / sandboxed child
        processes on this host); zero failures in files touched by this PR

Command: pnpm vitest --run __tests__/repositoryMapContract.test.ts
Result: 4 passed

Command: xcodebuild test -scheme PsycheCore (iOS 26.2 / iPhone 16 Pro)
Result: deferred to CI — this host has no Swift/Xcode toolchain

Proof gaps, stated plainly: Swift compilation and test execution are proven only by CI on this PR's head; no local iOS evidence exists. No production-path, physical-device, or TestFlight claim is made or implied by this slice.

Generated outputs

  • No generated output changed. (No new files, no Psyche.xcodeproj, no Info.plist, no docs generation diff.)

Authority/security impact

New HostReadinessMachine type only; no existing persistence format, wire protocol, command, or security boundary changes in this slice. The machine's contract is designed so composition (next slice) can only apply a snapshot after the secure-store commit succeeded — the fix for the contract-level finding recorded on PR #193 (finalizeWorkspaceReadiness ordering). Rollback: revert the single commit; nothing persisted changes format or behavior.

Vehicle note: opened in the fork CompleteDotTech/psyche-build as the CI vehicle — this token cannot write to OpenCoven/psyche-build. Re-target upstream once write access is restored. Refs #241.

Extract the first focused iOS replacement slice from PR OpenCoven#193: a
HostReadinessMachine that makes host readiness atomic, deterministic, and
unable to expose a newly fetched workspace as authoritative.

- Pure transition table (HostReadinessTransitions) over the issue OpenCoven#241
  state contract: unknown, discovering, pairing, authenticating,
  host_committed, synchronizing, ready, degraded, reconnecting, revoked.
- HostReadinessMachine commits authoritative paired-host identity through
  a secure-store adapter BEFORE any workspace snapshot is validated or
  applied; ready is reachable only through that committed spine.
- Deterministic rollback per boundary: transport, authentication,
  secure-store, decode/revision, workspace-apply, revocation. A failed
  new host leaves no state; a previously authoritative host's workspace
  stays visible labeled stale (stale is semantically distinct from live,
  with the previous confirmed-at timestamp preserved).
- Wrong-host identity and revocation fail closed without overwriting
  committed authority; concurrent or superseded flows cannot commit or
  apply (single active flow, superseded flows rejected).
- Exhaustive transition-table tests plus machine behavior tests covering
  every rollback boundary and the concurrency guard.

Slice 1 of the OpenCoven#241 decomposition (readiness core); discovery UI, app
composition, and lifecycle coverage follow as later slices.

Refs OpenCoven#241

Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

@CompleteDotTech is attempting to deploy a commit to the 0xBuns Team on Vercel.

A member of the Team first needs to authorize it.

CI caught two things on the first head: the non-transport failure table
did not handle the degraded state (switch must be exhaustive), and the
table's tests asserted the pre-slice pairing behavior instead of the
retarget flow that atomic authority actually protects.

- the authentication/secure-store/decode/revision/workspace-apply table
  now handles degraded explicitly: resting states own no flow, so a
  failure event is inert there and recovery starts a new flow;
- pairing may start from ready — pairing a new host while one is ready
  is exactly the retarget flow whose authority must be atomic — while it
  stays rejected inside an in-flight flow and from degraded;
- the discarded rollback results are discarded explicitly, matching
  Swift 6 strictness;
- transition tests updated to pin the corrected table (including the
  ready→pairing retarget edge and the two new rejections).

Refs OpenCoven#241

Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>
The pull_request synchronize run for 5ecdf72 did not start within 15
minutes of the push (no run, zero check-runs on the head SHA, and the PR
object still reporting the prior head). An empty commit nudges a fresh
synchronize event; no content changes.

Refs OpenCoven#241

Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>
The machine source compiled; the readiness test additions did not.
XCTest assertion autoclosures cannot contain `await`, so actor property
reads are hoisted into locals before asserting. Also fixes one test that
referenced the wrong binding for the still-committed host and makes the
shared drive-to-ready helper @discardableResult so every call site stays
warning-free.

Refs OpenCoven#241

Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
psyche-build-docs Ready Ready Preview Aug 30, 2026 9:21pm

Request Review

- the workspace-apply rollback test forgot the accumulated boundary log
  from establishing the previously authoritative host;
- the degraded-recovery test kept the apply-boundary failure stub armed
  into the recovery flow, so the retry's apply legitimately failed. Clear
  the stub before the recovery spine runs.

Refs OpenCoven#241

Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>

@BunsDev BunsDev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep this as the active first iOS gate, but fix the actor reentrancy boundary before merge. revoke() can interleave while external commit/apply adapters are awaited, leaving durable or visible state changed while the machine reports revoked/superseded. Add adversarial revocation/supersession tests and make publication atomic or compensatable, then request independent R3 review.

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.

Extract an atomic iOS host-readiness state machine from PR #193

2 participants