T-NRTX-mask-bare-pid-columns-in-findings
Status: open/ready · Impact: medium · Complexity: small
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-08-02 because the task is autonomy: autonomous/pr. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.
The quality baseline finding-diff still false-flags a dashboard-test server
banner as new-drift, because normalizeFinding’s PID mask only fires on the
literal pid keyword. A banner line that carries the PID as a bare integer
column can never match its baseline twin, so it surfaces as branch-introduced
drift on every run regardless of what the branch touched.
The baseline finding-diff still false-flags a dashboard-test server banner as new-drift: the observed line was
/private<TMPDIR> 27439 http://127.0.0.1:<PORT>/, where the normalizer masked the tempdir and the port but not the PID, so the line can never match its baseline twin. T-BQRU-quality-normalize-ports-pids-timings (closed/done, PR #473) added a PID mask tonormalizeFindinginapps/sdlc/lib/services/quality/baseline.ts, but it is anchored on the literalpidkeyword (/\bpid(\s*[:=]?\s*)\d{1,7}\b/gi), so a bare integer column carrying the PID slips through. Extend the normalizer to mask process-ephemeral bare-integer columns in server-banner lines (or drop server-banner lines from the finding set), with unit tests covering the observed line shape and a negative case proving unrelated integers stay unmasked.
| Location | Role today |
|---|---|
apps/sdlc/lib/services/quality/baseline.ts#normalizeFinding | Masks ephemeral tokens in a finding line, in five fixed steps: tmpdir roots, bare commit SHAs, ephemeral ports, PIDs, timing tokens. Step 4 is anchored on the literal pid keyword (/\bpid(\s*[:=]?\s*)\d{1,7}\b/gi), so a PID that appears as a bare integer column with no keyword survives masking. |
apps/sdlc/lib/services/quality/baseline.ts#diff | Per-verb set difference that normalizes both the current and baseline sides before subtracting. A line whose PID survives masking can never equal its baseline twin, so it is reported as branch-introduced drift on every run. |
apps/sdlc/lib/services/dashboard/server.ts#listDashboards | Emits the offending banner shape — one row per running dashboard as `${proot} ${pid} ${url}`, where the PID is a bare padEnd(6) integer column carrying no pid keyword, next to a tmpdir-rooted project root and a http://127.0.0.1:<port>/ URL. |
apps/sdlc/lib/services/quality/tests/quality_ops.test.ts | Holds the T-BQRU normalizeFinding unit tests — positive cases for port/PID/timing, a combined multi-kind case, and the negative case found 12345 issues that pins bare integers as unmasked. |
Proposed
Section titled “Proposed”normalizeFinding gains a sixth mask that fires only on lines already
recognizable as server banners after masks 1-5 have run — that is, lines
carrying BOTH the `<TMPDIR>` sentinel AND the `<PORT>` sentinel.
On such a line, every whitespace-delimited token that is a bare 2-to-7 digit
integer collapses to `<PID>`, and runs of two or more spaces/tabs
collapse to one space so that the emitter’s column padding (which varies with
the PID’s digit count) stops contributing drift of its own.
The conjunction of the two sentinels is what keeps the mask narrow: a
count-bearing line such as found 12345 issues or expected 4 on :49870
carries no tmpdir sentinel, so its integers stay untouched and a genuine
count change still surfaces as drift.
Approach
Section titled “Approach”-
In
apps/sdlc/lib/services/quality/baseline.ts#normalizeFinding, after the existing step 5 (timing tokens), add step 6 guarded on the banner shape:// 6. Bare-integer PID columns on server-banner lines.if (out.includes("<TMPDIR>") && out.includes("<PORT>")) {out = out.replace(/(?<=^|\s)\d{2,7}(?=\s|$)/g, "<PID>").replace(/[ \t]{2,}/g, " ");}The guard runs on the already-masked
out, not on the raw input, so the sentinels are present by the time it is evaluated. The 2-digit floor keeps single-digit counts out of the mask; the 7-digit ceiling matches step 4’s existing PID width. -
Extend the module docstring’s numbered mask list (the “Masks, in fixed order” block above
normalizeFinding) with the new step 6, naming the two-sentinel guard and the reason it exists — thedashboard listbanner row whose PID is a bare column. -
In
apps/sdlc/lib/services/quality/tests/quality_ops.test.ts, add adescribeblock for T-NRTX next to the existing T-BQRU block covering: the observed banner shape; adiff()case where two banner rows differing only in PID and column padding cancel; and negative cases proving integers on non-banner lines stay unmasked. -
Run
bun testagainstapps/sdlc/lib/services/quality/tests/quality_ops.test.tsand confirm the pre-existing T-BQRU cases (in particular the combinedserver /tmp/abc/sock 127.0.0.1:50000 pid 4242 (9ms)case, which carries both sentinels but no bare-integer token) still pass unchanged.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/lib/services/quality/baseline.ts#normalizeFinding | modify | Add mask step 6 — bare-integer PID columns plus padding collapse, guarded on the two-sentinel banner shape. |
apps/sdlc/lib/services/quality/baseline.ts | modify | Extend the “Masks, in fixed order” docstring list with step 6 and its rationale. |
apps/sdlc/lib/services/quality/tests/quality_ops.test.ts | modify | Add the T-NRTX unit tests: banner shape, diff cancellation, and the non-banner negative cases. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
normalizeFinding("/tmp/proj-abc 27439 http://127.0.0.1:54321/")returns"<TMPDIR> <PID> http://127.0.0.1:<PORT>/". - AC-2:
diff()returns[]for a verb whose current findings are["/tmp/a 27439 http://127.0.0.1:54321/"]and whose baseline findings are["/tmp/b 931 http://127.0.0.1:49870/"]— the PID, the padding width, and the port cancel. - AC-3:
normalizeFinding("found 12345 issues")still returns"found 12345 issues", andnormalizeFinding("expected 4 on :49870")still returns"expected 4 on :<PORT>"— neither line carries the tmpdir sentinel, so its integers are not masked. - AC-4:
normalizeFinding("server /tmp/abc/sock 127.0.0.1:50000 pid 4242 (9ms)")still returns"server <TMPDIR> 127.0.0.1:<PORT> pid <PID> (<TIME>)"— the new mask leaves the pre-existing combined case byte-identical. - AC-5:
bun testoverapps/sdlc/lib/services/quality/tests/quality_ops.test.tsexits 0. - AC-6: the
normalizeFindingmodule docstring lists six numbered masks, the sixth naming the two-sentinel banner guard.
Out of scope
Section titled “Out of scope”- Dropping server-banner lines from the finding set wholesale (the alternative the source task offered) — this task masks instead, so a genuine change in a banner line still surfaces as drift.
- Changing
listDashboards’s output format, column widths, or padding inapps/sdlc/lib/services/dashboard/server.ts. - Normalizing column padding on lines that carry no bare-integer PID column —
for example the
dashboard listheader row. - Re-capturing, pruning, or rewriting baseline JSON files on disk; masking stays a diff-time concern, as it is today.
- Widening the mask to lines that carry a URL but no tmpdir sentinel.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-08-03 UTC from
T-HTN8-lease-gate-memo-fields in git@github.com:sksizer/dev.git.