Skip to content

Add an update command that reads, diffs, and writes - #17

Open
oveddan wants to merge 1 commit into
mainfrom
claude/update-command
Open

Add an update command that reads, diffs, and writes#17
oveddan wants to merge 1 commit into
mainfrom
claude/update-command

Conversation

@oveddan

@oveddan oveddan commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Read the controller, work out what differs, write only that.

The existing export/plan/apply flow exists so a plan can be reviewed before applying. Worth having, but it is not what most changes need — and it fails the README's own motivating example:

$ mft-config plan --snapshot snap.json --set bank.1.encoder.1.colors.active=green ...
mft-config: bank.1.encoder.1.colors.active is already 43

Asking for the top row to be green aborts if any one of those knobs already is. That is correct for plan, where the contract is "the state I described is the state I found". It is wrong for an update.

$ mft-config update --set bank.1.encoder.1.colors.active=green ... (4 knobs)
unchanged  bank.1.encoder.1.colors.active is already 43
unchanged  bank.1.encoder.2.colors.active is already 43
change     bank.1.encoder.3.colors.active: 43 -> 1
change     bank.1.encoder.4.colors.active: 43 -> 1

2 change(s) not written. Re-run with --yes to apply.

Verified against real hardware.

Why this is not behind the #14 gate

No plan file changes hands, so there is nothing to forge and nothing to replay — the entire class of defect in #14 comes from serialising a plan, handing it to someone else, and reading it back. update therefore needs neither the journal nor the single-use machinery, keeps no hidden state, and writes a backup file you name. 0d, the cwd-relative state root that apply is gated on, does not apply to it.

It still gets every device-side guard: fresh export, precondition check on each value, firmware eligibility recomputed from the device, frames derived and verified, and full read-back.

Notes

  • planUpdate rejects two --sets for one field rather than picking a winner
  • it delegates to createPatchPlan once no-ops are filtered, so both paths build frames through identical code
  • an update where everything is already set succeeds having done nothing

🤖 Generated with Claude Code

Dan: "we should have an apply update method, that looks up the existing
ones, and then just applies the deltas... just pull the current state, run
an update against it, then update the controller, its that simple."

The three-step export/plan/apply flow exists so a plan can be reviewed
before it is applied. That is worth having, but it is not what most changes
need, and it fails the README's own motivating example. Asking for the top
row to be green aborts outright if any one of those knobs is already green,
because `createPatchPlan` treats a no-op as a contract violation — which is
right for `plan`, where the contract is "the state I described is the state
I found", and wrong for an update.

`update` reads the controller, works out what actually differs, prints it,
and writes only that with --yes. Already-correct values are reported as
unchanged and skipped; an update where everything is already set succeeds
having done nothing.

Verified against hardware: the four-knob request above reports two
unchanged and two changes, where `plan` refuses at the first no-op.

No plan file changes hands, so there is nothing to forge and nothing to
replay. That is why this needs neither the journal nor the single-use
machinery `apply` carries, and why it is not behind the #14 gate: it keeps
no hidden state, and the backup is a file you name.

`planUpdate` rejects two --set operations for one field rather than picking
a winner. It delegates to `createPatchPlan` once the no-ops are filtered,
so both paths build frames through exactly the same code.

Refs #14

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95449ff177

ℹ️ 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 src/cli.ts
const connection = backend.connectForApply(device);
try {
const result = await applyPatchPlan(connection, device, plan, {
journalPath: resolve(dirname(backupPath), "mft-updates.ndjson"),

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 Keep the journal separate from the requested backup

When --backup names <directory>/mft-updates.ndjson, saveBackup first writes the JSON snapshot there and then applyPatchPlan appends journal records to the same path. The command reports this file as the backup, but it is no longer valid JSON and cannot be used for restoration. Since the CLI permits any backup filename and does not reserve this name, reject this collision or store the journal at a path that cannot overlap the requested backup.

Useful? React with 👍 / 👎.

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