Repository navigation
Description
Problem
validate_packet in the fleet-queue crate type-checks some packet fields but not the packet's string fields or string-list elements. A packet whose top-level body is a number/object/array/bool, whose payload problem or priority_reason is a number/object/array, whose evidence / acceptance / tests / context_refs lists contain numbers, nulls, objects, bools, or nested arrays, whose scope.include / scope.exclude / risk.flags lists contain non-strings, or whose dedupe.checked_at / dedupe.basis are numbers validates with zero errors and zero warnings — and task_from_packet forwards every one of those values verbatim into the agent.tasks row the broker inserts on /enqueue. The queue worker and every packet consumer downstream then read strings that are not strings.
This is the same shape-vs-type gap #5549 fixed for packet identity (placeholder prs elements like [null], [{"number": "5424"}], [{}] used to pass on the array being non-empty alone). The identity fields got element-type checks; the prose fields never did.
Evidence (all at origin/main a2b4345e80; validate.rs md5-identical in the laptop primary, origin, and the probe copy)
pkgs/fleet/crates/fleet-queue/src/validate.rs:38-46—is_empty()returnsfalsefor any number/bool value, so a non-string can never be "empty".validate.rs:72-76— payloadproblem/priority_reasonare checked only withis_empty(...):problem: 123andproblem: {"x": 1}pass.validate.rs:77-88— payloadevidence/acceptance/tests/context_refsare checked only for being a list (and, for the first three, non-empty). Element types are never inspected:evidence: [123],[null],[{"x": 1}],tests: [["t"]]all pass.validate.rs:91-94and:101-103—scope.includeandrisk.flagsget the same list-only check;scope.excludegets only theis_emptycheck:[123]passes everywhere.validate.rs:115-116—dedupe.checked_at/dedupe.basisare checked only with!is_empty(...): the number123passes for both.validate.rs:467-474— top-levelbodyis read onlyif let Some(Value::String(body)), for the >250-word warning. A non-stringbodyskips even that; an empty-stringbodypasses silently too (contrasttitleatvalidate.rs:~455, which is required to be a non-empty string — the probe'stitle: 123control correctly errors).validate.rs:521—task_from_packetcopiespacket.bodyverbatim into the task row; the whole payload (with the malformed fields above) is copied verbatim at the same time.pkgs/fleet/crates/fleet-queue/src/queue_broker.rs:327-347— the broker's/validateand/enqueueboth run this samevalidate_packet, and/enqueueinsertstask_from_packet's output. There is no second, stricter layer for non-CLI callers; the tasks table'spayloadis jsonb, so the database accepts the malformed values as-is.
Verified this run (laptop probe, real crate)
Probe ~/git/fleet/probe-queue-body on the laptop, built against the real fleet-queue crate (path dependency, queue-broker feature), calling validate_packet + task_from_packet on an otherwise-valid packet (the same base packet shape the #6426 probe used):
- Control (valid packet):
errors=[] warnings=[], fields forwarded as strings. - Controls that must fail, and do:
title: 123→["packet.title: required non-empty string"];evidence: []→["payload.evidence: must not be empty"]. - All 19 malformed cases returned
errors=[] warnings=[]and were forwarded verbatim in the task row:body=12345/{"x":1}/["a"]/true/"";problem=123/{"x":1};priority_reason=["r"];evidence=[123]/[null]/[{"x":1}];acceptance=[false];tests=[["t"]];context_refs=[42];scope.include=[123];scope.exclude=[123];risk.flags=[123];dedupe.checked_at=123;dedupe.basis=123.
Why it matters
The packet is the queue's contract: producers are scripts and agents, and a producer bug that emits evidence: [null] or problem: 123 (a JSON-serialization slip, a templating bug) currently enqueues a task that looks valid in every listing — the worker only discovers the corruption when it tries to read the evidence/acceptance strings mid-task, if it notices at all. The validator already type-checks title, priority, dedupe_key, issue.number, and (since #5549) every prs element, precisely so malformed packets die at the producer with an error naming the field. The prose fields are the unguarded remainder of the same contract.
Acceptance criteria
validate_packetrejects a top-levelbodythat is present and not a string, with an error namingpacket.body; string bodies keep the existing word-count warning behavior.validate_packetrejects non-stringpayload.problem/payload.priority_reasonand non-stringpayload.dedupe.checked_at/payload.dedupe.basis, with errors naming the field.validate_packetrejects any non-string element inpayload.evidence,payload.acceptance,payload.tests,payload.context_refs,payload.scope.include,payload.scope.exclude, andpayload.risk.flags, with errors naming the field (index included where the codebase's message style allows).- Every real packet in the fleet corpus still validates (the fleet-queue(validate): payload identity accepts placeholders — prs:[null], string/absent PR numbers, and set:{} validate with no usable identity #5549 fix verified the same property for identity shapes: 87/87 real
prselements already conformed — the fix should re-run that corpus check for the newly typed fields). - Tests pin each malformed case above plus the valid control, in the style of the existing
validate.rstests (placeholder_identities_do_not_validate).
Dedup / relations
- Distinct from fleet(queue): packet not_before is never validated — garbage and far-future values are forwarded verbatim, and a far-future task is born unclaimable #6426 (top-level
not_beforevalue validation), fleet(queue): packet top-level max_attempts is never validated but overrides the validated payload value — a task can be born unclaimable (max_attempts <= 0) #6423 (top-levelmax_attemptsoverride), and fleet(queue): repo-queue duration flags (--lease-seconds, --extend-seconds, --delay-seconds) accept 0, negative, and unbounded values — a 0/negative lease is born expired, a negative park delay defeats the park #6420 (client duration flags): none of them touches field/element types inmanual_schema_errorsor thebodyread. - Precedent, not a duplicate: fleet-queue(validate): payload identity accepts placeholders — prs:[null], string/absent PR numbers, and set:{} validate with no usable identity #5549 (closed) applied exactly this fix class to identity fields only; its rationale — a placeholder that passes shape checks becomes a second, broken identity for the work — applies here to the prose contract.
- Scope is the Rust validator + broker path only (they share
validate_packet); no SQL or RPC changes are needed.
Activity
developing-today commentedon Nov 6, 2026
CODE-1927
dezren39 commentedon Nov 6, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 6, 2026
Lane B (code-b) claimed task 10881 for issue #6430. Execution-lane assignment returned
opencode(§1b), so this task is handed off to the opencode claimant — checkpointedhanded_offand parked 1200s for the claimant to launch the bound session. Issue verified OPEN with no hard-stop labels (held / user/owned / do-not-merge / do-not-auto-edit).36 remaining items
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off