You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vibed/nbd/.beans/nbd-o3k8--remove-id-field-f...

3.5 KiB

title status type priority created_at updated_at
Remove id field from ticket JSON body completed task critical 2026-03-10T23:30:31Z 2026-03-10T23:30:31Z

The ticket ID is already encoded in the filename (a3f9c2.json). Storing it redundantly inside the JSON body creates a potential consistency hazard (the two could disagree) and wastes space. The filename should be the sole source of truth for the ID.

Motivation

  • Eliminates a redundancy: filename stem already IS the id.
  • Removes the risk of id mismatch (e.g. if a file is renamed manually).
  • Simplifies the JSON schema — consumers only need the body fields, not a duplicated key.

Serde approach

In ticket.rs, annotate the id field with #[serde(skip)]:

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Ticket {
    #[serde(skip)]
    pub id: String,
    // ...
}

#[serde(skip)] means:

  • Serialise: the id field is omitted from JSON output entirely.
  • Deserialise: the field is not read from JSON; it is initialised with String::default() (empty string) and must be set manually after deserialization.

Because serde ignores unknown fields by default, existing files with "id": "..." in the JSON body continue to deserialise without error — the field is simply discarded. This means the change is backwards-compatible for reads; existing stores work immediately. A separate nbd migrate command (see companion ticket) cleans up the stale id field from disk.

store.rs changes

read_ticket(root, id) — inject id from the id parameter after deserialising:

let mut ticket: Ticket = serde_json::from_slice(&bytes)?;
ticket.id = id.to_string();   // authoritative source: the filename
Ok(ticket)

list_tickets(root) — inject id from each file's stem:

let stem = path.file_stem().and_then(|s| s.to_str()).ok_or("invalid filename")?;
let mut ticket: Ticket = serde_json::from_slice(&bytes)?;
ticket.id = stem.to_string();
tickets.push(ticket);

write_ticket — no change needed; #[serde(skip)] already prevents id from being written.

ticket_path — no change; it already takes id: &str as a separate parameter.

Impact on other tickets

  • The nbd migrate companion ticket (see deps) provides the command to scrub the old id field from existing files.
  • Partial ID matching (resolve_id) is unaffected — it works on filenames, not JSON content.
  • All display, list, and read commands continue to work; ticket.id is populated from the filename in every read path.

Tests

Unit tests (src/tests.rs):

  • write_ticket output does NOT contain the "id" key.
  • read_ticket with a file that has NO id field correctly sets ticket.id from the parameter.
  • read_ticket with an old-format file (has "id" in JSON) still sets ticket.id from the parameter (ignores JSON value).
  • list_tickets injects correct ids from filenames for all tickets.
  • Serialisation roundtrip: write then read, id is preserved via filename not JSON.

Integration tests (tests/integration.rs):

  • nbd create output (tabular and --json) contains the correct ID.
  • The created .json file on disk does NOT contain the "id" key.
  • nbd read <id> displays the correct ID.

Files touched

  • src/ticket.rs — add #[serde(skip)] to id
  • src/store.rsread_ticket and list_tickets inject id from filename
  • src/tests.rs — update and add unit tests
  • tests/integration.rs — add assertion that written files lack "id" key