Skip to content

fix(drivers): drivers follow the Core release - #1422

Merged
frahlg merged 1 commit into
masterfrom
fix/drivers-follow-release
Sep 25, 2026
Merged

frahlg merged 1 commit into
masterfrom
fix/drivers-follow-release

Conversation

@frahlg

@frahlg frahlg commented Sep 25, 2026

Copy link
Copy Markdown
Member

Why

On the home box, a managed easee_cloud 1.3.2 from the beta channel kept running over the bundled 1.3.3 in v0.136.4-beta.1. The resolver picks a driver by which file exists (local, then managed, then bundled), never by version, and Settings called the old copy up to date. The owner then decided the model: drivers reach users with the Core release. There is one version, and ftw update and ftw rollback cover the drivers. A driver fix is a pin bump in the next beta. The signed channel serves installs that cannot take a new Core (1.x–3.x) and expert overrides. An override ends when a release catches up.

What changes

  • Retire superseded installs (driverrepo.RetireSupersededByBundled, called before driver paths resolve). At the first start of a new release, Core deactivates each managed install whose bundled copy at the same path is at least as new.
    • Within one release an override stays, including an older version chosen on purpose.
    • The last reconciled release is kept in the config key-value store (driver_repository/bundled_release). No schema change. A box without that key reconciles on its first start.
    • The comparison goes by path, the key the resolver already uses. For drivers whose source and channel ids or versions differ (easee-cloud/easee_cloud, sdm630 1.1.1/2.0.1), a lower bundled version keeps the override, which is the safe direction.
  • ftw status shows drivers. The driver catalog gains used_by, the enabled configured drivers that run each file. ftw status prints Drivers: easee 1.3.3, sungrow 1.5.8 and an Override: line for a channel install or a local file. A Core without used_by leaves the section out.
  • Docs. AGENTS.md (Releases), architecture, self-update, device repository and an ADR 0007 amendment describe the model. drivers-stable following stable Core is marked proposed. The device repository doc no longer claims the channel is read-only (control: true entries keep their control path), and it drops the outdated YAML repoint for beta in favour of the built-in beta channel.

AGENTS.md and architecture.md are also touched by open #1364 and #1292, in other hunks (#1364 around line 271; #1292 around lines 91 and 283 and in architecture.md). This PR changes only the Releases paragraph and the versioning paragraph.

Evidence

  • New go/internal/driverrepo/bundled_supersedes_test.go covers:
    • a new release retires an equal-version install;
    • the first boot without a recorded release reconciles;
    • a downgrade stays within its release and ends at the next one;
    • a newer managed install stays;
    • an unbundled driver stays.
  • TestDriverCatalogNamesTheDriversThatRunEachFile checks used_by before and after an install and for a disabled driver.
  • The CLI status test covers the drivers line, overrides and an older Core.
  • make test: 68 packages ok. go vet ./... is clean, and ftw, ftw-cli and ftw-launcher build.

Follow-up steps and bugs found along the way are in a separate issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7

On the home box a managed easee_cloud 1.3.2 from the beta channel kept
running over the bundled 1.3.3 in v0.136.4-beta.1. The resolver picks a
driver by which file exists, local then managed then bundled, never by
version, and Settings called the old copy up to date.

Drivers now ship with the release. At the first start of a new release,
Core retires each managed install whose bundled copy at the same path is
at least as new, so the release's driver runs. Within one release an
override stays, including an older version chosen on purpose. The
release that last reconciled is kept in the config key-value store; a
box without it reconciles on its first start. The comparison goes by
path, the key the resolver uses, so it is safe for drivers whose source
and channel ids or versions are spelled differently: a lower bundled
version keeps the override.

The driver catalog now names the configured drivers that run each file,
and `ftw status` lists each driver's version and names any override.
AGENTS.md, the architecture, self-update and device repository docs and
ADR 0007 describe the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-25T05:45:13.997116Z 9cf17ab PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 9cf17ab313

ℹ️ 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 go/cmd/ftw/main.go
)
// Drivers ship with the release. A managed install that this release's
// bundled copy has caught up with ends here, before any path resolves.
driverRepository.RetireSupersededByBundled(resolveDriverDir())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Retire overrides only after trial readiness

During a native update trial, this runs long before trial.complete marks the candidate ready. If the candidate later fails readiness and the launcher falls back, the shared managed-driver symlink and state have already been deactivated, so the previous Core returns with its older bundled driver rather than the previously working override. Defer this destructive reconciliation until the trial is committed, or restore the activation during fallback.

AGENTS.md reference: AGENTS.md:L172-L176

Useful? React with 👍 / 👎.

Comment thread go/internal/api/api.go
s.deps.CfgMu.RLock()
for _, d := range s.deps.Cfg.Drivers {
if !d.Disabled && d.Lua != "" {
usedBy[filepath.Base(d.Lua)] = append(usedBy[filepath.Base(d.Lua)], d.Name)

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 Match running drivers by resolved path

For a supported configuration that points lua at an absolute custom file outside the user, managed, and bundled catalog directories, matching only the basename either attaches used_by to an unrelated bundled entry with the same filename or omits the driver entirely. Consequently ftw status can report the wrong version/source or fail to identify a local override; classify the resolved path against the source directories and parse unmatched configured files directly.

AGENTS.md reference: AGENTS.md:L17-L19

Useful? React with 👍 / 👎.

Comment on lines +933 to +934
if err := m.store.SaveConfig(releaseMarkerKey, m.hostVersion); err != nil {
slog.Warn("driver repository: record reconciled release", "err", err)

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 Retry retirement failures before recording the release

The release marker is persisted even when one or more calls to Deactivate failed. On every later boot of the same release, the marker causes an immediate return, so a filesystem failure leaves the managed driver shadowing the bundled copy indefinitely; after a transient database failure, reconcileActive can even recreate the removed symlink on restart and the marker prevents removing it again. Record the marker only when every eligible retirement succeeds.

Useful? React with 👍 / 👎.

Comment thread docs/device-repository.md
Comment on lines +109 to +111
permissions. A driver the catalog marks `control: true` keeps its control path
and runs under the same terms as the copy bundled with Core, which is the same
source. Every other entry gets only the read permissions it needs:

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 Describe control drivers' allowed write phases accurately

Including control: true entries in this section makes the following blanket statement that FTW denies writes during command and default mode false. The runtime deliberately permits granted modbus.write operations during those two leased phases while denying writes during init, poll, and cleanup, so the trust documentation now contradicts the enforced control-v2 policy and should distinguish control packages from read-only entries.

Useful? React with 👍 / 👎.

@frahlg
frahlg merged commit 70f2563 into master Sep 25, 2026
16 checks passed
@frahlg
frahlg deleted the fix/drivers-follow-release branch September 25, 2026 05:45
frahlg added a commit that referenced this pull request Sep 25, 2026
…gs (#1426)

Follow-up to #1422 after the owner set the rule: an owner picks which
signed driver version runs, and nothing changes without that choice.

- A version installed although the release bundles a newer one is a
  choice and stays across Core updates until the owner changes it. A
  newer version installed early still ends when a release catches up.
  The choice is kept per logical path in the config key-value store and
  forgotten when the owner goes back to the release's copy.
- The bundled sources spell ids their own way (easee-cloud for the
  channel's easee_cloud). "Use bundled" answered "no bundled file
  declares driver easee_cloud" on the home box, and a first install did
  not reach the running instance until Core restarted. Both now accept
  the file the signed manifest names when its id passes the publisher's
  identity rule (device-drivers _identifies_same_driver); an equal id
  still counts anywhere, and a different id at that path does not.
- The driver catalog reports release_version beside an override, and
  `ftw status` prints it.
- The built-in beta channel honours device_repository.enabled: false.
- Update Center counted any beta whose version differed as an update,
  an older one included; it now compares SemVer.
- The note in drivers/BUNDLED_SOURCE.json no longer claims every channel
  artifact is write-inert.


Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Sep 25, 2026
Independent review of #1422 and #1426:

- RetireSupersededByBundled deactivated the managed selection when a
  release bundled a version at least as new. A failed update trial that
  fell back, or `ftw rollback`, then ran the older release's older copy
  and lost the version the owner had installed early.
- A choice counted only when it was older than the bundled copy at that
  moment, so going back from an early 1.3.3 to 1.3.2 under a release
  with 1.3.0 was not kept, and the next release ran the rejected 1.3.3.

The selection is no longer changed at start. driver-repository/active
records what the owner selected; driver-repository/effective, which
paths now resolve through (config, catalog, API, driver inventory), is
derived from it and the release's drivers at every start and after
every change. A selection runs while it is at least as new as the
release's copy, or when the owner went back to it from a newer version
that was running. A rollback of an activation restores the choice it
replaced, so recovering from a failed install leaves no mark. The
release marker is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7
frahlg added a commit that referenced this pull request Sep 25, 2026
…runs

Codex review of #1422 found two defects:

- RetireSupersededByBundled recorded the release as reconciled even
  when a Deactivate failed, so a retirement blocked once (for example by
  an unwritable active directory) was never tried again on that
  release. The release marker is now saved only when every retirement
  succeeded.
- The catalog's used_by matched configured drivers by file name. An
  operator's own file elsewhere with the same name was credited to the
  catalog entry, and `ftw status` reported a version that was not
  running. It now compares the configured resolved path with the
  entry's file in its source directory.

Both new tests fail on the previous code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7
frahlg added a commit that referenced this pull request Sep 25, 2026
Independent review of #1422 and #1426:

- RetireSupersededByBundled deactivated the managed selection when a
  release bundled a version at least as new. A failed update trial that
  fell back, or `ftw rollback`, then ran the older release's older copy
  and lost the version the owner had installed early.
- A choice counted only when it was older than the bundled copy at that
  moment, so going back from an early 1.3.3 to 1.3.2 under a release
  with 1.3.0 was not kept, and the next release ran the rejected 1.3.3.

The selection is no longer changed at start. driver-repository/active
records what the owner selected; driver-repository/effective, which
paths now resolve through (config, catalog, API, driver inventory), is
derived from it and the release's drivers at every start and after
every change. A selection runs while it is at least as new as the
release's copy, or when the owner went back to it from a newer version
that was running. A rollback of an activation restores the choice it
replaced, so recovering from a failed install leaves no mark. The
release marker is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7
frahlg added a commit that referenced this pull request Sep 25, 2026
* fix(drivers): retry failed retirements and credit only the file that runs

Codex review of #1422 found two defects:

- RetireSupersededByBundled recorded the release as reconciled even
  when a Deactivate failed, so a retirement blocked once (for example by
  an unwritable active directory) was never tried again on that
  release. The release marker is now saved only when every retirement
  succeeded.
- The catalog's used_by matched configured drivers by file name. An
  operator's own file elsewhere with the same name was credited to the
  catalog entry, and `ftw status` reported a version that was not
  running. It now compares the configured resolved path with the
  entry's file in its source directory.

Both new tests fail on the previous code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7

* fix(drivers): never lose a driver selection to a Core update

Independent review of #1422 and #1426:

- RetireSupersededByBundled deactivated the managed selection when a
  release bundled a version at least as new. A failed update trial that
  fell back, or `ftw rollback`, then ran the older release's older copy
  and lost the version the owner had installed early.
- A choice counted only when it was older than the bundled copy at that
  moment, so going back from an early 1.3.3 to 1.3.2 under a release
  with 1.3.0 was not kept, and the next release ran the rejected 1.3.3.

The selection is no longer changed at start. driver-repository/active
records what the owner selected; driver-repository/effective, which
paths now resolve through (config, catalog, API, driver inventory), is
derived from it and the release's drivers at every start and after
every change. A selection runs while it is at least as new as the
release's copy, or when the owner went back to it from a newer version
that was running. A rollback of an activation restores the choice it
replaced, so recovering from a failed install leaves no mark. The
release marker is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Sep 25, 2026
…1434)

When a release's newer copy overtakes a kept driver selection, the
selection stays but does not run. Three places still assumed it ran:

- A failed trial restarted the kept selection's file in the effective
  directory, which no longer exists, so the device stayed stopped.
  The restart now falls back to the release's copy when no managed
  file runs at the path.
- A failed switch recovered with ActivateInstalled, which pinned the
  kept older version and ran it over the release's copy. It now uses
  Rollback, which restores the selection and the choice together.
- The Versions list marked the kept selection as selected and hid its
  "Use this". /versions now names it as superseded_version, and the
  list offers it like any version on disk.

Found by the Codex review of #1422..#1433.


Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant