# Assistant Caller Contract

Status: first UI/caller contract.
Date: 2026-06-19

This contract sits above the low-level execution gateway. UI and assistant surfaces should call this layer instead of calling the executor directly.

## Phases

`preview`

- resolves the requested action;
- runs policy and route safety checks;
- does not make network requests;
- returns a compact preview for UI;
- returns a `confirmation.token` for write actions.

`execute`

- requires the same action input;
- requires `confirmationToken` for write actions;
- requires Launcher internal token for HUB gateway execution;
- returns a normalized execution result.

## Preview Request

```json
{
  "phase": "preview",
  "input": {
    "actionId": "hub.user.block",
    "assistantRole": "admin",
    "membershipRole": "client_admin",
    "actorUserId": "user_root",
    "targetUserId": "user_silver_psih",
    "confirmed": true,
    "idempotencyKey": "ui:block:user_silver_psih:2026-06-19"
  }
}
```

CLI:

```text
npm run assistant:caller -- --input-json examples/assistant-caller-preview-block-user.example.json
```

## Preview Response

Write preview returns:

```json
{
  "ok": true,
  "phase": "preview",
  "decision": "preview_ready",
  "preview": {
    "app": "hub",
    "actionId": "hub.user.block",
    "riskLevel": "privileged",
    "expectedEffect": "Block HUB user account through existing Launcher admin guards.",
    "request": {
      "method": "PATCH",
      "path": "/api/admin/users/user_silver_psih/profile",
      "body": {
        "globalStatus": "blocked"
      }
    }
  },
  "confirmation": {
    "token": "<preview token>",
    "request": {
      "method": "PATCH",
      "path": "/api/admin/users/user_silver_psih/profile",
      "idempotencyKey": "ui:block:user_silver_psih:2026-06-19"
    }
  },
  "execution": {
    "requiresUserConfirmation": true,
    "requiresInternalToken": true
  }
}
```

The UI must show the exact app/action/target/effect before execute. It must not silently execute privileged write previews.

## Execute Request

```json
{
  "phase": "execute",
  "confirmationToken": "<preview token>",
  "input": {
    "actionId": "hub.user.block",
    "assistantRole": "admin",
    "membershipRole": "client_admin",
    "actorUserId": "user_root",
    "targetUserId": "user_silver_psih",
    "confirmed": true,
    "idempotencyKey": "ui:block:user_silver_psih:2026-06-19"
  }
}
```

CLI:

```text
NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN=... npm run assistant:caller -- --base-url http://launcher.local.nodedc --input-json request.json
```

## Execute Response

Successful execution returns:

```json
{
  "ok": true,
  "phase": "execute",
  "decision": "executed",
  "result": {
    "status": 200,
    "method": "PATCH"
  }
}
```

Blocked execution returns:

```json
{
  "ok": false,
  "phase": "execute",
  "decision": "execution_blocked",
  "reason": "write_confirmation_envelope_missing"
}
```

## Rules For Callers

- Never call `execute` for write actions without first showing `preview`.
- Never generate or modify `confirmationToken` in UI.
- Never reuse a token after changing action input, target, body, idempotency key, or actor.
- Never map delete requests to hidden alternatives. Show the refusal and safe alternatives from preview.
- Keep app ownership visible: HUB actions mutate HUB/Launcher only; OPS and ENGINE actions require their own adapters.

## Tests

```text
npm run smoke:assistant-caller
```

The smoke test covers:

- write preview returns confirmation token;
- execute without token is blocked;
- execute with token and mock internal token reaches the adapter;
- read preview does not require confirmation token.
