chain (multi-root): 5 issues — idd-issue multi-finding spec hardening family from #48 verify#113
Conversation
…tion type Addresses #76 + #79 + partial #75 in multi-finding mode spec: - run_id format: ISO-8601 second precision → millisecond precision + UTC Z suffix + nonce-retry on collision. Pre-v2.67.0 second-precision `run_id` collided under parallel `/loop` / CI batch / concurrent terminals → silent audit-trail overwrite, the irreversible-side-effect failure mode added to Layer P vocabulary in v2.64.0 #103 F4. - TOCTOU symlink check before jsonl write: `[ -L "$JSONL_PATH" ] && abort`. Closes the predictable-path + truncate-write hardening gap (#76 MEDIUM — attacker with local FS write access could pre-create the audit path as a symlink at e.g. ~/.ssh/authorized_keys). - Noclobber retry helper (`JSONL_WRITE_GUARD`) on hostile concurrency (bash for-loops within same millisecond) — combines with ms-precision run_id and symlink check to close the collision channel even under adversarial use. - Audit footer adds `> **Action**: {create|comment|edit|update}` line (#79 Gap 2 REQ-6) — reader can identify dispatch shape from GitHub body without cross-referencing jsonl. - Audit footer adds "may be invalid on abort/skip-commit" caveat next to Run log line (#79 Gap 1 P3.2 disposition: lightweight documentation pending separate decision on write-on-abort vs compensating comment). - Schema enum adds `"srt"` as first-class source_type (#79 Gap 3 REQ-6) resolving the example/enum mismatch where srt sources serialized as "pasted-text". - Schema adds optional `aborted?: boolean` field (#79 Gap 1 enables write-minimal-aborted-jsonl path in future, currently documented). - `finding_quote` CAUTION banner above schema (#75 F1 partial — dual-track contract; sanitize_source_label() + content sanitization contract subsection follows in next commit). All changes additive — existing single-issue mode + bundle mode unaffected. Multi-finding mode users see ms-precision run_id + action-typed footer on next dispatch. Refs #76 #79 #75
Addresses #75 F1 + F2 + F8 in multi-finding mode spec: NEW `### Content sanitization contract (v2.67.0+, #75)` subsection between JSONL example and Merge mechanism. Three deliverables: - F1 (HIGH) — Dual-track contract: jsonl `finding_quote` verbatim per IC_R007 (line 1007) + GitHub body `finding_quote_display` sanitized. Strip C0/C1 control chars, warn-and-strip bidi-override (U+202A-U+202E + U+2066-U+2069), normalize CRLF, preserve other Unicode. Sanitization is composition-time projection, not retroactive on stored record — preserves IC_R007 fidelity. Rationale documented (downstream forensic / locale analysis needs verbatim source-of-truth; sanitization at rendering boundary, not storage boundary). - F2 (HIGH) — `sanitize_source_label()` bash helper that strips control chars, escapes backticks, and *refuses* (not silently strips) embedded `@[A-Za-z0-9_-]+` mention tokens. Refuse-not-strip preserves audit trail integrity for legitimate `@scope/package.docx` names while forcing the caller through rules/tagging-collaborators.md 5-step protocol. All Stage 4 footer composition paths MUST run `<source>` through this helper. - F8 (MEDIUM) — Mandate `jq --arg` / `--argjson` parameter binding for JSONL write + body composition with user content. String interpolation into jq filter is vulnerable to JSON injection when values contain " / \ / control chars. Spec shows the required pattern + refused anti-pattern explicitly. All changes additive — pre-v2.67.0 multi-finding dispatches retain their verbatim semantics. New dispatches get sanitized GitHub body content with verbatim jsonl preserved. Refs #75
…ty heuristics Addresses #80 (3 LLM-determinism gaps) + #77 (7 spec contract gaps) + #79 Gap 1 (write-on-abort jsonl disposition). #80 (Stage 1 + Stage 2 LLM determinism): - Gap 1 / P3.1 — Stage 1 anchor heuristics for the "AI MAY merge / MAY split" clauses: default = preserve original granularity; split only if paragraph contains ≥3 distinct independent topics; merge only if 2 consecutive paragraphs same-topic AND combined length <200 chars. Reduces variance for typical sources while preserving MAY semantics for genuinely ambiguous cases (so mode-switch via ≥2 threshold is more reproducible). - Gap 2 / REQ-3 — `max_possible_score` denominator explicitly defined as `title_token_count × 2 + min(body_token_count, 300) × 1` from the finding's own keyword set. Yields scores in [0,1] consistently — same candidate now displays same absolute score across invocations. - Gap 3 / REQ-3 — Degenerate-case picker shape table: N=0 → skip to [Other] second-level; N=1 → 1+Other (2-option); N=2 → 2+Other (3-option); N≥3 → existing top-3+Other (unchanged). #77 (spec contract gaps): - Gap 1 / P1.1 — Flag-conflict refusal layering table: explicit flag pairs refuse at Step 0 arg-parse (no Stage 1 cost); --bundle-mode + auto-trigger refuses post-Stage 1 (detection requires extraction). - Gap 2 / P2.1 — `partner_eligible_set` formal definition consolidating rules previously 18 lines apart in Merge mechanism: `{f | f.id > current_id AND f.id NOT IN merged_into_set AND f.id NOT IN already_routed_set}`. Pickers SHALL only surface candidates from this set; three-way+ merge stays refused (MVP scope). - Gap 3 / P2.2 — Stage 3 `[Edit row N]` soft cap at >5 cumulative edits with warn-not-block confirmation prompt. Heuristic: "if reviewer keeps revisiting, signal is more likely wrong source extraction than still picking". - Gap 4 / P2.3 — `[Back to top-3]` added as 5th option in Stage 2 Other second-level picker. Escape hatch for users who entered Other by mistake or changed mind after seeing second-level options. - Gap 5 / Security F4 — Stage 1 entry MUST canonicalize source paths + refuse paths outside repo work tree. Re-uses Step 1 adapter discipline (multi-finding mode does NOT bypass) — prevents `../../etc/passwd` leaking into body/jsonl. - Gap 6 / REQ-5 — Agent-crash recovery semantics documented as known gap (not auto-recovered). Trade-off documented: incremental persist (extra I/O) vs accepting partial-trail (rare failure mode, observable footer link 404). Recovery workflow: user reconciles GitHub state manually + re-runs. Incremental persist deferred to future enhancement. - Gap 7 — Stage 4.5 unattended-mode fallback: `[ ! -t 0 ] AND IDD_ALL_UNATTENDED/CI` set → auto-default to `skip-commit` (safest for unattended: jsonl locally, no commit). Explicit `IDD_JSONL_GITIGNORE_GATE=false` bypass remains as the audit-cited escape hatch. #79 Gap 1 / P3.2 disposition (a) — abort path writes minimal `aborted: true` jsonl with `actions[]` already dispatched + partial timestamps. File exists → footer link valid → collaborators viewing already-dispatched bodies don't get 404. In-memory entries beyond abort point still discarded. All changes additive — pre-v2.67.0 invocations retain their semantics; new invocations benefit from tighter spec contracts. Refs #80 #77 #79
#77 #79 #80) - CHANGELOG.md: NEW [2.67.0] entry covering 5 sister bugs from #48 verify (security, audit trail, LLM determinism, spec gaps). Each addressed in skills/idd-issue/SKILL.md multi-finding mode section. - plugin.json: version 2.66.0 → 2.67.0 (minor — 5 additive spec hardening changes; pre-v2.67.0 invocations retain semantics, new invocations benefit from tighter contracts) - Marketplace.json sync deferred to /idd-close Step 6.5 chain per repo precedent (#103 / #102 / #110) Refs #75 #76 #77 #79 #80
4 HIGH blocking findings from 6-AI verify ensemble (PR #113 round 1): F75-1 (logic, HIGH) — `sanitize_source_label()` corrupts CJK/emoji/UTF-8 multibyte sequences. `LC_ALL=C tr -d '\200-\237'` operates at byte level — empirically reproduced: `中` byte 0x96 in `中文` (e4 b8 ad e6 96 87) gets stripped. Replaced with Python's Unicode-aware code-point filter that preserves all non-control non-bidi characters while stripping C0/DEL/C1/bidi-override (U+202A-U+202E + U+2066-U+2069 Trojan-Source CVE-2021-42574 family). CRLF normalization included. F76-1 (logic, HIGH) — BSD date (macOS native /bin/date) does NOT error on `%3N` format specifier. It silently emits literal `3N` with exit 0, so `2>/dev/null || .000Z` fallback never triggers. RUN_ID becomes malformed `2026-05-20T03:09:08.3NZ`. Replaced with dispatch chain: (1) try GNU date %3N + regex-validate output shape; (2) fall through to Python datetime stdlib (works on every platform); (3) absolute fallback to .000Z with warn message. Collision-resistance preserved on macOS. F76-2 (logic, HIGH; DA + regression converged) — `JSONL_WRITE_GUARD()` defined but never invoked. Added call site at run-start (right after RUN_ID is set), explicitly NOT at materialize phase. Comment explains the cross-issue interaction: mutating path at materialize would invalidate footer URLs already written by Stage 4 dispatch (#79 Gap 1 depends on footer URL stability after abort). Also corrected `RANDOM % 65536` → `RANDOM % 32768` since bash $RANDOM is 15-bit (range 0-32767) — birthday-paradox collision threshold ~181 concurrent invocations, not ~256. #79 abort spec/impl contradiction (regression, HIGH) — new spec at Stage 4.5 abort branch said "write minimal aborted: true jsonl" but unchanged impl said `unset RUN_LOG_ENTRIES` + "JSONL NOT written". Same PR shipped contradiction. Updated abort branch to actually write the aborted jsonl with `jq -n --arg/--argjson` parameter binding (per #75 F8 mandate), `aborted: true` field (per new schema), and partial timestamps. Footer URLs in already-dispatched GitHub bodies now resolve to a valid file with abort marker. Refs #75 #76 #77 #79 #80
/idd-verify --pr 113 — cluster verify reportPhase: verified (verify-gated PASS post-fix) Aggregate verdict: PASS (post-fix)
Findings dispatched in-PR (commit
|
| # | Severity | Source | What | Fix |
|---|---|---|---|---|
| F75-1 | HIGH | logic | sanitize_source_label() byte-level tr -d '\200-\237' corrupts UTF-8 multibyte sequences (CJK / emoji / accented Latin); empirically 中 byte 0x96 stripped |
Replaced with Python Unicode-aware code-point filter |
| F76-1 | HIGH | logic | BSD date (/bin/date on macOS) doesn't error on %3N — emits literal 3N; fallback chain never fires; RUN_ID malformed |
Dispatch chain: GNU %3N + regex-validate → Python datetime stdlib fallback → .000Z last resort |
| F76-2 | HIGH | logic + DA + regression | JSONL_WRITE_GUARD() defined but never invoked; comment-code drift on RANDOM (bash 15-bit not 16-bit) |
Added explicit call site at run-start; corrected % 32768 and DOCUMENT in comment block |
| #79 spec/impl | HIGH | regression | Abort branch spec said "write minimal aborted: true jsonl" but unchanged impl said unset RUN_LOG_ENTRIES + "JSONL NOT written" — same PR shipped contradiction |
Updated abort branch to actually write the aborted jsonl with parameter binding, aborted: true, partial timestamps |
| DA-3 | MEDIUM | DA | WRITE_GUARD called at materialize phase would mutate path AFTER Stage 4 footer composed → footer 404 | Called WRITE_GUARD at run-start; comment explains ordering rationale + cites #79 Gap 1 dependency |
Per-issue verdict
#75 — Content sanitization contract: PASS
- F1 dual-track (jsonl verbatim + GitHub display sanitized) — Python implementation covers C0/DEL/C1/bidi-override (BOTH
U+202A-U+202EANDU+2066-U+2069Trojan-Source CVE-2021-42574 family) + CRLF normalization + preserves CJK/emoji/accented - F2
sanitize_source_label()UTF-8-safe, refuse-on-@tokencross-referencesrules/tagging-collaborators.md5-step protocol - F8 jq
--arg/--argjsonmandate with REQUIRED + REFUSED examples - CAUTION banner above schema readable from jsonl file itself
#76 — run_id collision + symlink: PASS
- ms-precision dispatch chain (GNU
%3N→ Python fallback →.000Z) — macOS BSD date hazard closed - TOCTOU symlink check fail-closed before any write
JSONL_WRITE_GUARD()invoked at run-start; nonce 15-bit space documented; second-collision abort- Footer template + schema TypeScript updated
#77 — 7 corner-case gaps: PASS
- Gap 1 flag-conflict layering table (Step 0 vs post-Stage 1)
- Gap 2
partner_eligible_setformal definition - Gap 3 Edit-row soft cap >5 (warn-not-block)
- Gap 4
[Back to top-3]5th option - Gap 5 Stage 1 path canonicalization cross-references Step 1 (security reviewer noted cross-reference gap — Step 1 doesn't currently enforce; documented as known descriptive-only for v2.67.0 + flagged for follow-up issue)
- Gap 6 agent-crash recovery known gap with trade-off rationale
- Gap 7 unattended fallback (
! -t 0+IDD_ALL_UNATTENDED/CI)
#79 — Audit trail completeness: PASS
- Gap 1 abort-path now writes
aborted: truejsonl with partial timestamps — footer URLs in already-dispatched bodies remain valid (fix commit0fe06ed) - Gap 2 footer
> **Action**line - Gap 3
"srt"enum + Stage 1 source-type list
#80 — Stage 1 + Stage 2 LLM determinism: PASS
- Gap 1 anchor heuristics (preserve / split if ≥3 distinct topics / merge if <200 chars same-topic)
- Gap 2
max_possible_scoreformula explicit - Gap 3 N<3 picker shape table (N=0/1/2/≥3)
Non-blocking observations (filed as follow-up candidates)
- Cosmetic drift in inline JSONL example at SKILL.md (still uses pre-v2.67.0
"run_id": "2026-05-10T17:00:00"and"source_type": "pasted-text"for.srtsource) — would benefit from refresh but illustrative, not contract-bearing - [enhancement] idd-issue multi-finding spec gap audit: 7 corner-case contract gaps (v2.55.0+) #77 Gap 5 cross-reference gap — spec says "re-uses Step 1 adapter discipline" for path canonicalization, but Step 1 currently doesn't enforce
realpath-style containment. Follow-up: tighten Step 1 OR move canonicalization directly into Stage 1 entry - [enhancement] idd-issue multi-finding: Stage 1 reproducibility heuristics + score formula + N<3 picker #80 Gap 1 heuristic constants lack rationale annotation (DA finding) —
≥3 topics/<200 charspicked from anchor without explicit empirical justification. Optional follow-up: add inline rationale or revisit after first-real-use observations
These are filed as candidates for /idd-issue follow-up, not as blocking for v2.67.0.
Process gap
Codex gpt-5.5 xhigh background task hung at 12+ minutes with no findings output (file 0 bytes when this report compiled). 5-AI Claude ensemble carried successfully; cross-model independent verification not achieved this round. Recorded transparently per v2.59.0+ Process Gap convention (precedent: chain 1 DA stale-file finding) — not hidden in aggregate verdict.
Next: merge ready
Cluster ready to merge per verify-gated doctrine:
- Merge chain (multi-root): 5 issues — idd-issue multi-finding spec hardening family from #48 verify #113 (squash recommended — single review surface)
/idd-close #75 #76 #77 #79 #80per issue (per-issue closing summary required, no shortcut)- Distribution sync v2.66.0 → v2.67.0 via
/plugin-tools:plugin-update issue-driven-dev
Verify: 5/6-AI ensemble PASS (post-fix). Findings dispatched in same PR per feedback_verify_fix_same_pr.
…n-dev v2.67.0 Distribution sync after PR #113 merge (14bc930) closing #75 #76 #77 #79 #80 (multi-finding spec hardening family from #48 verify): - .claude-plugin/marketplace.json: 2.66.0 → 2.67.0 + full description (NSQL doctrine follow-up family rationale + per-issue scope + verify fix highlights + follow-up candidates) - plugins/issue-driven-dev/README.md: Version History row v2.56.0–v2.66.0 → v2.56.0–v2.67.0, appended v2.67.0 paragraph with per-issue scope + 5/6-AI verify outcome + fix commit reference Refs #75 #76 #77 #79 #80
Refs #75 #76 #77 #79 #80
Summary
Multi-root chain (N=5 roots: #75 #76 #77 #79 #80) solved as one cluster via
/idd-all-chain(v2.60+, traversal=dfs). All 5 sister issues from #48's 6-AI verify, all same-file (skills/idd-issue/SKILL.md), shipped as one chain per cluster-PR eligibility heuristic (same-file ✓).Cluster overview
Per-issue details
#75 (root_id=75) — [security] idd-issue multi-finding: dual-track sanitization design (IC_R007 verbatim vs GitHub body sanitization)
NEW
### Content sanitization contract (v2.67.0+, #75)subsection between JSONL example and Merge mechanism (commit7c63c4a).finding_quoteverbatim (IC_R007 line 1007) + GitHub bodyfinding_quote_displaysanitized (strip C0/C1 + warn-and-strip bidi-override U+202A-U+202E + U+2066-U+2069 + normalize CRLF + preserve other Unicode). Sanitization is composition-time projection, not retroactive on stored record.sanitize_source_label()bash helper: strips control chars + escapes backticks + refuses@[A-Za-z0-9_-]+mention tokens. Refuse-not-strip preserves audit trail integrity while forcing callers throughrules/tagging-collaborators.md5-step protocol.jq --arg/--argjsonparameter binding for JSONL write + body composition (refuses string-interpolation anti-pattern vulnerable to JSON injection).Partial #75 (CAUTION banner above schema) shipped in commit
8c9ce5ealongside #76 / #79 schema additions.#76 (root_id=76) — [bug] idd-issue multi-finding: run_id second-precision collision + symlink overwrite hardening
Stage 4.5 jsonl write block hardened (commit
8c9ce5e):run_idformat: ISO-8601 second precision → millisecond precision + UTC Z suffix (2026-05-10T17:00:00.123Z)[ -L "$JSONL_PATH" ] && abort(fail-closed)JSONL_WRITE_GUARDwithRANDOM-nonce suffix on hostile concurrency#77 (root_id=77) — [enhancement] idd-issue multi-finding spec gap audit: 7 corner-case contract gaps (v2.55.0+)
7 spec contract gap closures (commit
2e45fd6):partner_eligible_set = {f | f.id > current_id AND f.id NOT IN merged_into_set AND f.id NOT IN already_routed_set}[Edit row N]soft cap at >5 cumulative edits with warn-not-block confirmation[Back to top-3]added as 5th optionIDD_ALL_UNATTENDED/CI→ auto-default toskip-commit)#79 (root_id=79) — [enhancement] idd-issue multi-finding: audit trail completeness (stale footer links, action type in footer, source_type enum)
3 audit trail completeness gaps (commit
8c9ce5efor Gaps 2-3 + commit2e45fd6for Gap 1):aborted: truejsonl withactions[]already dispatched + partial timestamps (disposition (a)). Footer link no longer 404s after abort.> **Action**: {create|comment|edit|update}line. Reader can identify dispatch shape from GitHub body without cross-referencing jsonl.source_typeenum adds"srt"as first-class adapter. Stage 1 source-type description updated to enumeratesrt.#80 (root_id=80) — [enhancement] idd-issue multi-finding: Stage 1 reproducibility heuristics + score formula + N<3 picker
3 LLM-determinism gaps (commit
2e45fd6):max_possible_scoredenominator explicitly defined astitle_token_count × 2 + min(body_token_count, 300) × 1from finding's own keyword set. Yields consistent [0,1] scores across invocations.Review status
/idd-close #N(per issue, sequentially) per issue after merge🤖 Generated by /idd-all-chain. Do NOT add GitHub close trailers (Closes/Fixes/Resolves) — IDD discipline requires manual /idd-close per issue after merge to enforce checklist gate + per-issue closing summary.