fix(testing): reconcile ORCH-116 with merged ORCH-123 (ADR renumber, CHANGELOG, env parity)
Recovery from the merge-gate rebase-conflict bounce. The feature branch was rebased onto origin/main (which had merged ORCH-123). The single conflicting hunk — docs/architecture/README.md — was resolved during the rebase: kept ORCH-123's host-side staging-runner line AND the ORCH-116 test-runner bullet. This follow-up commit reconciles the remainder: - Renumber the global sweeping ADR adr-0049 -> adr-0050. ORCH-123 took adr-0049 (adr-0049-host-side-docker-execution-boundary.md) on main while ORCH-116 was in flight, so ORCH-116 yields to the merged task and moves to the next free number. Mechanical cross-reference reconciliation only (git mv + title + every test-runner reference across README/internals/CLAUDE/CHANGELOG/config.py + 06-adr/ADR-001 + 12-review). Main's adr-0049 host-side references are left byte-for-byte untouched. No design/verdict content was altered. - Restore the ORCH-116 CHANGELOG entry that the CHANGELOG auto-merge silently dropped (both ORCH-123 and ORCH-116 inserted at the same [Unreleased] anchor; git kept only ORCH-123). - Add the missing ORCH_TEST_RUNNER_* keys to .env.example (parity with the ORCH_STAGING_RUNNER_* block; ORCH-101 canon of start keys). Refs: ORCH-116 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
28
.env.example
28
.env.example
@@ -585,6 +585,34 @@ ORCH_STAGING_RUNNER_INFRA_MAX_RETRIES=2
|
||||
ORCH_STAGING_RUNNER_INFRA_RETRY_DELAY_S=30
|
||||
ORCH_STAGING_RUNNER_EXEC_HOST_SIDE=true
|
||||
|
||||
# ORCH-116: deterministic test-runner replacing the LLM `tester` agent on the
|
||||
# `testing` stage for the self-hosting orchestrator (2nd determinization slice,
|
||||
# mirror of the ORCH-115 staging-runner). A leaf src/test_runner.py is intercepted
|
||||
# in launch_job BEFORE _spawn: it runs the SAME regression `python -m pytest <target>`
|
||||
# in the task worktree (+ optional read-only smoke), maps the exit-code -> result:
|
||||
# PASS|FAIL, writes 13-test-report.md and initiates the UNCHANGED check_tests_passed
|
||||
# gate. Replaces only the producer of the artifact; the gate / STAGE_TRANSITIONS / DB
|
||||
# schema are byte-for-byte unchanged. See ADR-001-deterministic-test-runner.md / adr-0050.
|
||||
# TEST_RUNNER_ENABLED -> kill-switch; false -> the prior LLM tester runs on
|
||||
# testing via _spawn 1:1.
|
||||
# TEST_RUNNER_REPOS -> CSV scope; empty -> self-hosting only. A repo with
|
||||
# no resolvable test-contract is never intercepted (BR-9).
|
||||
# TEST_RUNNER_TARGET -> pytest target of the test-contract (default tests/).
|
||||
# TEST_RUNNER_TIMEOUT_S -> wall-clock budget for the pytest regression
|
||||
# (malformed/non-positive -> default 900 + WARNING).
|
||||
# TEST_RUNNER_SMOKE_ENABLED -> optional read-only smoke (/health,/status,/queue +
|
||||
# serial_gate block); false -> pytest exit-code is the sole signal.
|
||||
# TEST_RUNNER_INFRA_MAX_RETRIES -> tool-error (suite did NOT execute) bounded DEFER
|
||||
# budget before a fail-closed FAIL (anti ORCH-110).
|
||||
# TEST_RUNNER_INFRA_RETRY_DELAY_S-> delay before the re-queued tester job.
|
||||
ORCH_TEST_RUNNER_ENABLED=true
|
||||
ORCH_TEST_RUNNER_REPOS=
|
||||
ORCH_TEST_RUNNER_TARGET=tests/
|
||||
ORCH_TEST_RUNNER_TIMEOUT_S=900
|
||||
ORCH_TEST_RUNNER_SMOKE_ENABLED=true
|
||||
ORCH_TEST_RUNNER_INFRA_MAX_RETRIES=2
|
||||
ORCH_TEST_RUNNER_INFRA_RETRY_DELAY_S=30
|
||||
|
||||
# ORCH-057 (follow-up ORCH-040): legacy root-owned ownership detect + actionable
|
||||
# worktree error. After the uid migration (user: "1000:1000") legacy root:root files
|
||||
# in /repos broke worktree creation under uid 1000 with a raw "Permission denied".
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
Формат: [Keep a Changelog](https://keepachangelog.com/). Записи — на смысловой PR/задачу.
|
||||
|
||||
## [Unreleased]
|
||||
- **Детерминированный test-раннер вместо LLM-тестера на `testing`** (ORCH-116, `feat`): второй реализованный срез determinization-roadmap (ORCH-118 A5, `needs-hybrid-fallback`) — на стадии `testing` для self-hosting `orchestrator` **LLM-агент `tester` заменён детерминированным кодом** (`src/test_runner.py`). PASS/FAIL-ядро агента было деривируемым (регресс `pytest` + read-only smoke → `result:`); каждый прогон жёг токены/время opus-агента (~60–150k / 5–20 мин) и встраивал недетерминизм LLM в точку ветвления `testing → deploy-staging` / `testing → development`. **Инвариант (NFR-1):** это замена *продюсера* артефакта, **не** гейта — контракт `13-test-report.md`, гейт `check_tests_passed`/`_parse_tests_verdict`, `STAGE_TRANSITIONS`, machine-verdict `result:` (+ legacy `verdict:`/`status:`), схема БД — **байт-в-байт не тронуты**. Аддитивно, под kill-switch, never-raise, fail-closed, скоуп self-hosting, гибрид (LLM строго off-control-path). Эталон — `src/staging_runner.py` (ORCH-115). ADR: `docs/work-items/ORCH-116/06-adr/ADR-001-deterministic-test-runner.md`, сквозной `docs/architecture/adr/adr-0050-deterministic-test-runner.md`.
|
||||
- **Перехват в `launch_job` до `_spawn` (D1):** `if job.agent=="tester" and test_runner.should_intercept(job)` → `_run_test_runner_job` (зеркало `_run_staging_runner_job`, прецедент `deploy-finalizer`/`post-deploy-monitor`/`staging-runner` `launcher.py:397/402/405`): синхронно ведёт `jobs`-строку через `mark_job`, возвращает `None` (нет `agent_runs`, нет токенов). Дискриминатор — роль `tester` **И** стадия задачи `testing` (defense-in-depth: `tester` — единственный агент входа в `testing`, коллизии стадий нет, в отличие от общей роли `deployer`) **И** `applies(repo)`; `should_intercept` never-raise → `False` → штатный `_spawn` (fail-safe к LLM-пути).
|
||||
- **Leaf `src/test_runner.py` (новый, чистый never-raise):** по образцу `staging_runner`/`self_deploy`/`proc_group` (на импорте только `config`/`proc_group`; `db`/`git_worktree`/`self_deploy`/`qg.checks`/`stage_engine`/`notifications` — лениво). `applies(repo)` = kill-switch `test_runner_enabled` + скоуп `test_runner_repos` (пусто → self-hosting only) **И** резолв тест-контракта `_has_test_contract` (BR-9: репо без контракта → `False` → LLM-tester — enduro-trails 1:1 как до ORCH-116, даже если руками добавлен в CSV). Исполняет регресс `python -m pytest <test_runner_target>` **в worktree ветки** (`git_worktree.get_worktree_path`, анти checkout-гонка ORCH-112) через `proc_group.run_in_process_group` (tree-kill, таймаут `test_runner_timeout_s=900`, малформ/непозитив → дефолт + WARNING) + опц. **read-only smoke** (`/health`/`/status`/`/queue` + блок `serial_gate`, stdlib `urllib`; транзиентная недостижимость — ограниченный ретрай, не-200/нет блока — немедленный FAIL; `test_runner_smoke_enabled`). Маппит exit-код **единым** контрактом `self_deploy.map_exit_code_to_status` в токенах `result:` (`0→PASS`/иначе/None→`FAIL`, fail-closed; smoke-провал AND-ится в `FAIL`); пишет `13-test-report.md` (тот же machine-key `result:` UPPERCASE + 52c-схема, `author_agent: test-runner`/`model_used: n/a`) + best-effort push в **фичеветку**; вызывает **существующий** `advance_stage(current_stage="testing", finished_agent="tester")` — без новых рёбер/исходов (transition-lease ORCH-114 берётся внутри `advance_stage` — граница O1).
|
||||
- **Анти-коллизия 52c-`status:` ↔ парсер (D6.1, специфично для tester):** `_parse_tests_verdict` читает вердикт из **трёх** равноранговых полей (`verdict:`/`status:`/`result:`) с negative-token-priority. 52c-обязательное `status:` поэтому читается тем же парсером → раннер **ВСЕГДА выравнивает** `status:` по вердикту (`PASS → status: success`, `FAIL → status: failed`) — иначе негативный токен в `status:` при `result: PASS` дал бы ложный FAIL здорового прогона. Прибито unit-тестом через **неизменённый** парсер.
|
||||
- **Двухуровневый исход (D5, анти-ORCH-110):** сюита **исполнилась** (реальный exit-код) → verdict→advance (FAIL → тот же откат `testing → development` + developer-retry, что у FAIL-вердикта LLM, `stage_engine.py:849`); сюита **не исполнилась** (tool-error: spawn-error/таймаут/`returncode None`) → инфра-сбой ≠ код-фейл → bounded **DEFER** (re-queue **`tester`**-джоба с задержкой + restart-safe маркер `test-runner infra-retry` в `task_content`, счётчик подсчётом маркера — без правки схемы БД), на исчерпании `test_runner_infra_max_retries=2` → fail-closed `FAIL` + advance + INFRA-alert. Раннер **никогда** не делает тихий advance/ложный green, **никогда** не клинит очередь, **не** жжёт developer-retry на транзиентной инфре.
|
||||
- **Гибрид (D11/BR-8/NFR-7):** в Phase 1 на `testing` (in-scope) вердикт `result:` производит **только** детерминированный раннер; LLM **не** вызывается в потоке управления вердикта. Архитектура не запрещает будущий **off-control-path** LLM-триаж падений / маппинг TC↔критерии после детерминированного FAIL (отдельная роль/джоб, **не** выносит и **не** переопределяет `result:`, **не** добавляет ребро в `STAGE_TRANSITIONS`) — в этом срезе не реализуется. Self-hosting safety: в командах раннера нет рестарта 8500 / `docker compose up orchestrator` / `--build` / force-push / правок `.env`; smoke строго read-only. Наблюдаемость — in-process счётчики (`runs`/`pass`/`fail`/`tool_error`/`deferred`) + read-only блок `test_runner` в `GET /queue` + один структурный лог-вердикт на прогон (различает код-фейл и tool-error). Флаги (`config.py`, дефолт = боевое): `test_runner_enabled`/`test_runner_repos`/`test_runner_target`/`test_runner_timeout_s`/`test_runner_smoke_enabled`/`test_runner_infra_max_retries`/`test_runner_infra_retry_delay_s` (env `ORCH_TEST_RUNNER_*`). Откат = `ORCH_TEST_RUNNER_ENABLED=false` → на `testing` снова LLM-`tester` через `_spawn` **байт-в-байт**.
|
||||
- **Норматив сопровождения ORCH-118 (NFR-6):** обновлены `docs/architecture/llm-call-sites.md` (A5 — реализован; машинный `ORCH-118-INVENTORY-BLOCK` сохраняет tester как `avoidable=yes`/`axis=C`/`needs-hybrid-fallback`), `llm-determinization-roadmap.md` (rank 2 tester — ✅ реализован; инвариант «ровно один `first_slice = yes`» у rank 1 deployer цел), `llm-usage-policy.md` (§5 — единственный транспорт S0 не нарушен, раннер LLM не зовёт), `.openclaw/agents/tester.md` (LLM-ветвь `testing` — fallback), `docs/architecture/README.md`/`internals.md`, витрина `docs/overview/tech-pipeline.md`/`tech-agents.md`/`tech-quality-security.md`. Покрытие — `tests/test_orch116_test_runner.py` (TC-01…TC-14) + зелёные `tests/test_llm_call_site_inventory.py`/`test_llm_determinization_docs.py` (TC-15).
|
||||
- **Host-side исполнение staging-раннера + классификация environment-дефекта** (ORCH-123, `fix`, bug→escalate full-cycle): устранён инцидент **ORCH-116** — детерминированный staging-раннер (ORCH-115) вызывал `docker exec` **изнутри** прод-контейнера `orchestrator`, где **нет бинаря `docker`** (образ несёт только `openssh-client git curl`, `Dockerfile:11`; `/var/run/docker.sock` смонтирован, но клиента нет) → `Popen(["docker", …])` падал `FileNotFoundError` → ветка tool-error → инфра-DEFER×2 → fail-closed `FAILED` → **ложный** откат `deploy-staging → development` (как код-фейл, с расходом developer-retry). Так до фикса **любая** self-hosting задача, дойдя до `deploy-staging`, была обречена на ложный откат. Аддитивно, под флагами, never-raise, скоуп self-hosting; `STAGE_TRANSITIONS` / реестр `QG_CHECKS` / семантика и имена `check_staging_status`/`_parse_staging_status` / machine-verdict-ключи (`staging_status:`/`deploy_status:`) / схема БД — **байт-в-байт не тронуты** (замена *стратегии исполнения продюсера* `15-staging-log.md`, **не** гейта/стадии; зеркало инварианта ORCH-115 NFR-1). ADR: `docs/work-items/ORCH-123/06-adr/ADR-001-host-side-staging-execution-and-env-classification.md`, сквозной `docs/architecture/adr/adr-0049-host-side-docker-execution-boundary.md`.
|
||||
- **Host-side ssh-стратегия (D1):** `staging_runner.build_staging_command()` теперь обёртывает ту же `docker exec orchestrator-staging python3 … staging_check.py … --mode stub` в `ssh -o StrictHostKeyChecking=no <deploy_ssh_user>@<deploy_ssh_host> '<docker exec …>'` (зеркало `self_deploy.build_deploy_command` / `image_freshness.image_revision(ssh_target=…)`); канал — существующий доверенный (`ORCH_DEPLOY_SSH_HOST=127.0.0.1`, ssh-ключ смонтирован `:ro`, `openssh-client` в образе) → **новых секретов/привилегий не вводится** (NFR-3). Меняется **инициатор/канал** запуска, **не** сама сюита (она по-прежнему бежит **внутри** `orchestrator-staging` 8501). **Security (D2):** docker CLI/SDK в контейнер **не добавляется**, `docker.sock` **не используется изнутри** — это было бы root-эквивалентным расширением поверхности атаки (доступным и LLM-агентам); host-side ssh достигает цели без расширения привилегий.
|
||||
- **Трёхсторонняя классификация исхода (D3, чистая `classify_staging_outcome`, зеркало `merge_gate.classify_retest_failure`):** `suite-ran` (распознанный exit-код, кроме 255, **без** env-маркера в stderr → доверяем коду: `0→SUCCESS`/`≠0→FAILED`; анти-over-tolerance BR-3 — реальный фейл сюиты **никогда** не реклассифицируется в инфру), `permanent-env` (spawn-error `rc=None` без таймаута / нет ssh-target / `rc∈{126,127}` / env-маркер `No such container`/`Cannot connect to the Docker daemon`/`command not found` → ретрай бессмыслен), `transient-infra` (timeout / ssh transport `rc=255` / неизвестный сигнал → ретрай осмыслен). Дизамбигуация коллизии `exit=1` (`docker exec` «No such container»=1 vs суита fail=1) — **скан stderr на env-маркеры**, не голый exit-код; fail-safe при неоднозначности → `transient-infra` (DEFER), никогда тихий `suite-ran`.
|
||||
|
||||
@@ -548,7 +548,7 @@ Plane, **не** Quality Gate и **не** стадия).
|
||||
целы) + `tester.md` (LLM-ветвь — fallback). Покрытие — `tests/test_orch116_test_runner.py`
|
||||
(TC-01…TC-14) + зелёные LLM-анти-дрейф тесты (TC-15). Детали —
|
||||
`docs/work-items/ORCH-116/06-adr/ADR-001-deterministic-test-runner.md`, сквозной
|
||||
`docs/architecture/adr/adr-0049-deterministic-test-runner.md`.
|
||||
`docs/architecture/adr/adr-0050-deterministic-test-runner.md`.
|
||||
|
||||
## Машинный журнал уроков (ORCH-098)
|
||||
Шаг 1 («Фундамент», F2) эпика саморазвития: формализует свободнотекстовые «уроки» из `memory/` в
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@ created_at: 2026-06-16
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# adr-0049: Детерминированный test-раннер — второй реализованный срез determinization-roadmap (tester-гибрид)
|
||||
# adr-0050: Детерминированный test-раннер — второй реализованный срез determinization-roadmap (tester-гибрид)
|
||||
|
||||
> **Сквозной (cross-cutting) ADR.** Агрегирует решение ORCH-116, влияющее на **весь**
|
||||
> оркестратор: вводит новый компонент-leaf `src/test_runner.py`, снимает вторую avoidable
|
||||
@@ -109,7 +109,7 @@ LLM). Leaf `src/staging_runner.py` (зеркало `run_deploy_finalizer`) ис
|
||||
`self_deploy.map_exit_code_to_status`, пишет `15-staging-log.md` (тот же machine-key `staging_status:`)
|
||||
и вызывает существующий `advance_stage(finished_agent="deployer")` (см. §5). Так LLM-агент `deployer`
|
||||
на `deploy-staging` исчезает из happy-path; `STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД не тронуты.
|
||||
**ORCH-116 ([adr-0049](adr/adr-0049-deterministic-test-runner.md)):** тем же паттерном (рядом с
|
||||
**ORCH-116 ([adr-0050](adr/adr-0050-deterministic-test-runner.md)):** тем же паттерном (рядом с
|
||||
ORCH-115) перехватывается джоб `tester` на стадии `testing` для in-scope репо с тест-контрактом
|
||||
(дискриминатор — роль `tester` **И** `tasks.stage == "testing"` **И** `test_runner.applies(repo)` под
|
||||
kill-switch `test_runner_enabled`, скоуп `test_runner_repos`, резолв `_has_test_contract`; пусто →
|
||||
|
||||
@@ -13,7 +13,7 @@ Work Item: **ORCH-116** — заменить LLM-агента `tester` на ст
|
||||
(self-hosting `orchestrator`) детерминированным test-раннером (второй срез
|
||||
determinization-roadmap, **rank 2 / tester-гибрид**).
|
||||
Стадия: **architecture**
|
||||
Сквозная регистрация: **`docs/architecture/adr/adr-0049-deterministic-test-runner.md`**
|
||||
Сквозная регистрация: **`docs/architecture/adr/adr-0050-deterministic-test-runner.md`**
|
||||
(решение кросс-каттинговое — вводит новый компонент-leaf `src/test_runner.py` и реализует
|
||||
второй срез determinization-roadmap; влияет на карту LLM-консультаций всего оркестратора).
|
||||
|
||||
@@ -459,7 +459,7 @@ LLM-консультации; политика соблюдена.
|
||||
- Тест-план: `docs/work-items/ORCH-116/04-test-plan.yaml`
|
||||
- Инфра: `docs/work-items/ORCH-116/07-infra-requirements.md`;
|
||||
Данные: `08-data-requirements.md`; Риски: `10-tech-risks.md`
|
||||
- Сквозной ADR: `docs/architecture/adr/adr-0049-deterministic-test-runner.md`
|
||||
- Сквозной ADR: `docs/architecture/adr/adr-0050-deterministic-test-runner.md`
|
||||
- Эталон реализации: `src/staging_runner.py` (ORCH-115),
|
||||
`docs/work-items/ORCH-115/06-adr/ADR-001-deterministic-staging-runner.md`
|
||||
- Сверено по коду: `src/agents/launcher.py:397/404/438`, `src/stages.py:17-18`,
|
||||
|
||||
@@ -23,7 +23,7 @@ leaf `src/test_runner.py`, перехватываемым в `launch_job` **до
|
||||
байт-в-байт: `src/stages.py`, `src/qg/checks.py`, `src/staging_runner.py`, `src/transition_lease.py`,
|
||||
`src/proc_group.py` — **не тронуты** (подтверждено `git diff --stat`); в `src/` изменены только
|
||||
`test_runner.py` (новый), `launcher.py`/`config.py`/`main.py` (аддитивно). Документация = golden
|
||||
source обновлена в том же PR (8 doc-файлов + CHANGELOG + ADR-001/adr-0049). Полный регресс
|
||||
source обновлена в том же PR (8 doc-файлов + CHANGELOG + ADR-001/adr-0050). Полный регресс
|
||||
**проверен лично**: `pytest tests/ -q` → **2137 passed**; ORCH-116 + LLM-анти-дрейф + system-docs →
|
||||
**74 passed**. **P0/P1/P2 findings отсутствуют → `APPROVED`.**
|
||||
|
||||
@@ -68,7 +68,7 @@ source обновлена в том же PR (8 doc-файлов + CHANGELOG + AD
|
||||
|
||||
### 2. Соответствие ADR — PASS
|
||||
|
||||
Реализация дословно следует ADR-001 (D1–D12) и сквозному adr-0049. Сверено критическое:
|
||||
Реализация дословно следует ADR-001 (D1–D12) и сквозному adr-0050. Сверено критическое:
|
||||
- **D6.1 (анти-коллизия 52c-`status:` ↔ `_parse_tests_verdict`)** — самая тонкая мина задачи.
|
||||
Сверено по **неизменённому** парсеру (`src/qg/checks.py:226-282`): он читает вердикт из ТРЁХ
|
||||
равноранговых полей `verdict:`/`status:`/`result:` c negative-token-priority. `build_test_report`
|
||||
@@ -124,7 +124,7 @@ source обновлена в том же PR (8 doc-файлов + CHANGELOG + AD
|
||||
fallback под выключенным флагом / для репо без контракта»; канон 52d (5 секций, ключ `result:`) цел.
|
||||
- **`CHANGELOG.md`** — развёрнутая запись `[Unreleased]`.
|
||||
- **ADR** заведён (`06-adr/ADR-001-deterministic-test-runner.md` + сквозной
|
||||
`adr/adr-0049-deterministic-test-runner.md`).
|
||||
`adr/adr-0050-deterministic-test-runner.md`).
|
||||
|
||||
Обзорные доки / README «Известные ограничения» (ORCH-079): раннер — **новая способность**, не
|
||||
закрытие документированного ограничения → дополнительных правок README не требуется.
|
||||
@@ -163,7 +163,7 @@ source обновлена в том же PR (8 doc-файлов + CHANGELOG + AD
|
||||
Обновлена в полном объёме в том же PR (см. ось 4): `CLAUDE.md`, `docs/architecture/README.md`,
|
||||
`internals.md`, `llm-call-sites.md`, `llm-determinization-roadmap.md`, `llm-usage-policy.md`,
|
||||
`docs/overview/{tech-pipeline,tech-agents,tech-quality-security}.md`, `.openclaw/agents/tester.md`,
|
||||
`CHANGELOG.md`, ADR-001 + сквозной adr-0049. Анти-дрейф-тесты документации
|
||||
`CHANGELOG.md`, ADR-001 + сквозной adr-0050. Анти-дрейф-тесты документации
|
||||
(`test_llm_call_site_inventory.py`, `test_llm_determinization_docs.py`, `test_system_docs.py`) —
|
||||
зелёные (лично прогнаны). Невыполненных требований к обновлению документации **нет**.
|
||||
|
||||
|
||||
@@ -489,7 +489,7 @@ class Settings(BaseSettings):
|
||||
# STAGE_TRANSITIONS and the DB schema are byte-for-byte UNCHANGED — this only
|
||||
# replaces the *producer* of the artifact. Pattern = staging_runner_* / coverage_gate_*.
|
||||
# See docs/work-items/ORCH-116/06-adr/ADR-001-deterministic-test-runner.md and
|
||||
# docs/architecture/adr/adr-0049-deterministic-test-runner.md.
|
||||
# docs/architecture/adr/adr-0050-deterministic-test-runner.md.
|
||||
# test_runner_enabled -> SINGLE kill-switch (env ORCH_TEST_RUNNER_ENABLED).
|
||||
# False -> the intercept never fires -> the prior
|
||||
# LLM tester runs on testing via _spawn byte-for-byte
|
||||
|
||||
Reference in New Issue
Block a user