Repository navigation
Description
fleet(queue): packet not_before is never validated — garbage and far-future values are forwarded verbatim, and a far-future task is born unclaimable
Evidence (verified this run, origin/main a2b4345e80927cb1d8ac652756cb219b971bf635)
The top-level packet field not_before is the one scheduling field validate_packet never looks at:
- Validator —
pkgs/fleet/crates/fleet-queue/src/validate.rs:validate_packet(:433) checks the top-leveltitle(non-empty),priority(design scale),body(word-count warning), and every payload field — but contains no reference tonot_beforeat all.task_from_packet(:510) forwards it verbatim at :525:"not_before": packet.get("not_before").cloned().unwrap_or(Value::Null). - Broker —
pkgs/fleet/crates/fleet-queue/src/queue_broker.rs:/enqueue(:371) copies the task'snot_beforeinto the RPC argumentp_not_beforeunchecked. The/parkpath (:439-440) does the same for the park verb'snot_before. - Client park flag —
pkgs/fleet/crates/fleet-queue/src/client.rs:cmd_park(:1053) puts the raw--not-beforestring into the park body with no parse or range check. - SQL —
pm-stack/opencode-claimant/migration-0023.sql: the claim predicate (:262) admits a queued task only whennot_before is null or not_before <= now();park_taskassignsv_not_before := p_not_beforeverbatim (:161-162) with no sanity bound.
Verified on the laptop with a probe crate against the real fleet-queue crate from origin/main (probe ~/git/fleet/probe-queue-notbefore, calling the real validate_packet + task_from_packet):
packet not_before |
validate errors | forwarded task not_before |
|---|---|---|
| absent (control) | [] |
null |
"2026-11-06T12:00:00Z" (control) |
[] |
"2026-11-06T12:00:00Z" |
"9999-01-01T00:00:00Z" |
[] |
"9999-01-01T00:00:00Z" |
"banana" |
[] |
"banana" |
"" |
[] |
"" |
12345 (number) |
[] |
12345 |
{"x": 1} (object) |
[] |
{"x": 1} |
true (bool) |
[] |
true |
"2000-01-01T00:00:00Z" (past) |
[] |
"2000-01-01T00:00:00Z" |
Consequences, by value:
- Far-future valid timestamp (e.g.
9999-01-01, or a year typo like2206-…) → the task enqueues successfully and then satisfies the claim predicate never (or not for years): it sits inqueuedcounts forever while every claim returns null — a silently dead task of exactly the shape lane shifts keep reporting ("claim returned null despite queued=144+"). Sibling of 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's born-unclaimablemax_attempts <= 0, through a different field. - Garbage string / empty string / non-string types → no validation error names the field; the value reaches the
timestamptzRPC parameter and fails (if it fails) as a downstream database cast error, detached from the packet field that caused it — or, for a string Postgres happens to parse (date-only forms, offset-less forms interpreted in the database timezone), is stored with a meaning the producer did not intend. - Park
--not-beforehas the same gap on an already-claimed task: a typo'd far-future value parks the task effectively forever with a normal-looking success response.
Relation to existing issues
- 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 covers top-level
max_attempts(never validated, overrides the validated payload value). This is the same validator's other unexamined top-level field, with a different failure mode (scheduling instead of attempt counting). - 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 covers the duration flags (
--lease-seconds/--extend-seconds/--delay-seconds); the park--not-beforetimestamp flag was not in its scope.
Why it matters
Every producer (creators, pr-diff-review, curators, sweeps) enqueues through this validator, and not_before is the field producers use to defer work. A single typo'd year or a script emitting epoch numbers instead of timestamps produces either a task that never runs while looking perfectly queued, or an opaque database error at enqueue time. A validation error naming not_before at the packet boundary — plus the same parse check on the park flag — closes the class at the one place all producers pass through.
Acceptance criteria
-
validate_packetrejects a top-levelnot_beforethat is not a string, is empty, or does not parse as an RFC 3339 timestamp, with an error namingpacket.not_before. - A parsed
not_beforebeyond a documented sane horizon (proposed: 366 days from enqueue) is rejected or warned on explicitly — the bound and error-vs-warning choice are triage's call, but a year-9999 value must not silently enqueue as a normal queued task. -
repo-queue park --not-before <garbage>is rejected with a usage error naming--not-before(exit 2), consistent with the fleet(queue): repo-queue client parser silently overwrites duplicate flags, accepts --dry-run=false as true, and silently defaults a non-numeric --limit #5939 non-numeric rejection; valid timestamps park exactly as before. - The broker applies the same check on
/enqueueand/park, so non-CLI callers get the same guard. - Tests pin: absent (null), a near-future timestamp, a past timestamp (accepted — immediate eligibility is legitimate), garbage, empty string, number, object, and the horizon boundary.
Activity
developing-today commentedon Nov 6, 2026
CODE-1925
dezren39 commentedon Nov 6, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 6, 2026
🤖 Agent note · picked up
not_beforevalidation gap against current main and route the fix.15 remaining items
dezren39 commentedon Nov 6, 2026
agent-note:v1 — Agent: repo-design-architect-code (pass 59); state: adopted as Phase 1 of design #6448 ("Validate what you forward"); no parallel issue filed; this issue's fleet state is unchanged by the adoption.
Phase 1 = this issue + #6423 (packet scalars). Recorded pins for this phase (design #6448, decisions 1/2/5): the horizon is now+366 days as this issue proposes, and the disposition is REJECT, not warn — the warn option is settled against. The park --not-before flag takes exactly the packet not_before rule (parse + horizon). Triage/holder: record the phase structure; do not re-file, re-scope or split this issue into a parallel.
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
opencodedezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
agent-note:v1
task: 10873
lane: code-q
state: parked (handed off)
execution_lane: opencode
next: opencode claimant launches the bound session for task 10873
verified: issue #6426 OPEN, no hard-stop labels (held/user/owned/do-not-merge/do-not-auto-edit absent), fresh gh check 2026-11-06
dezren39 commentedon Nov 6, 2026
Claimed by the OpenChamber scheduled lane for a fresh check. Issue #6426 is open; no matching PR found. The task progress is explicitly assigned to the opencode execution lane, so I will not implement it from this lane and am returning it to parked for the intended claimant.
dezren39 commentedon Nov 6, 2026
Task 10873 is parked for 30 minutes. Resume condition: the intended opencode claimant takes ownership, because the task progress records assigned_lane=opencode. Freshly verified #6426 is open and no matching PR exists. The issue labels now reflect the parked queue state; work/assigned remains.