Build a Read-Only Floor Auditor
Build a Python CLI to audit Claude Code settings and report provenance safely.
Introduction
30 Second Summary
On a Windows machine, the same instruction can appear in several places. A quick spot check can miss which copy actually controls the result.
In this project, you will build a portable Python command-line auditor for the Claude Code settings floor. It will turn the observable floor into deterministic evidence with secret values redacted.
What You'll Build
You'll run the auditor against your live Windows settings floor to reveal the source behind each effective value without changing any configuration.
By the end of this project, you'll have:
- A read-only source inventory that shows each observable settings location as LOADED, ABSENT, MALFORMED, or SKIPPED.
- An effective configuration report that traces every resolved key to the file or managed group that supplied it.
- A safe evidence package with a detailed local report plus a filtered shareable report that excludes matched secret values.
- Secret Mission: A twelfth test that proves duplicate default_mode_auto_inert findings stay independent across project_settings and local_settings.
Are there any prerequisites?
You do not need to install anything before starting. The first step verifies Python 3.12.10 plus pytest 9.0.3 in Git Bash before you trust the test results.
Before We Start
Before any hands-on work, this checkpoint locks in the auditor's narrow authority. It may read observable Claude Code floor sources and create new report files only inside its own project directory. Claude Code settings, registry policy, hooks, MCP files, sealed evidence, sandbox configuration, PowerShell, .ps1 files, Claude Desktop slash commands, and blind writes stay outside its authority.
Verify the Python Test Environment
A trustworthy auditor needs a test result that another run can reproduce. That starts with proving the selected Python interpreter before any code is trusted.
You’ll create the workspace in Visual Studio Code. You’ll verify Python 3.12.10 plus pytest 9.0.3 from a separate Git Bash terminal.
In this step, get ready to:
- Create the floor-auditor workspace structure.
- Verify the exact Python test environment.
- Create the package shell with its development pin.
Create the auditor workspace
The workspace keeps every auditor file inside one named project directory. This boundary also gives later report writes a safe destination.
- Select File from the Visual Studio Code menu.
- Select Open Folder....
- Choose Desktop in the Windows folder dialog.
The folder dialog now points to your Desktop. The project stays easy to find throughout the build.
- Select New Folder.
- Enter floor-auditor as the folder name.
- Select Select Folder.
You’ll see floor-auditor as the root folder in the Explorer sidebar.
- Select the floor-auditor root in the Explorer sidebar.
- Select New Folder from the Explorer controls.
- Enter floor_auditor as the folder name.
You’ll see the floor_auditor directory beneath the workspace root.
- Select the floor-auditor root again.
- Select New Folder from the Explorer controls.
- Enter tests as the folder name.
The Explorer now shows both child directories. Your package code has a separate home from its test suite.
- Right-click the floor-auditor root in the Explorer sidebar.
- Select Reveal in File Explorer.
- Right-click an empty area inside the opened floor-auditor folder.
- Select the Git Bash option that opens a terminal in this folder.
You’ll see a separate Git Bash terminal whose prompt ends in floor-auditor. This terminal stays at the project root for every command in this step.
Workspace structure looks different?
Check that floor_auditor uses an underscore. Check that floor-auditor uses a hyphen.
Make sure floor_auditor plus tests appear directly beneath the workspace root.
Help me check my workspace structure.
Verify Python and pytest
Module invocation keeps the test runner tied to the selected Python interpreter. That connection prevents a different installation from quietly running the suite.
- Check both version pins from the project root by running these commands:
python --version
python -m pytest --version
What do these checks prove?
- The first line identifies the Python interpreter selected by the python command.
- The second line loads pytest through that same interpreter.
✔️ Both versions match
You’ll see Python 3.12.10 plus pytest 9.0.3. That confirms your test command uses the required environment.
ⓧ pytest is missing or different
The project pins one pytest release so every learner runs the same test behavior. Install the required release through the selected Python interpreter.
- Install the pinned pytest release by running:
python -m pip install pytest==9.0.3
What does this command do?
The selected Python interpreter runs its package installer. The exact pytest==9.0.3 pin prevents an unplanned release from entering the test environment.
- Repeat both version checks by running:
python --version
python -m pytest --version
Why repeat the checks?
The repeated output proves the installation landed under the interpreter selected by python. It also confirms the exact pytest pin is active.
You’ll now see Python 3.12.10 plus pytest 9.0.3.
pytest still does not match?
Check that you ran the installation from the same Git Bash terminal as the version checks. A different terminal can select another Python interpreter.
Help me diagnose the Python and pytest mismatch.
Create the package shell and pin
The development requirements file records the test dependency expected by this project. Anyone can inspect the file to see the exact pytest release.
- Return to Visual Studio Code.
- Select the floor-auditor root in the Explorer sidebar.
- Select New File... from the Explorer controls.
- Enter requirements-dev.txt as the file name.
You’ll see requirements-dev.txt open in the editor. It also appears directly beneath the workspace root.
- Set the contents of requirements-dev.txt by entering the following line:
pytest==9.0.3
What does this pin do?
The file declares pytest 9.0.3 as the project’s development dependency. The exact pin keeps later test runs reproducible.
- Save requirements-dev.txt.
- Confirm the Explorer lists requirements-dev.txt directly beneath floor-auditor.
Can't find the requirements file?
Check that the file name ends in .txt. Make sure it sits beside the floor_auditor directory.
Help me place the requirements file correctly.
A Python directory becomes an importable package shell when it contains __init__.py. The package shell gives later auditor modules a stable namespace.
- Select the floor_auditor directory in the Explorer sidebar.
- Select New File... from the Explorer controls.
- Enter __init__.py as the file name.
You’ll see __init__.py nested inside floor_auditor.
- Set the contents of floor_auditor/__init__.py by entering this package description:
"""Portable read-only Claude Code floor auditor."""
What does this package file do?
The file marks floor_auditor as the package that holds the auditor. Its module docstring records the package’s read-only purpose.
- Save floor_auditor/__init__.py.
- Confirm the Explorer shows __init__.py inside floor_auditor.
Package file in the wrong place?
Check that __init__.py is nested inside floor_auditor. A copy beside that directory does not create the required package shell.
Help me correct the package file location.
✔️ Awesome, I've got everything
Great. Double-check that both files are saved in Visual Studio Code.
ⓧ I'd like to double check the full code
Compare your development requirements file with this complete version.
pytest==9.0.3
The file contains one exact pytest pin.
Compare your package shell with this complete version.
"""Portable read-only Claude Code floor auditor."""
The package shell contains one module docstring.
Before you run the final check, do you expect both versions to match the project pins now?
- Return to the separate Git Bash terminal.
- Confirm the environment from the project root by running these commands:
python --version
python -m pytest --version
What does the final check prove?
The first line confirms the interpreter selected for the project. The second line proves the same interpreter can load the pinned test runner.
You’ll see Python 3.12.10 plus pytest 9.0.3. That’s the test foundation confirmed before the auditor reads a single floor source.
Final versions do not match?
Confirm the Git Bash prompt still ends in floor-auditor. Then compare the final output with the earlier successful version check.
Help me fix the final environment check.
Your reproducible package shell is ready. Next, you’ll inventory the observable Claude Code floor without changing it.
Discover the Observable Floor
Your Python package shell is ready. The next question is what the auditor can prove about the live machine without changing it.
This step builds read-only discovery for the observable Claude Code floor. It separates measured absence from unreadable evidence before applying the managed-source first-wins decision.
In this step, get ready to:
- Discover ordinary settings files plus managed settings sources.
- Model source status plus managed-source selection without writing to the floor.
- Run an inventory that proves the observed Windows state.
Create the discovery engine
The discovery engine turns every observable source into one consistent record. Each record carries a status of LOADED, ABSENT, MALFORMED, or SKIPPED.
Why the evidence labels matter
A missing source is an observed result. The auditor labels that result MEASURED.
An existing source can fail to open. The auditor records that source as SKIPPED with the evidence label UNVERIFIED.
- Right-click the floor_auditor folder in the Visual Studio Code file tree.
- Create a new file named discovery.py inside the floor_auditor folder.
You should see discovery.py beside __init__.py in the package folder.
- Select the full-code tab below.
- Copy the complete discovery.py reference into your new file.
- Save floor_auditor/discovery.py.
✔️ Awesome, I've got everything!
Your discovery engine is saved. It now models file sources plus read-only Windows registry sources.
ⓧ I'd like to double check the full code
Compare your floor_auditor/discovery.py file against this complete reference.
from __future__ import annotations
import json
import os
import sys
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any, Iterable
REGISTRY_SUBKEY = r"SOFTWARE\Policies\ClaudeCode"
REGISTRY_VALUE = "Settings"
CONTROL_KEYS = {"managedSourcesBehavior", "wslInheritsWindowsSettings"}
@dataclass(frozen=True)
class SourceRecord:
source_id: str
scope: str
file_role: str
location: str
precedence: int
group: str
order: int
status: str
evidence_label: str
data: dict[str, Any] | None = None
error: str | None = None
def inventory(self) -> dict[str, Any]:
row = asdict(self)
row.pop("data")
return row
def platform_family() -> str:
if sys.platform == "win32":
return "native_windows"
if sys.platform == "darwin":
return "macos"
try:
marker = Path("/proc/version").read_text(encoding="utf-8").casefold()
except OSError:
marker = ""
return "wsl2" if "microsoft" in marker else "linux"
def default_managed_root(family: str) -> Path:
if family == "native_windows":
return Path("C:/Program Files/ClaudeCode")
if family == "macos":
return Path("/Library/Application Support/ClaudeCode")
return Path("/etc/claude-code")
def resolve_config_root() -> Path:
configured = os.environ.get("CLAUDE_CONFIG_DIR")
return Path(configured).expanduser() if configured else Path.home() / ".claude"
def _parse_object(text: str) -> dict[str, Any]:
value = json.loads(text)
if not isinstance(value, dict):
raise ValueError("top-level JSON value must be an object")
return value
def _file_record(
source_id: str,
scope: str,
file_role: str,
path: Path,
precedence: int,
group: str,
order: int = 0,
) -> SourceRecord:
location = str(path.resolve(strict=False))
try:
text = path.read_text(encoding="utf-8")
except FileNotFoundError:
return SourceRecord(
source_id, scope, file_role, location, precedence, group, order,
"ABSENT", "MEASURED"
)
except OSError as exc:
return SourceRecord(
source_id, scope, file_role, location, precedence, group, order,
"SKIPPED", "UNVERIFIED", error=f"{type(exc).__name__}: {exc}"
)
try:
data = _parse_object(text)
except (json.JSONDecodeError, UnicodeDecodeError, ValueError) as exc:
return SourceRecord(
source_id, scope, file_role, location, precedence, group, order,
"MALFORMED", "MEASURED", error=f"{type(exc).__name__}: {exc}"
)
return SourceRecord(
source_id, scope, file_role, location, precedence, group, order,
"LOADED", "MEASURED", data=data
)
def _registry_record(hive_name: str, precedence: int) -> SourceRecord:
source_id = hive_name.casefold()
location = f"registry://{hive_name}/{REGISTRY_SUBKEY}/{REGISTRY_VALUE}"
scope = "managed" if hive_name == "HKLM" else "managed_user"
role = "hklm_policy" if hive_name == "HKLM" else "hkcu_policy"
group = "hklm" if hive_name == "HKLM" else "hkcu"
try:
import winreg
hive = {
"HKLM": winreg.HKEY_LOCAL_MACHINE,
"HKCU": winreg.HKEY_CURRENT_USER,
}[hive_name]
access = winreg.KEY_READ | winreg.KEY_WOW64_64KEY
with winreg.OpenKey(hive, REGISTRY_SUBKEY, 0, access) as key:
value, value_type = winreg.QueryValueEx(key, REGISTRY_VALUE)
if value_type == winreg.REG_EXPAND_SZ:
value = winreg.ExpandEnvironmentStrings(value)
if value_type not in {winreg.REG_SZ, winreg.REG_EXPAND_SZ}:
raise ValueError("Settings must be REG_SZ or REG_EXPAND_SZ")
data = _parse_object(value)
return SourceRecord(
source_id, scope, role, location, precedence, group, 0,
"LOADED", "MEASURED", data=data
)
except FileNotFoundError:
return SourceRecord(
source_id, scope, role, location, precedence, group, 0,
"ABSENT", "MEASURED"
)
except (json.JSONDecodeError, UnicodeDecodeError, ValueError) as exc:
return SourceRecord(
source_id, scope, role, location, precedence, group, 0,
"MALFORMED", "MEASURED", error=f"{type(exc).__name__}: {exc}"
)
except OSError as exc:
return SourceRecord(
source_id, scope, role, location, precedence, group, 0,
"SKIPPED", "UNVERIFIED", error=f"{type(exc).__name__}: {exc}"
)
def _has_policy_key(source: SourceRecord) -> bool:
return bool(source.data is not None and any(key not in CONTROL_KEYS for key in source.data))
def _group_has_policy(records: Iterable[SourceRecord]) -> bool:
return any(_has_policy_key(record) for record in records)
def _group_has_control_or_policy(records: Iterable[SourceRecord]) -> bool:
return any(
record.data is not None
and ("managedSourcesBehavior" in record.data or _has_policy_key(record))
for record in records
)
def _group_behavior(records: list[SourceRecord]) -> str:
for record in reversed(sorted(records, key=lambda item: item.order)):
if record.data is not None:
value = record.data.get("managedSourcesBehavior")
if value in {"first-wins", "merge"}:
return value
return "first-wins"
def select_observed_sources(
sources: Iterable[SourceRecord],
) -> tuple[list[SourceRecord], dict[str, Any]]:
source_list = list(sources)
loaded = [
source for source in source_list
if source.status == "LOADED" and source.data is not None
]
ordinary = [source for source in loaded if source.group == "ordinary"]
admin_groups = [
("remote", [source for source in loaded if source.group == "remote"]),
("hklm", [source for source in loaded if source.group == "hklm"]),
(
"managed_file",
sorted(
[source for source in loaded if source.group == "managed_file"],
key=lambda item: item.order,
),
),
]
hkcu = [source for source in loaded if source.group == "hkcu"]
control_group = next(
((name, records) for name, records in admin_groups if _group_has_control_or_policy(records)),
None,
)
behavior = _group_behavior(control_group[1]) if control_group else "first-wins"
selected_groups: list[tuple[str, list[SourceRecord]]] = []
if behavior == "merge":
selected_groups = [
(name, records) for name, records in admin_groups if _group_has_policy(records)
]
if not selected_groups and _group_has_policy(hkcu):
selected_groups = [("hkcu", hkcu)]
else:
for name, records in [*admin_groups, ("hkcu", hkcu)]:
if _group_has_policy(records):
selected_groups = [(name, records)]
break
selected_names = [name for name, _ in selected_groups]
policy_group_names = [
name for name, records in [*admin_groups, ("hkcu", hkcu)]
if _group_has_policy(records)
]
ignored_names = [name for name in policy_group_names if name not in selected_names]
managed = [record for _, records in selected_groups for record in records]
selected = sorted(ordinary + managed, key=lambda item: (item.precedence, item.order))
remote_record = next(
(source for source in source_list if source.file_role == "remote_cache"),
None,
)
return selected, {
"managed_sources_behavior_observed": behavior,
"behavior_source_group": control_group[0] if control_group else None,
"selected_managed_source_groups": selected_names,
"ignored_managed_source_groups": ignored_names,
"remote_cache_status": remote_record.status if remote_record else None,
"remote_cache_evidence_label": remote_record.evidence_label if remote_record else None,
"remote_cache_trust": "USER_WRITABLE_LOCAL_CACHE",
"completeness": "OBSERVABLE_LOCAL_STATE",
}
def discover_sources(
project_root: Path,
managed_root: Path | None = None,
config_root: Path | None = None,
family: str | None = None,
include_registry: bool = True,
) -> list[SourceRecord]:
project_root = project_root.resolve(strict=False)
family = family or platform_family()
managed_root = (managed_root or default_managed_root(family)).resolve(strict=False)
config_root = (config_root or resolve_config_root()).resolve(strict=False)
records = [
_file_record(
"user", "user", "user_settings", config_root / "settings.json", 10, "ordinary"
),
_file_record(
"project", "project", "project_settings",
project_root / ".claude" / "settings.json", 20, "ordinary"
),
_file_record(
"local", "local", "local_settings",
project_root / ".claude" / "settings.local.json", 30, "ordinary"
),
_file_record(
"remote", "managed", "remote_cache",
config_root / "remote-settings.json", 100, "remote"
),
]
if family == "native_windows" and include_registry:
records.append(_registry_record("HKLM", 90))
records.append(
_file_record(
"managed-file", "managed", "managed_settings",
managed_root / "managed-settings.json", 80, "managed_file"
)
)
drop_in_root = managed_root / "managed-settings.d"
try:
drop_ins = sorted(
path for path in drop_in_root.glob("*.json") if not path.name.startswith(".")
)
except OSError:
drop_ins = []
for index, path in enumerate(drop_ins, start=1):
records.append(
_file_record(
f"managed-drop-in-{index}", "managed", "managed_drop_in",
path, 80, "managed_file", index
)
)
if family == "native_windows" and include_registry:
records.append(_registry_record("HKCU", 70))
return records
What does this file do?
- The SourceRecord data class gives every source the same inventory shape.
- The file readers parse strict JSON objects. They preserve malformed input as measured evidence.
- The registry reader uses read-only access for HKLM plus HKCU policy.
- The selector ranks remote settings above endpoint policy. It reports lower policy-bearing groups as ignored under first-wins.
The saved file now contains the source inventory plus the managed-source decision. It also resolves CLAUDE_CONFIG_DIR before looking for user settings or the remote cache.
Add the deferred command-line entry point
The package needs a command-line entry point before it can print the inventory. The audit import stays below the --inventory-only branch because audit.py does not exist yet.
- Right-click the floor_auditor folder in the Visual Studio Code file tree.
- Create a new file named __main__.py inside the floor_auditor folder.
You should see __main__.py listed beside discovery.py.
- Select the full-code tab below.
- Copy the complete __main__.py reference into your new file.
- Save floor_auditor/__main__.py.
✔️ Awesome, I've got everything!
Your package entry point is saved. Inventory mode can now finish before Python reaches the deferred audit import.
ⓧ I'd like to double check the full code
Compare your floor_auditor/__main__.py file against this complete reference.
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from .discovery import (
default_managed_root,
discover_sources,
platform_family,
select_observed_sources,
)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="python -m floor_auditor",
description="Read-only Claude Code floor auditor",
allow_abbrev=False,
)
parser.add_argument("--project-root", type=Path, default=Path("."))
parser.add_argument("--managed-root", type=Path)
parser.add_argument("--seal-index", type=Path)
parser.add_argument("--verification-log", type=Path)
parser.add_argument("--staleness-hours", type=int, default=24)
parser.add_argument("--output-dir", type=Path)
parser.add_argument("--inventory-only", action="store_true")
parser.add_argument("--write-reports", action="store_true")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
family = platform_family()
project_root = args.project_root.resolve(strict=False)
managed_root = (args.managed_root or default_managed_root(family)).resolve(strict=False)
if args.inventory_only:
sources = discover_sources(project_root, managed_root=managed_root, family=family)
_, selection = select_observed_sources(sources)
print(
json.dumps(
{
"sources": [source.inventory() for source in sources],
"managed_source_selection": selection,
},
indent=2,
sort_keys=True,
)
)
return 0
from .audit import RuntimeAuditError, SpecError, plan_reports, run_audit, write_reports
seal_index = args.seal_index or project_root / ".floor" / "seal-index.json"
verification_log = (
args.verification_log or project_root / ".floor" / "verification-trigger.jsonl"
)
output_dir = args.output_dir or project_root / "artifacts" / "run-1"
try:
local, shareable, exit_code = run_audit(
project_root=project_root,
managed_root=managed_root,
seal_index_path=seal_index,
verification_path=verification_log,
staleness_hours=args.staleness_hours,
family=family,
)
plans = plan_reports(local, shareable, output_dir)
print(
json.dumps(
{
"overall_state": local["overall_state"],
"exit_class": exit_code,
"staleness_limit_hours": local["verification_trigger"][
"staleness_limit_hours"
],
"source_coverage": local["source_coverage"],
"bash_rule_inventory": local["bash_rule_inventory"],
"report_plan": plans,
},
indent=2,
sort_keys=True,
)
)
if args.write_reports:
if any(plan["action"] != "CREATE" for plan in plans):
raise RuntimeAuditError("Dry-run plan refused at least one destination.")
written = write_reports(local, shareable, output_dir, project_root)
print(json.dumps({"written": [str(path) for path in written]}, indent=2))
return exit_code
except SpecError as exc:
print(f"MALFORMED_SPEC: {exc}", file=sys.stderr)
return 20
except RuntimeAuditError as exc:
print(f"RUNTIME_FAILURE: {exc}", file=sys.stderr)
return 30
if __name__ == "__main__":
raise SystemExit(main())
What does this file do?
- The argument parser defines inventory controls plus the report controls used in later steps.
- The inventory branch discovers the floor. It prints a sorted JSON inventory.
- The deferred import prevents the missing floor_auditor.audit module from blocking inventory mode.
- The default path remains read-only. Report creation requires an explicit flag in a later step.
The command path is now connected. Inventory mode can run with only the package files created in this step.
Run the read-only inventory
This check reads locally observable settings plus native Windows policy. It prints the evidence to the terminal without creating reports or modifying the floor.
Before you run the inventory, which source do you expect to appear as measured absence?
- Switch back to the separate Git Bash terminal from the previous step.
- Run the inventory from the floor-auditor project root with this command:
python -m floor_auditor --project-root . --managed-root "C:/Program Files/ClaudeCode" --inventory-only
What does this command check?
- The --project-root . argument points discovery at the current project.
- The --managed-root "C:/Program Files/ClaudeCode" argument identifies the native Windows managed settings directory.
- The --inventory-only flag exits before the deferred audit import.
You should see remote_cache with status ABSENT plus evidence label MEASURED.
You should also see local_settings with status LOADED. The managed-source result should report managed_sources_behavior_observed as first-wins.
Inventory command failed?
Check that the terminal is still at the floor-auditor project root. The package directory must be directly below the current location.
Compare discovery.py plus __main__.py against the full-code tabs. A missing parenthesis can stop the package before discovery begins.
Help me debug the inventory command.
- Inspect the floor-auditor file tree in Visual Studio Code.
- Confirm that no artifacts directory exists.
The inventory output proves discovery is active without creating a report. Your Claude Code settings plus registry policy remain unchanged.
Your auditor can now map the observable floor without changing it. Next, you will make the missing resolver fail visibly so incomplete logic cannot look trustworthy.
Make the Missing Resolver Fail Visibly
The discovery layer can inventory the observable Claude Code floor. It cannot yet prove which values are effective or whether Windows command text survives matching unchanged.
A deterministic pytest harness turns those missing guarantees into executable requirements. This designed obstacle makes an absent resolver visible before any result can look trustworthy.
In this step, get ready to:
- Create the complete eleven-test audit harness.
- Inspect the literal Windows Bash-rule fixture.
- Run the harness to expose the missing resolver.
Create the deterministic test harness
The harness defines the behavior the auditor must eventually satisfy. Its assertions cover discovery, provenance, literal matching, light switches, redaction, report filtering, and exit classes.
- Switch back to the Visual Studio Code workspace from earlier.
- Use the file sidebar to add test_audit.py inside the existing tests folder.
- Populate tests/test_audit.py with the complete reference in the second tab below.
✔️ I created the test file
Your tests/test_audit.py file now holds the complete eleven-test contract.
ⓧ I'd like to double check the full code
- Compare your entire tests/test_audit.py file with this reference.
import hashlib
import json
from pathlib import Path
from floor_auditor.audit import (
GREEN,
OVERDUE,
RED,
bash_rule_matches,
build_shareable,
command_executable_name,
evaluate_floor_integrity,
evaluate_verification_trigger,
plan_reports,
resolve_effective_config,
run_audit,
scan_secret_findings,
scan_source_findings,
summarize_bash_rules,
)
from floor_auditor.discovery import SourceRecord, discover_sources, select_observed_sources
def write_json(path: Path, value) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value), encoding="utf-8")
def source(
role: str,
location: Path,
data: dict,
scope: str = "project",
precedence: int = 20,
group: str = "ordinary",
order: int = 0,
) -> SourceRecord:
return SourceRecord(
source_id=role,
scope=scope,
file_role=role,
location=str(location),
precedence=precedence,
group=group,
order=order,
status="LOADED",
evidence_label="MEASURED",
data=data,
)
def test_discovery_statuses_config_root_and_effective_provenance(tmp_path, monkeypatch):
config_root = tmp_path / "user"
project_root = tmp_path / "project"
managed_root = tmp_path / "managed"
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(config_root))
write_json(config_root / "settings.json", {"permissions": {"allow": ["Read"]}, "model": "user"})
write_json(project_root / ".claude/settings.json", {"permissions": {"ask": ["Bash(git push *)"]}})
write_json(project_root / ".claude/settings.local.json", {"model": "local"})
write_json(managed_root / "managed-settings.json", {"permissions": {"deny": ["Read(./.env)"]}, "model": "managed"})
records = discover_sources(
project_root,
managed_root=managed_root,
family="linux",
include_registry=False,
)
effective, coverage = resolve_effective_config(records)
by_role = {record.file_role: record for record in records}
assert by_role["remote_cache"].status == "ABSENT"
assert by_role["remote_cache"].evidence_label == "MEASURED"
assert by_role["local_settings"].status == "LOADED"
assert by_role["remote_cache"].location == str((config_root / "remote-settings.json").resolve())
assert coverage["managed_sources_behavior_observed"] == "first-wins"
assert coverage["selected_managed_source_groups"] == ["managed_file"]
assert effective["model"]["value"] == "managed"
assert effective["permissions.allow"]["value"] == ["Read"]
assert effective["permissions.ask"]["value"] == ["Bash(git push *)"]
assert effective["permissions.deny"]["value"] == ["Read(./.env)"]
def test_remote_cache_wins_and_lower_managed_source_is_ignored(tmp_path):
remote = source(
"remote_cache", tmp_path / "remote-settings.json", {"model": "remote"},
"managed", 100, "remote"
)
endpoint = source(
"managed_settings", tmp_path / "managed-settings.json", {"model": "endpoint"},
"managed", 80, "managed_file"
)
selected, coverage = select_observed_sources([endpoint, remote])
effective, _ = resolve_effective_config([endpoint, remote])
assert [record.file_role for record in selected] == ["remote_cache"]
assert coverage["managed_sources_behavior_observed"] == "first-wins"
assert coverage["selected_managed_source_groups"] == ["remote"]
assert coverage["ignored_managed_source_groups"] == ["managed_file"]
assert effective["model"]["value"] == "remote"
def test_managed_permission_lock_excludes_lower_rules(tmp_path):
user = source(
"user_settings", tmp_path / "settings.json",
{"permissions": {"allow": ["Read"]}}, "user", 10, "ordinary"
)
managed = source(
"managed_settings", tmp_path / "managed-settings.json",
{"allowManagedPermissionRulesOnly": True, "permissions": {"deny": ["Read(./.env)"]}},
"managed", 80, "managed_file"
)
effective, _ = resolve_effective_config([user, managed])
assert "permissions.allow" not in effective
assert effective["permissions.deny"]["value"] == ["Read(./.env)"]
def test_windows_bash_rule_matching_is_literal_and_reporting_preserves_path(tmp_path):
rule = r"Bash(C:\Users\dante\run.sh *)"
command = r"C:\Users\dante\run.sh --flag"
corrupted = r"C:Usersdanterun.sh --flag"
assert bash_rule_matches(rule, command)
assert not bash_rule_matches(rule, corrupted)
assert command_executable_name(command) == "run.sh"
assert command_executable_name(r'"C:\Program Files\Acme\run.exe" --flag') == "run.exe"
record = source(
"local_settings",
tmp_path / "settings.local.json",
{"permissions": {"allow": [rule]}},
"local",
)
rows = summarize_bash_rules([record])
assert rows[0]["rule"] == rule
assert rows[0]["executable"] == "run.sh"
assert rows[0]["matching_basis"] == "LITERAL_COMMAND_STRING"
def test_native_windows_sandbox_is_red(tmp_path):
record = source(
"managed_settings", tmp_path / "managed-settings.json",
{"sandbox": {"enabled": True}}, "managed", 80, "managed_file"
)
findings = scan_source_findings([record], "native_windows")
assert any(item["check_name"] == "native_windows_sandbox_present" for item in findings)
assert any(item["severity"] == RED for item in findings)
def test_missing_evidence_is_overdue_with_none_counts(tmp_path):
floor = evaluate_floor_integrity(tmp_path / "missing-index.json", [])
trigger = evaluate_verification_trigger(tmp_path / "missing.jsonl", now_epoch=1000)
assert floor["state"] == OVERDUE
assert trigger["state"] == OVERDUE
assert all(floor[key] is None for key in ("sealed", "live", "changed", "missing", "unlisted"))
assert all(
trigger[key] is None
for key in (
"clean_ticks_ever", "ticks_started", "failures_since_clean",
"last_clean_epoch", "malformed", "records"
)
)
def test_verification_malformed_is_distinct(tmp_path):
evidence = tmp_path / "ticks.jsonl"
evidence.write_text(
'{"tick_id":"1","started_epoch":10,"finished_epoch":20,"result":"CLEAN"}\nnot-json\n',
encoding="utf-8",
)
result = evaluate_verification_trigger(evidence, now_epoch=30)
assert result["state"] == RED
assert result["records"] == 1
assert result["malformed"] == 1
def test_floor_integrity_detects_changed_file(tmp_path):
root = tmp_path / "root"
root.mkdir()
floor_file = root / "managed-settings.json"
floor_file.write_text("before", encoding="utf-8")
digest = hashlib.sha256(b"before").hexdigest()
manifest = tmp_path / "manifest.json"
write_json(manifest, [{"path": "managed-settings.json", "sha256": digest, "size_bytes": 6}])
index = tmp_path / "seal-index.json"
write_json(index, {"roots": [{"name": "managed", "path": str(root), "manifest": str(manifest)}]})
floor_file.write_text("after", encoding="utf-8")
result = evaluate_floor_integrity(index, [floor_file])
assert result["state"] == RED
assert result["changed"] == 1
assert result["missing"] == 0
def test_shareable_filters_unremediated_and_exact_fields():
local = [
{
"id": "a", "check_name": "open_hole", "scope": "managed",
"file_role": "managed_settings", "severity": RED,
"evidence_label": "MEASURED", "detail": "local only", "remediated": False,
},
{
"id": "b", "check_name": "closed_hole", "scope": "managed",
"file_role": "managed_settings", "severity": GREEN,
"evidence_label": "MEASURED", "detail": "safe to share", "remediated": True,
},
]
shareable = build_shareable(local)
assert [row["id"] for row in shareable["findings"]] == ["b"]
assert set(shareable["findings"][0]) == {
"id", "check_name", "scope", "file_role", "severity", "evidence_label"
}
def test_secret_scan_never_reports_the_value(tmp_path):
path = tmp_path / "settings.json"
secret_value = "do-not-print-this-secret"
path.write_text(json.dumps({"api_key": secret_value}), encoding="utf-8")
findings = scan_secret_findings(
[source("user_settings", path, {"api_key": secret_value}, "user")]
)
assert findings[0]["check_name"] == "secret_pattern_match"
assert secret_value not in json.dumps(findings)
assert "pattern=credential_assignment" in findings[0]["detail"]
def test_run_audit_uses_distinct_exit_classes_and_dry_run(tmp_path):
project = tmp_path / "project"
managed = tmp_path / "managed"
config = tmp_path / "user"
write_json(managed / "managed-settings.json", {})
local, shareable, exit_code = run_audit(
project,
managed,
project / ".floor/seal-index.json",
project / ".floor/verification-trigger.jsonl",
now_epoch=1000,
family="linux",
config_root=config,
include_registry=False,
)
assert exit_code == 10
assert local["overall_state"] == OVERDUE
assert local["sources"][3]["file_role"] == "remote_cache"
assert local["sources"][3]["status"] == "ABSENT"
plans = plan_reports(local, shareable, project / "artifacts/run-1")
assert {plan["action"] for plan in plans} == {"CREATE"}
assert not (project / "artifacts").exists()
What Does This Harness Cover?
- The discovery tests preserve measured source status plus per-file provenance.
- The matching test protects literal Windows backslashes from tokenization.
- The evidence tests keep missing counts at None while preserving distinct health states.
- The report tests keep secret values out of findings plus unremediated posture out of the shareable artifact.
- Save tests/test_audit.py.
- Confirm the file sidebar lists test_audit.py inside tests.
Does the Test File Look Incomplete?
Confirm the file starts with the hashlib import. Confirm it ends with the assertion that the artifacts folder does not exist.
Use the full-code tab to replace any missing section. Keep all eleven tests unchanged.
help me compare my tests/test_audit.py file with the required eleven-test harness
Inspect the literal Windows path fixture
A Bash permission rule is evaluated against the literal command string. The test uses one valid Windows path plus one deliberately corrupted string to catch backslash loss.
- In tests/test_audit.py find test_windows_bash_rule_matching_is_literal_and_reporting_preserves_path().
- Compare the three fixture values with this reference:
rule = r"Bash(C:\Users\dante\run.sh *)"
command = r"C:\Users\dante\run.sh --flag"
corrupted = r"C:Usersdanterun.sh --flag"
Why Keep the Command Literal?
The valid fixture preserves every backslash in C:\Users\dante\run.sh --flag. The corrupted fixture removes those separators.
A correct matcher accepts the first string. It rejects the corrupted string before any report-only executable summary is involved.
- Confirm the first assertion checks the literal command.
- Confirm the second assertion rejects the corrupted command.
- Save tests/test_audit.py.
Do the Backslashes Differ?
Use Python raw-string prefixes exactly as shown. A missing r changes how the fixture represents backslashes.
help me verify the Windows raw strings in my Bash-rule test
Test the incomplete package
The harness now describes the full audit contract. The package still contains only discovery code because floor_auditor/audit.py is intentionally absent.
- Switch back to the separate Git Bash terminal from earlier.
- Confirm the terminal is still inside the floor-auditor folder.
Before you run the suite, do you think pytest can collect tests while the imported audit module is absent?
- Run the complete test harness with quiet output by running this command:
python -m pytest -q
Pytest stops during collection. The error names the missing floor_auditor.audit module.
Why Collection Stops First
The test file imports the audit functions before pytest can execute an assertion. The missing module therefore blocks the entire contract from being collected.
This is the intended shortfall. Missing provenance, matching, light-switch, redaction, and report logic cannot be mistaken for a passing audit.
Before you check the status, which non-zero exit code do you expect Git Bash to report?
- Print the previous command's exit code by running:
echo $?
Git Bash prints 1. The harness has failed visibly at the missing implementation boundary.
Did Pytest Collect the Tests?
Confirm floor_auditor/audit.py is still absent. An existing file can move the run past the designed collection boundary.
Confirm tests/test_audit.py imports from floor_auditor.audit exactly as shown in the full-code tab.
help me reproduce the intended pytest collection failure for the missing audit module
That failure is doing useful work. Next, you will implement the missing resolver until all eleven tests pass.
Implement Provenance and Safe Reporting
The failing harness now protects the project from a false pass. The missing module keeps every provenance check and safety check visibly unresolved.
This step adds the complete Python auditor. It preserves literal Windows command text while producing deterministic evidence about the observable Claude Code floor.
In this step, get ready to:
- Preserve literal Bash command strings during permission-rule matching.
- Calculate effective configuration provenance and four-state evidence health.
- Create redacted report data with overwrite-safe report planning.
Preserve literal Windows commands
Bash permission rules apply to the whole command text. Passing a Windows path through POSIX tokenization can remove its backslashes before matching.
Why keep matching literal?
The bash_rule_matches() function compares each rule with the original command string. This preserves a path such as C:\Users\dante\run.sh character for character.
The command_executable_name() function uses shlex.split(command, posix=False) only to create a report summary. That derived token never decides whether a rule matches.
Resolve provenance and reporting safely
A resolved value needs evidence showing which source contributed it. Lists can combine contributors while scalar values follow precedence.
What the evidence model protects
- The effective_config_per_key report records each effective value with its resolution basis and contributing files.
- The floor_integrity switch compares live files with SHA-256 seals while keeping unavailable counts as None.
- The verification_trigger switch reads JSON Lines evidence while preserving distinct GREEN, DEGRADING, RED, and OVERDUE states.
- The shareable report includes remediated findings only. Secret matches identify the file, line, and pattern without copying the matched value.
Create and verify the auditor
The complete module connects literal matching, provenance, light switches, redaction, and report safety. It also keeps report creation inside the project while refusing existing destinations.
- Return to the Explorer sidebar in Visual Studio Code.
- Create audit.py inside the existing floor_auditor folder using the Explorer file controls.
- Use the complete-file tab below as the exact source for floor_auditor/audit.py.
✔️ Awesome, I've got everything!
You have added the complete auditor module. Save floor_auditor/audit.py before running the tests.
ⓧ I'd like to double check the full code
- Compare floor_auditor/audit.py with this complete file.
from __future__ import annotations
import hashlib
import json
import re
import shlex
import time
from pathlib import Path
from typing import Any, Iterable
from .discovery import SourceRecord, discover_sources, platform_family, select_observed_sources
GREEN = "GREEN"
DEGRADING = "DEGRADING"
RED = "RED"
OVERDUE = "OVERDUE"
CANONICAL_SCHEMA = "https://json.schemastore.org/claude-code-settings.json"
PERMISSION_ARRAYS = {"permissions.allow", "permissions.ask", "permissions.deny"}
NON_MERGING_LISTS = {"fallbackModel", "modelPicker.options"}
SHAREABLE_FIELDS = (
"id",
"check_name",
"scope",
"file_role",
"severity",
"evidence_label",
)
SECRET_PATTERNS = (
(
"private_key_header",
re.compile(r"-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----"),
),
(
"credential_assignment",
re.compile(
r'''(?ix)["']?(?:api[_-]?key|access[_-]?token|password|secret)["']?\s*[:=]\s*["'][^"'\r\n]{8,}["']'''
),
),
)
class SpecError(Exception):
pass
class RuntimeAuditError(Exception):
pass
def _canonical(value: Any) -> str:
return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False)
def _json_text(value: Any) -> str:
return json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + "\n"
def _hash_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _bash_pattern(rule: str) -> str | None:
if rule == "Bash":
return "*"
if rule.startswith("Bash(") and rule.endswith(")"):
return rule[5:-1]
return None
def bash_rule_matches(rule: str, literal_command: str) -> bool:
pattern = _bash_pattern(rule)
if pattern is None:
return False
if "*" not in pattern:
return literal_command == pattern
if pattern.endswith(" *") and pattern.count("*") == 1:
prefix = pattern[:-2]
return literal_command == prefix or literal_command.startswith(prefix + " ")
expression = re.escape(pattern).replace(r"\*", ".*")
return re.fullmatch(expression, literal_command, flags=re.DOTALL) is not None
def command_executable_name(command: str) -> str | None:
try:
tokens = shlex.split(command, posix=False)
except ValueError:
return None
if not tokens:
return None
token = tokens[0]
if len(token) >= 2 and token[0] in {"'", '"'} and token[-1] == token[0]:
token = token[1:-1]
if not token or token == "*":
return None
return token.replace("\\", "/").rsplit("/", 1)[-1]
def summarize_bash_rules(sources: Iterable[SourceRecord]) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for source in sources:
if source.status != "LOADED" or source.data is None:
continue
permissions = source.data.get("permissions")
if not isinstance(permissions, dict):
continue
for effect in ("deny", "ask", "allow"):
rules = permissions.get(effect)
if not isinstance(rules, list):
continue
for rule in rules:
if not isinstance(rule, str):
continue
pattern = _bash_pattern(rule)
if pattern is None:
continue
rows.append(
{
"scope": source.scope,
"file_role": source.file_role,
"effect": effect,
"rule": rule,
"executable": command_executable_name(pattern),
"matching_basis": "LITERAL_COMMAND_STRING",
}
)
return rows
def _flatten(value: dict[str, Any], prefix: tuple[str, ...] = ()):
for key in sorted(value):
child = value[key]
path = prefix + (key,)
if isinstance(child, dict) and child:
yield from _flatten(child, path)
else:
yield ".".join(path), child
def resolve_effective_config(
sources: Iterable[SourceRecord],
) -> tuple[dict[str, Any], dict[str, Any]]:
selected, coverage = select_observed_sources(sources)
managed_lock = any(
source.scope == "managed"
and source.data
and source.data.get("allowManagedPermissionRulesOnly") is True
for source in selected
)
contributions: dict[str, list[tuple[SourceRecord, Any]]] = {}
for source in selected:
assert source.data is not None
for key_path, value in _flatten(source.data):
if managed_lock and key_path in PERMISSION_ARRAYS and source.scope != "managed":
continue
contributions.setdefault(key_path, []).append((source, value))
resolved: dict[str, Any] = {}
for key_path in sorted(contributions):
entries = contributions[key_path]
highest_source, highest_value = entries[-1]
all_lists = all(isinstance(value, list) for _, value in entries)
managed_available_models = key_path == "availableModels" and any(
source.scope == "managed" for source, _ in entries
)
replace_list = key_path in NON_MERGING_LISTS or managed_available_models
if all_lists and not replace_list:
merged: list[Any] = []
seen: set[str] = set()
for _, values in entries:
for value in values:
marker = _canonical(value)
if marker not in seen:
seen.add(marker)
merged.append(value)
effective_value = merged
basis = "list_union"
contributing = entries
else:
effective_value = highest_value
basis = "highest_precedence"
contributing = [entries[-1]]
resolved[key_path] = {
"value": effective_value,
"resolution": basis,
"evidence_label": "MEASURED",
"sources": [
{
"scope": source.scope,
"file_role": source.file_role,
"location": source.location,
}
for source, _ in contributing
],
}
return resolved, coverage
def _finding_id(check_name: str, source: SourceRecord | None, suffix: str = "") -> str:
seed = ":".join(
(
check_name,
source.file_role if source else "system",
source.location if source else "system",
suffix,
)
)
return _hash_text(seed)[:16]
def finding(
check_name: str,
severity: str,
evidence_label: str,
detail: str,
source: SourceRecord | None = None,
remediated: bool = False,
) -> dict[str, Any]:
return {
"id": _finding_id(check_name, source, detail),
"check_name": check_name,
"scope": source.scope if source else "system",
"file_role": source.file_role if source else "system",
"severity": severity,
"evidence_label": evidence_label,
"detail": detail,
"remediated": remediated,
}
def _nested(data: dict[str, Any], *parts: str) -> Any:
current: Any = data
for part in parts:
if not isinstance(current, dict) or part not in current:
return None
current = current[part]
return current
def scan_source_findings(
sources: Iterable[SourceRecord], family: str
) -> list[dict[str, Any]]:
results: list[dict[str, Any]] = []
sandbox_found = False
for source in sources:
if source.status == "MALFORMED":
results.append(
finding("config_parse", RED, "MEASURED", source.error or "Malformed JSON", source)
)
continue
if source.status == "SKIPPED":
results.append(
finding("source_access", OVERDUE, "UNVERIFIED", source.error or "Unreadable", source)
)
continue
if source.status != "LOADED" or not source.data:
continue
data = source.data
if family == "native_windows" and "sandbox" in data:
sandbox_found = True
results.append(
finding(
"native_windows_sandbox_present",
RED,
"MEASURED",
"A sandbox block is present on a native Windows target.",
source,
)
)
if "$schema" in data and data["$schema"] != CANONICAL_SCHEMA:
results.append(
finding(
"schema_url_invalid",
RED,
"MEASURED",
"The settings schema URL does not match the documented Claude Code URL.",
source,
)
)
bypass = _nested(data, "permissions", "disableBypassPermissionsMode")
if bypass is not None and bypass != "disable":
results.append(
finding(
"disable_bypass_value_invalid",
RED,
"MEASURED",
"permissions.disableBypassPermissionsMode must be the string disable.",
source,
)
)
mode = _nested(data, "permissions", "defaultMode")
if source.scope in {"project", "local"} and mode == "auto":
results.append(
finding(
"default_mode_auto_inert",
RED,
"MEASURED",
"permissions.defaultMode auto is inert at this scope.",
source,
)
)
if "hooks" in data:
results.append(
finding(
"hook_role_coverage",
OVERDUE,
"UNVERIFIED",
"Hook configuration was observed, but referenced-script discovery is deferred to the enforcement-harness project.",
source,
)
)
if family == "native_windows" and not sandbox_found:
results.append(
finding(
"native_windows_sandbox_absent",
GREEN,
"MEASURED",
"No sandbox block was observed in the readable native Windows settings sources.",
remediated=True,
)
)
return results
def scan_secret_findings(sources: Iterable[SourceRecord]) -> list[dict[str, Any]]:
results: list[dict[str, Any]] = []
scanned = 0
for source in sources:
if source.status != "LOADED" or source.location.startswith("registry://"):
continue
path = Path(source.location)
try:
lines = path.read_text(encoding="utf-8").splitlines()
except OSError:
continue
scanned += 1
for line_number, line in enumerate(lines, start=1):
for pattern_name, pattern in SECRET_PATTERNS:
if pattern.search(line):
results.append(
finding(
"secret_pattern_match",
RED,
"MEASURED",
f"{source.location}:{line_number} pattern={pattern_name}",
source,
)
)
if not results:
results.append(
finding(
"secret_scan",
GREEN,
"MEASURED",
f"No configured secret pattern matched across {scanned} readable settings files.",
remediated=True,
)
)
return results
def known_live_floor_paths(
sources: Iterable[SourceRecord], project_root: Path, managed_root: Path
) -> list[Path]:
paths: set[Path] = set()
for source in sources:
if source.status in {"LOADED", "MALFORMED", "SKIPPED"} and not source.location.startswith(
"registry://"
):
paths.add(Path(source.location).resolve(strict=False))
for path in (project_root / ".mcp.json", managed_root / "managed-mcp.json"):
if path.exists():
paths.add(path.resolve(strict=False))
return sorted(paths, key=lambda item: str(item).casefold())
def evaluate_floor_integrity(
seal_index_path: Path, live_paths: Iterable[Path]
) -> dict[str, Any]:
if not seal_index_path.exists():
return {
"state": OVERDUE,
"sealed": None,
"live": None,
"changed": None,
"missing": None,
"unlisted": None,
"evidence_label": "MEASURED",
"reason": "Seal index was never written.",
}
try:
index = json.loads(seal_index_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as exc:
raise SpecError(f"Malformed seal index: {exc}") from exc
if not isinstance(index, dict) or not isinstance(index.get("roots"), list):
raise SpecError("Seal index must contain a roots array.")
sealed: dict[Path, dict[str, Any]] = {}
roots: list[Path] = []
for root_entry in index["roots"]:
if not isinstance(root_entry, dict) or set(root_entry) != {"name", "path", "manifest"}:
raise SpecError("Each seal root must contain only name, path, and manifest.")
root = Path(root_entry["path"]).expanduser()
if not root.is_absolute():
root = seal_index_path.parent / root
root = root.resolve(strict=False)
roots.append(root)
manifest_path = Path(root_entry["manifest"]).expanduser()
if not manifest_path.is_absolute():
manifest_path = seal_index_path.parent / manifest_path
if not manifest_path.exists():
return {
"state": OVERDUE,
"sealed": None,
"live": None,
"changed": None,
"missing": None,
"unlisted": None,
"evidence_label": "MEASURED",
"reason": f"Manifest for root {root_entry['name']} was never written.",
}
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as exc:
raise SpecError(f"Malformed manifest {manifest_path}: {exc}") from exc
if not isinstance(manifest, list):
raise SpecError("Each manifest must be a JSON array.")
manifest_paths = [entry.get("path") for entry in manifest if isinstance(entry, dict)]
if manifest_paths != sorted(manifest_paths):
raise SpecError("Manifest entries must be sorted by path.")
for entry in manifest:
if not isinstance(entry, dict) or set(entry) != {"path", "sha256", "size_bytes"}:
raise SpecError("Manifest entries must contain path, sha256, and size_bytes only.")
relative = Path(entry["path"])
if relative.is_absolute() or ".." in relative.parts:
raise SpecError("Manifest paths must be relative and may not contain parent traversal.")
sealed[(root / relative).resolve(strict=False)] = entry
changed = 0
missing = 0
existing_sealed: set[Path] = set()
for path, expected in sealed.items():
if not path.exists():
missing += 1
continue
existing_sealed.add(path)
stat = path.stat()
if stat.st_size != expected["size_bytes"] or sha256_file(path) != expected["sha256"]:
changed += 1
observed_live = set(existing_sealed)
for candidate in live_paths:
candidate = candidate.resolve(strict=False)
if any(candidate == root or candidate.is_relative_to(root) for root in roots):
observed_live.add(candidate)
unlisted = len(observed_live - set(sealed))
state = RED if changed or missing or unlisted else GREEN
return {
"state": state,
"sealed": len(sealed),
"live": len(observed_live),
"changed": changed,
"missing": missing,
"unlisted": unlisted,
"evidence_label": "MEASURED",
}
def evaluate_verification_trigger(
record_path: Path, staleness_hours: int = 24, now_epoch: int | None = None
) -> dict[str, Any]:
effective_now = int(time.time()) if now_epoch is None else int(now_epoch)
base = {
"staleness_limit_hours": staleness_hours,
"evidence_label": "MEASURED",
}
if not record_path.exists():
return {
**base,
"state": OVERDUE,
"clean_ticks_ever": None,
"ticks_started": None,
"failures_since_clean": None,
"last_clean_epoch": None,
"malformed": None,
"records": None,
"reason": "Verification evidence was never written.",
}
parsed: list[dict[str, Any]] = []
malformed = 0
nonblank = 0
try:
lines = record_path.read_text(encoding="utf-8").splitlines()
except OSError as exc:
raise RuntimeAuditError(f"Cannot read verification evidence: {exc}") from exc
for line in lines:
if not line.strip():
continue
nonblank += 1
try:
record = json.loads(line)
required = {"tick_id", "started_epoch", "finished_epoch", "result"}
if not isinstance(record, dict) or set(record) != required:
raise ValueError("record shape mismatch")
if not isinstance(record["result"], str) or not record["result"]:
raise ValueError("result must be a named state")
int(record["started_epoch"])
int(record["finished_epoch"])
parsed.append(record)
except (json.JSONDecodeError, TypeError, ValueError):
malformed += 1
if nonblank == 0:
return {
**base,
"state": OVERDUE,
"clean_ticks_ever": None,
"ticks_started": None,
"failures_since_clean": None,
"last_clean_epoch": None,
"malformed": None,
"records": None,
"reason": "Verification evidence contains no records.",
}
if not parsed:
return {
**base,
"state": RED,
"clean_ticks_ever": None,
"ticks_started": None,
"failures_since_clean": None,
"last_clean_epoch": None,
"malformed": malformed,
"records": 0,
"reason": "Every verification record is malformed.",
}
clean_positions = [index for index, record in enumerate(parsed) if record["result"] == "CLEAN"]
last_clean_index = clean_positions[-1] if clean_positions else None
failures_since_clean = sum(
1
for index, record in enumerate(parsed)
if (last_clean_index is None or index > last_clean_index) and record["result"] != "CLEAN"
)
last_clean_epoch = (
int(parsed[last_clean_index]["finished_epoch"]) if last_clean_index is not None else None
)
latest_epoch = max(int(record["finished_epoch"]) for record in parsed)
stale = effective_now - latest_epoch > staleness_hours * 3600
if malformed:
state = RED
elif stale:
state = OVERDUE
elif failures_since_clean:
state = DEGRADING
else:
state = GREEN
return {
**base,
"state": state,
"clean_ticks_ever": len(clean_positions),
"ticks_started": len(parsed),
"failures_since_clean": failures_since_clean,
"last_clean_epoch": last_clean_epoch,
"malformed": malformed,
"records": len(parsed),
}
def build_shareable(findings: Iterable[dict[str, Any]]) -> dict[str, Any]:
rows = []
for item in findings:
if item.get("remediated") is True:
rows.append({field: item[field] for field in SHAREABLE_FIELDS})
return {"findings": sorted(rows, key=lambda row: row["id"])}
def _overall_state(findings: Iterable[dict[str, Any]], *subsystems: dict[str, Any]) -> str:
active = [
item["severity"]
for item in findings
if item["evidence_label"] in {"MEASURED", "QUOTED"} and not item.get("remediated")
]
active.extend(subsystem["state"] for subsystem in subsystems)
for state in (RED, OVERDUE, DEGRADING):
if state in active:
return state
return GREEN
def run_audit(
project_root: Path,
managed_root: Path,
seal_index_path: Path,
verification_path: Path,
staleness_hours: int = 24,
now_epoch: int | None = None,
family: str | None = None,
config_root: Path | None = None,
include_registry: bool = True,
) -> tuple[dict[str, Any], dict[str, Any], int]:
family = family or platform_family()
sources = discover_sources(
project_root,
managed_root=managed_root,
config_root=config_root,
family=family,
include_registry=include_registry,
)
effective, coverage = resolve_effective_config(sources)
findings = scan_source_findings(sources, family)
findings.extend(scan_secret_findings(sources))
live_paths = known_live_floor_paths(sources, project_root, managed_root)
floor_integrity = evaluate_floor_integrity(seal_index_path, live_paths)
verification_trigger = evaluate_verification_trigger(
verification_path, staleness_hours=staleness_hours, now_epoch=now_epoch
)
findings.append(
finding(
"floor_integrity",
floor_integrity["state"],
floor_integrity["evidence_label"],
_canonical({key: floor_integrity[key] for key in ("sealed", "live", "changed", "missing", "unlisted")}),
remediated=floor_integrity["state"] == GREEN,
)
)
findings.append(
finding(
"verification_trigger",
verification_trigger["state"],
verification_trigger["evidence_label"],
_canonical(
{
key: verification_trigger[key]
for key in (
"clean_ticks_ever",
"ticks_started",
"failures_since_clean",
"last_clean_epoch",
"malformed",
"records",
)
}
),
remediated=verification_trigger["state"] == GREEN,
)
)
overall = _overall_state(findings, floor_integrity, verification_trigger)
malformed_input = any(source.status == "MALFORMED" for source in sources) or bool(
verification_trigger.get("malformed")
)
exit_code = 20 if malformed_input else (0 if overall == GREEN else 10)
local = {
"generated_epoch": int(time.time()) if now_epoch is None else int(now_epoch),
"platform": family,
"overall_state": overall,
"exit_class": exit_code,
"source_coverage": coverage,
"sources": [source.inventory() for source in sources],
"effective_config_per_key": effective,
"bash_rule_inventory": summarize_bash_rules(sources),
"floor_integrity": floor_integrity,
"verification_trigger": verification_trigger,
"findings": sorted(findings, key=lambda item: item["id"]),
}
shareable = build_shareable(local["findings"])
return local, shareable, exit_code
def plan_reports(
local: dict[str, Any], shareable: dict[str, Any], output_dir: Path
) -> list[dict[str, Any]]:
plans = []
for name, value in (
("local-report.json", local),
("shareable-report.json", shareable),
):
path = output_dir / name
text = _json_text(value)
plans.append(
{
"action": "REFUSE" if path.exists() else "CREATE",
"path": str(path.resolve(strict=False)),
"sha256": _hash_text(text),
"size_bytes": len(text.encode("utf-8")),
}
)
return plans
def write_reports(
local: dict[str, Any],
shareable: dict[str, Any],
output_dir: Path,
project_root: Path,
) -> list[Path]:
project_root = project_root.resolve(strict=False)
output_dir = output_dir.resolve(strict=False)
if not output_dir.is_relative_to(project_root):
raise RuntimeAuditError("Report output must stay inside the project root.")
targets = [output_dir / "local-report.json", output_dir / "shareable-report.json"]
if any(path.exists() for path in targets):
raise RuntimeAuditError("Refusing to overwrite an existing report.")
output_dir.mkdir(parents=True, exist_ok=True)
values = (local, shareable)
written = []
for path, value in zip(targets, values, strict=True):
try:
with path.open("x", encoding="utf-8", newline="\n") as handle:
handle.write(_json_text(value))
except OSError as exc:
raise RuntimeAuditError(f"Report write failed for {path}: {exc}") from exc
written.append(path)
return written
How the module fits together
- The matching helpers preserve literal command text while producing a separate executable summary.
- The resolver records scalar winners and merged list contributors for every observable key.
- The evidence evaluators preserve missing, malformed, degrading, and clean states without inventing zero counts.
- The reporting functions filter shareable rows and refuse unsafe report destinations.
- Save floor_auditor/audit.py in Visual Studio Code.
Before you run the suite, predict whether all eleven tests can now reach their assertions.
- Switch back to the separate Git Bash terminal from earlier.
- Run the deterministic test suite with this command:
python -m pytest -q
What should you see?
The terminal reports 11 passed. The collection error has disappeared because floor_auditor.audit now exists.
That result proves the literal Windows fixture, first-wins provenance, missing-evidence counts, secret exclusion, report filtering, and exit classes all satisfy the deterministic harness.
Still seeing a test failure?
Confirm that audit.py is inside the existing floor_auditor folder. A file beside that folder cannot satisfy the package import.
Compare the first failing assertion with the complete file above. Pay close attention to indentation and escaped backslashes in the regular expressions.
Help me diagnose the failing floor auditor test.
That is the core auditor working. Next, you will run it against the live Windows floor before explicitly creating the local and shareable reports.
Audit the Live Floor and Create Reports
Your eleven passing tests prove the auditor against controlled fixtures. The next run measures your live native-Windows Claude Code floor from a separate Git Bash terminal.
The first run builds a report plan in memory. An explicit second run creates the two evidence files without changing any floor source.
In this step, get ready to:
- Preview the live floor audit without writing reports.
- Create the local report plus the filtered shareable report.
- Validate both JSON reports against their disclosure boundaries.
Preview the live audit
A dry run reads the observable floor before calculating the report contents. It prints the proposed destinations without creating them.
- Switch back to the separate Git Bash terminal from earlier.
- Confirm the terminal prompt points to your floor-auditor folder.
Before you run the audit, predict whether the report plan can stay read-only while still calculating file hashes.
- Audit the live floor without writing reports by running this command:
python -m floor_auditor --project-root . --managed-root "C:/Program Files/ClaudeCode"
What does this command inspect?
- The --project-root option anchors project settings plus planned artifacts to the current floor-auditor folder.
- The --managed-root option points discovery at C:/Program Files/ClaudeCode.
- The missing --write-reports flag keeps both reports in memory.
- The report_plan rows show each destination plus its planned action.
You will see a formatted JSON object containing the observed source coverage. It also shows the overall light-switch state plus the deterministic exit class.
- Read remote_cache_status under source_coverage to confirm it is ABSENT.
- Confirm remote_cache_evidence_label is MEASURED.
- Review selected_managed_source_groups to identify the observed managed winner.
- Review ignored_managed_source_groups to identify lower policy-bearing groups.
- Check both report_plan rows for the CREATE action.
- Switch to the Explorer sidebar in Visual Studio Code.
- Confirm the artifacts/run-1 folder is still absent.
The safety boundary is working. Your auditor has measured the live floor without creating a report or changing a configuration source.
Does the audit stop before the plan?
Confirm your terminal is inside the floor-auditor folder. The package dispatcher needs that folder as its current project location.
Check that the managed path remains wrapped in matching quote characters. Its spaces require the shell to treat the path as one argument.
Help me troubleshoot the live audit command.
Create the two reports
This is the only point where the auditor writes to disk. Its create-only writer stays inside the project folder plus refuses existing report destinations.
Creating these reports is a controlled write. Your Claude Code settings, registry policy, hooks, MCP files, plus sealed evidence remain untouched.
- Check that the dry-run plan shows CREATE for local-report.json.
- Check that the dry-run plan shows CREATE for shareable-report.json.
- Confirm both planned paths sit inside floor-auditor/artifacts/run-1.
Before you run the explicit write, predict which two paths will appear in the final written list.
- Create both planned reports by running this command:
python -m floor_auditor --project-root . --managed-root "C:/Program Files/ClaudeCode" --output-dir artifacts/run-1 --write-reports
How does the write stay controlled?
- The command prints the deterministic plan before it attempts a write.
- The --output-dir option places both targets under artifacts/run-1.
- The --write-reports flag explicitly authorizes report creation.
- Exclusive file creation protects any report that already exists.
You will see a written array containing the two report paths. The reported exit class records the measured posture independently from report creation.
- Refresh the Explorer sidebar in Visual Studio Code.
- Expand the artifacts/run-1 folder.
- Confirm local-report.json appears in the folder.
- Confirm shareable-report.json appears in the folder.
That is the controlled write complete. You now have a private evidence artifact plus a filtered artifact designed for sharing.
Does the plan show REFUSE?
A REFUSE action means at least one destination already exists. This safeguard prevents a later run from replacing prior evidence.
Check the artifacts/run-1 folder for the existing report. Keep the file if it contains the successful audit you just created.
Help me understand a refused report write.
Validate the disclosure boundary
The JSON validator checks that each report can be parsed. Pretty-printed output also gives you a final view of the fields each artifact exposes.
Before you validate the files, predict which report will contain the full local findings plus which report will contain only filtered rows.
- Validate both report files by running these commands:
python -m json.tool artifacts/run-1/local-report.json
python -m json.tool artifacts/run-1/shareable-report.json
What does validation prove?
Each command parses one report before printing formatted JSON. A complete formatted document confirms that the file contains valid JSON.
The local report preserves full audit evidence for review on this machine. The shareable report keeps only remediated findings plus its approved field set.
Both commands print formatted JSON without a parsing error. The second output ends with the filtered findings array from shareable-report.json.
- Review the floor_integrity block in the local report output.
Absent seal evidence keeps sealed, live, changed, missing, plus unlisted as null. JSON uses null for the Python value None.
- Review the verification_trigger block in the local report output.
Missing verification evidence also preserves its count fields as null. A missing measurement cannot look like a measured zero.
- Review every detail value under the local findings array.
Secret findings name only the file, line, plus pattern. No matched credential value appears in the report.
- Review each object in the shareable findings array.
Each exported row contains only id, check_name, scope, file_role, severity, plus evidence_label.
- Compare the local findings array with the shareable findings array.
Unremediated posture stays in local-report.json only. The filtered report exposes remediated rows without their local detail text.
Does JSON validation fail?
Confirm both files exist under artifacts/run-1 in the floor-auditor folder. A path mismatch prevents the validator from reading the intended report.
Avoid editing either generated file before validation. The auditor creates deterministic JSON that should parse immediately.
Help me troubleshoot report validation.
You have audited the live Windows floor plus created two validated evidence artifacts. The project finishes with its read-only boundary intact.
Secret mission
Prove Per-File Defect Independence
The same inert setting can exist in two files at once. Add one test that proves each occurrence keeps its own finding. Verify that the findings retain separate file roles plus separate identifiers.
Clean Up Your Resources
Clean Up Your Resources
Everything you created stays inside the local floor-auditor workspace, so this project has no ongoing costs. Choose whether to keep the workspace, pause your work, or delete the files.
Resources you used:
- The floor-auditor project folder containing the Python package, test suite, and requirements-dev.txt.
- The artifacts/run-1 folder containing local-report.json and shareable-report.json.
Keep everything running
No action is needed. Choose this option if you want to continue testing the auditor or inspect its reports.
- Keep the floor-auditor folder with its package and twelve-test suite.
- Store local-report.json privately because it contains the complete findings from your machine.
- Use shareable-report.json when you need the filtered evidence artifact.
Pause - I'll come back to this later
The auditor has no background process to stop. Closing your tools pauses the project while every source file and report stays on disk.
- Close the separate Git Bash terminal after the current command has finished.
- Close the floor-auditor workspace in Visual Studio Code.
- Leave the floor-auditor folder on disk so you can resume with the package and reports intact.
Delete - I don't want to use this again
Removing the workspace is permanent. Your Claude Code settings and registry policy stay untouched because they sit outside floor-auditor.
- Switch back to the separate Git Bash terminal from earlier.
- Confirm the terminal prompt points inside floor-auditor.
- Remove the entire floor-auditor workspace from its parent folder by running these commands:
cd ..
rm -rf floor-auditor
ls
What Do These Commands Do?
- The first line moves the terminal into the folder that contains floor-auditor.
- The second line deletes the floor-auditor workspace plus every file inside it.
- The final line lists the remaining folders so you can verify the deletion.
You should no longer see floor-auditor in the final listing. That confirms the package, tests, and reports are gone.
You have cleaned up the project without touching the live Claude Code floor.
Folder Still Listed?
- Close Visual Studio Code if it still has a file from floor-auditor open.
- Move the Git Bash prompt to the folder above floor-auditor before retrying the cleanup.
Help me diagnose why Git Bash could not remove the project folder.
Nice Work!
Nice Work!
Mission complete! Your portable Python auditor now reads the observable Claude Code floor without changing live configuration.
You've learned how to:
- Built read-only discovery across user, project, local, remote-cache, managed-file, drop-in, HKLM, and HKCU sources. You classified every source as LOADED, ABSENT, MALFORMED, or SKIPPED. You applied managed-source first-wins selection while keeping selected groups visible. You also kept ignored groups visible.
- Calculated per-key provenance for scalar winners and merged arrays. You preserved literal Windows paths during Bash-rule matching. You used shlex.split(command, posix=False) only for report-only executable names.
- Implemented deterministic evidence with floor_integrity and verification_trigger. You kept GREEN, DEGRADING, RED, and OVERDUE distinct. You preserved None counts when evidence was absent. You redacted secret values from findings. You produced separate local and shareable JSON reports.
- Secret Mission: Proved per-file defect independence by adding a twelfth pytest test. The suite now reports 12 passed. The duplicate default_mode_auto_inert findings keep separate scopes. They also keep separate file roles and IDs.
Ready to quiz yourself?