feat(delivery): final report + reserved-label guard + Windows test fixes (v2 phase A3-final)

Gap-closing on top of the merged A3-3..A3-6 work:

- ProjectService.create_preview_domain now refuses a label that is a reserved
  platform name (git, mcp, api, panel, ...) via PROTECTED_HOST_LABELS. The rules
  set existed but only the owner-facing validate_extra_host consulted it, so the
  tenant path could reserve one.
- process.py: signal.SIGKILL is resolved through getattr with a SIGTERM
  fallback, so the module imports and type-checks on Windows where SIGKILL does
  not exist.
- Three more Windows test repairs: a git-created .git must be unlinked and
  recreated rather than overwritten in place; POSIX-only chmod and symlink
  assertions are gated on os.name (the security property they assert is checked
  everywhere, the platform-specific mechanics only where they apply).
- docs/plan/reports/agent-3-final.md: the integration hand-off — host
  requirements, every setting, every endpoint added or changed, the tables, the
  open CHANGE REQUESTS, a 12-step manual QA script and the known limitations.

Checks: ruff clean, mypy clean (136 files), 521 API tests pass, 83 web tests
pass, tsc clean, next lint clean.
This commit is contained in:
Agent 3
2026-10-14 12:07:10 +03:30
parent 5095a29df9
commit 22374f3e1c
5 changed files with 252 additions and 3 deletions
+5
View File
@@ -142,6 +142,11 @@ def test_safe_join_refuses_a_parent_escape(tmp_path):
safe_join(str(root), "a/b/../../../etc/passwd")
@pytest.mark.skipif(
os.name == "nt",
reason="creating a symlink on Windows requires SeCreateSymbolicLinkPrivilege "
"(admin or Developer Mode); the path-escape guard is covered on POSIX hosts",
)
def test_safe_join_refuses_a_symlink_that_escapes(tmp_path):
"""A symlink planted inside the work dir must not become a host read."""
root = tmp_path / "work"