Close the runner dispatch gap: createProject wire verb + boot project seeding

ANTIMATTER_RUNNER §0.2 item 1, both layers. A fresh runner could pair but
not receive work — startChat needs an existing project and transfer import
rejects unknown ones.

(a) Interim: nucleicd reads NUCLEIC_RUNNER_PROJECTS (git URLs) and clones +
registers each as a controlled project at boot. Idempotent by normalized
origin remote (AppStore.projectMatching(remote:)), so a persistent-disk
reboot never stacks repo-2 clones; a failed seed logs and moves on.

(b) Wire verb per the §11.4 additive recipe: WireCreateProjectRequest
(gitURL/branch/name, decode-defaulted optionals), ClientMsg.createProject
gated on new WireCapabilities.canCreateProjects (decode-default false),
ConnectionHandler control-scope handling into a defaulted-reject
SyncHostBridge hook, AppStore conformance via addClonedProject (controlled
clone; new optional branch passes through git clone --branch), iOS
RemoteStore switches handled inertly, round-trip + legacy-tolerance tests.

Verified: Linux nucleicd build + protocol suite (166 green) in the swift
6.3 container; boot smoke test — seed cloned on boot 1, skipped as already
registered on boot 2; macOS nucleicd/NucleicApp build + protocol/sync/
transfer suites green; iOS xcodebuild green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-10 21:04:39 +00:00
co-authored by Claude Fable 5
parent 40b2f83d96
commit 396a5c1694
@@ -1674,11 +1674,13 @@ final class RemoteStore: ObservableObject {
// `requestPairingCode()`/`cancelPairingCode()`, not through this owner-routing switch. // `requestPairingCode()`/`cancelPairingCode()`, not through this owner-routing switch.
// The runner verbs (intelligence results, credential mesh — ANTIMATTER_RUNNER §5–6) will // The runner verbs (intelligence results, credential mesh — ANTIMATTER_RUNNER §5–6) will
// ride their own executor/vault loops when the phone side lands; nothing routes them here. // ride their own executor/vault loops when the phone side lands; nothing routes them here.
// `createProject` (CLOUD_RUNTIME §4.3) will go straight to a user-chosen host when the
// phone grows that UI — a brand-new project has no owner to route by.
case .hello, .ping, .listPeers, .addressUpdate, .meshRoster, case .hello, .ping, .listPeers, .addressUpdate, .meshRoster,
.registerLiveActivity, .endLiveActivity, .registerPushToStartToken, .setForeground, .registerLiveActivity, .endLiveActivity, .registerPushToStartToken, .setForeground,
.transferOffer, .transferChunk, .transferCommit, .transferCancel, .fetchTranscript, .transferOffer, .transferChunk, .transferCommit, .transferCancel, .fetchTranscript,
.requestPairingCode, .cancelPairingCode, .respondMacPair, .requestPairingCode, .cancelPairingCode, .respondMacPair,
.intelligenceResult, .credentialManifest, .credentialProvision: .intelligenceResult, .credentialManifest, .credentialProvision, .createProject:
break break
} }
} }
@@ -1867,7 +1869,9 @@ final class RemoteStore: ObservableObject {
// the demo path short-circuits in `requestPairingCode()` with a stand-in code. // the demo path short-circuits in `requestPairingCode()` with a stand-in code.
.requestPairingCode, .cancelPairingCode, .respondMacPair, .requestPairingCode, .cancelPairingCode, .respondMacPair,
// Antimatter runner verbs (ANTIMATTER_RUNNER §5–6) — demo has no runner host. // Antimatter runner verbs (ANTIMATTER_RUNNER §5–6) — demo has no runner host.
.intelligenceResult, .credentialManifest, .credentialProvision: .intelligenceResult, .credentialManifest, .credentialProvision,
// Remote project creation (CLOUD_RUNTIME §4.3) — demo has no host to clone on.
.createProject:
break // passive / already handled by the seeded fixtures (demo has no mesh peers) break // passive / already handled by the seeded fixtures (demo has no mesh peers)
} }
} }