Why: two remaining hardcoded ports in the test suite were still binding real sockets. test_addr_change_keeps_database_and_auth in tests/test_svc.py used a fixed 18793/18794, and register_agent in tests/test_svc_backup.py defaulted to 8899. Either could fail intermittently when the runner already held the port. They were the same class of issue as the 18951 case closed earlier.
Evidence:
With 18794 occupied by an unrelated process, test_addr_change_keeps_database_and_auth failed with [svc] 端口被占用(非本服务). After the change it passes under the same occupation.
Both files share the test repo: tests/_port_alloc.py already exports free_test_port (each call opens a fresh kernel reservation, no pooling), and tests/test_svc.py has an inline with socket.socket() as r: r.bind(("127.0.0.1", 0)); port = r.getsockname()[1] pattern at line 366 that the changed test now mirrors.
For test_addr_change_keeps_database_and_auth the same port is now used for both the seed and the restart --addr, preserving the test's intent that the second start binds the same port.
For register_agent the default port=8899 is removed; when callers don't pass a port it gets a free one from the helper. Callers that already passed a port keep the existing behaviour. The function only writes stub text into svc.env and does not start a real process, so the usual TOCTOU window is acceptable here.
Remaining, not in this PR (the unit deliberately did not expand its authorized scope):
test_refusals_keep_registration_without_sqlite_fallback (18796 and several others)
These are the same class of issue — same fix, would be a follow-up commit.
Refs: the same governance that produced _port_alloc.py and the 18951 fix; this PR closes the two remaining hardcoded-binding cases that the earlier one missed.
Why: two remaining hardcoded ports in the test suite were still binding real sockets. `test_addr_change_keeps_database_and_auth` in `tests/test_svc.py` used a fixed 18793/18794, and `register_agent` in `tests/test_svc_backup.py` defaulted to 8899. Either could fail intermittently when the runner already held the port. They were the same class of issue as the 18951 case closed earlier.
Evidence:
- With 18794 occupied by an unrelated process, `test_addr_change_keeps_database_and_auth` failed with `[svc] 端口被占用(非本服务)`. After the change it passes under the same occupation.
- Both files share the test repo: `tests/_port_alloc.py` already exports `free_test_port` (each call opens a fresh kernel reservation, no pooling), and `tests/test_svc.py` has an inline `with socket.socket() as r: r.bind(("127.0.0.1", 0)); port = r.getsockname()[1]` pattern at line 366 that the changed test now mirrors.
- For `test_addr_change_keeps_database_and_auth` the same `port` is now used for both the seed and the restart `--addr`, preserving the test's intent that the second start binds the same port.
- For `register_agent` the default `port=8899` is removed; when callers don't pass a port it gets a free one from the helper. Callers that already passed a port keep the existing behaviour. The function only writes stub text into `svc.env` and does not start a real process, so the usual TOCTOU window is acceptable here.
Remaining, not in this PR (the unit deliberately did not expand its authorized scope):
- `test_legacy_exec_dsn_survives_consecutive_restarts` (18790/18791)
- `test_dbref_binding_survives_bare_restart_and_redeploy` (18893/18894)
- `test_refusals_keep_registration_without_sqlite_fallback` (18796 and several others)
These are the same class of issue — same fix, would be a follow-up commit.
Refs: the same governance that produced `_port_alloc.py` and the 18951 fix; this PR closes the two remaining hardcoded-binding cases that the earlier one missed.
Why: establish the authorized task workspace and its initial baseline.
Evidence: preserved precheck receipt in .task/fix-svc-test-port-governance.md; R0=33d6ef69212595c680a744549986c19bb6acd6c3.
Impact: task metadata only; the feature workspace starts at 33d6ef6921.
Refs: .task/fix-svc-test-port-governance.md
Why: include the current integration baseline before verification.
Evidence: fetched origin/main=d65195bd41317e480c5a0cfa5133b0ad98a48d4b.
Impact: integrate upstream changes without rewriting task history.
Refs: task-sync
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why: two remaining hardcoded ports in the test suite were still binding real sockets.
test_addr_change_keeps_database_and_authintests/test_svc.pyused a fixed 18793/18794, andregister_agentintests/test_svc_backup.pydefaulted to 8899. Either could fail intermittently when the runner already held the port. They were the same class of issue as the 18951 case closed earlier.Evidence:
test_addr_change_keeps_database_and_authfailed with[svc] 端口被占用(非本服务). After the change it passes under the same occupation.tests/_port_alloc.pyalready exportsfree_test_port(each call opens a fresh kernel reservation, no pooling), andtests/test_svc.pyhas an inlinewith socket.socket() as r: r.bind(("127.0.0.1", 0)); port = r.getsockname()[1]pattern at line 366 that the changed test now mirrors.test_addr_change_keeps_database_and_auththe sameportis now used for both the seed and the restart--addr, preserving the test's intent that the second start binds the same port.register_agentthe defaultport=8899is removed; when callers don't pass a port it gets a free one from the helper. Callers that already passed a port keep the existing behaviour. The function only writes stub text intosvc.envand does not start a real process, so the usual TOCTOU window is acceptable here.Remaining, not in this PR (the unit deliberately did not expand its authorized scope):
test_legacy_exec_dsn_survives_consecutive_restarts(18790/18791)test_dbref_binding_survives_bare_restart_and_redeploy(18893/18894)test_refusals_keep_registration_without_sqlite_fallback(18796 and several others)These are the same class of issue — same fix, would be a follow-up commit.
Refs: the same governance that produced
_port_alloc.pyand the 18951 fix; this PR closes the two remaining hardcoded-binding cases that the earlier one missed.