feat(data): surface jobTag in data search (swamp-club#245)#1304
Conversation
…ch (swamp-club#245) Follow-up to swamp-club#237. After #237, workflow step output carries `data.tags.job` (populated via `workflowTagOverrides`), but the legacy `swamp data search` result mapping at src/libswamp/data/search.ts:305-306 exposed only `workflowTag` and `stepTag` — leaving the provenance triple incomplete on this surface compared to `swamp data query` (which already supports `jobName == "..."`). This change fills the gap with three small, additive edits: - DataSearchItem gains `jobTag?: string`, ordered between workflowTag and stepTag to mirror the production tag-write order in execution_service.ts (workflow → job → step). - The dataSearch generator populates `jobTag: data.tags.job`. Pre-#237 data has no `job` tag, so jobTag becomes `undefined` — same nullable handling already used for workflowTag/stepTag. - The Ink renderer surfaces jobTag both as a searchable picker token and as a `**Job:**` line in the preview metadata, between Workflow and Step. Two regression tests cover the wiring: a positive case asserting workflow/job/step tags are populated together (proves order parity), and a negative case locking in the `jobTag === undefined` contract when the source data has no `job` tag. No `--job` filter added — `data search` already has asymmetric filter coverage (`--workflow` exists, no `--step`); adding `--job` would introduce new asymmetry without solving a real ergonomic gap. Power users should use `swamp data query 'jobName == "..."'`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
- JSON output — undefined fields:
JSON.stringifysilently dropsjobTagwhenundefined, so scripts consuming--jsonoutput can't distinguish "this record has no job tag" from "this version of swamp doesn't know about jobTag" without reading bothjobTagandtags.job. The existingworkflowTag/stepTagfields have the same ambiguity, so this isn't a regression — just worth noting if the JSON contract is ever tightened.
Verdict
PASS — focused, consistent addition. jobTag is wired into the fuzzy-search token string, the preview metadata pane (in the correct workflow→job→step order), and flows through the existing JSON serialization path automatically. No new flags, no help-text changes needed.
There was a problem hiding this comment.
Code Review
Clean, well-scoped additive change. No blocking issues.
CLAUDE.md Compliance
- Import boundary: Renderer imports
DataSearchItemfrom../../libswamp/mod.ts— correct. Test file imports from internal paths but lives insidesrc/libswamp/, which is permitted. - Test naming: Follows the
"functionName: describes behavior"convention. - License headers: No new files created; existing files already have headers.
- No
anytypes: All additions are properly typed (jobTag?: string).
DDD Review
DataSearchItemis a Value Object (search result projection) — addingjobTagis consistent with the existingworkflowTag/stepTagpattern and mirrors the domain's provenance triple (workflow → job → step).- The generator mapping at
search.ts:307is a straightforward projection from the domain'sdata.tags.job, same pattern as the two siblings. - No domain model changes needed —
jobTagis already persisted indata.tagsby the execution service; this PR just surfaces it.
Test Coverage
- Positive test: verifies all three tag fields are populated together from
data.tags. - Negative test: locks in the
jobTag === undefinedcontract whendata.tagshas nojobkey. - Both tests follow existing patterns and use the established
makeDataItem/makeDepshelpers.
Security
- No user input flows, no injection surfaces. The
jobTagvalue comes from internaldata.tagsand is rendered via Ink's text components (no raw HTML).
Suggestions
- The positive test could also assert that
workflowTagandstepTagareundefinedin the negative test (alongsidejobTag) to fully lock in the nullable contract for all three fields — but the existing tests elsewhere likely cover that, so this is minor.
Looks good to merge.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None.
Medium
None.
Low
src/libswamp/data/search.ts:307—Record<string, string>index access typed asstring, notstring | undefined:data.tags.jobis typed asstringby TypeScript'sRecord<string, string>, but at runtime it'sundefinedwhen the key is missing. WithnoUncheckedIndexedAccessenabled this would be flagged. However, this is the pre-existing pattern used identically byworkflowTag: data.tags.workflowandstepTag: data.tags.stepat lines 306/308, and the receiving field is correctly declared optional (jobTag?: string), so all consumers (nullish coalescing??, truthiness guardif (item.jobTag)) handleundefinedproperly. Not a regression from this PR.
Verdict
PASS — Small, additive change that mirrors the existing workflowTag/stepTag pattern exactly. The interface extension is backward-compatible (optional field), the Ink renderer correctly guards against undefined, the JSON renderer needs no changes (serializes e.data directly), and the two regression tests cover both the populated and absent tag cases. No new error paths, no security concerns, no concurrency issues.
Summary
Follow-up to swamp-club#237. The legacy
swamp data searchsurface exposedworkflowTagandstepTagfromdata.tags, but notjobTag— leaving the provenance triple incomplete compared toswamp data query(which already supportsjobName == \"...\"after #237 wireddata.tags.jobviaworkflowTagOverrides).This PR closes the gap:
DataSearchItemgainsjobTag?: string, ordered betweenworkflowTagandstepTagto mirror the production tag-write order inexecution_service.ts(workflow → job → step).dataSearchgenerator populatesjobTag: data.tags.jobin the result mapping.jobTagas a searchable picker token and as a**Job:**line in the preview metadata, between Workflow and Step.Closes swamp-club#245.
Why no
--jobfilterdata searchalready has asymmetric filter coverage —--workflowexists, but--stepdoes not. Adding--jobwould introduce new asymmetry without solving a real ergonomic gap. Power users wanting filter-by-job should useswamp data query 'jobName == \"...\"', which is the documented modern path.Backward compatibility
Pre-#237 data has no
jobtag, sojobTagresolves toundefinedfor those records — identical nullable handling toworkflowTag/stepTagon non-workflow data. No migration needed; forward-only.Test plan
deno fmt --checkpassesdeno lintpassesdeno checkpassesdeno run test— full suite (5403 passed, 0 failed)data.tags = { workflow, job, step }, all three*Tagfields are populated togetherdata.tags = {},jobTag === undefined(locks in the nullable contract)Follow-up
A consolidated swamp-uat issue will be filed alongside this PR covering CLI surfacing of
workflowTag/jobTag/stepTagindata search --json(none of the three are exercised at the UAT tier today).🤖 Generated with Claude Code