--- id: "bug-1581" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "unknown" severity: "unknown" timestamp: "2026-08-18T12:10:00Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: [".claude/skills/xce-test-resources/run-resource-verify.sh"] tags: ["bash", "ifs", "tsv-parsing", "resource-value-verification", "task-7.1", "aggregate_verdict"] related: [] legacy: true --- # bug-1581: aggregate_verdict() synthetic self-test scenario (c) [one layer UNREACHABLE] reported… ## Description aggregate_verdict() synthetic self-test scenario (c) [one layer UNREACHABLE] reported unreachable=2 instead of 1, and the apiserver layer of an accepted+reachable record showed api_ok=2.5/api_obs=(empty) after IFS=$'\t' read -- fields silently shifted by one position. ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause aggregate_verdict()/build_verdict_record() (task 7.1, new this session) originally joined/split its 9-field verdict record with a literal tab character (IFS=$' ' read), matching every other TSV parse already in this file. But this is the FIRST record format in the file where a field can be genuinely empty (an UNREACHABLE or reachable-but-empty-value *_observed field) -- and bash treats tab (like space/newline) as "IFS whitespace" for field splitting even when IFS is set to ONLY a tab character, so two adjacent tab delimiters around an empty field silently COLLAPSE into one, shifting every field after it. Every prior TSV in this file (injection-results.tsv, *-check-results.tsv) never carries an empty field, which is why this bash gotcha never surfaced before. ## Fix Switched the record-format delimiter from a literal tab to ASCII Unit Separator (0x1F, $'', VERDICT_FIELD_SEP) in build_verdict_record()/aggregate_verdict() only -- not a whitespace-class character, so bash never collapses adjacent delimiters or strips empty fields. Caught immediately by this task's own required synthetic self-test invocations (per task 7.1's TDD expectation to sanity-check aggregate_verdict() with synthetic records before relying on it), before any live-run validation -- all 4 required synthetic scenarios (match/mismatch/unreachable/mixed) plus edge cases now pass. ## Verification _Not recorded (migrated from buglog.json)._ ## References _Not recorded (migrated from buglog.json)._