From 2ee6752d4da1f022ee2996d792a951048c78d9c5 Mon Sep 17 00:00:00 2001 From: Maximilian Kindshofer Date: Sat, 5 Sep 2026 07:57:30 +0200 Subject: [PATCH 1/3] docs: say in the README that this is a fork, and what that changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README was upstream's product pitch, and it still is below the new section — deliberately, so an upstream sync stays a sync rather than a merge argument. What it never said is that this repository is a fork Exkulpa runs as a network service, which is the fact AGPL-3.0 §13 attaches obligations to, and that four fork-local rules apply that upstream has no reason to carry. The badges are rebuilt around what is true here. The CI badge now points at this fork's workflow instead of upstream's, so it reports our runs and not somebody else's. The clone commands point at this fork for the same reason. The two gate badges deliberately state a rule rather than a number. There is no repository-wide coverage or mutation percentage to report: both gates grade what a change touches, and a repo total is a figure nobody has to defend which improves when you measure less. A percentage badge would have had to be invented or fetched from a third party, and the diff-scoped mutation score in particular covers five declared files — printing it as a repository score would be a straightforward misrepresentation. Since both gates are jobs inside the CI workflow, the CI badge already goes red when either fails, which is the honest live signal. The stars badge is gone. On a fork nobody stars it measured nothing. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dc7a0272be..1f75812ad1 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,54 @@

- GitHub stars - License - CI - PRs Welcome + CI + Diff coverage enforced per pull request + Mutation gate: no unjustified survivors + Fork of QuackbackIO/quackback + License

+> **The two blue badges state a rule, not a measurement.** There is no +> repository-wide coverage or mutation percentage here, deliberately: the gates +> grade **what a change touches**, not the total, because a total is a number +> nobody has to defend and it improves when you measure less. `diff-coverage` +> and `mutation` are jobs inside the CI workflow, so the CI badge above goes red +> when either of them fails. What the two rules actually require is in +> [CLAUDE.md](CLAUDE.md). + +## This is a fork + +Upstream is [QuackbackIO/quackback](https://github.com/QuackbackIO/quackback); +everything below this section is upstream's own description of the product and +is left as it stands, so a sync stays a sync. This section is the fork-local +part. + +Exkulpa runs this codebase as a network service for its own products. Under +AGPL-3.0 §13 that carries obligations towards the people using that instance — +what they are, what is already satisfied, and what is still open is recorded in +[LEGAL.md](LEGAL.md). Read it before building anything proprietary in here. + +Four things differ from upstream and will bite whoever ignores them: + +- **Everything written into the repo is English** — code, comments, commits, + docs. The conversation around it is German; the file is not. A patch has to be + able to go upstream without a translation pass. +- **The contract comes before the tests, and the tests before the code.** For + anything with branches, money, dates, permissions or parsing, the guarantees + are written as a numbered list in plain domain language and confirmed _before_ + a property is written. A property read off the implementation cannot fail. +- **Two CI gates grade the diff, not the repo** — see the badges above. +- **Never deploy a moving tag.** A push to `main` publishes + `ghcr.io/therealbithive/quackback:main`, which is fine to try things with and + wrong to run: a pod rescheduled overnight silently picks up a different build, + and afterwards nobody can say which source was running. Every merge gets a + `vX.Y.Z-exkulpa.N` tag instead. + +The working rules are in [CLAUDE.md](CLAUDE.md), and the concrete gotchas that +have already cost someone an afternoon are collected in +[SELF-IMPROVE.md](SELF-IMPROVE.md). Both are fork-local and absent upstream, so +neither ever conflicts on a sync. +

Quackback feedback portal

@@ -73,7 +115,7 @@ Slack, Linear, Jira, GitHub, GitLab, Asana, ClickUp, Monday, Trello, Notion, Sho ### Docker ```bash -git clone https://github.com/QuackbackIO/quackback.git +git clone https://github.com/theRealBithive/quackback.git cd quackback cp .env.example .env # Edit with your configuration docker build -t quackback -f apps/web/Dockerfile . @@ -97,7 +139,7 @@ See the [Contributing Guide](CONTRIBUTING.md) to get started. Prerequisites: [Bun](https://bun.sh/) v1.4.0+ and [Docker](https://docker.com/) ```bash -git clone https://github.com/QuackbackIO/quackback.git +git clone https://github.com/theRealBithive/quackback.git cd quackback bun run setup # Install deps, start Docker, run migrations bun run db:seed # Optional: seed demo data From af52f51fee9e1a642a5956cfd69bde23205e5d14 Mon Sep 17 00:00:00 2001 From: Maximilian Kindshofer Date: Sat, 5 Sep 2026 07:58:08 +0200 Subject: [PATCH 2/3] docs: record what declaring a file in the mutation manifest actually costs Adding a manifest entry asserts that the named suites pin the file. On a pre-existing file that claim is usually false, and the only way to find out is to run the gate. One file this round cost a dozen small tests to make the claim true; the next would have cost sixty mutants of untouched error handling and was backed out instead. Also records two traps that each cost a run: NoCoverage mutants mean the entry names too few suites rather than that the code is untested, and editing the manifest as text puts the new record in `equivalents` too, which the gate reports as a missing reason. Co-Authored-By: Claude Opus 5 (1M context) --- SELF-IMPROVE.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/SELF-IMPROVE.md b/SELF-IMPROVE.md index 5201b665bf..15ee49cb14 100644 --- a/SELF-IMPROVE.md +++ b/SELF-IMPROVE.md @@ -310,6 +310,40 @@ database up in one command. # Resolved +## 1x — Declaring a file in the mutation manifest is a claim, and it can cost an afternoon to find out it is false + +Adding an entry to `scripts/mutation-manifest.json` asserts that the named +suites, on their own, pin that file's behaviour. The word "asserts" is doing +real work: on a file that was already in the repository, the claim is usually +**not** true, and there is no cheap way to find out except running the gate. + +Measured while touching two GitLab files with one field each. +`gitlab/server/inbound.ts` came back with 21 survivors — all but one in guards +nobody had ever pinned (an issue hook naming no issue, a note whose text is not +text, an absent author, both 401 bodies). Closing them was worth doing and took +about a dozen small tests, because the file is pure parsing. Then +`gitlab/server/hook.ts` came back with **60**: 15 survivors and 45 never +executed, across outbound error handling the change had not touched. That one +was backed out — the entry would have been a false claim, and the gate's +designed answer for an unpinned file is to name it as ungraded. + +So: the size of the claim has nothing to do with the size of the diff. Before +declaring a pre-existing file, run the gate with the entry added and read the +number before committing to it. A pure module is usually a dozen tests away +from honest; a module that does I/O in its branches is a work item of its own. + +Two smaller traps found on the way: + +- `NoCoverage` mutants mean the entry names too few suites, not that the code is + untested. `inbound.ts` had 18 of them purely because `verifySignature` lives + in that file and is tested from `signature-matrix.test.ts` two directories + away. The fix is a second entry in `suites`, not more tests. +- Editing the manifest with a blind string replace puts the new record in + `equivalents` as well as `graded`, because the same `"file":` line matches in + both arrays. The gate refuses to run at all and says an equivalence record + needs a `why` — which reads like a missing reason rather than a record that + should not exist. Edit that file as JSON, not as text. + ## 1x — A Stryker run leaves two things behind that nothing else guards Both surfaced within an hour of the mutation gate going green, and neither is From 236e5d71d3217950328f6c1cbf138221b91fdade Mon Sep 17 00:00:00 2001 From: Maximilian Kindshofer Date: Sat, 5 Sep 2026 08:02:57 +0200 Subject: [PATCH 3/3] docs(readme): say where the two gates actually bite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The badge blockquote claimed the CI badge goes red when `diff-coverage` or `mutation` fails. True as far as it goes — neither job is `continue-on-error` and neither is skipped on a push — but it reads as if the green badge on `main` were evidence those two gates found something. It is not: both compare against the merge base, and on `main` that is `main`, so the diff is empty and they grade nothing. The evidence is on the pull request, which is what the badge label says and what the sentence now says too. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1f75812ad1..0c73c35e6e 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,10 @@ > repository-wide coverage or mutation percentage here, deliberately: the gates > grade **what a change touches**, not the total, because a total is a number > nobody has to defend and it improves when you measure less. `diff-coverage` -> and `mutation` are jobs inside the CI workflow, so the CI badge above goes red -> when either of them fails. What the two rules actually require is in -> [CLAUDE.md](CLAUDE.md). +> and `mutation` are jobs in the workflow the CI badge reports, and per PR is +> where they bite: on `main` the comparison base is `main`, so the diff is empty +> and there is nothing for them to grade. What the two rules actually require is +> in [CLAUDE.md](CLAUDE.md). ## This is a fork