mirror of
https://github.com/saymrwulf/BraiinsRatchet.git
synced 2026-05-14 20:37:52 +00:00
Make native app domain first
This commit is contained in:
parent
56163922c0
commit
9704ac8452
6 changed files with 192 additions and 211 deletions
27
README.md
27
README.md
|
|
@ -15,27 +15,12 @@ The first implementation is deliberately conservative:
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/ratchet setup
|
./scripts/ratchet app
|
||||||
./scripts/ratchet
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`./scripts/ratchet` is the cockpit. It tells you exactly what to do next.
|
This builds and opens the native macOS control room. Use the app for normal operation; terminal commands are advanced fallback tools.
|
||||||
|
|
||||||
If the cockpit is in cooldown and you want the app to wait until the earliest next action, run:
|
The lifecycle state persists in `data/ratchet.sqlite`. If the app or Mac restarts, open the app again and it reads the same state.
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet pipeline
|
|
||||||
```
|
|
||||||
|
|
||||||
It prints the exact monitor-only plan and asks `yes/no` before doing anything.
|
|
||||||
|
|
||||||
For the durable forever lifecycle supervisor:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet supervise
|
|
||||||
```
|
|
||||||
|
|
||||||
It persists lifecycle state in `data/ratchet.sqlite`. If the process crashes or the Mac reboots, start the same command again and it resumes from SQLite.
|
|
||||||
|
|
||||||
When you manually place a Braiins bid, record the exposure so the supervisor blocks new experiments:
|
When you manually place a Braiins bid, record the exposure so the supervisor blocks new experiments:
|
||||||
|
|
||||||
|
|
@ -49,7 +34,7 @@ Close it only when finished:
|
||||||
./scripts/ratchet position close POSITION_ID
|
./scripts/ratchet position close POSITION_ID
|
||||||
```
|
```
|
||||||
|
|
||||||
For the native macOS SwiftUI shell:
|
For the native macOS app:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/ratchet app
|
./scripts/ratchet app
|
||||||
|
|
@ -57,9 +42,9 @@ For the native macOS SwiftUI shell:
|
||||||
|
|
||||||
This builds `macos/build/Braiins Ratchet.app` and opens the real app bundle. Do not use `swift run` for normal operation.
|
This builds `macos/build/Braiins Ratchet.app` and opens the real app bundle. Do not use `swift run` for normal operation.
|
||||||
|
|
||||||
The app is a native visual control room: Mission Control, Research Map, Manual Exposure ledger, Reports, and a Ratchet Lecture. The design rationale is in `docs/APP_DESIGN_RESEARCH.md`.
|
The app is a native visual control room: Mission Control, Research Map, Manual Exposure ledger, Advanced diagnostics, and a Ratchet Lecture. The design rationale is in `docs/APP_DESIGN_RESEARCH.md`.
|
||||||
|
|
||||||
For a 6-hour monitoring session:
|
Advanced fallback for a 6-hour CLI monitoring session:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/ratchet watch 6
|
./scripts/ratchet watch 6
|
||||||
|
|
|
||||||
175
START_HERE.md
175
START_HERE.md
|
|
@ -1,18 +1,12 @@
|
||||||
# Start Here
|
# Start Here
|
||||||
|
|
||||||
This project now has one operator entry point:
|
This project now has one normal operator entry point:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/ratchet
|
./scripts/ratchet app
|
||||||
```
|
```
|
||||||
|
|
||||||
That is the same as:
|
That command builds and opens the native macOS app. The app is the control room. The terminal is only the launcher and fallback diagnostic path.
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet next
|
|
||||||
```
|
|
||||||
|
|
||||||
It prints the cockpit: current state, exact next action, interpretation, reference commands, and ratchet rule.
|
|
||||||
|
|
||||||
## Your Job
|
## Your Job
|
||||||
|
|
||||||
|
|
@ -20,113 +14,43 @@ Your job is not to understand every metric.
|
||||||
|
|
||||||
Your job is:
|
Your job is:
|
||||||
|
|
||||||
1. Run `./scripts/ratchet`.
|
1. Open the app with `./scripts/ratchet app`.
|
||||||
2. Do only what it says under `DO THIS NOW`.
|
2. Stay on `Mission Control` unless you intentionally need raw diagnostics.
|
||||||
3. Ignore every other command unless `DO THIS NOW` tells you to run it.
|
3. Read `Current Decision` first.
|
||||||
4. If it tells you to run `./scripts/ratchet watch 2`, start it, leave the terminal open, and come back after about 2 hours.
|
4. Read `Who Is In Control` second.
|
||||||
5. If you manually place a Braiins canary, write down the order details outside this repo and wait through the maturity window before judging it.
|
5. Use `Next Passive Action` only when it is enabled.
|
||||||
|
6. If you manually place a Braiins canary, record it in `Manual Exposure` immediately.
|
||||||
|
|
||||||
|
Do not start extra terminal watches while the app says a watch, cooldown, or manual exposure owns control.
|
||||||
|
|
||||||
## Who Is In Control?
|
## Who Is In Control?
|
||||||
|
|
||||||
If `watch` is running, the Python process is in control of that terminal.
|
The app has one ownership model:
|
||||||
|
|
||||||
You do not need to babysit it. It will:
|
1. `The app is ready`: you may start the enabled passive action.
|
||||||
|
2. `A watch run owns control`: leave it alone until it finishes.
|
||||||
|
3. `Cooldown owns control`: wait until the shown earliest action time.
|
||||||
|
4. `Manual exposure owns control`: supervise the real-world Braiins/OCEAN position and do not start new experiments.
|
||||||
|
5. `The app is busy`: a monitor-only backend operation is running right now.
|
||||||
|
|
||||||
1. Collect samples every 5 minutes.
|
This is the anti-babysitting rule: if the app says something else owns control, your workload is zero unless you are supervising a real manual exposure.
|
||||||
2. Write the run report when it finishes.
|
|
||||||
3. Print the cockpit again.
|
|
||||||
4. Return control to your shell prompt.
|
|
||||||
|
|
||||||
If you want the technical report, run `./scripts/ratchet report`. The normal workflow intentionally shows the cockpit first.
|
## What The App Does
|
||||||
|
|
||||||
After a watch finishes, the cockpit enters a post-watch cooldown. That is deliberate.
|
The app is monitor-only. It never places, modifies, or cancels Braiins orders.
|
||||||
|
|
||||||
Post-watch cooldown means:
|
It can:
|
||||||
|
|
||||||
1. The current experimental stage is complete.
|
1. Read persisted lifecycle state from `data/ratchet.sqlite`.
|
||||||
2. Starting another identical watch immediately is not useful ratcheting.
|
2. Collect OCEAN and public Braiins market samples.
|
||||||
3. The run report is the evidence artifact.
|
3. Run passive watch-only research windows.
|
||||||
4. The next planned touch is a later fresh sample, usually `./scripts/ratchet once`.
|
4. Write run reports under `reports/`.
|
||||||
|
5. Track manually executed Braiins exposure that you enter yourself.
|
||||||
During cooldown, the cockpit shows:
|
6. Resume from the same SQLite state after a crash or reboot.
|
||||||
|
|
||||||
1. A progress bar.
|
|
||||||
2. The earliest next action time.
|
|
||||||
3. The remaining minutes.
|
|
||||||
|
|
||||||
## Controlled Automation
|
|
||||||
|
|
||||||
If you do not want to babysit the cooldown manually, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet pipeline
|
|
||||||
```
|
|
||||||
|
|
||||||
The pipeline first prints a proposal like:
|
|
||||||
|
|
||||||
```text
|
|
||||||
I am going to: wait until this time, run one fresh sample, print the cockpit, then stop.
|
|
||||||
Are you OK with this? Type yes or no.
|
|
||||||
```
|
|
||||||
|
|
||||||
It only runs after you type `yes`.
|
|
||||||
|
|
||||||
It is still monitor-only. It never places, changes, or cancels Braiins orders.
|
|
||||||
|
|
||||||
## Forever Supervisor
|
|
||||||
|
|
||||||
For the full autoresearch lifecycle, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet supervise
|
|
||||||
```
|
|
||||||
|
|
||||||
The supervisor is the long-running engine. It:
|
|
||||||
|
|
||||||
1. Loads persisted state from `data/ratchet.sqlite`.
|
|
||||||
2. Waits through cooldown if cooldown is active.
|
|
||||||
3. Runs the next passive watch when due.
|
|
||||||
4. Writes reports and lifecycle events.
|
|
||||||
5. Re-enters cooldown.
|
|
||||||
6. Repeats until you stop it.
|
|
||||||
|
|
||||||
If it crashes or the Mac reboots, start the same command again. It resumes from SQLite.
|
|
||||||
|
|
||||||
Use this to inspect persisted state without starting the loop:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet supervise --status
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manual Braiins Exposure
|
|
||||||
|
|
||||||
If you manually start a Braiins bid, record it immediately:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet position open --description "Braiins order abc, 0.0001 BTC, 3h canary" --maturity-hours 72
|
|
||||||
```
|
|
||||||
|
|
||||||
While a manual position is active:
|
|
||||||
|
|
||||||
1. The cockpit says `HOLD`.
|
|
||||||
2. The supervisor blocks new watch experiments.
|
|
||||||
3. Restarting the app keeps the manual exposure state.
|
|
||||||
|
|
||||||
List positions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet position list
|
|
||||||
```
|
|
||||||
|
|
||||||
When the Braiins/OCEAN exposure is truly finished:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/ratchet position close POSITION_ID
|
|
||||||
```
|
|
||||||
|
|
||||||
## Native Mac App
|
## Native Mac App
|
||||||
|
|
||||||
The native SwiftUI shell is in:
|
The native SwiftUI app is in:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
macos/BraiinsRatchet
|
macos/BraiinsRatchet
|
||||||
|
|
@ -140,34 +64,23 @@ Build and open the real app bundle:
|
||||||
|
|
||||||
This creates `macos/build/Braiins Ratchet.app`. After that, you can open that app bundle directly from Finder or pin it in the Dock.
|
This creates `macos/build/Braiins Ratchet.app`. After that, you can open that app bundle directly from Finder or pin it in the Dock.
|
||||||
|
|
||||||
The app is a native cockpit over the same durable Python lifecycle engine.
|
|
||||||
|
|
||||||
The app includes controls to record and close manual exposure, but the same rule applies: it never places Braiins orders.
|
|
||||||
|
|
||||||
The app is organized as:
|
The app is organized as:
|
||||||
|
|
||||||
1. `Mission Control`: one exact next action, cooldown, metrics, and direct watch-only controls.
|
1. `Mission Control`: current decision, control ownership, next passive action, progress, evidence, and plain English interpretation.
|
||||||
2. `Research Map`: visual autoresearch stage model.
|
2. `Research Map`: visual autoresearch stage model.
|
||||||
3. `Manual Exposure`: record or close manually executed Braiins exposure.
|
3. `Manual Exposure`: record or close manually executed Braiins exposure.
|
||||||
4. `Reports`: raw cockpit, report, and ledger artifacts.
|
4. `Advanced`: raw cockpit, report, and ledger artifacts for diagnostics.
|
||||||
5. `Ratchet Lecture`: the general observe, hypothesize, bound, mature, adapt method.
|
5. `Ratchet Lecture`: the general observe, hypothesize, bound, mature, adapt method.
|
||||||
|
|
||||||
## Research Pathway
|
## Research Pathway
|
||||||
|
|
||||||
The cockpit has two different time horizons:
|
The app has three time horizons:
|
||||||
|
|
||||||
1. `DO THIS NOW` is the only command you should run next.
|
1. `Immediate`: what can happen now, usually start, wait, refresh, or hold.
|
||||||
2. `Ratchet Pathway Forecast` tells you what the next stages probably look like.
|
2. `Midterm`: what probably happens after the current watch, cooldown, or manual exposure matures.
|
||||||
|
3. `Longterm`: what could happen after multiple evidence artifacts point in the same direction.
|
||||||
|
|
||||||
The forecast is not a profit prediction. It is a workload and research-flow prediction.
|
The pathway is allowed to change after each report. That is the point of ratcheting: the next stage adapts to measured evidence instead of following a rigid plan.
|
||||||
|
|
||||||
It is split into:
|
|
||||||
|
|
||||||
1. `Immediate`: what happens now.
|
|
||||||
2. `Midterm`: what probably happens after the current run or sample.
|
|
||||||
3. `Longterm`: what could happen after multiple reports mature.
|
|
||||||
|
|
||||||
Expect the pathway to change after each report. That is the point of ratcheting: the next stage adapts to measured evidence instead of following a rigid plan.
|
|
||||||
|
|
||||||
## What The Actions Mean
|
## What The Actions Mean
|
||||||
|
|
||||||
|
|
@ -177,7 +90,7 @@ Expect the pathway to change after each report. That is the point of ratcheting:
|
||||||
|
|
||||||
`manual_bid` means the stricter profit-seeking guardrails cleared. The code still does not place the order. You decide manually in Braiins.
|
`manual_bid` means the stricter profit-seeking guardrails cleared. The code still does not place the order. You decide manually in Braiins.
|
||||||
|
|
||||||
## Where The Reports Are
|
## Where The Evidence Lives
|
||||||
|
|
||||||
The master ledger is:
|
The master ledger is:
|
||||||
|
|
||||||
|
|
@ -191,12 +104,24 @@ Each completed watch creates one run report:
|
||||||
reports/run-*.md
|
reports/run-*.md
|
||||||
```
|
```
|
||||||
|
|
||||||
Older sessions can be embedded with:
|
Use the app's `Advanced` tab when you need raw artifacts. Mission Control intentionally hides raw logs during normal operation.
|
||||||
|
|
||||||
|
## Advanced Fallback Commands
|
||||||
|
|
||||||
|
Use these only if the native app cannot be opened or you are debugging:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/ratchet retro START_UTC END_UTC
|
./scripts/ratchet
|
||||||
|
./scripts/ratchet once
|
||||||
|
./scripts/ratchet watch 2
|
||||||
|
./scripts/ratchet supervise
|
||||||
|
./scripts/ratchet position list
|
||||||
|
./scripts/ratchet report
|
||||||
|
./scripts/ratchet experiments
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The preferred workflow remains the native app.
|
||||||
|
|
||||||
## The Ratchet Rule
|
## The Ratchet Rule
|
||||||
|
|
||||||
One run is not a verdict. One run is a measurement.
|
One run is not a verdict. One run is a measurement.
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ The SwiftUI app turns that into native surfaces:
|
||||||
- `Mission Control`: one exact action, cooldown, direct watch-only controls, and metrics.
|
- `Mission Control`: one exact action, cooldown, direct watch-only controls, and metrics.
|
||||||
- `Research Map`: the ratchet pathway as a visual stage model.
|
- `Research Map`: the ratchet pathway as a visual stage model.
|
||||||
- `Manual Exposure`: the ledger for real manually placed Braiins exposure.
|
- `Manual Exposure`: the ledger for real manually placed Braiins exposure.
|
||||||
- `Reports`: raw artifacts kept available but no longer primary.
|
- `Advanced`: raw artifacts kept available but no longer primary.
|
||||||
- `Ratchet Lecture`: a teachable model of observe, hypothesize, bound, mature, adapt.
|
- `Ratchet Lecture`: a teachable model of observe, hypothesize, bound, mature, adapt.
|
||||||
|
|
||||||
## The Ratchet UX Rule
|
## The Ratchet UX Rule
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Braiins Ratchet Mac
|
# Braiins Ratchet Mac
|
||||||
|
|
||||||
Native SwiftUI shell for the durable Braiins Ratchet lifecycle engine.
|
Native SwiftUI control room for the durable Braiins Ratchet lifecycle engine.
|
||||||
|
|
||||||
The Python supervisor remains the source of truth. This app reads the same repository-local SQLite state through `./scripts/ratchet`.
|
The Python lifecycle engine remains the source of truth. This app reads the same repository-local SQLite state through structured app state, not by making Mission Control a terminal transcript.
|
||||||
|
|
||||||
## Normal Run
|
## Normal Run
|
||||||
|
|
||||||
|
|
@ -19,10 +19,10 @@ This builds `macos/build/Braiins Ratchet.app` and opens the packaged app. Use th
|
||||||
- Research Map with the full ratchet pathway.
|
- Research Map with the full ratchet pathway.
|
||||||
- Direct watch-only controls without an approval gate.
|
- Direct watch-only controls without an approval gate.
|
||||||
- Manual exposure recording and closing controls.
|
- Manual exposure recording and closing controls.
|
||||||
- Reports panel for raw artifacts.
|
- Advanced panel for raw artifacts and backend diagnostics.
|
||||||
- Ratchet Lecture for the general autoresearch method.
|
- Ratchet Lecture for the general autoresearch method.
|
||||||
- Monitor-only. It never places Braiins orders.
|
- Monitor-only. It never places Braiins orders.
|
||||||
|
|
||||||
## Product Direction
|
## Product Direction
|
||||||
|
|
||||||
The next production step is wiring LaunchAgent controls for the durable supervisor.
|
The next production step is wiring LaunchAgent controls for the durable supervisor while keeping Mission Control domain-first.
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,6 @@ struct ContentView: View {
|
||||||
case .mission:
|
case .mission:
|
||||||
MissionControlView(
|
MissionControlView(
|
||||||
appState: appState,
|
appState: appState,
|
||||||
transcript: transcript,
|
|
||||||
isRunning: isRunning,
|
isRunning: isRunning,
|
||||||
glow: glow,
|
glow: glow,
|
||||||
refresh: { Task { await refreshAppState() } },
|
refresh: { Task { await refreshAppState() } },
|
||||||
|
|
@ -98,8 +97,8 @@ struct ContentView: View {
|
||||||
close: closeManualExposure,
|
close: closeManualExposure,
|
||||||
list: { Task { await runTextCommand(label: "position list", ["position", "list"], refreshAfterwards: true) } }
|
list: { Task { await runTextCommand(label: "position list", ["position", "list"], refreshAfterwards: true) } }
|
||||||
)
|
)
|
||||||
case .reports:
|
case .advanced:
|
||||||
ReportsView(
|
AdvancedView(
|
||||||
transcript: transcript,
|
transcript: transcript,
|
||||||
lastCommand: lastCommand,
|
lastCommand: lastCommand,
|
||||||
isRunning: isRunning,
|
isRunning: isRunning,
|
||||||
|
|
@ -224,7 +223,7 @@ enum AppSection: String, CaseIterable, Identifiable {
|
||||||
case mission
|
case mission
|
||||||
case map
|
case map
|
||||||
case exposure
|
case exposure
|
||||||
case reports
|
case advanced
|
||||||
case lecture
|
case lecture
|
||||||
|
|
||||||
var id: String { rawValue }
|
var id: String { rawValue }
|
||||||
|
|
@ -234,7 +233,7 @@ enum AppSection: String, CaseIterable, Identifiable {
|
||||||
case .mission: "Mission Control"
|
case .mission: "Mission Control"
|
||||||
case .map: "Research Map"
|
case .map: "Research Map"
|
||||||
case .exposure: "Manual Exposure"
|
case .exposure: "Manual Exposure"
|
||||||
case .reports: "Reports"
|
case .advanced: "Advanced"
|
||||||
case .lecture: "Ratchet Lecture"
|
case .lecture: "Ratchet Lecture"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +243,7 @@ enum AppSection: String, CaseIterable, Identifiable {
|
||||||
case .mission: "scope"
|
case .mission: "scope"
|
||||||
case .map: "point.3.connected.trianglepath.dotted"
|
case .map: "point.3.connected.trianglepath.dotted"
|
||||||
case .exposure: "lock.shield"
|
case .exposure: "lock.shield"
|
||||||
case .reports: "doc.text.magnifyingglass"
|
case .advanced: "wrench.and.screwdriver"
|
||||||
case .lecture: "graduationcap"
|
case .lecture: "graduationcap"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +251,6 @@ enum AppSection: String, CaseIterable, Identifiable {
|
||||||
|
|
||||||
struct MissionControlView: View {
|
struct MissionControlView: View {
|
||||||
let appState: AppStatePayload?
|
let appState: AppStatePayload?
|
||||||
let transcript: String
|
|
||||||
let isRunning: Bool
|
let isRunning: Bool
|
||||||
let glow: Bool
|
let glow: Bool
|
||||||
let refresh: () -> Void
|
let refresh: () -> Void
|
||||||
|
|
@ -268,6 +266,7 @@ struct MissionControlView: View {
|
||||||
VStack(spacing: 14) {
|
VStack(spacing: 14) {
|
||||||
AutoresearchOrb(phase: ResearchPhase.from(appState), glow: glow)
|
AutoresearchOrb(phase: ResearchPhase.from(appState), glow: glow)
|
||||||
.frame(height: 250)
|
.frame(height: 250)
|
||||||
|
ControlOwnershipCard(appState: appState, isRunning: isRunning)
|
||||||
PassiveRunCard(
|
PassiveRunCard(
|
||||||
plan: appState?.automationPlan,
|
plan: appState?.automationPlan,
|
||||||
isRunning: isRunning,
|
isRunning: isRunning,
|
||||||
|
|
@ -277,9 +276,9 @@ struct MissionControlView: View {
|
||||||
.frame(width: 350)
|
.frame(width: 350)
|
||||||
}
|
}
|
||||||
|
|
||||||
MetricsDeck(appState: appState)
|
EvidenceDeck(appState: appState)
|
||||||
ResearchTimeline(appState: appState, compact: false)
|
ResearchTimeline(appState: appState, compact: false)
|
||||||
PlainEnglishCard(appState: appState, transcript: transcript)
|
PlainEnglishCard(appState: appState)
|
||||||
}
|
}
|
||||||
.padding(28)
|
.padding(28)
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +311,7 @@ struct HeroPanel: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Text("Do This Now")
|
Text("Current Decision")
|
||||||
.font(.caption.weight(.heavy))
|
.font(.caption.weight(.heavy))
|
||||||
.textCase(.uppercase)
|
.textCase(.uppercase)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
@ -322,17 +321,6 @@ struct HeroPanel: View {
|
||||||
Text(directive.detail)
|
Text(directive.detail)
|
||||||
.font(.title3.weight(.semibold))
|
.font(.title3.weight(.semibold))
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
|
|
||||||
if let command = directive.command {
|
|
||||||
HStack(spacing: 10) {
|
|
||||||
Image(systemName: "terminal")
|
|
||||||
Text(command)
|
|
||||||
.font(.system(.body, design: .monospaced).weight(.semibold))
|
|
||||||
}
|
|
||||||
.padding(12)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.background(.black.opacity(0.18), in: RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let watch = appState?.operatorState.completedWatch {
|
if let watch = appState?.operatorState.completedWatch {
|
||||||
|
|
@ -345,6 +333,53 @@ struct HeroPanel: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ControlOwnershipCard: View {
|
||||||
|
let appState: AppStatePayload?
|
||||||
|
let isRunning: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GlassPanel(padding: 16) {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
Label("Who Is In Control", systemImage: symbol)
|
||||||
|
.font(.headline)
|
||||||
|
Text(title)
|
||||||
|
.font(.title3.weight(.bold))
|
||||||
|
Text(detail)
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var symbol: String {
|
||||||
|
if isRunning { return "gearshape.2" }
|
||||||
|
guard let state = appState?.operatorState else { return "questionmark.circle" }
|
||||||
|
if state.activeWatch != nil { return "binoculars" }
|
||||||
|
if !state.activeManualPositions.isEmpty { return "lock.shield" }
|
||||||
|
if state.completedWatch != nil { return "timer" }
|
||||||
|
return "scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var title: String {
|
||||||
|
if isRunning { return "The app is busy" }
|
||||||
|
guard let state = appState?.operatorState else { return "Loading state" }
|
||||||
|
if state.activeWatch != nil { return "A watch run owns control" }
|
||||||
|
if !state.activeManualPositions.isEmpty { return "Manual exposure owns control" }
|
||||||
|
if state.completedWatch != nil { return "Cooldown owns control" }
|
||||||
|
return "The app is ready"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var detail: String {
|
||||||
|
if isRunning { return "A monitor-only operation is running. Do not start a competing action." }
|
||||||
|
guard let state = appState?.operatorState else { return "Reading the lifecycle database." }
|
||||||
|
if state.activeWatch != nil { return "Let the watch finish; duplicate watches corrupt the research trail." }
|
||||||
|
if !state.activeManualPositions.isEmpty { return "A real-world position is active, so new experiments stay blocked." }
|
||||||
|
if let watch = state.completedWatch { return "Wait until \(watch.earliestActionLocal) before the next useful sample." }
|
||||||
|
return "No active watch, no manual exposure, and no cooldown block."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct PassiveRunCard: View {
|
struct PassiveRunCard: View {
|
||||||
let plan: AutomationPlanPayload?
|
let plan: AutomationPlanPayload?
|
||||||
let isRunning: Bool
|
let isRunning: Bool
|
||||||
|
|
@ -353,7 +388,7 @@ struct PassiveRunCard: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GlassPanel {
|
GlassPanel {
|
||||||
VStack(alignment: .leading, spacing: 14) {
|
VStack(alignment: .leading, spacing: 14) {
|
||||||
Label("Watch-only Control", systemImage: "binoculars")
|
Label("Next Passive Action", systemImage: "arrow.forward.circle")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.title3.weight(.bold))
|
.font(.title3.weight(.bold))
|
||||||
|
|
@ -419,7 +454,7 @@ struct PassiveRunCard: View {
|
||||||
switch plan.kind {
|
switch plan.kind {
|
||||||
case "once_now": return "Refresh Now"
|
case "once_now": return "Refresh Now"
|
||||||
case "watch_2h": return "Start Watch-only Run"
|
case "watch_2h": return "Start Watch-only Run"
|
||||||
case "wait_then_once": return plan.waitSeconds > 0 ? "Wait" : "Refresh Now"
|
case "wait_then_once": return plan.waitSeconds > 0 ? "Cooldown Active" : "Refresh Now"
|
||||||
case "report_only": return "Open Report"
|
case "report_only": return "Open Report"
|
||||||
default: return "Refresh State"
|
default: return "Refresh State"
|
||||||
}
|
}
|
||||||
|
|
@ -438,43 +473,50 @@ struct PassiveRunCard: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MetricsDeck: View {
|
struct EvidenceDeck: View {
|
||||||
let appState: AppStatePayload?
|
let appState: AppStatePayload?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 14), count: 4), spacing: 14) {
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 14), count: 4), spacing: 14) {
|
||||||
MetricTile(
|
MetricTile(
|
||||||
title: "Market Freshness",
|
title: "Braiins Market",
|
||||||
value: freshnessText,
|
value: marketPrice,
|
||||||
detail: appState?.operatorState.latestMarketTimestamp ?? "no sample",
|
detail: marketDetail,
|
||||||
symbol: "clock"
|
symbol: "chart.line.uptrend.xyaxis"
|
||||||
)
|
)
|
||||||
MetricTile(
|
MetricTile(
|
||||||
title: "Strategy Action",
|
title: "Model Net",
|
||||||
value: appState?.operatorState.action ?? "none",
|
value: proposalValue("expected_net_btc", fallback: "n/a"),
|
||||||
detail: actionDetail,
|
detail: actionDetail,
|
||||||
symbol: "target"
|
symbol: "plus.forwardslash.minus"
|
||||||
)
|
)
|
||||||
MetricTile(
|
MetricTile(
|
||||||
title: "Manual Exposure",
|
title: "OCEAN Pool",
|
||||||
value: exposureText,
|
value: oceanValue("pool_hashrate_eh_s", suffix: " EH/s"),
|
||||||
detail: "Blocks new experiments while active",
|
detail: oceanValue("network_difficulty_t", prefix: "difficulty "),
|
||||||
symbol: "shield.lefthalf.filled"
|
symbol: "water.waves"
|
||||||
)
|
)
|
||||||
MetricTile(
|
MetricTile(
|
||||||
title: "Latest Report",
|
title: "Evidence",
|
||||||
value: appState?.operatorState.latestReport?.lastPathComponent ?? "none",
|
value: evidenceValue,
|
||||||
detail: appState?.operatorState.latestReport ?? "No artifact yet",
|
detail: evidenceDetail,
|
||||||
symbol: "doc.text"
|
symbol: "archivebox"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var freshnessText: String {
|
private var marketPrice: String {
|
||||||
guard let state = appState?.operatorState else { return "loading" }
|
if let fillable = appState?.latest.market?["fillable_price_btc_per_eh_day"]?.description, fillable != "n/a" {
|
||||||
if state.isFresh { return "fresh" }
|
return fillable
|
||||||
if let minutes = state.freshnessMinutes { return "stale \(minutes)m" }
|
}
|
||||||
return "unknown"
|
return appState?.latest.market?["best_ask_btc_per_eh_day"]?.description ?? "n/a"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var marketDetail: String {
|
||||||
|
let freshness = appState?.operatorState.freshnessMinutes.map { "\($0)m old" } ?? "age unknown"
|
||||||
|
let ask = appState?.latest.market?["best_ask_btc_per_eh_day"]?.description ?? "n/a"
|
||||||
|
let last = appState?.latest.market?["last_price_btc_per_eh_day"]?.description ?? "n/a"
|
||||||
|
return "\(freshness), ask \(ask), last \(last)"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var actionDetail: String {
|
private var actionDetail: String {
|
||||||
|
|
@ -484,9 +526,26 @@ struct MetricsDeck: View {
|
||||||
return "No useful market action"
|
return "No useful market action"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var exposureText: String {
|
private var evidenceValue: String {
|
||||||
let count = appState?.operatorState.activeManualPositions.count ?? 0
|
let count = appState?.operatorState.activeManualPositions.count ?? 0
|
||||||
return count == 0 ? "none" : "\(count) active"
|
if count > 0 { return "\(count) active exposure" }
|
||||||
|
return appState?.operatorState.latestReport?.lastPathComponent ?? "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var evidenceDetail: String {
|
||||||
|
if let watch = appState?.operatorState.completedWatch {
|
||||||
|
return "cooldown \(watch.remainingMinutes)m remaining"
|
||||||
|
}
|
||||||
|
return appState?.operatorState.latestReport ?? "No artifact yet"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func proposalValue(_ key: String, fallback: String) -> String {
|
||||||
|
appState?.latest.proposal?[key]?.description ?? fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
private func oceanValue(_ key: String, prefix: String = "", suffix: String = "") -> String {
|
||||||
|
guard let value = appState?.latest.ocean?[key]?.description else { return "n/a" }
|
||||||
|
return "\(prefix)\(value)\(suffix)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -617,7 +676,6 @@ struct TimelineNode: View {
|
||||||
|
|
||||||
struct PlainEnglishCard: View {
|
struct PlainEnglishCard: View {
|
||||||
let appState: AppStatePayload?
|
let appState: AppStatePayload?
|
||||||
let transcript: String
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GlassPanel {
|
GlassPanel {
|
||||||
|
|
@ -799,7 +857,7 @@ struct ActiveExposureList: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReportsView: View {
|
struct AdvancedView: View {
|
||||||
let transcript: String
|
let transcript: String
|
||||||
let lastCommand: String
|
let lastCommand: String
|
||||||
let isRunning: Bool
|
let isRunning: Bool
|
||||||
|
|
@ -811,9 +869,9 @@ struct ReportsView: View {
|
||||||
VStack(alignment: .leading, spacing: 18) {
|
VStack(alignment: .leading, spacing: 18) {
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("Reports")
|
Text("Advanced")
|
||||||
.font(.system(size: 36, weight: .black, design: .rounded))
|
.font(.system(size: 36, weight: .black, design: .rounded))
|
||||||
Text("Raw artifacts remain here, away from the noob cockpit.")
|
Text("Raw artifacts and backend diagnostics live here, away from Mission Control.")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
@ -824,7 +882,7 @@ struct ReportsView: View {
|
||||||
|
|
||||||
GlassPanel {
|
GlassPanel {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Label(lastCommand, systemImage: "terminal")
|
Label(lastCommand, systemImage: "wrench.and.screwdriver")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
ScrollView {
|
ScrollView {
|
||||||
Text(transcript)
|
Text(transcript)
|
||||||
|
|
@ -1095,37 +1153,35 @@ enum ResearchPhase {
|
||||||
struct Directive {
|
struct Directive {
|
||||||
let title: String
|
let title: String
|
||||||
let detail: String
|
let detail: String
|
||||||
let command: String?
|
|
||||||
let color: Color
|
let color: Color
|
||||||
|
|
||||||
static func from(_ appState: AppStatePayload?) -> Directive {
|
static func from(_ appState: AppStatePayload?) -> Directive {
|
||||||
guard let appState else {
|
guard let appState else {
|
||||||
return Directive(title: "LOAD STATE", detail: "The app is reading the ratchet lifecycle database.", command: nil, color: .secondary)
|
return Directive(title: "LOAD STATE", detail: "The app is reading the ratchet lifecycle database.", color: .secondary)
|
||||||
}
|
}
|
||||||
if let watch = appState.operatorState.completedWatch {
|
if let watch = appState.operatorState.completedWatch {
|
||||||
return Directive(
|
return Directive(
|
||||||
title: "STOP",
|
title: "STOP",
|
||||||
detail: "Wait until \(watch.earliestActionLocal). Repeating the same watch now would be loop-chasing.",
|
detail: "Wait until \(watch.earliestActionLocal). Repeating the same watch now would be loop-chasing.",
|
||||||
command: "./scripts/ratchet once",
|
|
||||||
color: .orange
|
color: .orange
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if appState.operatorState.activeWatch != nil {
|
if appState.operatorState.activeWatch != nil {
|
||||||
return Directive(title: "WAIT", detail: "A watch is already running. Do not start another one.", command: nil, color: .orange)
|
return Directive(title: "WAIT", detail: "A watch is already running. Do not start another one.", color: .orange)
|
||||||
}
|
}
|
||||||
if !appState.operatorState.activeManualPositions.isEmpty {
|
if !appState.operatorState.activeManualPositions.isEmpty {
|
||||||
return Directive(title: "HOLD", detail: "Manual Braiins exposure is active. Supervise it; do not start new experiments.", command: "./scripts/ratchet supervise --status", color: .orange)
|
return Directive(title: "HOLD", detail: "Manual Braiins exposure is active. Supervise it; do not start new experiments.", color: .orange)
|
||||||
}
|
}
|
||||||
if !appState.operatorState.hasOcean || !appState.operatorState.hasMarket || !appState.operatorState.isFresh {
|
if !appState.operatorState.hasOcean || !appState.operatorState.hasMarket || !appState.operatorState.isFresh {
|
||||||
return Directive(title: "REFRESH", detail: "The latest market state is stale or missing. Collect exactly one fresh sample.", command: "./scripts/ratchet once", color: .green)
|
return Directive(title: "REFRESH", detail: "The latest market state is stale or missing. Collect exactly one fresh sample.", color: .green)
|
||||||
}
|
}
|
||||||
if appState.operatorState.action == "manual_canary" {
|
if appState.operatorState.action == "manual_canary" {
|
||||||
return Directive(title: "WATCH", detail: "Run one bounded passive watch. This buys information, not a promise of profit.", command: "./scripts/ratchet watch 2", color: .green)
|
return Directive(title: "WATCH", detail: "Run one bounded passive watch. This buys information, not a promise of profit.", color: .green)
|
||||||
}
|
}
|
||||||
if appState.operatorState.action == "manual_bid" {
|
if appState.operatorState.action == "manual_bid" {
|
||||||
return Directive(title: "REVIEW", detail: "Read the full report before any manual Braiins action.", command: "./scripts/ratchet report", color: .green)
|
return Directive(title: "REVIEW", detail: "Read the full report before any manual Braiins action.", color: .green)
|
||||||
}
|
}
|
||||||
return Directive(title: "OBSERVE", detail: "No useful action window is visible right now.", command: nil, color: .secondary)
|
return Directive(title: "OBSERVE", detail: "No useful action window is visible right now.", color: .secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,16 @@ class MacAppPackagingTest(unittest.TestCase):
|
||||||
self.assertIn("./scripts/ratchet app", text)
|
self.assertIn("./scripts/ratchet app", text)
|
||||||
self.assertNotIn("swift run BraiinsRatchetMac", text)
|
self.assertNotIn("swift run BraiinsRatchetMac", text)
|
||||||
|
|
||||||
|
def test_start_here_is_app_first_and_not_pipeline_first(self):
|
||||||
|
text = (ROOT / "START_HERE.md").read_text()
|
||||||
|
|
||||||
|
self.assertIn("This project now has one normal operator entry point", text)
|
||||||
|
self.assertIn("./scripts/ratchet app", text)
|
||||||
|
self.assertIn("The app is the control room", text)
|
||||||
|
self.assertIn("Who Is In Control", text)
|
||||||
|
self.assertNotIn("Controlled Automation", text)
|
||||||
|
self.assertNotIn("./scripts/ratchet pipeline", text)
|
||||||
|
|
||||||
def test_swift_app_uses_native_dashboard_not_raw_terminal_as_primary_ui(self):
|
def test_swift_app_uses_native_dashboard_not_raw_terminal_as_primary_ui(self):
|
||||||
source = ROOT / "macos" / "BraiinsRatchet" / "Sources" / "BraiinsRatchetMac" / "BraiinsRatchetApp.swift"
|
source = ROOT / "macos" / "BraiinsRatchet" / "Sources" / "BraiinsRatchetMac" / "BraiinsRatchetApp.swift"
|
||||||
text = source.read_text()
|
text = source.read_text()
|
||||||
|
|
@ -56,6 +66,11 @@ class MacAppPackagingTest(unittest.TestCase):
|
||||||
self.assertIn("AppStatePayload", text)
|
self.assertIn("AppStatePayload", text)
|
||||||
self.assertIn("loadAppState", text)
|
self.assertIn("loadAppState", text)
|
||||||
self.assertIn("PassiveRunCard", text)
|
self.assertIn("PassiveRunCard", text)
|
||||||
|
self.assertIn("ControlOwnershipCard", text)
|
||||||
|
self.assertIn("EvidenceDeck", text)
|
||||||
|
self.assertIn("AdvancedView", text)
|
||||||
|
self.assertIn("Current Decision", text)
|
||||||
|
self.assertNotIn("Do This Now", text)
|
||||||
self.assertNotIn("Automation Gate", text)
|
self.assertNotIn("Automation Gate", text)
|
||||||
self.assertNotIn("confirmationDialog", text)
|
self.assertNotIn("confirmationDialog", text)
|
||||||
self.assertNotIn("showAutomationApproval", text)
|
self.assertNotIn("showAutomationApproval", text)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue