Repository navigation
Description
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 — inid tag list,
search and subject listings — as exactly that tag, although no
set_tagever wrote it. - It can never be removed by the normal path:
del_tag(tags.rs:851)
deletes the exact 3-field encodingsencode_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 ownTupleEncoder): tupledecodereturnsOkwith 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_keyanddecode_omega_keyrefuse any key whose
decoded field count is not exactly 3, with the existing
"expected 3" wording (change the guards tofields.len() != 3). - Unit tests: a 4-field α key and a 4-field Ω key (built with
TupleEncoderfrom 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
developing-today commentedon Oct 28, 2026
CODE-761
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
2 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
Merge pull request #4046 from developing-today/queue/5046-1832-tag-ke…
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done