Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include LICENSE README.md SECURITY.md
recursive-include docs *
recursive-include scripts *
recursive-include assets *
recursive-include Sources *
recursive-include tests *
global-exclude __pycache__ *.py[cod] .DS_Store
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ Some devices are powerful. Some are private. Some are fragile. Some are approval
```bash
git clone https://github.com/workingclassbuddha/open-compute-protocol.git
cd open-compute-protocol
python3 -m pip install -e .
python3 scripts/start_ocp_easy.py
```

Expand Down Expand Up @@ -199,6 +200,16 @@ If you want the shell-based starter instead of the auto-open launcher:

For a fuller walkthrough, see [docs/QUICKSTART.md](./docs/QUICKSTART.md).

### Trustworthy Alpha Notes

OCP v0.1.7 is a stabilization pass around packaging, security posture, protocol contract visibility, tests, and demo flow. It is still alpha and should not be treated as production-secure or protocol-stable.

- [Security Model](./docs/SECURITY_MODEL.md)
- [Operator Authorization](./docs/OPERATOR_AUTH.md)
- [HTTP API Overview](./docs/OCP_HTTP_API.md)
- [Two Macs and a Phone Demo](./docs/DEMO_TWO_MACS_AND_PHONE.md)
- [v0.1 Draft Spec](./docs/spec/OCP_v0.1.md)

---

## OCP App
Expand All @@ -221,6 +232,26 @@ swift run OCPDesktop

This native Mission Control shell uses the same OCP server, state paths, operator-token phone links, app-status polling, persisted app-history samples, charts, client-derived route topology, guided setup, and default-worker startup behavior as the Python launcher.

### Native Proof Assistant

The native Mac app now includes a one-click Proof Assistant for the two-device OCP proof. Launch the app, then click **Run Proof Assistant** from the Overview, Setup Doctor, toolbar, or Mesh menu.

<p align="center">
<img src="./assets/ocp-proof-assistant.svg" alt="Native Proof Assistant flow" width="100%" />
</p>

The assistant runs the no-terminal path end to end:

1. Generates and persists an operator token if needed.
2. Starts Mesh Mode when the server is not already running in mesh mode.
3. Waits for `/mesh/app/status` to become reachable.
4. Copies the tokened phone link once for the run and shows it in the app.
5. Calls the existing Autonomic Mesh activation flow with proof and repair enabled.
6. Polls status until setup becomes `strong`, OCP reports a proof issue, or the proof times out with a concrete next fix.
7. Records one app-history sample at the end so the Mission Control charts reflect the run.

No server routes or schemas are added for this flow. The native assistant only orchestrates the existing `/mesh/app/status`, `/mesh/app/history`, `/mesh/app/history/sample`, and `/mesh/autonomy/activate` endpoints. The individual Start Mesh, Copy Phone Link, Activate Mesh, and Open App controls remain available as secondary controls.

Unsigned macOS beta bundle:

```bash
Expand Down Expand Up @@ -295,6 +326,7 @@ These are meant to give the project a clearer identity as:
## Tests

```bash
python3 scripts/check_protocol_conformance.py
python3 -m unittest tests.test_sovereign_mesh
python3 server.py --help
```
Expand Down
3 changes: 3 additions & 0 deletions Sources/OCPDesktop/App/OCPDesktopApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ struct OCPDesktopApp: App {
.commands {
CommandGroup(replacing: .newItem) {}
CommandMenu("Mesh") {
Button("Run Proof Assistant") { model.runProofAssistant() }
.keyboardShortcut("p", modifiers: [.command, .shift])
.disabled(model.isProofAssistantRunning)
Button("Activate Mesh") { model.activateMesh() }
.keyboardShortcut("a", modifiers: [.command, .shift])
.disabled(model.isActivating)
Expand Down
191 changes: 189 additions & 2 deletions Sources/OCPDesktop/Stores/OCPDesktopModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ final class OCPDesktopModel: ObservableObject {
@Published var history = AppStatusHistory.empty
@Published var isRunning = false
@Published var isActivating = false
@Published var isProofAssistantRunning = false
@Published var proofAssistant = ProofAssistantStatus.idle

let repoRoot: URL
let paths: LaunchPaths
Expand Down Expand Up @@ -154,6 +156,14 @@ final class OCPDesktopModel: ObservableObject {
}
}

func runProofAssistant() {
guard !isProofAssistantRunning else { return }
isProofAssistantRunning = true
Task {
await runProofAssistantFlow()
}
}

func saveConfig() {
let normalized = config.normalized(defaultNodeID: LauncherCore.defaultNodeID())
config = normalized
Expand Down Expand Up @@ -181,6 +191,178 @@ final class OCPDesktopModel: ObservableObject {
}
}

private func runProofAssistantFlow() async {
var copiedPhoneLink = false
defer {
isProofAssistantRunning = false
isActivating = false
}

ensureOperatorToken()
proofAssistant = ProofAssistantReducer.initial(mode: currentMode, phoneURL: phoneURL)
statusText = proofAssistant.message

if currentMode != .mesh || !isRunning {
startMesh()
} else {
refreshStaticLinks(mode: .mesh)
}

proofAssistant = ProofAssistantReducer.waitingForServer(phoneURL: phoneURL)
statusText = proofAssistant.message

let firstSnapshot: AppStatusSnapshot
do {
firstSnapshot = try await waitForReachableServer(timeout: 20)
} catch ProofAssistantRunError.startupTimeout {
proofAssistant = ProofAssistantReducer.startupTimeout(seconds: 20)
statusText = proofAssistant.message
await recordHistorySample(preserveStatus: true)
return
} catch {
proofAssistant = ProofAssistantReducer.failure(
"Could not reach OCP: \(error.localizedDescription)",
detail: "Start Mesh Mode manually or check the configured port, then run the assistant again.",
phoneURL: phoneURL
)
statusText = proofAssistant.message
await recordHistorySample(preserveStatus: true)
return
}

apply(firstSnapshot)
copiedPhoneLink = copyProofAssistantPhoneLinkIfReady()
proofAssistant = ProofAssistantReducer.phoneLinkReady(phoneURL: phoneURL, copiedPhoneLink: copiedPhoneLink)
statusText = proofAssistant.message

proofAssistant = ProofAssistantReducer.activating(phoneURL: phoneURL, copiedPhoneLink: copiedPhoneLink)
statusText = proofAssistant.message
isActivating = true

do {
try await client().activateMesh()
} catch {
isActivating = false
proofAssistant = ProofAssistantReducer.failure(
"Activate Mesh failed: \(error.localizedDescription)",
detail: nextFix,
phoneURL: phoneURL,
copiedPhoneLink: copiedPhoneLink
)
statusText = proofAssistant.message
await recordHistorySample(preserveStatus: true)
return
}

isActivating = false
proofAssistant = ProofAssistantStatus(
phase: .pollingProof,
title: "Polling proof",
message: "Activation completed. The assistant is watching for strong mesh status.",
phoneURL: phoneURL,
copiedPhoneLink: copiedPhoneLink
)
statusText = proofAssistant.message

let finalStatus = await pollProofUntilDone(timeout: 90, copiedPhoneLink: copiedPhoneLink)
proofAssistant = finalStatus
statusText = finalStatus.message
await recordHistorySample(preserveStatus: true)
}

private func waitForReachableServer(timeout: TimeInterval) async throws -> AppStatusSnapshot {
let deadline = Date().addingTimeInterval(timeout)
var lastError: Error?

while Date() < deadline {
do {
let next = try await client().fetchStatus()
apply(next)
try? await refreshHistory()
return next
} catch {
lastError = error
try? await Task.sleep(nanoseconds: 500_000_000)
}
}

if lastError != nil {
throw ProofAssistantRunError.startupTimeout
}
throw ProofAssistantRunError.startupTimeout
}

private func pollProofUntilDone(timeout: TimeInterval, copiedPhoneLink: Bool) async -> ProofAssistantStatus {
let deadline = Date().addingTimeInterval(timeout)
var latestSnapshot = snapshot

while Date() < deadline {
do {
let next = try await client().fetchStatus()
latestSnapshot = next
apply(next)
try? await refreshHistory()
let reduced = ProofAssistantReducer.status(
for: next,
mode: currentMode,
phoneURL: phoneURL,
currentPhase: .pollingProof,
copiedPhoneLink: copiedPhoneLink
)

switch reduced.phase {
case .completed, .needsAttention, .failed:
return reduced
default:
proofAssistant = reduced.phase == .pollingProof ? reduced : ProofAssistantStatus(
phase: .pollingProof,
title: "Polling proof",
message: reduced.message,
detail: reduced.detail.isEmpty ? "Waiting for OCP to report strong status or a concrete fix." : reduced.detail,
phoneURL: reduced.phoneURL,
copiedPhoneLink: copiedPhoneLink
)
statusText = proofAssistant.message
}
} catch {
proofAssistant = ProofAssistantStatus(
phase: .pollingProof,
title: "Polling proof",
message: "Status polling is retrying: \(error.localizedDescription)",
detail: "The assistant will keep trying until the proof timeout.",
phoneURL: phoneURL,
copiedPhoneLink: copiedPhoneLink
)
statusText = proofAssistant.message
}

try? await Task.sleep(nanoseconds: 2_000_000_000)
}

return ProofAssistantReducer.proofTimeout(
snapshot: latestSnapshot,
phoneURL: phoneURL,
copiedPhoneLink: copiedPhoneLink
)
}

private func ensureOperatorToken() {
guard config.operatorToken.isEmpty else { return }
config.operatorToken = Self.generateToken()
let normalized = config.normalized(defaultNodeID: LauncherCore.defaultNodeID())
config = normalized
try? LauncherCore.saveConfig(normalized, to: paths.configPath)
}

private func copyProofAssistantPhoneLinkIfReady() -> Bool {
let value = phoneURL.hasPrefix("http") ? phoneURL : appLink(for: .mesh)
guard value.hasPrefix("http") else { return false }
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(value, forType: .string)
phoneURL = value
return true
}

private func start(mode: LaunchMode) {
stop()
currentMode = mode
Expand Down Expand Up @@ -227,16 +409,17 @@ final class OCPDesktopModel: ObservableObject {
history = try await client().fetchHistory(limit: 240)
}

private func recordHistorySample() async {
private func recordHistorySample(preserveStatus: Bool = false) async {
guard !isSampling else { return }
isSampling = true
let previousStatus = statusText
defer { isSampling = false }
do {
_ = try await client().recordHistorySample()
lastSampleAt = Date()
try await refreshHistory()
} catch {
statusText = "Status is live, but history sampling failed: \(error.localizedDescription)"
statusText = preserveStatus ? previousStatus : "Status is live, but history sampling failed: \(error.localizedDescription)"
}
}

Expand Down Expand Up @@ -271,3 +454,7 @@ final class OCPDesktopModel: ObservableObject {
"\(UUID().uuidString.lowercased())-\(UUID().uuidString.lowercased())"
}
}

private enum ProofAssistantRunError: Error {
case startupTimeout
}
Loading
Loading