Add bounded Lean project inspection - #14
Conversation
Deicyde
left a comment
There was a problem hiding this comment.
Review result: changes needed.
project/inspect.py:494-503probes lowercaseblueprintwithout requiring exact directory-entry spelling. On default macOS filesystems, formal-math's trackedBlueprint/Lean library is falsely reported as an Autoform vault; Linux reports differently.project/inspect.py:257-266rejects projects containing both Lake configuration formats, although Lake 4.32.2 validly selectslakefile.lean. Treat the unused file as an advisory diagnostic or document the stricter policy.project/inspect.py:819-825permits C1 controls such as U+009B and prints them raw in human output. Apply the catalog's Unicode control/surrogate rejection consistently.
All three cases were reproduced; the focused inspection suite still passes.
Deicyde
left a comment
There was a problem hiding this comment.
Second pass on exact head 8bbe9d8, in addition to the three findings already filed:
-
P1: compatibility can be computed from a project state that never existed. The root descriptor is retained, but Lake, toolchain, and manifest files are read sequentially without a generation recheck (
project/inspect.py:60-67,606-676). I began with Mathlib 4.32.2 plus Lean 4.31.0, switched to Mathlib 4.31.0 plus Lean 4.32.2 after the Lake read, and receivedstatus: supportedfor the synthetic 4.32.2 pair. Snapshot all decision-file identities/content together, or revalidate and retry/fail on change. -
P2: nonregular config nodes are opened before being rejected.
_relative_statusidentifies a FIFO as unsafe, but_inspect_laketreats every non-missing status as readable and_read_fileopens it beforefstat(inspect.py:257-280,625-653). Opening a FIFO unblocked a waiting writer even though inspection later emittedlake-config-not-regular; device opens can have stronger effects. Reject the known non-file status before opening, while retaining the post-open race check. -
P2: a Lake-valid dependency is rejected. Explicit
scope = ""is Lake's accepted default (tryDecodeD scope ""), including for a direct Git dependency. Exact Lake 4.32.2 accepts and translates it, while inspection returnsinvalid-lake-field, discards the Lake/Mathlib model, and marks compatibility indeterminate (inspect.py:697-700). Treat absent and explicit-empty scope equivalently. -
P2: the documented offline operation can invoke account-service I/O without a deadline.
Path(target).expanduser()(inspect.py:121) resolves~userthroughpwd.getpwnam, which may use LDAP/NIS/SSSD. Restrict expansion to the current user's local home semantics or reject named-user forms. -
P2:
project inspectis unavailable on Windows without that limitation being declared. The capability gate requires POSIXO_NOFOLLOW,O_DIRECTORY, and dir-fd operations, so every Windows call returnssecure-file-inspection-unavailable. Add a safe backend or explicitly scope and test platform support. -
P3: malformed target paths escape the stable diagnostic contract.
inspect_project("\0")raisesValueError, and an unencodable surrogate raisesUnicodeEncodeError;_entry_status/_open_directorycatch onlyOSError. Convert these totarget-unreadable. -
P3: the compatibility catalog accepts contradictory release metadata. A catalog entry pairing toolchain
leanprover/lean4:v4.32.2withlean.version = "v9.9.9"parses and certifies a 4.32.2 project because matching ignores the version field (catalog.py:55-72,inspect.py:550-558). Validate consistency inside the authority document.
Validation: all 91 project-inspection tests pass; the first four behaviors were reproduced independently.
# Conflicts: # tests/test_plugin_runtime.py # uv.lock
Deicyde
left a comment
There was a problem hiding this comment.
Third pass on exact head 32e76b7. Review result: changes needed.
13 of the 17 earlier findings are fixed, including the generation recheck, rejecting FIFOs before opening them, explicit empty scope, and the malformed-target exceptions. Four problems block merging. Three make the compatibility answer, the headline feature, wrong or missing in common cases; the fourth is a regression from the last fix round. All four were reproduced on 32e76b7.
Blocking
-
P1: compatibility ignores
lake-manifest.json, sosupportedis reported while Lake builds a different Mathlib._compatibility(project/inspect.py:807-847) compares onlylean-toolchainwith thelakefile.tomlrequire, and_optional_digest(:849-867) only hashes the manifest. Bump the lakefile to Mathlibv4.32.2and the toolchain tov4.32.2without runninglake update, so the manifest still locksv4.31.0:lake buildonly warnsmanifest out of date: git revision of dependency 'mathlib' changedand builds the locked v4.31.0 (Lake'smaterializeDeps/validateManifestinLoad/Resolve.lean), while inspection reportssupported (lean-v4.32.2-mathlib-v4.32.2)with no diagnostic. A manifest with"version": "2.0.0"or invalid JSON, which Lake refuses to load (Manifest.lean:217-230), also yieldssupported. Fix: parse the manifest with Lake's version rules, treat the root's directmathlibentry (url,inputRev,rev) as what will be built, and never claimsupportedwhen it disagrees with the require (emit a stale-manifest diagnostic instead). Adding the Mathlib commit to the catalog (v4.32.2is905b95818eb32af7874a58b427f50c1711a5e96c) lets the lockedrevbe checked exactly instead of trusting a tag name..lake/package-overrides.jsoncan also replace entries: account for it, or do not reportsupportedwhen it is present. -
P1: catalog matching cannot recognize Lake's own Mathlib template or any real project checked.
_parse_mathlib(:903-930) returnsNoneunless the require has agitkey, butlake new foo mathwritesscope = "leanprover-community"plusrev = "v4.32.2"(mathTomlConfigFileContentsin Lake'sCLI/Init.lean). At exactly the catalog pair, inspection reportsmathlib: null,indeterminate, and "Offline inspection cannot determine a Lean and Mathlib release pair", which is false whenever a manifest exists, since the manifest records the Mathliburl,inputRevand lockedrev. Three real Mathlib projects (all using the scope form) also come backindeterminate, andtest_lake_generated_scope_requirement_is_indeterminate_offline(tests/test_project_inspect.py:593) pins this outcome. The exact-string URL match also rejectshttps://github.com/leanprover-community/mathlib4without.git, which is the URL the manifest records. Decision needed before freezing schema v1: how a catalog entry identifies Mathlib (git URL, Reservoir scope and name, or the manifest entry). Fix together with item 1: resolve scope-form requires through the root manifest entry, normalize URLs, and reportindeterminateonly when no usable manifest exists. -
P1: a case-variant
Lakefile.leanis hidden on macOS while Lake executes it. WithLakefile.leannext tolakefile.tomlon default case-insensitive APFS, inspection returnsok: true,lake.format: "toml"and nolakefile-lean-not-evaluatedwarning. Lake 4.32.2 in the same directory printslakefile.lean and lakefile.toml are both present; using lakefile.leanand evaluates the file (its#evalran). This contradicts the README ("Alakefile.lean... takes precedence overlakefile.toml, matching Lake") and hides exactly the fact that warning exists to report. Cause: the exact-spelling rule added forBlueprint/(_exact_entry_exists,:698, used by_relative_infoat:718-719, by_capture_fileand by_open_parent_descriptor) reports any case variant asmissing, for Lake configuration as well as for Autoform scaffold paths. The same rule makes discovery skip a subpackage whose only marker isLakefile.toml. Fix: distinguish "exists under some spelling" (a no-follow stat of the canonical name succeeds) from "exists under the exact spelling"; treat a case-variant decision file as present for precedence and fail closed with a dedicated diagnostic; keep exact spelling only for scaffold detection. Add a test that is skipped on case-sensitive filesystems. While there: every existence check lists the whole parent directory (23 to 37listdircalls per run, 3 to 4 s with 15,000 entries in the root), so list each directory at most once per attempt or check per name. -
P2: an unreadable decision file now aborts as
project-changed-during-inspection(regression from 3a259da).chmod 000onlake-manifest.json,lean-toolchainorlakefile.tomlof a stable supported project returns onlyproject-changed-during-inspection, statusindeterminate,lake: null. At8bbe9d8the same cases gavelake-manifest-unreadable(a warning, stillsupported),lean-toolchain-unreadableandlake-config-unreadable. Cause:_capture_filerecords a failed open or read as_SnapshotEntry("unreadable", None, None)(:378,:390,:406,:438), and the recheck (:339-344) compares that with a fresh stat that still saysfile, so every attempt counts as changed and the accurate diagnostics are discarded after_SNAPSHOT_ATTEMPTS. Fix: compare stat-derived identity with stat-derived identity (keep contentNoneand the unreadable diagnostic), and add chmod-000 tests for the manifest and the toolchain. While reworking the snapshot:- Make content the generation token: re-read each file and compare bytes or a digest. The metadata check misses a same-size rewrite within one timestamp tick on coarse-timestamp filesystems, and replacing the torn-read check with
changed = Falsefails no test. - Compare only type and inode for nodes whose content is never read (
.git,blueprint,mkdocs.yml, the workflow files): churn inside.gitmade 3 of about 2,000 stable inspections fail.
- Make content the generation token: re-read each file and compare bytes or a digest. The metadata check misses a same-size rewrite within one timestamp tick on coarse-timestamp filesystems, and replacing the torn-read check with
Non-blocking follow-ups (the cheap ones are better done before JSON v1 freezes)
project_rootis always"."(:104), so a report never says which directory it describes. Inspecting.lake/packages/mathlib/Mathlib/Algebrasilently reports the nested Mathlib package. Report the root relative to the target (for example../..).- Format, bidi and line-separator characters (Cf, Zl, Zp, such as U+202E and U+2028) from project files are printed raw in the human report, so report lines can be forged. Escape them in
_print_project_inspection(which also fixes theUnicodeEncodeErroron a non-UTF-8 stdout) or reject them in consumed fields. - The no-follow policy also applies above the project root:
inspect /tmp/projfails on macOS, as does a project under a symlinked~/codeor under an execute-only ancestor. Apply it from the discovered root down, or document it and name the offending path component. ~\xstill reachespwd.getpwnamon POSIX (earlier finding 4 is only partly fixed). Expand only a bare~or a~/prefix, fromHOME.- On Python 3.10 the locked
tomli2.4.1 accepts TOML 1.1 that Lake andtomllibon 3.11+ reject, so results depend on the Python version (the same gap exists on main). - Keys Lake accepts and ignores (
lean_lib.root,lean_exe.roots) and Lake'sgit = {url, subDir}table form are rejected asinvalid-lake-field, which discards the whole model.lean-toolchainforms that elan accepts (a trailing blank line, a barevX.Y.Z) are rejected or left unnormalized. - Guards with no test that fails when they are removed: the 2 MiB read cap, control-character rejection in Lake strings, never opening an ignored
lakefile.toml(32e76b7), each TOML depth guard on its own (test_toml_depth_limit_is_independent_of_python_recursion_limithas no effect), and several post-open race guards. skeleton.lean_librariesis a second Lake-config reader with toml-first precedence and unbounded TOML parsing (this predates the PR).autoform_cli.projectshould become the single authority.- Nits: the catalog validator still accepts duplicate release pairs, ids that contradict their contents and URLs no project could match (earlier finding 7 is only partly fixed); unreadable paths get
*-is-symlinkcodes; the README says symlinked decision-bearing files fail inspection, but a symlinked manifest only warns; small JSON shape inconsistencies; a few conditions that can never be true.
Validation: every blocking item was reproduced independently on 32e76b7. The Lake side of items 1 and 3 was confirmed with real Lake 4.32.2: a stale-manifest build against a local two-tag Mathlib repository, and lake env on the case-variant project. The tree merged with current main passes 281 tests, with only the known test_plugin_runtime load flake failing; ruff is clean.
|
Fourth-pass blockers are fixed at |
Compatibility now comes from lean-toolchain plus the Mathlib entry that lake-manifest.json locks (or package-overrides.json replaces), compared by commit against the catalog. Files are read with plain size-capped reads, so inspection works on Windows and through symlinked directories such as macOS /tmp. Drops the descriptor walk, generation snapshots, bounded TOML, Lean-name lexer, and material-identity tuple.
A differential run of the previous 190-test suite against the slim inspector, plus checks against real elan 4.2.0 and Lake 4.32.0, showed behaviours the cut lost: - lean-toolchain follows elan: the trimmed first line decides, and a blank or malformed first line means elan uses the default toolchain - a direct Mathlib lock that lakefile.toml no longer requires is unused, and lakefile.lean projects stay indeterminate - a catalog match also needs Mathlib loaded from its repository root with lakefile.lean and lake-manifest.json; commits compare in any case - legacy manifest versions are advisory, null packages mean none, NaN is rejected, overrides apply only over a manifest, path Mathlib is indeterminate - lakefile.toml needs a name, a StdVer version, named entries, and distinct targets, as Lake requires - credentials in a Mathlib URL are redacted from reports - the blueprint vault must be a directory; «mathlib» is mathlib
2a030b9 to
06b31ac
Compare
|
The slimmer current head is easier to review, but three headline blockers remain:
Please keep the slim public model, but strictly decode every Lake-consumed field |
When .github/workflows is listable but not searchable (mode 0644), `autoform project inspect` crashed with a PermissionError traceback. _exists_exactly lists each directory and so still finds the workflow name, but _inspect_autoform_paths then checked its kind with Path.is_file, which before Python 3.14 re-raises every OSError except ENOENT, ENOTDIR, EBADF and ELOOP. _AUTOFORM_PATHS now uses os.path.isfile and os.path.isdir, which read any path they cannot stat as absent, the same way _exists_exactly treats an unlistable directory. Ported from e01b2a8 on the #14 follow-up. Finding: ROB-3.
Main brings four pull requests since the last merge (42c4522): - PR #57 adds the complementary cryptography constraint, 49 or later everywhere but Intel Macs, relocks with the uv version CI pins, and tests that pyproject.toml alone declares the split. - PR #44 makes roadmap invocations goal-driven: the roadmap skill and its agent prompt, the thesis roadmap reference, the root README, the Codex default prompt, a CLI README note that the CLI is agent-facing, and the skill-example and plugin-surface tests. - PR #14 adds bounded project inspection: autoform_cli/project with its bundled release catalog, the `autoform project inspect` and `autoform project versions` subcommands, their README section, tests/test_project_inspect.py, and a wheel test that installs the CLI and runs both. - PR #45 settles the homepage progress semantics: the hero's `Scoped roadmap` figure counts formalizable leaf targets that are fully proved with every dependency, keeps 0% and 100% for exactly none and all, drops role="img" from the bar, and adds a declared source coverage line that links the coverage contract. The human-review skill says how to read the figure. Four files conflicted, one hunk each: autoform_cli/__main__.py: imports. The stack imports readback, review and render's publication_issues; #14 imports from .project. Both are kept, in module order. #14's parser, dispatch and _project, _print_project_inspection and _human_text merged without conflict beside the stack's skeleton, review and render commands. autoform_cli/render.py: imports. The stack imports from .approvals; #45 adds COVERAGE_DISPOSITIONS to the coverage import. Both are kept. #45's landing page, hero and stylesheet hunks merged without conflict beside the stack's link moving, page checks, MathJax configuration and publication guards. The coverage line is part of the dashboard render adds after the page checks, which judge the landing page as the author wrote it, and its href is the fixed coverage/index.html that _as_published gives for coverage/README.md, so it needs none of the encoding authored links get. skills/human-review/SKILL.md: both sides append after the coarse-to-fine paragraph. #45's paragraph explains the landing-page progress summary that paragraph names, so it stays in the opening section, and the stack's "Review formalized statements through prepared evidence and read-backs" section follows it. tests/test_render.py: imports. The stack imports site_converter and statement_and_notes from autoform_cli.markdown; #45 imports _COVERAGE_SUMMARY_ORDER, _completion_percentage and derive. Both are kept. autoform_cli/README.md, pyproject.toml, uv.lock and tests/test_skill_examples.py changed on both sides and merged without conflict. Against each parent, every conflicted file carries exactly the other side's added and removed lines.
Part of the reviewed split of #8. This PR is independent of #13.
What this adds
autoform project inspect [path] [--json]: a read-only report on the nearest Lean project: the package name and targets fromlakefile.toml, the Lean toolchain, the Mathlib thatlake-manifest.jsonlocks, Autoform scaffold paths, and whether that Lean/Mathlib pair is in the bundled release catalog.autoform project versions [--json]: lists the catalog (one pair today, Lean and Mathlib v4.32.2).Nothing runs Lake, Lean, Git, or the network.
How compatibility is decided
supported: the toolchain and the locked Mathlib commit match a catalog entry.unlisted: both are known, but the pair is not in the catalog.indeterminate: no manifest, no Mathlib, alakefile.lean(never evaluated), a path-based Mathlib, or any file error.The rules follow the tools, checked against elan 4.2.0 and Lake 4.32.0. elan reads only the trimmed first line of
lean-toolchainand ignores the file when that line is blank or malformed. Lake builds the manifest's lock (a stale lock gets a warning), applies.lake/package-overrides.jsonover the manifest, and refuses alakefile.tomlwith no name, a malformed version, or duplicate targets. Credentials in a Mathlib URL are redacted from reports.History
The first version of this PR (5.2k lines) hardened every read against concurrent modification with descriptor walks, generation rechecks, and no-follow paths. That made inspection fail on Windows and under symlinked paths such as macOS
/tmp, and four review rounds kept finding new edge cases.f7bfd97cuts it to plain size-capped reads.44fd1f2restores the behaviours the cut lost, found by running the previous 190-test suite against the new code. The remaining differences are deliberate: symlinks and case variants are treated as Lake treats them, and Lean's name grammar for manifest root names is not re-implemented.Code that later PRs used from the first version moves into them:
bounded_toml.pyto #16, configuration hashes to #32, and inspection bound to a staged directory to #26. Write-side case-collision hardening is tracked separately in #62.Validation
tests/test_project_inspect.py: 82 passed on Python 3.13, with each guard mutation-testedruff check autoform_cli servers testsReference implementation: #8