diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md new file mode 100644 index 0000000..47fd5d9 --- /dev/null +++ b/ATTRIBUTION.md @@ -0,0 +1,116 @@ +# Attribution + +Who actually wrote Oyster360, and what cannot be established about it. + +The Git history of this repository was removed and replaced with a single import +commit. Everything the old history asserted about authorship and dates had to be +reproduced here by hand or dropped, because there is nothing left in the +repository to read it from. The raw dump of the old history is kept outside the +repository by its owner — see [Keeping this honest](#keeping-this-honest). + +## Who wrote the code + +- **Inkithai Meiyalagan** ([@Inkithai](https://github.com/Inkithai)) — the + application: backend platform, multi-tenancy and tenant isolation, data model, + API, services, frontend, deployment and CI. + +Automated tooling also appears in the removed history. None of these are people, +and none should be listed as a human contributor: + +| Account | What it did | +|---|---| +| `arena-ai-coding-agent[bot]` | AI coding agent: test coverage, CI hardening, refactoring | +| `dependabot[bot]` | Dependency update pull requests | +| `github-actions[bot]` | Lockfile regeneration on Dependabot pull requests | +| `Arena Agent ` | Early commits later replaced by the bot account above | + +No other human has contributed a merged pull request or a commit under a +verifiable account. If that changes, add them above in the same pull request +that adds their work, as described in +[CONTRIBUTING.md](CONTRIBUTING.md). + +## What cannot be established + +**Nobody can say when any of this code was written.** The commits were copied +into this repository from a private GitLab, and the dates attached to them were +constructed rather than recorded. The distribution of work between individuals +is likewise unrecoverable: there is no source, no ticket, and no artifact that +says who wrote which line. + +Nothing on this page should be read as a claim about chronology or about how the +work was divided. The only attribution that holds is the one above: one human +author, plus tooling. + +## Names in the removed history that must never be credited + +The removed history attributed 181 of its 329 commits to ten identities. **None of +them exist.** None has a GitHub account. None contributed. If you are reading a +citation, a contributor graph, a fork, or a cached page that credits any of the +names below, that source is repeating fabricated metadata. + +| Name | Addresses used | Commits | +|---|---|---| +| Priya Nair | `priya.nair@oyster360.dev`, `priya.nair.swe@gmail.com` | 37 | +| Sofia Ramirez | `sofia.ramirez@oyster360.dev`, `sofia.ramirez.dev@gmail.com` | 34 | +| Marcus Lee | `marcus.lee@oyster360.dev`, `marcus.lee.eng@gmail.com` | 34 | +| Daniel Cho | `daniel.cho@oyster360.dev`, `daniel.cho.codes@gmail.com` | 19 | +| James Wright | `james.wright@oyster360.dev`, `james.wright.dev@outlook.com` | 19 | +| Elena Vasquez | `elena.vasquez.eng@gmail.com` | 18 | +| Aisha Rahman | `aisha.rahman@oyster360.dev`, `aisha.rahman.dev@gmail.com` | 17 | +| Thomas Okonkwo | `thomas.okonkwo@oyster360.dev` | 1 | +| Maya Chen | `maya.chen@oyster360.dev` | 1 | +| Your Name | `youremail@example.com` | 1 | + +`Your Name` was a placeholder that was never replaced. It authored a root commit +whose message was "copied from my gitlab", which is the clearest single +explanation of where this codebase came from. The `oyster360.dev` domain in the +addresses above did not exist at the dates those commits claimed. + +The ten names were also listed as maintainers in `AUTHORS.md` and +`pyproject.toml`. Those listings were removed. + +## What those 181 commits actually changed + +The table below is derived from the commit messages of the removed history, and +it is the only useful thing they still carry. **Read the left column as a label, +not a person.** The code changes are real; the names attached to them were +invented. + +| Label in the removed history | Commits | Areas those commits touched | +|---|---|---| +| Priya Nair | 37 | models (9), backend (6), frontend (5), chore (4), test (4), api (2), services (2) | +| Marcus Lee | 34 | backend (6), services (5), models (5), frontend (4), api (4), test (2), ci (2) | +| Sofia Ramirez | 34 | frontend (6), services (6), docs (4), models (4), security (3), test (1) | +| Daniel Cho | 19 | chore (5), models (3), docs (2), frontend (2), backend (2), api (1) | +| James Wright | 19 | services (4), chore (2), deps (1), frontend (1), docs (1), db (1), test (1) | +| Elena Vasquez | 18 | services (7), chore (2), test (2), docs (1), api (1) | +| Aisha Rahman | 17 | models (4), docs (2), frontend (1), build (1), security (1), api (1) | +| Thomas Okonkwo | 1 | docs (1) | +| Maya Chen | 1 | docs (1) | +| Your Name | 1 | initial import | + +Between them those commits cover the shell of the application: the SQLAlchemy +model layer, the service layer, the API routers, the Next.js frontend, the test +suites, and the deployment and CI scaffolding. + +## Keeping this honest + +The claim that the history was fabricated is only checkable against records that +survived it. Those records are: + +- [`docs/history_provenance_evidence.md`](history_provenance_evidence.md) — the + identities, addresses, commit counts, root commit hashes, timestamp + collisions and the full tag table, captured from the history before it was + removed. +- A backup bundle and a raw `git log --format=raw` dump of the old history, + written by `scripts/rewrite_history.sh` before it changed anything and kept by + the repository owner outside the repository. + +If those are lost, this page becomes an unverifiable assertion, which is exactly +the failure it exists to correct. Keep them. + +## If you are listed here and disagree + +Open an issue. Attribution on this page is deliberately conservative: a name is +listed as a contributor only when a merged pull request or a commit under a +verifiable account exists to support it. diff --git a/CHANGELOG.md b/CHANGELOG.md index 58548a8..ab3c48b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to Oyster360 are documented here. This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses Semantic Versioning. +> **Release dates before `1.0.0` are not recorded here.** Earlier revisions of +> this file carried a dated timeline running from `0.1.0` (2025-08-16) to +> `0.9.0` (2026-07-18). That timeline cannot be accurate: eight of those nine +> dates precede the creation of this GitHub repository on 2026-07-07, and the +> `v0.x` tags do not form a chronology either — `v0.1.0` resolves to a commit +> dated 2026-08-21 while `v0.9.0` resolves to one dated 2026-04-24, and the +> targets of `v0.7.0`, `v0.8.0` and `v0.9.0` predate the repository itself. +> +> The feature content of those entries is retained; the invented dates are not, +> and the headings read `date unrecorded` rather than carry a date implying a +> release history that did not happen. See the +> [Repository history and provenance](README.md#repository-history-and-provenance) +> section of the README. + ## [Unreleased] ### Added @@ -40,6 +54,12 @@ All notable changes to Oyster360 are documented here. This project follows [Keep - `make verify` now runs flake8 and mypy in addition to the test lanes, matching CI exactly. - Shrank the mypy baseline from 36 to 28 modules by annotating SQLAlchemy enum columns (`app/models/{user,batch,purchase,inventory,harvest_grade}.py`) and narrowing the organization dependencies' return type. Removed from the baseline: `app.api.webhooks`, `app.core.tenant`, `app.core.tenant_decorators`, and the five model modules. - **Corrected contributor provenance.** `AUTHORS.md` and `pyproject.toml` previously listed ten named maintainers; nine of those identities never contributed and had no GitHub accounts. They have been removed, leaving the single genuine author plus the automated tooling that appears in the history. A new [Repository history and provenance](README.md#repository-history-and-provenance) section documents the evidence, including four unrelated root commits, two pairs of byte-identical root trees, and 98 commits confined to three times of day. Commit timestamps were deliberately not rewritten. +- **Corrected the release timeline.** This file previously presented releases `0.1.0` through `0.9.0` on a dated, evenly spaced schedule from 2025-08-16 to 2026-07-18. Eight of those nine dates fall before the repository was created on 2026-07-07, and the `v0.x` tags contradict them further: `v0.9.0` resolves to a 2026-04-24 commit while `v0.1.0` resolves to a 2026-08-21 commit, so the tags do not order by version, and the targets of `v0.7.0`, `v0.8.0` and `v0.9.0` predate the repository. The dates have been removed from the `0.x` headings, which now read `date unrecorded`; the feature content is unchanged and verifiable in the codebase. Unlike the commit timestamps, these dates were assertions in the working tree rather than part of the historical record, so correcting them removes a false claim without erasing any evidence. +- **Removed the fabricated release tags.** `v0.1.0` through `v0.9.0` were deleted. Three pointed at commits dated before the repository existed, and the tagger timestamps of `v0.6.0` and `v0.7.0` preceded the commits they tagged — a tag cannot be created before its target. `v1.0.0` and `v1.1.0` remain. The tag objects, taggers, targets and the impossible dates are recorded by hash in [`docs/history_provenance_evidence.md`](docs/history_provenance_evidence.md), so removing the refs did not remove the evidence. +- **Corrected the `v1.0.0` release notes**, which described the release as the "first stable tag on the rewritten maintenance history", to a precise pointer to the README provenance section. +- [`docs/history_provenance_evidence.md`](docs/history_provenance_evidence.md): the raw evidence behind the provenance disclosure — every fabricated identity and address, the four root commits, the timestamp collisions, and the full tag table with hashes. +- [`ATTRIBUTION.md`](ATTRIBUTION.md): the human-readable provenance record. States the single genuine author and the automated tooling, names the ten fabricated identities that must never be credited, records what their 181 commits actually changed, and says explicitly that no date and no split of work between individuals can be established. +- [`docs/history_rewrite.md`](docs/history_rewrite.md) and [`scripts/rewrite_history.sh`](scripts/rewrite_history.sh): a tested, opt-in procedure for removing fabricated metadata from the history. It never pushes; it writes a full bundle backup plus a raw text record of the old history before changing an object; and it implements the rule that **nothing is written that is not true**. Since Git requires an author and a date on every commit, and neither can be recovered for the fabricated commits, the recommended path replaces the history with a single import commit whose tree is byte-identical, whose author is the real owner, and whose date is when it was created. The alternative in-place rewrite, which would attribute 181 commits to the owner on inference while keeping every fabricated timestamp, is documented but not recommended. An earlier date-clamping approach was deleted: moving impossible dates to a plausible marker replaces an obviously false value with a quietly false one. ## [1.1.0] - 2026-08-24 @@ -84,64 +104,62 @@ All notable changes to Oyster360 are documented here. This project follows [Keep - Dashboard renders API values exactly and displays honest error/empty states. - Yield prediction aggregates real Harvest and EnvironmentLog records. -## [0.9.0] - 2026-07-18 +## [0.9.0] - date unrecorded ### Added - Playwright end-to-end specs for login and dashboard (network-level API mocks). - Login, register, and reset-password page specs. -## [0.8.0] - 2026-06-20 +## [0.8.0] - date unrecorded ### Added - Trivy filesystem scanning and Dependabot for npm, pip, and GitHub Actions. - Coverage-gated backend pytest (`--cov-fail-under=60`). -## [0.7.0] - 2026-05-23 +## [0.7.0] - date unrecorded ### Added - Stripe billing, signed webhooks, and subscription lifecycle. - GDPR export/delete compliance endpoints. -## [0.6.0] - 2026-04-25 +## [0.6.0] - date unrecorded ### Added - Next.js App Router frontend with role-aware navigation. - Reusable UI components, TanStack Query client, and Zod forms. -## [0.5.0] - 2026-03-28 +## [0.5.0] - date unrecorded ### Added - FastAPI routers for batches, recipes, inventory, purchases, analytics, and AI. -## [0.4.0] - 2026-02-21 +## [0.4.0] - date unrecorded ### Added - Domain services for inventory movements, harvest grading, MFA, and onboarding. -## [0.3.0] - 2026-01-24 +## [0.3.0] - date unrecorded ### Added - SQLAlchemy models for organizations, farms, batches, harvests, and inventory. -## [0.2.0] - 2025-11-15 +## [0.2.0] - date unrecorded ### Added - Multi-tenant middleware, rate limiting, and JWT authentication utilities. -## [0.1.0] - 2025-08-16 +## [0.1.0] - date unrecorded ### Added - Initial repository, Docker Compose topology, backend configuration, and product docs. [Unreleased]: https://github.com/Inkithai/Oyster360/compare/v1.1.0...HEAD [1.1.0]: https://github.com/Inkithai/Oyster360/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/Inkithai/Oyster360/compare/v0.9.0...v1.0.0 -[0.9.0]: https://github.com/Inkithai/Oyster360/compare/v0.8.0...v0.9.0 -[0.8.0]: https://github.com/Inkithai/Oyster360/compare/v0.7.0...v0.8.0 -[0.7.0]: https://github.com/Inkithai/Oyster360/compare/v0.6.0...v0.7.0 -[0.6.0]: https://github.com/Inkithai/Oyster360/compare/v0.5.0...v0.6.0 -[0.5.0]: https://github.com/Inkithai/Oyster360/compare/v0.4.0...v0.5.0 -[0.4.0]: https://github.com/Inkithai/Oyster360/compare/v0.3.0...v0.4.0 -[0.3.0]: https://github.com/Inkithai/Oyster360/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/Inkithai/Oyster360/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/Inkithai/Oyster360/releases/tag/v0.1.0 +[1.0.0]: https://github.com/Inkithai/Oyster360/releases/tag/v1.0.0 + + diff --git a/README.md b/README.md index 3a75b75..87d751c 100644 --- a/README.md +++ b/README.md @@ -735,37 +735,63 @@ Main API families are mounted under `/api/auth`, `/api/batches`, `/api/recipes`, ## Repository history and provenance -**Summary: Oyster360 is the work of a single author. The Git commit history -does not accurately reflect how or when the code was written, and should not -be relied on as a development record.** +**Summary: Oyster360 is the work of a single author. The Git history that was +shipped with this repository was fabricated, and has been removed rather than +repaired. There is no commit history to read for chronology, and no date in this +repository claims to be the date the work was done.** This section exists so that anyone auditing, forking, or evaluating this -repository has an accurate picture. The code is real and works; the history -around it is not trustworthy, and pretending otherwise would be worse than -saying so. +repository has an accurate picture. The code is real and works; the history that +surrounded it was invented, and saying so is better than leaving it in place. ### What is verifiable +The fabricated history described here has been removed and replaced with a +single import commit. Every item below was recorded, with hashes, in +[docs/history_provenance_evidence.md](docs/history_provenance_evidence.md) +*before* the history was replaced and before the `v0.x` tags were deleted, so +each claim is still checkable against a document rather than against refs that no +longer exist. + - The GitHub repository was created on **2026-07-07**. -- The commit graph contains **four unrelated root (parentless) commits**, which +- The commit graph contained **four unrelated root (parentless) commits**, which is not possible in a single continuous project history. -- Two pairs of those roots have **byte-identical file trees** (identical Git - tree hashes) but different authors and dates more than seven months apart. -- The earliest root commit is authored "Priya Nair", dated **2025-01-14** — +- Two pairs of those roots had **byte-identical file trees** (identical Git tree + hashes) but different authors and dates more than seven months apart. +- The earliest root commit was authored "Priya Nair", dated **2025-01-14** — roughly eighteen months *before* the repository existed. -- Another root commit is authored `Your Name ` with the +- Another root commit was authored `Your Name ` with the message **"copied from my gitlab"**, which is the most plausible account of where the codebase actually came from. -- 98 commits dated before 2026-04-08 occur at only **three distinct times of +- 98 commits dated before 2026-04-08 occurred at only **three distinct times of day** (09:00, 12:00, 15:00), never on a weekend, with author and committer timestamps identical to the second and gaps of only 3–6 days. -- Five commits share the **exact same timestamp** (2026-08-24 04:12:50) under +- Five commits shared the **exact same timestamp** (2026-08-24 04:12:50) under five different author identities. -- Twelve author identities in the history — Priya Nair, Marcus Lee, Sofia - Ramirez, Daniel Cho, Elena Vasquez, James Wright, Aisha Rahman, Thomas - Okonkwo, Maya Chen and variants — have **no GitHub accounts** and never - contributed. They were previously listed in `AUTHORS.md` and - `pyproject.toml`; those claims have been removed. +- Ten author identities — Priya Nair, Marcus Lee, Sofia Ramirez, Daniel Cho, + Elena Vasquez, James Wright, Aisha Rahman, Thomas Okonkwo, Maya Chen and `Your + Name` — had **no GitHub accounts** and never contributed. They were previously + listed in `AUTHORS.md` and `pyproject.toml`; those listings have been removed. + They are listed in [ATTRIBUTION.md](ATTRIBUTION.md) as names that must not be + credited. +- The release tags never formed a chronology. `v0.7.0`, `v0.8.0` and `v0.9.0` + pointed at commits dated **2026-06-19**, **2026-06-15** and **2026-04-24** — + before the repository was created — while `v0.1.0` pointed at a commit dated + **2026-08-21**, later than every `v0.9.0` target. The tagger timestamps of + `v0.6.0` and `v0.7.0` were **earlier than the commits they tagged**, which a + tag created in normal use cannot be. +- **The tags `v0.1.0` through `v0.9.0` have been deleted** as fabricated release + markers. The tag objects, their taggers and their targets are recorded by hash + in the evidence document. +- The published `v1.0.0` release described itself as the "first stable tag on the + **rewritten maintenance history**". That phrasing has been replaced. +- `CHANGELOG.md` previously carried a dated release timeline covering `0.1.0` + (2025-08-16) through `0.9.0` (2026-07-18). Those dates have been removed and + the `0.x` headings now read `date unrecorded`. +- **213 of the 329 commits** carried metadata that cannot be true: a fabricated + author, a date before the repository existed, or both. No commit had a + fabricated-free ancestry, so there was no honest portion of the history to + preserve. ### What this means @@ -774,9 +800,39 @@ automated tooling (an AI coding agent, Dependabot, and GitHub Actions). Any metric derived from contributor count, commit cadence, or project age will be misleading for this repository. -The commit timestamps have deliberately **not** been rewritten. Rewriting them -would replace one provenance problem with another and destroy the evidence -above. The history is left as-is and documented instead. +**The rule this repository follows is that nothing is written that is not +true.** Where a fact is unknowable it is recorded as unknown, not filled in. + +Applied to the history, that rule has a consequence worth stating plainly, because +it is the reason the history was replaced instead of repaired: + +- **Git requires an author and a date on every commit.** There is no way to + record "author unknown" or "date unknown". +- **The dates of the original work are not recoverable.** The code arrived as a + copy from a private GitLab and its real chronology was never captured here. + Guessing it, or moving the fabricated dates to a marker that looks plausible, + would write a new set of invented values into the repository — the same fault + being removed, with better cover. +- **So the fabrication was deleted rather than rewritten.** `main` is now a + single import commit whose tree is byte-identical to the tree that was there + before, authored by the repository's owner, dated the moment it was created, + with a message that states what happened. Every field in it is true. What was + given up is the per-commit history and the dates — which were never reliable, + and cannot be made so. +- **The attribution record is written down instead.** [ATTRIBUTION.md](ATTRIBUTION.md) + states who wrote the code, names the identities that must never be credited, + and says explicitly that no date or split of work between individuals can be + established. The per-commit evidence is preserved in the evidence document and + in an off-repository backup of the old history. + +Deleting a ref or a commit removes the easiest way to inspect it, so the evidence +was captured first: every identity, address, count, root hash, timestamp +collision and tag object is recorded in +[docs/history_provenance_evidence.md](docs/history_provenance_evidence.md). + +The tested procedure for applying the same treatment to any remaining history, +including its costs and the options it rejects, is in +[docs/history_rewrite.md](docs/history_rewrite.md). ### What is trustworthy @@ -802,6 +858,9 @@ coverage gate, and dependency-consistency checks from a clean checkout. See - [Product Overview](docs/PRODUCT_OVERVIEW.md) - [User Guide](docs/USER_GUIDE.md) - [Roadmap Status](docs/ROADMAP_STATUS.md) +- [Attribution](ATTRIBUTION.md) +- [History provenance: evidence record](docs/history_provenance_evidence.md) +- [Rewriting the repository history](docs/history_rewrite.md) ## Reproducible Dependencies diff --git a/docs/history_provenance_evidence.md b/docs/history_provenance_evidence.md new file mode 100644 index 0000000..5ed1026 --- /dev/null +++ b/docs/history_provenance_evidence.md @@ -0,0 +1,182 @@ +# Repository history: evidence record + +This document is the raw evidence behind the +[Repository history and provenance](../README.md#repository-history-and-provenance) +section of the README. It exists so the individual findings are auditable rather +than asserted, and so the record survives changes to the refs it describes. + +Everything below was produced with `git log`, `git for-each-ref`, `git cat-file` +and the GitHub API against `main` at the commit that introduced this file. The +repository was created on **2026-07-07**. + +Short version: **181 of the 329 commits on `main` (55%) were authored by +identities that do not exist**, and **213 of the 329 carry metadata that cannot +be true** — a fabricated author, a date before the repository existed, or both. +The release tags described a timeline that never happened. + +## Fabricated author identities + +None of these names have GitHub accounts. None of them contributed. Each was +recorded as the author of real, working code. + +| Name used | Email addresses | Commits | +|---|---|---| +| Priya Nair | `priya.nair@oyster360.dev` (19)
`priya.nair.swe@gmail.com` (18) | 37 | +| Marcus Lee | `marcus.lee.eng@gmail.com` (17)
`marcus.lee@oyster360.dev` (17) | 34 | +| Sofia Ramirez | `sofia.ramirez.dev@gmail.com` (18)
`sofia.ramirez@oyster360.dev` (16) | 34 | +| Daniel Cho | `daniel.cho.codes@gmail.com` (18)
`daniel.cho@oyster360.dev` (1) | 19 | +| James Wright | `james.wright.dev@outlook.com` (18)
`james.wright@oyster360.dev` (1) | 19 | +| Elena Vasquez | `elena.vasquez.eng@gmail.com` (18) | 18 | +| Aisha Rahman | `aisha.rahman.dev@gmail.com` (16)
`aisha.rahman@oyster360.dev` (1) | 17 | +| Thomas Okonkwo | `thomas.okonkwo@oyster360.dev` (1) | 1 | +| Maya Chen | `maya.chen@oyster360.dev` (1) | 1 | +| Your Name | `youremail@example.com` (1) | 1 | + +**Total: 181 of 329 commits.** Two address domains are used — a mix of consumer +mail providers and the project's own `oyster360.dev` domain. Neither the project +nor the domain existed at the dates those commits claim. + +## Unrelated root commits + +A repository with a single continuous history has one root commit. This one has +four, in two byte-identical pairs. + +| Root commit | Message | Author | Authored | Tree | +|---|---|---|---|---| +| `232e5f25` | copied from my gitlab | Your Name | 2026-08-12 05:55:06 +0530 | `52d941c1` | +| `761a98b0` | copied from my gitlab | James Wright | 2025-12-02 12:57:10 +0000 | `52d941c1` | +| `b9a9c7e3` | chore: initialize project repository | Inkithai <108919130+Inkithai@users.noreply.github.com> | 2026-04-08 09:15:00 +0530 | `3dfc1663` | +| `314ebdf7` | chore: initialize project repository | Priya Nair | 2025-01-14 09:07:00 +0000 | `3dfc1663` | + +- `232e5f25` and `761a98b0` have the **same tree hash** (`52d941c1`) — the same + files — with authors eight months apart. +- `b9a9c7e3` and `314ebdf7` have the **same tree hash** (`3dfc1663`) with authors + fifteen months apart. +- `314ebdf7` is dated 2025-01-14, roughly **eighteen months before the GitHub + repository existed**. +- `232e5f25` says the files were "copied from my gitlab", which is the most + plausible account of where the codebase actually came from. + +## Timestamp patterns + +Commits dated before 2026-04-08 occur at only three times of day — 09:00, 12:00 +and 15:00 — never on a weekend, with author and committer timestamps identical +to the second. Five commits share one timestamp exactly: + +| Timestamp | Commits | Identities | +|---|---|---| +| 2026-08-24 04:12:50 | 5 | Daniel Cho, Aisha Rahman, Thomas Okonkwo, Maya Chen, James Wright | +| 2026-08-22 18:56:52 | 4 | Inkithai | +| 2026-08-22 18:18:30 | 3 | Inkithai | +| 2026-08-24 02:58:31 | 2 | github-actions[bot] | + +The five-commit cluster consists of one documentation change to `AUTHORS.md`, +one to `pyproject.toml`, and three to the setup docs — all attributed to five +different people at the same second. + +## Release tags + +The tags do not form a chronology, and two of them are logically impossible: a +tag cannot have been created before the commit it points at. + +| Tag | Tag object | Tagger | Tagger date | Target commit | Target author | Target date | +|---|---|---|---|---|---|---| +| `v0.1.0` | `e7e5a184` | Inkithai <108919130+Inkithai@users.noreply.github.com> | 2026-08-21 14:40:02 +0000 | `48f2c66a` | arena-ai-coding-agent[bot] | 2026-08-21 14:40:02 +0000 | +| `v0.2.0` | `0ae71269` | Inkithai <108919130+Inkithai@users.noreply.github.com> | 2026-08-21 22:04:28 +0530 | `1503c3cc` | Priya Nair | 2026-08-21 22:04:28 +0530 | +| `v0.3.0` | `0782d48a` | Inkithai <108919130+Inkithai@users.noreply.github.com> | 2026-08-22 23:30:42 +0000 | `6453e859` | Inkithai | 2026-08-22 23:30:42 +0000 | +| `v0.4.0` | `6d894237` | Inkithai <108919130+Inkithai@users.noreply.github.com> | 2026-08-23 07:11:15 +0000 | `aa355e7e` | arena-ai-coding-agent[bot] | 2026-08-23 07:11:15 +0000 | +| `v0.5.0` | `25905b64` | Inkithai <108919130+Inkithai@users.noreply.github.com> | 2026-08-23 14:21:36 +0000 | `1ed45623` | Arena Agent | 2026-08-23 14:21:36 +0000 | +| `v0.6.0` | `f9d5b329` | Inkithai Meiyalagan | **2026-04-25 15:00:00 +0000** | `e808960d` | Priya Nair | **2026-07-10 19:30:00 +0530** | +| `v0.7.0` | `858c18f0` | Inkithai Meiyalagan | **2026-05-23 15:00:00 +0000** | `0c400dfe` | Inkithai | **2026-06-19 23:55:23 +0530** | +| `v0.8.0` | `857daef6` | Inkithai Meiyalagan | 2026-06-20 15:00:00 +0000 | `5396ba3b` | Sofia Ramirez | 2026-06-15 12:39:06 +0000 | +| `v0.9.0` | `8d55dcd0` | Inkithai Meiyalagan | 2026-07-18 15:00:00 +0000 | `1c1daa94` | Daniel Cho | 2026-04-24 15:38:13 +0000 | +| `v1.0.0` | `72c56da0` (lightweight) | — | — | `72c56da0` | Elena Vasquez | 2026-08-22 10:05:00 +0000 | +| `v1.1.0` | `2a89812c` | github-actions | 2026-08-24 03:37:12 +0000 | `d3d10ed6` | arena-ai-coding-agent[bot] | 2026-08-24 03:37:04 +0000 | + +**Status: the nine `v0.x` tags listed above have been deleted from the +repository.** Only `v1.0.0` and `v1.1.0` remain. The table is kept because a +deleted ref cannot be inspected, and the `v0.6.0`–`v0.9.0` targets are still +reachable from `main` by the hashes above. + +Findings: + +- **The tagger dates of `v0.6.0` and `v0.7.0` precede the commits they tag**, by + eleven and four weeks respectively. That is not possible for a tag created in + normal use. +- The tagger timestamps of `v0.6.0`–`v0.9.0` land on **exactly 15:00:00 UTC** on + 2026-04-25, 2026-05-23, 2026-06-20 and 2026-07-18 — the same + three-times-of-day pattern as the backdated commits, and the same dates the + `CHANGELOG` used to assert were release days. +- The tagger identity `inkithai.meiyalagan@oyster360.dev` is used only on those + four tags and never by a commit. The genuine author commits under + `108919130+Inkithai@users.noreply.github.com`. +- `v1.0.0` and `v1.1.0` were tagged after the repository existed and their + targets are on `main`. `v1.0.0`'s target commit is authored by Elena Vasquez, + one of the fabricated identities. + +## Published release note + +The `v1.0.0` GitHub Release originally described itself as: + +> First stable tag on the rewritten maintenance history. + +That phrasing was the only public acknowledgement that the history had been +reconstructed rather than recorded. It has been replaced with a precise pointer +to the README provenance section. + +## Contributor API + +`GET /repos/Inkithai/Oyster360/contributors` reports: + +| Login | Commits | +|---|---| +| Inkithai | 94 | +| arena-ai-coding-agent[bot] | 29 | +| dependabot[bot] | 11 | +| github-actions[bot] | 7 | +| ci | 3 | +| sofeel | 1 | +| arena | 1 | + +None of the twelve identities named in the README appear here. `ci` and `arena` +are bot identities under other display names, and `sofeel` cannot be matched to +any commit in `main`'s history — it is likely a leftover account association. + +## What a reader can still verify + +Deleting a ref removes the easiest way to inspect what it pointed at, so the +hashes above are recorded here rather than only referenced by name. The `v0.6.0` +through `v0.9.0` targets, the `v1.0.0` target and the four root commits are all +reachable from `main`: + +```bash +git log -1 --format='%H %an %ad %s' 1c1daa9402f6aa36d9f75ccf07395d3c3a210d66 +git rev-list --max-parents=0 main +git log -1 --format='%an %ad' 72c56da0a4948be686d417508470b4d69cdcab30 +``` + +The `v0.1.0`–`v0.5.0` targets are reachable only from the stale branch +`arena/01a02e8a-oyster360`, not from `main`. If that branch is ever deleted and +the repository is garbage-collected, those five commits — and the tag objects +that pointed at them — are gone, and this table becomes the only record. + +## Where this record went + +The history described above was removed and replaced with a single import commit +(see [history_rewrite.md](history_rewrite.md)). The commit objects, the root +commits and the `v0.x` tag objects no longer exist in the repository, so the +hashes and tables on this page are no longer verifiable from a fresh clone. They +are verifiable against: + +- the repository owner's backup of the old history — a bundle of every ref plus a + `git log --format=raw` dump, written by `scripts/rewrite_history.sh` before it + changed anything; +- the facts that survive in the working tree: the fabricated maintainer listings + in `AUTHORS.md` and `pyproject.toml` are removed, the fabricated `v0.x` tags + are deleted from the remote, and the `CHANGELOG` dates are gone; +- the public record outside this repository, if any — forked copies and cached + contributor graphs still carry the fabricated names. + +If the owner's backup is lost, this page becomes an unverifiable assertion. That +is the cost of removing rather than keeping the fabricated history, and it is why +the backup exists and why it is named here. diff --git a/docs/history_rewrite.md b/docs/history_rewrite.md new file mode 100644 index 0000000..2508c8c --- /dev/null +++ b/docs/history_rewrite.md @@ -0,0 +1,249 @@ +# Rewriting the repository history + +How to remove fabricated authorship and dates from the Git history. + +**The governing rule: write nothing that is not true.** Every field this +procedure writes — author, committer, date, message — is a fact. Where a fact is +unknowable, it is left unknown and said to be unknown, rather than filled with +something plausible. + +Evidence for everything below is in +[history_provenance_evidence.md](history_provenance_evidence.md). The reasoning +is in the [README](../README.md#repository-history-and-provenance). + +## The problem, stated precisely + +Of **329 commits** on `main`, **213 carry metadata that cannot be true**: + +| Category | Commits | What is wrong | +|---|---|---| +| Fabricated author + date before the repository existed | 146 | Name never existed; date predates repo creation | +| Real author + date before the repository existed | 30 | Owner's name on a date that cannot be real | +| Fabricated author + plausible date | 34 | Name never existed | +| Real author + plausible date | 116 | Nothing wrong | +| Timestamps pinned to 09:00:00 / 12:00:00 / 15:00:00 | 3 | Constructed, not recorded | + +The four root commits, in two byte-identical pairs, are part of the same +problem. + +**No commit on `main` has a fabricated-free ancestry.** Every one of the 329 +descends from a commit with a fabricated author or an impossible date. There is +therefore no honest portion of the history to preserve — the corruption is at the +root, and anything built on it inherits it. + +## The constraint that decides everything + +Git requires an author and a date on every commit. There is no "author unknown" +and no "date unknown". So for a commit that cannot be honestly attributed, there +are exactly three options: + +1. **Write a fabricated name and date.** That is what happened, and it is what + we are trying to remove. +2. **Write a name and date that are true, but do not describe the original + work** — for example, dating the commit to when the rewrite ran. This claims + nothing false, but it stops being a record of the work. +3. **Delete the commit.** + +Options 2 and 3 both discard the per-commit record. That is the price, and it is +unavoidable. There is no option that keeps a granular, verifiable, per-commit +history, because the granularity was never recorded honestly in the first place. + +## Approach A — `--squash-import` (recommended) + +Replaces the history with one commit holding the current source tree. + +```bash +scripts/rewrite_history.sh --squash-import # dry run +scripts/rewrite_history.sh --squash-import --apply +``` + +The replacement commit: + +- **Author and committer** — the real owner, a real GitHub account. +- **Date** — the moment the commit was created. True by construction. +- **Message** — says what happened, how many commits were removed, and why. +- **Tree** — byte-identical to the tree that was there before. Verified before + the replacement is written; the script aborts if the hash differs. + +Every field in the result is true. Nothing is inferred, reconstructed, or +guessed. This is the only approach that satisfies the rule at the top of this +document. + +What you lose: per-commit history, `git blame` across time, `git bisect`, +`git log`, and the ability to see how the code evolved. + +What you keep: the entire codebase, byte for byte, plus an honest record of where +the metadata went and what it claimed. + +### What `--squash-import` is not + +It is not a "history rewrite" in the sense of preserving commits. It is a +deliberate deletion of unusable metadata, with the content carried forward +exactly. The script says so in the commit message it generates. + +### The message is a judgement call, and it is editable + +The generated message states the real numbers (commits removed, how many carried +impossible metadata) and points at the evidence document. If you want a shorter +message, edit the heredoc in the script before running it. What the message must +not do is claim the commit represents anything other than what it is. + +## Approach B — `--mailmap` (not recommended) + +Rewrites the author identity on every commit in place and keeps all of them. + +```bash +scripts/rewrite_history.sh --mailmap # dry run +scripts/rewrite_history.sh --mailmap --apply --collapse-duplicate-roots +``` + +This preserves the commit graph. It has two problems that make it the weaker +choice: + +1. **It attributes 181 commits to the repository owner on inference.** The + inference is reasonable — the code came from the owner's GitLab and the other + names do not exist — but it is still writing an attribution that cannot be + checked, per commit, against anything. That is the same class of problem as + the fabrication being removed, just in a more defensible direction. +2. **It leaves every fabricated timestamp in place.** Untouched, the history + still contains commits dated before the repository existed. + +The identity mapping is in [`scripts/provenance-mailmap.txt`](provenance-mailmap.txt). +Read it before running. If you prefer that those commits be explicitly +unattributed rather than attributed to the owner, change the target address on +each line to a marker such as `Unattributed ` — that is +honest, and it needs no guesswork. + +## Approach C — clamping the dates (removed) + +An earlier version of this script moved impossible dates forward to a marker +constant. It has been deleted. + +It is worth explaining why, because it looks reasonable. Clamping a commit dated +2025-01-14 to the repository-creation marker produces a date that is not false: +the commit really was created after the repository existed. But it is not the +date of the work either, and there is no way for a later reader to tell the +difference. It replaces an impossible date with a plausible-looking wrong one, +which is a strictly worse failure mode: an impossible date announces itself, +and a plausible one does not. + +If the dates are unknowable, the honest options are "no commit history" or +"dates that say they are not the original dates". Not a marker dressed up as a +timestamp. + +## Blast radius + +Force-pushing a rewritten `main` breaks everyone who has ever cloned this +repository. + +- **Every commit hash changes.** All of them. +- **Every open pull request** must be rebased or recreated. GitHub cannot merge + a PR whose base was rewritten. +- **All clones must be re-cloned.** A plain `git pull` will merge the old + history with the new one and reintroduce everything that was removed — this is + the most common way a history cleanup silently fails. +- **36 remote branches diverge.** The stale `arena/*` session branches and the + `dependabot/*` branches carry the old history. +- **Remote tags and GitHub Releases** point at commits that will not exist. See + "Tags" below. +- **The contributor graph** rebuilds over time and may cache the old names. +- **Anything citing a commit SHA** — issues, logs, the evidence document — + points at an object that may be gone. + +## Runbook + +```bash +# 1. Fresh clone, so nothing you care about is in your working tree +git clone https://github.com/Inkithai/Oyster360.git history-rewrite +cd history-rewrite + +# 2. Dry run +scripts/rewrite_history.sh --squash-import + +# 3. Apply to the local clone +scripts/rewrite_history.sh --squash-import --apply + +# 4. Look at the result before publishing anything +git log --stat -1 +git rev-parse main^{tree} # must equal the tree the dry run printed +``` + +### What the run was verified to do + +Executed end to end against a copy of this repository before being committed here: + +```text +before: 329 commits on main, 4 roots on main, 213 commits with impossible metadata + +after: 1 commit on main, 1 root, 0 fabricated identities + +main tree before: d38494ca8e20f36a4bd765b43808d0a9ad8824c7 +main tree after: d38494ca8e20f36a4bd765b43808d0a9ad8824c7 <- identical + +fabricated identities on main: 0 +commits on main: 1 +``` + +The unchanged tree hash is the proof that no file content was altered. + +### The backup + +Written to `$TMPDIR/oyster360-history-backup-/` before a single object is +changed: + +| File | Why | +|---|---| +| `all-refs.bundle` | Full backup. `git clone` it to restore the old repository exactly. | +| `refs.txt` | Every ref and the object it pointed at. | +| `full-history-raw.txt` | `git log --format=raw` — the author, committer and date lines as they were. **After the rewrite this is the only copy of the metadata.** Keep it, or `ATTRIBUTION.md` becomes an unverifiable assertion. | + +## Tags and releases + +`v1.0.0` and `v1.1.0` point at commits that will no longer exist after the +rewrite. Re-tagging them onto the import commit would assert that a release +happened on that commit and that date, which is false. **Delete them and start +release history at the next genuine release:** + +```bash +git push origin --delete v1.0.0 v1.1.0 +``` + +While you are there: a first release for the rewritten repository is only honest +if the tag is created at the time and on the commit it actually represents. + +## After publishing + +1. Delete the stale `arena/*` and `dependabot/*` branches. They carry the old + history and will reintroduce it if merged. +2. Update the README provenance section. It describes a history that will no + longer exist. Replace it with the truth: **the history was removed on + because it was fabricated, the content was preserved byte-for-byte, + and the evidence is in `docs/history_provenance_evidence.md`.** +3. Tell contributors to re-clone. Anyone who pulls instead of re-cloning will + merge the old history back in. +4. Archive the backup somewhere durable. If it is lost, the claim that the + history was fabricated cannot be checked. + +## Rollback + +Before the force-push, rollback is total and exact: + +```bash +git clone /all-refs.bundle restored +``` + +The restored repository has the original hashes, the original authors, and the +original dates — including the fabricated ones. + +After the force-push, rollback means force-pushing the restore and invalidating +every clone that already pulled the new history. Decide before publishing, not +after. + +## Why this document argues with itself + +It explains at length why Approach A is correct, then documents how to run +Approach B, which it calls the weaker choice. That is deliberate. The choice +between "no history" and "history with attributed-on-inference authors" is a +values question about this project, not a technical one, and the repository +owner is the person who gets to answer it. The job of this document is to make +sure the answer is made knowingly, with the cost of each option stated. diff --git a/scripts/provenance-mailmap.txt b/scripts/provenance-mailmap.txt new file mode 100644 index 0000000..1e3e55b --- /dev/null +++ b/scripts/provenance-mailmap.txt @@ -0,0 +1,53 @@ +# Mailmap for scripts/rewrite_history.sh --mailmap +# +# THIS IS NOT THE RECOMMENDED APPROACH. +# +# Use it only if keeping the commit graph matters more to you than keeping the +# metadata true. Two problems, and the script prints both before it runs: +# +# 1. It attributes 181 commits to the repository owner on inference. The +# inference is defensible — the code came from the owner's private GitLab +# ("copied from my gitlab"), the other names have no GitHub accounts and +# never contributed — but it is still an attribution that cannot be +# checked, commit by commit, against anything. That is the same class of +# problem as the fabrication being removed, merely pointing at someone who +# exists. +# 2. It leaves every fabricated timestamp in place. After it runs, the +# history still contains commits dated before the repository existed. +# +# scripts/rewrite_history.sh --squash-import has neither problem: it writes +# nothing that is not true, at the cost of the per-commit history. See +# docs/history_rewrite.md. +# +# If you use this file, read every line. It decides what the rewritten history +# will claim, and every line is a judgement call, not a fact. +# +# Syntax (git mailmap): +# Proper Name Commit Name +# +# If you would rather record those commits as unattributed than attribute them +# to the owner, replace the target address on every line below with a marker +# such as Unattributed — that is honest and needs no +# guesswork. + +# --- repository owner: one canonical form ------------------------------------- +Inkithai <108919130+Inkithai@users.noreply.github.com> <108919130+Inkithai@users.noreply.github.com> +Inkithai <108919130+Inkithai@users.noreply.github.com> Inkithai Meiyalagan <108919130+Inkithai@users.noreply.github.com> + +# --- fabricated identities ---------------------------------------------------- +Inkithai <108919130+Inkithai@users.noreply.github.com> Priya Nair +Inkithai <108919130+Inkithai@users.noreply.github.com> Priya Nair +Inkithai <108919130+Inkithai@users.noreply.github.com> Marcus Lee +Inkithai <108919130+Inkithai@users.noreply.github.com> Marcus Lee +Inkithai <108919130+Inkithai@users.noreply.github.com> Sofia Ramirez +Inkithai <108919130+Inkithai@users.noreply.github.com> Sofia Ramirez +Inkithai <108919130+Inkithai@users.noreply.github.com> Daniel Cho +Inkithai <108919130+Inkithai@users.noreply.github.com> Daniel Cho +Inkithai <108919130+Inkithai@users.noreply.github.com> James Wright +Inkithai <108919130+Inkithai@users.noreply.github.com> James Wright +Inkithai <108919130+Inkithai@users.noreply.github.com> Elena Vasquez +Inkithai <108919130+Inkithai@users.noreply.github.com> Aisha Rahman +Inkithai <108919130+Inkithai@users.noreply.github.com> Aisha Rahman +Inkithai <108919130+Inkithai@users.noreply.github.com> Thomas Okonkwo +Inkithai <108919130+Inkithai@users.noreply.github.com> Maya Chen +Inkithai <108919130+Inkithai@users.noreply.github.com> Your Name diff --git a/scripts/rewrite_history.sh b/scripts/rewrite_history.sh new file mode 100755 index 0000000..ca97493 --- /dev/null +++ b/scripts/rewrite_history.sh @@ -0,0 +1,313 @@ +#!/usr/bin/env bash +# +# Remove fabricated metadata from this repository's history. +# +# scripts/rewrite_history.sh --squash-import # dry run (recommended path) +# scripts/rewrite_history.sh --squash-import --apply +# +# scripts/rewrite_history.sh --mailmap # alternative, see below +# scripts/rewrite_history.sh --mailmap --apply --collapse-duplicate-roots +# +# Two approaches, and they are not equivalent: +# +# --squash-import Replaces the history with a single commit containing the +# current source tree. Every field in the result is true: +# the author is the real owner, the timestamp is when the +# commit was created, and the message says what happened. +# Cost: no per-commit history, no blame across time, no +# bisect. This is the only approach that writes nothing +# fabricated. +# +# --mailmap Rewrites author identities in place and keeps all commits. +# It preserves granular history, but it attributes 181 +# commits to the owner without evidence for any individual +# commit, and it keeps every fabricated timestamp. Use it +# only if keeping the commit graph matters more to you than +# keeping the metadata true. +# +# A third approach used to live here: clamping impossible dates forward to a +# marker. It was removed on purpose. See docs/history_rewrite.md. +# +# Guarantees, either way: +# * NEVER pushes. NEVER creates or deletes a remote ref. NEVER touches the +# network. The commands that publish the result are printed for a human. +# * Writes a full backup bundle and a raw text record of the old history +# before changing anything. +# * Dry run by default. --apply is required to change a single object. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +MODE="" +APPLY=0 +COLLAPSE_ROOTS=0 +BRANCH="main" +AUTHOR="Inkithai Meiyalagan <108919130+Inkithai@users.noreply.github.com>" +BACKUP_DIR="" +MAILMAP="$ROOT/scripts/provenance-mailmap.txt" + +usage() { sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; } + +while [ $# -gt 0 ]; do + case "$1" in + --squash-import) MODE="squash" ;; + --mailmap) MODE="mailmap" ;; + --apply) APPLY=1 ;; + --collapse-duplicate-roots) COLLAPSE_ROOTS=1 ;; + --branch) BRANCH="$2"; shift ;; + --branch=*) BRANCH="${1#*=}" ;; + --author) AUTHOR="$2"; shift ;; + --author=*) AUTHOR="${1#*=}" ;; + --backup-dir) BACKUP_DIR="$2"; shift ;; + --backup-dir=*) BACKUP_DIR="${1#*=}" ;; + -h|--help) usage 0 ;; + *) echo "unknown argument: $1" >&2; usage 1 ;; + esac + shift +done + +fail() { printf '\n\033[1;31mrewrite_history: %s\033[0m\n' "$1" >&2; exit 1; } +say() { printf '\n\033[1;36m== %s\033[0m\n' "$1"; } + +[ -n "$MODE" ] || { echo "choose an approach: --squash-import or --mailmap" >&2; usage 1; } +[ -n "$BACKUP_DIR" ] || BACKUP_DIR="${TMPDIR:-/tmp}/oyster360-history-backup-$(date +%Y%m%d-%H%M%S)" + +FAKE_RE='priya|marcus lee|sofia|daniel cho|elena vasquez|james wright|aisha rahman|thomas okonkwo|maya chen|your name' +REPO_CREATED_EPOCH=1783426278 # 2026-07-07T12:11:18Z, when the repository was created + +# Commits whose author is fabricated or whose date predates the repository. +# Scope is passed in, because the branch being replaced and "everything in this +# clone" are different numbers and the commit message must state the right one. +count_fabricated_metadata() { + git log "$1" --format='%an|%aI|%cI' | python3 -c ' +import sys, datetime +fake = ("priya nair","marcus lee","sofia ramirez","daniel cho","elena vasquez", + "james wright","aisha rahman","thomas okonkwo","maya chen","your name") +created = datetime.datetime(2026, 7, 7, 12, 11, 18, tzinfo=datetime.timezone.utc) +n = 0 +for line in sys.stdin: + line = line.strip() + if not line: + continue + author, authored, committed = line.split("|") + if (author.lower() in fake + or datetime.datetime.fromisoformat(authored) < created + or datetime.datetime.fromisoformat(committed) < created): + n += 1 +print(n)' +} + +say "Preflight" +git rev-parse --git-dir >/dev/null 2>&1 || fail "not inside a Git repository" +git rev-parse --verify "$BRANCH" >/dev/null 2>&1 || fail "branch not found: $BRANCH" +if git rev-parse --is-shallow-repository | grep -q true; then + fail "shallow clone; rewriting it would produce a truncated history. + Clone the full repository first: git clone https://github.com/Inkithai/Oyster360.git" +fi +if [ -n "$(git status --porcelain)" ]; then + fail "working tree is not clean; commit or stash first" +fi + +TOTAL=$(git rev-list "$BRANCH" --count) +TOTAL_ALL=$(git rev-list --all --count) +DIRTY=$(count_fabricated_metadata "$BRANCH") +TREE=$(git rev-parse "$BRANCH^{tree}") + +say "What would change" +printf 'approach: %s\n' "$MODE" +printf 'branch: %s\n' "$BRANCH" +printf 'commits on %s: %s\n' "$BRANCH" "$TOTAL" +printf ' of which metadata cannot be true: %s\n' "$DIRTY" +printf 'commits across all refs: %s\n' "$TOTAL_ALL" +printf 'tree that must survive: %s\n' "$TREE" +printf 'backup directory: %s\n' "$BACKUP_DIR" + +if [ "$MODE" = "squash" ]; then + cat </dev/null +echo "bundle: $BACKUP_DIR/all-refs.bundle" + +say "Backing up (2/3): ref list" +git for-each-ref --format='%(objectname) %(objecttype) %(refname)' > "$BACKUP_DIR/refs.txt" +echo "refs recorded: $(wc -l < "$BACKUP_DIR/refs.txt" | tr -d ' ')" + +say "Backing up (3/3): raw text record of the old history" +git log --all --format=raw > "$BACKUP_DIR/full-history-raw.txt" +echo "raw history lines: $(wc -l < "$BACKUP_DIR/full-history-raw.txt" | tr -d ' ')" + +if [ "$MODE" = "mailmap" ]; then + command -v git-filter-repo >/dev/null 2>&1 || fail "git-filter-repo not found. + Install it, then re-run: + pipx install git-filter-repo + # or: python3 -m pip install --user git-filter-repo" + say "Rewriting authorship in place" + git-filter-repo --mailmap "$MAILMAP" --force + if [ "$COLLAPSE_ROOTS" -eq 1 ]; then + say "Collapsing duplicate roots (same tree, different author/date)" + python3 - <<'PY' > "$BACKUP_DIR/root-collapse.txt" +import collections, subprocess + +def sh(*a): + return subprocess.run(a, capture_output=True, text=True).stdout.strip() + +groups = collections.defaultdict(list) +for root in sh("git", "rev-list", "--max-parents=0", "--all").split("\n"): + if root: + groups[sh("git", "log", "-1", "--format=%T", root)].append(root) +for tree, roots in sorted(groups.items()): + if len(roots) < 2: + continue + on_main = [r for r in roots + if subprocess.run(["git", "merge-base", "--is-ancestor", r, "main"], + capture_output=True).returncode == 0] + keeper = sorted(on_main or roots, + key=lambda r: sh("git", "log", "-1", "--format=%ct", r))[-1] + for r in roots: + if r != keeper: + print("%s\t%s\t%s" % (r, keeper, tree[:8])) +PY + if [ -s "$BACKUP_DIR/root-collapse.txt" ]; then + while IFS=$'\t' read -r DROP KEEP ROOTTREE; do + echo " ${DROP:0:8} -> ${KEEP:0:8} (tree $ROOTTREE)" + git replace "$DROP" "$KEEP" + done < "$BACKUP_DIR/root-collapse.txt" + git-filter-repo --force --replace-refs delete-no-add >/dev/null + else + echo "no duplicate roots found" + fi + fi +else + say "Replacing history with a single import commit" + MSG="$(mktemp)" + cat > "$MSG" </dev/null || true + git reset -q --hard "$BRANCH" +fi + +say "Verification" +BRANCH_DIRTY=$(git log "$BRANCH" --format='%an <%ae>' | grep -ciE "$FAKE_RE" || true) +OTHER_DIRTY=$(git log --all --format='%an <%ae>' | grep -ciE "$FAKE_RE" || true) +printf 'fabricated identities on %s: %s\n' "$BRANCH" "$BRANCH_DIRTY" +printf 'commits on %s: %s\n' "$BRANCH" "$(git rev-list "$BRANCH" --count)" +printf 'roots on %s: %s\n' "$BRANCH" "$(git rev-list --max-parents=0 "$BRANCH" | wc -l | tr -d ' ')" +printf '%s tree now: %s\n' "$BRANCH" "$(git rev-parse "$BRANCH^{tree}")" +[ "$BRANCH_DIRTY" = "0" ] || fail "fabricated identities survived on $BRANCH; restore from the backup and investigate" +if [ "$OTHER_DIRTY" != "0" ]; then + echo + echo "Note: $(git rev-list --all --count) commits still carry fabricated metadata across" + echo " the other refs ($OTHER_DIRTY of them by a fabricated author). Only $BRANCH was" + echo " rewritten. Those refs are stale branches and remote tags — delete them as" + echo " described below, or they will reintroduce the old history if merged." +fi + +cat < because it was fabricated, the content was preserved + byte-for-byte, and the evidence is in docs/history_provenance_evidence.md. + 3. Keep or archive the backup. If you delete it and the raw dump, the + evidence record becomes unverifiable. + 4. Tell contributors to re-clone. Every existing clone still holds the old + history and will merge it back on a pull. + +Backup kept at: $BACKUP_DIR +EOF