# Post-to and post-back in posting invokers

## Two targets

Posting coroutines (`TaggedPostingPromise` / `*ViralPostingInvoker` / `*NonViralPostingInvoker`) use two different `io_context` targets:

| Direction | When | Target |
|---|---|---|
| **Post-TO** | `initial_suspend` | Where the callee coroutine **starts running** |
| **Post-BACK** | `final_suspend` | Where the caller resumes after `co_await` |

Post-back always uses `callerIoContext`, initialized from `ComponentThread::getSelf()->getIoContext()` at the `co_await` site. That behavior is unchanged.

Post-to defaults to the static `ThreadTag::io_context()` for the invoker alias (e.g. `MrnttThreadTag`, `BodyThreadTag`).

## Static post-TO (default)

Existing coroutines need no signature change. `TaggedPostingPromise::initial_suspend()` posts the callee handle to `ThreadTag::io_context()`.

## Dynamic post-TO (`ExplicitPostTarget`)

Opt in by adding `sscl::co::ExplicitPostTarget` to the coroutine parameter list. The promise constructor extracts it into `std::optional<ExplicitPostTarget> calleePostTarget`; `initial_suspend()` posts to `calleePostTarget->ioContext` instead of `ThreadTag::io_context()`.

```cpp
// Viral member example: run on body thread, post back to mrntt caller
BodyViralPostingInvoker<bool> Body::initializeCReq(
    sscl::co::ExplicitPostTarget postTarget)
{
    co_await ...;
}

// Non-viral example
reattachKnownListCReq(
    sscl::co::ExplicitPostTarget{someIoContext},
    exceptionPtr,
    callback);
```

The caller must ensure the referenced `io_context` outlives the callee frame.

## DeviceManager attach/detach (mrntt orchestrator, library on body/world)

`DeviceManager::attachStimBuffDeviceCReq` and `detachStimBuffDeviceCReq` stay
`MrnttViralPostingInvoker` on the marionette thread. They `co_await` library
`attachDeviceCReq` / `detachDeviceCReq` (`DynamicViralPostingInvoker`) with
`ExplicitPostTarget` first:

- **edev** (`sensorType == 'e'`) → world thread
- **otherwise** → body thread

Post-TO runs the library coroutine on that target; post-back resumes the
DeviceManager coroutine on mrntt. Attach also passes `componentThread` for
device/producer binding inside the library.

```cpp
auto targetThread = threadForDeviceOp(*spec);
co_return co_await lib.stimBuffApiDesc.sal_mgmt_libOps.attachDeviceCReq(
    sscl::co::ExplicitPostTarget{targetThread->getIoContext()},
    spec,
    targetThread);
```

Detach uses the same `threadForDeviceOp` rule (not producer lookup).

## Constructor discrimination

`PostingPromise` mirrors the existing implicit-`this` peel:

- Member coroutines pass the object parameter first; it is discarded.
- When present, `ExplicitPostTarget` is the next parameter (before non-viral `exception_ptr&` and completion callback).
- Free non-viral dynamic: `ExplicitPostTarget`, then `exception_ptr&`, then callback.
- `ExplicitPostTarget` is recognized by type (`is_explicit_post_target_v`) and stored in `calleePostTarget`.
- `ObjectArg` requires `!is_explicit_post_target_v` so the wrapper is never mistaken for `thisptr`.

## Timing invariant

Post-TO and post-BACK work must run from `InitialSuspendPostingInvoker::await_suspend` and `FinalSuspendPostingInvoker::await_suspend`, not synchronously inside `initial_suspend()` / `final_suspend()` bodies. See comments in `postingPromise.h`.
