Follow-up from #321 / PR #322. #322 makes delegated-credential resolution robust despite poisoned catalog rows (union alias probe + heal-forward). This issue tracks the upstream source that writes those rows, so the catalog stops accumulating poison (and the per-resolve "not uniquely resolved" warning #322 emits stops firing on a permanently-polluted catalog).
Symptom
Live ~/.relayfile/workspaces.json contains an inverted row where id and relayWorkspaceId are swapped relative to the normal rows:
- Normal:
name="default", id="50587328-…uuid", relayWorkspaceId="rw_7ccfea89"
- Inverted:
name="rw_7ccfea89", id="rw_7ccfea89", relayWorkspaceId="50587328-…uuid"
- plus a no-cross-link twin:
name="rw_7ccfea89", id="rw_7ccfea89", relayWorkspaceId=""
Because matching is by Name or ID, the rw_-named record and the UUID-id'd record never reconcile → duplicates + the inverted twin accumulate. For input rw_7ccfea89, canonicalization then sees two different record.IDs and (correctly, post-#322) fails closed to raw-shard hashing.
Root cause (cmd/relayfile-cli/main.go)
upsertWorkspaceDetails (main.go:7771-7773) defaults record.ID = record.Name when ID is empty. If a workspace is ever created/keyed with the rw_ handle as its Name (any command that accepts rw_… as a workspace selector and upserts it), record.ID becomes the rw_ handle — never a UUID.
- Later
persistDelegatedWorkspace (main.go:1823-1830): relayWorkspaceID := bundle.Workspace() returns the canonical UUID (firstNonEmpty(RelayfileWorkspaceID, WorkspaceID), delegatedauth.go:70). Since record.ID is already non-empty (the rw_ handle) and != UUID, it assigns record.RelayWorkspaceID = UUID. Net: id=rw_handle, relayWorkspaceId=UUID → the inverted row.
This is the same app-UUID↔rw_ identity split that produced #321: the local catalog conflates "the string the user typed to select a workspace" (which can be a rw_ handle) with "the canonical workspace ID" (UUID).
Proposed fix direction
record.ID should be the canonical workspace identity (UUID) and must never be defaulted from a rw_ handle. Options:
- (a) In
upsertWorkspaceDetails, if the would-be ID has the rw_ prefix, route it to RelayWorkspaceID and leave ID empty/uncanonicalized until the UUID is known.
- (b) In
persistDelegatedWorkspace, if bundle.Workspace() is a UUID and record.ID is a rw_ handle, treat the UUID as the authoritative ID and demote the rw_ to RelayWorkspaceID (correct the swap), then de-dupe against the canonical-ID record.
- Plus a one-time catalog migration to collapse existing inverted/duplicate rows.
Severity
Non-blocking. #322 already prevents this from breaking mounts; this is hygiene to stop poison accumulation and silence the cosmetic per-resolve warning.
Filed by cloud-expert per review of #321 / #322.
Follow-up from #321 / PR #322. #322 makes delegated-credential resolution robust despite poisoned catalog rows (union alias probe + heal-forward). This issue tracks the upstream source that writes those rows, so the catalog stops accumulating poison (and the per-resolve "not uniquely resolved" warning #322 emits stops firing on a permanently-polluted catalog).
Symptom
Live
~/.relayfile/workspaces.jsoncontains an inverted row whereidandrelayWorkspaceIdare swapped relative to the normal rows:name="default", id="50587328-…uuid", relayWorkspaceId="rw_7ccfea89"name="rw_7ccfea89", id="rw_7ccfea89", relayWorkspaceId="50587328-…uuid"name="rw_7ccfea89", id="rw_7ccfea89", relayWorkspaceId=""Because matching is by Name or ID, the rw_-named record and the UUID-id'd record never reconcile → duplicates + the inverted twin accumulate. For input
rw_7ccfea89, canonicalization then sees two differentrecord.IDs and (correctly, post-#322) fails closed to raw-shard hashing.Root cause (cmd/relayfile-cli/main.go)
upsertWorkspaceDetails(main.go:7771-7773) defaultsrecord.ID = record.NamewhenIDis empty. If a workspace is ever created/keyed with the rw_ handle as its Name (any command that acceptsrw_…as a workspace selector and upserts it),record.IDbecomes the rw_ handle — never a UUID.persistDelegatedWorkspace(main.go:1823-1830):relayWorkspaceID := bundle.Workspace()returns the canonical UUID (firstNonEmpty(RelayfileWorkspaceID, WorkspaceID), delegatedauth.go:70). Sincerecord.IDis already non-empty (the rw_ handle) and!= UUID, it assignsrecord.RelayWorkspaceID = UUID. Net:id=rw_handle, relayWorkspaceId=UUID→ the inverted row.This is the same app-UUID↔rw_ identity split that produced #321: the local catalog conflates "the string the user typed to select a workspace" (which can be a rw_ handle) with "the canonical workspace ID" (UUID).
Proposed fix direction
record.IDshould be the canonical workspace identity (UUID) and must never be defaulted from a rw_ handle. Options:upsertWorkspaceDetails, if the would-beIDhas therw_prefix, route it toRelayWorkspaceIDand leaveIDempty/uncanonicalized until the UUID is known.persistDelegatedWorkspace, ifbundle.Workspace()is a UUID andrecord.IDis a rw_ handle, treat the UUID as the authoritativeIDand demote the rw_ toRelayWorkspaceID(correct the swap), then de-dupe against the canonical-ID record.Severity
Non-blocking. #322 already prevents this from breaking mounts; this is hygiene to stop poison accumulation and silence the cosmetic per-resolve warning.
Filed by cloud-expert per review of #321 / #322.