From 5a72af977b33ed76df988d33294116013aa5e540 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 22 Aug 2026 14:14:35 -0700 Subject: [PATCH 1/3] Stop the 3.7 Bootstrap Probes From Reading as This Repo's Python Floor spec/host-tools.json's python3 target floor is 3.13, but repo-config/configure.sh's resolve_description.py probe (PEP 563, 3.7+) sat next to it with no cross-reference, reading as a second, lower floor for this repo's own spec/ code rather than what it actually is: the oldest interpreter that one script happens to parse and run on. - repo-config/configure.sh: reword the probe's comment and failure message so they state the real floor (3.13, enforced by scripts/host_gate.py) instead of implying 3.7 is a supported target. - spec/host-tools.json: cross-reference configure.sh and the skills_install.sh / install-skills.* bootstrap scripts from the python3 entry's why, so a reader who hits either script's 3.7 language finds the real floor documented beside it. - python-codestyle skill (code-style.md, hand-authored in .agents/skills/, dist regenerated via scripts/build_dist.py): state the 3.13 floor explicitly under Type hints, so modern syntax (X | None, match, ...) needs no quoting or __future__ guard on version-compat grounds alone, and name the two bootstrap-script exceptions. - .github/copilot-instructions.md: delete a Disproved Claims entry whose subject moved out from under it. It was proved against install.py's interpreter floor at 3.7 (dbd1cdc); PR #644 raised that floor to 3.11 without touching this entry, so it now states a fact about install.py that is no longer true. Per this file's own rule, an entry whose subject moves is deleted rather than edited to look current. Co-Authored-By: Claude Sonnet 5 --- .agents/skills/python-codestyle/references/code-style.md | 9 +++++++++ .claude-plugin/fleet-skills/.source-digest | 2 +- .../skills/python-codestyle/references/code-style.md | 9 +++++++++ .github/copilot-instructions.md | 6 ------ .github/skills/python-codestyle/references/code-style.md | 9 +++++++++ repo-config/configure.sh | 6 ++++-- spec/host-tools.json | 2 +- 7 files changed, 33 insertions(+), 10 deletions(-) diff --git a/.agents/skills/python-codestyle/references/code-style.md b/.agents/skills/python-codestyle/references/code-style.md index b7e62b00..e8779494 100644 --- a/.agents/skills/python-codestyle/references/code-style.md +++ b/.agents/skills/python-codestyle/references/code-style.md @@ -44,6 +44,15 @@ - **Use modern syntax**: `list[int]` not `List[int]`, `dict[str, X]` not `Dict[str, X]`, `X | None` not `Optional[X]`, `from __future__ import annotations` only when needed for forward references. +- **Don't hedge that syntax for an older interpreter.** `pyproject.toml` pins `target-version` / + `python_version` to 3.13 for every Python profile in this repo, and `spec/host-tools.json` + carries that as the host floor `scripts/host_gate.py` enforces, so 3.10+-only syntax (`X | None`, + `match`, etc.) needs no quoting, no `typing.Union` fallback, and no `from __future__ import + annotations` guard on that account alone. Add that import only when a real forward reference + needs it, per the bullet above. The floor is repo-wide with two named exceptions that say so + themselves: `scripts/skills_install.sh` and the `install-skills.*` bootstrap scripts, which must + run on whatever interpreter a host already has before this floor's toolchain exists to install + one, and stay compatible back to 3.7 for exactly that reason. - **Don't add `# type: ignore` to silence pyright errors without a comment** explaining the constraint. If a recurring false positive needs suppression, configure it project-wide in `[tool.pyright]`. A new port doesn't change this, fix freshly surfaced type errors rather than diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index 31e3ed9d..b28fbc22 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -716018234e006002 +f36e72b767cea6c6 diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md index b7e62b00..e8779494 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md @@ -44,6 +44,15 @@ - **Use modern syntax**: `list[int]` not `List[int]`, `dict[str, X]` not `Dict[str, X]`, `X | None` not `Optional[X]`, `from __future__ import annotations` only when needed for forward references. +- **Don't hedge that syntax for an older interpreter.** `pyproject.toml` pins `target-version` / + `python_version` to 3.13 for every Python profile in this repo, and `spec/host-tools.json` + carries that as the host floor `scripts/host_gate.py` enforces, so 3.10+-only syntax (`X | None`, + `match`, etc.) needs no quoting, no `typing.Union` fallback, and no `from __future__ import + annotations` guard on that account alone. Add that import only when a real forward reference + needs it, per the bullet above. The floor is repo-wide with two named exceptions that say so + themselves: `scripts/skills_install.sh` and the `install-skills.*` bootstrap scripts, which must + run on whatever interpreter a host already has before this floor's toolchain exists to install + one, and stay compatible back to 3.7 for exactly that reason. - **Don't add `# type: ignore` to silence pyright errors without a comment** explaining the constraint. If a recurring false positive needs suppression, configure it project-wide in `[tool.pyright]`. A new port doesn't change this, fix freshly surfaced type errors rather than diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8febbd7f..b39ce5c4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -65,12 +65,6 @@ Review effort is user-controlled. The automation observes `Lite`, `Balanced`, or - **Proved against** - the `norm` filter in `repo-config/configure.sh` on `develop` at `756a53e`. - **Delete when** - the filter stops calling `keys_unsorted`, or nothing this check runs on carries a jq older than 1.6. -- **Splitting the fallback parse in `host-setup/agent-safety/gh-write-guard.py` a line at a time mis-reads a newline inside a quoted argument, reintroducing the false deny that path exists to remove.** Raised against the branch that made a newline end a command, on the ground that a `--body` argument holding a newline and a `git push origin develop` would have that line read as a push. - - **Disproved by** - the arm being unreachable, and then by measuring it rather than resting on that. `punctuation_chars` arrived in Python 3.6, the module uses f-strings throughout, and `install.py` refuses to install below 3.7, so an interpreter that would raise the `TypeError` fails to import the module before reaching the fallback. Simulated against a `shlex` that rejects the keyword and passes everything else through, the quoted-newline example is allowed on both paths, because splitting a line whose quoting cannot be parsed leaves the quote glued to the token and the push target reads as `develop"`, matching no branch. The shape does bite one line further out, where a three-line body whose middle line is a bare `git push origin develop` denies on the forced path, and the alternative is worse where it counts: parsing the whole command at once keeps a quoted newline intact and drops every real one, so an ordinary push followed by a `gh pr create` denies under every interpreter rather than under none. - - **Proved against** - `_git_subcommand_arglists` in `host-setup/agent-safety/gh-write-guard.py` and the interpreter floor in `host-setup/agent-safety/install.py`, on `develop` at `dbd1cdc`. - - **Delete when** - the floor drops below 3.6, or the fallback stops splitting the command a line at a time. - - **Earned anyway** - a test case rather than a change. Only `ValueError` from unbalanced quoting reaches that path in practice and nothing covered it, so a finding wrong about its own reachability was right that the path was untested. - - **A description's stale commit claims are found by extracting the bare SHAs it quotes.** Not a reviewer's finding but the method this repository's own backlog specified for the `claims` check in `scripts/pr_review.py`, recorded here because a rejected method costs the same to re-propose as a declined finding costs to re-derive, and because a backlog has a place for a claim the tree contradicts and none for a method a measurement rejects. - **Disproved by** - running it over the 25 most recent merged pull requests, where it raised four references and all four were correct prose: a `develop` commit named as history, a SHA inside a pasted digest, and two commits in another repository written without a URL. Nothing in the shape of a bare SHA separates those from a claim, and separating them by meaning is the similarity heuristic [GOVERNANCE.md "Documentation Style Conventions"](../GOVERNANCE.md#documentation-style-conventions) rules out. A path arm measured on the same corpus is worse, flagging 54 of 215 backticked candidates, nearly all of them bare basenames and other repositories. - **Proved against** - the 25 most recent merged pull requests as of `develop` at `756a53e`, the corpus on which the anchored verb form that ships instead raises one reference, and that one true. diff --git a/.github/skills/python-codestyle/references/code-style.md b/.github/skills/python-codestyle/references/code-style.md index b7e62b00..e8779494 100644 --- a/.github/skills/python-codestyle/references/code-style.md +++ b/.github/skills/python-codestyle/references/code-style.md @@ -44,6 +44,15 @@ - **Use modern syntax**: `list[int]` not `List[int]`, `dict[str, X]` not `Dict[str, X]`, `X | None` not `Optional[X]`, `from __future__ import annotations` only when needed for forward references. +- **Don't hedge that syntax for an older interpreter.** `pyproject.toml` pins `target-version` / + `python_version` to 3.13 for every Python profile in this repo, and `spec/host-tools.json` + carries that as the host floor `scripts/host_gate.py` enforces, so 3.10+-only syntax (`X | None`, + `match`, etc.) needs no quoting, no `typing.Union` fallback, and no `from __future__ import + annotations` guard on that account alone. Add that import only when a real forward reference + needs it, per the bullet above. The floor is repo-wide with two named exceptions that say so + themselves: `scripts/skills_install.sh` and the `install-skills.*` bootstrap scripts, which must + run on whatever interpreter a host already has before this floor's toolchain exists to install + one, and stay compatible back to 3.7 for exactly that reason. - **Don't add `# type: ignore` to silence pyright errors without a comment** explaining the constraint. If a recurring false positive needs suppression, configure it project-wide in `[tool.pyright]`. A new port doesn't change this, fix freshly surfaced type errors rather than diff --git a/repo-config/configure.sh b/repo-config/configure.sh index 5016f373..4fa55150 100755 --- a/repo-config/configure.sh +++ b/repo-config/configure.sh @@ -70,13 +70,15 @@ description="" if [ -f "$registry" ]; then # Resolved here, not near the top, so a run with no registry (an explicit model, no hub checkout) never needs Python at all. # The name python3 is not universal: native Windows can register a Microsoft Store stub under that name that resolves on PATH but fails when actually run, so this runs it rather than just checking PATH (docs/host-setup.md). - # The probe itself is spec/resolve_description.py's actual floor (PEP 563, Python 3.7+) rather than an arbitrary version number, so a too-old interpreter fails here with a clear message instead of a bare SyntaxError from the script. + # The probe checks 3.7+ (PEP 563, from __future__ import annotations) because that is the oldest interpreter resolve_description.py happens to parse and run on, not because 3.7 is this repo's supported floor. + # That floor is 3.13 (spec/host-tools.json's python3 target, enforced on the host by scripts/host_gate.py), and spec/ code is written to it without hedging for anything older. + # A too-old interpreter fails here with a clear message instead of a bare traceback from the script. if python3 -c "from __future__ import annotations" >/dev/null 2>&1; then py_cmd=(python3) elif py -3 -c "from __future__ import annotations" >/dev/null 2>&1; then py_cmd=(py -3) else - echo "No Python 3.7+ interpreter found (python3 or py -3). See docs/host-setup.md." >&2 + echo "No Python interpreter found (python3 or py -3) able to run resolve_description.py. This repo targets Python 3.13; see docs/host-setup.md." >&2 exit 1 fi # Delegates to spec/resolve_description.py rather than a third hand-rolled copy of description_errors(). diff --git a/spec/host-tools.json b/spec/host-tools.json index 6c8e310a..30356d3e 100644 --- a/spec/host-tools.json +++ b/spec/host-tools.json @@ -87,7 +87,7 @@ "probes": [["python3", "--version"], ["py", "-3", "--version"]], "pattern": "Python (\\d+(?:\\.\\d+)*)", "minimum": "3.13", - "why": "Every script here is standard library only, so a bare interpreter is enough and no package floor exists. The floor is the toolchain target rather than a measured breakage one version below it, which is the one entry here that reads that way and says so rather than implying a defect nobody found. pyproject.toml sets ruff target-version to py313 and mypy python_version to 3.13, so what those tools report describes 3.13 and describes no other interpreter, and a run below the floor is unverified rather than known broken. Both tools run in CI through uvx at their latest releases, beside the doc linters, the registry and spec validation, the script self-tests under coverage, and the repo and prose gates. That run judges the code rather than any host, so this floor is still a configuration choice rather than an enforced result, and a host failing it has no CI failure to point at. Two hard requirements are measured, both sit lower, and they fail differently. str.removeprefix and str.removesuffix need 3.9, and each is called where the tree actually calls it: removeprefix in scripts/prose_lint.py and spec/audit.py, removesuffix in spec/audit.py alone. An older interpreter starts, runs, and raises AttributeError when it reaches one. datetime.UTC needs 3.11 and arrives through a module-level from datetime import UTC in spec/audit.py, scripts/pr_review.py and its tests, so an older interpreter raises ImportError before any of those modules run at all. Which mode a host sees is decided by the script it runs rather than by the interpreter alone: spec/audit.py carries both and fails at import, scripts/pr_review.py carries only the import and fails the same way, and scripts/prose_lint.py carries only the call and therefore starts, runs, and fails partway through. The name rather than the version is what differs per platform, which the second probe covers.", + "why": "Every script here is standard library only, so a bare interpreter is enough and no package floor exists. The floor is the toolchain target rather than a measured breakage one version below it, which is the one entry here that reads that way and says so rather than implying a defect nobody found. pyproject.toml sets ruff target-version to py313 and mypy python_version to 3.13, so what those tools report describes 3.13 and describes no other interpreter, and a run below the floor is unverified rather than known broken. Both tools run in CI through uvx at their latest releases, beside the doc linters, the registry and spec validation, the script self-tests under coverage, and the repo and prose gates. That run judges the code rather than any host, so this floor is still a configuration choice rather than an enforced result, and a host failing it has no CI failure to point at. Two hard requirements are measured, both sit lower, and they fail differently. str.removeprefix and str.removesuffix need 3.9, and each is called where the tree actually calls it: removeprefix in scripts/prose_lint.py and spec/audit.py, removesuffix in spec/audit.py alone. An older interpreter starts, runs, and raises AttributeError when it reaches one. datetime.UTC needs 3.11 and arrives through a module-level from datetime import UTC in spec/audit.py, scripts/pr_review.py and its tests, so an older interpreter raises ImportError before any of those modules run at all. Which mode a host sees is decided by the script it runs rather than by the interpreter alone: spec/audit.py carries both and fails at import, scripts/pr_review.py carries only the import and fails the same way, and scripts/prose_lint.py carries only the call and therefore starts, runs, and fails partway through. The name rather than the version is what differs per platform, which the second probe covers. Two scripts outside this gate probe a lower number on purpose rather than by drift: repo-config/configure.sh accepts 3.7+ because that is the oldest interpreter resolve_description.py happens to run on (PEP 563), not a second floor for this repo, and scripts/skills_install.sh / host-setup/*/install-skills.* accept 3.7+ because they must run on whatever a host already has before this floor's own toolchain exists to install one; neither is evidence that spec/ or scripts/ code should hedge for anything older than 3.13.", "source": { "linux": "Whatever the platform provides at or above the floor, since the scripts need an interpreter and no packages, so no distribution or build is pinned here.", "macos": "Whatever the platform provides at or above the floor, on the same reasoning as Linux.", From 19bae1e9315a3c5a294dc899112b01e3d8dade2e Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 22 Aug 2026 14:26:35 -0700 Subject: [PATCH 2/3] Round 1: Name resolve_description.py as a Third 3.7 Bootstrap Exception CodeRabbit (PR #920) is right that the prior wording overclaimed: 'spec/ code is written to 3.13 without hedging for anything older' isn't quite true of spec/resolve_description.py itself, which is what repo-config/configure.sh's own 3.7+ bootstrap probe actually invokes, and which carries from __future__ import annotations for exactly that reason. Name it as a third exception alongside scripts/skills_install.sh and the install-skills.* scripts, in both configure.sh's comment and the python-codestyle skill (regenerated dist copies included). Co-Authored-By: Claude Sonnet 5 --- .agents/skills/python-codestyle/references/code-style.md | 8 ++++++-- .claude-plugin/fleet-skills/.source-digest | 2 +- .../skills/python-codestyle/references/code-style.md | 8 ++++++-- .github/skills/python-codestyle/references/code-style.md | 8 ++++++-- repo-config/configure.sh | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.agents/skills/python-codestyle/references/code-style.md b/.agents/skills/python-codestyle/references/code-style.md index e8779494..9ad17f29 100644 --- a/.agents/skills/python-codestyle/references/code-style.md +++ b/.agents/skills/python-codestyle/references/code-style.md @@ -49,10 +49,14 @@ carries that as the host floor `scripts/host_gate.py` enforces, so 3.10+-only syntax (`X | None`, `match`, etc.) needs no quoting, no `typing.Union` fallback, and no `from __future__ import annotations` guard on that account alone. Add that import only when a real forward reference - needs it, per the bullet above. The floor is repo-wide with two named exceptions that say so + needs it, per the bullet above. Three named exceptions carry a lower floor on purpose and say so themselves: `scripts/skills_install.sh` and the `install-skills.*` bootstrap scripts, which must run on whatever interpreter a host already has before this floor's toolchain exists to install - one, and stay compatible back to 3.7 for exactly that reason. + one, and `spec/resolve_description.py`, which `repo-config/configure.sh`'s own bootstrap probe + accepts down to 3.7 for the same reason, and which carries `from __future__ import annotations` + for exactly that purpose rather than out of habit. No other `spec/` code has a reason to hedge, + so that import or a quoted annotation appearing anywhere else in `spec/` is a sign this one + exception got copied rather than a pattern to follow. - **Don't add `# type: ignore` to silence pyright errors without a comment** explaining the constraint. If a recurring false positive needs suppression, configure it project-wide in `[tool.pyright]`. A new port doesn't change this, fix freshly surfaced type errors rather than diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index b28fbc22..8e8ae0ac 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -f36e72b767cea6c6 +598e6a55149a7fde diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md index e8779494..9ad17f29 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md @@ -49,10 +49,14 @@ carries that as the host floor `scripts/host_gate.py` enforces, so 3.10+-only syntax (`X | None`, `match`, etc.) needs no quoting, no `typing.Union` fallback, and no `from __future__ import annotations` guard on that account alone. Add that import only when a real forward reference - needs it, per the bullet above. The floor is repo-wide with two named exceptions that say so + needs it, per the bullet above. Three named exceptions carry a lower floor on purpose and say so themselves: `scripts/skills_install.sh` and the `install-skills.*` bootstrap scripts, which must run on whatever interpreter a host already has before this floor's toolchain exists to install - one, and stay compatible back to 3.7 for exactly that reason. + one, and `spec/resolve_description.py`, which `repo-config/configure.sh`'s own bootstrap probe + accepts down to 3.7 for the same reason, and which carries `from __future__ import annotations` + for exactly that purpose rather than out of habit. No other `spec/` code has a reason to hedge, + so that import or a quoted annotation appearing anywhere else in `spec/` is a sign this one + exception got copied rather than a pattern to follow. - **Don't add `# type: ignore` to silence pyright errors without a comment** explaining the constraint. If a recurring false positive needs suppression, configure it project-wide in `[tool.pyright]`. A new port doesn't change this, fix freshly surfaced type errors rather than diff --git a/.github/skills/python-codestyle/references/code-style.md b/.github/skills/python-codestyle/references/code-style.md index e8779494..9ad17f29 100644 --- a/.github/skills/python-codestyle/references/code-style.md +++ b/.github/skills/python-codestyle/references/code-style.md @@ -49,10 +49,14 @@ carries that as the host floor `scripts/host_gate.py` enforces, so 3.10+-only syntax (`X | None`, `match`, etc.) needs no quoting, no `typing.Union` fallback, and no `from __future__ import annotations` guard on that account alone. Add that import only when a real forward reference - needs it, per the bullet above. The floor is repo-wide with two named exceptions that say so + needs it, per the bullet above. Three named exceptions carry a lower floor on purpose and say so themselves: `scripts/skills_install.sh` and the `install-skills.*` bootstrap scripts, which must run on whatever interpreter a host already has before this floor's toolchain exists to install - one, and stay compatible back to 3.7 for exactly that reason. + one, and `spec/resolve_description.py`, which `repo-config/configure.sh`'s own bootstrap probe + accepts down to 3.7 for the same reason, and which carries `from __future__ import annotations` + for exactly that purpose rather than out of habit. No other `spec/` code has a reason to hedge, + so that import or a quoted annotation appearing anywhere else in `spec/` is a sign this one + exception got copied rather than a pattern to follow. - **Don't add `# type: ignore` to silence pyright errors without a comment** explaining the constraint. If a recurring false positive needs suppression, configure it project-wide in `[tool.pyright]`. A new port doesn't change this, fix freshly surfaced type errors rather than diff --git a/repo-config/configure.sh b/repo-config/configure.sh index 4fa55150..62d54a90 100755 --- a/repo-config/configure.sh +++ b/repo-config/configure.sh @@ -71,7 +71,7 @@ if [ -f "$registry" ]; then # Resolved here, not near the top, so a run with no registry (an explicit model, no hub checkout) never needs Python at all. # The name python3 is not universal: native Windows can register a Microsoft Store stub under that name that resolves on PATH but fails when actually run, so this runs it rather than just checking PATH (docs/host-setup.md). # The probe checks 3.7+ (PEP 563, from __future__ import annotations) because that is the oldest interpreter resolve_description.py happens to parse and run on, not because 3.7 is this repo's supported floor. - # That floor is 3.13 (spec/host-tools.json's python3 target, enforced on the host by scripts/host_gate.py), and spec/ code is written to it without hedging for anything older. + # That floor is 3.13 (spec/host-tools.json's python3 target, enforced on the host by scripts/host_gate.py); resolve_description.py's own from __future__ import annotations is what lets it stay parseable that low, not a license for spec/ code in general to hedge for anything older. # A too-old interpreter fails here with a clear message instead of a bare traceback from the script. if python3 -c "from __future__ import annotations" >/dev/null 2>&1; then py_cmd=(python3) From 0a272d0ca9e1db0d5feb336cfe54e4c423f940c4 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 22 Aug 2026 14:33:54 -0700 Subject: [PATCH 3/3] Round 2: Say 'The Repository Floor' Instead of 'That Floor' CodeRabbit round 2 on PR #920: 'That floor' in the preceding sentence read as referring back to the probe's 3.7 minimum rather than the 3.13 repository floor the sentence itself states. Name it explicitly, and say 'any other spec/ code' rather than 'spec/ code in general' to scope the no-hedging rule unambiguously around the one named exception. Co-Authored-By: Claude Sonnet 5 --- repo-config/configure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo-config/configure.sh b/repo-config/configure.sh index 62d54a90..2af0f165 100755 --- a/repo-config/configure.sh +++ b/repo-config/configure.sh @@ -71,7 +71,7 @@ if [ -f "$registry" ]; then # Resolved here, not near the top, so a run with no registry (an explicit model, no hub checkout) never needs Python at all. # The name python3 is not universal: native Windows can register a Microsoft Store stub under that name that resolves on PATH but fails when actually run, so this runs it rather than just checking PATH (docs/host-setup.md). # The probe checks 3.7+ (PEP 563, from __future__ import annotations) because that is the oldest interpreter resolve_description.py happens to parse and run on, not because 3.7 is this repo's supported floor. - # That floor is 3.13 (spec/host-tools.json's python3 target, enforced on the host by scripts/host_gate.py); resolve_description.py's own from __future__ import annotations is what lets it stay parseable that low, not a license for spec/ code in general to hedge for anything older. + # The repository floor is 3.13 (spec/host-tools.json's python3 target, enforced on the host by scripts/host_gate.py); resolve_description.py's own from __future__ import annotations is what lets it stay parseable below that, not a license for any other spec/ code to hedge for anything older. # A too-old interpreter fails here with a clear message instead of a bare traceback from the script. if python3 -c "from __future__ import annotations" >/dev/null 2>&1; then py_cmd=(python3)