@namewta/speculo 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/structured.js +1 -1
- package/dist/src/structured.js.map +1 -1
- package/package.json +1 -1
- package/template/workflows/ops/A-archive-and-learn/A-archive-and-learn.md +2 -0
- package/template/workflows/ops/H-computer-hygiene/H-computer-hygiene.md +105 -0
- package/template/workflows/ops/H-computer-hygiene/examples/README.md +9 -0
- package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.environment.md +45 -0
- package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.json +970 -0
- package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.md +216 -0
- package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.environment.md +45 -0
- package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.json +970 -0
- package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.md +216 -0
- package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.environment.md +45 -0
- package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.json +969 -0
- package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.md +216 -0
- package/template/workflows/ops/H-computer-hygiene/references/00-sources.md +58 -0
- package/template/workflows/ops/H-computer-hygiene/references/01-safety.md +52 -0
- package/template/workflows/ops/H-computer-hygiene/references/10-windows.md +47 -0
- package/template/workflows/ops/H-computer-hygiene/references/11-macos.md +46 -0
- package/template/workflows/ops/H-computer-hygiene/references/12-linux.md +13 -0
- package/template/workflows/ops/H-computer-hygiene/references/20-toolchains.md +83 -0
- package/template/workflows/ops/H-computer-hygiene/references/21-path-migration.md +64 -0
- package/template/workflows/ops/H-computer-hygiene/references/22-repositories.md +35 -0
- package/template/workflows/ops/H-computer-hygiene/references/30-software-removal.md +41 -0
- package/template/workflows/ops/H-computer-hygiene/references/40-report-spec.md +54 -0
- package/template/workflows/ops/H-computer-hygiene/references/50-speculo-integration.md +26 -0
- package/template/workflows/ops/H-computer-hygiene/references/source-register.json +406 -0
- package/template/workflows/ops/H-computer-hygiene/rules/catalog.json +191 -0
- package/template/workflows/ops/H-computer-hygiene/scripts/hygiene.py +1348 -0
- package/template/workflows/ops/H-computer-hygiene/scripts/run.ps1 +7 -0
- package/template/workflows/ops/H-computer-hygiene/scripts/run.sh +5 -0
- package/template/workflows/ops/H-computer-hygiene/templates/config.example.json +13 -0
- package/template/workflows/ops/H-computer-hygiene/templates/maven-settings.fragment.xml +6 -0
- package/template/workflows/ops/H-computer-hygiene/templates/native-evidence.example.json +19 -0
- package/template/workflows/ops/H-computer-hygiene/tests/cli_smoke.py +65 -0
- package/template/workflows/ops/H-computer-hygiene/tests/make_examples.py +114 -0
- package/template/workflows/ops/H-computer-hygiene/tests/test_hygiene.py +486 -0
- package/template/workflows/ops/README.md +9 -3
- package/template/workflows/ops/common/rules/artifact-contract.md +2 -1
- package/template/workflows/ops/common/rules/closure-and-learning.md +2 -0
- package/template/workflows/ops/common/schemas/change-status.schema.json +2 -2
- package/template/workflows/ops/common/tools/validate-ops.mjs +29 -6
- package/template/workflows/ops/manifest.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Read-only audit wrapper. Run with a trusted Python 3.10+ interpreter.
|
|
2
|
+
[CmdletBinding()]
|
|
3
|
+
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$AuditArgs)
|
|
4
|
+
$ErrorActionPreference = 'Stop'
|
|
5
|
+
$scriptFile = Join-Path $PSScriptRoot 'hygiene.py'
|
|
6
|
+
& python $scriptFile audit @AuditArgs
|
|
7
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"project_roots": [],
|
|
4
|
+
"exclude_roots": [],
|
|
5
|
+
"target_root": null,
|
|
6
|
+
"max_entries_per_tree": 20000,
|
|
7
|
+
"max_entries_total": 200000,
|
|
8
|
+
"seconds_per_tree": 3.0,
|
|
9
|
+
"seconds_total": 90.0,
|
|
10
|
+
"project_depth": 3,
|
|
11
|
+
"min_age_days": 14,
|
|
12
|
+
"large_file_mib": 512
|
|
13
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- EXAMPLE ONLY: merge localRepository into the existing settings.xml.
|
|
2
|
+
Do not replace the whole file: servers/profiles/mirrors may be essential.
|
|
3
|
+
Replace DEVROOT with a verified absolute path; back up local-only artifacts. -->
|
|
4
|
+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
|
|
5
|
+
<localRepository>DEVROOT/data/maven/repository</localRepository>
|
|
6
|
+
</settings>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"record_kind": "TEMPLATE_NOT_REAL_EVIDENCE",
|
|
4
|
+
"scan_id": "REPLACE_WITH_REAL_SCAN_ID",
|
|
5
|
+
"collected_at": null,
|
|
6
|
+
"tool": "go",
|
|
7
|
+
"trusted_executable_path": "REPLACE_WITH_VERIFIED_ABSOLUTE_PATH",
|
|
8
|
+
"user_authorized_execution": false,
|
|
9
|
+
"argv": ["env", "GOPATH", "GOBIN", "GOCACHE", "GOMODCACHE", "GOROOT", "GOENV"],
|
|
10
|
+
"executed": false,
|
|
11
|
+
"exit_code": null,
|
|
12
|
+
"version": null,
|
|
13
|
+
"allowed_path_values": {},
|
|
14
|
+
"effective_scope": "unknown until actually queried; CLI/project/IDE may differ",
|
|
15
|
+
"full_stdout_recorded": false,
|
|
16
|
+
"full_environment_recorded": false,
|
|
17
|
+
"source_document_verified_at": null,
|
|
18
|
+
"limitations": ["Example only. Starting even a query can invoke shims, plugins, downloads or telemetry."]
|
|
19
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Linux-only end-to-end CLI test in a temporary HOME; no real account scan."""
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
if platform.system() != "Linux":
|
|
16
|
+
print(json.dumps({"status": "skipped", "reason": "Linux fixture test; native platform acceptance is separate"}))
|
|
17
|
+
return 0
|
|
18
|
+
script = Path(__file__).resolve().parents[1] / "scripts/hygiene.py"
|
|
19
|
+
stages = []
|
|
20
|
+
with tempfile.TemporaryDirectory(prefix="hygiene-cli-fixture-") as tmp:
|
|
21
|
+
root = Path(tmp).resolve()
|
|
22
|
+
home = root / "home"
|
|
23
|
+
cache = home / ".npm/_cacache"
|
|
24
|
+
payload = cache / "content-v2/aa/data"
|
|
25
|
+
payload.parent.mkdir(parents=True)
|
|
26
|
+
payload.write_bytes(b"CLI-FIXTURE-ONLY\n")
|
|
27
|
+
original = hashlib.sha256(payload.read_bytes()).hexdigest()
|
|
28
|
+
old = time.time() - 40 * 86400
|
|
29
|
+
for path in sorted(cache.rglob("*"), key=lambda x: len(x.parts), reverse=True):
|
|
30
|
+
os.utime(path, (old, old))
|
|
31
|
+
os.utime(cache, (old, old))
|
|
32
|
+
(home / "bin").mkdir()
|
|
33
|
+
env = {"HOME": str(home), "PATH": str(home / "bin"), "LANG": "C.UTF-8", "PYTHONDONTWRITEBYTECODE": "1"}
|
|
34
|
+
def call(stage, args, expected=0, raw=False):
|
|
35
|
+
result = subprocess.run([sys.executable, str(script), *args], env=env, cwd=root,
|
|
36
|
+
capture_output=True, text=True, timeout=30, check=False)
|
|
37
|
+
stages.append({"stage": stage, "exit_code": result.returncode, "expected": expected})
|
|
38
|
+
if result.returncode != expected:
|
|
39
|
+
raise RuntimeError(f"{stage} failed: {result.stderr}")
|
|
40
|
+
return result.stdout.strip() if raw else (json.loads(result.stdout) if result.stdout.strip() else None)
|
|
41
|
+
version = call("version", ["--version"], raw=True)
|
|
42
|
+
files = call("audit", ["audit", "--output", str(root / "reports")])
|
|
43
|
+
assert payload.exists() and not (home / ".speculo-hygiene").exists()
|
|
44
|
+
review = call("review", ["review", "--plan", files["plan"]])
|
|
45
|
+
assert len(review["actions"]) == 1
|
|
46
|
+
approval = root / "approval.json"
|
|
47
|
+
result = call("approve", ["approve", "--plan", files["plan"], "--ids", review["actions"][0]["id"],
|
|
48
|
+
"--output", str(approval), "--ack", "I_REVIEWED_EACH_ID"])
|
|
49
|
+
args = ["apply", "--plan", files["plan"], "--approval", str(approval), "--confirm", result["apply_confirmation"]]
|
|
50
|
+
call("missing-tools-closed-refused", args, expected=2)
|
|
51
|
+
assert payload.exists()
|
|
52
|
+
applied = call("apply", [*args, "--tools-closed"])
|
|
53
|
+
assert not payload.exists() and applied["freed_disk_space"] is False
|
|
54
|
+
preview = call("restore-preview", ["restore", "--receipt", applied["receipt"]])
|
|
55
|
+
call("restore", ["restore", "--receipt", applied["receipt"], "--confirm", preview["confirmation"]])
|
|
56
|
+
assert payload.exists() and hashlib.sha256(payload.read_bytes()).hexdigest() == original
|
|
57
|
+
output = {"status": "passed", "fixture_only": True, "temporary_home_only": True,
|
|
58
|
+
"version": version, "stages": stages, "restored_payload_hash_matches": True,
|
|
59
|
+
"default_audit_no_source_mutation": True, "no_permanent_deletion": True}
|
|
60
|
+
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build explicitly synthetic examples without reading the real user's home/apps.
|
|
3
|
+
|
|
4
|
+
Only isolated tempfile content is audited. Native OS inventories are replaced by
|
|
5
|
+
fixture metadata. Never run this as evidence about a real computer.
|
|
6
|
+
"""
|
|
7
|
+
import importlib.util
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import tempfile
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
WORK = Path(__file__).resolve().parents[1]
|
|
15
|
+
spec = importlib.util.spec_from_file_location("hygiene", WORK / "scripts/hygiene.py")
|
|
16
|
+
h = importlib.util.module_from_spec(spec)
|
|
17
|
+
spec.loader.exec_module(h)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FixtureAuditor(h.Auditor):
|
|
21
|
+
def platform_inventory(self):
|
|
22
|
+
self.coverage.append(dict(area="applications/startup", status="SIMULATED-NOT-NATIVE",
|
|
23
|
+
detail="Synthetic application metadata only; no real registry, Applications, launchd or service reads."))
|
|
24
|
+
example = {"name": "Example Editor", "version": "1.0-fixture", "system_component": False,
|
|
25
|
+
"action": "fixture candidate; not real software and never uninstalled"}
|
|
26
|
+
if self.system == "Windows":
|
|
27
|
+
example["identity"] = "HKCU/Uninstall/FIXTURE-EXAMPLE-ID"
|
|
28
|
+
else:
|
|
29
|
+
example.update(path=str(self.home / "Applications/Example Editor.app"), bundle_id="invalid.example.editor")
|
|
30
|
+
return {"applications": [example], "startup": [], "persistent_environment": [],
|
|
31
|
+
"errors": ["Real OS inventory intentionally disabled for this example."]}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def populate(home, system):
|
|
35
|
+
h.ensure_private_dir(home)
|
|
36
|
+
for folder in ("bin-primary", "bin-old"):
|
|
37
|
+
name = "java.exe" if system == "Windows" else "java"
|
|
38
|
+
binary = home / folder / name
|
|
39
|
+
binary.parent.mkdir(parents=True)
|
|
40
|
+
binary.write_text("FIXTURE: NOT EXECUTABLE CODE")
|
|
41
|
+
binary.chmod(0o755)
|
|
42
|
+
repo = home / ".m2/repository/example/internal/1.0"
|
|
43
|
+
repo.mkdir(parents=True)
|
|
44
|
+
(repo / "internal-1.0.jar").write_bytes(b"LOCAL_ONLY_FIXTURE" * 1024)
|
|
45
|
+
(home / ".m2/settings.xml").write_text(f"<settings><localRepository>{home / '.m2/repository'}</localRepository><servers><server><password>DO_NOT_EXPORT_DEMO_SECRET</password></server></servers></settings>")
|
|
46
|
+
cargo = home / ".cargo"
|
|
47
|
+
cargo.mkdir()
|
|
48
|
+
(cargo / "credentials.toml").write_text('token="DO_NOT_EXPORT_DEMO_SECRET"')
|
|
49
|
+
(cargo / "config.toml").write_text("# protected fixture config")
|
|
50
|
+
for kind, path in h.default_cache_allowlist(home, system).items():
|
|
51
|
+
relative = {"npm-content-cache": "content-v2/aa/data", "pip-cache": "wheels/aa/demo.whl", "go-build-cache": "aa/demo-a"}[kind]
|
|
52
|
+
f = path / relative
|
|
53
|
+
f.parent.mkdir(parents=True)
|
|
54
|
+
f.write_bytes(b"regenerable-fixture-cache\n" * 2048)
|
|
55
|
+
old = time.time() - 45 * 86400
|
|
56
|
+
for child in sorted(path.rglob("*"), key=lambda p: len(p.parts), reverse=True):
|
|
57
|
+
os.utime(child, (old, old))
|
|
58
|
+
os.utime(path, (old, old))
|
|
59
|
+
project = home / "Projects/example-web"
|
|
60
|
+
project.mkdir(parents=True)
|
|
61
|
+
(project / ".git").mkdir()
|
|
62
|
+
(project / "package.json").write_text('{"packageManager":"pnpm@9.0.0"}')
|
|
63
|
+
for name in ("pnpm-lock.yaml", "package-lock.json", ".nvmrc"):
|
|
64
|
+
(project / name).write_text("fixture")
|
|
65
|
+
(project / "node_modules").mkdir()
|
|
66
|
+
(project / "node_modules/local-note.txt").write_text("May be unique data: do not auto-delete")
|
|
67
|
+
(home / ".zshrc").write_text('export JAVA_HOME="$HOME/old-jdk" # DO_NOT_EXPORT_DEMO_SECRET\n')
|
|
68
|
+
(home / ".profile").write_text('export JAVA_HOME="$HOME/new-jdk"\n')
|
|
69
|
+
downloads = home / "Downloads"
|
|
70
|
+
downloads.mkdir()
|
|
71
|
+
(downloads / "old-installer.zip").write_bytes(b"installer fixture")
|
|
72
|
+
unsafe = downloads / "source-clone"
|
|
73
|
+
unsafe.mkdir()
|
|
74
|
+
(unsafe / ".git").write_text("gitdir: ../../Projects/example-web/.git/worktrees/example")
|
|
75
|
+
delimiter = ";" if system == "Windows" else ":"
|
|
76
|
+
return {"PATH": delimiter.join(str(home / n) for n in ("bin-primary", "bin-old", "bin-primary", "missing-bin")),
|
|
77
|
+
"HOME": str(home), "LOCALAPPDATA": str(home / "AppData/Local"),
|
|
78
|
+
"JAVA_HOME": str(home / "old-jdk"), "CARGO_HOME": str(cargo),
|
|
79
|
+
"GOCACHE": str(h.default_cache_allowlist(home, system)["go-build-cache"])}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main():
|
|
83
|
+
generated = []
|
|
84
|
+
for system, folder in (("Windows", "windows"), ("Darwin", "macos"), ("Linux", "linux")):
|
|
85
|
+
with tempfile.TemporaryDirectory(prefix="hygiene-demo-") as temporary:
|
|
86
|
+
home = Path(temporary).resolve() / "fixture-home"
|
|
87
|
+
env = populate(home, system)
|
|
88
|
+
auditor = FixtureAuditor(home=home, system=system, environ=env,
|
|
89
|
+
options={"project_roots": [str(home / "Projects"), str(home / "Downloads")], "seconds_per_tree": 10},
|
|
90
|
+
label="SYNTHETIC_FIXTURE_NOT_YOUR_COMPUTER", native_platform_reads=False)
|
|
91
|
+
report, plan = auditor.run()
|
|
92
|
+
report["host"].update(release="SIMULATED_OS_METADATA", machine="SIMULATED_ARCHITECTURE",
|
|
93
|
+
elevated=False, fingerprint="FIXTURE_NOT_FOR_APPROVAL",
|
|
94
|
+
disk_total_bytes=512 * 1024**3, disk_free_bytes=120 * 1024**3,
|
|
95
|
+
execution_context_signals={"fixture_only": True})
|
|
96
|
+
report["scope"]["limitations"].append("Disk capacity, architecture and application identity are illustrative fixture values, not real measurements.")
|
|
97
|
+
report["findings"] = [x for x in report["findings"] if x["category"] != "privilege"]
|
|
98
|
+
report["summary"]["findings"] = len(report["findings"])
|
|
99
|
+
output = WORK / "examples" / folder
|
|
100
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
date = report["date"]
|
|
102
|
+
md = h.render_report(report, plan)
|
|
103
|
+
public = h.redact(report, home)
|
|
104
|
+
assert "DO_NOT_EXPORT_DEMO_SECRET" not in md + json.dumps(public)
|
|
105
|
+
(output / f"{date}.md").write_text(md, encoding="utf-8")
|
|
106
|
+
(output / f"{date}.json").write_bytes(h.json_bytes(public))
|
|
107
|
+
target = h.target_root(home, system, env, None)
|
|
108
|
+
(output / f"{date}.environment.md").write_text("**模拟示例,不是已执行迁移。**\n\n" + h.render_environment(report, target), encoding="utf-8")
|
|
109
|
+
generated.append({"system": system, "example": str(output / f"{date}.md"), "findings": len(report["findings"]), "quarantine_candidates": len(plan["actions"])})
|
|
110
|
+
print(json.dumps({"fixture_only": True, "generated": generated}, ensure_ascii=False, indent=2))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
main()
|