Skip to content

Docs: REST API reference - #4

Merged
frahlg merged 1 commit into
masterfrom
docs/api-reference
Apr 15, 2026
Merged

frahlg merged 1 commit into
masterfrom
docs/api-reference

Conversation

@frahlg

@frahlg frahlg commented Apr 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds docs/api.md — a complete REST API reference projected from go/internal/api/api.go
  • Groups all 29 endpoints by domain (status+control, config, history+series, devices+drivers, MPC, ML twins, battery models+self-tune, HA, forecast+prices) with method, purpose, query params, request body, response shape, and side notes for each
  • Every endpoint entry cites its handler file:line; base URL / auth / content-type / no-WS covered in the top conventions section; site sign convention called out where power fields appear

Test plan

  • cd go && go build ./... clean
  • cd go && go test -timeout 120s -count=1 ./... green
  • Every documented endpoint has a matching s.handle(…) in go/internal/api/api.go

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Generated with Claude Code

Projects go/internal/api/api.go into a readable per-endpoint spec
grouped by domain (status, config, history, devices, MPC, twins,
battery models, HA, prices/forecast). Each entry cites its handler
line, notes query params + request/response shape, and calls out
site-sign gotchas where relevant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3666ebbe62

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/api.md
}
```

Both lists are sorted alphabetically

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drop sorted-order guarantee for series catalog lists

The docs promise alphabetical ordering here, but handleSeriesCatalog just forwards State.DriverNames() and State.MetricNames(), and both functions currently iterate maps without sorting (go/internal/api/api.go:756-770, go/internal/state/store_ts.go:206-218). Consumers that trust this contract can see nondeterministic ordering between runs, which is enough to cause flaky tests and unstable dropdown ordering.

Useful? React with 👍 / 👎.

@frahlg
frahlg merged commit 0d2cf6d into master Apr 15, 2026
@frahlg
frahlg deleted the docs/api-reference branch April 15, 2026 06:46
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.0.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

erikarenhill added a commit that referenced this pull request Apr 20, 2026
5-agent vote per comment accepted 6 of 7:

- Offload HealthTick observeAt to a goroutine so a slow ntfy Publish
  (10 s timeout) can't stall the control loop. The event bus runs
  handlers inline on the publisher, and HealthTick is emitted every
  control tick. (Copilot #1)
- Nil-publisher guard in dispatch(): cold-start or reload with an
  unknown/unconfigured provider used to panic at s.pub.Publish; now
  it's treated as a failed dispatch with a history entry. (Copilot #3)
- Emit NotificationDispatched on template render failures too, so the
  history UI surfaces template errors instead of only publish errors.
  (Copilot #4)
- activeAlert leak fix: when no driver_recovered rule is enabled, a
  post-pass cleanup clears activeAlert for any driver whose telemetry
  is fresh (<30 s). Preserves the original lifecycle: when recovered
  IS enabled, the recovered rule still owns the clear. (Copilot #5)
- Stop seeding backend default templates into currentConfig. Render
  title/body template fields as raw inputs whose placeholder is the
  server default. Blank stays blank, backend fallback keeps working,
  and future server-side default changes apply without operator
  action. (Copilot #6)
- driver_recovered ignores threshold_s on the backend (hardcoded 30 s
  staleness window). Don't render the threshold input for that event
  type — show a clarifying note instead. (Copilot #7)

Tests: new TestActiveAlertClearsWhenRecoveredDisabled and
TestNilPublisherNoPanic. Full suite still green (35 packages).

Skipped Copilot #2 (SQLite write in NotificationDispatched subscriber
blocks the publisher): the write runs after HTTP publish on a path
that already blocks for up to 10 s, a sub-millisecond SQLite insert
adds nothing measurable. 3-of-5 agent votes INVALID.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
erikarenhill added a commit that referenced this pull request Apr 30, 2026
Five comments, four logical fixes:

#1, #2, #3 — config.SafetyMarginA: float64 → *float64 so we can
distinguish "unset → use 0.5 default" from "explicit 0 → operator
chose to disable margin". Single source of truth via new
DefaultFuseSafetyMarginA constant + Fuse.EffectiveSafetyMarginA()
helper. main.go startup + hot-reload both call the helper instead of
duplicating `<= 0 → 0.5` shortcuts that clobbered the disable case.
Validate() updated to dereference; explicit 0 is accepted (and
verified by a new round-trip test).

#4, #5 — direction attribution in applyFuseGuard + forceFuseDischarge
now gates on `currentGrid` (live aggregate at the meter) instead of
`predicted` (post-target hypothetical). Per-phase amps and
currentGrid come from the same DerMeter sample; predicted mixes in
sumTarget which can swing across 0 on a large planner request and
silently flip the gate, attributing a current-export overage to the
import path (or vice versa).

New tests:
- TestFuseSafetyMarginNilUsesDefault — back-compat for unset YAML
- TestFuseSafetyMarginExplicitZeroDisables — locks the new contract
- TestFuseSafetyMarginExplicitValuePassesThrough
- TestValidateRejectsNegativeSafetyMargin (parses YAML)
- TestValidateRejectsSafetyMarginAtOrAboveMaxAmps
- TestValidateAcceptsExplicitZeroSafetyMargin (round-trips through
  Parse + EffectiveSafetyMarginA to verify 0 stays 0)
erikarenhill added a commit that referenced this pull request May 20, 2026
Addresses Copilot review item #4 on PR #285: `PlannedGridW *float64`
forced the SlotDirective bridge in main.go to take the address of a
local variable, escape-allocating a *float64 every dispatch tick
(≈17k/day on the Pi at the default 5 s control interval — small but
unnecessary GC pressure).

Replaces the pointer with `PlannedGridW float64 + HasPlannedGridW bool`.
HasPlannedGridW=false is the new opt-out signal; the zero-value
SlotDirective used by legacy callers / older tests behaves identically
to the previous nil-pointer case. No behaviour change — the cap fires
under the same conditions, just gates on a bool instead of a nil check.

Wiring update in main.go's directive bridge: drops the `&plannedGridW`
local, sets `PlannedGridW: d.GridW, HasPlannedGridW: true` directly.

Tests updated: four PlannedGridW callsites switch from `ptrF64(X)` to
`PlannedGridW: X, HasPlannedGridW: true`. The nil-bypass regression
test (renamed `TestEnergyDispatchUnsetPlannedGridWBypassesCap`) now
asserts the zero-value (HasPlannedGridW=false) path stays bypassed.
frahlg added a commit that referenced this pull request Jun 5, 2026
Codex returned NO-GO on the parallel build; these close the findings relevant
to the shippable relay-hardening (slices 1+2):
- #1 CRITICAL: require -home-web when a home host is set (else the relay fell
  back to forwarding anonymous GETs to the Pi — the exact surface we forbid).
  requireHomePin now enforces it.
- #2 HIGH: device_pubkeys are now covered by the registration signature
  (MeRegisterSigningStringV2); a captured /me/register can no longer be replayed
  with a swapped/added device key. + regression test.
- #4 MED: symlink-escape in the -home-web static server closed (EvalSymlinks +
  re-check the resolved path stays under the web root).
Deferred to the C2-enforcement phase (inert until the device-key gate is ON),
with TODOs: #3 (bind device_pubkey to the enroll ceremony) and #5 (separate
per-IP limiter on challenge issuance). All Go unit + 97 web tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Jun 5, 2026
* feat(home-route): inline passkey sign-in on the dashboard

The dashboard now reveals a discreet sign-in banner + header key when whoami
reports the viewer isn't signed in on a remote origin, and runs the passkey
login ceremony in place over the same strict P2P channel (ownerFetch) — no
redirect to /owner-access/login.html, which would spawn a fresh channel with
no session. Re-checks on P2P (re)connect. LAN bypass + signed-in views never
show it. Closes the discoverability gap (part of #438 seamless UX).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home-route): full sign-in gate + informational transport indicator

Evolve the inline-signin banner into a real gate: a logged-out remote visitor
now sees ONLY a clean sign-in card, never the empty dashboard chrome (which
rendered 'No devices configured' and read as an unconfigured instance). The
gate covers the dashboard until whoami confirms a signed-in owner; the passkey
ceremony runs in place over the strict P2P channel. Suppresses the no-devices
prompt while logged out. The transport indicator is now informational (direct/
relayed/connecting), not a confusing toggle that broke the P2P-only channel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(home-route): no dashboard flash on reload behind the sign-in gate

A reload briefly painted the dashboard before next-app.js could cover it with
the gate. Add a pre-paint inline <head> script (mirroring the theme-flash
guard) that marks <html class=ftw-gated> on a remote origin from the first
frame; CSS shows the opaque full-screen gate immediately, and setupAuth()
clears ftw-gated once whoami confirms a signed-in owner. The gate is now a
class toggle (not the hidden attribute) so the pre-paint CSS can win.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: hardened-relay + device-key design spec (no anonymous path to a Pi)

Spec for the relay-hardening Fredrik approved: relay serves the sign-in shell
+ /api/identity from its own embedded copy (Pi not touched), and forwards
signaling to the Pi ONLY when it carries a device-key proof the relay can
verify. The device-key (LAN-born, wallet-bound) becomes the sole credential
that can reach a Pi from the internet, and also mints the session silently.
Surface-by-surface threat model + build slices + one open trade-off for review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home-route): hardened relay + device-key remote access (no anonymous path to a Pi)

Relay serves the sign-in shell (-home-web) + /api/identity (from -home-pubkey)
itself, so anonymous visitors never reach the Pi. Signaling is gated by a
device-key proof (relay-issued nonce, signed by a non-extractable WebCrypto
ECDSA P-256 key minted at LAN enrollment, verified against the device-pubkeys
the Pi publishes on /me/register) — else 403, Pi never woken. The device-key
also mints the session silently over the channel (device-PoP); step-up keeps a
passkey; revocation drops the key on Pi + relay. Built in parallel across relay/
Pi/browser against locked wire contracts (C1-C4), then integrated. All Go unit
tests + 97 web tests pass. Pending Codex security GO before live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(home-route): address Codex security review of the device-key build

Codex returned NO-GO on the parallel build; these close the findings relevant
to the shippable relay-hardening (slices 1+2):
- #1 CRITICAL: require -home-web when a home host is set (else the relay fell
  back to forwarding anonymous GETs to the Pi — the exact surface we forbid).
  requireHomePin now enforces it.
- #2 HIGH: device_pubkeys are now covered by the registration signature
  (MeRegisterSigningStringV2); a captured /me/register can no longer be replayed
  with a swapped/added device key. + regression test.
- #4 MED: symlink-escape in the -home-web static server closed (EvalSymlinks +
  re-check the resolved path stays under the web root).
Deferred to the C2-enforcement phase (inert until the device-key gate is ON),
with TODOs: #3 (bind device_pubkey to the enroll ceremony) and #5 (separate
per-IP limiter on challenge issuance). All Go unit + 97 web tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(relay): -require-device-key rollout flag (C2 gate dormant by default)

Slices 1+2 (relay serves the shell + /api/identity itself, closing the
anonymous-FETCH surface) ship independently of the device-key signaling gate:
-require-device-key defaults OFF, so signalBrowserOffer parks the raw offer
with no proof (pre-C2 behaviour) and a home Pi that doesn't yet publish
device-keys keeps working. Flip it on once device-keys are enrolled to close
the anonymous-SIGNALING surface. C2 tests now assert the enforced path; a new
test asserts the dormant default. All Go unit + web tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant