subagent: carry inherited policy overrides in the child session header
Review fix (ds-review-bot critical #2 on #623): the first-turn event stamp had a durability hole no turn anchoring can close — an idle SessionStart- style injection persists a complete one-shot turn before any prompt turn opens, so a crash in that window left a resumable-looking child with no inherited policy, falling back to a possibly wider deployment default. The captured overrides now ride the child's creation meta into its immutable SessionHeader (sandboxMode/approvalPolicy, neutral strings at the session boundary — the delegationDepth precedent), durable from the moment the session exists: no listener ordering can starve the baseline and no crash window can lose it. overrideOf(session) on both policy services resolves fold(events past header.seedLength) ?? header baseline, validating against the closed vocabulary on read; stampOverride and the prompt-submit listener machinery are deleted. The header field rides both persistence backends (JSONL header line; SQLite sessions columns, SCHEMA_VERSION 11 — pre-release, no migration). pty-local reads through overrideOf so PTY spawns see the baseline too. Red-first: header-durability-before-any-turn test (the injection crash window shape), baseline/seed-boundary/closed-vocabulary contract tests in both service suites; the real-wall suite (race, veto, fork stale-seed, grandchild) re-anchored on header assertions and green. The Agent Note's Alternatives now records the superseded event-stamping iteration with the review evidence; bilingual docs updated.
This commit is contained in:
@@ -216,7 +216,7 @@ roots(): Agent[]
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/agent/src/index.ts:225`](../../packages/core/agent/src/index.ts)
|
||||
Source: [`packages/core/agent/src/index.ts:227`](../../packages/core/agent/src/index.ts)
|
||||
|
||||
## `ctx.approval` — `ApprovalService`
|
||||
|
||||
@@ -244,27 +244,21 @@ Approval service that applies session policy before answerers and logs every ask
|
||||
async request(req: ApprovalRequest): Promise<ApprovalOutcome>
|
||||
|
||||
/**
|
||||
* A session's approval-policy OVERRIDE — the fold alone, never the
|
||||
* configured default. The read half of delegation inheritance: the subagent
|
||||
* driver captures this synchronously at delegation, so a parent switch
|
||||
* racing the child's asynchronous creation belongs to the parent's future,
|
||||
* not to the child ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).
|
||||
* @param session - the session whose override chain to fold.
|
||||
* @returns the last switched policy, or `undefined` for a never-switched session.
|
||||
* A session's approval-policy OVERRIDE — the override chain alone, never
|
||||
* the configured default: the fold of the session's OWN switches (events
|
||||
* past the seed boundary — a fork seed's stale parent switch is subsumed by
|
||||
* the baseline captured after it), else the header's inherited delegation
|
||||
* baseline. The subagent driver stamps `overrideOf(parent.session)` into
|
||||
* each child's creation meta, so a `'never'` (headless/CI) parent cannot
|
||||
* mint children that fall back to a prompting default, at any depth
|
||||
* ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).
|
||||
* @param session - the session whose override chain to resolve.
|
||||
* @returns the effective override, or `undefined` for a session following
|
||||
* the configured default.
|
||||
* @throws when the durable header baseline is outside the closed policy
|
||||
* vocabulary (a corrupt or foreign log; durable-boundary validation).
|
||||
*/
|
||||
overrideOf(session: Session): ApprovalPolicy | undefined
|
||||
|
||||
/**
|
||||
* Stamp a captured override onto a child session through the canonical
|
||||
* write path — the write half of delegation inheritance: a `'never'`
|
||||
* (headless/CI) parent must not mint children that fall back to a prompting
|
||||
* default. A child whose log (e.g. a fork seed) already folds to the policy
|
||||
* is left untouched. Callers must append inside an open child turn — a bare
|
||||
* between-turn event is crash-tail garbage on reload.
|
||||
* @param child - the child session the override is appended to.
|
||||
* @param policy - the captured {@link overrideOf} value to stamp.
|
||||
*/
|
||||
stampOverride(child: Session, policy: ApprovalPolicy): void
|
||||
```
|
||||
|
||||
Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalPolicy](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [Session](../core-data-structures/session.md)
|
||||
@@ -974,28 +968,21 @@ The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mo
|
||||
resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy
|
||||
|
||||
/**
|
||||
* A session's sandbox-mode OVERRIDE — the fold alone, never the deployment
|
||||
* default. The read half of delegation inheritance: the subagent driver
|
||||
* captures this synchronously at delegation, so a parent switch racing the
|
||||
* child's asynchronous creation belongs to the parent's future, not to the
|
||||
* child ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).
|
||||
* @param session - the session whose override chain to fold.
|
||||
* @returns the last switched mode, or `undefined` for a never-switched session.
|
||||
* A session's sandbox-mode OVERRIDE — the override chain alone, never the
|
||||
* deployment default: the fold of the session's OWN switches (events past
|
||||
* the seed boundary — a fork seed's stale parent switch is subsumed by the
|
||||
* baseline captured after it), else the header's inherited delegation
|
||||
* baseline. The subagent driver stamps `overrideOf(parent.session)` into
|
||||
* each child's creation meta, so the chain collapses one level per
|
||||
* delegation and a tightened parent binds children at any depth
|
||||
* ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)).
|
||||
* @param session - the session whose override chain to resolve.
|
||||
* @returns the effective override, or `undefined` for a session following
|
||||
* the deployment default.
|
||||
* @throws when the durable header baseline is outside the closed mode
|
||||
* vocabulary (a corrupt or foreign log; durable-boundary validation).
|
||||
*/
|
||||
overrideOf(session: Session): SandboxMode | undefined
|
||||
|
||||
/**
|
||||
* Stamp a captured override onto a child session through the canonical
|
||||
* write path — the write half of delegation inheritance: a child agent runs
|
||||
* under the policy its delegating parent was switched to, not under the
|
||||
* (possibly wider) deployment default. A child whose log (e.g. a fork seed)
|
||||
* already folds to the mode is left untouched. Callers must append inside
|
||||
* an open child turn — a bare between-turn event is crash-tail garbage on
|
||||
* reload.
|
||||
* @param child - the child session the override is appended to.
|
||||
* @param mode - the captured {@link overrideOf} value to stamp.
|
||||
*/
|
||||
stampOverride(child: Session, mode: SandboxMode): void
|
||||
```
|
||||
|
||||
Types: [SandboxExecutionPolicy](../core-data-structures/sandbox.md) · [SandboxMode](../core-data-structures/sandbox.md) · [SandboxPolicyRequest](../core-data-structures/sandbox.md) · [Session](../core-data-structures/session.md)
|
||||
@@ -1389,7 +1376,7 @@ fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId):
|
||||
|
||||
Types: [CreateSessionOptions](../core-data-structures/persistence.md) · [OutOfBandSessionEventType](../core-data-structures/session.md) · [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) · [SessionEventMap](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md) · [TurnTrigger](../core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:606`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:614`](../../packages/core/session/src/index.ts)
|
||||
|
||||
## `ctx.sessionTitle` — `SessionTitleService`
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
persistence.md: 4ec967873e946c8f185f8a8f497f2af4a363474e
|
||||
persistence.zh.md: 3030ff2fe949cb02385331800d826df227e3d6cd
|
||||
persistence.md: 0c8d067fc28ee9075354bf32e511e72d66ef530d
|
||||
persistence.zh.md: fcb07486a401283ff52e64a4d8ed2f25e2218187
|
||||
|
||||
@@ -72,12 +72,30 @@ interface SessionHeader {
|
||||
* resume — a runtime-only depth would reset a resumed child to top-level.
|
||||
*/
|
||||
readonly delegationDepth?: number
|
||||
/**
|
||||
* The sandbox-mode override inherited from the delegating parent at
|
||||
* creation (the delegation-inheritance baseline). A neutral string here:
|
||||
* the policy owner (`dsh-sandbox-policy`) validates it against its closed
|
||||
* vocabulary on every read, this being a durable boundary. Absent for
|
||||
* top-level sessions and for children of unswitched parents, which keep
|
||||
* following the LIVE deployment default. Header-carried (the
|
||||
* `delegationDepth` precedent) so the baseline is durable from the creation
|
||||
* moment — no first-turn event survives every crash window, because an
|
||||
* idle injection can persist a complete turn before any prompt turn opens.
|
||||
*/
|
||||
readonly sandboxMode?: string
|
||||
/**
|
||||
* The approval-policy override inherited from the delegating parent at
|
||||
* creation. Same contract as {@link SessionHeader.sandboxMode}; validated
|
||||
* by `dsh-user-approval` on read.
|
||||
*/
|
||||
readonly approvalPolicy?: string
|
||||
}
|
||||
```
|
||||
|
||||
## `CreateSessionOptions` — seeding and metadata
|
||||
|
||||
Creating a `Session` through the store takes a `seed` (replay/fork an existing event log) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the `delegationDepth`, and — only when reconstructing a persisted session — the original `createdAt` to preserve it.
|
||||
Creating a `Session` through the store takes a `seed` (replay/fork an existing event log) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the `delegationDepth`, the inherited `sandboxMode`/`approvalPolicy` delegation baselines, and — only when reconstructing a persisted session — the original `createdAt` to preserve it.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -98,6 +116,8 @@ interface CreateSessionOptions {
|
||||
readonly createdAt?: number
|
||||
readonly seedLength?: number
|
||||
readonly delegationDepth?: number
|
||||
readonly sandboxMode?: string
|
||||
readonly approvalPolicy?: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -72,12 +72,30 @@ interface SessionHeader {
|
||||
* resume — a runtime-only depth would reset a resumed child to top-level.
|
||||
*/
|
||||
readonly delegationDepth?: number
|
||||
/**
|
||||
* The sandbox-mode override inherited from the delegating parent at
|
||||
* creation (the delegation-inheritance baseline). A neutral string here:
|
||||
* the policy owner (`dsh-sandbox-policy`) validates it against its closed
|
||||
* vocabulary on every read, this being a durable boundary. Absent for
|
||||
* top-level sessions and for children of unswitched parents, which keep
|
||||
* following the LIVE deployment default. Header-carried (the
|
||||
* `delegationDepth` precedent) so the baseline is durable from the creation
|
||||
* moment — no first-turn event survives every crash window, because an
|
||||
* idle injection can persist a complete turn before any prompt turn opens.
|
||||
*/
|
||||
readonly sandboxMode?: string
|
||||
/**
|
||||
* The approval-policy override inherited from the delegating parent at
|
||||
* creation. Same contract as {@link SessionHeader.sandboxMode}; validated
|
||||
* by `dsh-user-approval` on read.
|
||||
*/
|
||||
readonly approvalPolicy?: string
|
||||
}
|
||||
```
|
||||
|
||||
## `CreateSessionOptions`:seed 与元数据
|
||||
|
||||
通过 store 创建 `Session` 时会接收 `seed`(回放/fork 现有事件日志)与 `meta`(store 折叠进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、`delegationDepth`,以及——仅在重建已持久化会话时——需要保留的原始 `createdAt`。
|
||||
通过 store 创建 `Session` 时会接收 `seed`(回放/fork 现有事件日志)与 `meta`(store 折叠进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、`delegationDepth`、继承的 `sandboxMode`/`approvalPolicy` 委派基线,以及——仅在重建已持久化会话时——需要保留的原始 `createdAt`。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -98,6 +116,8 @@ interface CreateSessionOptions {
|
||||
readonly createdAt?: number
|
||||
readonly seedLength?: number
|
||||
readonly delegationDepth?: number
|
||||
readonly sandboxMode?: string
|
||||
readonly approvalPolicy?: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `agent/inbox/enqueue` | `emit` | [`packages/core/agent/src/types.ts:316`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), [`goal-session`](../packages/goal/goal-session), [`tui`](../packages/ui/tui) |
|
||||
| `agent/post-step` | `serial` | [`packages/core/agent/src/types.ts:448`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`compact-basic`](../packages/compact/compact-basic), [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy) |
|
||||
| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:379`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`time-context`](../packages/context/time-context), [`user-approval`](../packages/ui/user-approval) |
|
||||
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:395`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
|
||||
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:395`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) |
|
||||
| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:409`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) |
|
||||
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/types.ts:463`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`llm-retry`](../packages/llm/llm-retry), [`plan-mode`](../packages/plan/plan-mode) |
|
||||
| `agent/session-prefix` | `waterfall` | [`packages/core/agent/src/types.ts:424`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) |
|
||||
|
||||
@@ -78,7 +78,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}[T]
|
||||
```
|
||||
|
||||
Sources: [`packages/core/session/src/types.ts:324`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:337`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:366`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:398`](../packages/core/session/src/types.ts)
|
||||
Sources: [`packages/core/session/src/types.ts:344`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:357`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:386`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:418`](../packages/core/session/src/types.ts)
|
||||
|
||||
## Events
|
||||
|
||||
@@ -150,7 +150,7 @@ Source: [`packages/ui/user-approval/src/index.ts:67`](../packages/ui/user-approv
|
||||
|
||||
Types: [StreamChunk](core-data-structures/llm-streaming.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:270`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:290`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `assistant/message` — surface
|
||||
|
||||
@@ -166,7 +166,7 @@ Source: [`packages/core/session/src/types.ts:270`](../packages/core/session/src/
|
||||
|
||||
Types: [ContentBlock](core-data-structures/core.md) · [TokenUsage](core-data-structures/llm-streaming.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:277`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:297`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `compact/*`
|
||||
|
||||
@@ -329,7 +329,7 @@ Source: [`packages/plan/plan-mode/src/index.ts:40`](../packages/plan/plan-mode/s
|
||||
|
||||
Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:268`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:288`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `request/*`
|
||||
|
||||
@@ -343,7 +343,7 @@ Source: [`packages/core/session/src/types.ts:268`](../packages/core/session/src/
|
||||
'request/header': { header: EpochHeader; reason: RequestHeaderReason }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:312`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:332`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `sandbox/*`
|
||||
|
||||
@@ -399,7 +399,7 @@ Source: [`packages/session-title/session-title-llm/src/index.ts:44`](../packages
|
||||
'steering/message': PromptMessageData & { turn: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:305`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:325`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `step/*`
|
||||
|
||||
@@ -410,7 +410,7 @@ Source: [`packages/core/session/src/types.ts:305`](../packages/core/session/src/
|
||||
'step/end': { turn: number; step: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:253`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:273`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `step/start` — log-only
|
||||
|
||||
@@ -419,7 +419,7 @@ Source: [`packages/core/session/src/types.ts:253`](../packages/core/session/src/
|
||||
'step/start': { turn: number; step: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:251`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:271`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `todo/*`
|
||||
|
||||
@@ -432,7 +432,7 @@ Source: [`packages/core/session/src/types.ts:251`](../packages/core/session/src/
|
||||
|
||||
Types: [TodoItem](core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:307`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:327`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `tool/*`
|
||||
|
||||
@@ -449,7 +449,7 @@ Source: [`packages/core/session/src/types.ts:307`](../packages/core/session/src/
|
||||
|
||||
Types: [CallId](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:283`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:303`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `tool/code-dispatch` — log-only
|
||||
|
||||
@@ -503,7 +503,7 @@ Source: [`packages/core/tools/src/code-mode.ts:34`](../packages/core/tools/src/c
|
||||
|
||||
Types: [CallId](core-data-structures/core.md) · [ContentBlock](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:295`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:315`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `turn/*`
|
||||
|
||||
@@ -521,7 +521,7 @@ Source: [`packages/core/session/src/types.ts:295`](../packages/core/session/src/
|
||||
|
||||
Types: [TurnEndReason](core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:249`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:269`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `turn/start` — log-only
|
||||
|
||||
@@ -537,7 +537,7 @@ Source: [`packages/core/session/src/types.ts:249`](../packages/core/session/src/
|
||||
|
||||
Types: [TurnTrigger](core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:242`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:262`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `user/*`
|
||||
|
||||
@@ -556,4 +556,4 @@ Source: [`packages/core/session/src/types.ts:242`](../packages/core/session/src/
|
||||
'user/message': PromptMessageData
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:263`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:283`](../packages/core/session/src/types.ts)
|
||||
|
||||
Reference in New Issue
Block a user