Skip to content

Energy UI β€” Compliance against the published spec

GFTCL-LION-ENERGY-LEDGER-001 / UI compliance audit. This document is a section-by-section cross-check of the published Apple/Swift UI spec against the actually-shipped implementation. It names matches, deliberate adaptations, and named-next gaps verbatim so any reviewer can compare without reading source.

TL;DR

Core functionality: 100% shipped. Governor control + live ledger bar + sweep chart + summit board with dependency locks + receipt list + replication endpoints + immutability triggers + USD scene + gate enforcement. All smoke tests ALL PASS.

Deliberate adaptations (different from the spec on purpose): 1. Int64 quanta instead of generic Rational. The Lean seal proves the theorems over Nat; the Swift gate uses Int64. Switching to a generic Rational layer would require a new Lean theorem and break the existing decide-closed proofs. The doctrine point β€” exact-arithmetic, no Float β€” is preserved. 2. GRDB direct reads through EnergySummitReader / AlertQueueReader actors instead of SwiftData @Model. Consistent with the existing app (LedgerReader, MeaningAtomResolver, etc., all use GRDB directly). SwiftData would be a re-architecture of the substrate read path. 3. Plain executableTarget smoke tests (swift run M8…) instead of Swift Testing @Test. Same invariants are exercised; tests run the same way as the rest of the cell's smoke tests.

Named-next gaps (called out, not silently missing): - Menu bar extra (MenuBarExtra with arm-state glyph + unread count) - Receipt detail sheet with the inline three-command repro string - Summit submission-log drill-in card - Asset-catalog semantic color tokens (currently inline Color(red:…)) - Dynamic Type full scaling (currently fixed font sizes) - ARMED+IDLE "listening" pulse animation (currently static color)


Section-by-section

0. Platform / tooling

Spec Implementation Status
macOS 15 (Sequoia) min @available(macOS 14.0, *) on EnergyDomainPanel shipped; min could tighten to 15 if desired
Swift 6 strict concurrency swiftLanguageMode(.v6) on EnergyUI, EnergyLedger, EnergyAlertableEvents βœ“
Modules: EnergyGovernor / EnergyWitness / EnergyUI / EnergyReplication / AlertKit Targets named EnergyLedger / EnergyUI / EnergyAlertableEvents / (alert primitive in GaiaFTCLCore) / replication in GaiaNodeServerCore adapted; same separation, different names. AlertKit is shared in core because finance + energy both consume it.

1. Concurrency / state architecture

Spec Implementation Status
@MainActor UI views EnergyGovernorModel is @MainActor @Observable; SwiftUI views run on main βœ“
actor LedgerBreakerEngine actor EnergyLedgerEngine (in EnergyLedger target) βœ“ same shape
actor WitnessStore Split: EnergyLedgerEngine writes receipts; EnergySummitReader reads/writes summits + arm log; SubstrateDatabase owns the GRDB pool adapted; same single-writer discipline
AsyncStream<LedgerTransition> UI bridge EnergyGovernorModel.transitions is @Observable and refreshes via refreshReceipts() after each enforce; UI observes property changes adapted; same outcome (UI updates without sync polling)
Sendable everywhere All structs (LedgerInput, LedgerReceipt, SummitRow, EnergyReceiptRow, AlertableValue, AlertRule) are Sendable value types βœ“

2. Domain models

Spec name Implementation name Notes
LedgerPartition LedgerInput (engine in/out via separate LedgerReceipt) same fields
BreakerVerdict LedgerTerminalState enum + receipt fields functionally equivalent
RefusalReason refused state recorded as terminal_state='refused' with witness columns showing input==output preserved as substrate state, not as Swift enum case
LedgerTransition EnergyReceiptRow same data, different name
SummitStatus / SummitState SummitRow + summit_states table βœ“
PreRegisteredPrediction columns on summit_states (effect_summary, predicted_values_json, success_criterion, null_result_clause, required_controls_json) denormalized into the table; trigger-immutable
LabSubmission LabSubmissionRow + lab_submissions table βœ“
GovernorArmState EnergyGovernorModel.ArmState: disarmed/armedIdle/armedEnforcing βœ“ exact match

3. SwiftUI surfaces

3.1 C⁴ rail entry β€” governor control

Spec Implementation Status
DISARMED dimmed, single "Arm Governor" affordance armControl View; DISARMED shows gray + button "Arm Governor" + "Receipts shown while disarmed are stale-flagged" note βœ“
ARMED+IDLE "listening" pulse shown as solid blue color + "Listening β€” no input." partial β€” color state βœ“; explicit pulse animation deferred
ARMED+ENFORCING active green + "Enforcing input β€” Ξ  active." βœ“
Toggling publishes gaiaftcl.energy.ledger.arm / .disarm EnergyGovernorModel.arm() / disarm() writes governor_arm_log AND ingests gaiaftcl.energy.governor.arm via AlertGovernor βœ“
Append-only arm-log row governor_arm_log table with prev_arm_log_id chain + immutability trigger βœ“
Stale flag on receipts when disarmed ReceiptRowView shows yellow "STALE" pill when model is disarmed βœ“

3.2 Live ledger readout

Spec Implementation Status
Conserved bar Sβ‚„ | Sᢜ with T marker conservedBar View; GeometryReader-driven proportional fill; T marker as white vertical line + "T" label βœ“
Sβ‚„ green / amber / refused-red s4Color computes color from current state βœ“
.animation(.snappy) on partition .animation(reduceMotion ? nil : .snappy, value: …) βœ“ + accessibilityReduceMotion
Scrolling trace of last N recentReceipts View; ScrollView + LazyVStack; newest first; tap-able tap drill-in deferred to benchtop sheet
Swift Charts sweep view (Sβ‚„ + Sᢜ vs input, RuleMark for T, PointMark for REFUSED) sweepChart View; LineMark for both dims + PointMark .cross for refused βœ“ (operator's xmark symbol replaced by .cross β€” RP equivalent)
Segmented control toggle Picker(selection:) with .segmented style between Conserved bar / Sweep chart βœ“
Both read same stream model.transitions for the bar; model.currentSession for the sweep; both populated from same enforce() returns βœ“
VoiceOver accessibility accessibilityLabel(accessibilityLabel(s4:sc:threshold:)) β€” narrates "S4 at X percent of threshold, S8 conserved at Y quanta, [state]" βœ“

3.3 Receipt surface

Spec Implementation Status
Input / output partitions visible ReceiptRowView shows Sβ‚„: 70 β†’ 50 and Sᢜ: 10 β†’ 30 βœ“
Sβ‚ˆ conservation check Image(systemName: conserves ? "checkmark.seal.fill" : "exclamationmark.triangle.fill") βœ“
Verdict glyph terminal state pill colored by state βœ“
Content-hash monospaced, copyable first 12 chars of witnessHashSHA256 with .textSelection(.enabled) βœ“
Witnessed-live flag implicit via STALE pill (its inverse) adapted β€” same information
sealedAt timestamp applied_at_iso prefix(19) βœ“
Disclosure with 3-command re-run NOT inline; will land in the upcoming Benchtop sheet named-next
Insert-only on sealed receipts substrate trigger trig_elr_witness_immutable enforces immutability βœ“ β€” at the SQL layer, not by missing UI methods

3.4 Summit / replication board

Spec Implementation Status
3 cards in dependency order summitBoard HStack(spacing: 10) { ForEach(model.summits) }; ordered by ring ASC βœ“
Status pill (4 states) StatusPill View; NAMED OPEN / IN REPLICATION / CLOSED Β· CONFIRMED / CLOSED Β· REFUTED βœ“
REFUTED rendered as first-class outcome (violet, NOT alarm red) closedRefuted β†’ Color(red: 0.55, green: 0.50, blue: 0.85) (violet); equal styling to confirmed βœ“ β€” explicit doctrinal honoring
Pre-registered prediction read-only with timestamp Text("Pre-registered \(formatDate(summit.preregisteredAtISO))") + Image(systemName: "lock.shield") βœ“
Pre-registration immutable substrate trigger trig_summit_states_preregistration_immutable βœ“
Submission log per summit not shown inline on cards named-next (drill-in sheet)
Dependency lock visible LockBadge overlay on summits whose dependency isn't closedConfirmed βœ“ explicit "locked until Ring N closed-confirmed" badge

3.5 Alert queue surface

Spec Implementation Status
Shared AlertKit primitive AlertGovernor + AlertableDomain protocol in GaiaFTCLCore βœ“
Energy as producer (not bespoke) EnergyAlertableEvents declared as the SECOND domain (finance was first) βœ“
Co-resident HOME queue view currently delivered through RSS feed + Apple notifications; no SwiftUI co-resident queue list named-next
Signal-colored rows rendered in RSS as [BAD] / [GOOD] / [INFO] prefixes βœ“ (RSS surface)
Witnessed-live proof binding event_payload_sha256 + snapshot_hash columns on alert_queue βœ“

3.6 Menu bar extra

Spec Implementation Status
MenuBarExtra arm state + unread count NOT implemented named-next

4. Alert delivery

Spec Implementation Status
Apple alert adapter AppleAlertAdapter in GaiaFTCLCore βœ“
UNUserNotificationCenter wrapped in #if canImport(UserNotifications) βœ“
Signal β†’ interruption level (bad=timeSensitive, good=passive, neutral=active) interruption(for:) βœ“
UNNotificationRequest userInfo includes domain / eventRef / signal / snapshotHash βœ“ in postNotification(_:) βœ“
Mark delivery_state = pushed markPushed(alertID:notifID:) writes alert_queue + alert_delivery_log row βœ“
Tap deep-link via openURL route userInfo["eventRef"] carried but the in-app URL handler gaiaftcl://energy/receipt/<id> is not wired named-next
Request authorization at first arm adapter requests at first drain() call partial β€” adapter-side, not strictly "at first arm"

5. Lab-facing served package

Spec Implementation Status
GET /energy/replication HTML + JSON EnergyReplicationRenderer.renderReplicationHTML/JSON mounted in HTTPListener βœ“
GET /energy/summits/feed RSS 2.0 renderSummitStatusRSS mounted βœ“
POST /energy/summits/{slug}/witness HTTPListener routes to acceptWitnessSubmission(summitSlug:membraneToken:body:) βœ“
Membrane-token authenticated ENERGY_LAB_TOKEN env var; constant-time compare; unauthorized β†’ 401 βœ“ v1; per-lab token registry is named-next
Structured against pre-registered fields submission body decoded as JSON; fields land in measured_values_json βœ“ (full schema validation against predicted_values_json shape is named-next)
replicationFeedPublic flag omitted in v1 β€” feeds are local-private by default (127.0.0.1 binding on the listener) adapted β€” same outcome via network binding, not via a feature flag

6. SwiftData model layer

Spec Implementation Status
@Model classes NOT used deliberate adaptation β€” see TL;DR
Sealed-records discipline enforced at the substrate layer via SQL triggers (witness columns immutable, append-only logs) βœ“ β€” same constitutional outcome
Content-hashed snapshots witness_hash_sha256 / snapshot_hash columns; computed via WitnessHasher.sha256Hex βœ“

7. Design system

Spec Implementation Status
Semantic color tokens in asset catalog (gaiaManifest, gaiaConstraint, gaiaPass, gaiaAmber, gaiaRefuse, gaiaTotal) Currently inline Color(red:green:blue:) matching the spec's intent named-next: extract to asset catalog tokens
Dynamic Type throughout fixed .font(.system(size: N)) named-next: switch to .body / .caption semantic sizes
Dark/light via asset catalog panel ships its own dark background; works dark only in v1 named-next: full asset-catalog support
Reduce-motion honored @Environment(\.accessibilityReduceMotion) on the bar animation βœ“
VoiceOver accessibility label on the bar with numeric content βœ“

8. Test suite (Swift Testing)

Spec idiom Implementation idiom Status
@Test annotations with #expect executableTarget mains under Sources/M8…SmokeTest/main.swift printing [OK] / [FAIL] adapted β€” same invariants exercised
6 spec-named tests (breaker repartition, refusal preserves conservation, disarmed stales, lab submission auth, null-result-closes) All 6 invariants covered across M8EnergyLedgerSmokeTest + M8WitnessAlertSmokeTest + M8MetricAuditGateTest βœ“ functionally
Lean kernel + Swift gate same invariants M8EnergyLedgerSmokeTest exercises the same three instances the Lean theorems reduce by decide; the substrate CHECK refuses any divergence βœ“ β€” dual-gate discipline holds

9. Build / ship checklist

Spec Implementation Status
Zero data-race warnings swift build clean βœ“
No @unchecked Sendable none βœ“
gaiaftcl.* subjects spelled correctly proof/scripts/check_nats_subject_spelling.sh greps on the pattern; build can wire this βœ“
SQLite witness store only SubstrateDatabase.shared.pool() βœ“
No ArangoDB / no external DB / no external API none βœ“
Submission requires membrane token 401 without ENERGY_LAB_TOKEN βœ“
Reduce-motion + VoiceOver verified βœ“ on the bar βœ“
Inventory audit run swift run M8InventoryAuditor β†’ COMPLETE βœ“
Notification authorization at first arm adapter-side at first drain() partial

What the next operator should do

To bring this from "core complete + named gaps" to "full spec compliance":

  1. MenuBarExtra with governor.armState.glyph + unread alert count β†’ opens HOME to the energy surface.
  2. Receipt detail sheet with the inline three-command repro string. Wire gaiaftcl://energy/receipt/<id> URL handler in GaiaFTCLApp to open the sheet.
  3. Summit drill-in card showing the lab_submissions log for one summit with pending/sealed_confirm/sealed_refute actions for the operator.
  4. Asset-catalog color tokens β€” extract every inline Color(red:…) into a Colors.xcassets with semantic names. Dynamic Type pass at the same time.
  5. ARMED+IDLE pulse β€” withAnimation(.easeInOut.repeatForever(autoreverses: true)) on the arm-control accent.
  6. Swift Testing @Test migration β€” rewrap the three smoke tests as @Test functions in a M8EnergyTests package.
  7. Per-lab membrane token registry with revocation + audit (in lab_credentials table; current ENERGY_LAB_TOKEN env-var is the v1 anchor).

Federation-cosigned

This page's source is sealed in the GaiaFTCL federation manifest β€” page SHA-256 b365b11d7a8c7faf…, manifest witness a090592e0609adc8…, signed 2026-06-02T18:58:22Z by cell gaiaftcl-mac-cell. Verify with gaiaftcl wiki sign --all and compare wiki-all-signatures.json.