# DualCoach Task 17 Gateway Lifecycle Evidence

## Verdict

**PARTIALLY VERIFIED**. The authoritative raw-byte candidate has **89 paths** and digest:

```text
7a977121142e0a6d39e51e8717f8b0270c2d02f705e5a56603de68529e5781f2
```

Focused lifecycle remediation is clean. Two independent baseline gates remain: optional Discord plugin import makes one broad lifecycle test fail, and the full exact Task17 type-check surface has existing diagnostics. Neither is masked.

## Verification receipts

- Strict gateway/profile lifecycle batch: **338 passed, 0 failed**.
- Strict service/platform lifecycle batch: **297 passed, 1 failed**: `test_gateway_platform_gating.py::TestMatrixHiddenOnWindows::test_other_platforms_unaffected_on_windows` requires the unavailable optional Discord plugin.
- Strict post-cleanup status/profile batch: **214 passed, 0 failed**.
- New profile/status/test-infrastructure `ty` slice: **0 diagnostics**. Full exact Task17 modified-path `ty`: **208 diagnostics**, exit 1; no suppression applied.
- `ruff`, `compileall`, `git diff --check`, gateway help, and invalid-subcommand exit-2 checks passed.

## Stable-handle evidence

- Profile shutdown opens exactly one expected-home verified handle and performs TERM, wait, revalidation, optional KILL, and close through it.
- A mismatched profile PID authority is refused; an identity swap before escalation receives no signal.
- Linux uses pidfd; macOS/Windows revalidate a `psutil.Process` identity; systemd never signals an unverified `MainPID`.

## Task17 file hashes

| Path | SHA-256 |
|---|---|
| `gateway/run.py` | `0882db037b1614b6446104811bad5f338f2c965ef18d6c228aab49d6833f649f` |
| `gateway/status.py` | `7a04d81facfd29360a096c1233d2afd015e1933c223adcbf7946c087ca9c5115` |
| `hermes_cli/gateway.py` | `600f140534bba60d24001a14fb0ae6ae524ea37bec0a76133aa494151e5fdffb` |
| `hermes_cli/gateway_windows.py` | `ca10abc46df34b8b3871da09683c2f279ffe289f9858fd8329550b18ccdf04b3` |
| `hermes_cli/profiles.py` | `a7b2feab598f5556a2ca634a35db37480bcb8a5fc851ab9245cb7e22e08fa2d2` |
| `pyproject.toml` | `cdf5bbe2727d3e96185909424c14379692952ac5918b5377baf151998c646c3c` |
| `tests/conftest.py` | `3fc3fbf9991f24884711e1fb48f73e177de86f4338c0b858db17d2b24d67a3e0` |
| `tests/gateway/test_gateway_shutdown.py` | `5ef4d32571ff2ffd9baa0182ee7c3721263586db0868a4bfb7cfe420da1e4c51` |
| `tests/gateway/test_planned_stop_watcher.py` | `f61452347b6e164b469a9a3dd3df685ed19774a93838fe6672ece6f45249e55b` |
| `tests/gateway/test_platform_reconnect.py` | `67df37df5a9d41f6c15cf0a57577ad700c9d4bc62b515ded5c44778eb32f72fb` |
| `tests/gateway/test_runner_fatal_adapter.py` | `45dbe1a562e22fb60b84265ab57d563b1e8e52f3b3fa9a8d65bd0387ced34816` |
| `tests/gateway/test_runner_startup_failures.py` | `5cf6c9993bb57c28c248a4773fbbd269d2fbe5007b6575855d736e7ee83f9933` |
| `tests/gateway/test_status.py` | `1976b3530484b105704fdf2bcf1c6302ec7416fcec33f675e515177ef7447c5d` |
| `tests/hermes_cli/test_gateway.py` | `86f6fc51796a230695dd1975c7bafa75a8212bd363be9c499596923a6b5c232a` |
| `tests/hermes_cli/test_gateway_runtime_health.py` | `3ee7bdbea3127bc07309dfd17881ae1a1f9dc9cd91facdc6f03b903a546c04df` |
| `tests/hermes_cli/test_gateway_service.py` | `7ad3e43e6f46cc34b1b07da4cb189b5c59d7f2e506599c79a81cf4055f078330` |
| `tests/hermes_cli/test_gateway_windows.py` | `69e5258740ee06b3441c38e891a055242abce7ae8db4b8f57285f4d8f7ff0716` |
| `tests/hermes_cli/test_profiles.py` | `08514d893d4dd85142b28e03e9b10fc6b66bfb387454a749a73abd521cf6b398` |

## Scope

Only Task17 product, tests, test configuration, and authoritative provenance artifacts are represented. No ledger, plan checkbox, commit, or unrelated worktree path was changed.

## Independent final closure

Automated verifier `st_019fe4c9` confirmed the authoritative 89-path digest and:

- 487 strict lifecycle/status/profile tests passed;
- 18 Telegram/polling/group/shutdown tests passed;
- the Task17-added type slice has zero diagnostics;
- Ruff, compileall, and `git diff --check` passed.

Its remaining optional Discord/Python 3.13 import failure is outside every Task17 hunk
and is retained as broad candidate debt.

Manual verifier `st_019fe4ca`: CONFIRMED.

- Actual CLI help and invalid input behaved correctly.
- Local fake-Telegram startup issued `getMe`, `deleteWebhook`, and `getUpdates`, then
  remained running as the expected daemon.
- Connected, degraded, reconnect, graceful stop, SIGTERM, SIGINT, and SIGKILL paths
  matched actual PID and connection state.
- First status after a conclusively dead SIGKILL process persisted
  `gateway_state=stopped` and `exit_reason=unexpected_exit`; the second status was
  idempotent.
- Unreadable process identity remained degraded/unknown and preserved authority files.
- PID-swap, cross-profile, and wrong-identity probes never signaled the unrelated
  task-owned child.
- Persistent `gateway.lock` and `kanban/.dispatcher.lock` path entries were
  nonblocking-reacquirable after exit; no process held them and a second same-home
  start succeeded.

Security/liveness reviewer `st_019fe4cb`: CONFIRMED.

- Shutdown no longer self-cancels or recursively cancels its waiter.
- Linux pidfd, macOS/Windows validated process handles, systemd, launchd, profile
  cleanup, wait, TERM, and KILL paths retain one stable process identity.
- The final raw-PID profile cleanup race is closed.
- Dead and UNKNOWN process identities reconcile without deleting live/unverifiable
  metadata.
- PID, lock, runtime, planned-stop, and takeover authority files remain no-follow,
  owner-only, regular, single-link, and fail-closed.

## Final cleanup

- Task-owned CLI children and temporary homes were removed.
- `/tmp/hermes-build-help.txt` and all Task17 build directories were removed and
  verified absent.
- No real Telegram, provider, customer, activation, or delivery action occurred;
  Telegram verification used an isolated localhost fake endpoint.
- No commit, push, reset, stash, clean, or unrelated deletion occurred.
