<!-- task=m-node-provider-execution-liveness-recovery/01_activity_contract plan=1 tag=API milestone-task=activity-contract -->

# PLAN — Node Provider Activity Contract

## For the Implementing Agent

> **MANDATORY:** Implement only the checklist below, preserve unrelated user changes, and keep every edit inside the `activity-contract` slice. Do not update roadmap state, create follow-up plans, commit, push, or run an official code review. After implementation, fill every implementation-owned section of `CODE_REVIEW-cloud-G06.md` and leave both active files in place for the review agent.

## Background

The approved liveness SDD requires one provider-originated activity contract for normalized `RuntimeEvent` and raw `ProviderTunnelFrame`, plus a provider-level `response_stall_timeout_ms` that reaches Node for every provider-first and legacy route. Current runtime types expose events and frames but no shared activity classifier, while provider-pool candidate resolution and the normalized/tunnel request wire do not carry the selected provider's timeout. A static adapter-level value would be incorrect because multiple provider resources may share one legacy adapter with different overrides. This slice therefore resolves the setting on the selected provider candidate and carries it on each immutable request; direct/legacy requests use the Node-side default. It establishes the contract without starting a timer or changing execution lifecycle.

The user supplied starting reference is `95a81ca65fdd24733ec06e4191551dd547c5902e`. Planning was performed against the current branch after its approved SDD/roadmap updates, with a clean worktree and passing targeted Go/race baselines.

This replan incorporates the explicit pre-implementation self-review. The original pair was structurally valid, but its verification set regenerated checked-in Flutter protobuf bindings without running the client domain's mandatory test target. No implementation had started, so the runtime contract and file scope remain unchanged while the missing client verification is added.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-node-provider-execution-liveness-recovery/01_activity_contract/plan_local_G06_0.log`.
- Prior review stub: `agent-task/m-node-provider-execution-liveness-recovery/01_activity_contract/code_review_cloud_G06_0.log`.
- Prior verdict: none; implementation and implementation-owned evidence had not started.
- Required carryover: regenerate Go/Dart bindings as planned and also run `make client-test` because the checked-in Flutter binding surface changes.

## Analysis

### Files Read

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/client/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/client-smoke.md`
- `agent-roadmap/phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md`
- `agent-roadmap/sdd/operational-observability-provider-management/node-provider-execution-liveness-recovery/SDD.md`
- `agent-spec/index.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `agent-contract/index.md`
- `agent-contract/inner/execution-runtime.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `packages/go/execution/types.go`
- `packages/go/execution/failure.go`
- `packages/go/config/provider_types.go`
- `proto/iop/runtime.proto`
- `makefile`
- `apps/edge/internal/node/mapper.go`
- `apps/edge/internal/configrefresh/classify.go`
- `apps/node/internal/adapters/config_set.go`
- `apps/edge/internal/service/model_queue_types.go`
- `apps/edge/internal/service/provider_resolution.go`
- `apps/edge/internal/service/provider_pool.go`
- `apps/edge/internal/service/run_types.go`
- `apps/edge/internal/service/run_wire.go`
- `apps/edge/internal/service/provider_tunnel.go`
- `apps/node/internal/node/runtime_bridge.go`
- `apps/node/internal/router/router.go`
- `apps/node/internal/node/tunnel_handler.go`
- `configs/edge.yaml`
- `packages/go/config/provider_catalog_validation_config_test.go`
- `apps/edge/internal/service/provider_scheduling_advanced_test.go`
- `apps/edge/internal/service/run_command_test.go`
- `apps/edge/internal/service/run_dispatch_internal_test.go`
- `apps/node/internal/node/runtime_bridge_test.go`
- `apps/node/internal/router/router_test.go`
- `apps/node/internal/node/provider_tunnel_test.go`
- `apps/edge/internal/configrefresh/provider_classify_test.go`

### SDD Criteria

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/node-provider-execution-liveness-recovery/SDD.md` (`승인됨`, implementation lock released).
- Decision basis: D01 is resolved; this slice does not add Edge runtime health overlay ownership.
- Scenario: S01 / milestone task `activity-contract` (`SDD.md:92`).
- Evidence row: S01 requires config validation and fake-clock-ready normalized/tunnel activity, deadline, and transport assertions (`SDD.md:103`). This slice supplies config and pure activity evidence; the dependent watchdog plan supplies clock/deadline/transport lifecycle evidence.
- Contract requirements: default/zero `300000`, positive override, negative error, legacy default, restart-required refresh (`SDD.md:67`); normalized start-point/progress/terminal semantics (`SDD.md:70`); tunnel response-start/header/body/usage and terminal semantics (`SDD.md:71`).

### Verification Context

- Environment: local Go 1.26.2, module `/config/workspace/iop-s1/go.mod`.
- Required generators are present: `protoc`, `protoc-gen-go`, and `protoc-gen-dart`; `make -n proto` and `make -n proto-dart` resolve successfully.
- The client domain owns the checked-in Dart binding output and requires `make client-test` after that output changes.
- Baseline passed:
  - `go test -count=1 ./packages/go/execution ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/configrefresh ./apps/node/internal/adapters`
  - `go test -race -count=1 ./packages/go/execution ./apps/node/internal/node ./apps/node/internal/transport`
- No external provider, secret, deployment, migration, or field host is required.

### Test Coverage Gaps

- There is no table test that defines provider activity consistently across normalized and tunnel types.
- Provider config tests do not cover stall-timeout default/override/negative semantics.
- Provider-pool candidate and dispatch tests do not prove that the winning provider's effective value survives queue re-resolution and reaches normalized/tunnel wire requests when providers share an adapter.
- Direct/legacy request builders and Node runtime mappers do not prove that wire zero becomes the default without mutating request hard timeout.
- Refresh tests do not classify this field as `restart_required`.
- Generated Go/Dart bindings cannot carry the field yet.
- The original verification list regenerated Dart bindings but omitted the client test target required for changes under `apps/client`.

### Symbol References

- `packages/go/execution/types.go:31-54` — normalized event kinds and payload.
- `packages/go/execution/types.go:228-253` — tunnel frame kinds and payload.
- `packages/go/config/provider_types.go:91-98,100-128` — provider-first execution fields and validation.
- `proto/iop/runtime.proto:53-83,99-132` — tunnel and normalized request wire schemas.
- `apps/edge/internal/service/model_queue_types.go:71-106` — selected provider candidate snapshot.
- `apps/edge/internal/service/provider_resolution.go:278-298,381-480` — initial and queued provider dispatch facts.
- `apps/edge/internal/service/run_wire.go:37-68` and `provider_tunnel.go:502-537` — normalized/tunnel request construction.
- `apps/node/internal/node/runtime_bridge.go:8-21` and `apps/node/internal/router/router.go:35-55` — wire-to-runtime normalized propagation.
- `apps/node/internal/node/tunnel_handler.go:25-39` — wire-to-runtime tunnel propagation.
- `apps/edge/internal/configrefresh/classify.go:89-133,274-281` — provider snapshot and restart-required comparisons.

### Split Judgment

- Classification: large. The slice changes config and protobuf wire contracts and generated bindings, so it cannot be direct-small even though the runtime classifier itself is pure.
- Cohesion: the setting and activity classifier must land together because the watchdog needs one effective timeout and one source of truth for reset/terminal decisions.
- Dependency: none. This is the foundation for `02+01_stall_watchdog`.
- Collision check: no active PLAN/CODE_REVIEW claims the target task ids or listed files at plan creation.

### Scope Rationale

- In scope: activity semantics, config schema/default/validation, selected-candidate propagation on both request variants, Node runtime retention, refresh classification, generated bindings, tests, matching specs, and inner contracts.
- Out of scope: timers, cancellation, terminal synthesis, attempt fencing, health probes, observation sequence, Edge health overlay, recovery/retry, and operational metrics.
- New files are limited to the shared classifier and its focused test; existing config/mapping test files are extended instead of creating parallel suites.

### Final Routing

- `evaluation_mode=isolated-reassessment`; `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`.
- Build closure: scope/context/verification/evidence/ownership/decision all closed and trusted; capability gap none.
- Build score: `scope=2`, `state=0`, `blast=2`, `evidence=1`, `verification=1` -> G06; `base_route_basis=local-fit`, `route_basis=local-fit`, lane `local`, file `PLAN-local-G06.md`.
- Build signals: `large_indivisible_context=false`, positive loop risk `boundary_contract` (`count=1`), `review_rework_count=0`, `evidence_integrity_failure=false`; risk/recovery boundary not matched.
- Review closure: scope/context/verification/evidence/ownership/decision all closed and trusted; capability gap none.
- Review score: `scope=2`, `state=0`, `blast=2`, `evidence=1`, `verification=1` -> G06; `route_basis=official-review`, lane `cloud`, adapter/model `codex/gpt-5.6-sol`, reasoning `xhigh`, file `CODE_REVIEW-cloud-G06.md`.

## Implementation Checklist

- [ ] [API-1] Define the effective response-stall timeout and the shared normalized/tunnel provider-activity contract.
- [ ] [API-2] Propagate `response_stall_timeout_ms` through provider-pool candidate resolution, normalized/tunnel wire requests, Node runtime types, and refresh classification.
- [ ] [TEST-1] Add deterministic contract/config/mapping tests and regenerate checked-in Go/Dart bindings.
- [ ] [DOC-1] Update the three matching inner contracts and the provider-first example without claiming watchdog behavior.
- [ ] Run every command in Final Verification and record exact results in `CODE_REVIEW-cloud-G06.md`.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [API-1] Define the effective timeout and provider-activity contract

**Problem**

`RuntimeEvent` and `ProviderTunnelFrame` expose provider output but have no single progress/terminal classifier (`packages/go/execution/types.go:31-54,228-253`). A watchdog implemented directly in handlers would duplicate subtly different rules.

**Solution**

Add `packages/go/execution/liveness.go` with:

- `DefaultResponseStallTimeoutMS = 300000` and an effective-value helper that maps `0` to the default, passes positive values, and does not silently accept negatives.
- A small `ProviderActivityDisposition` enum (`none`, `start`, `progress`, `terminal`) and pure classifiers for `RuntimeEvent` and `ProviderTunnelFrame`. `start` lets the observer establish its initial baseline without conflating that transition with later progress resets.
- Normalized rules: `start` is the start disposition; non-empty `delta`/`reasoning_delta` and non-terminal usage are progress; complete/error/cancelled are terminal before any usage check; empty/unknown events are none.
- Tunnel rules: response-start (including headers), non-empty body, and usage are progress; end/error are terminal before payload checks; empty/unknown frames are none.

Before: handlers would need to switch independently on event/frame kinds. After: all later timers consume the same pure disposition and cannot treat heartbeat/socket/process activity as provider progress because those signals never enter these classifiers.

**Modified files**

- [ ] `packages/go/execution/liveness.go`
- [ ] `packages/go/execution/liveness_test.go`

**Test decision**

Required. Use table tests for every event/frame kind, non-empty versus empty payloads, usage, terminal-with-payload precedence, and unknown values. The tests must use no wall-clock sleep.

**Verification**

- `go test -count=1 ./packages/go/execution`
- `go test -race -count=1 ./packages/go/execution`

### [API-2] Carry the selected provider timeout on each request

**Problem**

`NodeProviderConf` ends at `request_timeout_ms` (`packages/go/config/provider_types.go:91-98`). Provider-pool candidate resolution selects a provider id independently from its adapter key, but `RunRequest` and `ProviderTunnelRequest` carry only adapter/target/timeouts unrelated to liveness. The watchdog therefore cannot distinguish different provider overrides when multiple resources share one legacy adapter.

**Solution**

- Add `ResponseStallTimeoutMS int` to `NodeProviderConf` with `mapstructure/yaml:"response_stall_timeout_ms"`, reject negative values in `Validate`, and expose an effective helper using the shared default.
- Add additive, never-reused `int32 response_stall_timeout_ms` fields to both protobuf request messages. Regenerate Go and Dart outputs through repository Make targets; do not edit generated files manually.
- Extend `candidateNode` with the effective timeout and populate it in `applyProviderDispatchFields`, which is shared by initial resolution and queued re-resolution. Copy it into normalized and tunnel submit DTOs immediately after admission and before request construction. Do not derive it from adapter key or target, and do not expose mutable config pointers.
- Extend `SubmitRunRequest`, `SubmitProviderTunnelRequest`, and `RunDispatch` so the selected immutable value can be built, reported, and tested on both surfaces. Direct/non-pool calls that do not name a provider carry zero on the wire and therefore use the documented default; they do not acquire a synthetic provider identity.
- Extend host-neutral `RunRequest`, `ExecutionSpec`, and `ProviderTunnelRequest`, plus Node wire bridges/router, with the effective value. Normalize zero to `300000` at the Node boundary. If an unexpected negative wire value reaches Node, reject the request before router/provider invocation rather than disabling the observer or silently defaulting it.
- Extend the config-refresh provider snapshot and comparison so `nodes[].providers[...].response_stall_timeout_ms` is `restart_required`, using effective values so omitted and explicit zero compare equal.

Before: the field is absent at every boundary. After: every dispatched attempt owns the selected provider's immutable positive timeout, including two providers that share an adapter but use different values.

**Modified files**

- [ ] `packages/go/config/provider_types.go`
- [ ] `packages/go/execution/types.go`
- [ ] `proto/iop/runtime.proto`
- [ ] `proto/gen/iop/runtime.pb.go`
- [ ] `apps/client/lib/gen/proto/iop/runtime.pb.dart`
- [ ] `apps/client/lib/gen/proto/iop/runtime.pbjson.dart`
- [ ] `apps/edge/internal/service/model_queue_types.go`
- [ ] `apps/edge/internal/service/provider_resolution.go`
- [ ] `apps/edge/internal/service/provider_pool.go`
- [ ] `apps/edge/internal/service/run_types.go`
- [ ] `apps/edge/internal/service/run_wire.go`
- [ ] `apps/edge/internal/service/provider_tunnel.go`
- [ ] `apps/node/internal/node/runtime_bridge.go`
- [ ] `apps/node/internal/router/router.go`
- [ ] `apps/node/internal/node/tunnel_handler.go`
- [ ] `apps/edge/internal/configrefresh/classify.go`

**Test decision**

Required because this changes config and wire behavior. Cover omitted, explicit zero, positive override, negative rejection, immediate and queued provider-pool dispatch, normalized and tunnel paths, two providers sharing one adapter with different values, direct legacy default, and timeout-only restart-required refresh.

**Verification**

- `make proto`
- `make proto-dart`
- `make client-test`
- `go test -count=1 ./packages/go/config ./apps/edge/internal/service ./apps/edge/internal/configrefresh ./apps/node/internal/node ./apps/node/internal/router`

### [TEST-1] Lock generated and mapping behavior

**Problem**

Existing tests cover adjacent request/queue fields but not this generic liveness value, and a generated binding drift could compile only one client surface.

**Solution**

Extend the closest existing tests with compact tables:

- config validation/effective-value cases;
- selected candidate, queue re-resolution, normalized/tunnel request round-trip, shared-adapter/different-timeout assertions;
- Node wire bridge/router/tunnel domain propagation and direct legacy default assertions;
- refresh classification/effective-zero assertions;
- Go protobuf round-trip assertion for the new field.

Run both generators and the client test target, then use `git diff --check`; never hand-edit generated code. Do not add fake timers here—the dependent watchdog plan owns time behavior.

**Modified files**

- [ ] `packages/go/config/provider_catalog_validation_config_test.go`
- [ ] `apps/edge/internal/service/provider_scheduling_advanced_test.go`
- [ ] `apps/edge/internal/service/run_command_test.go`
- [ ] `apps/edge/internal/service/run_dispatch_internal_test.go`
- [ ] `apps/node/internal/node/runtime_bridge_test.go`
- [ ] `apps/node/internal/router/router_test.go`
- [ ] `apps/node/internal/node/provider_tunnel_test.go`
- [ ] `apps/edge/internal/configrefresh/provider_classify_test.go`

**Test decision**

Required; all fixtures are deterministic and local.

**Verification**

- `go test -count=1 ./packages/go/execution ./packages/go/config ./apps/edge/internal/service ./apps/edge/internal/configrefresh ./apps/node/internal/node ./apps/node/internal/router`
- `go test -race -count=1 ./packages/go/execution`

### [DOC-1] Synchronize contracts and example

**Problem**

The matching contracts currently describe execution events, Edge-to-Node adapter payloads, and restart-required provider fields without the new timeout/activity rules.

**Solution**

Update the contracts in the same change as implementation:

- execution runtime: effective default and exact activity/terminal classifier semantics;
- Edge-Node wire: per-attempt `RunRequest`/`ProviderTunnelRequest.response_stall_timeout_ms` propagation and mixed-version/default behavior;
- Edge config/refresh: schema, zero/default equivalence, negative rejection, and restart-required classification.

Add one provider-first example field to `configs/edge.yaml`. State explicitly that request hard timeout, queue timeout, heartbeat/disconnect, and CLI `response_idle_timeout_ms` retain their existing ownership. Do not document timers, health classification, retry, or Edge overlay as implemented by this slice.

**Modified files**

- [ ] `agent-contract/inner/execution-runtime.md`
- [ ] `agent-contract/inner/edge-node-runtime-wire.md`
- [ ] `agent-contract/inner/edge-config-runtime-refresh.md`
- [ ] `agent-spec/runtime/edge-node-execution.md`
- [ ] `agent-spec/runtime/provider-pool-config-refresh.md`
- [ ] `configs/edge.yaml`

**Test decision**

No separate doc test. Contract accuracy is checked against the schema/mapping tests and diff.

**Verification**

- `git diff --check`

## Modified Files Summary

| Path | Action | Checklist |
|------|--------|-----------|
| `packages/go/execution/liveness.go` | add | API-1 |
| `packages/go/execution/liveness_test.go` | add | API-1 |
| `packages/go/config/provider_types.go` | modify | API-2 |
| `packages/go/execution/types.go` | modify | API-2 |
| `proto/iop/runtime.proto` | modify | API-2 |
| `proto/gen/iop/runtime.pb.go` | regenerate | API-2 |
| `apps/client/lib/gen/proto/iop/runtime.pb.dart` | regenerate | API-2 |
| `apps/client/lib/gen/proto/iop/runtime.pbjson.dart` | regenerate | API-2 |
| `apps/edge/internal/service/model_queue_types.go` | modify | API-2 |
| `apps/edge/internal/service/provider_resolution.go` | modify | API-2 |
| `apps/edge/internal/service/provider_pool.go` | modify | API-2 |
| `apps/edge/internal/service/run_types.go` | modify | API-2 |
| `apps/edge/internal/service/run_wire.go` | modify | API-2 |
| `apps/edge/internal/service/provider_tunnel.go` | modify | API-2 |
| `apps/node/internal/node/runtime_bridge.go` | modify | API-2 |
| `apps/node/internal/router/router.go` | modify | API-2 |
| `apps/node/internal/node/tunnel_handler.go` | modify | API-2 |
| `apps/edge/internal/configrefresh/classify.go` | modify | API-2 |
| `packages/go/config/provider_catalog_validation_config_test.go` | modify | TEST-1 |
| `apps/edge/internal/service/provider_scheduling_advanced_test.go` | modify | TEST-1 |
| `apps/edge/internal/service/run_command_test.go` | modify | TEST-1 |
| `apps/edge/internal/service/run_dispatch_internal_test.go` | modify | TEST-1 |
| `apps/node/internal/node/runtime_bridge_test.go` | modify | TEST-1 |
| `apps/node/internal/router/router_test.go` | modify | TEST-1 |
| `apps/node/internal/node/provider_tunnel_test.go` | modify | TEST-1 |
| `apps/edge/internal/configrefresh/provider_classify_test.go` | modify | TEST-1 |
| `agent-contract/inner/execution-runtime.md` | modify | DOC-1 |
| `agent-contract/inner/edge-node-runtime-wire.md` | modify | DOC-1 |
| `agent-contract/inner/edge-config-runtime-refresh.md` | modify | DOC-1 |
| `agent-spec/runtime/edge-node-execution.md` | modify | DOC-1 |
| `agent-spec/runtime/provider-pool-config-refresh.md` | modify | DOC-1 |
| `configs/edge.yaml` | modify | DOC-1 |
| `agent-task/m-node-provider-execution-liveness-recovery/01_activity_contract/CODE_REVIEW-cloud-G06.md` | update evidence | all |

## Final Verification

1. `make proto`
2. `make proto-dart`
3. `make client-test`
4. `go test -count=1 ./packages/go/execution ./packages/go/config ./apps/edge/internal/service ./apps/edge/internal/configrefresh ./apps/node/internal/node ./apps/node/internal/router`
5. `go test -race -count=1 ./packages/go/execution`
6. `go test -count=1 ./...`
7. `make readability-audit`
8. `git diff --check`

Record command, exit status, and concise output in the review stub. If a generator changes any file not listed in Modified Files Summary, stop and reconcile the plan through the owning runtime instead of silently expanding scope.

**After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.**
