# Assistant Execution Gateway

Status: first HUB gateway execution path implemented.
Date: 2026-06-19

This document defines the execution boundary after an assistant request resolves to an action card and an app-owned request plan.

The gateway exists to keep assistant execution out of prompt-only logic. It is a code-level safety boundary.

UI and assistant surfaces should call the higher-level caller contract first:

```text
docs/ASSISTANT_CALLER_CONTRACT.md
src/assistant-action-caller.mjs
```

The low-level executor remains the final safety gate.

## Modes

`dry-run`

- default mode;
- builds the action plan;
- validates route/method/idempotency safety;
- for write requests, returns a confirmation envelope and token;
- does not make network requests.

`execute`

- available only when the caller explicitly passes execution mode;
- requires network execution to be enabled;
- requires `baseUrl`;
- refuses planned/future adapters unless an explicit unsafe dev override is set;
- for write requests, requires a matching confirmation envelope token from a previous preview;
- for `adapter.hub.launcher_admin_api`, requires Launcher internal token and assistant actor identity headers;
- still refuses forbidden/destructive actions and non-allowlisted routes.

## Current CLI

Dry-run:

```text
npm run assistant:execute -- --input-json examples/assistant-action-block-user.example.json
```

For write actions, dry-run returns:

```text
confirmationEnvelope.token: <preview token>
```

Attempted network execution:

```text
npm run assistant:execute -- --execute --base-url http://launcher.local.nodedc --input-json examples/assistant-action-block-user.example.json
```

For write actions, execution without the preview token returns:

```text
decision: execution_blocked
reason: write_confirmation_envelope_missing
```

Live write execution requires both:

- the matching `--confirmation-token` from dry-run;
- `NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN` or `NODEDC_INTERNAL_ACCESS_TOKEN`;
- an input actor.

```text
NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN=... npm run assistant:execute -- --execute --confirmation-token <preview token> --base-url http://launcher.local.nodedc --input-json examples/assistant-action-block-user.example.json
```

The executor sends the token as `Authorization: Bearer ...` plus:

```text
X-NODEDC-Assistant-Gateway: ontology-core
X-NODEDC-Assistant-Actor-User-Id: <actorUserId>
```

The Launcher BFF accepts this server-to-server path only for the allowlisted routes below. The actor still has to resolve to an active Launcher user with Launcher admin scope, and the request still passes the source route guards.

## Safety Checks

The executor validates:

- the plan is `ok`;
- method is allowlisted;
- path is under `/api/admin/`;
- route is explicitly allowlisted;
- `DELETE` is always forbidden;
- write requests include `Idempotency-Key`;
- write requests passed prior confirmation checks;
- write execution includes a matching confirmation envelope token;
- destructive/forbidden action decisions cannot execute;
- adapter safety flags are present.

Current HUB allowlist:

```text
GET   /api/admin/control-plane
PATCH /api/admin/users/:userId/profile
PATCH /api/admin/memberships/:membershipId
```

## Execution Readiness

The current implementation is still conservative:

- action cards can be policy-allowed;
- request plans can be generated;
- executor can dry-run, return write confirmation envelope, and mock-execute;
- real network execution is available only for implemented HUB allowlist actions and only with gateway auth headers.

## Launcher Route Readiness

Current Launcher BFF status for the first HUB block:

- `GET /api/admin/control-plane` is behind `requireLauncherAdmin`;
- `PATCH /api/admin/users/:userId/profile` is behind `requireLauncherAdmin` and `assertAdminCanManageUser`;
- `PATCH /api/admin/memberships/:membershipId` is behind `requireLauncherAdmin` and `assertAdminCanManageMembership`;
- if no browser session exists, `requireLauncherAdmin` accepts assistant gateway auth only for the three allowlisted routes;
- assistant gateway auth requires the shared internal token and `X-NODEDC-Assistant-Gateway: ontology-core`;
- gateway actor identity is passed through `X-NODEDC-Assistant-Actor-*` headers and resolved to a real Launcher user before admin scope checks;
- both PATCH routes emit control-plane audit through `controlPlaneStore`;
- both PATCH routes now use an `Idempotency-Key` envelope with replay and conflict behavior;
- Launcher contract tests cover these route/guard/idempotency invariants.

The idempotency envelope is process-local and intended to protect immediate retries/replays. It is enough for the current local assistant execution scaffold. A persistent gateway-level idempotency ledger should be added before distributed or multi-process production execution.

The following HUB actions are marked `implemented` because they map only to the current allowlist:

```text
hub.user.read_admin_summary
hub.invite.list_pending
hub.user.block
hub.user.unblock
hub.membership.change_role
hub.membership.disable
hub.assistant_access.change_role
```

OPS, ENGINE, Gateway-level action resolver, and broader admin routes remain `planned` or `future`.

To mark a route production-ready:

1. Verify source app guards directly.
2. Verify auth identity propagation.
3. Verify audit event output.
4. Verify idempotency behavior.
5. Add integration tests against local Launcher.
6. Change only the specific action adapter status to `implemented`.
7. Keep the route in both executor and Launcher BFF allowlists.

## Tests

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

The smoke test covers:

- default dry-run;
- write confirmation envelope generation;
- write execution blocked without confirmation envelope token;
- implemented adapter blocking execution without internal token;
- mock execution through allowlisted PATCH route with gateway auth headers;
- delete action blocked before network;
- corrupted DELETE plan rejected by safety validation.
