# Assistant Action Registry

Status: draft implementation contract.
Date: 2026-06-19

This document defines the scalable execution model for NDC Core Assistant actions.

The assistant must not grow through prompt-only rules. Every executable or refusible command should resolve to a registered action card, pass assistant access policy, pass risk policy, and then route to an app-owned adapter.

## Execution Pipeline

```mermaid
flowchart LR
  A["User request"] --> B["Action resolver"]
  B --> C["Ontology context"]
  C --> D["Assistant access policy"]
  D --> E["Risk policy"]
  E --> F["App-owned adapter"]
  F --> G["Confirmation"]
  G --> H["Audit / idempotent execution"]
```

The resolver can return:

- `action_not_registered`: no action card exists;
- `forbidden`: the action is destructive or explicitly forbidden;
- `denied`: the actor lacks capability, admin scope, or self-action safety;
- `needs_confirmation`: policy allows the action, but explicit confirmation is required;
- `future_adapter`: the correct app boundary is known, but the adapter is not implemented yet;
- `allowed`: policy allows the action. `execution.executionReady` is true only when the adapter is implemented.

## Action Card

Action cards live in:

```text
catalog/assistant-actions.json
```

Each action card must define:

- `id`: stable action id, for example `hub.user.block`;
- `app`: authority boundary: `hub`, `ops`, `engine`, or `gateway`;
- `domain`: local functional area;
- `intentAliases`: phrases the resolver can match;
- `entityIds`: ontology entities touched by the action;
- `capabilityIds`: assistant capabilities required from `assistant-access-policy.json`;
- `riskLevel`: `read`, `write`, `privileged`, `destructive`, `root_only`, or `future`;
- `confirmationMode`: `none`, `explicit`, `typed`, or `forbidden`;
- `selfActionPolicy`: `allowed`, `blocked`, `no_self_lockout`, or `app_owned`;
- `requiredScopes`: source-app scope checks required for privileged actions;
- `adapterId` and `adapterStatus`: the app-owned execution boundary;
- `hardDenyIds` and `safeAlternativeActionIds` when relevant.

The validator rejects dangling entity, capability, risk, adapter-status, hard-deny, and safe-alternative references.

## App Boundaries

HUB / Launcher owns:

- entry access;
- users;
- client memberships;
- invites;
- service access matrix;
- NDC Core Assistant role assignment.

OPS Product owns:

- workspaces;
- projects;
- cards;
- workspace/project membership;
- OPS-internal role rules.

ENGINE owns:

- workflow context;
- workflow ACL;
- workflow sharing;
- runtime/workflow source of truth.

OPS Gateway owns:

- tool grants;
- MCP/tool scope selection;
- idempotency;
- audit envelope;
- adapter execution routing.

HUB may route to OPS or ENGINE, but must not directly mutate OPS Product roles or ENGINE workflow ACLs. Those mutations must go through app-owned adapters.

## Hard Denies

The assistant must never:

- delete HUB users;
- hard-delete database records, files, storage, backups, runtime data, or production state;
- let an actor remove their own last admin/access path;
- mutate app-owned roles through the wrong app boundary;
- execute a write command from prompt text alone without a registered action card.

Preferred safe alternatives:

- block a user instead of deleting the user;
- disable a membership instead of deleting access records;
- update/close an OPS card instead of deleting it;
- route ENGINE workflow ACL changes to an Engine-owned adapter.

## First Practical Block

The first implementation block should be HUB access administration:

- read admin-visible user/access summaries;
- list pending invites;
- block/unblock users;
- disable memberships;
- change `NDC Core Assistant` role between `blocked`, `member`, and `admin`.

This is the lowest-risk useful block because the Launcher already has manual admin flows and backend guards. The assistant adapter should call the same guarded routes and add idempotency/audit around them.

## Adding A New Functional Block

To add another block without prompt sprawl:

1. Add or reuse canonical entities in `catalog/entities.json`.
2. Add required capabilities to `catalog/assistant-access-policy.json`.
3. Add action cards to `catalog/assistant-actions.json`.
4. Classify risk and confirmation using `catalog/assistant-risk-policy.json`.
5. Add examples under `examples/assistant-action-*.json`.
6. Run validation and smoke tests.
7. Only then implement the app-owned adapter.

Commands:

```text
npm run validate
npm run smoke:assistant-policy
npm run smoke:assistant-actions
npm run smoke:assistant-action-plan
npm run smoke:assistant-caller
npm run smoke:assistant-executor
npm run assistant:action -- --input-json examples/assistant-action-block-user.example.json
npm run assistant:plan -- --input-json examples/assistant-action-block-user.example.json
npm run assistant:caller -- --input-json examples/assistant-caller-preview-block-user.example.json
npm run assistant:execute -- --input-json examples/assistant-action-block-user.example.json
```

## Current Limitation

Many non-HUB action cards are currently `adapterStatus=planned` or `adapterStatus=future`.

That is intentional. The ontology core can already answer whether an action is conceptually registered and policy-allowed, but actual mutation must wait for the app-owned adapter implementation.

The current HUB adapter maps the first implemented allowlist block:

```text
src/adapters/hub-launcher-admin.mjs
```

It maps allowed HUB actions to guarded Launcher backend routes:

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

It intentionally does not map existing Launcher `DELETE` routes. Deletion requests resolve to `forbidden` before a request plan is produced.

The UI/caller contract lives in:

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

Callers should use it for preview/execute flows instead of calling the low-level executor directly.

The execution gateway lives in:

```text
src/assistant-action-executor.mjs
```

It defaults to dry-run, validates the request plan, requires confirmation tokens for write execution, and refuses network execution until the specific adapter is marked `implemented`.
