Repository navigation
Description
Problem
The shared key=value plain-text renderer for id group, id doc and id claim — text() in pkgs/id/src/commands/group.rs:64-72, used by tokens() (group.rs:75) and reached from doc.rs print_action/print_rows (doc.rs:40,51) and claim.rs print_set/claim_fields (claim.rs:26,36) — passes Unicode format characters (category Cf) raw to the terminal in both of its paths:
Value::String(string) if string.chars().any(|c| c.is_whitespace() || c.is_control()) => {
Value::String(string.clone()).to_string()
}
Value::String(string) => string.clone(),- Unquoted path: the quoting trigger is
is_whitespace() || is_control().char::is_control()is category Cc only andis_whitespace()is the White_Space property; every Cf character — U+202A–U+202E (bidi embedding/override, incl. RIGHT-TO-LEFT OVERRIDE), U+2066–U+2069 (isolates), U+200E/U+200F (LRM/RLM), U+200B, U+FEFF, U+00AD — is neither, so a stored claim name/value carrying only an override is returned byte-identical. - Quoted path: even when quoting does trigger (e.g. the value also contains a space), the quoted form is a serde_json string literal, which escapes only control characters,
"and\— the Cf character is emitted raw inside the quotes, where it still acts on terminal rendering.
A RIGHT-TO-LEFT OVERRIDE in a claim value therefore visually reorders the rest of the row on a terminal: the stored name/value sit before the row's real trailing author= and event= fields (the exact field order the #1611 fix protected against textual forgery), so the spoof the renderer exists to prevent can still be done visually. Zero-width/format characters (U+200B, U+FEFF, U+00AD) likewise reach the terminal invisibly, so two rows that look identical can carry different bytes.
How the values get there
Claim names/values are signed tag events that sync between nodes. Ingest validation (check_text, pkgs/id/src/tag_claims.rs:145-153) checks only byte length and NFC; Cf characters are NFC-stable, so a claim authored by another node with a U+202E in its name or value passes check_op, is stored, and is printed by id claim list / id claim search (and the same renderer serves id doc rows and id group output).
Evidence (verified this run, main 201e0c35)
- Source read: group.rs:64-72 (trigger + both paths), group.rs:75 (
tokens), claim.rs:26/36 (stored name/value fields placed beforeauthor=/event=), doc.rs:40/51 (shared use), tag_claims.rs:145-153 (length + NFC only). - Laptop Rust probe (
~/git/fleet/probe-2026-10-31-laneA-group-text-cf/, rustc, replicating the exact predicate): for U+202E, U+202A, U+2066, U+200B, U+200E, U+FEFF and U+00AD —is_control=false is_whitespace=false quoted_trigger=falsein every case. - Python
unicodedatacross-check: all seven are category Cf and not White_Space.
Distinct from existing issues
- id(peers): plain_field passes bidi/format controls raw — a gossip peer name with U+202E reaches the terminal unescaped and visually reorders the row's columns #2839 covers the other renderer,
plain_fieldinpkgs/id/src/commands/text.rs(TSV rows inid peers/ REPL). Its fix extendsbreaks_rowthere; it does not touchgroup::text, whose trigger, quoting rule and consumers are different code. - id(claim/doc/group): plain-text output renders names/values raw — a newline in a tag name or value forges extra rows and duplicate author=/event= fields #1611 (closed) fixed this same renderer for whitespace/control (newline forgery); Cf was outside its trigger set, as it was for the petname fix in id(petname): format characters (Cf) bypass both petname guards — U+200B makes a spoof of another contact's name, U+202E passes the 'no control characters' rule #1674 (closed).
- id(directory): format characters (Cf) bypass clean_name/clean_description — U+202E and U+200B pass the 'no control characters' rule for account and group names #1778 is ingest-side validation for directory account/group names — a different store, and this renderer's ruling (see text.rs module doc) is that rendering must be safe regardless of what ingest allowed, because a store may already hold the value.
Acceptance criteria
- A claim name or value containing U+202E (alone, and combined with a space so the quoted path is taken) renders with the Cf character escaped (e.g.
\u202e) inid claim list/searchplain output — it never reaches the terminal raw; same for the other Cf characters named above. - Regression tests in the group.rs renderer tests (alongside
test_newline_in_value_stays_one_lineetc.) cover: Cf-only value (unquoted path) and Cf-plus-space value (quoted path). - Values with no structure-breaking or format characters still render byte-identically to before (existing tests, incl.
test_space_free_values_render_byte_identicallyandtest_json_output_is_unchanged, stay green);--jsonoutput is unchanged. - The fix is in the shared renderer (
text/tokens), soid group,id docandid claimall inherit it — no per-command copies.
Activity
dezren39 commentedon Oct 31, 2026
🤖 Agent note · filed
group::text) passes Cf bidi/format characters raw in both paths; verified on main 201e0c3 by source read (group.rs:64-72, claim.rs:26/36, doc.rs:40/51, tag_claims.rs:145-153) plus a laptop Rust probe replicating the exact predicate (all seven Cf chars: is_control=false, is_whitespace=false). Enqueued as a triage packet in queue repo-health-code-pilot.dezren39 commentedon Nov 1, 2026
Adopted as Phase 2 of design #3455 (a value cannot act on the terminal — one format-character contract for pkgs/id plain output; design doc
files/designs/code-id-plain-output-rendering.mdin the keep-the-four-repos-healthy goal workspace). Triage: do NOT re-file, re-scope or split this issue — record the phase structure instead.Gating: this phase is gated on Phase 1 (#2839) for the shared Cf set definition only — hence the
status/blockedlabel added with this note. The implementation work itself (trigger + escaping ingroup::text) is otherwise independent.Decisions recorded on #3455 that bind this phase:
petname.rs's Unicode 16is_format_charset, promotedpub(crate)— no local subset.\uXXXXitself: serde_json'sto_string()emits Cf raw, so widening the trigger alone leaves the quoted path broken (this issue's own evidence). Keep serde_json's existing escapes for Cc/"/\byte-identical — the existing group.rs test corpus is the regression net.dezren39 commentedon Nov 1, 2026
🤖 Agent note · triage verdict: CONFIRMED (queue task 4531, lane E)
Re-verified independently against current main
f82b20b58(filing read was 201e0c3; substance unchanged):pkgs/id/src/commands/group.rs:66—text()still triggers the quoted path onis_whitespace() || is_control()only (Cc/White_Space); a grep of the whole file finds no Cf / U+202E handling at all. U+202E alone renders raw and unquoted; with a space it takes the serde_json-quoted path, which does not escape Cf either.pkgs/id/src/tag_claims.rscheck_text— byte length + NFC only; Cf is NFC-stable, so a remote-authored claim value carrying a bidi override passes ingest and reaches the terminal, where it can visually reorder the row's realauthor=/event=fields.Phase structure (design #3455): this is Phase 2; the
status/blockedlabel stays — per the design-architect adoption note it records the gate on Phase 1 (#2839) for the shared Cf set definition only.Routing: fix task 5635 enqueued (dedupe
repo-health:v1:fix:developing-today/code:issue:3111): implement Cf escaping in the shared renderer using the Phase 1 shared Cf definition when landed (park naming the dependency if not), with regression tests for the Cf-only and Cf-plus-space paths. Its closure, with #2839, unblocks the gated Phase 3b audit (task 5380).93 remaining items
dezren39 commentedon Nov 2, 2026
agent-note:v1 — parked
Agent: repo-worker-code (OpenChamber scheduled lane)
Task: repo-health-code-pilot task 5635
At: 2026-11-02T08:44:20Z
State: parked
Fresh evidence: #3111 is OPEN; dependency #2839 is OPEN; its implementation PR #4264 is OPEN and authored by dezren39. The design gates Phase 2 on #2839 landing. All-state search found no active fix PR for #3111.
Disposition: parked 30 minutes on the unmet Phase 1 dependency. No PR content, branch, worktree, checks, reviews, or source inspected or touched. Labels reflect parked state; work/assigned remains set.
Resume condition: #2839 closes/merges; revalidate #3111 and dependency state fresh. Do not inspect/touch Drew-owned PR #4264 or its branch/worktree/checks/reviews.
Links: #3111 · #2839 · #4264 · task 5635
dezren39 commentedon Nov 2, 2026
agent-note:v1 — task 5635 parked (lane A, 2026-11-02)
Fresh gate check: Phase 1 issue #2839 is still OPEN and its PR #4264 is still OPEN @ 90782ce. Per this task's packet, Phase 2 (#3111) consumes the shared Cf set definition from Phase 1, and the task parks naming that dependency until #2839 lands. Issue #3111 OPEN, no hard-stop labels. Parked task 5635 for 1h; resume when #2839 is closed / PR #4264 merged, then implement the group.rs renderer Cf escaping with the shared definition.
dezren39 commentedon Nov 2, 2026
🤖 Agent note · parked
dezren39 commentedon Nov 2, 2026
agent: repo-worker-code (lane E)
state: parked
task: 5635
issue: 3111
Lane E claimed task 5635 (Phase 2 of #3455) and revalidated the Phase 1 gate fresh at 2026-11-02T11:24Z: #2839 is still OPEN and its PR #4264 is still OPEN at 90782ce (branch queue/5439-2839-plain-field-unicode-cf); #2839 currently carries queue/claimed + status/in-progress, so Phase 1 is actively held elsewhere. Per the packet, Phase 2 does not start before the shared Cf set definition lands. Parking task 5635; resume after #2839 closes / PR #4264 merges.
dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
Merge pull request #4433 from developing-today/queue/5635-3111-group-…
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done
crate::is_format_charset from issue id(peers): plain_field passes bidi/format controls raw — a gossip peer name with U+202E reaches the terminal unescaped and visually reorders the row's columns #2839) and escapes Cf as\uXXXXinside the literal;id group,id docandid claimplain outputs all flow through this renderer (doc/claim viatokens/print_rows); plain values byte-identical;--jsonunchangedcargo test --lib— commands::group::tests 10 passed including 3 new Cf regression tests (Cf-only, Cf-in-name, and every acceptance-named Cf char alone and with a space), commands::doc::tests 3 passed, commands::claim::tests 10 passed, doc-id integration 33 passed. Codex review on PR fix(id): escape Cf format chars in the shared key=value renderer (#3111) #4433 completed with no findings. Note:cargo test --features web --libcannot build in a fresh worktree (pkgs/id/web/dist missing; errors only in src/web/*) — pre-existing, unrelated to this change