Repository navigation
Description
Summary
GET /api/tags silently discards the value query parameter in two of its dispatch combinations, returning unfiltered results with a 200 as if the filter had been applied:
GET /api/tags?value=rust(value, no key, no subject) returns every tag in the namespace — the request is indistinguishable fromGET /api/tagswith no filters at all.GET /api/tags?subject=a.md&value=rust(subject + value, no key) returns all of the subject's tags, including values other thanrust.
TagQuery::value is documented as "Filter by tag value (requires key)" (pkgs/id/src/web/tags_ws.rs:125), but nothing enforces the requirement and nothing applies the filter in these arms — the parameter is accepted and dropped.
Evidence (verified this run on main 63ba1705)
pkgs/id/src/web/tags_ws.rs:275-307—get_tags_handlerdispatches on(subject, key, value):(Some(subject), None, _)at:294callsget_tags(subject)— the_discardsvalue.(None, None, _)at:304callslist_all(ns)— the_discardsvalue.- The combinations that do honour
valueare(Some, Some, value)(:280-293, post-filter) and(None, Some(key), Some(value))(:296-300,find_by_key_value).
- Probe (verbatim dispatch arms copied from the handler, stub store recording which query ran; run on the laptop,
tags_ws.rssha256645c8e20identical both sides):- CTRL
key=label&value=rust→find_by_key_value, 2 tags (bothrust). - CTRL
subject=a.md&key=label&value=rust→get_by_key+ filter, 1 tag. - CTRL
subject=a.md→ 2 tags; CTRL no filters → 4 tags. - BUG1
value=rustalone → armlist_all, 4 tags — includinglabel=pythonand a value-less tag; byte-for-byte the no-filter result. - BUG2
subject=a.md&value=rust→ armget_tags(subject), 2 tags —rustandpython; thepythontag proves the filter never ran.
- CTRL
- No test pins either combination: the handler has no unit tests for the
value-without-keyshapes, and the web UI only ever calls?subject=(pkgs/id/web/src/main.ts:909), so this is an API-surface defect, not a UI one.
Why it matters
A caller that passes value without key — an easy mistake, since the parameter is accepted with a 200 — gets a superset of what it asked for and no signal that the filter was ignored. For a value-only caller that superset is the entire namespace. This is the silent-wrong-answer shape: the response looks like a successful filtered query, so downstream code (scripts, the REPL, future UI) will act on tags it never asked for. Every other loosely-formed input on this surface is refused (request_value rejects value+value_b64 together, resolve_ns 400s an unknown namespace) — value without key is the loose one.
Acceptance criteria
-
GET /api/tags?value=...withoutkeynever returns unfiltered results: either a 400 naming the missingkey(matching the documented "requireskey"), or a real value search — one behaviour, pinned by tests. -
GET /api/tags?subject=...&value=...withoutkeygets the same treatment (400, or the subject's tags filtered by value). - Handler-level tests pin both shapes plus the existing controls:
key+value,subject+key+value,subjectonly, and no filters all keep their current results. - No behaviour change for well-formed callers (the web UI's
?subject=calls included).
Dedup
Searched open + closed issues for tags value filter, api/tags value, and related terms: no filing covers the tags REST dispatch. Adjacent but distinct: #1547 (projector read-API limit validation — different repo component and parameter), #1208 (Project pool truncation — names fetchProjectPool, not this handler).
Activity
developing-today commentedon Oct 28, 2026
CODE-632
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
valuefilter inGET /api/tags(pkgs/id/src/web/tags_ws.rs get_tags_handler dispatch): pin one behaviour for value-without-key (400 naming the missing key, or a real value search) and add handler tests.dezren39 commentedon Oct 28, 2026
🤖 Agent note · picked up
valuefilter in GET /api/tags (pkgs/id/src/web/tags_ws.rs): verify against current main, pin one behaviour for value-without-key, route the fix.dezren39 commentedon Oct 28, 2026
🤖 Agent note · triage verdict — CONFIRMED, behaviour pinned: 400 (fix = queue task 1848)
Verified fresh this run:
pkgs/id/src/web/tags_ws.rson current main is byte-identical to the creator probe file (sha256645c8e2099ce3235366880a7da8a1e9fa7c39178c49a465b9c326eea3d8db6fc), so the probe results hold on current main: value-only returns the whole namespace, subject+value returns all the subject's tags. Code read confirms the mechanism — the(Some(subject), None, _)and(None, None, _)dispatch arms discardvaluevia the wildcard.Decision — 400, not a value search. Three grounds: (1)
TagQuery::valueis already documented "Filter by tag value (requireskey)" (:125) — the contract exists, it is just unenforced; (2) this handler's existing refusal style for malformed input is 400 +ApiResponse { ok: false, message }(seebad_ns, :71-80, for bad namespaces) — the fix should reuse that shape with a message naming the missingkey; (3) value-only search is not missing functionality: the dedicated surfaceGET /api/tags/searchalready supports:valuequeries (SearchQuery doc, ~:131). Building a second value search into the list endpoint would duplicate it.Routing: fix enqueued as queue task 1848 (small_fix, dedupe
repo-health:v1:fix:developing-today/code:issue:1607): 400 in both affected arms + handler tests pinning both 400 shapes and all controls (key+value, subject+key+value, subject-only, key-only, no-filter — the web UI's?subject=calls included). No PR exists for #1607 (checked this run).dezren39 commentedon Oct 28, 2026
🤖 Dispatcher binding · openchamber-dispatcher run 2026-10-28 13:45 CDT (burn-by-expiry run: Drew directive 13:44 CDT — fill free session slots, burn the Go window resetting 2:26 PM and the Meta window resetting 4:23 PM)
small_fix) ↔ issue id(web): GET /api/tags silently drops the value filter without a key — ?value= returns the whole namespace, ?subject=&value= returns all the subject's tags #1607ses_e7c23ddd7ffeOY25pzGaqZnkAV(laptop OpenChamber, agent build)meta/muse-spark-1.3-contributor(Meta pool; fresh quota read this run ~13:47 CDT: 5h 1% / weekly 11%, reset 4:23 PM — OPEN, plan quota only, never PAYG; seated on Meta per the run's burn-by-expiry directive)~/git/fleet/1848-1607-tags-value-without-keyon the laptop, branchqueue/1848-1607-tags-value-without-key(from origin/main 35726e8)pkgs/id/src/web/tags_ws.rsdispatch arms and are applying the 400 enforcement with pinning tests — the id(web): GET /api/tags silently drops the value filter without a key — ?value= returns the whole namespace, ?subject=&value= returns all the subject's tags #1607 fix surface. The queue task itself remainsqueuedin the fleet queue (no targeted claim/binding verb exists yet — Dispatcher v1 live: scheduled launches of OpenCode/OpenChamber sessions for queued issues #1337/Routing policy alignment: map OpenChamber routing categories to the fleet's Workhorse-first tiers + POOL MAP gates #1272); the owning lane reconciles/harvests from here, and the babysitter shepherds the session.dezren39 commentedon Oct 28, 2026
Queue task 1848 checkpoint — fix implemented and PR opened.
What changed
pkgs/id/src/web/tags_ws.rsget_tags_handler: both value-without-key dispatch arms ((Some(subject), None, Some(value))and(None, None, Some(value))) now return 400 with anApiResponse{ok:false}message naming the missingkeyparameter (bad_ns-style), pointing at/api/tags/search?q=:valuefor value-only search. Previously they silently droppedvalueand returned unfiltered 200s.Test results
cargo test --lib --features web web::tags_ws(inpkgs/id, laptop): 5 passed, 0 failed. Note: thewebfeature requiresweb/dist(built via./scripts/build.sh assets debug); plain--libwithout the feature compiles 0 tests for this module.cargo fmt --check src/web/tags_ws.rs: clean.PR
dezren39 commentedon Oct 28, 2026
Fix ready for review: #1786 (branch
queue/1848-1607-tags-value-without-key, commit 56a94fb — NOT merged).What changed (
pkgs/id/src/web/tags_ws.rsonly, +147):get_tags_handler: new first dispatch arm — whenvalueis present andkeyis absent, in both the(subject, no key)and(no subject, no key)combinations, return 400 with anApiResponse{ok:false}message naming the missingkeyparameter and pointing at/api/tags/search?q=:value(bad_ns-style shape). The wildcard arms that silently discardedvalueare now unreachable for that case.find_by_key_value, key-only, subject-only, no-filter, POST/DELETE, search handler.Test results (
cargo test --lib --features web web::tags_wsinpkgs/id, laptop):key), subject+value (no key) -> 400 (message nameskey), plus controls key+value (2 tags), subject+key+value (1 exact tag), subject-only (2 tags — the web UI?subject=shape), key-only (3 tags), no-filter (3 tags).cargo fmt --check src/web/tags_ws.rs: clean.cargo test --lib web::tags_wsmatches 0 tests — the web module needs--features web(plus a prior./scripts/build.sh assets debugforweb/dist); CI's--all-featurescovers this.dezren39 commentedon Oct 28, 2026
Queue task 1848 checkpoint — fix implemented and PR opened.
What changed
pkgs/id/src/web/tags_ws.rsget_tags_handler: both value-without-key dispatch arms ((Some(subject), None, Some(value))and(None, None, Some(value))) now return 400 with anApiResponse{ok:false}message naming the missingkeyparameter (bad_ns-style), pointing at/api/tags/search?q=:valuefor value-only search. Previously they silently droppedvalueand returned unfiltered 200s.Test results
cargo test --lib --features web web::tags_ws(inpkgs/id, laptop): 5 passed, 0 failed. Note: thewebfeature requiresweb/dist(built via./scripts/build.sh assets debug); plain--libwithout the feature compiles 0 tests for this module.cargo fmt --check src/web/tags_ws.rs: clean.PR
dezren39 commentedon Oct 29, 2026
🤖 Agent note · bound
queue/1848-1607-tags-value-without-keynames the task), but the pickup never landed in the queue — task 1848 still sitsqueued, attempts 0, never claimed. The queued task is therefore to be treated as PARKED on this PR: resume as the PR's shepherd — review CI, merge when green, do NOT re-implement. Queue-side claim/park by the monitor is pending the by-id claim/bind verb (Dispatcher v1 live: scheduled launches of OpenCode/OpenChamber sessions for queued issues #1337); until it lands, this note is the binding record a lane's revalidation will find.