Skip to content

id(tags): α/Ω tag-key decoders accept a trailing fourth field — a key that is not any tag's key decodes as that tag, and the exact-key delete path can never remove it #1832

Closed

Description

@dezren39

id(tags): α/Ω tag-key decoders accept a trailing fourth field — a key that is not any tag's key decodes as that tag, and the exact-key delete path can never remove it

Problem

pkgs/id/src/tags.rs decodes the tuple keys read back from the α and Ω
namespace documents with an under-count check only:

fn decode_alpha_key(raw: &[u8]) -> Result<(TagValue, TagValue, Option<TagValue>)> {  // tags.rs:585
    let fields = decode(raw)?;
    if fields.len() < 3 {                       // tags.rs:587
        bail!("α key has {} fields, expected 3", fields.len());
    }
    // ... reads fields[0], fields[1], fields[2] only
}

fn decode_omega_key(raw: &[u8]) -> Result<(TagValue, TagValue, Option<TagValue>)> {  // tags.rs:609
    let fields = decode(raw)?;
    if fields.len() < 3 {                       // tags.rs:611
        bail!("Ω key has {} fields, expected 3", fields.len());
    }
    // ... reads fields[0], fields[1], fields[2] only
}

The error message says "expected 3", but a key with 4 (or more) fields is
accepted and its trailing fields are silently dropped. The only keys the
writers ever produce are exactly 3 fields — encode_alpha_key /
encode_omega_key (tags.rs) encode (subject, key, value|null) and
(value|null, key, subject) and nothing else.

Why it matters

  • These decoders run over entry keys returned by prefix queries on the
    synced α/Ω documents (query_alpha_prefix / query_omega_prefix,
    tags.rs:1377/1389 → entry_to_tag_alpha / entry_to_tag_omega,
    tags.rs:1401/1414). A 4-field key whose first three fields match a real
    tag sorts under the same subject/value prefixes (tuple prefix property),
    so it is returned by those queries and presented — in id tag list,
    search and subject listings — as exactly that tag, although no
    set_tag ever wrote it.
  • It can never be removed by the normal path: del_tag (tags.rs:851)
    deletes the exact 3-field encodings encode_alpha_key(subject, key, value) / encode_omega_key(...), which are different byte strings
    from the 4-field key, so the phantom entry survives the delete that
    reports success and keeps reappearing in listings.
  • Lenient decoding also hides schema drift/corruption: a future or buggy
    writer emitting a wider key would be silently misread as the old
    3-field tag instead of failing loudly at decode, the one place every
    read path passes through. (Tuple-level canonicality of individual
    fields is id(tuple): decode accepts negative ints that wrap to zero/positive and NaN floats — the negative branch has no range check, unlike the positive branch #1610; this is the field-count check above it.)

Evidence (verified this run, main 528886e)

Laptop probe at origin/main 528886e (probe crate
~/git/fleet/probe-2026-10-28-laneA-tagkey on the laptop; it compiles the
shipped tuple.rs itself via #[path] — sha256
7f538ef683bfb8694270fe0bc113c043b7d3730d2bf6510c3832be8b64d42a14,
identical in the sandbox and laptop clones, as is tags.rs
e2483c0b5edfe9ee063111b2b84cb5ab29a85eaca137138e895e9c4812e859fe — and
applies decode_alpha_key's guard verbatim: fields.len() < 3 bail,
then read fields [0], [1], [2] only):

  • 3-field α key (file.txt, tag, v1): Ok(("file.txt", "tag", Some("v1"))) — control passes.
  • 4-field key (same key plus a trailing "EXTRA" field, built with the
    crate's own TupleEncoder): tuple decode returns Ok with 4 fields,
    and the guard-shaped decode accepts it as
    ("file.txt", "tag", Some("v1"))
    — the trailing field is silently
    dropped. BUG CONFIRMED.
  • 2-field control: refused with alpha key has 2 fields, expected 3 —
    the guard fires only under 3, never over.

Source, sandbox clone at 528886e:
decode_alpha_key tags.rs:585-607 and decode_omega_key tags.rs:609-631
guard only fields.len() < 3 and index [0], [1], [2]; the Ω decoder
is the same shape, so the same 4-field acceptance applies to it.

Dedup: gh issue list --search for "tuple decode" / "alpha key fields" /
"tag key trailing" (all states, 2026-10-28) — #1610 (open) covers
tuple-level NaN / non-canonical integer payloads inside one field, not
the α/Ω field count; no filing covers decode_alpha_key /
decode_omega_key accepting extra fields.

Acceptance criteria

  • decode_alpha_key and decode_omega_key refuse any key whose
    decoded field count is not exactly 3, with the existing
    "expected 3" wording (change the guards to fields.len() != 3).
  • Unit tests: a 4-field α key and a 4-field Ω key (built with
    TupleEncoder from a valid 3-field key plus one extra field) both
    error; the existing 3-field round-trip tests keep passing.
  • No encoder change: keys on disk / in synced documents are unchanged.
  • Triage pins what a read path should do when a synced document
    already contains such a key (fail that query loudly vs skip the
    entry with a warning) — either is acceptable if it is explicit and
    tested; silently presenting it as a normal tag is not.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. added
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 28, 2026
  3. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator (lane A, new-issue-creator-code-a)
    • Session / ID: cron new-issue-creator-code-a, run 2026-10-28 16:14 CDT
    • Found via: filed this run
    • Task: Triage the α/Ω tag-key field-count leniency: pin the read-path behaviour for an already-synced 4-field key (fail loudly vs skip with warning), then make decode_alpha_key/decode_omega_key refuse field counts != 3.
    • Expectation: effort/xs fix once triaged; done = both decoders refuse != 3 fields with tests, no encoder change.
    • Links: evidence in issue body; probe ~/git/fleet/probe-2026-10-28-laneA-tagkey (laptop, shipped tuple.rs via #[path], main 528886e)
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. 2 remaining items

  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added a commit that references this issue on Nov 1, 2026
  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions