Skip to content

recipes: key reached inputs and persist structural reach - #587

Open
Burntgogi wants to merge 12 commits into
midagedev:mainfrom
Burntgogi:fix/issue529-recipe-reach-keys
Open

Burntgogi wants to merge 12 commits into
midagedev:mainfrom
Burntgogi:fix/issue529-recipe-reach-keys

Conversation

@Burntgogi

@Burntgogi Burntgogi commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Change

Closes #529.

Graph.inputs remains the broad compilation/affected/narrow/release bound. New ledger-specific Graph.key_inputs uses uncut structural Reach, not the profile-pruned runtime view, and closes recipe dependencies separately. The v2 key format intentionally invalidates old green keys once.

Mandatory inputs remain: the selected target's module/root sources, build scripts/helpers/literals/build dependencies, proc-macro compilation closures, manifest views/globals, dep-info, scripts and explicit file/directory reads. Broad never-skip/tree-scope safety detectors are unchanged. targets, why and gaps use/report the ledger view where appropriate.

Compiled invocations of resolved source macros add structural roots even in otherwise unreached modules, so generated startup code and implicit impls retain transitive helpers. Qualified source calls reuse the existing expansion edges. Generated imports, nested expansions and attributes conservatively bound the invoking owner; shadowed or unknown calls remain opaque. Nonstandard #[path] modules participate in shared implicit-impl rules.

Opaque macro expansion conservatively retains its owner's compilation and forward-dependency bounds. Compiled runtime-active owners are scanned for startup attributes (including nested cfg_attr), unused imports/explicit crate links, and generated/foreign-link obligations. A selected Rust module's filename need not end in .rs. Ambiguous generated includes and foreign linkage retain broad inputs. This does not claim arbitrary proc-macro filesystem completeness.

Persistent structural cache

tools/bloomery/reach_cache.py stores the complete structural facts/nodes/edges/rules/indexes as private JSON under ~/.cache/bloomery/reach/<checkout-id>/graph.json, not per-recipe reach lists or green verdicts. Code, metadata, source corpus/enumeration, symlink referents and observed reads/probes are content checked independently of git/mtime. Fixed schema, checksum and index validation precede hydration; input stability is rechecked before publication/key output. Corrupt/foreign/stale caches rebuild. Imported code is bound to its actual loaded code marker; cache data is never executable code.

A measured publication hotspot used millions of token-sized Python writes; publication now uses the standard C JSON encoder with one write, preserving the JSON protocol.

Current performance cutover

The private snapshot format is now 2 (the ledger key format remains v2). Only structural state is persisted: the recomputable _resolved lookup memo is omitted, and hydration discards any eager pre-snapshot resolver results. Nodes, edges, facts, rules, dispatch/index validation and fresh code/source/metadata/read/probe checks remain. Obsolete validation clauses for the omitted memo were removed; no persisted structural validation was skipped.

The exact full key_self_test profile on the preceding head passed every assertion and recorded 1,938,101,399 calls; encoding accounted for 228 cumulative seconds. A separate real-tree field probe identified 299,230 resolver-memo entries / 75,153,569 encoded bytes. This cutover removes that derived data rather than raising deadlines or dropping coverage.

Current-head verification (94ee6b2)

Current upstream main 69b66367 is integrated. All three MUST_FIX items in the 2026-10-22 review are addressed:

  • Absolute paths and raw identifiers retain the existing conservative owner/dependency bound rather than losing called sources.
  • Cargo-selected Rust roots and modules are traversed regardless of a .rs suffix; non-source data remains a data node.
  • The removed plain-link hf mutation now has its required dated contract rationale.

Verification:

  • New actual key/ledger CLI regressions failed before the correction in all three cases (absolute call, raw identifier, nonstandard target root): a changed called module incorrectly remained skip.
  • Complete CLI safety suite: 22 methods passed, 62.701 s.
  • Separate real Rust consumers were compiled and run for all three forms. Each changed output from used1/raw1 to used2/raw2; the public ledger returned run for the called recipe and skip for the unrelated linked recipe. The source mutation preserved size/mtime; the temporary consumer's dep build was cleaned before recompilation to separate Cargo's own mtime reuse from the content-key check.
  • LC_ALL=C bash tools/contrib-check.sh --tier t0 --only '^(fmt|check-recipes|check-comments|check-arch|check-levers)$': 5 units / 0 red, wall 620 s. The full check-recipes ran 146 blocks / 0 green-key skips and completed in 598 s; the per-unit limit remains 1,200 s. Header: 1c0765bd+local, the same corrected sources subsequently committed as 94ee6b2.
  • Documentation updated in docs/contrib/local-check.md. No deadline, selector, numeric band or coverage was relaxed.

just pre-review was attempted after committing, but exited 75: no Claude CLI is installed, so it reviewed nothing. New-head GitHub review/CI are pending; the earlier green T0 at 6e6e15b is not relabelled as this head's CI. No NVIDIA execution or performance claim.

@github-actions github-actions Bot added the tools Tools, recipes, CI and the toolchain: tools/, justfile, .github/, Cargo label Oct 21, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: MUST_FIX: Two reachable startup/destructor paths can change behavior without changing a green ledger key.

Must fix

1. Retain startup expansions of known source macros

tools/recipes.py:4701 exempts known macros even when their invocation is outside the reached items. A reached dependency can have an unreached module containing macros::boot!(), where a regular library exports boot! and emits an .init_array initializer calling crate::hidden::run(). The initializer runs, but the invocation, macro definition and hidden helper are all excluded from the key. Editing the helper therefore preserves the green key and skips the gate. In-memory input selection reproduces all three omissions.
Close this path before green ledger rows conceal startup changes. Conservatively retain source and qualified macro bounds; replace the condition and body with:

                    if (macro not in safe_macros or "::" in call
                            or any(macro in self.macros(rk) for rk in roots[pname])
                            or any(self.exported(p).get(macro) for p in linked_packages)):
                        opaque = True

2. Include implicit impl calls from modules with other suffixes

tools/recipes.py:4591 now uses _close to discard compilation inputs, but tools/recipes.py:4203 excludes impl files whose names do not end in .rs. With #[path = "drop.rust"] mod drop;, a selected binary that constructs dep::Token and lets it leave scope runs its Drop implementation there. Both that file and a helper it calls are omitted; changing either preserves the green key. In-memory checks retain the identical implementation in drop.rs.
Fix the shared impl scanner now so destructor changes cannot inherit stale green results. Replace lines 4202–4205 with:

        for f in files:
            facts = self.facts(rk, f)

Non-blocking

⚠ Exercise mandatory proc-macro inputs without opaque attributes: tools/recipes-key-test.py:373 activates the opaque fallback, so deleting mandatory.add(pname) at tools/recipes.py:4642 still retains every input this test asserts. The mutation was checked in memory. Isolate that branch now by adding this before the existing baseline:

        ws.green("gate-linked")
        with ws.changed("crates/macros/src/helper.rs", "pub fn observe() { panic!(); }\n"):
            ws.assert_status(ws.keys("gate-linked", ledger=True), {"gate-linked": "run"})

Checked

Duplicates: no duplication finding to validate. No dryer pairs were supplied.
Complexity: no Rust functions changed; crapper supplied no functions to prioritize.
Silent failure: omitted inputs flow into closure-scoped key assembly; a matching green key makes ledger_status skip.
Tests: in-memory graph checks reproduced both omissions and verified both proposed fixes. The mandatory-input mutant retains all three asserted files in the existing fixture; the proposed linked-only case exposes it.
Gates: no tolerances moved. The removed linkage-only mutation has a dated contract rationale in the PR body.
Escape catalog: C1–C4 did not match the changed shell code.

Notes

Dryer and crapper did not run because no Rust file changed. Full CLI and existing self-test suites were not run during validation.

🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since the last review:

Verdict: MUST_FIX: Both paths still allow changed executable behavior to reuse a green ledger key.

Must fix

1. Retain startup expansions of known source macros

tools/recipes.py:4701 exempts known macros outside reached items. A reached dependency can contain an unreached module invoking macros::boot!(), where a regular library exports boot! and emits an .init_array initializer calling crate::hidden::run(). The invocation, macro definition and hidden helper are omitted from the key. Editing the helper preserves the green key and skips changed startup behavior.
Fix the shared scanner before more recipes record stale green results. Replace the condition and body with:

                    if (macro not in safe_macros or "::" in call
                            or any(macro in self.macros(rk) for rk in roots[pname])
                            or any(self.exported(p).get(macro) for p in linked_packages)):
                        opaque = True

2. Include implicit impl calls from modules with other suffixes

tools/recipes.py:4203 excludes impl files without a .rs suffix. With #[path = "drop.rust"] mod drop;, a selected binary constructing dep::Token runs its Drop implementation there when the value leaves scope. Both the implementation file and its called helper are omitted; editing either preserves the green key.
Fix the shared impl scanner before destructor changes inherit stale green results. Replace lines 4202–4205 with:

        for f in files:
            facts = self.facts(rk, f)

Checked

Duplicates: no new definitions in the incremental diff; no dryer pairs supplied.
Complexity: no Rust functions changed; crapper supplied no candidates.
Silent failure: both omitted-input paths flow through key filtering and closure-scoped assembly to ledger reuse.
Tests: in-memory input-selection probes reproduced both omissions. Edits to the omitted files preserved the selected-input digest; both proposed fixes retained those inputs and made their edits change the digest.
Gates: no thresholds, pins or assertions removed; extern-crate coverage was extended and dependency metadata corrected.
Escape catalog: C1–C4 do not match the incremental changes.

Notes

Dryer and crapper did not run because no Rust file changed. Full CLI and self-test suites were not run; probes used in-memory inputs with persistent caching disabled.

🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Addressed both MUST_FIX items from #587 (review) in f9c69bb.

  • Known unqualified regular-source macro invocations in compiled but otherwise unreached units seed additional uncut structural roots. Qualified/shadowed/unknown calls remain conservatively bounded. The actual two GPU key closures remain narrower than compilation (604→560 and 604→559); local unconditional enum-variant globs do not falsely imply macro shadowing.
  • Shared implicit-impl rules no longer discard non-.rs module files. #[path="drop.rust"] Drop and its transitive helper are retained.
  • The proc-macro helper test now establishes a green merely-linked target and checks helper invalidation before adding the separate opaque attribute, addressing the nonblocking test concern independently.

Four review-focused CLI methods pass. Four real CPU Cargo programs compile and execute (exported regular-library startup, local macro-generated Drop, builtin-named source macro, and nonstandard Drop path): helper edits change output source-macro1→source-macro2; caller=run, unrelated=skip. Comments, Python compilation and the 69-tool registration check pass. Both complete suites are now running on this exact published head; no complete-suite pass is claimed yet. PR body updated with current counts and pending status.

github-actions[bot]
github-actions Bot previously approved these changes Oct 21, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since the last review:

  • Must-fix 1 is fixed: known source macro invocations add structural roots or retain opaque bounds at tools/recipes.py:4692.
  • Must-fix 2 is fixed: implicit impl scanning includes modules without a .rs suffix at tools/recipes.py:4203.

Verdict: CLEAN: The incremental changes close both previously reported key omissions, with no new must-fix finding.

Checked

Duplicates: 4 added or renamed definitions checked; no duplicate owner found. No dryer pairs supplied.
Complexity: no crapper candidates; key_files adds closure iteration and macro/glob branches, and _close adds root seeding.
Silent failure: 2 empty-result paths followed. Absent extra roots preserve ordinary reach; unresolved globs force opaque retention.
Tests: in-memory mutants dropping extra roots at tools/recipes.py:4505, restoring suffix filtering at tools/recipes.py:4203, dropping mandatory proc macros at tools/recipes.py:4632, and marking enum globs opaque at tools/recipes.py:4702 violate the corresponding assertions. Helper edits change the selected-input digest; unrelated binaries exclude those helpers. A further macro-to-dependency startup scenario retains its downstream helper.
Gates: no thresholds, pins or assertions removed.
Escape catalog: C1–C4 do not match the incremental changes.

Notes

Dryer and crapper did not run because no Rust file changed. Full CLI/cache and self-test suites were not run; verification used in-memory structural probes with persistent caching bypassed.

🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)

@Burntgogi

Copy link
Copy Markdown
Contributor Author

f4335da fixes the precision regression exposed by the complete f9c69bb self-test (13 unnecessary-key failures). Qualified source macros are resolved and seeded through the same structural expansion edges as reached calls, instead of unconditionally widening their callers. Per-definition bodies come from the canonical unit parser; nested expansions/attributes still conservatively bound the invoker. Unrelated opaque items elsewhere in a macro provider do not falsely widen that caller.

All 86 unchanged synthetic Reach assertions pass; no assertion was removed or weakened. Four focused CLI methods pass with two added matrix variants. Six real CPU Cargo programs compile and execute, including pure qualified source-macro calls and regular-source → procedural startup: changed helper output source-macro1→source-macro2, caller=run, unrelated=skip. Pure source calls retain unused-file exclusion. Real recipe counts remain 604→560 and 604→559. Both complete suites are running on this exact published head; the PR body explicitly records the previous failure and pending full verification.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since the last review:

  • Earlier must-fix 1 remains fixed: source macro invocations seed structural reach.
  • Earlier must-fix 2 remains fixed: implicit impl scanning includes non-.rs modules.

Verdict: MUST_FIX: Qualified macro expansions can omit behavior-changing caller imports from ledger keys.

Must fix

1. Keep opaque bounds for macro-generated imports

tools/recipes.py:4719: When dep::startup::run() invokes macros::boot!() with exported body () => {{ use crate::hidden::run; run(); }}, this check accepts the body and line 4745 bypasses opacity. rust_facts removes the import path from the macro's sites; Reach copies its sites and identifiers into the invoker without its import bindings. The invoker's crates/dep/src/hidden.rs is excluded from the key. Editing its output leaves the selected-input digest unchanged and an existing green recipe skippable.
Fixing this classifier now prevents stale green entries from accumulating. Retain the invoker's compilation bound for imports, including explicit crate links:

if body is None or re.search(r"\b(?:use|extern\s+crate)\b", body) or any(_key_attributes(body)) or any(
        match.group(1).removeprefix("r#") not in syntax_words
        for match in _MACRO_PATH_CALL.finditer(body)):
    opaque = True

Add the imported-helper variant to the existing macro fixture and assert that editing hidden.rs changes its ledger status to run.

Checked

Duplicates: the two added definitions reuse the existing lexer and unit parser. No dryer pairs supplied.
Complexity: key_files adds qualified-call resolution and macro-body safety branches in tool code.
Silent failure: unresolved qualified calls and missing macro bodies retain opacity; macro-generated imports bypass it.
Tests: an in-memory production Reach probe excludes the imported helper and leaves its selected-file digest unchanged after an edit. The proposed guard retains the helper and changes that digest. The direct-path control still excludes the unused sibling. The inline replacement parses as valid Python and sits on an added line.
Gates: no thresholds or pins moved; no assertions removed.
Escape catalog: C1–C4 do not match the incremental diff.

Notes

Dryer and crapper did not run because no Rust file changed. Full CLI/cache suites were not run; validation used in-memory structural probes with persistent caching bypassed.

🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)

Comment thread tools/recipes.py Outdated
body = _source_macro_bodies(self.text(h[1]), h[0][2]).get(h[2])
# Nested expansions/attributes may emit names absent from source edges.
# Bound the invoker, not an unrelated item elsewhere in the macro crate.
if body is None or any(_key_attributes(body)) or any(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A qualified macro that imports a caller helper loses that helper's binding during reach analysis; retain the invoker's compilation bound so helper edits invalidate the key.

Suggested change
if body is None or any(_key_attributes(body)) or any(
if body is None or re.search(r"\b(?:use|extern\s+crate)\b", body) or any(_key_attributes(body)) or any(

github-actions[bot]
github-actions Bot previously approved these changes Oct 21, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since the last review:

  • Macro-generated imports: fixed at tools/recipes.py:4719. Import-bearing expansions retain the invoker's compilation inputs and forward dependencies.

Verdict: CLEAN: The added guard closes the reported stale-ledger-key path.

Checked

Duplicates: 0 new definitions; the imported-helper case extends the existing fixture. No dryer pairs supplied.
Complexity: key_files gains one safety condition in tool code; no engine hot path changes.
Silent failure: 1 missing-body fallback followed to retained compilation inputs.
Tests: the imported-helper variant reaches the file-presence assertion at tools/recipes-key-test.py:331 and the ledger run/skip assertion after a helper edit. Dropping the import guard restores the path that bypasses conservative retention. Both changed Python files parse.
Gates: no thresholds or pins moved; no assertions removed.
Escape catalog: C1–C4 do not match the incremental diff.

Notes

Dryer and crapper did not run because no Rust file changed. Validator checks were static code inspection and Python parsing; full CLI/cache suites were not rerun.

🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Addressed #587 (review) in d200e41 and 758109c: source-macro bodies containing use or extern crate retain the invoker's conservative compilation/forward bounds. This applies to both qualified source calls and parent-defined unqualified source macros, rather than special-casing the reviewed spelling.

The qualified imported-helper case is now a permanent variant. I also reproduced the same root problem with a local macro expanding use super::hidden::run; run();: actual Cargo output changed source-macro1→source-macro2 while the old key stayed skip. After the shared fix the same actual output change produces caller=run, unrelated=skip. That case is permanent too. The pure direct-path control still excludes genuinely unused source. All 86 unchanged synthetic Reach assertions and the expanded focused CLI method pass.

For context, the complete prior-head f4335da suite passed (203 recipes, 0 failures), as did all 20 CLI methods. Both suites are now running on committed 758109c; the PR body distinguishes prior-head proof from pending final-head proof. Current real-repository key sizes remain 604→560 and 604→559.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Final local verification is complete on 758109c:

  • python3 tools/recipes.py --self-test: 203 gate recipes, 0 failures, 1207.76 s.
  • python3 tools/recipes-key-test.py --self-test: 20 passed, 217.91 s; includes both qualified and local-relative generated-import regressions, source startup/Drop, nonstandard module paths, mandatory build/proc inputs and content/cache invalidation boundaries.
  • All 86 existing synthetic Reach assertions retained and passing; no weakening/removal to hide the earlier over-keying failures.
  • Actual CPU Cargo behavior was exercised across eight macro/Drop scenarios. The local-relative imported helper was specifically failing-before (changed output with caller=skip) and passing-after (same output change with caller=run, unrelated=skip).
  • Fresh isolated-HOME cold/warm processes publish then hydrate the structural graph and retain exactly the same file-set SHA-256 values. The two real GPU recipe bounds are compilation604/key560 and compilation604/key559, without whole-tree fallback. No controlled speedup claim.

The PR body now records completed final-head local proof rather than pending runs, plus the scope/safety tradeoffs and review fixes. Latest observed GitHub T0 is still in progress and maintainer review is required; neither is represented as passed. No NVIDIA/model execution or merge claimed.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Addressing the actual T0 timeout rather than raising its 1200-second bound. Current head: 1ca2973; upstream main f956b65 is integrated.

A cProfile scenario with six repeated real cached recipe-input requests observed 12 full freshness checks and 36 calls to Python compile. The fix checks current content once before returning an existing input closure, and memoizes disk-code compilation by exact source bytes + filename + optimization level. File bytes are still reread; no mtime-based freshness shortcut was added. Final key-output validation remains unchanged.

The same six-request scenario now observes 6 freshness checks and 0 compilation calls after warm-up. Exact file-set fingerprints are unchanged for both sampled GPU recipes (560 and 559 key files). This is call-count/profile evidence, not a controlled throughput claim.

The existing imported-code regression now first establishes a green warm-cache lookup, then edits each analysis module while preserving both size and mtime; every changed module is still refused rather than emitted as skippable. That focused regression and the comments guard pass.

All CLI regressions and both previously timed-out commands are running on this head with the original timeout --kill-after=30 1200 bound. Complete final results will be attached after observation. No test, failure clause or time limit was relaxed. Separate-session #590 work is not touched.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Current-head performance verification (1ca2973)

The unchanged 1,200-second bounds now pass locally after integrating upstream main f956b65:

  • python3 tools/recipes-key-test.py --self-test: 20 methods passed (58.083 s), including loaded-analysis-code changes after a warm key lookup.
  • timeout --kill-after=30 1200 python3 tools/recipes.py --self-test: 208 recipes, 0 failures; key section 804 s.
  • LC_ALL=C timeout --kill-after=30 1200 bash tools/check-recipes.sh: passed, 146 blocks run, 0 skipped by a green key, 859.34 s wall; internal 208-recipe self-test 841 s.

The first local integrated run finished below the timeout but failed English shell-diagnostic fixtures under inherited Korean locale. The C-locale run above passed all 869 card tests and all 99 tree-commit checks without changing their assertions or increasing a timeout.

A same-tree six-request cached-lookup profile (before upstream integration) reduced code compilations 36 → 0 and full snapshot validations 12 → 6; both sampled input file sets and their hashes were identical. Exact source bytes are still reread, restored-mtime source changes are refused, and final key publication still revalidates its inputs. This is call-count evidence, not a controlled throughput benchmark. The temporary profiler has been removed.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

CI result update for 1ca2973: the EPYC 7763 run has now completed, and both recipes-self-test and check-recipes still hit the unchanged 1,200 s unit bound (rc 124); the other 102 units passed. The earlier local 859 s integrated pass is real but is not CI success. I am continuing the performance fix rather than rerunning the same failing check or raising its bound. The retained profile identifies repeated full-corpus/recorded-read validation and path resolution as the remaining cached-lookup cost; the next change will retain content and publication checks.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Second CI-performance fix published at 726256e; the 1,200-second bounds are unchanged.

The observed cold-rebuild profile exposed 8.8 million repeated schema-introspection calls per graph validation; schema descriptors are now reused while every value is still strictly validated (including bool ≠ int). A retained before/after checker agreed on all 384 acceptance/rejection comparisons. The two full cold profiles retain exactly 78,342 nodes and 2,260 fact records.

Uncached recipe analysis now reuses immutable exact-text file syntax, and collects module ancestors once per root/file through the existing bases index rather than rescanning every module for every reached unit. Bindings, macro resolution, active-owner decisions and file/content/publication validation are not cached away. Four real recipe input path sets and their SHA-256 hashes were identical; the sampled analysis still performed all 8 full snapshot validations before and after. Profile timings are not a controlled throughput benchmark.

All 20 CLI safety methods pass (59.129 s). Full recipes.py --self-test and check-recipes.sh are now running pinned to CPUs 0,1 under their original 1,200-second bounds; this is a local Intel two-CPU check, not an EPYC equivalence claim. The temporary profilers have been removed. CI on the previous 1ca2973 timed out; success is not claimed until the new-head result arrives. A maintainer review is still needed because the automatic review cap has been reached.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Two-CPU smoke update at the same 726256e source:

  • Full recipes.py --self-test: PASS, 208 recipes/0 failures; slowest key section 849 s (original 1,200 s bound).
  • The integrated checker pinned to CPUs 0,1 but using its default scheduler timed out. Inspection shows why this is not the same worker configuration as a two-vCPU runner: this host reports os.cpu_count() == 16 while affinity is [0, 1], and static-sched defaults to cpu_count. No source/test change is being made to hide that stress result.
  • Replaying the complete generated set of 146 checker jobs with the existing documented static-sched.py --max 2, still on CPUs 0,1 and under the original 1,200 s bound; no filtering, ledger or green-key skipping. This aligns the slot count with a two-vCPU CI runner. The actual new-head GitHub CI is still being watched independently.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

726256e actual CI has completed: 103/104 units passed. recipes-self-test now returned 0 (the reported wall is 1,222 s, so margin is not sufficient), but integrated check-recipes still timed out at its unchanged 1,200 s bound. I am continuing to profile the exact key-self-test section and the remaining cold-cache work; not claiming the CI issue is solved and not rerunning that same failed CI head. The local explicit two-slot full checker is still running.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Local full-check result for 6e6e15b: LC_ALL=C timeout --kill-after=30 1200 bash tools/check-recipes.sh completed in 507.75 s. All 146 blocks ran, 0 green-key skips, and the embedded self-test passed 208 recipes / 0 failures. CLI ledger safety: 20 methods passed. Actual cold/cache-hit API smoke preserves all 78,342 nodes / 320,084 edges and the four earlier real-recipe path-set hashes. No timeout, coverage, or scheduler policy was changed.

Upstream T0 for this exact head is still running: https://github.com/midagedev/bloomery/actions/runs/39594669632 . This local result is not yet a claim that upstream CI passed.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Exact-head upstream T0 for 6e6e15b is now green, testing synthetic merge c67de03 onto main 5b626c3:

  • recipes-self-test rc=0: 796 s
  • tools/recipes-key-test.py rc=0: 80 s
  • check-recipes rc=0: 962 s
  • DONE tier=t0 total=106 red=0

https://github.com/midagedev/bloomery/actions/runs/39594669632/job/119203910635

The unchanged limit is 1,200 s per unit. This runner was an Intel Xeon Platinum 8573C; the preceding failed run used a different CPU, so these are pass/deadline results, not a controlled speedup benchmark. The local unchanged integrated checker also passed all 146 blocks / 0 ledger skips. The timeout blocker is resolved on this head; ready for maintainer review.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: MUST_FIX: valid Rust calls can be omitted from ledger keys and reuse an outdated green result.

Must fix

1. Keep inputs behind absolute paths and raw identifiers

tools/recipes.py:3431 The path parser ignores ::dep::used::run() and truncates dep::r#type::run(). In-memory Graph/Reach probes confirm that the new key filter drops the called module in both cases. Editing that module leaves the key unchanged, so a recorded green result can skip the changed gate.
Fix this before v2 green entries accumulate with missing inputs. Conservatively mark these forms opaque by replacing this return argument:

        opaque or bool(re.search(r"(?<![\w:>\]])::|r#[A-Za-z_]", shape)),

Add CLI regressions that edit each called module after recording a green key.

2. Traverse selected source roots without a .rs suffix

tools/recipes.py:4681 A Cargo target with path = "main.rust" and fn main() { dep::used::run(); } retains its own source but drops dep/used.rs. Reach.units assigns the root a data node, so its calls never seed reach. Editing the called module can therefore reuse a green result. The same probe with main.rs retains the dependency.
Fix source classification before these targets produce reusable green keys. Replace Reach.units:

    def units(self, rk: RootKey, f: str) -> list[Hit]:
        """Every unit of a Rust module; a non-source data file uses index -1."""
        if not f.endswith(".rs") and f not in self.mods(rk).values():
            return [(rk, f, -1)]
        n = len(self.facts(rk, f).units)
        return [(rk, f, i) for i in range(n)] or [(rk, f, -1)]

Add a CLI regression with an explicit non-.rs target path.

3. Date the removed FAIL-first coverage

tools/recipes.py:11140 The plain-link hf mutation and its two assertions were removed without a dated reason. AGENTS requires a dated reason for removed coverage.
Record the contract change while its reason is known. Insert after the docstring:

    # PIN(2026-10-22): Drop the plain-link hf mutation: v2 keys exclude uncalled runtime code.

Checked

Duplicates: 78 new functions, types and constants checked, including nested functions; no proven duplicate. Dryer supplied no pairs.
Complexity: no Rust functions changed; no crapper targets. The change touches tools, not inference hot paths.
Silent failure: 3 cache fallback sites followed; invalid snapshots rebuild, and failed publication retains the computed graph.
Tests: in-memory Graph/Reach probes reproduce the absolute/raw-path and non-.rs-root omissions; the proposed fixes retain the missing inputs. A normal .rs-path control retains its dependency. Both inline suggestions target added lines and parse as Python.
Gates: no thresholds moved; the removed mutation lacks a dated reason (item 3).
Escape catalog: no C1–C4 pattern matched the changed executable code.

Notes

Dryer and crapper did not run because no Rust file changed.
The full CLI suite was not run because its fixtures write files outside the permitted outputs. The PR body reports passing runs but supplies no FAIL-first output for the new tests.

🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)

Comment thread tools/recipes.py Outdated
calls = frozenset(match.group(1) for match in _MACRO_PATH_CALL.finditer(shape))
return KeySyntax(
frozenset(re.findall(r"\bextern\s+crate\s+([A-Za-z_]\w*)", shape)),
opaque,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Absolute and raw paths escape structural reach; retain their owner bounds so a called module edit changes the key.

Suggested change
opaque,
opaque or bool(re.search(r"(?<![\w:>\]])::|r#[A-Za-z_]", shape)),

Comment thread tools/recipes.py
and the chains `why --recipe` prints."""
import copy

"""The runtime key/reach pins and genuine Cargo-linkage explanation chains on the real tree."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removed coverage requires a dated reason under AGENTS.

Suggested change
"""The runtime key/reach pins and genuine Cargo-linkage explanation chains on the real tree."""
"""The runtime key/reach pins and genuine Cargo-linkage explanation chains on the real tree."""
# PIN(2026-10-22): Drop the plain-link hf mutation: v2 keys exclude uncalled runtime code.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Oct 22, 2026
@Burntgogi

Copy link
Copy Markdown
Contributor Author

Addressed all three MUST_FIX items from #587 (review) in 94ee6b2, on current upstream main 69b6636. The new public key/ledger regressions failed before the fix in all three executable-input cases; all 22 CLI methods now pass. Separate compiled Rust consumers change their output and correctly invalidate the called recipe, preserving the unrelated linked recipe. Full owning static verification: 5 units / 0 red, check-recipes 146 blocks / 0 skips, unchanged 1,200 s bounds. The dated hf-coverage rationale is recorded. Full commands/results and the local pre-review prerequisite failure are in the updated PR body. Fresh GitHub review/CI are not yet claimed green.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Additional compatibility check with the independently scoped multiline-attribute input fix in #658: it applies cleanly to 94ee6b2; the combined tree passes all 22 public key/ledger CLI tests (67.355 s). A real Rust consumer using thiserror + multiline-attribute include_str! also changes output on the input edit and yields run with the combined reached-key implementation. This does not add unrelated #637 changes to this PR.

@Burntgogi

Copy link
Copy Markdown
Contributor Author

Current-head review handoff: the review gate selected run=false (6 automatic reviews (cap 5); run the workflow by hand for another). Thus 94ee6b2 has not received a new automated review; the earlier three MUST_FIX corrections and their runtime/check evidence are above. My upstream repository permission is pull-only, so I cannot dispatch that workflow. Please manually run the review workflow for the current head. No cap bypass or approval is being claimed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation tools Tools, recipes, CI and the toolchain: tools/, justfile, .github/, Cargo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tools: gate keys per recipe from what its bins reach, not whole crates

1 participant