Skip to content

State three laws the record-damage cycle left only in the code - #404

Merged
typeless merged 1 commit into
mainfrom
wip/383-promote-record-laws
Aug 13, 2026
Merged

State three laws the record-damage cycle left only in the code#404
typeless merged 1 commit into
mainfrom
wip/383-promote-record-laws

Conversation

@typeless

Copy link
Copy Markdown
Owner

Doc-only. The #383 cycle settled three questions and left every answer
implicit in the code, where it reads as a choice someone made rather than a
rule someone must keep. This states all three in DESIGN.md, at the sections
that already own them. 29 added lines, one file, no deletions.

An announcement filter announces by default (§ Error Handling).
Enumerating error codes selects the wording of a particular announcement, or
excuses one proven benign; it is never the condition for being announced at
all. The announced set grows every time the enum does while the benign set
stays closed, so a rejection added later announces unless deliberately
excused. load_old_index (src/cli/context.cpp) is the standing example,
with IndexVersionMismatch the one excused code. The law ranges over
filters whose default outcome would otherwise be silence — not over any
dispatch switch whose every arm acts.

ErrorCode is an in-process vocabulary (§ Error Handling). Nothing is
serialized, mapped back from persisted bytes, or emitted as a stable token
in machine-read output; the on-disk record carries its own magic, version
and layout. Enumerators may be added, renamed or deleted freely and have
been — IndexTruncated and InvalidFormat are both gone from the enum.
That freedom lasts exactly as long as the in-process boundary holds, which
is the reason to write it down rather than leave it to be rediscovered, or
violated, by whoever first wants to print a code.

Atomicity entitles the reader to a conclusion (§ IndexWriter). Because
an interrupted write leaves the previous record in place instead of a prefix
of the new one, a record that fails validation was damaged after putup
wrote it, by something other than putup's own writer — so the reader may
announce damage instead of suspecting a half-finished write of its own. The
entitlement is wholeness, not freshness; it covers files written through
pup::platform::atomic_write, not build outputs, which user commands write
under no such regime.

Verification

  • make spec-check: pass, 105 requirements, 0 gaps.
  • No suite run and none needed — nothing under src/ or test/ changed.
  • Every claim checked against the line it describes rather than against the
    cycle's notes: src/index/reader.cpp (IndexDamaged on the too-small and
    bad-magic rows), src/cli/context.cpp (the filter's default arm),
    src/index/writer.cpp and both atomic_write implementations,
    spec/requirements/record-read.ears.md (REQ-READ-ANNOUNCE-DAMAGE).
  • Reviewed by a second model, which also swept independently for any site
    persisting or printing an ErrorCode as a machine-read token — the claim
    likeliest to be false — and found none.

One leg is not verified here

That a Win32 replacement leaves the previous record whole rests on
external documentation, not on anything runnable in this tree: no
crash-injection test for atomic_write exists on either platform. The prose
claims wholeness rather than strict MoveFileExW atomicity, so it is not
hedged — but nothing here exercises it. Pinning that leg is crash injection,
a code-and-test task, out of scope for a doc-only change.

Deliberately not fixed

The "Write process" list under § IndexWriter is stale and left untouched. It
misplaces both destination and layer: serialization builds a buffer in
memory and the temporary file is created inside atomic_write, so the
checksum and footer are produced during serialization rather than after any
file exists. Independent of this change; a follow-up issue covers the whole
list.

🤖 Generated with Claude Code

https://claude.ai/code/session_014wx1bWwRf23eFT82y9D641

The #383 cycle settled three questions and left every answer implicit in
the code, where it reads as a choice someone made rather than a rule
someone must keep. A later change can quietly restore any of the three to
a judgement call. DESIGN.md now states all three.

An announcement filter announces by default. Enumerating error codes
selects the wording of a particular announcement, or excuses one proven
benign; it is never the condition for being announced at all. The
announced set therefore grows every time the enum does while the benign
set stays closed, so a rejection added later announces unless someone
deliberately excuses it, instead of falling silent by omission.
load_old_index (src/cli/context.cpp) is the standing example, with
IndexVersionMismatch the one excused code. The site keeps its one-line
comment and DESIGN.md carries the class law; the law ranges over filters
whose default outcome would otherwise be silence, not over any dispatch
switch whose every arm acts.

ErrorCode is an in-process vocabulary. No value is serialized, mapped
back from persisted bytes, or emitted as a stable token in machine-read
output -- the on-disk record carries its own magic, version and layout
instead. Enumerators may therefore be added, renamed or deleted freely,
and have been: IndexTruncated and InvalidFormat are both gone from the
enum. That freedom lasts exactly as long as the in-process boundary
holds, which is the reason to write it down rather than leave it for
whoever first wants to print a code to rediscover, or to violate.

Atomicity entitles the reader to a conclusion, and that conclusion is
what makes announcing damage honest rather than presumptuous. Because an
interrupted write leaves the previous record in place instead of a prefix
of the new one, a record that fails validation was damaged after putup
wrote it, by something other than putup's own writer -- so the reader may
announce damage rather than suspect a half-finished write of its own. The
entitlement is wholeness, not freshness: which whole record survives a
crash turns on directory-entry durability, which is the filesystem's
affair. It covers the files written through pup::platform::atomic_write,
not putup's outputs generally, since build outputs are written by user
commands under no such regime. A whole record from outside the readable
version window is not damage either.

Doc-only, and nothing further is owed: the code fix shipped earlier in
the cycle -- src/index/reader.cpp already returns IndexDamaged for the
too-small and bad-magic rows -- and REQ-READ-ANNOUNCE-DAMAGE
(spec/requirements/record-read.ears.md) already pins the observable half
with four discharges. `make spec-check` passes at 105 requirements and 0
gaps. No suite was run and none is needed; nothing under src/ or test/
changed.

Each claim was checked against the line it describes rather than against
the cycle's own notes, and a second model reviewed the prose and swept
independently for any site that persists or prints an ErrorCode as a
machine-read token, finding none. One leg is not verified here: that a
Win32 replacement leaves the previous record whole rests on external
documentation, because no crash-injection test for atomic_write exists on
either platform. The prose claims wholeness rather than strict
MoveFileExW atomicity, so it is not hedged -- but nothing in this tree
exercises it.

The stale "Write process" list under ### IndexWriter is deliberately left
untouched. It misplaces both destination and layer: serialization builds
a buffer in memory (src/index/writer.cpp) and the temporary file is
created inside atomic_write, so the checksum and footer are produced
during serialization rather than after any file exists. That defect is
independent of this change and gets its own issue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wx1bWwRf23eFT82y9D641
@github-actions

Copy link
Copy Markdown

PR metrics

Performance (gcc example, Linux)

Workload Instructions CPU time Page faults D1 miss LL miss Wall Peak RSS
parse 1698 M 0.34 s 14.9 k 0.5% (-0.2pp) 0% (-0.1pp) 0.355 s 34 MB (+0.1MB)
dry-run 2315 M (-2.2%) 0.42 s 16.3 k 0.6% (-0.2pp) 0% 0.422 s 39.2 MB (+0.1MB)

Deterministic signals: instructions (cachegrind-simulated instruction reads — exact across runs, no PMU needed), page faults, peak RSS, and the cachegrind D1/LL miss rates. CPU time is user+sys from time(1).

Internal statistics (gcc example, up-to-date dry run)

Metric Value
Tupfiles parsed 24
Commands 3545
Commands scheduled 0
Files checked 5834
Files changed 0
Files in index 6102
Graph edges 381415
Index size (bytes) 7683630
Implicit deps 344126
Hash computations 0 (-100.0%)
Hashes skipped (stat cache) 5833 (+3.5%)
Stat calls 5885
Parse time (ms) 295.7
Total time (ms) 420.7
Runner CPU INTEL(R) XEON(R) PLATINUM 8573C

Counters from putup -n --stat on the fully-built gcc example (up-to-date dry run): deterministic work measures — a jump in commands scheduled, hash computations, or stat calls is a real behavior change, not noise. Timings are the minimum over repeated runs, compared only against a baseline from the same CPU model; the counters are the regression signal.
Timing deltas suppressed: baseline ran on different hardware (AMD EPYC 7763 64-Core Processor).

Binary size (Linux)

Binary .text .data .bss File
putup 565.4 KB 2.3 KB 98.7 KB 671 KB

Code churn (whole codebase, last 30d)

Files Lines written Still present Churned Churn rate
141 11750 9818 1932 16.4%

Of the lines written across the codebase in the last 30 days, how many are already gone — work that was written and then discarded or rewritten inside the same window. This is the state of the tree including this PR, not a measure of the PR itself. Only code we write is counted: tests, examples, vendored and generated files, CI plumbing and prose are excluded. 5442 lines were deleted in the window in total, most of them older than it.

Where the churn is
File Lines written then discarded
src/cli/cmd_build.cpp 656
src/graph/scanners/clang_cl.cpp 295
src/graph/scanners/gcc.cpp 113
src/cli/context.cpp 82
src/graph/builder.cpp 72
src/core/layout.cpp 69
src/graph/scanners/dep_words.cpp 64
src/core/new_delete.cpp 63
src/platform/file_io-posix.cpp 42
src/cli/strict_checks.cpp 36

Test coverage (lines)

Overall Median file Min file Max file
88.1% 96.4% 14.7% include/pup/parser/token.hpp 100.0% include/pup/core/arena.hpp

102 files · 16614/18854 lines covered

Deltas vs main@b8d649852.

Updated for c6755e2

@typeless
typeless merged commit 26ddb61 into main Aug 13, 2026
13 checks passed
@typeless
typeless deleted the wip/383-promote-record-laws branch August 13, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant