Rotproof
Rotproof keeps a project's structure from drifting while LLMs and people change it, so that it does not rot. It keeps two structures:
- The layers: which part of the code may import which (
handler,application,domain,infrastructure,utils, and an optionalui). Code that cannot be split into these layers mixes responsibilities, so the layers are how an LLM, or a person, is made to split it: every piece of code has to land in a layer whose role and allowed imports are written down. Rotproof makes them when a project starts and checks them on every run, so the direction of dependencies stays what it was meant to be. - The specs and records: what is open, what is being changed, how things are now, and why. A backlog (open
defects and postponed work, each with a trigger, a state and a deadline), specs, knowledge documents (an API, a data
model, a decision, edited in place and named in the log at every edit), and a log, each kept to strict rules. A
finding does not stay outside them: a
TODOorNOTEin a code comment fails the check, and a stop hook sends an agent back when its last message leaves something open that it did not record.
Principles
- The declaration is the truth, and Rotproof does not repair. The structure a project declares is the structure. A tree that differs from it fails, either way, and someone decides whether the tree or the declaration is wrong; Rotproof changes the tree only when asked.
- Every rule has a check that can fail. A rule without a check is only a label, and soon drifts.
- A check with nothing to check fails. A check that passes on an empty tree protects nothing.
- The rules come with the tool. A project pins one version of Rotproof, and takes improvements to the rules by upgrading it, in a commit of its own.
- The same structure in every language. Rotproof is one binary with no language runtime, so a Rust or TypeScript project keeps the same layers and records as a Python one.
- Whoever picks up the work next reads one index, not every file. The index files are generated, never written by hand.
Rotproof makes the layer directories, checks that they are where the project declares them, and checks their direction: from the imports in Python and TypeScript, and from the dependencies each crate declares in Rust.
The records live in docs/, which is a bundle in OKF 0.2
(its SPEC.md as of commit ad30107): every document has YAML frontmatter with a type, index.md and log.md are
reserved names, and the log's headings are dates. An OKF reader can read the records as a bundle. The rules on top of
that format (a backlog item's trigger, state and deadline, a closed record opening with its resolution, a knowledge
document's hash in the log, the shape of a log entry) are Rotproof's own, stricter than OKF, and not part of it.
Rotproof is not an OKF validator.
Install
Rotproof is released for Windows x86_64 and Linux x86_64 (glibc 2.17 or newer). Pin the exact version, so that an upgrade, which can bring new rules, is a commit of its own:
pip install rotproof==0.1.0
The wheel carries only the binary; no Python code runs. Without Python, take the archive for your platform from
GitHub Releases and check it against SHA256SUMS there. Each archive
holds the binary with LICENSE-MIT, LICENSE-APACHE and THIRD-PARTY-LICENSES.txt.
On another platform, pip finds no Rotproof to install. Build it from source with rustup installed
(the toolchain version comes from rust-toolchain.toml):
cargo build --release # the binary is target/release/rotproof (rotproof.exe on Windows)
Usage
rotproof --root <repository> init --stack python # write .config/rotproof.toml, once
rotproof --root <repository> create # make the layers .config/rotproof.toml declares, the records skeleton and the guide
rotproof --root <repository> check # check the layers and the records; exits 1 when a rule is broken
rotproof --root <repository> index # write every generated file in docs/ (the index files and the rules)
rotproof guide --stack python # print the rules Rotproof keeps for a stack, with or without a project
rotproof stop-hook # run by Claude Code when the agent stops (see "The stop hook")
--root defaults to the current directory. The declaration is read from <repository>/.config/rotproof.toml, and the
records from <repository>/docs.
The binary leads the way without this README, for an agent that has only Rotproof: rotproof --help says what it is,
the order to start in and the exit codes, rotproof <command> --help what a command reads, writes and never does, and
each command's output names the next step. A test follows that path from an empty directory to a passing check.
Start a project with rotproof init --stack <stack> (python, typescript, rust, or none for a repository
that keeps records only). It writes only the declaration, so you declare in absent the layers you do not want before
anything is made, and it never overwrites a declaration that exists. Then run rotproof create.
A TypeScript project with React starts from Vite: run npm create vite@latest <name> -- --template react-ts first,
then rotproof init --stack typescript and rotproof create in it, which keep what Vite wrote. Vite's entry point,
src/main.tsx, is handler's where it is (index.html loads it); rotproof check names the rest of the starter
(App.tsx, the styles, assets/) as code outside the layers, with where each goes.
Run rotproof create when a project starts, and again after you change .config/rotproof.toml on purpose. It makes
only what is missing: a layer that is neither present nor declared absent, the project's files when they do not exist
(below), and the files Rotproof generates (.rotproof/AGENTS.md, and the index files and rules in docs/). It never
overwrites a file it does not generate, and never moves or deletes one. Nothing runs it on its own, so a layer removed
xtask/src/layers.rs in place of the file, and reads the comments of xtask/ for markers too, which the layout
leaves out; the records are kept outside, so they are not checked here.
The project's files are written once, as a starting point, and are the project's from then on:
| File | Content |
|---|---|
AGENTS.md |
The map (the layers present, with their roles to rewrite), the differences from what Rotproof keeps, and the project's own rules. It points at .rotproof/AGENTS.md |
CLAUDE.md |
@AGENTS.md and @.rotproof/AGENTS.md |
README.md |
The project's name (its root directory's) and how to run Rotproof |
.gitignore, .gitattributes |
For the stack; line endings as LF |
docs/log.md |
The log, with its title |
.claude/settings.json |
The stop hook (see "The stop hook") |
requirements-dev.txt |
python and none: Rotproof pinned with == |
.github/workflows/ci.yml |
python and none: installs requirements-dev.txt and runs rotproof check, with a time limit |
Cargo.toml |
rust: the workspace, whose members are the crates in crates/, so cargo build builds every layer present |
How a typescript or rust project pins Rotproof is not decided yet, so for those stacks rotproof create writes
neither the pin nor the workflow, and says so.
Run it also after upgrading Rotproof. When a newer Rotproof requires a field the declaration lacks, rotproof check
fails and says so, and rotproof create adds the field under a comment that says what it is and where its first value
came from (areas gets the tags the records use, sorted by name), keeping every comment and value already there. A
value that is present is never changed, so an upgrade fails only on what the new rules find.
Among the files Rotproof generates is .rotproof/AGENTS.md: the rules Rotproof keeps, written for the project's
stack. It says how to run Rotproof, lists the layers with where each lives and what it may import (from
layers/table.toml), and gives the rules of the records, starting with reading the index files before work. It names
the Rotproof version that wrote it, so after an upgrade rotproof check fails until rotproof create has rewritten
it. Edited by hand, it fails too: a project's own rules go in its own AGENTS.md.
The guide only helps if your agent reads it. The AGENTS.md and CLAUDE.md that rotproof create writes point at it;
a project that has its own points at it from its AGENTS.md (or whatever file its agent reads first), and imports it
in CLAUDE.md for Claude Code:
@AGENTS.md
@.rotproof/AGENTS.md
The stop hook
An agent's findings are lost when it reports them and stops: "not checked", "out of scope" in its last message, and
nothing in the backlog. rotproof stop-hook is Claude Code's Stop hook,
run when the agent stops, and reads that last message. When the message holds a phrase that leaves something open
and git status shows no change in docs/, the hook sends the agent back once, asking it to record the finding or to
say in one line where it already is. While the agent is
continuing because of a stop hook, the hook lets it stop, so it never loops. A line that points at the records (the
word spec, backlog or knowledge) is not read: what it leaves open is recorded where it points. The
phrases are built in (Japanese and English); the agent decides what each one meant.
rotproof create writes .claude/settings.json with the hook when it does not exist. A project that has one adds the
hook to it:
{
"hooks": {
"Stop": [{ "hooks": [{ "type": "command", "command": "rotproof stop-hook" }] }]
}
}
rotproof has to be on the PATH the agent runs hooks with (for a venv, start the agent with the venv active). The
project is the nearest directory upwards that holds .config/rotproof.toml; outside one, the hook says nothing. A
hook that fails (not a git repository, an input from another hook) exits 1, which both agents show without keeping
the agent from stopping; exit code 2 would keep it from stopping.
The layers
A project declares its structure in .config/rotproof.toml, the directory tools share for their configuration.
rotproof init writes it, and from then on it is the project's file. pip installs only the Rotproof binary: the layer
definitions are built into it, and the declaration is never shipped with it.
stack = "python" # python | typescript | rust | none
areas = ["billing", "records"] # the areas the records are grouped by, in this order (see The records)
absent = ["ui"] # layers this project does not have
unchecked = ["scripts"] # paths outside the layers that Rotproof does not look into
What the layers are (handler, ui, application, infrastructure, domain, utils, and the atomic levels of
ui: pages, templates, organisms, molecules, atoms) is written once, in layers/table.toml. Where they live
is written once per stack:
| Stack | A layer is | A ui level is |
Code Rotproof looks at |
|---|---|---|---|
python |
<layer>/__init__.py, the role as its docstring |
ui/<level>/__init__.py |
.py files anywhere (.PY too), except tests/ |
typescript |
src/<layer>/index.ts, the role as a doc comment |
src/ui/<level>/index.ts (React) |
every file in src/; src/main.tsx (Vite's entry point) is handler's |
rust |
a crate, crates/<layer>/ (handler a binary), the role as //! |
none: Rust has no ui yet |
every crate in crates/ |
stack = "none" declares a repository that keeps records only: rotproof create makes only docs/, and Rotproof check checks only the records and prints that it did not check the layers. It is a line in the declaration, not a
flag, so the structure check is never switched off where the declaration still declares layers.
A level of ui is declared absent by its dotted name (absent = ["ui.templates"]). Files .gitignore excludes and
hidden files are not looked at, so a virtual environment or a build directory is not code. A code file is matched in any
case (stray.PY, cargo.toml): Windows runs or reads it all the same.
The records
docs/
index.md generated
log.md what was done, newest first
backlog/
rules.md generated: the backlog rules (type: Guide)
index.md generated
<slug>.md one item per file (type: Backlog Item)
specs/ every spec, open or closed (type: Spec, status: draft, stable or deprecated)
rules.md generated: the spec rules (type: Guide)
index.md generated
knowledge/ how things are now, and why (type: Knowledge, status: stable or deprecated)
rules.md generated: the knowledge rules (type: Guide)
index.md generated
The three directories answer three questions: what is being changed (specs/, closed once implemented or dropped),
what is open (backlog/, closed once dealt with), and how things are now (knowledge/, edited in place, deprecated
only when it no longer holds). A closed spec is history; what it built is described in knowledge/, an API or a data
model, or why something was decided. Every edit of a knowledge document is named in the log by a hash of its
contents, under the label **Knowledge** (* **Knowledge**: knowledge/api.md@a3f9c1d2), and rotproof check fails
an edit the log does not name.
A record stays where it was written when it closes: its status says it is closed, and the index lists it under
# Closed. Its path, and every link to it, never changes, so closing a record is a change to that record and its
index line only.
Every backlog item and every spec belongs to exactly one area: its only tag, one of the areas the declaration
lists. The index files group by area, in the order of areas, so the project puts the largest or most active area
first. An area says where a record belongs (the layers, the records, billing), and never closes. A declared area that
no record uses passes, so an area is declared before its first record. Renaming an area is editing areas and the tag
of every record in it, closed ones included: the tag is frontmatter for the index, not history. Guides keep their
optional tags, which name no area.
A large piece of work is split into specs that are parts of an epic. The epic is a spec like any other, with goals and
an order of work of its own; a part names it by slug (its file name without .md) in epic:
type: Spec
title: Publish Rotproof for every stack
status: stable
tags: [rotproof]
epic: template-multi-stack
The two are independent: an area says where a record belongs and never closes, an epic says which piece of work a
spec is part of and closes when the work is finished. A part may be in another area than its epic. A spec that cannot
name one area mixes two, so it is split into one part per area, and the epic ties the parts back into one piece of
work. In an index, a part in the same area as its epic, and open or closed as its epic is, is listed under it,
indented; any other part is listed on its own with Epic: [<title>](...) after its line, so each spec appears once.
A backlog item has this frontmatter and these body headings:
---
type: Backlog Item
title: Some problem
description: One sentence: what the problem is.
tags: [area] # exactly one, declared in areas; the index groups items by it
status: stable # stable = open, deprecated = closed
filed: 2026-10-01
verified: {by: human:someone, at: 2026-10-01T10:00:00+09:00}
deadline_kind: until # until, or none with the reason in deadline
deadline: until the next deploy
stale_after: 2027-01-01T00:00:00+09:00 # optional: when to measure the state again
---
# Resolution (only when closed, and then first)
# Trigger
# State
# Details
What rotproof check checks
-
The tree matches
.config/rotproof.toml, either way: the declaration exists and names a known stack, and only layers that stack has inabsent(a misspelled field fails). Every layer of the stack is present or declared absent, and no layer declared absent is present. No code sits outside the layers, the stack's own paths (tests/) andunchecked. A path inuncheckedexists and neither holds nor sits in a layer, so a layer cannot be switched off by listing it. At least one layer is present: with every layer declared absent, nothing would be checked.uiholds only its levels: code inuibeside them fails, apart from the layer's own file (ui/__init__.py). What the whole UI shares goes in a level: a part that knows no project concept, visible or not (a design value, one behaviour, a provider of a theme), is an atom. -
The layers import only what the table allows (Python): every
importandfrom ... importin the layers, relative ones and those inside functions or underif TYPE_CHECKING:included. A layer may import itself and the layers in itsimports; a level ofuithe levels below it and the layers in itsimports. Only direct imports are judged: what the table allows is closed under chaining, so a chain of allowed imports never reaches a forbidden layer. A module is placed where its parts, as a path, land as the operating system that runs the check resolves them: on Windows,Infrastructure.dblands ininfrastructure, as Python imports it withPYTHONCASEOKset. Imports of modules in no layer (the standard library, packages) are not judged, and imports built at run time (importlib) are not seen. A file that is not UTF-8 or has a syntax error fails, since its imports cannot all be read. -
The layers import only what the table allows (TypeScript): every
import(import typetoo),export ... from,import x = require(...), andimport(...)andrequire(...)with a literal string, in the.ts,.tsx,.jsand.jsxfiles (and.mts,.cts,.mjs,.cjs) of the layers, read with oxc. The place of an import is the path it lands on, whether or not a file is there: a relative specifier from the file, one starting with/from the root (as Vite reads it), and any other throughcompilerOptions.pathsof thetsconfig*.jsonfiles at the root (and the local files they extend), then through theirbaseUrlwhen a module is there; otherwise it names a package, which is not judged. The path is placed where it lands as the operating system that runs the check resolves it: on Windows,../Infrastructure/db,../infrastructure./dband a short name (../INFRAS~1/db) land ininfrastructure, as Vite builds them, and links are followed everywhere. Atsconfig*.jsonthat cannot be read, or apathsentry with more than one*, fails rather than leaving its aliases unjudged. Two limits: only the first target of apathsentry is used, and a config extended from a package is not read, so an alias defined only there is taken for a package. -
The layers import only what the table allows (Rust): a crate is compiled only against the crates its
Cargo.tomldeclares, so the declarations are read instead ofuse: every dependency in[dependencies]and[build-dependencies](andbuild_dependencies), under[target.<cfg>]too, of theCargo.tomlfiles in the layers. The place of a dependency is where the path it comes from lands: itspathfrom the crate, or withworkspace = truethepathof its entry in[workspace.dependencies]from the workspace, which is the directory[package] workspacenames, or the nearest one up to the root that declares[workspace]. A path lands where the operating system that runs the check resolves it, as Cargo's does: on Windows, a path in another case, with dots or spaces at its end or with a short name (DOMAIN~1) lands indomain, and links are followed everywhere. A path that lands nowhere, where Cargo fails too, or outside the root is not judged. A renamed dependency (package) is placed by its path all the same.[dev-dependencies]serve the tests and are not judged; dependencies from a registry or git are not judged. ACargo.tomlthat is not UTF-8 or TOML fails, and so does a dependency taken from a workspace that does not declare it. Not seen:[patch]and[replace], and source files taken from another crate's directory (#[path],include!,[lib] path). -
No comment holds
TODO,FIXME,XXX,HACKorNOTE: in upper case, as whole words, in any code file outsideunchecked(a path there that holds or sits in a layer skips nothing),tests/included; in TypeScript, the//and/* */comments of the source files insrc/, JSX text not counted; in Rust, the//and/* */comments (doc comments too) of every.rsfile incrates/, a crate'stests/andbuild.rsincluded, read by Rotproof's own scanner, which skips strings, raw strings and character literals as Rust's lexer does. Work left to do belongs in the backlog, where it is listed and closed, and a decision with its reason in the spec or the log entry of the change; a comment that explains how to read the code stays, without the word. Only comments count:Status.TODOand"XXX-XXXX"are not markers, and docstrings are strings. Comments in other files (a stylesheet, aCargo.toml) are not read. -
The bundle is there:
docs/,docs/index.md,docs/backlog/,docs/backlog/rules.md,docs/specs/,docs/specs/rules.md,docs/knowledge/anddocs/knowledge/rules.mdexist, and the declaration with itsareascan be read. Without them every other check would pass with nothing checked. -
Every name is compared exactly, wherever Rotproof looks for a file or a directory: a link's target, the files above, the layers and the declaration. Rotproof reads the names the directories hold instead of asking the operating system, which on Windows also finds
README.mdforreadme.md,README.md.,README.md, a stream (README.md:secret) or a short name (README~1.MD). Linux and GitHub find none of them, so each fails, and the message says what the disk has. -
The areas are distinct headings: none is empty, has a space at either end or a line break, and no two differ only in case.
-
Every backlog item keeps the format: the fields above with their types, and non-empty Trigger, State and Details (and Resolution when closed, as the first heading). A field Rotproof does not know passes as an extension, as OKF allows, unless it looks like a misspelling of a field the document type has (
stale_afer,staleAfter,Title), or is a field only another type has (a backlog item'sdeadlineon a spec, a spec'sepicon a backlog item): those fail, in every document type, as a misspelled optional field would otherwise be silently dropped. A deadline is an event or a reason, never only a date (2026-10-31,2026/10/31,31.10.2026,2026年10月31日or a month alone); an event may contain a date. Every time has a time zone.stale_afteris later than the lastverified. A required text is not blank (spaces alone are empty), in every document type, and no tag is empty. What the index lists (title,description,deadlineand the tags) is on one line: a line break would end the entry and start a heading or an entry of its own. A title is written into the index with[,]and\escaped, so it stays the text of its own link. The one tag is a declared area, and the message lists the declared ones. -
Every link in
# Detailsresolves, with heading anchors computed as GitHub computes them. A path with a drive letter (C:/...) or afile:URL fails: it names a file on one machine. Only a URL (https:,mailto:) is not checked, and a file name with a line number (check.rs:104) is a path, not a URL. A path is separated with/: only Windows reads a backslash as a separator, so a path with one fails. A path that climbs above the repository fails: GitHub serves only the repository. Links are read as a CommonMark reader reads them: with a title ([a](b.md "title")), in angle brackets, as reference links defined in# Details, as images, and ashrefin HTML, and not inside code or comments. A link to a.pyfile names, in its text, a function or class defined there, at any depth. The file is parsed as Python, so adefline inside a string or a docstring does not count. The text is one name exactly as defined ([`render_index`](../tests/bundle.py)), not a call (render_index()) or a dotted path (Bundle.render); when it names nothing, the message says which name to write or lists the names the file defines. -
Every spec keeps the format:
title,description,statusand exactly one declared area intags, and a non-empty# Resolutionas the first heading once it is closed.epic, when present, is the slug of another spec indocs/specs/(not a path, not a backlog item or a guide, not the spec itself), and that spec has noepicof its own: one level only. A spec that breaks one of these is left out of the index files. -
An epic closes after its parts: no closed epic has an open part. A part that is dropped closes as dropped, as any spec does.
-
The log exists, points only at backlog items that exist (
backlog/<slug>.md, written with/or\, the slug percent-encoded or not), and its second-level headings are dates, newest first. Below the title, the log is a flat list of entries grouped under the dates (OKF 0.2, section 9): every entry is a list item, and its indented lines (wrapped text, nested items) belong to it. A task heading (### ...), a paragraph, or an entry before the first date fails. A new log, with only its title and HTML comments, passes. -
Every knowledge document keeps the format, as a spec does (without
epic, andstatusstableordeprecated), and the log names it as it is now: some**Knowledge**field of a log entry, with its wrapped lines, names it with the first 8 hex digits of SHA-256 of the whole file (every line ending as\n). An edit the log does not name fails, and the failure prints the line to write. A**Knowledge**field that names a documentdocs/knowledge/does not have fails too. -
Every document is a known type in its directory. The fields OKF defines for a document pass as OKF writes them (
generatedneeds onlyby; every entry ofsourcesneeds aresource;usage_windowis a{from, to}range). The names OKF reserves appear only where Rotproof writes and reads them:index.mdindocs/and in each directory of documents,log.mdindocs/. A markdown file is named.md, in lowercase: GitHub shows a.MDfile, but Rotproof would not read it. -
Every file Rotproof generates equals what
rotproof indexwrites: the index files, so nobody maintains a list by hand, anddocs/backlog/rules.mdanddocs/specs/rules.md, so the rules a project reads are the rules its Rotproof checks. A project's own rules go in another guide indocs/backlog/ordocs/specs/. -
No spec sits at the repository root.
The frontmatter is read as YAML 1.2: quoting a value never changes whether it passes. Its closing --- may end the
file. The body is read as GitHub
renders it: an HTML comment or a fenced code block (``` or ~~~, indented by up to 3 spaces, running to the end when
it is not closed) is not text, so a heading, a link or the only text of a section inside one counts for nothing. A
heading may be indented by up to 3 spaces, and a closing run of # (`## Notes ##`) is not part of it.
Development
cargo xtask ci runs format, lint and tests, the same checks as CI. It also fails when the map in AGENTS.md
misses a tracked top-level path or a module of a crate in crates/ (or names one that is gone), when
rust-toolchain.toml, the Dockerfile and the CI workflow name different toolchain versions, and when
THIRD-PARTY-LICENSES.txt does not list the crates the binary links (below), and when Rotproof's own crates break
the rules of the Rust layout it keeps: a crate outside the layers, a dependency the table does not allow, or a marker
in a comment of crates/ or xtask/. On Windows the host needs Visual
Studio's C++ tools and the Windows SDK. Everything also runs in the container (compose.yaml), where the host needs
only Docker: docker compose run --rm dev cargo xtask ci.
The binary links the crates Rotproof depends on, and their licenses require their notices to go with it.
THIRD-PARTY-LICENSES.txt holds them: every crate the binary links on any platform, with the license text each one
ships. Rotproof's own crates in crates/ (publish = false) are Rotproof, and are not listed. It is generated by
cargo-about from about.toml and the template about.hbs, never
edited by hand:
cargo xtask licenses # after any change to the dependencies; commit the file with the change
cargo xtask licenses --check # fails where the file differs from what cargo-about writes now
Both need cargo-about at the version the Dockerfile installs (cargo install --locked --features cli cargo-about@<version>); the container has it. The generation fails when a crate's license cannot be met from
accepted in about.toml, the licenses Rotproof agrees to ship. cargo-about takes minutes to build, so cargo xtask ci checks only the list of crates, against cargo tree, which needs cargo alone: a new or bumped dependency fails
there until the file is written again. The text is checked by .github/workflows/licenses.yml, on a push that changes
the dependencies, about.toml, about.hbs or the file itself. The Dockerfile and that workflow install the same
version of cargo-about, and cargo xtask ci fails when they differ.
The pip wheels (Windows x86_64 and Linux x86_64) are built with maturin, in the container:
docker compose run --rm dev maturin build --release --out dist # Linux
docker compose run --rm dev maturin build --release --target x86_64-pc-windows-msvc --out dist # Windows
Each wheel carries LICENSE-MIT, LICENSE-APACHE and THIRD-PARTY-LICENSES.txt in its .dist-info/licenses/
(license-files in pyproject.toml). The container's Linux wheel is tagged manylinux_2_34, after the container's
glibc, and its Windows wheel is cross-compiled with cargo-xwin, which downloads the MSVC runtime and the Windows SDK
under Microsoft's license. Both serve for trying the wheels, not for release.
A release is a pushed tag v<version> that matches Cargo.toml. .github/workflows/release.yml checks the licenses,
builds the Linux wheel in the manylinux2014 image (glibc 2.17) and the Windows wheel on Windows, installs each into a
fresh venv on its platform and runs it (.github/smoke.sh), and installs the Linux wheel in the manylinux2014 image
too, so the oldest glibc the tag claims is tested. It then publishes the wheels to PyPI and makes the GitHub Release
with the wheels, an archive of the binary per platform taken out of the tested wheel, and SHA256SUMS. Run by hand
(gh workflow run release.yml), it does everything but publish. CI builds and tests the Windows wheel natively on
Windows too. Other platforms (macOS, Linux aarch64) are welcome as contributions.
License
Licensed under either of MIT or Apache-2.0, at your option. The crates the binary includes are under their own licenses, listed with their texts in THIRD-PARTY-LICENSES.txt. The links are full URLs so that they work on PyPI, where this README is the project's page.
Metadata
Release files for rotproof 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rotproof-0.1.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| rotproof-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
Total release size: 6.7 MB
Release files / rotproof-0.1.0-py3-none-win_amd64.whl
| Download URL | rotproof-0.1.0-py3-none-win_amd64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
442fbd6b7244252a580325e98bd7b8f73b9a94cd4a6aa534ff62c9d46ef3f091
|
|
BLAKE2b-256 checksum How to use checksums |
179c02d7abb2445cada5493e899d4a53d5aa43d44b0bfa0221bbe223d42ec30b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency logRelease files / rotproof-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | rotproof-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
d91fb945ce0116cdae74a4f2e75136d167a9175404a440ef424bb18863c6cf55
|
|
BLAKE2b-256 checksum How to use checksums |
e121cfc6b0902d38ce473ac153268832303cffe7529bc7251b02fb7ac2c6d78b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency log