* backup: pin the #880 subscription sign-in policy with a test
A backup must not carry a subscription's sign-in: it is the sign-in of an
agent on one machine, so a bundle that carried one would sign the agent in
on the machine it was put on. #880 asked for that and was closed as
intentional. Nothing pinned it, so this adds the test that does.
Both stores, both credential policies, and the asymmetry the report tripped
over together: a subscription moved onto its plugin still has a row in
providers.json, and the row travels so the receiving machine offers the
subscription at all — the credential behind it does not. A user-typed key
is the control, so the policy cannot be read as "nothing credential-shaped
syncs", and Restore is checked for leaving this machine's sign-ins alone.
scopedKeys has no case for "providers" and falls through to the whole-
bundle bit. That fall-through is deliberately not pinned: scopedKeys is
called once, with "settings", so there is nothing observable to assert,
and a test that did would go red for a good reason the day the case is
added. The tests around it pass either way; the reason is written down.
Co-authored-by: multica-agent <github@multica.ai>
* backup: say what the #880 sign-in tests actually guarantee
Review of 974ca819 found no blocking problem but two comments that
pinned more than they could back up.
carried() claimed that a credential absent from a bundle's marshalled
bytes cannot be inside a sealed backup either. That is false, and it was
measured rather than argued: Seal marshals the whole Bundle in one
json.Marshal, but a []byte field is base64 in those bytes, and a Collect
that copies plugin-auth.json into b.Icons["auth-state.bin"] seals the
whole plugin store while every one of these tests stays green. The probe
is still the right one for what the policy is about, which is a
credential arriving as plain text, so the comment now says that and
records the two limits that keep it honest: a literal check misses JSON
escaping, and a Seal that grew a wire type of its own would decouple
carried() from the bytes it claims to describe.
TestRestoreLeavesLocalSignInsAlone compared the two sign-in stores byte
for byte. Byte equality is file identity, not policy, and re-indenting
logins.json — same accounts, same credentials, same counts — turned it
red. It now compares the sign-ins: who is signed in, and the credential
behind each, canonicalised, in a fixed order. An incidental reformat
cannot fail it, and a restore that really did take the sign-ins over
still does, which a probe that overwrote both stores confirms.
Also notes that TestCollectCarriesTheRowOfAMovedSubscription does not
compare Headers, and where that coverage does live.
Co-authored-by: multica-agent <github@multica.ai>
* backup: apply the #901 maintainer's four review changes
Drops the two line-number references in favour of the function names
Seal and scopedKeys("settings"), which do not go stale when the file
moves: Seal does the single json.Marshal of the Bundle (backup.go:252)
and the scopedKeys("settings") call is inside Restore (backup.go:401),
so both attributions check out.
Renames the four plugin fixtures that named a vendor they never used.
fixtureGrok* are written under movedProviderID ("opencode-zen") and
fixtureGeminiKey under "opencode-copilot"; both are OpenCode plugin
providers, so the names now say plugin. The built-in codex and claude
fixtures are left alone — those agents really do own those logins.
Removes TestScopedKeysFallsBackToTheWholeBundleBit and
TestScopedKeysPrefersAPartsOwnMarker with setScopedMarker. They are
about part-scoped key markers, not about the sign-in policy this file
states, and the file header no longer leans on them.
Closes the Icons blind spot instead of documenting it away. carried()
only ever saw plain text, and Bundle.Icons is map[string][]byte, so a
Collect copying plugin-auth.json into b.Icons["auth-state.bin"] put
the whole plugin store in a sealed backup with every assertion green.
whereSignInIs searches the decoded value of every Icons entry as well
as the marshalled bytes, and names the layer in the failure. Both the
keyless and keyed subtests now go red under that mutation, where they
previously passed.
Test-only: one file, no production code.
---------
Co-authored-by: multica-agent <github@multica.ai>