Skip to content

docs/test(gc): re-derive the current collector page's claims instead of asserting them (#7877) - #7936

Merged
proggeramlug merged 4 commits into
mainfrom
docs/7877-gc-doc-claims-gate
Aug 12, 2026
Merged

docs/test(gc): re-derive the current collector page's claims instead of asserting them (#7877)#7936
proggeramlug merged 4 commits into
mainfrom
docs/7877-gc-doc-claims-gate

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #7877.

Why this is a gate and not a correction

#7877 was reopened because the "current source of truth" page drifted inside the
same landing train that created it. Correcting the prose a second time buys
another few days. The half of a reference page that a machine can re-derive is
its paths and its numbers, so this binds those to the code and lets lint
adjudicate.

scripts/check_gc_doc_claims.py, three rules, each with a --self-test arm that
proves it can fail:

  1. Paths. Every repo path a current GC page cites must exist. A path:LINE
    citation is rejected outright — memory-model.md's source map pointed 8 of
    its 9 rows at a crates/perry-runtime/src/gc.rs deleted in the module split,
    with line numbers, and nothing noticed.
  2. Numbers. A documented constant carries a <!-- gc-fact: NAME = VALUE in PATH --> marker and is compared against the const in that file; a
    source-map row carries <!-- gc-symbol: NAME in PATH --> and must still name
    a definition. 17 facts are re-derived today. The rule asserts its own subject
    was live: deleting the markers instead of fixing them trips a floor.
  3. Tracker attribution. The operations page names no issue numbers. "That
    phase is atomic and unsliced (perf(gc): full/fallback weak processing is an unsliced O(total heap) atomic pause #7874)" was false the moment perf(gc): full/fallback weak processing is an unsliced O(total heap) atomic pause #7874 closed, and
    an issue's state is not a fact this repository holds. Off that page the rule
    is narrower — it flags "#N tracks", "tracked by #N", "blocked on #N" — so the
    historical, causal references in memory-model.md are untouched.

The script says plainly what it cannot catch: prose that is simply wrong about
behaviour. That is why rule 2 exists — a number is the part of a behavioural
claim a machine can hold onto.

What was actually wrong

Every row verified against code, not against another document.

claim reality
full/fallback weak processing "walks the whole arena … atomic and unsliced (#7874)" FullWeakProcessingState snapshots the holder registry and consumes a bounded number per step — O(registered holders), resumable
block reuse pool is "per-thread … capped at 64 MiB", no drain (#7875) reuse order is per-thread, the cap is process-wide and budget-scaled (÷8 on constrained budgets, 1 MiB floor); a critical-pressure drain request is sticky and empties the pool after the owed full collection
"#7876 tracks the missing rewrite contract" the contract exists and shipped; defrag is opt-in because no fragmentation workload exercises it
PROMOTION_AGE = 2 the copying minor's threshold is adaptive 1–4 with a survival-rate lock; the flat flag pair describes the non-copying path
whole-block in-place promotion, born-old thresholds undocumented; both materially change nursery residency
js_arena_stats "sums block offsets … UNCHANGED" (ratchet README) it reports a live census, so the README's "a whole-block jump means one object too many" triage advice inverted
CLAUDE.md: registry is in gc/mod.rs, "~55 entries" it is gc/roots.rs, and the population is 123 — the count appeared twice in CLAUDE.md and only one copy got corrected by hand, which is the argument against the number being in prose at all

CLAUDE.md also cited crates/perry-codegen/src/codegen.rs and
crates/perry-hir/src/lower.rs for the add-a-widget recipe; both are module
trees now. Rule 1 found those, not a human.

Code changes

Two inline literals become named constants so the page can cite them:
BLOCK_POOL_CAP_DEFAULT_BYTES (64 MiB) and SCAVENGE_NURSERY_CAP_DEFAULT_MB
(16). Neither changes a value or a code path.

Validation

  • --self-test: green, and each rule's failing arm is asserted, including a
    planted drifted value, a renamed constant, a renamed symbol, and a marker
    naming a file that does not exist.
  • End-to-end sabotage against the real tree, fix committed first: falsifying the
    documented number and changing the constant in the source both fail
    (exit 1, checked unpiped); renaming tenuring_survivals fails; adding
    #7876 tracks it to the operations page fails; deleting every marker fails
    the vacuity floor. Tree clean afterwards.
  • python3 scripts/gc_gate_wiring_check.py → 7 gates main-line-reachable;
    scripts/check_file_size.sh → OK; cargo fmt --all -- --check → clean.
  • Wired into test.ymllint (a required context) and into the Windows
    GC structural audits step, alongside the existing knob audit. Per CLAUDE.md's
    hazard 1, it is a plain step with no continue-on-error.

Corpus (runtime touched, so this is not optional)

Rebuilt -p perry -p perry-runtime-static -p perry-stdlib-static after the two
constant extractions, archive mtime confirmed to move past the edit, and the
19-program GC corpus compiled and run against node's recorded output:

corpus: 19 ok, 0 bad     # each: exit 0 AND byte-identical

Including the iso_miss canary at checksum 437840 misses 0, which is gated on
the miss counter rather than on the aggregate.

Summary by CodeRabbit

  • Documentation

    • Updated garbage-collector and memory-model documentation to reflect current behavior, including allocation, promotion, pooling, weak processing, and value representations.
    • Clarified memory-usage reporting and troubleshooting guidance for more accurate retention and live-byte measurements.
    • Refreshed references and guidance for native UI and garbage-collection internals.
  • Quality Improvements

    • Added automated validation to verify documented paths, values, symbols, and factual claims across supported platforms.
    • Added self-tests and safeguards to detect outdated or unsupported documentation claims.

…of asserting them (#7877)

The GC reference pages drifted again in the landing train that created them.
Rather than correct the prose a second time, bind the checkable half of it to
the code: `scripts/check_gc_doc_claims.py` verifies that every repo path the
current pages cite exists, that every documented number matches the constant it
came from (`gc-fact` markers), and that every source-map row still names a
defined symbol (`gc-symbol` markers). The operations page now names no issue
numbers at all, because an issue reference is a claim about a tracker that this
repository cannot see.

Corrected, each verified against code rather than against another document:
weak processing is registry-scoped and holder-sliced on every path; the block
reuse pool's cap is process-wide and budget-scaled, not per-thread; old-page
defrag has its rewrite contract and is opt-in pending a fragmentation corpus;
tenuring is an adaptive 1-4 threshold, not `PROMOTION_AGE = 2`; whole-block
in-place promotion and the type-dependent born-old thresholds were undocumented;
`js_arena_stats` reports a live census, so the ratchet README's whole-block
triage advice no longer holds; and the memory-model source map pointed 8 of 9
rows at files deleted in the `gc.rs` split.

Two inline literals become named constants so the page can cite them:
`BLOCK_POOL_CAP_DEFAULT_BYTES` and `SCAVENGE_NURSERY_CAP_DEFAULT_MB`. Neither
changes a value.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a GC documentation claim checker, names two runtime defaults, updates GC and accounting documentation, and runs validation in Linux lint and Windows structural-audit workflows.

Changes

GC documentation validation

Layer / File(s) Summary
Claim checker implementation
scripts/check_gc_doc_claims.py
Validates repository paths, numeric facts, source symbols, tracker references, marker counts, and required documents. It includes self-tests and CLI modes.
Named GC runtime defaults
crates/perry-runtime/src/gc/heap_budget.rs, crates/perry-runtime/src/gc/policy.rs
Adds named constants for the 64 MiB block-pool cap and 16 MiB nursery cap. Runtime fallbacks use these constants.
Current GC documentation and accounting
docs/src/internals/garbage-collector.md, docs/src/internals/memory-model.md, benchmarks/gc_ratchet/README.md, CLAUDE.md, changelog.d/7936-gc-doc-claim-gate.md
Updates GC behavior, controls, source markers, accounting, root-holder guidance, current source paths, and validation commands.
Cross-platform CI enforcement
.github/workflows/test.yml
Runs the checker self-test and repository audit in Linux lint and Windows structural-audit jobs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI
  participant Checker
  participant Documentation
  participant RustSources
  CI->>Checker: Run self-test and repository audit
  Checker->>Documentation: Read paths and claim markers
  Checker->>RustSources: Verify constants and symbols
  Checker-->>CI: Return validation status
Loading

Possibly related PRs

Suggested reviewers: jdalton

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses current GC claims, but [#7877] also requires deleted-knob removal and freshness updates not shown in the changes. Implement and verify the remaining [#7877] requirements for deleted knobs, stale-knob validation, freshness cadence, and engine-plan status.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main documentation validation and claim re-derivation work.
Description check ✅ Passed The description provides detailed rationale, changes, related issue, validation results, and corpus results, despite not using every template heading.
Out of Scope Changes check ✅ Passed The code, documentation, constants, checker, and CI wiring directly support the stated GC documentation validation objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/7877-gc-doc-claims-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Ralph Küpper added 3 commits August 12, 2026 11:17
…dd the changeset

The four claims that went false in the last landing train now carry a gc-symbol
marker naming their test, so a rename fails lint and a behaviour change fails
cargo-test. A doc sentence and a green suite can no longer disagree quietly.
@proggeramlug
proggeramlug marked this pull request as ready for review August 12, 2026 09:21
@proggeramlug
proggeramlug merged commit 8260a9e into main Aug 12, 2026
0 of 20 checks passed
@proggeramlug
proggeramlug deleted the docs/7877-gc-doc-claims-gate branch August 12, 2026 09:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check_gc_doc_claims.py`:
- Around line 241-261: Update check_repo() to count gc-symbol markers separately
from gc-fact markers and append a validation problem when the symbol count is
below its minimum floor. Extend the --self-test coverage so removing all
gc-symbol markers causes the audit to fail, ensuring the symbol rule cannot pass
vacuously.
- Around line 181-205: Update the Rust lookup logic in the gc-fact and gc-symbol
validation loops to inspect parsed Rust items or lexer-filtered source, not raw
text, so comments and string literals cannot satisfy markers. Ensure removed
declarations hidden only in comments or literals are reported as missing, and
add sabotage cases covering both scenarios.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b9d9689-01c2-4c96-aa56-c0e3981f56bd

📥 Commits

Reviewing files that changed from the base of the PR and between f110261 and 2058fba.

📒 Files selected for processing (9)
  • .github/workflows/test.yml
  • CLAUDE.md
  • benchmarks/gc_ratchet/README.md
  • changelog.d/7936-gc-doc-claim-gate.md
  • crates/perry-runtime/src/gc/heap_budget.rs
  • crates/perry-runtime/src/gc/policy.rs
  • docs/src/internals/garbage-collector.md
  • docs/src/internals/memory-model.md
  • scripts/check_gc_doc_claims.py

Comment on lines +181 to +205
source = source_path.read_text(encoding="utf-8")
item = re.search(RUST_ITEM_RE.format(name=re.escape(name)), source)
if item is None:
problems.append(
f"{rel}: gc-fact {name} is not defined in {path} "
"(renamed or deleted -- update the page, not the marker)"
)
continue
if not values_agree(documented, item.group("rhs")):
problems.append(
f"{rel}: gc-fact {name} documents {documented.strip()!r} "
f"but {path} defines {item.group('rhs').strip()!r}"
)
for match in SYMBOL_RE.finditer(text):
name, path = match.group("name"), match.group("path")
source_path = root / path
if not source_path.is_file():
problems.append(f"{rel}: gc-symbol {name} names {path}, which is not a file")
continue
source = source_path.read_text(encoding="utf-8")
if not re.search(SYMBOL_DEF_RE.format(name=re.escape(name)), source):
problems.append(
f"{rel}: gc-symbol {name} is not defined in {path} "
"(moved or renamed -- the source map is pointing at nothing)"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Parse Rust items before accepting a marker.

Lines 182 and 201 search raw Rust text. Both patterns also match Rust-like text in comments and string literals. If a refactor removes a documented item but retains its old declaration in a comment, the marker stays green.

Strip comments and literals with a Rust-aware lexer, or parse item declarations. Add sabotage cases where the only matching text is in a comment or string literal.

🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 181-181: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.search(RUST_ITEM_RE.format(name=re.escape(name)), source)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)


[warning] 200-200: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.search(SYMBOL_DEF_RE.format(name=re.escape(name)), source)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check_gc_doc_claims.py` around lines 181 - 205, Update the Rust
lookup logic in the gc-fact and gc-symbol validation loops to inspect parsed
Rust items or lexer-filtered source, not raw text, so comments and string
literals cannot satisfy markers. Ensure removed declarations hidden only in
comments or literals are reported as missing, and add sabotage cases covering
both scenarios.

Comment on lines +241 to +261
def check_repo(root: Path) -> list[str]:
problems = []
facts = 0
for rel in CURRENT_DOCS:
path = root / rel
if not path.is_file():
problems.append(f"{rel}: checked document is missing")
continue
text = path.read_text(encoding="utf-8")
facts += len(FACT_RE.findall(text))
problems.extend(check_document(rel, text, root))
# A rule that inspects nothing passes vacuously. The fact rule is the one
# with a population that can silently go to zero (delete every marker and it
# is green), so it asserts its own subject was live.
if facts < MIN_FACTS:
problems.append(
f"only {facts} gc-fact markers found, expected at least {MIN_FACTS}. "
"Rule 2 is the only defence against a documented number drifting; "
"removing markers instead of fixing them disarms it."
)
return problems

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the gc-symbol rule non-vacuous.

check_repo() counts only gc-fact markers. If all gc-symbol markers are removed, the symbol rule receives no input and the audit can still pass. Count gc-symbol markers, enforce a minimum floor, and make --self-test prove that removing all symbol markers fails.

As per coding guidelines: “A gate must assert its subject was live, not merely that nothing threw.”

🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 249-249: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: FACT_RE.findall(text)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check_gc_doc_claims.py` around lines 241 - 261, Update check_repo()
to count gc-symbol markers separately from gc-fact markers and append a
validation problem when the symbol count is below its minimum floor. Extend the
--self-test coverage so removing all gc-symbol markers causes the audit to fail,
ensuring the symbol rule cannot pass vacuously.

Source: Coding guidelines

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.

docs/test(gc): establish a current collector source of truth and remove deleted knobs from live gates

1 participant