From c93b675c7a40093671a2211bba7f428e669ef950 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 12 Sep 2026 06:38:50 +0000 Subject: [PATCH 1/2] docs: name the household loop as the product Write a product boundary into AGENTS.md: the box measures, plans and dispatches; everything else is an add-on. Standing non-goals, one live theme, and an expiry on open PRs so the queue cannot be a second roadmap. Own control, mpc, telemetry and web in CODEOWNERS because that loop is the product. A GitHub review still does not replace looking at the UI. Architecture and the roadmap point at AGENTS.md so LATER rows are not read as permission to land. Signed-off-by: Cursor Agent Co-authored-by: Fredrik Ahlgren --- .github/CODEOWNERS | 11 +++++++- AGENTS.md | 66 ++++++++++++++++++++++++++++++++++---------- APPROVAL_POLICY.md | 7 +++-- docs/architecture.md | 3 ++ docs/roadmap.md | 4 +++ 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 99674c35..8aabf798 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,10 +4,19 @@ # produced. # # This is about who gets asked, not who is allowed to contribute. +# The control loop is owned because that is the product. Process files are +# owned so agent rules cannot be rewritten in passing. -# Web/UI changes require a human visual check; see AGENTS.md. +# Web/UI changes still need a human visual check; see AGENTS.md. +# A GitHub review from the CODEOWNER does not replace looking at the page. # Do not auto-request @erikarenhill; he is not a CODEOWNER. +# The household loop: measure, plan, dispatch, and the UI that explains it. +/go/internal/control/ @frahlg +/go/internal/mpc/ @frahlg +/go/internal/telemetry/ @frahlg +/web/ @frahlg + # Drivers and the Lua host. /drivers/ @frahlg /go/internal/drivers/ @frahlg diff --git a/AGENTS.md b/AGENTS.md index 871e5dad..5ff15fb0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,32 @@ # FTW project guide -FTW is a local-first home energy management system written in Go, with Lua -drivers and a compiled Energyplan worker. +FTW is a box in the house that measures, plans and dispatches battery, PV and +charging so the site stays safe and intelligible when the cloud, the optimizer +or a driver is gone. + +That loop is the product. Everything else is an add-on. Work belongs in core +only when it makes that loop better this week, for a household, without a new +tariff model, a new protocol or a second safety authority. + +The implementation is Go, with Lua drivers and a compiled Energyplan worker. + +## Not this project + +These are standing no's, not a backlog. A later theme can reopen one of them; +until then they wait, and they do not queue against master. + +- Not a GIS or roof-drawing product. Irradiance that improves the plan is + core; a map of the house is not. +- Not a CPO. A local OCPP server that core commands is in; forwarding sessions + to an upstream operator waits. +- Not a Modbus server for other home-automation stacks. +- Not a worldwide tariff engine. Named household tariffs, after a written + contract, are enough. +- Not Matter, EnergyKit, V2X automation or a second commander on the site + until the household loop is boringly correct. +- A simulator is a test harness, not a second UI. + +The optimizer proposes. It is not the product. ## Architecture @@ -20,9 +45,9 @@ The repository has three explicit modules: [`go/internal/mpc`](go/internal/mpc). It proposes plans; core validates them and retains a Go fallback. -Keep new functionality in core unless it has a narrow versioned contract, -independent failure/update semantics and a safe unavailable state. Optional -modules never bypass core safety. +If work is not the household loop, it belongs outside core, and only with a +narrow versioned contract, independent failure and update semantics, and a +safe unavailable state. Optional modules never bypass core safety. Read [docs/architecture.md](docs/architecture.md) for the system map and [docs/site-convention.md](docs/site-convention.md) before changing power math. @@ -78,21 +103,31 @@ landed on somebody else's finished work. - **Check for open PRs touching the files you are about to change.** If one exists, it has right of way: rebase onto it, or say so and pick different work. Do not rewrite files out from under an open branch. +- **An open PR is a lock with an expiry.** Right of way is a reservation you + use. A PR whose base branch is already merged must be retargeted at + `master` or closed. Parking means the unique commits move, then the PR + closes. A branch that is not moving toward `master` is an abandoned + reservation, not a review queue. - **"This already exists" is a claim that needs evidence of the right kind.** For behaviour, that means a test or a run. For anything visual, it means rendering both versions and comparing them — counting tokens, selectors or symbols in the source does not establish that a UI change landed. -- **Prefer small PRs in one area.** Merging often only works when changes do - not overlap; a large cross-cutting pass conflicts with everything and ages - badly while it waits. +- **One live theme, small PRs inside it.** The default theme is the control + loop: measure, plan, dispatch. The repository owner may name a different + theme when opening that window. Merging often only works when changes do + not overlap. A large cross-cutting pass, or a green PR outside the theme, + waits; it does not queue against `master`. - **Respect `.github/CODEOWNERS`.** An owner reviews what lands in their - area, whoever — or whatever — wrote it. + area, whoever — or whatever — wrote it. Control, planning, telemetry and + the UI are owned because that is the product, not because they are + process files. - **Do not request GitHub reviews except CODEOWNERS.** Never request - @erikarenhill and never @mention people for attention — the GitHub + @erikarenhill and never @mention people for attention. The GitHub Discord bot turns both into pings. See [`APPROVAL_POLICY.md`](APPROVAL_POLICY.md). - **Review web/UI changes in a browser.** A human must inspect the rendered - interface; AI review and reading the source code are not enough. + interface; AI review, a GitHub approve and reading the source code are not + enough. Planning documents, design specs, task breakdowns and agent scratch notes stay out of the repository; [`.github/check-no-planning-docs.sh`](.github/check-no-planning-docs.sh) enforces @@ -145,10 +180,11 @@ because in this project review happens on the beta as much as in the PR. Fast-tracking moves a review, it never removes it — the CODEOWNERS owner still reviews what landed in their area, on the running beta. -Beta is the playground. Every merged change ships in the next beta; -testers run it on real sites and file what they find as issues or PRs, -each naming the beta it was seen on. Label a finding `release-blocker` -when the line must not promote to stable until it is fixed. +Beta is the playground for what landed in the live theme. Every merged +change ships in the next beta; testers run it on real sites and file what +they find as issues or PRs, each naming the beta it was seen on. Label a +finding `release-blocker` when the line must not promote to stable until it +is fixed. Green CI is not a reason to merge work outside the theme. There is no release calendar. A beta promotes once it has run on the validation sites for a few days with no open `release-blocker`, and diff --git a/APPROVAL_POLICY.md b/APPROVAL_POLICY.md index 347cce4b..9bdadb45 100644 --- a/APPROVAL_POLICY.md +++ b/APPROVAL_POLICY.md @@ -24,9 +24,10 @@ attention. The GitHub Discord bot turns those into pings too. ## When not to request anyone -If no CODEOWNERS path matches, request nobody. Web/UI changes need a -human looking at the rendered page (see AGENTS.md); that is not a -GitHub review request. +If no CODEOWNERS path matches, request nobody. + +A GitHub review from the web CODEOWNER is not the visual check. Web/UI +changes still need a human looking at the rendered page (see AGENTS.md). Do not re-request a reviewer on later pushes of the same PR. Once is enough. diff --git a/docs/architecture.md b/docs/architecture.md index d0b3bfa0..743d8df4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,6 +6,9 @@ safety boundary. Drivers translate hardware protocols. The optimizer proposes plans and supplies primary forecasts. A failure or upgrade outside core must never stop local measurement or make dispatch unsafe. +The product boundary — what that system is, and is not — lives in +[`AGENTS.md`](../AGENTS.md). This document is the system map. + ## Module boundaries | Module | Source | Runtime | Responsibility | diff --git a/docs/roadmap.md b/docs/roadmap.md index 3155cda4..ce5b1812 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -5,6 +5,10 @@ work already in implementation or acceptance. **NEXT** has a defined contract and entry gates. **LATER** items have no delivery promise; each must satisfy its promotion gate before it can move forward. +Operating law for what may land is [`AGENTS.md`](../AGENTS.md): the household +loop is the product, there is one live theme, and a LATER row is not +permission to open a PR against `master`. + The permanent rules do not move between lanes: core owns safety and dispatch, the site sign convention is unchanged, and local operation never depends on an optional service. Planner output remains untrusted input. A failed or stale From 21684fe07c658441512cb3eee7425201d43e151b Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 12 Sep 2026 10:48:59 +0200 Subject: [PATCH 2/2] docs: align FTW product vision and ownership Signed-off-by: Fredrik Ahlgren --- .cursor/approval-policies/ROUTING.md | 2 +- .github/CODEOWNERS | 29 -- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 19 +- .github/pull_request_template.md | 34 +-- AGENTS.md | 112 ++++---- APPROVAL_POLICY.md | 50 ++-- CONTRIBUTING.md | 70 +++-- README.md | 32 ++- SUPPORT.md | 6 +- VISION.md | 211 ++++++++++++++ docs/architecture.md | 32 ++- docs/roadmap.md | 311 +++++---------------- 13 files changed, 467 insertions(+), 443 deletions(-) delete mode 100644 .github/CODEOWNERS create mode 100644 VISION.md diff --git a/.cursor/approval-policies/ROUTING.md b/.cursor/approval-policies/ROUTING.md index 67a943fb..d984a001 100644 --- a/.cursor/approval-policies/ROUTING.md +++ b/.cursor/approval-policies/ROUTING.md @@ -2,4 +2,4 @@ boundary: / policies: - APPROVAL_POLICY.md - - Request GitHub reviews only from .github/CODEOWNERS for paths this PR changes. Never request erikarenhill. Do not select reviewers from commit history. + - Fredrik owns FTW. Do not automatically request reviewers or mention people for attention. Follow an explicit owner assignment; never infer reviewers from commit history. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 8aabf798..00000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,29 +0,0 @@ -# Ownership is here so overlap is caught by the tooling instead of by two -# people discovering they built the same thing twice. An owner's review is -# required before anything lands in their area — including work an agent -# produced. -# -# This is about who gets asked, not who is allowed to contribute. -# The control loop is owned because that is the product. Process files are -# owned so agent rules cannot be rewritten in passing. - -# Web/UI changes still need a human visual check; see AGENTS.md. -# A GitHub review from the CODEOWNER does not replace looking at the page. -# Do not auto-request @erikarenhill; he is not a CODEOWNER. - -# The household loop: measure, plan, dispatch, and the UI that explains it. -/go/internal/control/ @frahlg -/go/internal/mpc/ @frahlg -/go/internal/telemetry/ @frahlg -/web/ @frahlg - -# Drivers and the Lua host. -/drivers/ @frahlg -/go/internal/drivers/ @frahlg - -# Release, CI and the rules agents work under. -/.github/ @frahlg -/AGENTS.md @frahlg -/CLAUDE.md @frahlg -/APPROVAL_POLICY.md @frahlg -/.cursor/ @frahlg diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a1517419..cf0a209b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -82,5 +82,5 @@ body: attributes: label: Contribution path options: - - label: I will wait for a maintainer to confirm the fix scope before opening a code pull request. + - label: I understand that Sourceful handles implementation and external contributions use issues, not pull requests. required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 0be0844c..7fb3d876 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -10,10 +10,9 @@ body: a thread in [Discussions](../../discussions) first — issues are best for concrete, actionable proposals. - External code contributions start here as text. Describe and discuss - the change before writing code, then wait for a maintainer to accept - one bounded scope. Please do not open an implementation pull request - until that happens. + Sourceful develops FTW. External users submit issues with needs and + evidence; we do not accept external pull requests, including docs and + drivers. You do not need to design an API or write code to make a request. - type: textarea id: problem attributes: @@ -24,10 +23,8 @@ body: - type: textarea id: proposal attributes: - label: Proposed solution - description: How would you like this to work? Sketch the API / UX / config. - validations: - required: true + label: Desired result + description: Describe the result you want. A technical design is optional. - type: textarea id: alternatives attributes: @@ -39,15 +36,11 @@ body: label: Evidence and source material description: Link real hardware or protocol documents, tariff rules, screenshots, logs, user reports, or other evidence that defines the need. placeholder: State what the source proves, its version or effective date, and any limits. - validations: - required: true - type: textarea id: boundaries attributes: label: Boundaries, failure behavior and proof description: What stays out of scope? How should failure stay safe? What test, run, HIL result, or rendered view would prove the change? - validations: - required: true - type: dropdown id: area attributes: @@ -76,5 +69,5 @@ body: attributes: label: Contribution path options: - - label: I will wait for a maintainer to accept the written scope before opening an implementation pull request. + - label: I understand that Sourceful handles implementation and external contributions use issues, not pull requests. required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0b1566b1..add2211d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,36 +1,26 @@ -## Accepted text proposal - -Issue or Discussion: - -Maintainer comment that accepted this scope: - -## What changed - - - -## Why +## Problem and result - + -## Boundaries and safety +## Scope and safety - + ## Verification - + ## Checklist -- [ ] The diff implements one accepted scope and does not add follow-on work. -- [ ] I checked open pull requests that touch the same files. -- [ ] Tests cover the changed behaviour and its failure path. -- [ ] A human reviewed every changed web/UI view in a browser, or no UI changed. +- [ ] The change follows VISION.md and one selected scope. +- [ ] I checked overlapping PRs and coordinated shared files/contracts. +- [ ] Relevant checks cover the changed behaviour and failure paths. +- [ ] A human reviewed changed UI in a browser, or no UI changed. - [ ] A Changeset is included, or the change is exempt. - [ ] Every commit has a DCO sign-off. diff --git a/AGENTS.md b/AGENTS.md index 5ff15fb0..d1d51030 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,33 +1,36 @@ # FTW project guide -FTW is a box in the house that measures, plans and dispatches battery, PV and -charging so the site stays safe and intelligible when the cloud, the optimizer -or a driver is gone. - -That loop is the product. Everything else is an add-on. Work belongs in core -only when it makes that loop better this week, for a household, without a new -tariff model, a new protocol or a second safety authority. +FTW makes mixed home energy equipment work together through local planning, +safe control and clear feedback. Its default experience must serve a novice +and earn an expert's trust. Read [VISION.md](VISION.md) for the product +requirements and [docs/roadmap.md](docs/roadmap.md) for acceptance evidence. +These are direction, not claims that all planned behaviour has shipped. + +Fredrik owns FTW's direction. Sourceful develops and maintains it. External +users submit issues; we do not accept external pull requests, including docs +and drivers. See [CONTRIBUTING.md](CONTRIBUTING.md). + +## Product rules + +- Solve a concrete household need with the least total complexity. Require a + reason for every new setting, service or framework. Keep expert access and + Lua customization useful while reducing routine setup and decisions. +- Preserve working behaviour when simplifying. Removing a setting includes + handling its stored state; hidden configuration must not keep steering a site. +- Make request, acceptance, command, device response and measured effect + distinct. Show freshness and failures in the normal experience. +- Treat useful cold start, simple charging, notifications, recovery and + analysis by agents as parts of the product, not optional polish. +- Default battery wear cost is zero. Include charge/discharge efficiency. + Keep min/max SoC limits separate from forecast-based caution. +- External automation and agents express goals, schedules and proposed plans. + Core validates and dispatches. Temporary control expires; durable user + goals persist. Local operation survives loss of the caller or cloud. +- Decide scope exclusions with the owner as needs arise. Do not invent a + standing blacklist, a one-week value test or a ban on necessary maintenance. The implementation is Go, with Lua drivers and a compiled Energyplan worker. -## Not this project - -These are standing no's, not a backlog. A later theme can reopen one of them; -until then they wait, and they do not queue against master. - -- Not a GIS or roof-drawing product. Irradiance that improves the plan is - core; a map of the house is not. -- Not a CPO. A local OCPP server that core commands is in; forwarding sessions - to an upstream operator waits. -- Not a Modbus server for other home-automation stacks. -- Not a worldwide tariff engine. Named household tariffs, after a written - contract, are enough. -- Not Matter, EnergyKit, V2X automation or a second commander on the site - until the household loop is boringly correct. -- A simulator is a test harness, not a second UI. - -The optimizer proposes. It is not the product. - ## Architecture The repository has three explicit modules: @@ -45,9 +48,10 @@ The repository has three explicit modules: [`go/internal/mpc`](go/internal/mpc). It proposes plans; core validates them and retains a Go fallback. -If work is not the household loop, it belongs outside core, and only with a -narrow versioned contract, independent failure and update semantics, and a -safe unavailable state. Optional modules never bypass core safety. +Keep responsibilities cohesive and minimize the complexity of the whole +product. A separate module needs a concrete benefit, a narrow versioned +contract, independent failure and update semantics, and a safe unavailable +state. Optional modules never bypass Core safety. Read [docs/architecture.md](docs/architecture.md) for the system map and [docs/site-convention.md](docs/site-convention.md) before changing power math. @@ -103,34 +107,30 @@ landed on somebody else's finished work. - **Check for open PRs touching the files you are about to change.** If one exists, it has right of way: rebase onto it, or say so and pick different work. Do not rewrite files out from under an open branch. -- **An open PR is a lock with an expiry.** Right of way is a reservation you - use. A PR whose base branch is already merged must be retargeted at - `master` or closed. Parking means the unique commits move, then the PR - closes. A branch that is not moving toward `master` is an abandoned - reservation, not a review queue. +- **Coordinate stalled or overlapping work.** Identify a merged base or a + blocked dependency and propose the next step. Age alone does not authorize + closing a PR or moving another person's commits. Follow the owner's scope, + preserve unique work and coordinate with the active author. - **"This already exists" is a claim that needs evidence of the right kind.** For behaviour, that means a test or a run. For anything visual, it means rendering both versions and comparing them — counting tokens, selectors or symbols in the source does not establish that a UI change landed. -- **One live theme, small PRs inside it.** The default theme is the control - loop: measure, plan, dispatch. The repository owner may name a different - theme when opening that window. Merging often only works when changes do - not overlap. A large cross-cutting pass, or a green PR outside the theme, - waits; it does not queue against `master`. -- **Respect `.github/CODEOWNERS`.** An owner reviews what lands in their - area, whoever — or whatever — wrote it. Control, planning, telemetry and - the UI are owned because that is the product, not because they are - process files. -- **Do not request GitHub reviews except CODEOWNERS.** Never request - @erikarenhill and never @mention people for attention. The GitHub - Discord bot turns both into pings. See - [`APPROVAL_POLICY.md`](APPROVAL_POLICY.md). +- **Complete one coherent change.** Follow the owner's selected outcome. + Keep PRs small, pair cross-repo changes and avoid unrelated additions. + Necessary bug fixes, security, recovery and maintenance still proceed. +- **Ownership is project-wide.** Fredrik sets direction and authority. + Reviews supply evidence and independent judgment. We do not use path-based + ownership in Core as a substitute for that responsibility. +- **Do not request reviewers or ping people automatically.** Follow an + explicit owner assignment; do not infer reviewers from commit history. + See [APPROVAL_POLICY.md](APPROVAL_POLICY.md). - **Review web/UI changes in a browser.** A human must inspect the rendered interface; AI review, a GitHub approve and reading the source code are not enough. -Planning documents, design specs, task breakdowns and agent scratch notes -stay out of the repository; [`.github/check-no-planning-docs.sh`](.github/check-no-planning-docs.sh) enforces +Keep VISION.md and the roadmap current as the maintained product direction. +Task plans, design drafts, breakdowns and agent scratch notes stay out of the +repository; [`.github/check-no-planning-docs.sh`](.github/check-no-planning-docs.sh) enforces this. Commit the change, its tests and a changeset; put the reasoning in the PR description, where it is read during review and then archived. @@ -177,14 +177,14 @@ The repository owner cuts every release, unless they have explicitly handed that duty to someone. Cutting a beta needs green CI and nothing else: the owner may merge ahead of a pending review to keep pace, because in this project review happens on the beta as much as in the -PR. Fast-tracking moves a review, it never removes it — the CODEOWNERS -owner still reviews what landed in their area, on the running beta. +PR. Fast-tracking defers the review; the owner remains responsible for +ensuring that it happens on the running beta. -Beta is the playground for what landed in the live theme. Every merged -change ships in the next beta; testers run it on real sites and file what -they find as issues or PRs, each naming the beta it was seen on. Label a -finding `release-blocker` when the line must not promote to stable until it -is fixed. Green CI is not a reason to merge work outside the theme. +Every merged change ships in the next beta. Testers run it on real sites and +report findings in issues, naming the beta. Sourceful maintains the fixes and +implementation PRs. Label a finding `release-blocker` when the line must not +promote to stable until it is fixed. Green CI alone does not set product +priority. There is no release calendar. A beta promotes once it has run on the validation sites for a few days with no open `release-blocker`, and @@ -258,7 +258,7 @@ run with cwd `/agent`, so `cd` into the repo you mean before `make`. | [`srcfl/device-drivers`](https://github.com/srcfl/device-drivers) | Editable driver source. Change Lua there, never in `ftw/drivers/*.lua`. | | [`srcfl/device-simulator`](https://github.com/srcfl/device-simulator) | Simulated site FTW talks to over real protocols. | | [`srcfl/ftw-webapp`](https://github.com/srcfl/ftw-webapp) | Installable client. | -| [`srcfl/ftwdb`](https://github.com/srcfl/ftwdb) | Embedded energy database. | +| [`srcfl/ftwdb`](https://github.com/srcfl/ftwdb) | Archived storage experiment; new storage work belongs in Core. | `make e2e` and `make dev` still use the in-tree Go simulators (`sim-ferroamp`, `sim-sungrow`). For driver, protocol or site behaviour, diff --git a/APPROVAL_POLICY.md b/APPROVAL_POLICY.md index 9bdadb45..2823725c 100644 --- a/APPROVAL_POLICY.md +++ b/APPROVAL_POLICY.md @@ -1,38 +1,26 @@ -# Review routing +# Review and authority -Cursor's PR Routing agent has been requesting reviewers from commit -history. That pings @erikarenhill on Discord several times a day for -agent PRs he does not need to see, and it keeps doing so whether or -not he responds. +Fredrik owns FTW. Sourceful develops the product. Work follows the scope and +authority the owner gives; file-based reviewer lists do not assign ownership. +See [VISION.md](VISION.md) and [AGENTS.md](AGENTS.md). -This file overrides that reviewer-selection guidance. See -[PR Routing & Approval](https://cursor.com/docs/approval-agents). +## Review routing -## Who to request +Do not automatically request GitHub reviewers or mention people for attention. +Do not select reviewers from git blame, commit history or past participation. +The owner can assign a reviewer when an independent assessment is useful. +Follow an explicit assignment without adding further approval steps. -Request a GitHub review only from people listed in `.github/CODEOWNERS` -for a path this PR actually changes. +This file replaces reviewer-routing suggestions from tools, including +Cursor's PR Routing agent. Do not invent a review request because no reviewer +is assigned, or re-request someone on each push. -Do not pick reviewers from git blame, commit history, or "who else -collaborates here." +## Evidence and handoff -Never request @erikarenhill. He is not a CODEOWNER. A human who -actually wants his eyes can still request him by hand. +Review the actual change, relevant tests and unresolved risks. Web/UI changes +still require a human to inspect the rendered view; a GitHub approval is not +that check. Preserve the owner's release and beta-review rules in AGENTS.md. -Do not @mention people in PR bodies or comments to get their -attention. The GitHub Discord bot turns those into pings too. - -## When not to request anyone - -If no CODEOWNERS path matches, request nobody. - -A GitHub review from the web CODEOWNER is not the visual check. Web/UI -changes still need a human looking at the rendered page (see AGENTS.md). - -Do not re-request a reviewer on later pushes of the same PR. Once is -enough. - -## Approvals - -This file does not authorise auto-approving PRs. CODEOWNERS review -and the web visual check stay as they are. +This policy grants no extra authority to approve, merge, release or operate a +site. It also adds no confirmation requirement when the owner has already +authorized the action. External users submit issues; Sourceful maintains PRs. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f08a903..0e05374e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,22 @@ # Contributing to FTW -Thanks for helping build the local energy coordination layer. This document -covers the legal bits — for how the code is organized and how to add a driver, -start with [`AGENTS.md`](AGENTS.md) and -[`docs/writing-a-driver.md`](docs/writing-a-driver.md). +Sourceful develops FTW and Fredrik owns its direction. External users help +through [issues](https://github.com/srcfl/ftw/issues): report bugs, describe +needs and share evidence. We do not accept external pull requests, including +documentation, driver and website changes. Acceptance of an issue does not +invite an external implementation PR. + +The [product vision](VISION.md) guides the work Sourceful selects. Users may +still run, inspect and adapt FTW under its existing license. This policy does +not change the license or past contributions. The development and sign-off +instructions below apply to Sourceful-maintained changes and local work. ## Website -The public website () lives in its own +The public website () lives in its own repository, [`srcfl/ftw-web`](https://github.com/srcfl/ftw-web). Landing-page -copy, install instructions and other site content are edited there, not in this -repo — open a pull request against `srcfl/ftw-web` for website changes. +copy, install instructions and other site content live there. Report website +changes in [its issue tracker](https://github.com/srcfl/ftw-web/issues). ## License of contributions @@ -76,37 +82,25 @@ By making a contribution to this project, I certify that: this project or the open source license(s) involved. ``` -## Start with text - -External code contributions start with a written proposal, not an -implementation pull request. Open an issue or Discussion before writing code -and describe: - -- the problem and current behaviour; -- the result you want and what stays out of scope; -- real hardware, tariff, protocol or user evidence where it applies; -- safety and failure behaviour; -- the test, run or rendered view that would prove the change; -- related issues and open pull requests that touch the same area. - -Wait for a maintainer to accept the scope in that thread. An accepted proposal -covers one focused change; it does not approve a stack of follow-on pull -requests. Once the scope is agreed, link the thread from the implementation -pull request and keep the code within it. - -This review protects contributor time and keeps new work aligned with current -architecture and work already in progress. We may close an unsolicited code -pull request and invite the author to bring the proposal back as text first. -Documentation-only corrections may go straight to a pull request, but the pull -request must still explain the change and why it is correct. - -## Pull requests - -- Link the written proposal and the maintainer comment that accepted its scope. -- Keep PRs focused on one logical change. -- New code needs tests; `make verify` must pass before review. -- User-visible changes need a Changeset entry (`npx changeset`) — see the - Releases section in [`README.md`](README.md). +## Report a need or bug + +Describe the result you expected, what happened and the version and equipment +involved. Add relevant logs, screenshots or protocol documents when available. +Do not post credentials or private site data. You do not need to design an API +or write a patch to report a problem. + +Sourceful decides scope and priority and links implementation PRs to the issue. +External PRs are not the intake path; maintainers may close them and direct the +author to issues. General questions can still use Discussions or Discord. + +## Sourceful pull requests + +- State the household need, selected scope and relevant issue or owner request. +- Keep the change focused and coordinate overlap with open work. +- Test changed behaviour and failure paths; run the relevant repository checks. +- Review changed UI in a browser. +- Add a Changeset for user-visible runtime changes; documentation is exempt. +- Keep VISION.md, the roadmap and current-behaviour docs consistent. ## Betas, issues and releases diff --git a/README.md b/README.md index 634193f7..cf9f1747 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,22 @@ The control path stays on the local network. Cloud price, weather and device integrations degrade independently; they are not required for safe local operation. -FTW Community is Apache-2.0 software maintained by Sourceful Energy and project -contributors. Community help is best effort. See [SUPPORT.md](SUPPORT.md) for -the boundary between community use and separate commercial services. +## Product direction + +FTW should make mixed equipment simple to live with: useful first-day +planning, reliable daily charging, fast and honest live feedback, and +structured access for agents. The default experience should need few choices +while keeping expert controls and Lua drivers available. + +[VISION.md](VISION.md) is the product direction set by Fredrik. +[docs/roadmap.md](docs/roadmap.md) lists the outcomes and proof needed. These +include goals that have not shipped; the capability list below is separate. + +Sourceful Energy maintains FTW Community under Apache-2.0. External users +report bugs, needs and evidence through [issues](https://github.com/srcfl/ftw/issues). +We do not accept external pull requests, including documentation and drivers. +See [CONTRIBUTING.md](CONTRIBUTING.md). Community help is best effort; +[SUPPORT.md](SUPPORT.md) describes separate commercial services. ## Architecture @@ -27,9 +40,9 @@ FTW has three explicit modules: - **Optimizer** proposes plans over a versioned contract; core validates every result and keeps a Go fallback. -This separation lets drivers and the optimizer evolve independently without -moving safety authority out of core. New module types should follow the same -rule. See [docs/architecture.md](docs/architecture.md). +Drivers and the optimizer can evolve without moving safety authority out of +Core. A new module needs a concrete reason and must reduce the complexity of +the whole product. See [docs/architecture.md](docs/architecture.md). ## Capabilities @@ -96,9 +109,10 @@ Existing Forty Two Watts or older FTW deployments must use the are preserved. Raspberry Pi image installation is covered by [docs/rpi-image.md](docs/rpi-image.md). -The dashboard is intentionally local. Use a VPN or another operator-managed -private network when access is needed away from home; FTW does not ship a -public relay. +The on-box dashboard remains local. The optional +[FTW webapp](https://github.com/srcfl/ftw-webapp) connects through an encrypted +session and blind relay; relay loss does not stop local control. Cloud MCP +access is a product goal, not an endpoint provided by this installation guide. ## Install on Home Assistant diff --git a/SUPPORT.md b/SUPPORT.md index dd48dffa..1bb3f6f5 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,7 +1,7 @@ # FTW support FTW Community is a self-hosted, Apache-2.0-licensed project maintained by -Sourceful Energy (Sourceful Labs AB) and project contributors. +Sourceful Energy (Sourceful Labs AB). Fredrik owns the product direction. ## Community support @@ -17,7 +17,9 @@ Use the public community channels for help: searchable questions, ideas, and open-ended technical conversation; - report reproducible bugs and request features in [GitHub Issues](https://github.com/srcfl/ftw/issues); -- propose fixes, drivers, tests, and documentation through pull requests. +- describe proposed fixes, driver needs, test evidence and documentation + corrections in issues. Sourceful implements selected changes; external pull + requests are not accepted. See [CONTRIBUTING.md](CONTRIBUTING.md). Maintainers and community members may help on a best-effort basis. A question may receive no response, and a fix may not arrive on a particular schedule. diff --git a/VISION.md b/VISION.md new file mode 100644 index 00000000..f736b473 --- /dev/null +++ b/VISION.md @@ -0,0 +1,211 @@ +# FTW product vision + +FTW makes a home's energy equipment work together. It runs locally, plans +ahead, controls against current measurements and shows what actually happened. +Its default experience must be simple enough for a first-time user and good +enough to earn the trust of someone who builds their own energy system. + +This is the product direction set by Fredrik. It guides design and review; +it does not claim that every outcome below has shipped. The +[roadmap](docs/roadmap.md) names the work and evidence needed to reach it. +The [architecture](docs/architecture.md) describes the running system. + +## One system for mixed equipment + +Mixed makes and generations are a main use case: for example, an older +SolarEdge inverter, a Pixii battery and a separate charger. Support must state +which models, firmware, measurements and commands have been verified. A +catalog entry alone is not proof that a device can be controlled. + +FTW reasons from site physics: energy balance, storage capacity, conversion +losses, equipment response and physical limits. Models and state estimation +help it combine readings that arrive at different times and rates. They must +retain the difference between a measurement, an estimate and missing data. +Algorithms serve that result; their names are not a product promise. + +The normal mode gives FTW authority to plan and control within the user's +goals and the site's limits. It does not ask for approval on each action. +Core validates every plan and command, including plans supplied by another +system. Fuse, equipment, SoC, freshness and other quantified safety limits +always apply. Stale required site-meter data stops dispatch. + +## Trust through visible behaviour + +The live view is a core product feature. It must feel local and fast, and +make these separate facts easy to follow: + +- what the user, agent or planner requested; +- what Core accepted and why it changed or rejected a request; +- the command sent to a device and its response; +- the measured physical effect, its age and any delay or difference. + +A sent command is not proof of a changed power flow. Smooth rendering must +not make old readings look live. The normal Flow experience should carry this +clarity; the user should not need a separate diagnostic tool to trust it. +Whether the existing Live and Flow views become one view is a design decision +to validate in the browser, not a requirement to keep two interfaces. + +Show a failed integration and its effect in plain language. If battery +control is not working, planning and dispatch must stop relying on that +battery. Valid read-only telemetry may remain useful. Other devices may keep +working when the required measurements and safety conditions still hold. +Recovery must establish that control works again before relying on it. + +## Useful from the first day + +Discovery should obtain what the equipment can report. Ask only for what +FTW cannot establish reliably. The user must confirm the main fuse limit and +FTW needs a working site meter before active control. + +Ask for battery energy capacity in kWh when it cannot be read. Battery and +inverter power ratings should not normally be required form fields: obtain +verified device limits where available and learn the usable response within +safe bounds. Advanced users may set limits explicitly. Do not treat an +observed power level as proof of an absolute hardware or installation limit. + +For solar, the target is that "I have solar" is enough to start learning. +Installed kWp is an optional starting estimate. Approximate user input must +not permanently constrain a model when measurements support a better fit. +Panel drawings, orientations and engineering knowledge are not prerequisites. + +Initial load and solar models must already support useful first-day planning. +On-site learning improves them as evidence arrives. Elapsed days alone do +not prove model quality; state uncertainty honestly and handle cold start. + +Commissioning should include a short, controlled check of commands and +physical response, within known limits and with fresh site measurements. +Give the user a clear result showing what worked, what failed and what remains +unverified. A quick response check is not full-range hardware qualification. + +## Defaults and user preferences + +The default planner uses energy prices and charge/discharge efficiency. +**Battery wear cost defaults to zero.** This is a deliberate product choice. +Users may add a wear cost in settings; do not silently introduce one through +another penalty or hidden preference. + +Keep three concepts separate: + +- **Battery operating limits:** user-configured min/max SoC and equipment + limits remain binding. +- **Forecast caution:** extra reserve in a grid-connected home reflects + expected future demand, supply and uncertainty, and how much the user wants + to trust the forecast. It is not another fixed SoC floor. +- **Explicit backup needs:** an off-grid or backup use case may need a stated + reserve floor. This policy does not by itself establish off-grid support. + +Explain forecast caution through its effect on the plan. Users should be able +to understand why FTW keeps energy for later without choosing model internals. +Keep useful expert controls available, but require a clear need for each +setting in the normal experience. + +## Charging that fits daily life + +Plugging in should normally be enough. A persistent schedule can say +"80% by 07:00 every weekday". FTW plans toward that need and reports when it +cannot meet it. Cloud vehicle access must not be a prerequisite for charging. + +An offline car is a supported product use case. When its SoC is unknown, use +a stated default or estimate and distinguish it from a confirmed reading. +After plugging in, opening the webapp should expose the car's SoC slider +directly. Changing it needs no extra Save action; show the resulting plan +as soon as Core accepts the change and replans. Keep pending and rejected +changes visible. + +"Charge now" takes one action. The rest of the site adapts within its limits. +The user should not have to reconfigure battery policy to charge the car. + +Notifications are part of the charging product. Tell the user promptly when +charging fails or the goal is at risk, including when the app is closed. +Explain the problem and take the user to the relevant action. An estimated +SoC is not proof that the requested percentage was reached. + +## Comfort and heat + +The energy system serves household comfort. The first heat-pump phase reads +data to improve load forecasts and planning; it leaves comfort control with +the heat pump. Batteries and other flexible assets adapt around those needs. + +Later, support bounded heat storage where the installation makes it useful: +for example, heating a buffer tank or hot water during cheap periods. That +requires known storage and temperature bounds, a safe control path and a +clear user goal. Do not make a detailed thermal model a setup requirement for +every home, or turn data-only support into active heat control by implication. + +## External automation and agents + +FTW works on its own and also serves as a local core for Home Assistant, +MQTT automation and agents. They can change goals, modes and charging +schedules, and propose plans. They use Core's admission and safety checks. + +Temporary external control has a watchdog limit. It must be renewed; on +expiry FTW returns to its defined local automatic behaviour. Show who is +currently directing the action and why. A durable schedule or user goal +remains after its caller disconnects; it is not a control lease. + +Agent first means a supported way to use FTW, not just agent-friendly source +code. An authorized agent needs structured access to measurements, history, +forecasts, plans, decisions, command results and actual outcomes for analysis. +It must be able to change schedules and goals, submit a proposed plan and +verify the result without operating the UI. Report freshness, provenance, +rejection reasons and the distinction between acceptance and physical effect. + +Local access and cloud MCP access are part of the direction. Reuse the +webapp's encrypted session and relay where they fit; verify the fit before +adding another transport. Access requires the user's authorization and must +be revocable. The relay and escrow stay unable to read site data. An +authorized cloud agent is an endpoint that can read the data the user grants +it; do not describe that access as blind. The box keeps operating when the +agent, MCP service or network is unavailable. + +This document adds no new protocol operation, credential or access grant. +New shared names belong in the contract registry and must ship with their +validation and matching clients. + +## Show the value of FTW honestly + +The intended main savings comparison is the same site with the same solar, +battery, efficiency, limits, tariff and household needs under ordinary +self-consumption control. It should estimate the benefit of FTW's coordination. +The current comparison against no solar and no battery describes broader site +value; it must not be presented as the incremental value of FTW. + +Show actual grid cost and export revenue separately from the modelled +comparison. A fair comparison needs stated charging behaviour, comparable +starting stored energy, an account of ending stored energy and sufficient +measurement and price coverage. Show negative results and missing evidence. +Neither a forecast nor a replay is a measured alternative electricity bill. + +## Keep the whole product simple + +Installation, settings, local and remote UI, diagnostics, updates and recovery +are part of the product. Prefer fewer features that work together and fewer +decisions required from the user. Preserve useful expert access and flexible +Lua drivers. Removing a setting also requires handling its persisted state; +hiding it must not leave an old policy active without explanation. + +Choose the design with the least total complexity. A smaller Core that needs +more services, contracts and deployment steps may make the product harder. +Require a concrete reason for a new module or general framework. + +Scope exclusions will be decided as needs arise. There is no standing list +of banned protocols or features. A roadmap entry, green CI or available agent +capacity does not by itself establish priority. Fredrik sets the work; finish +coherent user outcomes and keep necessary maintenance moving. + +## Ownership and contributions + +Fredrik owns FTW's direction. Sourceful develops and maintains the product. +Agents work within the scope and authority given to them. Reviews should +provide an independent assessment and evidence; file-based reviewer lists +do not define product ownership. + +External users contribute through issues: bugs, needs, hardware evidence and +suggestions. FTW does not accept external pull requests, including drivers, +documentation and website changes. Sourceful maintains implementation PRs. +Users may still inspect, run and adapt the software under its existing license. +This contribution policy changes no license or copyright attribution. + +The operating rules live in [AGENTS.md](AGENTS.md), review routing in +[APPROVAL_POLICY.md](APPROVAL_POLICY.md), and contribution details in +[CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/docs/architecture.md b/docs/architecture.md index 743d8df4..b88c5a76 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,8 +6,10 @@ safety boundary. Drivers translate hardware protocols. The optimizer proposes plans and supplies primary forecasts. A failure or upgrade outside core must never stop local measurement or make dispatch unsafe. -The product boundary — what that system is, and is not — lives in -[`AGENTS.md`](../AGENTS.md). This document is the system map. +[VISION.md](../VISION.md) defines the product direction and +[the roadmap](roadmap.md) defines acceptance evidence. This document describes +the running system. Product requirements below guide future work and do not +by themselves add runtime behaviour or new protocol capabilities. ## Module boundaries @@ -22,13 +24,37 @@ driver, but one failed driver is isolated from the others. Optional integrations such as Home Assistant, notifications and Nova attach at core's API, state or telemetry boundaries; they do not own dispatch safety. -A future module belongs outside core only when it has: +Choose the design with the least total complexity. Moving code out of Core +is useful only when it improves the product's operation or maintenance. +A separate module requires a concrete benefit and: - a small, explicit and versioned contract; - independent failure and update semantics; - no authority to bypass core's validation or safety limits; - a useful fallback or a cleanly unavailable state. +## Product requirements across these boundaries + +Discovery, first-day models and controlled commissioning should establish +useful operation without extensive configuration. Keep verified limits, +learned capabilities and user estimates distinct. A failed control integration +must not remain available to planning merely because it still emits telemetry. + +The UI and agent clients need a trace from requested intent through acceptance, +dispatch, device result and measured effect, including age and uncertainty. +Reading a value and controlling its source are separate capabilities. + +Agents and external automation follow the same Core authority as the planner. +The target includes durable schedule changes and submitted plans, alongside +renewable temporary control. Expired temporary control returns to local +operation; disconnecting a client does not delete a stored household goal. + +Cloud MCP should reuse the encrypted client/session path where it fits. The +relay and escrow remain blind. An authorized agent endpoint can read only +what its grant allows, and its access must be revocable. Protocol extensions +require registry changes and paired implementation tests; this section does +not introduce wire names or bypass existing admission rules. + ## Power convention Above the driver boundary, positive power flows into the site and negative diff --git a/docs/roadmap.md b/docs/roadmap.md index ce5b1812..a8c0a5b9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,241 +1,76 @@ # FTW roadmap -This roadmap is a delivery boundary, not a feature inventory. **NOW** contains -work already in implementation or acceptance. **NEXT** has a defined contract -and entry gates. **LATER** items have no delivery promise; each must satisfy its -promotion gate before it can move forward. - -Operating law for what may land is [`AGENTS.md`](../AGENTS.md): the household -loop is the product, there is one live theme, and a LATER row is not -permission to open a PR against `master`. - -The permanent rules do not move between lanes: core owns safety and dispatch, -the site sign convention is unchanged, and local operation never depends on an -optional service. Planner output remains untrusted input. A failed or stale -driver still receives its autonomous default. Home Assistant stays optional. - -Feature work after NOW is ordered by return: the first track is the one that -changes what a household can do this week without a new tariff model, a new -protocol, or a second safety authority. - -Status notes dated 2026-09-02 record what is implemented on `master` on that -date, checked against the code and its tests. They are evidence, not promises: -a track is done only when its exit evidence exists. - -## NOW — close the P0 control and product loop - -NOW is complete only when these four tracks are implemented, tested together, -and understandable from the local UI: - -| Track | P0 outcome | Exit evidence | Status 2026-09-02 | -|---|---|---|---| -| Access boundary | One admission policy covers state-changing requests during setup, boot, normal API operation and local development. Trusted local access remains recoverable; non-local mutation fails closed. The separate site-controller identity remains read-only. | Positive and negative tests cover every lifecycle phase, origin/host handling, credential enforcement and local recovery. | Largely implemented ([#602](https://github.com/srcfl/ftw/pull/602) through [#995](https://github.com/srcfl/ftw/pull/995)): one `Authenticate` gate wraps setup, boot and the live mux; non-local mutation fails closed; loopback recovery works. Missing evidence only: the boot-phase listener has no admission test, the dev proxy's read-only default is untested, and no negative test shows the site-controller identity cannot admit a mutation. | -| Energy ledger and history | One durable ledger records import and export separately, with interval, source and quality/freshness attached. Daily and settlement-period views are derived from that record across hot, warm and cold history; control never offsets earlier import with later export. | Tier-boundary, restart, rolloff and reconciliation tests agree with the site sign convention and retain provenance. | Partial. The ledger itself is durable, directional and provenance-keyed, with tier-boundary and sign-convention tests. But the daily and settlement views still derive from the legacy history tables with no reconciliation test between the two accountings, the ledger has no cold tier (rows are deleted at two years, not archived), and the session receipt does not exist — no session table, no price column. | -| Battery-to-EV lease | Battery support for EV charging is an explicit local lease with a bounded site/loadpoint scope, start, expiry and observable state. The local UI treats a lease as a session: remaining energy, floor SoC, and a stop that the operator can see. Expiry, stale required telemetry or loss of the controlled session releases it; all normal SoC, fuse, power and slew limits still apply. | Tests cover grant, replacement, expiry, restart policy, stale-data release, optimizer interpretation and local operator priority. The UI shows remaining lease energy and the floor without a diagnostic page. | Partial. The control-side lease is complete and tested: bounded duration, sixteen observable stop reasons, stale-data release, restart preflight, planner wiring ([#871](https://github.com/srcfl/ftw/pull/871), [#970](https://github.com/srcfl/ftw/pull/970)). The lease has no energy budget — remaining time only — and its only UI sits behind the Advanced toggle, so both named exit lines are unmet. | -| Mobile and optimizer UX | The local experience works at narrow widths and explains current action, next planned action, fallback state, freshness and active battery-to-EV lease without requiring diagnostic knowledge. | Viewport checks and UI tests cover normal planning, optimizer unavailable/invalid, stale telemetry and lease expiry. | Partial. The explanation half shipped: plan brief, fallback and pause reasons in plain words, and the Ask why conversation ([#1004](https://github.com/srcfl/ftw/pull/1004), [#1010](https://github.com/srcfl/ftw/pull/1010), [#1037](https://github.com/srcfl/ftw/pull/1037)–[#1046](https://github.com/srcfl/ftw/pull/1046)). Narrow-width verification is still source-text assertions — no DOM-driven viewport tests, and the one headless 390 px smoke never runs in GitHub CI. The lease is absent from the normal mobile surface. | - -The ledger track is also the session receipt. A finished ledger must be able -to answer how much energy a charge session used, from which source, and at -what recorded price when a tariff is present. Missing price hides the money -column. It does not invent a saving. - -The active access-boundary and read-only site-controller work are inputs to -this lane, not parallel remote products. Their contracts must converge on one -rule: identity can establish who or what is speaking, but only core can admit a -mutation and validate its effect. - -## NEXT — household charging policy - -Entry gate: NOW is complete. These tracks run in the listed order. A later -track may start only when the earlier track has exit evidence, or when it -does not share control or UI state with an unfinished predecessor. - -Each track is a core policy or an optimizer-contract field. None of them -puts Home Assistant, a cloud optimizer, or a vehicle OEM API on the control -path. Drivers remain the only hardware dialect. The optimizer may propose; -core still validates and dispatches. - -| Order | Track | Outcome | Exit evidence | Status 2026-09-02 | -|---|---|---|---|---| -| 1 | Charge modes | A loadpoint has four named modes the operator can lock: Surplus, Min+surplus, Fast, Off. Surplus starts only when measured site surplus covers the charger minimum for the active phase count, with enable and disable thresholds in watts and minutes. Min+surplus holds the charger minimum and adds surplus on top. Fast may buy grid. Stale site-meter data turns Surplus into Off. It does not hold the last surplus. | Tests cover start/stop hysteresis, the 1-phase and 3-phase minima, stale-meter fail-closed, and mode lock vs optimizer suggestion. The UI names the modes without diagnostic copy. | Partial. Surplus hysteresis, the 1p/3p minima with a sticky day lock, and stale-meter fail-closed all exist ([#590](https://github.com/srcfl/ftw/pull/590)). The four named lockable modes do not: policy today is a surplus-only switch plus manual hold and schedule; Min+surplus is absent and the thresholds are hard-coded, not operator watts-and-minutes. | -| 2 | House reserve and discharge lock | Two SoC bands are first-class: the house reserve (surplus charges the battery first) and the car buffer (the EV may drain only above that band). When Fast or a grid-buy plan is active, core holds the battery so night energy hits the car instead of emptying the house. A lease from NOW may override the bands for one session. The optimizer consumes the bands. It does not invent them. | Tests cover reserve hold, buffer discharge, lock during grid-buy, lease override, and optimizer-unavailable fallback that still honours the reserve. | Partial. One configured floor (`soc_min`), the lease reserve and the battery-may-not-feed-EV clamp are enforced in dispatch and mirrored to the planner. The two first-class named bands are not modelled; the optimizer sees only the floor. | -| 3 | Freeze and hold | Core can command freeze-charge, freeze-export and hold as named intents. A driver that declares the capability executes the vendor hold. A driver that cannot freeze degrades to a quantified 0 W charge or discharge clamp, never a pretend hold. Idle 0 W is not freeze. | Driver capability tests for at least one hybrid that implements hold and one that degrades. Restart and stale-driver paths return to autonomous default, not a stuck freeze. | Not started, except the degrade half: idle mode holds every battery at a quantified 0 W and bounded manual holds auto-expire ([#817](https://github.com/srcfl/ftw/pull/817)). No freeze intents, no driver hold capability. | -| 4 | Fuse tree and phase scaling | Site limits are a tree: a child circuit has a parent, optional meter, and max current and/or max power. Before pausing a charge, core scales 3-phase to 1-phase when the charger can switch and the child still has headroom. New and live sessions share the tree; leftover-headroom-only is not enough. A stale circuit meter is over-limit, not a sum of children. | Tests cover nested circuits, metered vs summed children, 1p/3p before pause, live rebalance, and stale-meter fail-closed. The UI shows the tree and the active clip. | Partial. The single-fuse guard is mature — per-phase clamps, latching hysteresis, joint EV allocation — and 3p→1p happens before a surplus pause. No circuit tree exists: no children, parents, per-circuit meters or power caps. | -| 5 | Solar gate fallback | When the optimizer is unavailable, invalid or stale, core still allocates surplus. Below the house reserve, solar goes to the battery. The battery assists an EV only when live surplus clears a gate. This is the Go fallback, not a second planner. | Tests cover optimizer-down, optimizer-invalid, and surplus below/above the gate without emptying the house battery into the car. | Largely implemented, and overtaken by [#1030](https://github.com/srcfl/ftw/pull/1030): the Go DP is now the champion planner and the Python solver a measurement shadow. Solver failure falls back to the DP, a stale plan degrades to live self-consumption, and battery-assists-EV is gated — today on the per-loadpoint `surplus_unlock_bat_soc` threshold, because track 2's house reserve does not exist yet. | -| 6 | Charge as energy and deadline | A loadpoint goal is remaining energy or SoC, a ready-by time, optional weekday mask, and a strategy: cheapest slots or one continuous block. An optional late window moves the last minutes to just before leave. If the vehicle SoC is stale, the goal is kWh, never an invented percent. A session already drawing power pins the first planner slot to the measured watts so a replan does not cancel a human start. | Tests cover deadline, weekday mask, cheapest vs continuous, late window, stale SoC, and the t=0 pin. The local UI writes the loadpoint goal. | Partial. Ready-by deadline and the weekday mask shipped ([#869](https://github.com/srcfl/ftw/pull/869)); Charge now → target releases back to the plan ([#1007](https://github.com/srcfl/ftw/pull/1007)). The kWh goal, cheapest-vs-continuous strategy, late window, stale-SoC→kWh rule, t=0 pin are absent. | - -Tracks 1–5 are dispatcher policy. They keep working if the optimizer is down. -Track 6 is an intent the optimizer may fill; the Go fallback must still produce -a continuous block when the sidecar is absent. - -## NEXT — forecasts and plan quality - -Entry gate: household charging policy tracks 1–3 have exit evidence, so a -better forecast cannot empty the house into the car. - -| Order | Track | Outcome | Exit evidence | Status 2026-09-02 | -|---|---|---|---|---| -| 7 | Forecast beliefs | Load, PV and price forecasts stored for planning carry who produced them, when they were issued, and an uncertainty band. A six-hour-old PV curve is not treated as a meter. | Replay tests refuse to plan from a forecast missing issuer, issue time or freshness. The UI can say the plan used yesterday's weather. | Partial. Per-slot source and fetch-time provenance is recorded and replayed, and forecast trust persists as the k dial ([#968](https://github.com/srcfl/ftw/pull/968), [#1028](https://github.com/srcfl/ftw/pull/1028)). Provenance stays diagnostic: nothing refuses to plan on a missing issuer or freshness, issue time is explicitly out of scope in the schema, and no UI says which weather the plan used. | -| 8 | PV prior and percentiles | The optimizer request includes a physics or vendor PV prior plus p10/p50/p90 (or equivalent bands) and a bounded on-site residual only after that residual beats the prior on held-out days. Curtailment disables residual learning so the chop is not learned as low yield. | Contract tests for the new request fields. A site with no residual keeps the prior. A curtailed site does not shrink the prior. | Partial. A three-scenario band with learned per-slot relative PV error and a bounded on-site residual shipped ([#1026](https://github.com/srcfl/ftw/pull/1026)). There is no held-out skill gate before the residual is trusted, curtailment does not disable residual learning, and since [#1030](https://github.com/srcfl/ftw/pull/1030) the scenario block reaches only the measurement shadow. | -| 9 | Wear and improvement floor | Cycle cost, end-of-horizon battery value, and a minimum improvement to export or to swap slots are optimizer-contract fields. Displayed savings never include virtual wear. | Fixtures where a 0.1-unit spread no longer thrashes the battery, and the UI savings figure matches the tariff ledger, not the virtual cost. | Partial. End-of-horizon value and the arbitrage deadband are live, and displayed savings exclude virtual wear. `cycle_cost_ore_kwh` is declared on the wire but never populated by core, and there is no separate export-improvement floor. | - -These tracks extend the versioned optimizer handshake with features. They do -not bump the contract version unless the request shape itself changes. They -do not write `config.yaml` from a learned model. - -## NEXT — review the tariff and demand contract - -FTW will review the tariff and demand model in text before C&I implementation -resumes. [Issue #866](https://github.com/srcfl/ftw/issues/866) is the shared -review thread. It starts from named, current utility tariffs and source -documents, not from an implementation branch. - -The review must settle import and export direction, local civil time and DST, -complete and missing intervals, billing-cycle and peak rules, apparent-power -inputs, proven hardware limits, and the boundary between planning and live -control. Its fixtures must cover at least one real tariff and its failure -cases. - -No tariff, demand, optimizer, control or tariff-UI implementation PR starts -until that written contract is accepted. The accepted result will be split -into small issues and one focused PR at a time. - -After that contract, these follow in the same ROI order. They stay behind -the written tariff model because each one prices or clips against it: - -| Track | Outcome | +The [product vision](../VISION.md) sets the direction. This roadmap turns it +into user outcomes and acceptance evidence. It is not a list of shipped +features or permission to start every track at once. + +Fredrik chooses the next bounded change. Check current code, tests, open PRs +and site evidence before claiming a gap or completion. Preserve established +behaviour while simplifying the product. Bug fixes, security, recovery and +necessary maintenance continue alongside product work. + +## Current focus: a complete and trustworthy default experience + +Make discovery, planning, control and feedback fit together for mixed hardware +and for both novice and expert users. Minimize setup and routine decisions. +Complete a user flow across Core and clients when needed, using paired PRs +for shared contracts. + +Every row below is a target. Existing code contains parts of these flows; +the row is complete only when its evidence exists for the version under review. +This replaces older dated status snapshots. It does not reset completed work +or reopen closed issues. + +| Outcome | Product requirement | Acceptance evidence | +|---|---|---| +| Simple setup and first-day value | Discover mixed equipment. Confirm the main fuse and site meter. Read battery capacity where possible and ask for kWh when needed. Power settings and solar kWp are optional where safe device information and learning allow. Provide useful initial load and PV forecasts. | A fresh install reaches useful automatic operation without panel drawings or expert settings. Missing data and wrong start estimates have tested behaviour. Record device identity, known limits and uncertainty. Validate on named hardware combinations as well as simulators. | +| A clear commissioning result | Check commands and measured response within known limits. Distinguish working telemetry from working control. Exclude failed control from both the plan and dispatch. | Show request, device response, measured effect and timing. Cover delayed response, refusal, disconnect, stale site data and recovery. A short commissioning test does not claim full hardware qualification. | +| Live control that earns trust | Keep the fast local feel. Make request, acceptance, command, response, physical effect and freshness visible in the normal Flow experience. | Browser review on desktop and mobile, timing measurements on a target box, and traces with different sampling rates. A pending command or old reading never appears as completed or fresh. Examine existing Live and Flow views before deciding their final layout. | +| Good automatic planning | Use site physics and charge/discharge efficiency. Default wear cost is zero; users may opt in. Keep hard SoC limits separate from forecast-based caution and explicit backup needs. | Cold-start and learned forecasts, stale inputs, multiple assets and unavailable optimizer paths have tests. Backtests use held-out periods and report uncertainty. Defaults and persisted settings agree across UI, Core and worker. Site runs establish practical benefit. | +| Reliable daily charging | Persistent weekday target and deadline. Offline-car estimates. Direct SoC slider after connection, no extra save, prompt replanning, one-action Charge now and notifications when action is needed. | Test from app intent to charger/car response and delivered energy, including missed-goal risk, absent vehicle cloud, unknown SoC, reconnect and restart. Test notifications with the app closed. Confirm physical charging separately from simulation. | +| Useful analysis and fair savings | Keep enough provenance to explain plans and outcomes. Main savings target compares with ordinary self-consumption on the same installation. | Actual cost reconciles with measured import/export and prices. Specify EV behaviour, initial and final stored-energy accounting, efficiency and coverage. Show missing and negative results. Label the current no-PV/no-battery comparison as total site value until replacement is verified. | +| External automation and agent access | Give authorized clients structured analysis data, schedule/goal changes and proposed-plan submission. Temporary external control expires; durable goals persist. Support local access and secure cloud MCP access. | Paired Core/client contract tests cover permissions, expiry, replay, rejection, revocation and reconnect. An agent can trace a request through to measured outcome. Prove local fallback when the caller disappears. Reuse the session/relay where suitable and verify that relay and escrow remain blind. Cloud MCP is a target, not a claim of a shipped endpoint. | +| Less configuration, reliable operation | Every normal setting serves a user need. Keep expert controls discoverable. Installation, updates, backup and recovery remain part of the finished experience. | Audit settings and feature use before removal. Test migration of stored choices so hidden settings cannot keep directing behaviour. Verify restart, upgrade and restore on a target box and review affected UI flows. | + +Safety is part of each row. Core remains the only dispatch authority, every +plan is untrusted input, stale required site-meter data stops dispatch, and +failed devices receive their safe default where reachable. + +## Heat: data first, bounded control later + +First read heat-pump and heating data to improve load forecasts and planning. +Leave comfort control with the heat pump. The battery and other flexible +assets serve the household's needs. + +A later control case is a buffer tank or hot-water store charged during cheap +periods. It needs known temperature and storage bounds, safe defaults, verified +hardware control and a user goal. Do not imply active heat support from a +telemetry-only driver or require every house to supply a thermal model. + +## Client and repository responsibilities + +| Repository | Responsibility in this direction | |---|---| -| Peak and capacity | The planner sees a billing peak or capacity window. Live control still uses the fuse tree. Effective limit is at least the higher of the configured target and the peak already set this cycle. | -| Feed-in and curtail | Economic export pause and physical `max_export_w` are separate constraints. Stale price never curtails on economics. A DSO cap still binds. Drivers that can curtail report percent, not a boolean. | -| External events | A tariff plugin may emit a windowed event (saving session, free power, VPP stand-aside) as `{window, price, stand_aside, load_scale}`. Core only knows that object. Brand names stay in the plugin. | - -## NEXT — the FTW app - -The optional FTW Home Link was built, shipped and then removed whole; see -[ADR 0006](adr/0006-app-uplink.md). The remote lane is now one thing: the FTW -app at `app.ftw.energy`, talking to the box over its own protocol. Pairing or -relay availability must not change local control, setup, history or fallback -planning, and does not. - -### Identity and pairing - -- The box holds three secrets with three lifetimes: a Noise static X25519 key - that never changes, a rotatable 32-byte rendezvous secret, and a single-use - pairing code with a ten-minute life. None of them is in SQLite. -- Trust reaches the app optically. The QR code is a URL fragment carrying the - static public key, the pairing code, a LAN hint and the rendezvous secret. - A fragment is never sent in an HTTP request, so nothing the app trusts ever - passes through a server. -- The box is not a WebAuthn relying party. It authenticates a phone by the - pairing code in the first handshake message and afterwards by the app's - pinned static key. The passkey lives in the app against `app.ftw.energy`, - where it gates enrollment and privileged commands rather than reading. -- The machine identity in [`go/internal/gatewayidentity`](../go/internal/gatewayidentity) - is unchanged and unrelated: hardware-protected P-256 where the hardware - exists, a bound software key otherwise, and the same deterministic - adjective-color-animal display name derived from the stable 18-hex gateway - ID. It identifies the machine; it authorises nothing. -- Multi-site means several independently paired boxes. There is no central - user-to-site directory. A consolidated multi-site view stays gated in LATER. - -### Connection and authorization contract - -The box holds one outbound WSS connection to `wss://relay.ftw.energy`, joining -under a handle derived per epoch from the rendezvous secret with HKDF-SHA256. -The handle rotates hourly, so the protocol exposes no stable per-box name or -DNS alias. The relay still sees source IP, timing and connection continuity, -which can correlate a household across the hour boundary. -An epoch correction from the relay is read as a clock correction and clamped; -it is never an order. - -The relay forwards encrypted frames and holds no keys. Up to four phones share -one uplink; the relay broadcasts, and the box lets the AEAD decide which -session a frame belongs to, because asking the relay would require the relay to -know. Lane 0 frames are constant in length and cadence, because a -variable-length 1 Hz power stream leaks a household's load pattern through -perfect encryption. - -Commands carry an expiry and preconditions, and core revalidates against fresh -state before acting. A queued command is never replayed silently. Site mode -changes go through `control.ApplyMode` from every door. Stale telemetry, local -limits and local operator actions remain authoritative. - -The public `srcfl/device-drivers` release channel remains separate from -pairing and authentication. - -The three items that were open here shipped in 2026: - -- the on-box pairing surface is the Settings → FTW app tab, which mints the - single-use code on demand and draws the QR with a fragment-only payload - ([#828](https://github.com/srcfl/ftw/pull/828), - [#994](https://github.com/srcfl/ftw/pull/994)); -- per-device revocation is `DELETE /api/app-link/devices/{id}`, immediate at - the box, so one lost phone no longer costs the household its pairing - ([#831](https://github.com/srcfl/ftw/pull/831), - [#880](https://github.com/srcfl/ftw/pull/880)); -- the plan, history tiles, prices and EV commands ride the app protocol - ([#829](https://github.com/srcfl/ftw/pull/829), - [#836](https://github.com/srcfl/ftw/pull/836), - [#871](https://github.com/srcfl/ftw/pull/871)). Push deliberately does not: - it is Web Push with the relay as a blind dead-man courier - ([#872](https://github.com/srcfl/ftw/pull/872)). - -### Conditional Apple EnergyKit native companion - -Apple EnergyKit is a conditional native companion initiative, never an FTW -core implementation. The base framework, electricity guidance, and EV/HVAC -load events require iOS/iPadOS 26 or later. Named load devices, EV -status/reasons/targets and Home presentation belong to the OS 27 beta line. -EnergyKit remains officially limited to the contiguous United States, so a -Sweden pilot is blocked by both region and the stability of the beta APIs. - -The native app owns the entitlement, consent, venue mapping, guidance token and -offline event submission. For every venue, the person explicitly opts in to a -binding between their local passkey identity, the site-controller public key -and that venue. Person identity and site identity never collapse into one key. - -Core owns a versioned, vendor-neutral venue/guidance/load-event flow and a -durable neutral EV event journal. Guidance is advisory input and passes the -same completeness, freshness and safety validation as every other planner -output. Adapter absence, denial or regional unavailability leaves FTW planning -and local operation unchanged. - -This initiative cannot graduate until regional availability includes the -target site, the OS/API surface is stable, the neutral EV event journal is -durable, and the consent, retention and deletion model has passed privacy -review. See Apple's official [EnergyKit overview](https://developer.apple.com/energykit/) -and [EnergyKit updates](https://developer.apple.com/documentation/updates/energykit) -for the platform boundary. - -## LATER — promote only from evidence - -These are bounded follow-on directions, not scheduled commitments. Order -inside this table is still return: the first row is the one that should -be promoted first once its gate is met. - -| Direction | Promotion gate | -|---|---| -| External grid constraints | A versioned constraint record has provenance, effective window, expiry, conflict handling and an audit trail; it can never weaken physical site limits. The record caps the root of the fuse tree. Household charging policy track 4 (fuse tree) has exit evidence. | -| Active heat | Neutral thermal capabilities, comfort bounds, a legionella or equivalent hygiene constraint, and a safe autonomous default are demonstrated before dispatch is enabled. The heat pump remains the thermodynamic owner. Core may request boost, dim or continuous run; it never writes a compressor setpoint. Household charging policy tracks 1–3 have exit evidence so heat cannot empty the house battery. | -| Excess-PV sinks | A priority list after the house battery (EV surplus loop, water, then other dump loads) is defined as eligible slots plus a measured-surplus dispatcher. The optimizer does not open-loop a sink. Active heat or a dump-load driver exists with a default-off fail. | -| Matter support ([matter.js](https://github.com/project-chip/matter.js)) | A TypeScript matter.js process runs as an optional module behind a narrow versioned contract — like the optimizer: independent failure and update semantics, and a safe unavailable state in which local control, planning and existing drivers are unchanged. Matter devices reach control only as capability-declared devices keyed by stable node identity, with autonomous defaults and sign conversion at that boundary. A Matter fabric may observe the site but never becomes a second commander, and fabric credentials stay out of SQLite. | -| OCPP gateway | The EV lease/action model and stable charger identity are proven locally, including disconnect and autonomous-default behavior. *Status 2026-09-02: effectively delivered — a built-in OCPP 1.6J + 2.0.1 server shipped with stable charger identity, disconnect handling, a hold-last-limit autonomous default and a current-limit-only action model ([#979](https://github.com/srcfl/ftw/pull/979), [#999](https://github.com/srcfl/ftw/pull/999), [#1015](https://github.com/srcfl/ftw/pull/1015)); the battery-to-EV lease is not yet exercised against an OCPP loadpoint in tests.* | -| OCPP forwarding | The gateway is proven. A tap can forward session and meter frames to one upstream CPO and block remote start, stop and profile so core stays the only commander. Upstream loss does not stop local charging. | -| Passive battery awareness | An EV-only site can read house-battery SoC without owning the inverter, and still refuse to pull the pack below the house reserve. *Status 2026-09-02: only a building block exists — opt-in read-only PV/battery ingest from the Zap ([#974](https://github.com/srcfl/ftw/pull/974)).* | -| Native widgets and richer multi-site views | The app protocol's read schema, per-site pairing and privacy budget are stable in production. | -| [Dashboard render budget](https://github.com/srcfl/ftw/issues/881) | A reference Raspberry Pi trace proves zero chart and particle work while hidden, measures visible-frame cost on the agreed fixture, and shows no visual or control regression in automated checks and human browser review. | -| [Energy-ledger write batching](https://github.com/srcfl/ftw/issues/882) | Exact ledger and cursor parity and rollback tests pass; Pi arm64 shows at least 2x speed on tmpfs, 20% on deployment SD, and 35% fewer allocations at 12 observations, with no case slower by more than 5%. | -| V2X automation | Bidirectional capability, metering, lease ownership, interlocks and fallback are proven for the complete local actuation path. *Status 2026-09-02: the policy envelope exists and collapses to 0 W on stale inputs, but dispatch is still manual operator commands; the automated path stays gated.* | -| General vehicle snapshot adapter | A minimal vendor-neutral snapshot has stable vehicle identity, freshness and consent semantics without becoming a second control path. Offline and guest vehicles remain the default path. A sleeping OEM API must not block surplus charging. | -| What-if and tariff compare | An offline tool can replay the ledger under another tariff. It never sits in the control loop. The tariff contract from issue #866 is accepted. | - -## Later — Device Support package promotion - -Device Support may later consume an exact `srcfl/device-drivers` commit for -another product or a higher support level. That work must not create a second -editable source or replace FTW's public default channel. Core will consume only -packages that pass its host contract, signature, compatibility, activation and -rollback checks. - -The architectural decision for the FTW app lane is recorded in -[ADR 0005](adr/0005-outbound-site-link.md). +| `srcfl/ftw` | Core safety, state, commissioning, dispatch, on-box UI, history, API and shared product direction. | +| `srcfl/energyplan` | Private solver and forecast implementation; defaults and model quality must match Core's contract. Only compiled artifacts and public integration metadata go to Core. | +| `srcfl/device-drivers` | Mixed-device support, stable identity, trustworthy readings, declared limits, structured command results and hardware evidence. | +| `srcfl/ftw-webapp` | Fast everyday UI, charging interaction, intent/result feedback, notifications and encrypted client/session contracts. | +| `srcfl/ftw-app` | The same product principles, with current Pair + Now verification gates preserved before expanding native scope. | +| `srcfl/ftw-web` | Explain the product and contribution route accurately. Separate available behaviour from product goals. | + +## How work enters the roadmap + +External users submit issues, not PRs. Sourceful implements selected work. +Acceptance of an issue does not invite an external implementation PR. + +For each selected change, state the household need, the behaviour to change, +the existing work it touches and the evidence that will establish completion. +Keep details in the issue and PR. Do not create another feature inventory or +long-lived agent work plan beside this roadmap. + +Decide exclusions as concrete needs arise. Existing proposals, including the +[tariff and demand discussion](https://github.com/srcfl/ftw/issues/866), remain +evidence to assess; they are neither blanket implementation bans nor delivery +promises. An older PR's title or approval is not proof it still fits current +code or direction. Coordinate changes with its author and preserve unique +work before any owner-authorized closure.