joy-cli/docs/joy-cli-hardening-plan.md
feibisi 9d12fe24eb feat(cli): harden joy ABI — tiers, schema, dashboard package, audit
Phase 0–7 of docs/joy-cli-hardening-plan.md:
- golden fixtures + contract tests (commands/dashboard keys/whitelist)
- command tier metadata; help ≡ manifest (pipeline in core)
- _joy.schema_version on dict outputs; dashboard collect package split
- work_rebuild → shared rebuild; deprecated markers; journal vm-first compat
- web JSONL audit; lazy command import; plugins README
2026-07-10 11:42:32 +08:00

521 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# joy-cli 长期硬化完整方案(兼容三员 / 神兽 / joy web
> **状态****Phase 07 已执行**2026-07-10持续维护见修订表
> **日期**2026-07-10
> **范围**`~/Projects/joy-cli` 主仓;关联 `linuxjoy` 文档/生成模板、`~/office/*/CLAUDE.md|AGENTS.md`、launchd `joy.web` / `joy.dashboard`
> **原则****不破坏在飞轮上的用法**;可删入口只能 **deprecated 转发**,不能 silent 删;一切破坏性变更必须带契约测试与回归清单
---
## 0. 为什么要改(问题浓缩)
| # | 问题 | 风险 |
|---|------|------|
| P1 | 顶级命令 **58**agent 发现成本高 | 神兽/三员 prompt 与 help 漂移 |
| P2 | `dashboard.py` **~3.2k 行** 单体 | 改一处牵全身;回归靠人眼 |
| P3 | help / manifest 不同源(已出现 `pipeline` 漂移) | AI 发现层不可信 |
| P4 | JSON **无 schema_version** | 静默破坏 web/agent 解析 |
| P5 | 权限元数据存在但几乎未启用 | 误用 execute 难追责 |
| P6 | web 白名单含 executeaudit 弱 | 本机决策台误操作成本高 |
| P7 | 测试以 smoke 为主 | 「46 绿」≠ 飞轮语义正确 |
**本方案不是功能扩张**,是:收口、契约化、可维护、可回归。
---
## 1. 在飞轮里的真实调用面(改前必知)
### 1.1 常驻进程Mac launchd
| Label | 命令 | 依赖 |
|-------|------|------|
| `joy.dashboard` | `joy dashboard --watch` | 写 `/tmp/joy-dashboard.json`**字段形状是 web 真源** |
| `joy.web` | `joy web` | 读 cache + `/api/*` + 白名单 action前端 `src/joy/web/**` |
> 改 `dashboard`/`web` 等于改生产观测面。必须 **热替换后验 cache keys + HTTP + 前端关键 tab**。
### 1.2 三位管理员Mac office
| 角色 | 身份检测 | 文档写明的高频 joy 命令 | 影响敏感度 |
|------|----------|-------------------------|------------|
| **konqi 龙奇** | `JOY_IDENTITY` / cwd `office/konqi` / 进程启发式 | `joy konqi *``patrol``cluster-health``clone *``board``recall``poke``message``mind``tmux``infra``api``prlctl``work_rebuild``clone_restart``restart clones` | **极高** |
| **tux 塔司** | office/tux + opencode | `joy tux *``patrol``profile``board``recall``poke``message``mind``api probe` | **极高** |
| **wapuu** | office/wapuu | `joy intel``db``domain``board``recall``poke``message``mind` | **高** |
来源:`~/office/{konqi,tux,wapuu}/{AGENTS,CLAUDE}.md`
### 1.3 神兽16 joy-clones pi
**启动模板真源**`linuxjoy/scripts/mac/joy-clones.sh` 写入各 `~/office/<vm>/CLAUDE.md` 技术段。
神兽标准工具链(每台 VM 同构):
```text
joy patrol <vm>
joy profile <vm>
joy project <vm>
joy curiosity <vm>
joy work <vm> transcript|inject|alive|idle
joy journal <vm> write|last|doubts
joy board ls|claim|done
joy recall search|add
joy signal stuck|done|help
joy delegate / joy who / joy env / joy inbox
joy budget <vm>
```
> **神兽不依赖 joy web**;依赖 **CLI JSON 形状 + 子命令名**。
> 改名只能:旧命令保留转发;或同步改 `joy-clones.sh` 模板并 **rebuild 16 路 CLAUDE.md**。
### 1.4 人 / 菲比斯
- 浏览器 `http://localhost:8899`joy web
- 偶发 CLI`joy help``joy dashboard``joy board``joy web`
### 1.5 当前 dashboard JSON 顶层键(兼容红线)
实测 `/tmp/joy-dashboard.json` 顶层(**禁止无版本迁移就删/改名**
```text
audit_ledger, board_tasks, bottleneck, bridge, capabilities, collect_errors,
curiosity, data_assets, dispatch_queue, events, flywheel, health_grid, ideas,
infra, issues, llm_providers, mac_host, meter, monitoring, nats, new_api,
physical_hosts, pi_agents, pipelines, pulse, security, tmux, trends, ts, vms
```
前端 tabs 与 hero 按这些键投影。**拆 dashboard 文件时必须 golden 对比顶层键集合**。
---
## 2. 影响矩阵(改完会不会影响)
### 2.1 总原则
| 变更类型 | 对 web | 对三员 | 对神兽 | 策略 |
|----------|--------|--------|--------|------|
| 物理拆分 `dashboard.py`(行为不变) | 无感 | 无感 | 无感 | 优先做 |
| help/manifest 同源 + 补 pipeline | 无感 | 发现更好 | 发现更好 | 优先做 |
| JSON 加 `schema_version`**附加字段** | 无感* | 无感* | 无感* | 附加不删 |
| 旧命令 **deprecated 转发**stdout 多 `deprecated` 字段) | 无感 | **兼容** | **兼容** | 允许 |
| 删除顶级命令入口 | 可能破 web action | **会破 AGENTS** | **会破 16 CLAUDE** | **禁止本阶段** |
| 改子命令语义/字段改名 | 破 tab | 破解析 | 破解析 | 仅 schema_version bump + 双写过渡 |
| 收紧 web control 默认 | 部分按钮需确认/失败 | CLI 不受 | 不受 | 可做,需 UI 文案 |
| 懒加载命令 import | 冷启动变快 | 无感 | 无感 | 可做manifest 全量仍要加载 |
\*若某 agent 用「对象键集合严格相等」解析会炸——实测 joy 侧均按字段读取,**附加字段安全**。
### 2.2 明确「不会动」的 ABI冻结列表
下列 **命令路径 + 关键字段** 本阶段冻结;只允许 bugfix 与附加字段:
#### A. 神兽日用 ABI
| 命令 | 冻结点 |
|------|--------|
| `joy patrol <vm>` | 顶层含 session/activity/board/inbox/doubts/llm 类信息(允许附加) |
| `joy work <vm> {transcript,inject,alive,idle}` | 子命令名inject 字符串参数 |
| `joy journal <vm> write\|last\|doubts` | 路径与写 journal.jsonl 行为 |
| `joy board ls\|claim\|done\|create\|route` | 子命令名task id 语义 |
| `joy recall search\|add` | 子命令存在 |
| `joy signal` / `joy inbox` / `joy budget` | 入口存在 |
| `joy profile` / `joy project` / `joy curiosity` / `joy env` / `joy who` / `joy delegate` | 入口存在 |
#### B. 三员日用 ABI
| 角色 | 冻结 |
|------|------|
| konqi | `joy konqi *``clone *``tmux``infra``api``poke``mind``restart``work_rebuild``clone_restart``prlctl` |
| tux | `joy tux *``patrol``profile``board``poke` |
| wapuu | `joy intel``db``domain``board` |
#### C. joy web / dashboard ABI
| 契约 | 冻结 |
|------|------|
| `GET /api/dashboard` | 返回与 cache 同形;顶层键集合 ⊇ 现状 |
| `GET /api/dashboard/lite` | 保留 |
| `POST /api/action` | 白名单前缀兼容args 数组形态 |
| `ACTION_WHITELIST` 已有前缀 | **不删**;可增不可减(本阶段) |
| `/tmp/joy-dashboard.json` 路径 | 不改 |
| launchd 参数 `joy dashboard --watch` / `joy web` | 不改 |
#### D. 身份环境变量
- `JOY_IDENTITY` 优先逻辑保留
- `PYTHONPATH=.../joy-cli/src` launchd 注入保留
- `~/bin/joy``Projects/joy-cli/bin/joy` 符号链接保留
---
## 3. 目标架构(长期)
```
┌─ 人joy web :8899 ─────────────────────────────────────┐
│ 读 /api/dashboard(+lite) 写 /api/action三态+audit
└───────────────────────────┬─────────────────────────────┘
│ 同源
┌───────────────────────────▼─────────────────────────────┐
│ joy CLI = 飞轮 agent ABI稳定
│ A 核心运行时 B 领域扩展 C deprecated 转发 │
│ tools --manifest ≡ help 同源 │
└───────────────────────────┬─────────────────────────────┘
│ façade
┌───────────────────────────▼─────────────────────────────┐
│ linuxjoy scripts / Board / NATS / prlctl / SSH │
└─────────────────────────────────────────────────────────┘
```
**CLI 表面分层(元数据,不是删命令)**
| tier | 含义 | 示例 |
|------|------|------|
| **core** | 日常 ABIhelp 默认展示 | patrol, health, resources, vm, clone, work, tmux, board, journal, message, recall, data, pipeline, idea, dashboard, web, tools, help |
| **extended** | 领域/角色扩展 | konqi, tux, mind, intel, budget, profile… |
| **deprecated** | 仍可用help 标废JSON 可带 `deprecated` | prlctl, clone_restart, work_rebuild转发 work rebuild |
> 核心数量以 **help 展示收口** 为主;**注册表仍保留全部 58**,避免神兽/旧 AGENTS 断裂。
---
## 4. 分阶段执行计划(可验收)
### Phase 0 — 契约冻结与基线0.51 天)
**动作**
1. 本文件入库(`docs/joy-cli-hardening-plan.md`
2. 建立 **golden 基线**(只读采集,不改代码逻辑):
- `tests/fixtures/dashboard_keys_v1.json`:顶层键列表
- `tests/fixtures/command_inventory_v1.json`58 命令名 + requires/env
- `tests/fixtures/web_action_prefixes_v1.json`:白名单前缀
3. 文档化消费者矩阵§1进 README 链
4. 工作区未提交改动:**先 stash/提交归类**,避免硬化过程混入 hardware 等半成品
**验收**
- [ ] golden 文件存在且 CI/本地测试可读
- [ ] `pytest` 现网 46 绿仍过
**影响**:无。
---
### Phase 1 — 发现层与元数据同源0.51 天)**【低风险 · 先做】**
**动作**
1. `@command` 增加字段:
- `tier: core|extended|deprecated`
- `schema_version`(命令级可选)
- `replaced_by`deprecated 用)
2. `HELP_DATA` **从 COMMANDS 生成**(或生成后 diff 强制一致)
3. 补齐 `pipeline` 进入 help/core
4. `tools --manifest` 输出含 `tier``schema_version``identity`
5. 测试:`test_manifest_help_sync.py` — 注册集 == help 集pipeline 在 core
**验收**
- [ ] `joy help --json` 含 pipeline
- [ ] `joy tools --manifest | jq '.commands|length'` ≥ 现网
- [ ] 无命令从注册表消失
**影响**:无功能影响;三员/神兽 `joy help` 更准。
---
### Phase 2 — JSON 契约版本化1 天)**【低风险】**
**动作**
1. 统一成功输出包装策略(**双轨,兼容优先**
- 对「本已是 dict」的响应**附加** `"schema_version": 1`(或 `"_joy": {"schema_version":1}` 若担心冲突)
- 推荐 **`_joy.schema_version`** 命名空间,避免污染业务字段
2. 优先覆盖:`dashboard` cache、`patrol``board ls``tools --manifest``data *``pipeline *``health`
3. 错误 JSON 保持 `{error, code, detail?}` 不变
**验收**
- [ ] 旧解析(读业务字段)仍通过
- [ ] 新测试 assert `_joy.schema_version` 或顶层附加字段存在
- [ ] `/tmp/joy-dashboard.json` 仍含全部旧顶层键
**影响**web 前端忽略未知字段 → **无感**
---
### Phase 3 — dashboard 物理拆分12 天)**【中风险 · 行为冻结】**
**目标**`dashboard.py` 从 3k 行降到「编排壳 + collect 模块」,**输出字节级语义不变**(允许键序/空白差)。
**建议目录**
```text
src/joy/commands/dashboard.py # CLI 入口 + compose + --watch
src/joy/dashboard/
__init__.py
compose.py # 拼总 dict + 写 cache
schema.py # 顶层键常量 + schema_version
collectors/
mac_host.py
tmux_pi.py
vms_health.py
board_dispatch.py
llm_gateway.py
data_assets.py
pipelines.py
security_infra.py
...
```
**硬规则**
1. **禁止**本阶段改 collector 业务算法(除明确 bug
2. 每拆一块:`pytest` + **golden keys** + 可选 `joy dashboard --json | jq 'keys'` 对比
3. 去掉 `shell=True` 时改为 argv 列表,**对比 stdout 再合**
4. launchd `joy.dashboard` 不改参数;拆完后 `launchctl kickstart -k gui/$UID/joy.dashboard` 验证
**验收**
- [ ] 顶层键集合 == golden
- [ ] `pulse/bottleneck/vms/pi_agents/pipelines/data_assets` 抽样字段类型不变
- [ ] `joy.dashboard` KeepAlive 稳定 ≥ 2 个 watch 周期
- [ ] web 总览 / 神兽 / 任务 / 流水线 / 数据 tab 人工点检
**影响**:若 compose 漏字段 → **web 破**。用 golden 挡。
---
### Phase 4 — deprecated 转发不删入口0.51 天)**【中风险 · 兼容】**
**只做转发,不做删除**
| 旧入口 | 转发到 | 额外 |
|--------|--------|------|
| `joy prlctl ...` | `joy vm ...`(已有) | 确保 help 标废 |
| `joy clone_restart <vm>` | `joy clone restart <vm>` | 已有,补测 |
| `joy work_rebuild <vm>` | `joy work <vm> rebuild`**新增 sub**,旧入口保留) | 双入口 |
| (可选)`joy uptime` / `joy disk` | 内部调 `resources` 同数据 | 入口保留 |
**禁止本阶段**
- 删除 `work_rebuild` 顶级名konqi CLAUDE 仍写它)
- 删除 `prlctl` / `clone_restart`
**同步文档(非阻断)**
- 更新 `linuxjoy/docs/tools/joy-cli-reference.md`
- 更新 `office/konqi/CLAUDE.md` 推荐新写法,**旧写法仍可用**
**验收**
- [ ] 旧命令 exit 0 行为与新命令一致(同 vm 测)
- [ ] 响应可含 `"deprecated": "use joy ..."` 但不影响主字段
**影响**:三员旧 prompt **继续工作**
---
### Phase 5 — web 可追责与 action 统一12 天)**【中风险】**
**动作**
1. `/api/action` 统一路径:
`parse args → tier_for_args → whitelist → (control 门闩) → 调 joy CLI 同进程/子进程 → audit append`
2. audit`~/logs/joy-web-audit.jsonl``/tmp`+NAS 可选字段ts, identity, tier, args, rc, ms, err_snip
3. UIexecute 二次确认文案统一control 默认关(已有则加固)
4. **不删** 现有白名单前缀
**验收**
- [ ] `test_web_action_whitelist` 扩展:非法前缀 403/拒绝
- [ ] 合法 `patrol devops` / `idea list` / `board ls` 类 observe/register 可用
- [ ] audit 文件有追加
- [ ] 前端按钮仍能点通 3 个代表动作
**影响**:人用 web 更安全CLI 与神兽 **不受影响**
---
### Phase 6 — 运行时质量(懒加载 / Env / 清理1 天)**【低–中】**
1. **懒加载**`main` 仅 import 目标 command 模块;`tools --manifest` / `help` 才全量 import
2. `Env` 支持测试注入(不改 singleton 对外行为)
3. inventory 失败:**stderr 诊断 + 明确 fallback 列表**,不 silent
4. 删除空目录 `commands/``lib/``domains/*.sh``archive/domains/`(确认无引用)
5. `plugins/` 写 README实验命令准入规则
**验收**
- [ ] `time joy help` 不劣化;`time joy patrol` 可改善或持平
- [ ] 全量 pytest 绿
**影响**:应无感;若懒加载漏注册 → 命令「未知」——用 manifest 测试挡。
---
### Phase 7 — 测试金字塔与发布门禁(贯穿 + 收尾 1 天)
#### 7.1 测试分层
| 层 | 内容 | 门禁 |
|----|------|------|
| L0 静态 | `python -m compileall src/joy` | 必过 |
| L1 unit | cluster_data, pipeline_registry, action_tiers, env identity | 必过 |
| L2 contract | manifest↔helpdashboard keys goldenweb whitelist goldenschema_version | 必过 |
| L3 smoke CLI | 每条 **core** 命令 `--help` 或无参不崩JSON 错误形态 | 必过 |
| L4 integration | board ls / data catalog / pipeline listNAS 在则跑,不在则 skip | 尽力 |
| L5 live Mac | dashboard --json keyscurl web lite/dashboardlaunchd 状态 | 发布前必做 |
| L6 agent 回归 | 见 §5 检查单 | 发布前必做 |
#### 7.2 发布检查单Definition of Done
```text
[ ] pytest 全绿(新增 contract 全过)
[ ] goldencommands 集合未减少
[ ] goldendashboard 顶层键未减少
[ ] goldenweb action 前缀未减少
[ ] joy help 含 pipelinetools --manifest 与 help 一致
[ ] launchctl print joy.dashboard / joy.web 为 running
[ ] curl -s localhost:8899/ | head 200
[ ] curl -s localhost:8899/api/dashboard/lite 含 pulse/ts
[ ] /tmp/joy-dashboard.json 新鲜ts 在 2min 内)
[ ] 人工web 总览、神兽、任务、流水线、数据 tab 无空白/JS 错
[ ] 代表 CLI
JOY_IDENTITY=konqi joy konqi overview | head
JOY_IDENTITY=tux joy tux pending | head
JOY_IDENTITY=wapuu joy intel brief | head # 或 domains
joy patrol devops | head
joy board ls | head
joy clone status | head
joy work devops alive | head
[ ] 神兽模板joy-clones.sh 技术段命令仍全部可执行(抽 1 台 devops
[ ] 未改 JOY_IDENTITY / ~/bin/joy / launchd ProgramArguments
```
#### 7.3 回滚
| 场景 | 回滚 |
|------|------|
| dashboard 采集坏 | `git revert` 对应 commit`launchctl kickstart -k gui/$UID/joy.dashboard` |
| web 起不来 | 同上 + `kickstart joy.web` |
| 命令丢失 | 禁止出现;若出现从 git 恢复 `commands/*.py` |
| 半成品分支 | 功能开关:无(本阶段不做大开关);用 git 回滚 |
---
## 5. 消费者回归检查单(详细)
### 5.1 joy web
| 检查 | 方法 | 期望 |
|------|------|------|
| 服务存活 | `launchctl list \| rg joy.web` | PID 在 |
| 首页 | 浏览器 :8899 | 总览有 pulse |
| lite API | `curl /api/dashboard/lite` | 200 + ts |
| 全量 API | `curl /api/dashboard \| jq 'keys'` | ⊇ golden |
| 白名单列表 | `curl /api/actions` | 前缀齐全 |
| 写操作 | UIidea list / journal last / patrol 单 VM | 成功或明确错误 JSON |
| 样式/JS | DevTools Console | 无硬错误 |
### 5.2 三员
| 角色 | 命令烟测 | 权限烟测 |
|------|----------|----------|
| konqi | `JOY_IDENTITY=konqi joy konqi overview` | `JOY_IDENTITY=wenpai joy konqi overview` → E_PERM |
| tux | `JOY_IDENTITY=tux joy tux pending` | 非 tux → E_PERM |
| wapuu | `JOY_IDENTITY=wapuu joy board ls` | 可用 |
| 通信 | `joy poke` dry 路径(避免真注入可 mock/短消息) | requires 含三员 |
| 旧名 | `joy work_rebuild` / `joy clone_restart` / `joy prlctl` | 仍可用或明确转发 |
### 5.3 神兽
| 检查 | 方法 |
|------|------|
| 模板命令可运行 | 对 `devops` 跑 joy-clones 技术段每一条read-only 优先) |
| journal 写 | `joy journal devops last 1` |
| work 观测 | `joy work devops alive` / `transcript 3` |
| board | `joy board ls --vm devops` |
| 不依赖 web | 断 web 后 CLI 仍可用(可临时停 joy.web 再启) |
| rebuild 策略 | **默认不 rebuild 16 神兽**;仅当改 CLAUDE 模板时才 `joy-clones` 重生配置 |
### 5.4 VM 上 joy若已 deploy
| 检查 | 方法 |
|------|------|
| 存在 | `ssh devops 'joy --version'` |
| 基础 | `ssh devops 'joy help \| head'` |
| 与 Mac 差集 | VM 无 prlctl/web/dashboard 属预期env 限制) |
---
## 6. 明确不做(本方案非目标)
1. 重写前端框架 / 换 React
2. 删减 58→15 的硬删除(只做分层与转发)
3. 改 Board/NATS/dispatch 协议
4. 改 new-api / auto-pi 路由
5. 强制 16 神兽全部 rebuild除非模板变更
6. 生产防火墙、跨 VM 批量高危 prlctl
7. 把 joy web 变成真派活引擎dispatch 仍是真源)
---
## 7. 推荐执行顺序与工期
| 顺序 | Phase | 工期 | 风险 | 可独立合并 |
|------|-------|------|------|------------|
| 1 | Phase 0 基线 | 0.5d | 低 | ✅ |
| 2 | Phase 1 发现层 | 0.51d | 低 | ✅ |
| 3 | Phase 2 schema_version | 1d | 低 | ✅ |
| 4 | Phase 3 dashboard 拆分 | 12d | 中 | ✅ 需 L5 验 web |
| 5 | Phase 4 deprecated 转发 | 0.51d | 中 | ✅ |
| 6 | Phase 5 web audit | 12d | 中 | ✅ |
| 7 | Phase 6 懒加载/清理 | 1d | 低–中 | ✅ |
| 8 | Phase 7 门禁固化 | 1d | 低 | 与上并行 |
**合计约 610 人日**。建议 **每 Phase 单独 commit**,每 Phase 跑 §7.2 检查单再进下一 Phase。
---
## 8. 与既有文档关系
| 文档 | 关系 |
|------|------|
| `docs/joy-panel-cli-architecture-plan.md` | 产品 IA / 核心 15 愿景;**本方案是其「兼容优先落地版」** |
| `linuxjoy/docs/specs/2026-07-07-joy-python-rewrite.md` | Python 重写已完成;本方案是 2.0 后硬化 |
| `linuxjoy/docs/tools/joy-cli-reference.md` | 需在 Phase 4/7 同步(可滞后代码一个 commit |
| `office/*/AGENTS.md` + `joy-clones.sh` | **消费者**;改模板仅当 ABI 真变 |
---
## 9. 决策请求(执行前需菲比斯确认)
| # | 问题 | 建议默认 |
|---|------|----------|
| D1 | 是否按 Phase 0→7 全做? | **是**,按序 |
| D2 | schema 附加字段用 `_joy.schema_version` 还是顶层 `schema_version` | **`_joy` 命名空间** 更安全 |
| D3 | `work_rebuild` 是否增加 `work rebuild` sub 并保留旧入口? | **是** |
| D4 | 本阶段是否允许「仅文档」把 konqi 的 `prlctl` 写法改成 `vm` | 是,但代码转发必须先在 |
| D5 | web execute 是否强制二次确认(已有则加强)? | **是** |
| D6 | 是否在本方案中处理未提交 hardware tab | **分开 PR**,不与硬化混提 |
---
## 10. 一句话总结
**在不打断 joy web 常驻、三员 AGENTS、神兽 CLAUDE 的前提下,把 joy-cli 从「58 命令功能堆」收成「可版本化、可测试、可转发废弃」的长期 ABI先契约与拆分后加固与清理每一刀都用 golden + 消费者检查单验收。**
---
## 11. 修订
| 日期 | 变更 |
|------|------|
| 2026-07-10 | 首版完整方案:调用面审计 + 影响矩阵 + Phase 07 + 测试门禁 |
## 12. 执行记录
| 日期 | 内容 |
|------|------|
| 2026-07-10 | Phase 07 落地golden fixtures、tier/help 同源、`_joy.schema_version`、dashboard 包拆分、deprecated 转发、web JSONL audit、懒加载、contract 测试pytest 54 绿launchd web/dashboard 回归 |