Skip to content

Restore keg settings from pre-rename archives - #90

Merged
jlrickert merged 1 commit into
mainfrom
fix/archive-legacy-config-manifest
Aug 31, 2026
Merged

Restore keg settings from pre-rename archives#90
jlrickert merged 1 commit into
mainfrom
fix/archive-legacy-config-manifest

Conversation

@jlrickert

Copy link
Copy Markdown
Owner

Renaming the keg document from config to settings renamed the archive manifest key with it — with_config became with_settings — but left the format identifier at keg-archive/v3 on both sides of the change.

An archive is a stored artifact that outlives the code that wrote it, so v3 tarballs carrying the old spelling are already in users' hands. They still pass the version gate, then unmarshal with WithSettings false because with_config matches no field.

Why it is worse than a skipped field

Three decisions read that one value, and the third is inverted:

  • validation skips the keg-document check
  • replaceSettings never runs, so the document is not restored
  • if !manifest.WithSettings is now true, so import takes the "archive carried no settings" branch and calls touchSettingsUpdated

So a settings-bearing archive is treated as settings-free and the settings-updated stamp is refreshed. The restore returns success and looks like it handled the document. What is lost is the keg's title, summary, instructions, and custom indexes; nodes, schemas, attachments, and history all restore correctly, which is what makes it easy to miss.

The fix

Accept the pre-rename spelling and fold it forward immediately after the manifest is parsed, before anything reads WithSettings. There is exactly one unmarshal site and all three gates read that same local variable, so one normalization covers every path.

Export is unchanged and still writes only with_settings. The alias is read-only, and a test asserts a freshly written manifest never contains with_config so it cannot leak back into the writer.

The entry itself was always keg-archive/keg.yaml, so the document was present in every affected archive and merely unread — nothing was unrecoverable.

Scope

Deliberately narrow, after checking what else the rename touched:

  • The keg document schema is byte-identical across the rename — SettingsV2 and the former ConfigV2 carry the same 13 YAML keys. Stored keg.yaml, the archive entry, and Hub-side storage were never at risk.
  • KegInfo.ConfigSettings is the only other renamed key. That is a live wire format between tap and the Hub, not a stored artifact, and is covered by the coordinated release.

with_config was the only persisted break.

No released version is affected

v0.38.0 both writes and reads with_config, self-consistently, so it restores its own archives correctly. The break exists only between the rename and this commit — which is why this needs to land before the next release tag, so no published version ever ships it.

Verification

  • go build ./..., go vet ./..., go test ./..., and go test -race ./pkg/keg ./pkg/tapper all pass.
  • New TestArchiveImportRestoresKegSettingsFromLegacyConfigManifest builds its fixture from a real export whose manifest is rewritten to the old spelling, then imports it into a keg seeded with different settings — so matching the source proves the archived document was applied rather than the target's values surviving.
  • Confirmed the test is not vacuous: with the normalization disabled it fails with expected: "Legacy Title", actual: "Target Title", exactly the reported failure mode.
  • Reproduced and then confirmed fixed against a real archive exported from a live v0.38.0 hub: before the fix a restore imported 183 nodes and left the keg's title, summary, and indexes untouched while refreshing updated; after it, the document applies.

Renaming the keg document from config to settings renamed the archive
manifest key with it -- `with_config` became `with_settings` -- but left
the format identifier at `keg-archive/v3` on both sides of the change.

An archive is a stored artifact that outlives the code that wrote it, so
v3 tarballs carrying the old spelling are already in users' hands. They
still pass the version gate, then unmarshal with WithSettings false
because `with_config` matches no field. Three decisions read that one
value: validation skips the document, replaceSettings never runs, and the
inverted branch that stamps settings-updated for a settings-free archive
now fires. The restore reports success while silently dropping the keg's
title, summary, instructions, and custom indexes, and refreshes the
updated stamp so it looks handled. Nodes, schemas, attachments, and
history all restore correctly, which is what makes it easy to miss.

Accept the pre-rename spelling and fold it forward immediately after the
manifest is parsed, before anything reads WithSettings. Export is
unchanged and still writes only `with_settings`; the alias is read-only,
and a test asserts a freshly written manifest never contains
`with_config` so it cannot leak back into the writer.

The entry itself was always keg-archive/keg.yaml, so the document was
present in every affected archive and merely unread -- nothing is
unrecoverable.

No released version is affected: v0.38.0 both writes and reads
`with_config`, self-consistently. The break existed only between that
rename and this commit.

Covered by an import test built from a real export whose manifest is
rewritten to the old spelling. Confirmed it fails without the fix, with
the destination keeping its own title instead of the archived one.
@jlrickert
jlrickert merged commit cf15e33 into main Aug 31, 2026
1 check passed
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