Skip to content

fix: a save that changes nothing records nothing, and apply says so - #1799

Merged
jhgaylor merged 1 commit into
mainfrom
stack/1680-unchanged-verdict
Sep 10, 2026
Merged

jhgaylor merged 1 commit into
mainfrom
stack/1680-unchanged-verdict

Conversation

@jhgaylor

Copy link
Copy Markdown
Collaborator

Repo.update on an empty changeset skips the SQL, but Environments,
Vaults, Agents and Webhooks audited the {:ok, _} either way. An
idempotent re-apply of a manifest therefore wrote one *.updated row per
resource, each naming an empty changed-field list, about a record nobody had
touched. That is what CLAUDE.md's "only record what happened" forbids. The
four contexts now gate the recording on a non-empty changeset, the way
Fountain.Team.Schedules.update_schedule/3 already did.

The same re-apply also reported an update it had not made. Manifest now
compares the record before and after over the schema's own columns (timestamps
left out) and reports a third verdict, unchanged, so a second apply of the
same file says plainly that it wrote nothing. Inline spec.secrets are the
exception and keep reporting upserted: the stored ciphertext cannot be
compared with the plaintext given, so they are re-encrypted on every apply.

fountain apply prints = for such a row, next to + and ~.

This is the first of eight PRs that together land #1636 (one manifest
reconciles a whole estate). #1675 is the combined branch and stays open as the
reference until the stack is in.

Closes #1680

Validation:

  • mix test over the touched suites, mix precommit clean.
  • go test ./... in cli/.
  • The contract and TypeScript types are regenerated; the SDK version bump for
    the whole stack lands in the last PR, so no publish happens here.

The stack (each PR is based on the one above it; review and merge top down):

  1. stack/1680-unchanged-verdict — a save that changes nothing records nothing (Bulk apply reports updated for a document that changed nothing, so a client cannot tell a no-op from a write #1680) ← this one
  2. stack/1636-apply-isolation — a raise in one apply document fails that row
  3. stack/1636-cotenant-identity — a co-tenant follows a replacement only if it declared the same identity
  4. stack/1636-update-teammate — Team.update_teammate/4
  5. stack/1636-cli-group-by-kind — CLI: group apply documents by kind
  6. stack/1636-teammate-kind — apply reconciles Teammate documents
  7. stack/1636-schedule-kind — apply reconciles Schedule documents
  8. stack/1636-webhook-kind — apply reconciles Webhook documents (Bulk apply: accept Teammate, Schedule and Webhook documents so one manifest reconciles the whole estate #1636)

🤖 Generated with Claude Code

https://claude.ai/code/session_01WQyc3wWWDAeZJE1Vr6etQa

@jhgaylor jhgaylor added the release:skip-sdk Explicitly allows an SDK surface change without releasing a new package version. label Sep 10, 2026
@jhgaylor

Copy link
Copy Markdown
Collaborator Author

Labelled sdk-no-release. This PR regenerates sdk/contract/contract.json and the TypeScript types but leaves the version alone on purpose: the whole stack publishes once, from #1806, as 1.24.0. Four intermediate releases for one feature would each describe a half-finished apply vocabulary, and latest would move three times on the way there.

@jhgaylor
jhgaylor force-pushed the stack/1680-unchanged-verdict branch from 8d50b0c to 55c0abd Compare September 10, 2026 11:10
…1680)

`Repo.update` on an empty changeset skips the SQL, but `Environments`,
`Vaults`, `Agents` and `Webhooks` audited the `{:ok, _}` either way. An
idempotent re-apply of a manifest therefore wrote one `*.updated` row per
resource, each naming an empty changed-field list, against a record nobody had
touched. That is what CLAUDE.md's "only record what happened" forbids. The four
contexts now gate the recording on a non-empty changeset, the way
`Fountain.Team.Schedules.update_schedule/3` already did.

The same re-apply also reported an update it had not made. `Fountain.Manifest`
compares the record before and after over the schema's own columns, with the
timestamps left out, and reports a third verdict: `unchanged`. A second apply
of the same file now says plainly that it wrote nothing.

Inline `spec.secrets` are the exception and keep reporting `upserted`. The
stored ciphertext cannot be compared with the plaintext given, so they are
re-encrypted on every apply.

`fountain apply` prints `=` for such a row, beside `+` and `~`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQyc3wWWDAeZJE1Vr6etQa
@jhgaylor
jhgaylor force-pushed the stack/1680-unchanged-verdict branch from 55c0abd to e23057e Compare September 10, 2026 11:26
@jhgaylor
jhgaylor merged commit 0866b26 into main Sep 10, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:skip-sdk Explicitly allows an SDK surface change without releasing a new package version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bulk apply reports updated for a document that changed nothing, so a client cannot tell a no-op from a write

1 participant