mirror of
https://github.com/saymrwulf/BraiinsRatchet.git
synced 2026-05-14 20:37:52 +00:00
Remove native automation gate
This commit is contained in:
parent
3d328752c3
commit
56163922c0
6 changed files with 122 additions and 58 deletions
|
|
@ -146,7 +146,7 @@ The app includes controls to record and close manual exposure, but the same rule
|
|||
|
||||
The app is organized as:
|
||||
|
||||
1. `Mission Control`: one exact next action, cooldown, metrics, and automation approval.
|
||||
1. `Mission Control`: one exact next action, cooldown, metrics, and direct watch-only controls.
|
||||
2. `Research Map`: visual autoresearch stage model.
|
||||
3. `Manual Exposure`: record or close manually executed Braiins exposure.
|
||||
4. `Reports`: raw cockpit, report, and ledger artifacts.
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ Source: <https://media.nngroup.com/media/articles/attachments/Heuristic_Summary1
|
|||
The native app now treats the Python engine as a structured state provider, not as a terminal to embed. The new `app-state` command returns JSON with:
|
||||
|
||||
- Current operator state.
|
||||
- Automation plan.
|
||||
- Passive action plan.
|
||||
- Cockpit text for audit/debug.
|
||||
- Latest OCEAN, Braiins, and strategy proposal payloads.
|
||||
|
||||
The SwiftUI app turns that into native surfaces:
|
||||
|
||||
- `Mission Control`: one exact action, cooldown, approval gate, and metrics.
|
||||
- `Mission Control`: one exact action, cooldown, direct watch-only controls, and metrics.
|
||||
- `Research Map`: the ratchet pathway as a visual stage model.
|
||||
- `Manual Exposure`: the ledger for real manually placed Braiins exposure.
|
||||
- `Reports`: raw artifacts kept available but no longer primary.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ This builds `macos/build/Braiins Ratchet.app` and opens the packaged app. Use th
|
|||
- Native macOS SwiftUI control room.
|
||||
- Mission Control with one explicit next action.
|
||||
- Research Map with the full ratchet pathway.
|
||||
- Monitor-only automation approval gate.
|
||||
- Direct watch-only controls without an approval gate.
|
||||
- Manual exposure recording and closing controls.
|
||||
- Reports panel for raw artifacts.
|
||||
- Ratchet Lecture for the general autoresearch method.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ struct ContentView: View {
|
|||
@State private var lastCommand = "app-state"
|
||||
@State private var errorMessage: String?
|
||||
@State private var isRunning = false
|
||||
@State private var showAutomationApproval = false
|
||||
@State private var glow = false
|
||||
@State private var manualDescription = ""
|
||||
@State private var maturityHours = "72"
|
||||
|
|
@ -72,18 +71,6 @@ struct ContentView: View {
|
|||
glow = true
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Approve monitor-only automation?",
|
||||
isPresented: $showAutomationApproval,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Approve and run monitor-only plan") {
|
||||
Task { await runTextCommand(label: "pipeline --yes", ["pipeline", "--yes"], refreshAfterwards: true) }
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
} message: {
|
||||
Text(appState?.automationPlan.title ?? "The app will run only the printed monitor-only plan. It will not place Braiins orders.")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
@ -96,8 +83,7 @@ struct ContentView: View {
|
|||
isRunning: isRunning,
|
||||
glow: glow,
|
||||
refresh: { Task { await refreshAppState() } },
|
||||
previewAutomation: { Task { await runTextCommand(label: "pipeline preview", ["pipeline"], input: "no\n") } },
|
||||
approveAutomation: { showAutomationApproval = true }
|
||||
runPassiveAction: runPassiveAction
|
||||
)
|
||||
case .map:
|
||||
ResearchMapView(appState: appState, glow: glow)
|
||||
|
|
@ -212,6 +198,26 @@ struct ContentView: View {
|
|||
await runTextCommand(label: "position close", ["position", "close", positionId], refreshAfterwards: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func runPassiveAction() {
|
||||
guard let plan = appState?.automationPlan else {
|
||||
Task { await refreshAppState() }
|
||||
return
|
||||
}
|
||||
|
||||
switch plan.kind {
|
||||
case "once_now":
|
||||
Task { await runTextCommand(label: "once", ["once"], refreshAfterwards: true) }
|
||||
case "watch_2h":
|
||||
Task { await runTextCommand(label: "watch 2", ["watch", "2"], refreshAfterwards: true) }
|
||||
case "wait_then_once" where plan.waitSeconds <= 0:
|
||||
Task { await runTextCommand(label: "once", ["once"], refreshAfterwards: true) }
|
||||
case "report_only":
|
||||
Task { await runTextCommand(label: "report", ["report"]) }
|
||||
default:
|
||||
Task { await refreshAppState() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AppSection: String, CaseIterable, Identifiable {
|
||||
|
|
@ -250,8 +256,7 @@ struct MissionControlView: View {
|
|||
let isRunning: Bool
|
||||
let glow: Bool
|
||||
let refresh: () -> Void
|
||||
let previewAutomation: () -> Void
|
||||
let approveAutomation: () -> Void
|
||||
let runPassiveAction: () -> Void
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
|
|
@ -263,11 +268,10 @@ struct MissionControlView: View {
|
|||
VStack(spacing: 14) {
|
||||
AutoresearchOrb(phase: ResearchPhase.from(appState), glow: glow)
|
||||
.frame(height: 250)
|
||||
AutomationCard(
|
||||
PassiveRunCard(
|
||||
plan: appState?.automationPlan,
|
||||
isRunning: isRunning,
|
||||
preview: previewAutomation,
|
||||
approve: approveAutomation
|
||||
run: runPassiveAction
|
||||
)
|
||||
}
|
||||
.frame(width: 350)
|
||||
|
|
@ -341,52 +345,96 @@ struct HeroPanel: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct AutomationCard: View {
|
||||
struct PassiveRunCard: View {
|
||||
let plan: AutomationPlanPayload?
|
||||
let isRunning: Bool
|
||||
let preview: () -> Void
|
||||
let approve: () -> Void
|
||||
let run: () -> Void
|
||||
|
||||
var body: some View {
|
||||
GlassPanel {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
Label("Automation Gate", systemImage: "checkmark.seal")
|
||||
Label("Watch-only Control", systemImage: "binoculars")
|
||||
.font(.headline)
|
||||
Text(plan?.title ?? "Load state to see the next safe automation step.")
|
||||
Text(title)
|
||||
.font(.title3.weight(.bold))
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ForEach(Array((plan?.steps ?? []).enumerated()), id: \.offset) { index, step in
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
Text("\(index + 1)")
|
||||
.font(.caption.weight(.black))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 20, height: 20)
|
||||
.background(.green.opacity(0.75), in: Circle())
|
||||
Text(step)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(detail)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
HStack {
|
||||
Button("Preview") {
|
||||
preview()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
Button("Approve") {
|
||||
approve()
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(!canApprove || isRunning)
|
||||
Button(buttonTitle) {
|
||||
run()
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(!canRun || isRunning)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Label("No owner-token order placement", systemImage: "lock")
|
||||
Label("Manual Braiins actions stay outside the app", systemImage: "hand.point.up.left")
|
||||
Label("Watch runs only collect public/OCEAN data", systemImage: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var canApprove: Bool {
|
||||
guard let kind = plan?.kind else { return false }
|
||||
return !["no_action", "external_wait", "manual_exposure_hold"].contains(kind)
|
||||
private var title: String {
|
||||
guard let plan else { return "Load state" }
|
||||
switch plan.kind {
|
||||
case "once_now": return "Refresh one sample"
|
||||
case "watch_2h": return "Start passive 2-hour watch"
|
||||
case "wait_then_once": return plan.waitSeconds > 0 ? "Cooldown in progress" : "Cooldown complete"
|
||||
case "report_only": return "Read report"
|
||||
case "external_wait": return "Existing watch owns control"
|
||||
case "manual_exposure_hold": return "Manual exposure hold"
|
||||
default: return "No passive action"
|
||||
}
|
||||
}
|
||||
|
||||
private var detail: String {
|
||||
guard let plan else { return "The app is reading the lifecycle state." }
|
||||
switch plan.kind {
|
||||
case "once_now":
|
||||
return "This collects exactly one fresh monitor sample, then stops."
|
||||
case "watch_2h":
|
||||
return "This starts one bounded watch-only run. It does not spend BTC or place Braiins orders."
|
||||
case "wait_then_once":
|
||||
if plan.waitSeconds > 0 {
|
||||
return "The previous watch is still maturing. Do not run another identical watch yet."
|
||||
}
|
||||
return "The cooldown has ended; one fresh sample is now useful."
|
||||
case "report_only":
|
||||
return "The next useful step is reading the full report."
|
||||
case "external_wait":
|
||||
return "A watch is already running elsewhere. Starting another one would create duplicate state."
|
||||
case "manual_exposure_hold":
|
||||
return "A real manual position is active. The app should supervise, not create new experiments."
|
||||
default:
|
||||
return "No watch-only action is useful right now."
|
||||
}
|
||||
}
|
||||
|
||||
private var buttonTitle: String {
|
||||
guard let plan else { return "Refresh State" }
|
||||
switch plan.kind {
|
||||
case "once_now": return "Refresh Now"
|
||||
case "watch_2h": return "Start Watch-only Run"
|
||||
case "wait_then_once": return plan.waitSeconds > 0 ? "Wait" : "Refresh Now"
|
||||
case "report_only": return "Open Report"
|
||||
default: return "Refresh State"
|
||||
}
|
||||
}
|
||||
|
||||
private var canRun: Bool {
|
||||
guard let plan else { return true }
|
||||
switch plan.kind {
|
||||
case "once_now", "watch_2h", "report_only":
|
||||
return true
|
||||
case "wait_then_once":
|
||||
return plan.waitSeconds <= 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from decimal import Decimal
|
|||
from datetime import UTC, datetime
|
||||
import sqlite3
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from braiins_ratchet.guidance import (
|
||||
CompletedWatch,
|
||||
|
|
@ -19,7 +20,8 @@ class GuidanceTests(unittest.TestCase):
|
|||
conn = sqlite3.connect(":memory:")
|
||||
init_db(conn)
|
||||
|
||||
text = build_operator_cockpit(conn)
|
||||
with _isolated_operator_files():
|
||||
text = build_operator_cockpit(conn)
|
||||
|
||||
self.assertIn("Braiins Ratchet Cockpit", text)
|
||||
self.assertIn("./scripts/ratchet setup", text)
|
||||
|
|
@ -61,7 +63,8 @@ class GuidanceTests(unittest.TestCase):
|
|||
),
|
||||
)
|
||||
|
||||
text = build_operator_cockpit(conn)
|
||||
with _isolated_operator_files():
|
||||
text = build_operator_cockpit(conn)
|
||||
|
||||
self.assertIn("Latest strategy action: manual_canary", text)
|
||||
self.assertIn("./scripts/ratchet watch 2", text)
|
||||
|
|
@ -158,5 +161,14 @@ def _completed_watch(age_minutes: int) -> CompletedWatch:
|
|||
)
|
||||
|
||||
|
||||
def _isolated_operator_files():
|
||||
return patch.multiple(
|
||||
"braiins_ratchet.guidance",
|
||||
_active_watch=lambda: None,
|
||||
_latest_report=lambda: None,
|
||||
_running_runs=lambda: [],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -55,3 +55,7 @@ class MacAppPackagingTest(unittest.TestCase):
|
|||
self.assertIn("AutoresearchOrb", text)
|
||||
self.assertIn("AppStatePayload", text)
|
||||
self.assertIn("loadAppState", text)
|
||||
self.assertIn("PassiveRunCard", text)
|
||||
self.assertNotIn("Automation Gate", text)
|
||||
self.assertNotIn("confirmationDialog", text)
|
||||
self.assertNotIn("showAutomationApproval", text)
|
||||
|
|
|
|||
Loading…
Reference in a new issue