Advisory ledger-reference ratchet lint (#3328) - #3333
Conversation
Follow-up to #3288 (durability guardrail, per HITL cq-2): #3288 de-ledgered the docs corpus and reframed the documenter to emit snapshots, but explicitly deferred an automated guard against new slice-N / TASK-N / cq-N SDLC ledger refs creeping back into docs/docstrings. This adds that guard. scripts/check-ledger-references.py is an advisory (warn, never block) ratchet: - Detects the three unambiguous ledger token classes -- slice-N, TASK-N, cq-N -- across repo markdown plus non-test Python source. - Ratchet semantics via scripts/ledger-references-baseline.yaml (per-file counts): flags only net-new tokens, so the long pre-existing tail does not fire on every run. Lowering a count never warns. - False-positive controls: hyphenated patterns skip live runtime vocabulary (slice_id, contract.slices, EGG_*_SLICES); .egg-state/ and docs/templates/ (live plan-format TASK-N) are excluded; tests excluded; per-line `ledger-ok` escape hatch for legitimate live-machinery additions. - Auto-discovered by `make lint-custom` (scripts/check-*.py). Exits 0 by default; `--strict` makes net-new a hard failure (not wired into CI yet), per "advisory first, tune the allowlist before a hard gate". Change-log prose ("what was removed", "used to ... now ...") is deliberately out of scope for v1 -- it can't be matched without high false positives against the issue links that legitimately justify current-state rationale.
This comment has been minimized.
This comment has been minimized.
…-231517-3077/work
Merging main into the PR branch pulls in #3332's session-store files (session_state_store.py, session_state.py, session_state_sync.py) and edits to concurrent_executor.py / consensus_wrapper.py, which add SDLC ledger tokens (slice-N / TASK-N / cq-N) not present in the committed baseline. The advisory ratchet's TestRepoBaselineIsClean flagged these as net-new in the pull/3333/merge corpus that CI tests. Regenerated via scripts/check-ledger-references.py --update-baseline so a fresh checkout of the merged corpus scans clean.
Autofixer: Unit TestsFixed the in-scope failure and identified two pre-existing failures that are not caused by this PR. Fixed —
|
Autofix tracking{"Test/Unit Tests": 3} |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
docs/reference/orchestrator-cli.md was missing the #2548 cross-reference that test_cross_references_issue_2548 pins, and the egg/<id>/context clarification in concurrent-execution.md did not share a paragraph with a slice-1 mention as test_slice_1_paragraph_ties_to_context_branch requires. Add the #2548 link to the Context PR Surfaces field intro and tie the no-separate-context-branch note to slice-1 base resolution.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Revert the bundled #2548 doc edits (concurrent-execution.md, orchestrator-cli.md) that an autofixer added to chase two doc-test failures pre-existing on main. Those failures are unrelated to #3328 and are owned by the separate, green PR #3325 — keeping the edits here duplicates #3325 and would conflict on whichever lands second. Refresh the ledger baseline so the self-test stays green in both merge orderings: concurrent-execution.md goes 15->16, matching #3325's single added slice-1 token (the context-PR base clarification). With the baseline at the post-#3325 count and this PR's own tree doc-free, net-new is empty whether or not #3325 has landed yet. This PR is now scoped to its three ledger files. It still needs #3325 on main to clear the #2548 doc tests on the merge tree.
|
Pushed What changed
This PR is now scoped to its three ledger files ( Merge order: land #3325 first (it clears the #2548 doc tests on Caveat (design): |
There was a problem hiding this comment.
Review: Advisory ledger-reference ratchet lint (#3328)
I traced the full data flow (iter_scanned_files → scan_file → evaluate → _print_net_new / baseline I/O), the make lint-custom auto-discovery path, the make test-all test wiring, and ran the check + the 30-test suite locally (both green; --strict clean against the committed baseline). The implementation is solid, the detector is genuinely well-tested, and the feature works end-to-end. No blocking issues. Approving with several non-blocking observations below.
Verified working
make lint-custominvokes$(PYTHON) scripts/check-*.pywith no args (Makefile:315), so the script runs in advisory mode and exits0— confirmed it never blocks the lint target, as the PR claims.- The
slice-N/TASK-N/cq-Ndetection is independently verified byTestPattern,TestScanFile, andTestEvaluatewith hand-authored expected counts (e.g.test_counts_occurrences_not_linesassertscount == 3). I specifically checked whetherTestRepoBaselineIsCleanis a self-seeding golden — it is not a blocking concern: the baseline is impl-generated, but the detector's correctness is pinned by those independent unit tests, so the repo-baseline test is a legitimate freshness guard, not a circular detector golden. Good design. - The script correctly excludes itself (
iter_scanned_files,scripts/check-ledger-references.py:156-157,175) so theslice-4/TASK-4-5examples in its own docstring don't self-trip.
Non-blocking observations
1. The bundled freshness test is a de-facto hard CI gate — worth making explicit.
The PR is framed as "advisory (warn, never block); --strict intentionally not wired into CI yet." But TestRepoBaselineIsClean (tests/scripts/test_check_ledger_references.py:165-177) scans the whole live corpus and asserts zero net-new against the committed baseline, and it runs under make test-all (Makefile:398 roots include tests/). Net effect: any PR that introduces a token matching the pattern — including legitimate live-machinery false positives (a new slice-\d+ regex string, a TASK-\d+ docstring example, a cq-\d+ reference) — turns make test-all red until the author runs --update-baseline or adds ledger-ok. That partially front-loads the exact false-positive tuning cost the PR says it's deferring. This is the standard ratchet pattern and arguably correct (it keeps the baseline from rotting), but the "never blocks a PR" wording in the description understates it. Suggest documenting that the test, not the lint exit code, is the enforcement point.
2. Markdown under test directories is not excluded (asymmetry with .py).
is_test_path is only applied in the .py loop (check-ledger-references.py:171); the markdown loop (:159-163) filters via is_excluded only. So a future fixture like orchestrator/tests/fixtures/foo.md using slice-1 / task-1-1 as live test data would be scanned and counted, while its .py sibling would not. Currently latent — the one test-dir markdown (gateway/tests/README-integration.md) has no tokens — but the asymmetry will surprise someone. Suggest applying is_test_path in the md loop too, or noting the asymmetry in the docstring.
3. Case sensitivity. slice-\d+ / cq-\d+ are lowercase-only and TASK-\d+ uppercase-only (:98-102). Slice-4 at a sentence start, CQ-2, or a lowercase task-5 in prose would be missed. Probably fine given repo conventions, but since the detector is the whole point, a case-insensitive variant (with the underscore/dotted-access guards kept) would be more robust.
4. Docstring wording vs behavior (minor). The docstring says the Python scan covers "docstrings/inline comments" (:43-44), but scan_file regexes every line. In practice - can't appear in a Python identifier, so tokens only land in strings/comments anyway — the effect matches, but the wording implies a parse that doesn't happen.
5. Performance (minor). repo_root.rglob("*.md") (:159) walks the entire tree — including .git/, .venv/, node_modules/ — before filtering via is_excluded. Fine at current repo size; prune during traversal if those dirs grow.
None of these block merge. Nice, well-tested guardrail.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…q; prune traversal - Remove the committed-baseline freshness check from the blocking unit suite; the advisory script (make lint-custom) is the --update-baseline reminder, keeping the ratchet truly advisory (per #3328 intent). - Match slice-N / cq-N case-insensitively so sentence-initial Slice-4 / CQ-2 are caught; keep TASK-N uppercase-only (lowercase task-N is live runtime vocabulary). Baseline regenerated to absorb the newly-caught capitalized Slice-N tokens. - Apply is_test_path to the markdown loop too (symmetry with .py). - Prune .git/.venv/node_modules/.egg-state/test dirs during traversal via os.walk instead of rglob-then-filter. - Clarify docstring: every line is scanned, not just docstrings/comments.
Review feedback addressed (commit
|
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: Advisory ledger-reference ratchet lint (#3328)
Re-reviewed against the current branch tip (4e6fa8b). The PR is scoped to exactly its three ledger files — scripts/check-ledger-references.py, scripts/ledger-references-baseline.yaml, tests/scripts/test_check_ledger_references.py (confirmed via gh pr diff --name-only; the doc/pipelines.py deltas a two-dot diff shows are just main drift, the branch is behind on those files and does not author them). All five non-blocking observations from my prior review are addressed substantively, not superficially. No blocking issues. Approving.
Verified fixes
-
Blocking freshness self-test dethroned —
TestRepoBaselineIsClean::test_no_net_new_against_committed_baselineis gone from the blocking suite, replaced by a module-level NOTE (test_check_ledger_references.py:178-190) that records why it must stay advisory-only so it doesn't get re-added. This removes the de-facto hard CI gate / maintenance treadmill that contradicted the issue's "advisory, never block" intent. The committed-baseline freshness check now lives only inmake lint-custom(exit 0). Correct call. -
Markdown/Python test-dir exclusion now symmetric —
is_test_path(rel)is applied to every file in_walk_in_scope(check-ledger-references.py:171), covering both the.mdrepo-wide walk and the.pysource-root walks.test_test_dir_markdown_excludedpins it. The prior.md-vs-.pyasymmetry is closed. -
Case sensitivity —
slice-N/cq-Nare now case-insensitive via scoped inline groups(?i:slice)/(?i:cq), whileTASK-Nstays uppercase-only. I verified the compiledLEDGER_PATTERNempirically across 17 cases:Slice-4/SLICE-7/CQ-2/Cq-3match;task-5/task-123/task-20251129-222239(live runtime vocabulary) correctly do not;slice_id/contract.slices/subslice-4stay excluded. I agree with the split decision to leaveTASKuppercase-only — lowercasetask-Nis genuinely pervasive live vocabulary (timestamped run ids, contract task ids), and the plan-format casing is the right discriminator. Both directions are covered byTestPattern. -
Docstring wording — the "Scope" section now states every line is scanned and explains that tokens only land in strings/comments in practice because
-isn't a Python identifier char. Matches behavior. -
Performance —
iter_scanned_filesnow usesos.walkwith in-placedirnames[:]pruning (_walk_in_scope), so traversal never descends into.git//.venv//node_modules//.egg-state//docs/templates// test dirs. Replaces the priorrglob("*.md")whole-tree walk.
Test quality is sound: hand-authored expectations throughout (no self-seeding goldens), update_baseline exercised through the real production path, evaluate tested in isolation with scan_file/scan_all covered separately.
Non-blocking observations (residual, advisory-only impact)
-
test(singular) directory not excluded.EXCLUDED_DIR_NAMESis{"tests", "__pycache__"}andis_test_pathonly checks those parts. A future**/test/directory (a tester-writable pattern peragent-roles.md) carryingslice-N/cq-Nfixture data would be scanned, unlike itstests/sibling. Latent today; worth folding"test"in for completeness. Advisory-only, so at worst it's noise inmake lint-custom. -
Dangling-symlink robustness.
scan_fileusesread_text(errors="replace"), which tolerates bad encodings but not a broken symlink surfaced byos.walk(would raiseOSError). Extremely unlikely in-tree, advisory path only, but atry/except OSError: return zero-countwould harden it.
Neither blocks. Clean, well-tested guardrail — the feedback was handled thoroughly.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Review feedback addressed (commit
|
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: Advisory ledger-reference ratchet lint (#3328)
Reviewed the delta 4e6fa8b..5abfb5c (12+/3- in scripts/check-ledger-references.py, 24+ in the test module) — the two residual non-blocking observations from my prior re-review. Both are addressed substantively, with genuine tests that exercise the production path. No blocking issues. Approving.
Verified fixes
-
Singular
test/directory now excluded —EXCLUDED_DIR_NAMESis{"tests", "test", "__pycache__"}(check-ledger-references.py:102). This feeds both enforcement points:is_test_path(:170, exactpart in …match) and the in-place pruning in_walk_in_scope(:186, exactname not in …). The match is exact-on-path-component, so there's no over-reach —latest/,contest/, and a file literally namedtest.md(part"test.md" != "test") are all unaffected; I checked. Pinned bytest_singular_test_dir_skipped(predicate) andtest_singular_test_dir_pruned(end-to-enditer_scanned_fileswalk). Symmetric withtests/now, as intended. -
scan_filehardened against unreadable paths — theread_textis wrapped intry/except OSErrorreturning a zero-countFileFindings(:223-230).os.walksurfaces a dangling symlink infilenames, andread_text(errors="replace")tolerates bad encodings but not the resultingFileNotFoundError/OSError. Degrading to zero rather than crashing the whole run is the correct call for an advisory-only scan, and the broadOSErroris safe here:_walk_in_scopeonly ever passes regular files, and the input is the repo tree being scanned, not deliberately-set operator config — so this is legitimate graceful degradation, not a misconfiguration-masking fallback. Pinned bytest_unreadable_path_degrades_to_zero(real dangling symlink → realscan_file).
Verification performed
findoverorchestrator|gateway|sandbox|shared|scripts|configfor singulartest/dirs → none exist today, confirming the author's "no baseline change" claim. The exclusion is purely forward-looking.- No baseline entry contains a
/test/segment (113 entries scanned) → the broadened exclusion has zero effect on the current committed baseline. python3 scripts/check-ledger-references.py(advisory) and--strictboth exit0against the committed baseline.pytest tests/scripts/test_check_ledger_references.py→ 39 passed.
Test quality remains sound: hand-authored expectations throughout, no self-seeding goldens, the new tests construct real symlinks / real directory trees and drive the actual scan_file / is_test_path / iter_scanned_files production code (no hand-built fixtures bypassing the helpers).
Nothing residual worth flagging. Clean, well-tested guardrail — the feedback was handled thoroughly.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
| egg is addressing review feedback... |
Review feedback — disposition summaryThe latest review (egg-reviewer, Every observation raised across the three-review cycle was already resolved in-PR. Recording the full ledger for completeness: Review 1 (commit
Review 2 (commit
Review 3 (commit No new code changes were required for this round. All 39 tests in — Authored by egg |
|
egg feedback addressed. View run logs 13 previous review(s) hidden. |
Closes #3328. Follow-up to #3288 (durability guardrail deferred by HITL cq-2).
What
#3288 de-ledgered the docs corpus and reframed the documenter to emit snapshots, but — by explicit operator decision (cq-2 = "out of scope, note as follow-up") — did not add an automated guard to keep
slice-N/TASK-N/cq-Nledger references from creeping back in. This adds that deferred guard as an advisory (warn, never block) CI ratchet.How
New custom check
scripts/check-ledger-references.py, auto-discovered bymake lint-custom(anyscripts/check-*.py):slice-N,TASK-N,cq-N— across repo markdown plus non-test Python source (docstrings/comments).scripts/ledger-references-baseline.yaml(per-file counts): flags only net-new tokens, so the long pre-existing tail (tracked separately, decaying as docs are touched) does not fire on every run. Lowering a count never warns. Re-snapshot with--update-baseline.0so it never blocks a PR.--strictmakes net-new a hard failure (intentionally not wired into CI yet — per the issue's "advisory first, tune the allowlist before a hard gate").False-positive controls (the reason the guard was deferred)
The issue calls out live-runtime vocabulary that shares these tokens. Handled by:
slice_id,contract.slices,EGG_*_SLICES, "the slice DAG" all use underscores / dotted access / bare words and don't match.docs/templates/(plan.md'sTASK-Nis the live plan format),.egg-state/(pipeline state + BRC transcripts are a ledger by design), and test files (fixtures useslice-N/task-N-Nas live data).ledger-okin a comment on the line excludes it (e.g. a legitimate new slice-DAG error message).Deliberately out of scope for v1
Change-log prose ("what was removed", "used to … now …") — it can't be regex-matched without high false positives against the issue links that legitimately justify why the current system is shaped the way it is (which #3288 explicitly keeps). Noted in the script docstring as a tuning follow-up.
Tests
tests/scripts/test_check_ledger_references.py(30 tests): pattern match/non-match, occurrence counting, suppress marker, exclusions, ratchet evaluate (over/at/under baseline, new file), baseline round-trip, and a guard that the committed baseline is clean against the live corpus (so a fresh checkout produces no spurious advisory noise).make lint-custom→ledger-referencesruns clean.ruff check/ruff format --check/mypyclean on new files.