Skip to content

chore: make the repo agent-ready — SSOT concerns, a broken bin/setup, and AGENTS.md - #11

Merged
Harry-kp merged 9 commits into
mainfrom
refactor/simplify-structure
Sep 26, 2026
Merged

Harry-kp merged 9 commits into
mainfrom
refactor/simplify-structure

Conversation

@Harry-kp

Copy link
Copy Markdown
Owner

What & why

An audit for over-engineering, plus the agent instructions file the repo never had. The audit's main finding was that this repo did not need restructuring — it is a published Rails engine, so its paths are API and its depth is framework convention. Nothing moved. What it did surface was three real defects and two genuine duplicates.

bin/setup never worked from a fresh clone. It ran app:db:test:prepare, which loads test/dummy/db/schema.rb — gitignored, so nothing exists to load until something has migrated. It died with schema.rb doesn't exist yet. The clone-to-green path in CONTRIBUTING.md could not have worked; it only appeared to because anyone running it already had a schema dump on disk. It now migrates like CI does.

A stale schema.rb silently fails all 339 tests. Rails' maintain_test_schema loads that dump over your migrations, so a copy left from before the workflows→approvals rename produces UnknownAttributeError / UndefinedTable everywhere — errors that read like broken code, not a stale database. This cost the most to diagnose (first baseline run: 282 errors). Guarded in bin/setup, documented first in AGENTS.md.

CONTRIBUTING.md told contributors Ruby 3.1 while the gemspec has required >= 3.2.0 since 1.0 — anyone who believed it could not bundle install. .rubocop.yml targeted 3.1 too, suppressing 3.2 idioms.

The launch drafts claimed, in the author's name, that he had written this code at three jobs. That was invented for voice, not reported. The existing warning sat in a sibling README while the claim sat in the text you paste, and cited line numbers that had already drifted. All 8 instances are now [YOUR STORY] markers naming what each claimed; checklist.md gates on the grep returning nothing.

How

Two SSOT extractions, both verified behaviour-preserving:

  • approvals_required_is_valid was three byte-identical copies across Approval, Step and TemplateStep — error string included → ConsensusValidatable.
  • emit_outbox was two copies that had already drifted (Step's dropped the reason argument) → Outboxable. ApprovalBuilder.build_quarantine_approval! still writes inline: it emits on behalf of an approval from outside the model, and folding it in would mean widening a private method to public on a published gem.

Four same-name pairs were read and deliberately left alone — guard_consensus!, actor_label, target_label, current_tenant, approvable_models are each two different jobs, and merging them would have been a bug. No dead code existed; ModelExtensions is wired dynamically at engine.rb:8 and a naive grep would have called it dead.

AGENTS.md is 55 lines, with CLAUDE.md as a one-line import so there is one source of truth. Every command in it was run against this repo. Neither file ships in the gem — the gemspec uses an explicit allowlist.

Verification

Final run was from a dropped database with no schema.rb — a real clone simulation, not an incremental re-run.

Before After
Tests 339 runs, 1070 assertions, 0 failures identical
Rubocop 82 files clean 84 files clean, at a stricter TargetRubyVersion
gem build 1.1.0 OK 1.1.0 OK
bin/setup from fresh clone broken works

Coverage measured at 96.5% line / 81.8% branch.

Checklist

  • Tests added/updated (bin/rails app:test is green)
  • bundle exec rubocop is clean
  • CHANGELOG.md updated under "Unreleased"
  • Docs/README/cookbook updated if behavior changed — no behaviour changed; CONTRIBUTING and the launch docs corrected

Note for the author

docs/launch/posts.md now has 8 [YOUR STORY] slots waiting on you. The Short Ruby blurb is flagged hardest — it is third person and goes to an editor who may print it as sent. Where a neutral true alternative existed (the r/rails title) it is offered inline.

🤖 Generated with Claude Code

Harry-kp and others added 7 commits September 27, 2026 00:49
The same five-line validation, down to the error string, lived in Approval,
Step and TemplateStep. Editing the message in one place and missing the other
two was the obvious next bug; an admin mistyping a spec in the rule builder
and a host mistyping one in the DSL should be told the same thing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Approval and Step each carried their own copy; Step's omitted the `reason`
argument, so the two had already drifted in signature. One definition with
`reason = nil` covers both call shapes — Step passes nothing and error_payload
stays null exactly as before.

ApprovalBuilder.build_quarantine_approval! still writes its OutboxEvent
inline: it emits on behalf of an approval from outside the model, and folding
it in would mean widening a private method to public on a published gem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gemspec has required >= 3.2.0 and the README said 3.2+, but CONTRIBUTING
told contributors 3.1 and rubocop still targeted 3.1 — so anyone who believed
the contributing guide could not bundle install, and the linter was suppressing
3.2 idioms. Still clean at 3.2.

test/helpers/ and test/fixtures/files/ held nothing but .keep and nothing
referenced either path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`bin/rails app:db:test:prepare` loads test/dummy/db/schema.rb — which is
gitignored, so on a fresh clone there is nothing to load and setup died with
"schema.rb doesn't exist yet". The documented clone-to-green path in
CONTRIBUTING.md could not have worked; it only appeared to because everyone
running it already had a schema dump on disk.

Migrate instead, mirroring CI: create, engine pass, dummy pass, engine pass
again for the engine migrations that sort after the dummy's. The stale dump is
removed first — Rails' maintain_test_schema would otherwise load an old copy
over the migrations and fail the entire suite with errors that read like broken
code rather than a stale database.

Verified from a dropped database and no schema.rb: setup completes, 339 runs,
0 failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records only what can't be read off the code: the verified command set, the
two-pass migration, and the traps. Every command in it was run against this
repo. CLAUDE.md is a one-line import so there's one source of truth.

The headline gotcha is the one that cost the most to find — a stale
test/dummy/db/schema.rb fails all 339 tests with errors that read like broken
code. Second is that test/docs_test.rb treats the README as API, so renaming a
config key without touching the docs fails the suite by design.

Neither file is packaged: the gemspec ships an explicit allowlist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every launch draft claimed, in the author's name, that he had written this same
code at three jobs. Nobody reported that; it was written to give the posts a
voice. The directory README warned about it, but the warning sat in a sibling
file while the claim sat in the text you actually paste — and its line numbers
had already drifted.

Each of the eight instances is now a `[YOUR STORY]` marker naming what it
claimed, so a draft cannot be posted as-is without noticing. The newsletter
blurb is flagged hardest: it is third person and goes to an editor who may print
it as sent. Where a neutral true alternative existed — the r/rails title — it is
offered inline.

checklist.md now gates on `grep 'YOUR STORY'` returning nothing, next to the
existing greps for URLs and for features that do not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 26, 2026 19:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Harry-kp and others added 2 commits September 27, 2026 01:08
The [YOUR STORY] markers were a blocker sitting on an unreleased launch: six
drafts none of which could be posted until the author supplied a history he
hadn't written down. The posts don't need one.

Each draft now leads with the problem — stated as what happens to Rails apps
generally, which is the actual claim and is true — and is first-person only
about the one certain fact: he built the gem. The dev.to piece gains the
stronger line for it ("none of those eleven months involved a bad decision"),
since the point was always that the mess accumulates from reasonable local
choices.

The checklist gate stays but inverts: rather than blocking on an unfilled
marker, it greps for career claims and expects none. Reworded the explanatory
notes so they no longer trip their own grep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
json 3 removed the `quirks_mode` keyword. ActiveSupport's JSON encoder passes
it up to and including Rails 8.0 (active_support/json/encoding.rb calls
`JSON.generate(jsonified, quirks_mode: true, max_nesting: false)`), so the
first migration to touch a jsonb column aborts with
`ArgumentError: unknown keyword: quirks_mode` before any test runs.

This is not a regression from this branch: pristine main at dcccd7e fails
identically under RAILS_VERSION=8.0. main last ran CI on 2026-08-29 and
Gemfile.lock is gitignored, so the matrix re-resolved onto the new json and
went red on its own.

Rails 7.2 and 8.1+ carry the fix, which is why only two matrix entries failed
and why the unpinned entry is left alone. Dev-only — the gemspec is untouched,
so consumers are unconstrained.

Verified locally against both failing entries: 7.1 → 339 runs / 1015 assertions,
8.0 → 339 runs / 1070 assertions, 0 failures each. Latest (8.1, json 3
unpinned) unchanged at 339 / 1070.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Harry-kp
Harry-kp merged commit c07e703 into main Sep 26, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants