ideation: hard-reject a slug that collides with a shipped post #370

Merged
joshtronic merged 1 commit from fix/367-slug-collision into master 2026-07-09 22:53:57 +00:00
Collaborator

Closes #367. Adds a hard slug-collision gate to the ideation pipeline.

The shipped-digest dedup is a nudge to the model, not a gate — it didn't catch a same-slug regeneration (ergo-decedo, 7/7 then 7/8). The duplicate wrote the same Eleventy permalink, so the PR's CI could never go green (Output conflict), wasting a draft + rework round + escalation.

Now, right after the slug is chosen and before the expensive draft, any slug that already exists as a shipped post is rejected and the run exits clean — the daily slot retries next tick and re-rolls a fresh idea. So the un-mergeable duplicate-permalink PR is never opened.

11 lines, shellcheck clean, make test green. Reuses the existing *-<slug>.md find pattern the link-resolver already uses. (Left the optional 'auto-close an existing duplicate-permalink PR' bonus out — the source-side gate prevents it upstream.)

Closes #367. Adds a hard slug-collision gate to the ideation pipeline. The shipped-digest dedup is a *nudge* to the model, not a gate — it didn't catch a same-slug regeneration (`ergo-decedo`, 7/7 then 7/8). The duplicate wrote the same Eleventy permalink, so the PR's CI could never go green (Output conflict), wasting a draft + rework round + escalation. Now, right after the slug is chosen and **before** the expensive draft, any slug that already exists as a shipped post is rejected and the run exits clean — the daily slot retries next tick and re-rolls a fresh idea. So the un-mergeable duplicate-permalink PR is never opened. 11 lines, shellcheck clean, `make test` green. Reuses the existing `*-<slug>.md` find pattern the link-resolver already uses. (Left the optional 'auto-close an existing duplicate-permalink PR' bonus out — the source-side gate prevents it upstream.)
ideation: hard-reject a slug that collides with a shipped post
All checks were successful
Lint / check-sync (push) Successful in 5s
Lint / check-sync (pull_request) Successful in 4s
5806aa7dda
Closes #367. The shipped-digest dedup is only a nudge to the model and didn't
catch a same-slug regeneration on consecutive days (ergo-decedo, 7/7 then 7/8).
The duplicate wrote the same Eleventy permalink, so the PR's CI could never go
green -- wasting a draft, a rework round, and a human escalation.

Now, right after the slug is chosen and before the expensive draft, reject any
slug that already exists as a shipped post (posts/<slug>) and exit clean. The
daily slot retries next tick and re-rolls a fresh idea, so no un-mergeable
duplicate-permalink PR is ever opened.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
Collaborator

🤖 Review — COMMENT (automated)

CI for 5806aa7d: success

Adds an 11-line hard slug-collision gate to bin/ideation-pipeline.sh that exits clean (re-roll next tick) when the chosen slug already exists as a shipped post, before the expensive draft. Focused, in scope, honestly described, CI green. One correctness nuance worth a human glance before merge.

Findings

  • Potential false-positive from the suffix glob (bin/ideation-pipeline.sh, the find ... -name "*-${SLUG}.md" clause). Posts are named YYYY-MM-DD-slug.md, so *-${SLUG}.md also matches any file whose slug ends in -${SLUG}. E.g. a new slug decedo would match the existing 2026-07-07-ergo-decedo.md and be rejected even though its permalink (posts/decedo/) does not actually collide with posts/ergo-decedo/. The failure mode is benign (an unnecessary re-roll next tick, no bad PR opened), and the description notes this reuses the link-resolver's existing pattern — but it means the gate is broader than "same permalink." If the intent is strictly permalink-collision, an exact-slug check (e.g. matching ????-??-??-${SLUG}.md or deriving the actual permalink) would be tighter. Flagging so the human can decide whether the over-rejection is acceptable.

  • No dedicated test for the new gate. The contract asks for tests alongside behavior changes. This is a shell early-exit path; make test is green per CI, but I can't see a test that exercises the collision branch (skip vs. proceed). If the repo has any shell/bats harness, a case covering "slug matches shipped post → exit 0, no draft" would lock in the behavior. If there's no such harness, that's understandable — just noting the gap.

Non-issues (checked)

  • 2>/dev/null + grep -q . correctly treats a missing src/posts as "no collision" → proceeds. Fine.
  • exit 0 (clean) is intentional per the description so the scheduler retries next tick — consistent with the stated design.
  • No security concern: SLUG is quoted in the -name pattern; worst case a glob metachar in a slug causes a mismatch, not injection.

No blocking defect found; the suffix-match breadth is the one thing I'd want the human to confirm is intended.


Independent review by the harness on claude-opus-4-8 (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.

### 🤖 Review — `COMMENT` _(automated)_ CI for `5806aa7d`: **success** Adds an 11-line hard slug-collision gate to `bin/ideation-pipeline.sh` that exits clean (re-roll next tick) when the chosen slug already exists as a shipped post, before the expensive draft. Focused, in scope, honestly described, CI green. One correctness nuance worth a human glance before merge. **Findings** - **Potential false-positive from the suffix glob** (`bin/ideation-pipeline.sh`, the `find ... -name "*-${SLUG}.md"` clause). Posts are named `YYYY-MM-DD-slug.md`, so `*-${SLUG}.md` also matches any file whose slug *ends in* `-${SLUG}`. E.g. a new slug `decedo` would match the existing `2026-07-07-ergo-decedo.md` and be rejected even though its permalink (`posts/decedo/`) does **not** actually collide with `posts/ergo-decedo/`. The failure mode is benign (an unnecessary re-roll next tick, no bad PR opened), and the description notes this reuses the link-resolver's existing pattern — but it means the gate is broader than "same permalink." If the intent is strictly permalink-collision, an exact-slug check (e.g. matching `????-??-??-${SLUG}.md` or deriving the actual permalink) would be tighter. Flagging so the human can decide whether the over-rejection is acceptable. - **No dedicated test for the new gate.** The contract asks for tests alongside behavior changes. This is a shell early-exit path; `make test` is green per CI, but I can't see a test that exercises the collision branch (skip vs. proceed). If the repo has any shell/bats harness, a case covering "slug matches shipped post → exit 0, no draft" would lock in the behavior. If there's no such harness, that's understandable — just noting the gap. **Non-issues (checked)** - `2>/dev/null` + `grep -q .` correctly treats a missing `src/posts` as "no collision" → proceeds. Fine. - `exit 0` (clean) is intentional per the description so the scheduler retries next tick — consistent with the stated design. - No security concern: `SLUG` is quoted in the `-name` pattern; worst case a glob metachar in a slug causes a mismatch, not injection. No blocking defect found; the suffix-match breadth is the one thing I'd want the human to confirm is intended. --- <sub>Independent review by the harness on `claude-opus-4-8` (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.</sub> <!-- review sha=5806aa7dda0c3b817aa50b27430ce677978a0658 verdict=COMMENT ci=success -->
igor added spent time 2026-07-09 04:25:43 +00:00
40 seconds
joshtronic approved these changes 2026-07-09 22:53:36 +00:00
joshtronic deleted branch fix/367-slug-collision 2026-07-09 22:53:57 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
2 participants
Notifications
Total time spent: 40 seconds
igor
40 seconds
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
joshtronic/igor!370
No description provided.