core/process.py spawns a child that outlives its parent: its own session, so a signal to the parent's group or a timeout kill does not take the work with it; re-execing reach by BARE NAME, because an absolute path would freeze the child to whichever checkout was current at spawn time and silently run the wrong source after a repoint; and streams kept separate exactly as in the foreground, events to <id>.jsonl and real output to <id>.out. Testing a case the ticket did not name found a real hole. Recording completion inside @command looked right and was wrong: a child that fails BEFORE any command runs — bad arguments, an unknown verb, an import error — never reaches that decorator. `reach --detach check bogus` left its metadata reading "running" forever with the process long gone. That is the exit-0 trap wearing a new disguise and worse than the original, because a failed job that looks busy sits somewhere nobody is watching, and a caller polling for completion would wait indefinitely on something that failed in milliseconds. So completion is recorded at the PROCESS's exit instead. main.py gains main(), wrapping cli() in a single try/finally, and the entry point moves to main:main. Every exit path now passes through one place. Removed from @command rather than left in both — two writers of one field is how they drift. Verified on three paths: success records done/0, a real drift failure records failed/1, and the parse failure that exposed the hole now records failed/2. One narrow conformance exemption, with its reason inline so it does not read as an oversight: the no-domain-imports-core.jobs invariant fired on main.py, correctly by its letter and wrongly by its purpose. main.py is not a command; it is the entry point, and it already owns --detach. Still open, and carried to T-1278: a child killed outright cannot record anything, so jobs list must reconcile against process liveness rather than trusting the file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
82 lines
3.9 KiB
TOML
82 lines
3.9 KiB
TOML
[project]
|
||
name = "settled-reach-tooling"
|
||
version = "0.1.0"
|
||
requires-python = ">=3.11"
|
||
dependencies = [
|
||
"PyYAML",
|
||
"jsonschema",
|
||
"numpy",
|
||
# scipy 1.17.1 — checked clean against NVD + OSV, no CVEs on record (2026-04-06)
|
||
"scipy==1.17.1",
|
||
# Pillow 12.2.0 — checked clean against NVD + OSV (2026-04-06)
|
||
# CVE-2026-25990 fixed in 12.1.1, CVE-2025-48379 fixed in 11.3.0
|
||
"Pillow==12.2.0",
|
||
# typer 0.27.1 — the CLI transport (D-263). Checked clean against NVD, OSV
|
||
# and the GitHub Advisory Database: no advisories on record for typer at all
|
||
# (2026-08-20). Its transitive set was checked too, since typer pulls in
|
||
# rich -> pygments: pygments 2.21.0 clears CVE-2026-4539 (archetype-lexer
|
||
# ReDoS, fixed in 2.20.0); rich and click have no advisories on record.
|
||
#
|
||
# Plain `typer`, not `typer-slim`: slim is deprecated as of typer 0.22.0 and
|
||
# is now a shallow wrapper that installs all of typer, so it buys nothing.
|
||
# rich therefore ships as a transitive dependency — but `rich_markup_mode=None`
|
||
# in tooling/main.py keeps it off the import path entirely (verified: `rich`
|
||
# and `pygments` are absent from sys.modules after loading the CLI).
|
||
"typer==0.27.1",
|
||
# pydantic 2.13.4 — data shapes for domain schemas (D-263). Checked clean
|
||
# against NVD + OSV (2026-08-20). PYSEC-2026-1812 / CVE-2024-3772 (email
|
||
# regex ReDoS) is fixed in 2.4.0. NOTE the 2026 SSRF advisories
|
||
# CVE-2026-25580 and CVE-2026-54249 are against *pydantic-ai*, a different
|
||
# package that is not a dependency here — do not confuse the two on the
|
||
# next sweep.
|
||
"pydantic==2.13.4",
|
||
]
|
||
|
||
[project.scripts]
|
||
# The whole point of D-263: one bare command, so one permission-rule entry
|
||
# covers every tool.
|
||
#
|
||
# `cli` is a typer.Typer instance (callable), NOT the click.Group that T-1259
|
||
# originally specified: typer vendors click as of 0.26.0, so there is no
|
||
# top-level `click` to import and no supported way to extract typer's internal
|
||
# one. Lazy domain registration therefore goes through `typer.Typer(cls=...)`
|
||
# with a TyperGroup subclass (T-1260) rather than a click Group.
|
||
reach = "tooling.main:main"
|
||
|
||
[tool.setuptools.packages.find]
|
||
# Explicit, not flat-layout auto-discovery. The repo root holds client/, server/,
|
||
# docs/, wiki/, db/ and tests/ alongside tooling/, and auto-discovery either
|
||
# errors on the ambiguity or quietly ships something unintended (T-1258).
|
||
#
|
||
# Nothing needs excluding yet: the hyphenated directories (planet-gen,
|
||
# economy-db, garment-fit, pql-migrate) are invisible to package discovery
|
||
# because a hyphen is not a valid Python identifier, and tooling/econ-sim is a
|
||
# Rust crate with no __init__.py. That changes in T-1250, which renames them —
|
||
# at which point they become real packages and this include starts matching them.
|
||
include = ["tooling*"]
|
||
|
||
[project.optional-dependencies]
|
||
dev = [
|
||
# ruff 0.15.9 — checked clean against NVD + OSV, no CVEs on record (2026-04-05)
|
||
"ruff==0.15.9",
|
||
]
|
||
|
||
[tool.ruff]
|
||
line-length = 100
|
||
target-version = "py311"
|
||
|
||
[tool.ruff.lint]
|
||
# Widened from {E9, F401, F811, F821} to the full ruff-default tiers + W (T-1066).
|
||
# E4: import placement/style
|
||
# E7: statement-level pitfalls (== None, bare except, lambda assignment, ...)
|
||
# E9: runtime syntax/encoding errors
|
||
# F: all pyflakes (unused imports/names, undefined names, f-string misuse, ...)
|
||
# W: whitespace + invalid escape sequences (zero violations at adoption)
|
||
select = ["E4", "E7", "E9", "F", "W"]
|
||
# Rules excluded at adoption because the existing violation count was not
|
||
# trivially fixable (T-1066) — re-enable per-rule as the debt is paid down:
|
||
# E402 (43×): module-import-not-at-top — script-style sys.path.insert before import
|
||
# E702 (41×): semicolon-paired assignments, deliberate style in planet-gen noise math
|
||
# F841 (21×): unused locals, mostly in numeric/diagnostic code — needs manual review
|
||
ignore = ["E402", "E702", "F841"]
|