On-disk JSON shape for the single-blob format (macOS/Linux’s preferred
layout, write_single_blob/blob_key). Wraps
MuxBusTokens with a generation stamp — the SAME new_generation()
nanosecond-timestamp scheme the chunked layout already uses for its own
cross-field torn-write check — so a blob and split entries found
coexisting (muxbus_load_tokens’s reconciliation) can be compared for
actual freshness instead of assuming a fixed direction is always right.
The actual secret material. Every field is written to its own keychain
entry (field_key); this struct only groups them for callers and for
deserializing the legacy combined-blob format during migration.
Everything else on MuxBusCredentials is non-secret metadata and stays
in SQLite, matching how SecretRef::Keychain accounts already split
pointer-metadata (DB) from plaintext (keychain) elsewhere in this codebase.
Read field_key’s value + generation stamp back from its chunk entries.
Ok(None) means no :count entry exists yet (this field was never
written under the chunked layout — caller falls through to the
legacy-blob / legacy-plaintext sources). Err means a read genuinely
failed, or the chunk state is internally inconsistent (a chunk went
missing between the count write and now) — not just “no entry”. A
missing :gen entry on an otherwise-complete field is treated as a read
failure too, not defaulted to some sentinel — see read_split_tokens,
which needs a REAL generation to compare across fields, not a value
that would spuriously “match” another field’s own missing generation.
Why read_chunked_field has no value: the field is mid-write or was
interrupted (Yes, a torn read to retry), or the keychain failed (No).
What a keychain entry held immediately before a write to it, so a later
failure (the SQL write in muxbus_save, or a sibling field’s write in
write_split_tokens) can be rolled back to that exact prior state.
Three-way, not a bool: reagent P1 on #2260 caught a first version of this
that collapsed Ok(None) (“genuinely no prior entry”) and Err (“the
read itself failed, prior state UNKNOWN”) into the same None, so an
unrelated transient read failure would make the rollback branch delete a
real, valid, previously-stored credential it simply couldn’t read —
turning a transient glitch into a forced full re-login. Unknown gets
neither restore nor delete: we truly don’t know what was there, so the
only safe move is to leave whatever the write just wrote and log loudly.
The host-wide keychain namespace: the stable channel’s (and any
non-isolated channel’s) MuxBus tokens. Same string as the broker’s
credential id, crate::muxbus::CREDENTIAL_ID.
A first fix attempt (splitting the combined blob into one keychain entry
per field) turned out NOT to be sufficient: live-tested against a real
Cognito login, the exact same “Attribute ‘password’ is longer than
platform limit of 2560 chars” error still fired — a single token field
can itself exceed the cap depending on the app client’s token content
(custom claims, refresh token length), not just the three combined. So
each field is further chunked into as many entries as it takes, tracked
by an explicit <field>:count entry (write_chunked_field /
read_chunked_field below) — this holds regardless of any individual
token’s real-world size, instead of relying on an assumption about it.
A previous, longer value at a field could in principle need many chunks;
no real Cognito token comes remotely close (MAX_PLAUSIBLE_CHUNKS * MAX_CHUNK_LEN = 32,000 chars). Used only as muxbus_clear’s fallback
deletion bound when the real count can’t be read (see its call site) —
secret_store::delete on a non-existent entry is a no-op success, so
scanning past the real count there is harmless, just wasted calls.
How long a save, load or clear waits for another process. Past it the
operation fails as transient (a load is retried, a sign-in reports an
error) instead of running unlocked, which is what tore sign-ins.
Prefix for this channel’s rows in db_agent_credentials, which is keyed by
agent id alone: empty for stable and for the channel’s own store. An agent
id is [A-Za-z0-9_-], so the / can’t collide with one.
Pure chunking split — no keychain I/O — so the boundary math is testable
without touching the real OS keychain. A token’s bytes are always ASCII
in practice (JWTs / opaque base64url strings), so splitting on byte
boundaries never lands mid-character; unwrap_or("") is a defensive
fallback only, not an expected path.
Delete every entry the chunked per-field layout could have written
(FIELD_ACCESS/FIELD_REFRESH/FIELD_ID’s chunks + :count + :gen).
Shared by muxbus_clear (unconditional logout cleanup, any platform) and
the macOS/Linux migration path in muxbus_load_tokens (collapsing a
pre-fix install’s chunked entries into the single-blob format — see
blob_key’s doc comment). Best-effort: secret_store::delete
on a non-existent entry is a no-op success, and a real delete failure here
just leaves an orphaned, unreadable-without-the-others chunk behind rather
than losing anything live.
Parse a new_generation()-style nanosecond-timestamp string into a
comparable number. An empty or corrupted value reads as 0 — the
oldest possible timestamp — so it always loses a freshness comparison
rather than erroring a read that would otherwise succeed.
Take the lock at path, polling until wait has passed. None when it
couldn’t be taken: another process held it the whole time, or the file
couldn’t be opened.
A per-write identifier stamped onto all three fields by a single
write_split_tokens call, so read_split_tokens can detect a torn
write ACROSS a process crash — not the concurrent-read-in-one-process
case muxbus_load_impl’s lock already covers, but a kill signal
landing mid-write, between one field’s write_chunked_field call
completing and the next one starting (reagent P2: each field is
individually self-consistent, per write_chunked_field’s own
chunk+rollback atomicity, but with no lock surviving the process
there’s nothing to stop the OLD stale value for the not-yet-reached
field from still being present when the other two ARE the fresh ones —
silently reconstructing a token set that pairs pieces from two
different login sessions). High-resolution wall-clock nanos is unique
enough for this purpose in practice; a formal UUID would work too but
adds a dependency for no real benefit here.
The tokens stored under ns in either layout (chunked split entries or
the single blob), without migrating or deleting anything. None when
neither layout holds a complete, consistent set or a read fails. Used
only to adopt the host-wide session into a channel namespace.
Ok(0) means the :count entry genuinely doesn’t exist (field never
written under the chunked layout). Err means the read itself failed —
reagent P1: a prior version of this collapsed both into the same 0,
which muxbus_clear used to bound its per-field deletion loop
(for i in 0..count) — a transient read failure made that loop delete
NOTHING, while the field’s :count key was still deleted unconditionally
right after, so logout appeared to succeed while the real token chunks
were silently orphaned in the OS keychain. Callers must not treat Err
as “0 chunks.”
Read the split entries, retrying a few times while they look torn: a save
by an older build (no cross-process lock) may be mid-write, and a moment
later the same read is whole. A tear that persists is genuine.
Read the three split-entry (chunked) tokens. Ok(None) means none of the
three fields exist yet (not migrated to this layout — caller falls
through to the legacy-blob / legacy-plaintext sources). Err means a
read itself failed (locked keychain, no Secret Service daemon,
permission denied — not just “no entry”), which the caller may still
recover from via a legacy fallback.
Returns the tokens plus the generation stamp shared by all three fields
(validated equal below) — callers that need to compare this layout’s
freshness against a coexisting blob use the second element; callers that
don’t (the common case) just ignore it.
The db_muxbus_credentials row id: global for stable and for a store
that is already the channel’s own, channel:<ch> for a channel’s row in the
store channels share.
The channel whose own MuxBus sign-in this process uses. A channel is the
unit of sign-in, not the host: every channel but stable (the installed
releases, which have always used the host-wide set) signs in on its own, so
two channels on one machine can be signed in at once, to the same account
or to different ones, and one’s refresh, sign-in or sign-out never touches
the other’s (SPEC_MUXBUS_SIGN_IN_PER_CHANNEL_2026_10_08.md).
Whether the store this process reads is already this channel’s own file
(isolated auth with an instance dir: registry::paths::resolve_shared_store_path),
where the channel needs no per-row scoping.
Write value to field_key as one or more chunk entries (each under
MAX_CHUNK_LEN) plus a :count entry, clearing any stale trailing
chunks left over from a previous, longer value at this key, and stamps
generation (same value across all three fields for one
write_split_tokens call — see new_generation’s doc comment) on this
field’s :gen entry. Rolls back everything this call touched if any
single write/delete fails partway through.
Write all three tokens as one combined JSON blob under a single keychain
entry (blob_key — the CURRENT preferred format on
macOS/Linux, see that constant’s doc comment). One SecItemAdd/
SecItemUpdate call is inherently atomic at the OS level, so this needs
none of write_split_tokens’s cross-field generation-stamp/rollback
machinery — there’s only one field. Returns the pre-call state wrapped in
the same Vec<(String, PriorKeychainState)> shape write_split_tokens
returns, so muxbus_save’s SQL-failure rollback branch works unchanged
regardless of which one ran.
Write all three token fields, each independently chunked
(write_chunked_field). On a later field’s failure, rolls back every
entry every earlier field in this call already wrote — otherwise a
partial failure would leave the three fields holding a mix of old and
new tokens, which is worse than either the old or the new set alone.