Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Format lane: checks that the Python tools are formatted (ruff.toml). Reports
# only, like ci.yml: never a required check and never blocks a push or merge
# (AGENTS.md §4). Lint findings are not checked. The Ruff version matches the
# command in ruff.toml.
name: format

on:
push:
branches: [main]
paths:
- "core/tools/**/*.py"
- "tools/**/*.py"
- "ruff.toml"
- ".github/workflows/format.yml"
pull_request:
paths:
- "core/tools/**/*.py"
- "tools/**/*.py"
- "ruff.toml"
- ".github/workflows/format.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: format-${{ github.ref }}
cancel-in-progress: true

jobs:
ruff:
name: ruff (Python tools)
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v7.0.1
- run: pipx run --spec ruff==0.17.2 ruff format --diff
10 changes: 7 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,10 @@ remain binding; moving them out of this file does not weaken them.
- **CI reports; it never gates (owner, 2026-10-23).** GitHub Actions runs a
fast lane after each push or pull request (`.github/workflows/ci.yml`: fmt,
guards, Clippy and the fast tests) and a nightly lane (`nightly.yml`: slow and
FUSE mount tests). [core/tools/ci_tests.py](core/tools/ci_tests.py) decides
which tests go in which lane. Neither is a required check, and nothing runs
before a push. Do not wait for CI, and do not treat a green run as
FUSE mount tests); `format.yml` checks the Ruff formatting below.
[core/tools/ci_tests.py](core/tools/ci_tests.py) decides which tests go in
which lane. None is a required check, and nothing runs before a push. Do
not wait for CI, and do not treat a green run as
verification of your change: verify the changed scope yourself with
[core checks](core/AGENTS.md#checks-and-completion) and report checks/gaps.
There is still no pre-push gate: `tools/preflight.sh` stays retired; do not
Expand Down Expand Up @@ -347,6 +348,9 @@ remain binding; moving them out of this file does not weaken them.
- New core/application production files obey the 999-line ceiling and 200-line
declaration/delegation ceiling for `lib.rs`/`mod.rs`. Product-only source,
external tests and guard coverage are detailed in the core guide.
- Format changed Python tools with Ruff; `ruff.toml` names the pinned version,
scope and command. `format.yml` reports formatting drift after a push;
nothing runs before one.
- Update affected source architecture/API documentation with implementation
changes. Keep proposal, research, implemented and measured claims distinct.

Expand Down
120 changes: 80 additions & 40 deletions core/tools/check_fuser_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
receive-buffer patches have separate pinned identities; all other fuser
overrides remain rejected.
"""

from __future__ import annotations

import argparse
Expand Down Expand Up @@ -37,9 +38,7 @@


def is_fuser(name, value):
return name.split(":", 1)[0] == "fuser" or (
isinstance(value, dict) and value.get("package") == "fuser"
)
return name.split(":", 1)[0] == "fuser" or (isinstance(value, dict) and value.get("package") == "fuser")


def manifest_errors(document, approved=APPROVED_REVISIONS, patch_path=None):
Expand All @@ -51,9 +50,13 @@ def manifest_errors(document, approved=APPROVED_REVISIONS, patch_path=None):
if isinstance(group, dict):
for name, value in group.items():
if is_fuser(name, value):
permitted = (section == "patch" and registry == "crates-io"
and name == "fuser" and patch_path is not None
and value == {"version": "=0.18.0", "path": patch_path})
permitted = (
section == "patch"
and registry == "crates-io"
and name == "fuser"
and patch_path is not None
and value == {"version": "=0.18.0", "path": patch_path}
)
if not permitted:
errors.append(f"{section} override of fuser is not the authorized timestamp patch")

Expand Down Expand Up @@ -86,7 +89,8 @@ def visit(table):

visit(document)
if patch_path is not None and document.get("patch", {}).get("crates-io", {}).get("fuser") != {
"version": "=0.18.0", "path": patch_path
"version": "=0.18.0",
"path": patch_path,
}:
errors.append("owning manifest is missing its exact fuser timestamp patch")
return errors
Expand All @@ -105,16 +109,16 @@ def lock_errors(document, approved=APPROVED_REVISIONS, patched=False):
errors.append("registry fuser requires its locked checksum")
elif source.startswith("git+"):
valid = any(
source in (
source
in (
f"git+{OFFICIAL_GIT}?rev={revision}#{revision}",
f"git+{OFFICIAL_GIT.removesuffix('.git')}?rev={revision}#{revision}",
)
for revision in approved
)
if not valid:
errors.append("locked fuser Git source/revision is not approved")
elif (patched and not source and package.get("version") == "0.18.0"
and "checksum" not in package):
elif patched and not source and package.get("version") == "0.18.0" and "checksum" not in package:
pass # Local patched bytes have a separate checked provenance record.
else:
errors.append("locked fuser has a local/replacement source")
Expand All @@ -134,9 +138,10 @@ def patched_package_errors(root: Path):
return ["patched fuser package directory is redirected"]
if metadata["archive_sha256"] != BASE_CHECKSUM:
return ["patched fuser has the wrong published archive identity"]
if metadata["patch_sha256"] != PATCH_CHECKSUM or hashlib.sha256(
(record / "signed-timestamps.patch").read_bytes()
).hexdigest() != PATCH_CHECKSUM:
if (
metadata["patch_sha256"] != PATCH_CHECKSUM
or hashlib.sha256((record / "signed-timestamps.patch").read_bytes()).hexdigest() != PATCH_CHECKSUM
):
return ["fuser timestamp diff changed"]
if metadata["modified_files"] != {"src/time.rs": TIME_CHECKSUM}:
return ["fuser patch changes files beyond the authorized timestamp correction"]
Expand All @@ -147,13 +152,16 @@ def patched_package_errors(root: Path):
if hashlib.sha256(lifecycle_raw).hexdigest() != LIFECYCLE_PROVENANCE_CHECKSUM:
return ["fuser lifecycle provenance changed"]
lifecycle = json.loads(lifecycle_raw)
if (lifecycle["timestamp_provenance_sha256"] != PROVENANCE_CHECKSUM
or lifecycle["archive_sha256"] != BASE_CHECKSUM
or set(lifecycle["files"]) != LIFECYCLE_FILES):
if (
lifecycle["timestamp_provenance_sha256"] != PROVENANCE_CHECKSUM
or lifecycle["archive_sha256"] != BASE_CHECKSUM
or set(lifecycle["files"]) != LIFECYCLE_FILES
):
return ["fuser lifecycle delta has an unauthorized base or file scope"]
if (lifecycle["patch_sha256"] != LIFECYCLE_PATCH_CHECKSUM
or hashlib.sha256((record / "session-lifecycle.patch").read_bytes()).hexdigest()
!= LIFECYCLE_PATCH_CHECKSUM):
if (
lifecycle["patch_sha256"] != LIFECYCLE_PATCH_CHECKSUM
or hashlib.sha256((record / "session-lifecycle.patch").read_bytes()).hexdigest() != LIFECYCLE_PATCH_CHECKSUM
):
return ["fuser lifecycle diff changed"]
expected |= lifecycle["files"]
if len(expected) != 86:
Expand All @@ -162,14 +170,17 @@ def patched_package_errors(root: Path):
if hashlib.sha256(buffer_raw).hexdigest() != BUFFER_PROVENANCE_CHECKSUM:
return ["fuser receive-buffer provenance changed"]
buffer = json.loads(buffer_raw)
if (buffer["timestamp_provenance_sha256"] != PROVENANCE_CHECKSUM
or buffer["lifecycle_provenance_sha256"] != LIFECYCLE_PROVENANCE_CHECKSUM
or buffer["archive_sha256"] != BASE_CHECKSUM
or set(buffer["files"]) != BUFFER_FILES):
if (
buffer["timestamp_provenance_sha256"] != PROVENANCE_CHECKSUM
or buffer["lifecycle_provenance_sha256"] != LIFECYCLE_PROVENANCE_CHECKSUM
or buffer["archive_sha256"] != BASE_CHECKSUM
or set(buffer["files"]) != BUFFER_FILES
):
return ["fuser receive-buffer delta has an unauthorized base or file scope"]
if (buffer["patch_sha256"] != BUFFER_PATCH_CHECKSUM
or hashlib.sha256((record / "receive-buffer.patch").read_bytes()).hexdigest()
!= BUFFER_PATCH_CHECKSUM):
if (
buffer["patch_sha256"] != BUFFER_PATCH_CHECKSUM
or hashlib.sha256((record / "receive-buffer.patch").read_bytes()).hexdigest() != BUFFER_PATCH_CHECKSUM
):
return ["fuser receive-buffer diff changed"]
expected |= buffer["files"]
if len(expected) != 86:
Expand All @@ -194,8 +205,7 @@ def config_errors(document):
errors = []
if document.get("paths"):
errors.append("Cargo path overrides can replace fuser and are forbidden")
if any(isinstance(source, dict) and "directory" in source
for source in document.get("source", {}).values()):
if any(isinstance(source, dict) and "directory" in source for source in document.get("source", {}).values()):
errors.append("Cargo directory source replacement can vendor fuser")
return errors

Expand All @@ -213,7 +223,7 @@ def package_errors(archive: Path, source: Path, checksum: str):
if not entry.isfile() or not entry.name.startswith(prefix):
errors.append(f"unexpected archive entry: {entry.name}")
continue
relative = Path(entry.name[len(prefix):])
relative = Path(entry.name[len(prefix) :])
if relative.is_absolute() or ".." in relative.parts:
errors.append("invalid archive path")
continue
Expand All @@ -234,11 +244,29 @@ def package_errors(archive: Path, source: Path, checksum: str):


def repository_errors(root: Path):
paths = subprocess.check_output(
["git", "-C", str(root), "ls-files", "-z", "--cached", "--others",
"--exclude-standard", "--", "Cargo.toml", "Cargo.lock", "**/Cargo.toml",
"**/Cargo.lock", ".cargo/config.toml", ".cargo/config"]
).decode().split("\0")
paths = (
subprocess.check_output(
[
"git",
"-C",
str(root),
"ls-files",
"-z",
"--cached",
"--others",
"--exclude-standard",
"--",
"Cargo.toml",
"Cargo.lock",
"**/Cargo.toml",
"**/Cargo.lock",
".cargo/config.toml",
".cargo/config",
]
)
.decode()
.split("\0")
)
errors = []
checked = 0
for name in sorted(set(paths) - {""}):
Expand All @@ -248,9 +276,13 @@ def repository_errors(root: Path):
checked += 1
try:
document = tomllib.loads(path.read_text())
findings = (manifest_errors(document, patch_path=PATCH_MANIFESTS.get(name)) if path.name == "Cargo.toml"
else lock_errors(document, patched=name in PATCH_LOCKS) if path.name == "Cargo.lock"
else config_errors(document))
findings = (
manifest_errors(document, patch_path=PATCH_MANIFESTS.get(name))
if path.name == "Cargo.toml"
else lock_errors(document, patched=name in PATCH_LOCKS)
if path.name == "Cargo.lock"
else config_errors(document)
)
errors.extend(f"{name}: {error}" for error in findings)
except (OSError, ValueError) as error:
errors.append(f"{name}: cannot verify: {error}")
Expand All @@ -270,9 +302,17 @@ def main():
checked, errors = repository_errors(args.root)
if all(supplied):
errors.extend(package_errors(*supplied))
print(json.dumps({"checked_manifests_locks_configs": checked, "errors": errors,
"authorized_patch_verified": not errors,
"registry_package_verified": bool(all(supplied)) and not errors}, indent=2))
print(
json.dumps(
{
"checked_manifests_locks_configs": checked,
"errors": errors,
"authorized_patch_verified": not errors,
"registry_package_verified": bool(all(supplied)) and not errors,
},
indent=2,
)
)
return 1 if errors else 0


Expand Down
Loading