Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 118 additions & 5 deletions .github/workflows/report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@
# could not even reach its first step, and a workflow that is red for reasons
# unrelated to the report is one people learn to override.
#
# WHAT THAT COSTS, said here rather than left to be discovered: this is now the
# whole automated surface. Nothing checks that docs/state.yaml and
# docs/inherited-measurements.yaml are parseable YAML, and they are the record.
# See od-004, which is open and blocked only on the question of adding a
# dependency.
# THREE JOBS NOW: `ledgers` parses the record, `code` gates the one Python file
# in the repository, `build` compiles the document.
#
# THE `code` JOB EXISTS BECAUSE fc-002's SCOPE SAID IT WOULD. tools/md2tex.py
# generates report/sections/*.tex from report/draft.md; it was outside the
# repository, which made main.tex's claim that the .tex is regenerable true and
# unverifiable at once (od-007). Committing it reopened fc-002 and fc-003, and
# fc-002's scope is explicit that pyproject.toml comes back with the gates
# attached. Restoring the code without the gates would have been taking the
# convenient half of a decision and leaving the part that cost something.
# od-004 closed -- the gap that used to be recorded here, that nothing checked
# whether docs/state.yaml and docs/inherited-measurements.yaml still parse, is
# closed by the first job below.
#
# Triggers do not fire on literally every push, so a branch with an open pull
# request is not built twice for the same commit. Every change in this repository
Expand All @@ -32,6 +40,111 @@ concurrency:
cancel-in-progress: true

jobs:
# The record, checked first because it is cheap and because a ledger that no
# longer parses is a worse failure than a document that no longer builds.
#
# A SEPARATE JOB, NOT A STEP IN `build`: it must not wait behind a 44-second
# TeX install to report a one-character YAML error, and it must still run when
# the document build fails.
#
# NO DEPENDENCY, WHICH IS WHY IT IS THIS AND NOT PYTHON. YAML is not in
# Python's standard library and PyYAML is not guaranteed on the runner, so the
# obvious version of this check would have to `pip install` something -- and
# CLAUDE.md's working agreement says never add a dependency without asking.
# Ruby ships Psych in its standard library and is preinstalled on
# ubuntu-latest, so this costs one `ruby -ryaml` and no install step at all.
# `YAML.parse`, NOT `YAML.load`, AND THE REASON IS A FAILURE THIS CHECK ALREADY
# HAD. The first version used YAML.load. It passed locally on Ruby 2.6 (Psych 3,
# where load is unsafe-by-default) and FAILED on ubuntu-latest with
# "Tried to load unspecified class: Date" -- Psych 4 made YAML.load
# safe-by-default, and the ledgers carry unquoted dates like `2026-09-08`,
# which YAML resolves to a Date. The fix is not to permit the class; it is to
# stop instantiating classes at all. YAML.parse returns the node tree and
# constructs nothing, which is exactly what "is this file parseable" asks, and
# it behaves the same on every Psych version.
#
# THE LESSON IS THE ONE THIS REPOSITORY IS ABOUT, so it is recorded rather than
# quietly fixed: the check WAS watched failing before it was committed -- a
# broken ledger made it exit 1 -- but only on the axis its author thought of.
# It was never run anywhere but the author's machine, so the environment
# difference was invisible until CI. A check tested on one axis is a check
# tested on one axis.
ledgers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Ledgers parse
run: |
ruby -ryaml -e '
ok = true
["docs/state.yaml", "docs/inherited-measurements.yaml"].each do |f|
begin
doc = YAML.parse(File.read(f))
raise "empty document" if doc.nil?
raise "root is #{doc.root.class}, expected a mapping" unless doc.root.is_a?(Psych::Nodes::Mapping)
puts "ok #{f} (#{doc.root.children.length / 2} top-level keys)"
rescue => e
warn "FAIL #{f}: #{e.message.split("\n").first}"
ok = false
end
end
exit(ok ? 0 : 1)'

# The one Python file, and the two guards that make its claims testable.
#
# PYTEST IS THE POINT, and the other three are the price fc-002's scope set.
# test_scaffold.py regenerates the sections into a temporary directory and
# compares bytes, so "report/sections/*.tex is generated from report/draft.md"
# -- asserted in main.tex's header since the document was first typeset -- is
# now a claim that can fail. It was verified failing before it was committed,
# by appending a comment to one .tex.
#
# ONE INTERPRETER, NOT A MATRIX. The template ran 3.12 and 3.13 because its
# pyproject.toml declared `requires-python = ">=3.12"` and something had to
# test the declaration. This one declares the same, and the honest note is
# that a single stdlib script is not evidence for the floor -- the matrix is
# not restored because there is nothing here it would discriminate.
code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: pip

# `--group dev`, NOT `pip install ".[dev]"`, AND THE FIRST VERSION FAILED
# HERE. Installing the directory asks pip to BUILD IT AS A PACKAGE; there
# is no build backend on purpose, so setuptools was guessed at and rejected
# pyproject.toml with "`project` must contain ['version'] properties". It
# passed locally because the tools were already installed and the install
# step never ran. PEP 735 dependency groups install tooling without
# building anything, which is exactly this case. `--upgrade pip` first,
# because --group needs pip 25.1 or newer.
#
# No project dependencies to install: tools/md2tex.py is standard library
# only, and pyproject.toml says so.
- name: Install
run: |
python -m pip install --upgrade pip
pip install --group dev

- name: Lint
run: ruff check tools tests

- name: Format check
run: ruff format --check tools tests

- name: Types
run: mypy

# `-rs` reports skips: a silent skip is a gap in the evidence that looks
# like a pass.
- name: Tests
run: pytest -rs

build:
runs-on: ubuntu-latest

Expand Down
16 changes: 10 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
# claim about `visgraf/bio-3d-vision` or its predecessors, and "verifiable at
# source" means a checkout at the SHA pinned in docs/inherited-measurements.yaml.
#
# THE ENFORCEMENT THAT USED TO SIT BESIDE THIS IS GONE, and that is worth saying
# rather than leaving to be discovered. The template guarded this directory with
# a test asserting that no code reads from it. This repository has no code, so
# the guard had nothing to guard and was deleted with the rest of the suite. The
# convention survives on nobody's authority but this comment. If a figure script
# ever lands here, restore the guard with it -- see report/figures/README.md.
# THE ENFORCEMENT IS BACK, and this comment is no longer the only thing holding
# the convention up. It was: the template's guard was deleted with the rest of
# the suite because there was no code to guard, and this paragraph said so. When
# tools/md2tex.py landed (od-007), fc-003's scope required the guard to be
# restored FIRST, before any other -- and it was. tests/test_scaffold.py now
# fails if anything under tools/ or tests/ so much as names this directory.
#
# IT FIRED IMMEDIATELY, on the docstrings of the two files that restored it,
# which mentioned the path in prose. The prose was rewritten rather than the
# guard loosened. It is crude on purpose.
.reference/

# LaTeX build artifacts are ignored in report/.gitignore, beside the build that
Expand Down
57 changes: 39 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,19 @@ A task is done when all of these hold:
> *Why this clause is first:* the predecessor's five conditions were all about
> artifact quality, so a perfectly-tested component could be added to a loop
> that had never run — and was.
2. **Every claim it adds to the prose is traceable.** There is no test suite
here and clause 2 cannot be about one. Its replacement is the standard the
draft is held to: a new factual sentence names which repository it is true
of, and any figure in it has an entry in
`docs/inherited-measurements.yaml` — written at the same time, not later.
> *Why the substitution and not a deletion:* clause 2 existed so that new
> behaviour could not arrive unchecked. New prose is the only thing that
> arrives here, and the unchecked version of it is this project's entire
> failure mode.
2. **Every claim it adds to the prose is traceable, and new behaviour has a
test.** Both halves are live now. The prose half is the one that matters
almost always: a new factual sentence names which repository it is true of,
and any figure in it has an entry in `docs/inherited-measurements.yaml`,
written at the same time and not later. The code half applies to
`tools/` and `tests/`, which exist again as of `od-007`.
> *Why this clause was rewritten twice:* it originally read "new behaviour has
> a test, and the suite passes", which was inapplicable when there was no
> suite; it was replaced by the prose standard alone; and the code half came
> back when `tools/md2tex.py` did. **The prose half was never the substitute
> for the code half — it is the more important of the two here**, because new
> prose is what mostly arrives and the unchecked version of it is this
> project's entire failure mode.
3. Conventions are stated in `## Domain invariants` above, because there is no
code to state them at. See the note there.
4. `docs/state.yaml` is updated if any of it changed.
Expand Down Expand Up @@ -238,8 +242,8 @@ the source is the finding and the prose is the bug.

## Working agreement

- **Branch.** `docs/…`, `report/…`, `fix/…`. Never commit to `main`. (`exp/…`
and `spike/…` are dropped: this repository has neither.)
- **Branch.** `docs/…`, `report/…`, `feat/…`, `fix/…`. Never commit to `main`.
(`exp/…` and `spike/…` stay dropped: this repository has neither.)
- **Never commit or push unless asked.** Never force-push or rewrite history.
> *Why the asking is explicit:* a specification that names a branch but never
> asks for a commit leaves the work sitting uncommitted and costs a round
Expand Down Expand Up @@ -271,12 +275,29 @@ method, with its provenance.

```bash
make -C report # build the report PDF (needs a TeX installation)
make -C report sections # regenerate report/sections/*.tex from draft.md (needs Python)
pytest -q # the two scaffold guards
ruff check tools tests # lint
ruff format tools tests # format
mypy # types
```

**That is the whole list, and it is one line.** No suite, no linter, no type
checker — there is no code for them to run against, and `pyproject.toml` was
deleted rather than kept as an empty promise. The build is the only automated
check this repository has, and `.github/workflows/report.yml` is the only
workflow. Everything else that could go wrong here goes wrong in prose, and the
only instrument for that is `report/claims-verified.md`, run by a person against
a pinned checkout.
**This list was one line until `tools/md2tex.py` landed, and the change is worth
naming rather than absorbing.** The converter generates `report/sections/*.tex`
from `report/draft.md`. It lived outside the repository, which made
`report/main.tex`'s claim that the `.tex` is regenerable true and unverifiable at
the same time — `od-007`. Committing it reopened `fc-002` and `fc-003`, and
`fc-002`'s scope is explicit that `pyproject.toml` returns with the gates
attached. Taking the code without the gates would have been taking the convenient
half of a decision.

**What is still true:** there is no library and no experiment. `pyproject.toml`
carries tool configuration and no build backend. The PDF build needs no Python —
the `.tex` files are committed, so `make -C report` compiles from a clean
checkout with TeX alone, and `make -C report sections` is a separate step that
never runs during a build.

**And the prose instrument is unchanged and still the important one.** The gates
catch nothing that a claims pass catches. `report/claims-verified.md`, run by a
person against a pinned checkout, is where this repository's characteristic
failure is caught.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ done. Read the draft accordingly.
Instantiated from the template
[`visgraf/math-ai-method`](https://github.com/visgraf/math-ai-method) at commit
`81a638e`, then adapted to a document-only shape: no library, no experiments, no
test suite, no Python. What was deleted and what was kept is recorded with its
test suite, no Python. **Three of those four still hold.** `tools/md2tex.py`
generates `report/sections/*.tex` from the draft; it was outside the repository,
which made the claim that the `.tex` is regenerable true and unverifiable at once
(`od-007`). Committing it brought back `pyproject.toml` (tool configuration only,
no build backend), two scaffold guards, and the lint, type and test gates —
because `fc-002`'s scope said it would. What was deleted and what was kept is recorded with its
reasoning in the `foreclosures` block of
[`docs/state.yaml`](docs/state.yaml) — `fc-001` through `fc-006` — rather than
left to be inferred from an absence.
Expand Down
Loading
Loading