diff --git a/.agents/repo-guide.md b/.agents/repo-guide.md
index 1804498b1..de54e9c9d 100644
--- a/.agents/repo-guide.md
+++ b/.agents/repo-guide.md
@@ -110,13 +110,13 @@ All AI agent rules live in `.agents/rules/` (single source of truth).
### Always On
-| Rule | Description |
-| ------------------------------------------------------------------------------- | ----------------------------------------------------------- |
-| [`git/no-auto-commit`](rules/git/no-auto-commit.md) | Never commit or push without explicit user approval |
-| [`development/coding-conventions`](rules/development/coding-conventions.md) | TS strict, ESM only, naming, formatting, import conventions |
-| [`development/file-lifecycle`](rules/development/file-lifecycle.md) | Generated/downloaded file guardrails |
-| [`openspec/project-planning-memory`](rules/openspec/project-planning-memory.md) | OpenSpec workflow and project memory |
-| [`verification/after-changes`](rules/verification/after-changes.md) | Build, typecheck, test, lint, format checklist |
+| Rule | Description |
+| ------------------------------------------------------------------------------- | ------------------------------------------------------------ |
+| [`git/autonomous-delivery`](rules/git/autonomous-delivery.md) | Commit and push authorized feature work without re-prompting |
+| [`development/coding-conventions`](rules/development/coding-conventions.md) | TS strict, ESM only, naming, formatting, import conventions |
+| [`development/file-lifecycle`](rules/development/file-lifecycle.md) | Generated/downloaded file guardrails |
+| [`openspec/project-planning-memory`](rules/openspec/project-planning-memory.md) | OpenSpec workflow and project memory |
+| [`verification/after-changes`](rules/verification/after-changes.md) | Build, typecheck, test, lint, format checklist |
### On Demand (model_decision)
diff --git a/.agents/rules/git/autonomous-delivery.md b/.agents/rules/git/autonomous-delivery.md
new file mode 100644
index 000000000..5e2ca5c60
--- /dev/null
+++ b/.agents/rules/git/autonomous-delivery.md
@@ -0,0 +1,26 @@
+---
+trigger: always_on
+description: Commit and push authorized feature work without re-prompting.
+---
+
+# Autonomous Delivery Rule
+
+## Rule
+
+Once the user authorizes implementation, that authorization includes creating
+and pushing verified atomic commits to the scoped feature branch. Do not pause
+to request a second commit or push confirmation.
+
+Before committing and pushing:
+
+1. Inspect the exact staged files and diff.
+2. Run the relevant verification gates and record known blockers.
+3. Use a scoped, public-safe commit message and author identity where required.
+4. Push only the intended feature branch and report the resulting commit/MR.
+
+## Actions that remain gated
+
+- destructive history rewrites or force-pushes;
+- direct protected-branch updates or merges;
+- releases and package publication;
+- expanding the branch, repository, or delivery scope.
diff --git a/.agents/rules/git/no-auto-commit.md b/.agents/rules/git/no-auto-commit.md
deleted file mode 100644
index 4242cfa3a..000000000
--- a/.agents/rules/git/no-auto-commit.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-trigger: always_on
-description: Never commit or push without explicit user approval.
----
-
-# No Auto-Commit Rule
-
-## Rule
-
-**NEVER run `git commit` or `git push` without explicit user approval.**
-
-Before committing:
-
-1. Show the planned commit(s): message, staged files, diff summary
-2. Wait for the user to confirm
-3. Only then execute `git commit`
-
-## Exception: `$monitor-ci` Skill
-
-When the `$monitor-ci` skill is active (CI self-healing workflow), autonomous commits and pushes are permitted for:
-
-- Applying self-healing fixes
-- Retrying CI with empty commits
-- Updating lockfiles for pre-CI failures
-
-The skill has its own git safety rules (never `git add -A`, stage only fix-related files).
-
-## Applies To
-
-- All AI assistants (Devin, Windsurf, Claude, etc.)
-- Both interactive and background/subagent sessions
-
-## Why
-
-Commits are permanent project history. The user must review and approve:
-
-- What files are staged
-- The commit message
-- Whether changes should be split into multiple commits
-- Whether to push after committing
diff --git a/docs/roadmap/README.md b/docs/roadmap/README.md
index d755fc374..19181c177 100644
--- a/docs/roadmap/README.md
+++ b/docs/roadmap/README.md
@@ -119,5 +119,7 @@ Otherwise: open https://app.devin.ai/ → New session → paste the **Devin prom
- Every operation needs an entry in `packages/adt-cli/tests/e2e/parity..test.ts`.
- Real SAP fixtures in `@abapify/adt-fixtures` (mark `TODO-synthetic` only when no capture available).
- abapGit-style filenames everywhere a file path is emitted (use `adtUriToAbapGitPath`).
-- No commits without explicit user approval (per repo `.agents/rules/git/no-auto-commit`).
+- Authorized implementation is committed and pushed as verified atomic slices
+ without a separate confirmation prompt (see
+ `.agents/rules/git/autonomous-delivery`).
- Run `bunx nx format:write` before signalling done.
diff --git a/docs/roadmap/epics/_template.md b/docs/roadmap/epics/_template.md
index b1dcb3030..7eeb6ef69 100644
--- a/docs/roadmap/epics/_template.md
+++ b/docs/roadmap/epics/_template.md
@@ -91,7 +91,7 @@ Read these files first:
Implement everything in the "Scope" section. Stay strictly within "Out of scope" boundaries.
Run the full Acceptance block before declaring done.
-Do NOT commit unless explicitly approved by the operator.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
If you encounter blockers (missing contract, undecided design choice), document them in
docs/roadmap/epics/eXX-NAME.md under a new "Open questions" section and stop — don't guess.
diff --git a/docs/roadmap/epics/e01-include.md b/docs/roadmap/epics/e01-include.md
index e23960765..a1ff31921 100644
--- a/docs/roadmap/epics/e01-include.md
+++ b/docs/roadmap/epics/e01-include.md
@@ -82,6 +82,6 @@ Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e01-include.md
Read AGENTS.md, docs/roadmap/README.md, then this epic file. Implement Scope strictly.
Reference impl at /tmp/sapcli-ref/sapcli/ (clone if missing).
-Do NOT commit without explicit user approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
Run the Acceptance block before declaring done.
```
diff --git a/docs/roadmap/epics/e02-function.md b/docs/roadmap/epics/e02-function.md
index 07a19f874..98b8f1b44 100644
--- a/docs/roadmap/epics/e02-function.md
+++ b/docs/roadmap/epics/e02-function.md
@@ -93,5 +93,5 @@ Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e02-function.md
Read AGENTS.md, docs/roadmap/README.md, then this epic file. Implement Scope strictly.
Reference: /tmp/sapcli-ref/sapcli/sap/cli/function.py + sap/adt/function.py
-Do NOT commit without explicit user approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
diff --git a/docs/roadmap/epics/e03-badi.md b/docs/roadmap/epics/e03-badi.md
index 4beb4d225..177ede2d8 100644
--- a/docs/roadmap/epics/e03-badi.md
+++ b/docs/roadmap/epics/e03-badi.md
@@ -68,7 +68,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
```
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e03-badi.md
Read AGENTS.md + docs/roadmap/README.md first. Reference: /tmp/sapcli-ref/sapcli/sap/cli/badi.py.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Implementation notes (first pass)
diff --git a/docs/roadmap/epics/e04-strust.md b/docs/roadmap/epics/e04-strust.md
index 2888d66fa..913e933f9 100644
--- a/docs/roadmap/epics/e04-strust.md
+++ b/docs/roadmap/epics/e04-strust.md
@@ -67,7 +67,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
```
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e04-strust.md
Read AGENTS.md + docs/roadmap/README.md. Reference: /tmp/sapcli-ref/sapcli/sap/cli/strust.py.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions (post-landing)
diff --git a/docs/roadmap/epics/e05-format-plugin-api.md b/docs/roadmap/epics/e05-format-plugin-api.md
index 74f8f27a0..e3efa654e 100644
--- a/docs/roadmap/epics/e05-format-plugin-api.md
+++ b/docs/roadmap/epics/e05-format-plugin-api.md
@@ -104,7 +104,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e05-format-plugin-api.md
Read AGENTS.md + docs/roadmap/README.md, then this spec. This epic is foundation for E06/E07/E08;
breakages will block multiple downstream sessions.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions
diff --git a/docs/roadmap/epics/e06-gcts-format-plugin.md b/docs/roadmap/epics/e06-gcts-format-plugin.md
index 482cdf789..571fec601 100644
--- a/docs/roadmap/epics/e06-gcts-format-plugin.md
+++ b/docs/roadmap/epics/e06-gcts-format-plugin.md
@@ -109,7 +109,7 @@ Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e06-gcts-format-plugi
Read AGENTS.md, docs/roadmap/README.md, e05-format-plugin-api.md, then this file.
Reference SAP help docs for gCTS file layout — capture and pin locally before coding handlers.
This epic must NOT introduce gCTS *commands* (those are E07). Only serialization.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions
diff --git a/docs/roadmap/epics/e07-gcts-command-plugin.md b/docs/roadmap/epics/e07-gcts-command-plugin.md
index 6ee105fbb..3968405b7 100644
--- a/docs/roadmap/epics/e07-gcts-command-plugin.md
+++ b/docs/roadmap/epics/e07-gcts-command-plugin.md
@@ -114,7 +114,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e07-gcts-command-plugin.md
Read AGENTS.md, docs/roadmap/README.md, e05-format-plugin-api.md, e06-gcts-format-plugin.md.
Reference: /tmp/sapcli-ref/sapcli/sap/cli/gcts.py and sap/rest/gcts/.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions
diff --git a/docs/roadmap/epics/e08-checkin.md b/docs/roadmap/epics/e08-checkin.md
index dc6b7fff5..7743cc37d 100644
--- a/docs/roadmap/epics/e08-checkin.md
+++ b/docs/roadmap/epics/e08-checkin.md
@@ -91,7 +91,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e08-checkin.md
Read AGENTS.md, docs/roadmap/README.md, e05-format-plugin-api.md, packages/adk/AGENTS.md.
Reference: /tmp/sapcli-ref/sapcli/sap/cli/checkin.py.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions
diff --git a/docs/roadmap/epics/e09-acds-parser.md b/docs/roadmap/epics/e09-acds-parser.md
index 8d64ca918..136a96ea8 100644
--- a/docs/roadmap/epics/e09-acds-parser.md
+++ b/docs/roadmap/epics/e09-acds-parser.md
@@ -72,7 +72,7 @@ bunx nx build acds && bunx nx test acds
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e09-acds-parser.md
Read packages/acds/AGENTS.md and the existing grammar/parser. Refer to SAP CDS DDL spec.
This is foundation for RAP epics — keep API stable.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions
diff --git a/docs/roadmap/epics/e10-rap-bdef.md b/docs/roadmap/epics/e10-rap-bdef.md
index a9bd3e3d6..086f84241 100644
--- a/docs/roadmap/epics/e10-rap-bdef.md
+++ b/docs/roadmap/epics/e10-rap-bdef.md
@@ -77,7 +77,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e10-rap-bdef.md
Reads: AGENTS.md, docs/roadmap/README.md, e09-acds-parser.md, packages/adk/AGENTS.md.
Reference: /tmp/sapcli-ref/sapcli/sap/cli/behaviordefinition.py.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Delivered (2025-PR-103)
diff --git a/docs/roadmap/epics/e11-rap-srvd.md b/docs/roadmap/epics/e11-rap-srvd.md
index cf593764d..727fa94fa 100644
--- a/docs/roadmap/epics/e11-rap-srvd.md
+++ b/docs/roadmap/epics/e11-rap-srvd.md
@@ -64,7 +64,7 @@ packages/adt-plugin-abapgit/tests/filename/adt-uri-to-path.test.ts
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e11-rap-srvd.md
Reads: AGENTS.md, docs/roadmap/README.md, e09-acds-parser.md.
Capture a real SRVD source from any S/4HANA sample app for reference.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Status: Landed
diff --git a/docs/roadmap/epics/e12-rap-srvb.md b/docs/roadmap/epics/e12-rap-srvb.md
index 01689c80a..e2c867231 100644
--- a/docs/roadmap/epics/e12-rap-srvb.md
+++ b/docs/roadmap/epics/e12-rap-srvb.md
@@ -73,7 +73,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
```
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e12-rap-srvb.md
Reads: AGENTS.md, docs/roadmap/README.md, packages/adt-mcp/src/lib/tools/publish-service-binding.ts.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Status: Landed
diff --git a/docs/roadmap/epics/e13-startrfc.md b/docs/roadmap/epics/e13-startrfc.md
index 23598482b..00aaeef9d 100644
--- a/docs/roadmap/epics/e13-startrfc.md
+++ b/docs/roadmap/epics/e13-startrfc.md
@@ -80,7 +80,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e13-startrfc.md
Reads: AGENTS.md, docs/roadmap/README.md.
Reference: /tmp/sapcli-ref/sapcli/sap/cli/startrfc.py and sap/rfc/.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions
diff --git a/docs/roadmap/epics/e14-flp.md b/docs/roadmap/epics/e14-flp.md
index 41a76c3c3..8e98f3650 100644
--- a/docs/roadmap/epics/e14-flp.md
+++ b/docs/roadmap/epics/e14-flp.md
@@ -60,5 +60,5 @@ packages/adt-fixtures/src/mock-server/routes.ts
```
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e14-flp.md
Reference: /tmp/sapcli-ref/sapcli/sap/cli/flp.py.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
diff --git a/docs/roadmap/epics/e15-wb.md b/docs/roadmap/epics/e15-wb.md
index 652a74ba6..12c459a33 100644
--- a/docs/roadmap/epics/e15-wb.md
+++ b/docs/roadmap/epics/e15-wb.md
@@ -68,7 +68,7 @@ bunx nx typecheck && bunx nx lint && bunx nx format:write
```
Spec: /mnt/wsl/workspace/ubuntu/adt-cli/docs/roadmap/epics/e15-wb.md
Reference: /tmp/sapcli-ref/sapcli/sap/cli/wb.py and our existing MCP tools.
-Do NOT commit without approval.
+Commit and push verified changes to the scoped feature branch without a separate confirmation prompt.
```
## Open questions (post-real-SAP sweep, TRL 2025-11)
diff --git a/openspec/changes/add-delegated-assistant-read-scope/design.md b/openspec/changes/add-delegated-assistant-read-scope/design.md
new file mode 100644
index 000000000..eb4971235
--- /dev/null
+++ b/openspec/changes/add-delegated-assistant-read-scope/design.md
@@ -0,0 +1,44 @@
+# Design: delegated-assistant read scope
+
+## Decision
+
+`delegated-assistant` is a product-neutral signed invocation identity for an
+interactive MCP client acting on behalf of one authenticated principal. Its
+credential carries the fixed `server` and `read` classes plus one
+System/Destination binding. The server's catalogue remains the only
+tool-to-class authority.
+
+The exact constraint is:
+
+```ts
+{
+ kind: 'delegated-assistant-read-v1';
+ threadId: string;
+ executionId: string;
+ systemSid: string;
+}
+```
+
+`threadId` and `executionId` are UUIDs. Limits are empty because operational
+rate limits remain server-owned and do not determine tool admission.
+
+## Enforcement
+
+The invocation verifier accepts only the exact claim shape. HTTP session
+identity remains bound to the credential JTI. `tools/list` filters registered
+tools through the operation-class catalogue, and dispatch repeats the same
+class and Destination checks before acquiring a lease.
+
+The client requests the coarse read envelope by obtaining this credential. It
+does not send tool names, Destination keys, or resource-broadening arguments.
+
+## Alternatives rejected
+
+- Product-specific agent IDs: rejected because the public MCP server must
+ remain reusable outside one consumer.
+- Client-maintained tool allowlists: rejected because they drift from the
+ server catalogue.
+- Reusing `system-assistant`: rejected because it lacks explicit thread and
+ execution binding and is retained only for compatibility.
+- Adding `safe_execute`: rejected because checks require a separate exact,
+ single-use grant.
diff --git a/openspec/changes/add-delegated-assistant-read-scope/proposal.md b/openspec/changes/add-delegated-assistant-read-scope/proposal.md
new file mode 100644
index 000000000..b61ddf7e3
--- /dev/null
+++ b/openspec/changes/add-delegated-assistant-read-scope/proposal.md
@@ -0,0 +1,28 @@
+# Add a delegated-assistant read scope
+
+## Why
+
+An interactive assistant needs to discover and call the read tools permitted
+to its authenticated principal without duplicating the MCP server's tool
+catalogue in every client.
+
+## What changes
+
+- Add one product-neutral `delegated-assistant` signed invocation policy.
+- Bind the policy to the authenticated principal, MCP execution, thread, and
+ one System/Destination.
+- Let the server's operation-class catalogue select all permitted `server`
+ and `read` tools at discovery and dispatch.
+
+## Non-goals
+
+- This change adds no product-specific agent name or workflow.
+- This change adds no `safe_execute` or `write` authority.
+- This change adds no client-provided tool-name allowlist or call-count limit.
+- This change does not alter ordinary OAuth, bearer, proxy, or stdio modes.
+
+## Validation
+
+- Invocation tests prove only the exact delegated read policy is accepted.
+- HTTP integration proves multiple read tools are advertised.
+- Scope tests prove write and `safe_execute` tools remain absent and denied.
diff --git a/openspec/changes/add-delegated-assistant-read-scope/specs/adt-mcp/spec.md b/openspec/changes/add-delegated-assistant-read-scope/specs/adt-mcp/spec.md
new file mode 100644
index 000000000..fa029dfb6
--- /dev/null
+++ b/openspec/changes/add-delegated-assistant-read-scope/specs/adt-mcp/spec.md
@@ -0,0 +1,40 @@
+## ADDED Requirements
+
+### Requirement: Delegated assistants receive a server-owned read catalogue
+
+The server SHALL accept an exact signed delegated-assistant policy bound to
+one principal, thread, execution, System, and Destination. The resulting MCP
+catalogue SHALL contain every registered tool whose server-owned operation
+class is `server` or `read`.
+
+#### Scenario: Delegated assistant lists tools
+
+- **GIVEN** a valid delegated-assistant credential requests the read envelope
+- **WHEN** the client calls `tools/list`
+- **THEN** the server advertises multiple permitted read tools without a
+ client-provided tool-name allowlist
+
+#### Scenario: A new read tool is registered
+
+- **GIVEN** a new tool has a complete `read` catalogue classification
+- **WHEN** a delegated assistant refreshes `tools/list`
+- **THEN** the new tool is admitted without changing the client credential
+ contract
+
+### Requirement: Delegated read authority cannot widen
+
+The server SHALL reject malformed delegated-assistant policies and SHALL deny
+`safe_execute`, `write`, unknown, and out-of-Destination operations at both
+catalogue and dispatch.
+
+#### Scenario: Delegated assistant attempts a write
+
+- **WHEN** the client requests or directly calls a write-class tool
+- **THEN** the tool is absent from discovery and dispatch returns
+ `mcp_scope_denied` before a Destination lease or SAP operation
+
+#### Scenario: Delegated policy carries additional authority
+
+- **WHEN** the signed claim adds a tool list, resource override, non-empty
+ limits, another operation class, or an additional Destination
+- **THEN** the invocation exposes no MCP tools
diff --git a/openspec/changes/add-delegated-assistant-read-scope/tasks.md b/openspec/changes/add-delegated-assistant-read-scope/tasks.md
new file mode 100644
index 000000000..d7eb55872
--- /dev/null
+++ b/openspec/changes/add-delegated-assistant-read-scope/tasks.md
@@ -0,0 +1,13 @@
+# Tasks
+
+- [x] Define the product-neutral delegated-assistant read policy.
+- [x] Add failing policy-parser and HTTP catalogue tests.
+- [x] Implement exact invocation verification.
+- [x] Prove catalogue and dispatch remain server-owned and read-only.
+- [ ] Run adt-mcp build, typecheck, tests, lint, and full-tree formatting.
+ - Focused policy, catalogue, and signed-JWT tests pass.
+ - Scoped ESLint and Prettier checks pass; strict OpenSpec validation passes.
+ - Full build/typecheck and server-construction tests remain blocked by the
+ pre-existing dependency-tree mismatch (Zod 4 versus
+ `zod-to-json-schema`, stale generated declarations). A clean frozen
+ install is unavailable under the current registry policy.
diff --git a/packages/adt-mcp/src/index.ts b/packages/adt-mcp/src/index.ts
index 5a7d12174..d412bbb55 100644
--- a/packages/adt-mcp/src/index.ts
+++ b/packages/adt-mcp/src/index.ts
@@ -47,8 +47,10 @@ export {
} from './lib/http/server';
export {
createMcpInvocationVerifier,
+ parseDelegatedAssistantReadPolicy,
parseScopedAdtInvocationPolicy,
parseSafeExecutePolicy,
+ type DelegatedAssistantReadPolicy,
type ScopedAdtInvocationPolicy,
type McpInvocationJsonValue,
type McpInvocationVerifier,
diff --git a/packages/adt-mcp/src/lib/http/invocation.ts b/packages/adt-mcp/src/lib/http/invocation.ts
index b9da57067..2b00ea62b 100644
--- a/packages/adt-mcp/src/lib/http/invocation.ts
+++ b/packages/adt-mcp/src/lib/http/invocation.ts
@@ -33,6 +33,7 @@ const trustedAgentIds = new Set([
'system-assistant',
'autonomous-review-agent',
'adt-execution',
+ 'delegated-assistant',
]);
const trustedOperationClasses = new Set(['server', 'read', 'safe_execute']);
const scopedReadTools = new Set(['get_object', 'get_object_structure']);
@@ -55,7 +56,8 @@ export interface TrustedMcpInvocationClaims {
| 'ai-review'
| 'system-assistant'
| 'autonomous-review-agent'
- | 'adt-execution';
+ | 'adt-execution'
+ | 'delegated-assistant';
readonly classes: readonly McpTrustedOperationClass[];
readonly destinationKeys: readonly string[];
readonly correlationId: string;
@@ -131,6 +133,12 @@ export interface ScopedAdtInvocationPolicy {
readonly authorizationToken?: string;
}
+export interface DelegatedAssistantReadPolicy {
+ readonly threadId: string;
+ readonly executionId: string;
+ readonly systemSid: string;
+}
+
export interface McpInvocationVerifierOptions {
/** ES256 public key mounted at ADT Server; it cannot issue credentials. */
publicKey: CryptoKey | KeyObject;
@@ -188,6 +196,33 @@ function hasServerReadClasses(claims: TrustedMcpInvocationClaims): boolean {
);
}
+export function parseDelegatedAssistantReadPolicy(
+ claims: TrustedMcpInvocationClaims,
+): DelegatedAssistantReadPolicy | undefined {
+ if (
+ claims.agentId !== 'delegated-assistant' ||
+ claims.destinationKeys.length !== 1 ||
+ !hasServerReadClasses(claims) ||
+ Object.keys(claims.limits).length !== 0 ||
+ !hasExactSortedKeys(claims.constraint, [
+ 'kind',
+ 'threadId',
+ 'executionId',
+ 'systemSid',
+ ]) ||
+ claims.constraint.kind !== 'delegated-assistant-read-v1'
+ ) {
+ return undefined;
+ }
+
+ const threadId = requiredUuid(claims.constraint.threadId);
+ const executionId = requiredUuid(claims.constraint.executionId);
+ const systemSid = requiredSystemSid(claims.constraint.systemSid);
+ if (!threadId || !executionId || !systemSid) return undefined;
+
+ return Object.freeze({ threadId, executionId, systemSid });
+}
+
export function isMcpInvocationDispatchPolicySupported(
claims: TrustedMcpInvocationClaims,
): boolean {
@@ -204,6 +239,9 @@ export function isMcpInvocationDispatchPolicySupported(
if (claims.agentId === 'adt-execution') {
return parseScopedAdtInvocationPolicy(claims) !== undefined;
}
+ if (claims.agentId === 'delegated-assistant') {
+ return parseDelegatedAssistantReadPolicy(claims) !== undefined;
+ }
if (claims.agentId === 'ai-review') {
return (
hasServerReadClasses(claims) &&
diff --git a/packages/adt-mcp/tests/delegated-assistant-catalogue.test.ts b/packages/adt-mcp/tests/delegated-assistant-catalogue.test.ts
new file mode 100644
index 000000000..019ed2e72
--- /dev/null
+++ b/packages/adt-mcp/tests/delegated-assistant-catalogue.test.ts
@@ -0,0 +1,56 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import {
+ isMcpToolAllowed,
+ isMcpToolListed,
+ MCP_TOOL_SCOPE_CATALOGUE,
+ type McpRequestAccess,
+} from '../src/lib/tools/scope-catalogue.js';
+
+const access: McpRequestAccess = {
+ classes: ['server', 'read'],
+ destinationKeys: ['dev'],
+};
+
+test('delegated read envelope projects the complete server-owned read catalogue', () => {
+ const listed = Object.keys(MCP_TOOL_SCOPE_CATALOGUE)
+ .filter((name) => isMcpToolListed(access, name))
+ .sort();
+
+ assert.ok(listed.length > 10);
+ for (const readTool of [
+ 'system_info',
+ 'get_object',
+ 'find_references',
+ 'cts_get_transport',
+ ]) {
+ assert.ok(listed.includes(readTool), `missing read tool ${readTool}`);
+ }
+ assert.ok(!listed.includes('lock_object'));
+ assert.ok(!listed.includes('atc_run'));
+});
+
+test('delegated read envelope denies write and safe-execute dispatch', () => {
+ assert.strictEqual(
+ isMcpToolAllowed(access, 'get_object', {
+ destination: 'dev',
+ objectName: 'ZCL_SCOPE_TEST',
+ }),
+ true,
+ );
+ assert.strictEqual(
+ isMcpToolAllowed(access, 'lock_object', {
+ destination: 'dev',
+ objectName: 'ZCL_SCOPE_TEST',
+ }),
+ false,
+ );
+ assert.strictEqual(
+ isMcpToolAllowed(access, 'atc_run', {
+ destination: 'dev',
+ scope: { kind: 'package', packageName: 'ZPACKAGE' },
+ }),
+ false,
+ );
+ assert.strictEqual(isMcpToolAllowed(access, 'unknown_tool'), false);
+});
diff --git a/packages/adt-mcp/tests/delegated-assistant-policy.test.ts b/packages/adt-mcp/tests/delegated-assistant-policy.test.ts
new file mode 100644
index 000000000..9f72d59ba
--- /dev/null
+++ b/packages/adt-mcp/tests/delegated-assistant-policy.test.ts
@@ -0,0 +1,84 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import {
+ parseDelegatedAssistantReadPolicy,
+ type TrustedMcpInvocationClaims,
+} from '../src/index.js';
+import { isMcpInvocationDispatchPolicySupported } from '../src/lib/http/invocation.js';
+
+const threadId = '11111111-1111-4111-8111-111111111111';
+const executionId = '22222222-2222-4222-8222-222222222222';
+
+function claims(
+ overrides: Partial = {},
+): TrustedMcpInvocationClaims {
+ return {
+ tokenId: 'jti-delegated-assistant-001',
+ principal: 'engineer@example.invalid',
+ agentId: 'delegated-assistant',
+ classes: ['server', 'read'],
+ destinationKeys: ['tst-adt'],
+ correlationId: 'delegated-assistant:001',
+ constraint: {
+ kind: 'delegated-assistant-read-v1',
+ threadId,
+ executionId,
+ systemSid: 'TST',
+ },
+ limits: {},
+ ...overrides,
+ };
+}
+
+describe('Delegated-assistant read policy', () => {
+ it('accepts the exact principal-scoped read envelope', () => {
+ assert.deepStrictEqual(parseDelegatedAssistantReadPolicy(claims()), {
+ threadId,
+ executionId,
+ systemSid: 'TST',
+ });
+ assert.strictEqual(isMcpInvocationDispatchPolicySupported(claims()), true);
+ });
+
+ it('rejects tool lists, limits, extra classes, and extra Destinations', () => {
+ for (const candidate of [
+ claims({
+ constraint: {
+ ...claims().constraint,
+ toolNames: ['get_object'],
+ },
+ }),
+ claims({ limits: { maxToolCalls: 12 } }),
+ claims({ classes: ['server', 'read', 'safe_execute'] }),
+ claims({ destinationKeys: ['tst-adt', 'prd-adt'] }),
+ ]) {
+ assert.strictEqual(
+ parseDelegatedAssistantReadPolicy(candidate),
+ undefined,
+ );
+ assert.strictEqual(
+ isMcpInvocationDispatchPolicySupported(candidate),
+ false,
+ );
+ }
+ });
+
+ it('rejects malformed or incomplete execution identity', () => {
+ for (const constraint of [
+ { ...claims().constraint, kind: 'unknown' },
+ { ...claims().constraint, threadId: 'not-a-uuid' },
+ { ...claims().constraint, executionId: 'not-a-uuid' },
+ { ...claims().constraint, systemSid: 'TST!' },
+ {
+ kind: 'delegated-assistant-read-v1',
+ threadId,
+ executionId,
+ },
+ ]) {
+ assert.strictEqual(
+ parseDelegatedAssistantReadPolicy(claims({ constraint })),
+ undefined,
+ );
+ }
+ });
+});
diff --git a/packages/adt-mcp/tests/http-invocation.test.ts b/packages/adt-mcp/tests/http-invocation.test.ts
index 6e8e87b5f..c14797aa2 100644
--- a/packages/adt-mcp/tests/http-invocation.test.ts
+++ b/packages/adt-mcp/tests/http-invocation.test.ts
@@ -134,6 +134,28 @@ describe('MCP invocation verifier', () => {
assert.strictEqual(isMcpInvocationDispatchPolicySupported(verified), false);
});
+ it('verifies and dispatches a signed delegated-assistant read envelope', async () => {
+ const credential = await sign({
+ agentId: 'delegated-assistant',
+ classes: ['server', 'read'],
+ destinationKeys: ['tst-adt'],
+ correlationId: 'delegated-assistant:001',
+ constraint: {
+ kind: 'delegated-assistant-read-v1',
+ threadId: '11111111-1111-4111-8111-111111111111',
+ executionId: '22222222-2222-4222-8222-222222222222',
+ systemSid: 'TST',
+ },
+ limits: {},
+ });
+
+ const verified = await verifier.verify(`Bearer ${credential}`);
+
+ assert.ok(verified);
+ assert.strictEqual(verified.agentId, 'delegated-assistant');
+ assert.strictEqual(isMcpInvocationDispatchPolicySupported(verified), true);
+ });
+
it('rejects a credential with an invalid ES256 signature', async () => {
const { privateKey: untrustedPrivateKey } = await generateKeyPair('ES256');
const credential = await sign({}, { signingKey: untrustedPrivateKey });