# PPAS v0.9.5

## PipiPong Pet Animation Specification

**文档状态：** v0.9.5 实施基线（Implementation Baseline）  
**适用产品：** PipiPong Desktop Pet v0.9.5  
**规范标识：** `PPAS-0.9.5`  
**宠物包扩展名：** `.pppet`  
**建议 MIME 类型：** `application/vnd.pipipong.pet+zip`  
**目标读者：** PipiPong 客户端开发者、宠物包创作者、Pet Package Builder 开发者、市场审核与安全团队  
**文档语言：** 简体中文  
**语言一致性：** 简体中文与英文版本具有同等设计意图；如存在无法调和的歧义，以机器可读 Schema、注册表和一致性测试为准。  

---

## 0. 规范性用语

本规范使用以下关键词：

- **必须（MUST）**：实现或宠物包必须满足，否则不符合 PPAS v0.9.5。
- **不得（MUST NOT）**：明确禁止。
- **应该（SHOULD）**：强烈建议；如不遵守，应有明确理由。
- **不应该（SHOULD NOT）**：通常不应使用；如使用，应有明确理由。
- **可以（MAY）**：可选能力。

所有不认识的权限、事件提供器、必需扩展或动作节点，宿主程序必须采用 **fail-closed**：拒绝加载相关能力，而不是猜测执行。

---

# 1. 规范目标

PPAS v0.9.5 用于定义一种可安装、可验证、可执行、可分享的桌面宠物形象与行为包。一个符合规范的宠物包不仅可以包含角色图片和动画，还可以声明：

1. 一组所有宠物都必须具备的核心动作；
2. 创作者额外设计的自定义简单或复杂动作；
3. 动作在什么情况下被触发；
4. 宠物希望观察哪些 PipiPong 事件或电脑系统事件；
5. 观察系统事件所需的权限、范围、用途与数据处理方式；
6. 动作在运行时的优先级、打断策略、超时和回退行为。

PPAS v0.9.5 的核心目标是：

- **兼容性**：所有合法宠物包都能被同一套运行时识别和执行；
- **设计自由度**：创作者可以自由增加动作、动作组合和触发条件；
- **明确同意**：下载者在下载和启用前能明确看到包需要观察什么；
- **最小权限**：宠物包只能使用事先声明并由用户授权的能力；
- **安全执行**：宠物包不得包含任意代码，也不得直接访问操作系统；
- **跨平台**：同一宠物包可通过统一协议适配 macOS 和 Windows；
- **向后扩展**：未来可增加 Rive、Spine、Live2D 等渲染适配器，而不破坏现有包。

---

# 2. v0.9.5 的范围与非目标

## 2.1 v0.9.5 必须支持

PipiPong v0.9.5 必须支持：

- `.pppet` 包的导入、验证、安装、启用、禁用、卸载和回退；
- `sprite-sequence-v1` 序列帧动画渲染；
- PPAS Core Minimum 的 8 个必备动作；
- PipiPong 官方“孵化新宠物”流程输出 PPAS Core Standard 的 10 个动作；
- 自定义单段动画动作；
- 声明式复杂动作图；
- PipiPong 内部事件触发；
- 用户交互事件触发；
- 一组受控的电脑系统事件触发；
- 必需权限和可选权限声明；
- 按宠物包隔离的虚拟权限；
- 宿主侧事件匹配和过滤；
- 动作优先级、队列、打断、超时和 fallback；
- 沙箱预览和运行时故障恢复；
- 包更新时的权限差异检查和重新授权。

## 2.2 v0.9.5 不包含

以下能力不属于 PPAS v0.9.5：

- 任意 JavaScript、TypeScript、Python、Lua、Shell、Wasm 或原生代码；
- `.dll`、`.dylib`、`.node`、浏览器扩展或 Electron 插件；
- 宠物包直接访问文件系统、网络、剪贴板、摄像头、麦克风或屏幕；
- 宠物包直接运行程序、控制其他应用或调用 Codex；
- 自定义系统事件监听代码；
- 自定义渲染器插件；
- 读取文件内容、窗口正文、键盘输入、浏览器历史、Cookie 或密码；
- 远程加载动画、图片、音频或配置；
- 宠物包自行上传任何系统观察数据。

上述能力如未来开放，必须使用与普通宠物包分离的插件规范和更严格的审核模型。

---

# 3. 概念模型

PPAS 将一个宠物包拆分为六个相互隔离的层：

```text
电脑系统 / PipiPong / 用户交互
                │
                ▼
       System Event Providers
                │
                ▼
      Permission Broker + Host Filter
                │
                ▼
           Trigger Engine
                │
                ▼
          Pet Action Runtime
                │
        ┌───────┴────────┐
        ▼                ▼
 Animation Renderer   Safe Host Capabilities
 序列帧等             移动、缩放、声音、特效
```

主要对象定义如下：

| 对象 | 定义 |
|---|---|
| Pet Package | 一个 `.pppet` 安装包 |
| Clip | 一段可播放的底层动画资产 |
| Core Action | PipiPong 预定义语义动作，如 `core.working` |
| Custom Action | 创作者自行定义的动作，如 `custom.throw-paper` |
| Action Graph | 将多个动画、移动、等待、声音和分支组合成复杂动作的声明式图 |
| Event Provider | 由 PipiPong 实现的事件来源，如前台应用、电量、文件夹变化 |
| Trigger | “发生什么 + 满足什么条件 + 执行什么动作”的规则 |
| Capability | 宠物包希望使用的一项受控系统观察能力 |
| Permission Grant | 用户针对某个包、某个版本和某个范围授予的权限 |
| Host Filter | PipiPong 在宿主侧检查原始信息，只向动作运行时发送匹配结果 |

---

# 4. `.pppet` 容器格式

## 4.1 基本格式

`.pppet` 必须是标准 ZIP 容器，但文件扩展名必须为 `.pppet`。

容器必须满足：

- 所有文本文件使用 UTF-8；
- 所有路径使用 `/`；
- 所有路径必须为相对路径；
- 不得包含符号链接、硬链接或设备文件；
- 不得包含 `../`、绝对路径、盘符或路径穿越；
- 文件名大小写敏感；
- 根目录必须存在 `manifest.json`；
- 包内不得包含可执行文件或脚本；
- 所有运行所需资产必须在包内，不得引用远程 URL。

## 4.2 标准目录结构

```text
example-pet.pppet
├── manifest.json
├── previews/
│   ├── thumbnail.webp
│   └── cover.webp
├── animations/
│   ├── index.json
│   ├── core/
│   │   ├── idle/
│   │   ├── listening/
│   │   ├── thinking/
│   │   ├── working/
│   │   ├── success/
│   │   ├── error/
│   │   ├── clicked/
│   │   ├── dragging/
│   │   ├── wake/
│   │   └── sleep/
│   └── custom/
├── actions/
│   ├── core.json
│   ├── custom.json
│   └── graphs/
├── triggers/
│   └── triggers.json
├── capabilities/
│   └── capabilities.json
├── audio/
├── effects/
├── i18n/
│   ├── zh-CN.json
│   └── en-US.json
├── licenses/
│   └── NOTICE.txt
└── integrity.json
```

## 4.3 必需文件

| 文件 | 是否必须 | 说明 |
|---|---:|---|
| `manifest.json` | 是 | 包入口与兼容性声明 |
| `previews/thumbnail.webp` | 是 | 安装页和市场缩略图 |
| `animations/index.json` | 是 | 动画 clip 注册表 |
| `actions/core.json` | 是 | 核心动作映射 |
| `actions/custom.json` | 是 | 可以为空数组，但文件必须存在 |
| `triggers/triggers.json` | 是 | 可以为空数组，但文件必须存在 |
| `capabilities/capabilities.json` | 是 | 可以为空数组，但文件必须存在 |
| 至少一种语言文件 | 是 | 必须包含默认语言 |
| `integrity.json` | 本地包建议，市场包必须 | 文件哈希和签名信息 |

## 4.4 v0.9.5 资源限制

宿主必须在解压前和解压过程中执行限制：

| 项目 | 上限 |
|---|---:|
| 压缩后包大小 | 200 MiB |
| 解压后总大小 | 500 MiB |
| 文件总数 | 5,000 |
| 单个文件 | 100 MiB |
| 单张图片尺寸 | 4096 × 4096 px |
| 建议动画画布 | 512 × 512 或 1024 × 1024 px |
| 单段音频长度 | 30 秒 |
| 音频总大小 | 50 MiB |
| 自定义动作数量 | 256 |
| 触发器数量 | 128 |
| 单个动作图节点数 | 256 |
| 动作图最大嵌套深度 | 16 |
| 单次动作默认最大时长 | 60 秒 |

超过上限的包必须被拒绝，不得仅警告后继续运行。

---

# 5. `manifest.json`

## 5.1 必需字段

```json
{
  "ppasVersion": "0.9.5",
  "packageId": "creator.alice.sleepy-pig",
  "packageVersion": "1.0.0",
  "defaultLocale": "zh-CN",
  "nameKey": "package.name",
  "descriptionKey": "package.description",
  "author": {
    "name": "Alice",
    "creatorId": "alice"
  },
  "license": "LicenseRef-PipiPong-Creator-Standard",
  "compatibility": {
    "minAppVersion": "0.9.5",
    "platforms": ["macos", "windows"],
    "requiredRendererProfiles": ["sprite-sequence-v1"]
  },
  "coreProfile": "PPAS-Core-Standard-0.9.5",
  "entryPoints": {
    "animations": "animations/index.json",
    "coreActions": "actions/core.json",
    "customActions": "actions/custom.json",
    "triggers": "triggers/triggers.json",
    "capabilities": "capabilities/capabilities.json"
  },
  "previews": {
    "thumbnail": "previews/thumbnail.webp",
    "cover": "previews/cover.webp"
  },
  "extensionsUsed": [],
  "extensionsRequired": [],
  "dataPractices": {
    "localProcessingOnly": true,
    "networkTransmission": "none",
    "rawEventStorage": "none",
    "hostManagedTriggerState": true
  }
}
```

## 5.2 字段规则

### `ppasVersion`

- v0.9.5 必须为字符串 `"0.9.5"`。
- 不得使用包版本代替规范版本。

### `packageId`

- 必须在包生命周期内稳定；
- 建议格式：`creator.<creatorId>.<petSlug>`；
- 只能包含小写英文字母、数字、`.`、`-` 和 `_`；
- 长度 3–128 个字符；
- 市场发布时必须全局唯一；
- 本地导入冲突时，客户端必须要求用户替换、并存新 ID 或取消安装，不得静默覆盖。

### `packageVersion`

- 必须使用语义化版本 `major.minor.patch`；
- 权限或触发范围扩大时必须至少提升 minor 版本；
- 不兼容的动作或资源结构变化必须提升 major 版本。

### `license`

- 必须使用 SPDX ID 或 `LicenseRef-*`；
- 包含第三方资源时，必须在 `licenses/NOTICE.txt` 列出来源和授权。

### `extensionsUsed` 与 `extensionsRequired`

- 不认识 `extensionsUsed` 中的可选扩展时，宿主可以忽略该扩展；
- 不认识 `extensionsRequired` 中任何一项时，宿主必须拒绝启用该包；
- v0.9.5 不允许扩展绕过权限、沙箱或禁止能力。

---

# 6. 动画资源规范

## 6.1 v0.9.5 强制渲染配置

PipiPong v0.9.5 必须实现：

```text
sprite-sequence-v1
```

Rive、dotLottie、Spine、Live2D 可以在未来作为适配器加入，但不得作为 v0.9.5 基础兼容包的必需渲染器。

## 6.2 图片格式

- 必须支持：WebP、PNG；
- 应优先使用带透明通道的 WebP；
- 必须使用 sRGB；
- 同一 clip 中所有帧应该使用相同画布尺寸；
- 不得使用远程图片；
- 不得使用 SVG 中的脚本、外部资源或活动内容。

## 6.3 `animations/index.json`

```json
{
  "rendererProfile": "sprite-sequence-v1",
  "clips": [
    {
      "id": "clip.idle",
      "canvas": { "width": 1024, "height": 1024 },
      "anchor": { "x": 0.5, "y": 0.94 },
      "loop": { "mode": "infinite" },
      "frames": [
        { "src": "animations/core/idle/0001.webp", "durationMs": 83 },
        { "src": "animations/core/idle/0002.webp", "durationMs": 83 }
      ],
      "markers": [
        { "id": "action.safeToInterrupt", "frame": 1 }
      ]
    }
  ]
}
```

## 6.4 Clip 必需字段

| 字段 | 规则 |
|---|---|
| `id` | 包内唯一，建议使用 `clip.*` |
| `canvas` | 正整数宽高 |
| `anchor` | 归一化坐标，范围 0–1 |
| `frames` | 至少 1 帧 |
| `frames[].src` | 必须是包内相对路径 |
| `frames[].durationMs` | 16–2000 ms |
| `loop.mode` | `none`、`count` 或 `infinite` |
| `markers` | 可选，marker ID 在同一 clip 内必须唯一 |

## 6.5 坐标系

- 原点位于动画画布左上角；
- X 向右增加，Y 向下增加；
- `anchor` 使用 0–1 归一化坐标；
- `anchor` 表示宠物与桌面的主要接触点；
- 默认 anchor 为 `{ "x": 0.5, "y": 0.95 }`；
- 宿主移动宠物窗口时必须以 anchor 而不是图像左上角作为逻辑位置。

## 6.6 标准 Marker

PPAS v0.9.5 保留以下 marker：

```text
action.safeToInterrupt
action.phaseComplete
footstep.left
footstep.right
takeoff
land
grip
release
impact
effect.start
effect.end
```

创作者自定义 marker 必须使用：

```text
custom.<slug>
```

`action.complete` 不需要显式声明，clip 播放结束即视为完成。

## 6.7 音频

- v0.9.5 必须支持 OGG 和 WAV；
- 音频必须来自包内；
- 音量范围为 0–1；
- 默认音量不得超过 0.8；
- 不得自动播放持续性背景音乐；
- 用户必须能在 PipiPong 设置中统一关闭宠物包音效。

---

# 7. Core Action Profile

## 7.1 语义动作原则

PipiPong 只调用标准动作 ID，不依赖宠物内部动画文件名。宠物包必须把自己的 clip 或动作图映射到标准动作。

例如：

```json
{
  "bindings": {
    "core.idle": "clip.lazy-sit",
    "core.working": "clip.hammer-keyboard",
    "core.success": "clip.throw-confetti"
  }
}
```

## 7.2 PPAS Core Minimum：8 个必备动作

任何可启用的 v0.9.5 宠物包必须提供以下 8 个动作：

| 动作 ID | 语义 | 形式 | 默认行为 |
|---|---|---|---|
| `core.idle` | 默认待机 | 循环 | 没有更高优先级动作时播放 |
| `core.listening` | 正在听取用户语音 | 循环 | 语音监听期间播放 |
| `core.thinking` | AI 正在理解、规划或等待 | 循环 | 思考阶段播放 |
| `core.working` | 正在执行任务 | 循环 | 执行阶段播放 |
| `core.success` | 任务成功或操作完成 | 单次 | 完成后回到 idle |
| `core.error` | 任务失败或发生错误 | 单次 | 完成后回到 idle |
| `core.clicked` | 用户点击宠物 | 单次 | 可被高优先级事件打断 |
| `core.dragging` | 用户正在拖动宠物 | 循环 | 拖动结束后回到 idle 或 dropped |

缺少任意一个 Core Minimum 动作时，包必须被判定为不兼容并拒绝启用。

## 7.3 PPAS Core Standard：10 个官方孵化动作

PipiPong v0.9.5 的“孵化新宠物”功能必须生成 Core Minimum 的 8 个动作，并额外生成：

| 动作 ID | 语义 | 形式 |
|---|---|---|
| `core.wake` | 宠物被唤醒或应用启动 | 单次 |
| `core.sleep` | 长时间空闲后的睡眠 | 循环 |

因此官方孵化流程默认输出 10 个核心动作。

第三方导入包缺少 `core.wake` 或 `core.sleep` 时仍可通过 Minimum 验证，但宿主必须使用以下 fallback：

```text
core.wake  → core.idle
core.sleep → core.idle
```

## 7.4 推荐动作

以下动作不属于 v0.9.5 必备项，但建议支持：

```text
core.hover
core.dropped
core.cancelled
core.confused
core.offline
core.walk
core.run
core.jump
```

## 7.5 `actions/core.json`

```json
{
  "profile": "PPAS-Core-Standard-0.9.5",
  "bindings": {
    "core.idle": { "type": "clip", "ref": "clip.idle" },
    "core.listening": { "type": "clip", "ref": "clip.listening" },
    "core.thinking": { "type": "clip", "ref": "clip.thinking" },
    "core.working": { "type": "clip", "ref": "clip.working" },
    "core.success": { "type": "clip", "ref": "clip.success" },
    "core.error": { "type": "clip", "ref": "clip.error" },
    "core.clicked": { "type": "clip", "ref": "clip.clicked" },
    "core.dragging": { "type": "clip", "ref": "clip.dragging" },
    "core.wake": { "type": "clip", "ref": "clip.wake" },
    "core.sleep": { "type": "clip", "ref": "clip.sleep" }
  },
  "fallbacks": {
    "core.wake": "core.idle",
    "core.sleep": "core.idle",
    "core.hover": "core.idle",
    "core.dropped": "core.idle"
  }
}
```

同一 clip 可以临时映射到多个核心动作，但市场质量认证可以要求核心反馈在视觉上明显可区分。

---

# 8. 自定义动作

## 8.1 ID 规则

- 自定义动作 ID 必须以 `custom.` 开头；
- 示例：`custom.drink-coffee`、`custom.throw-paper`；
- 包内 ID 必须唯一；
- 宿主内部规范化 ID 为：

```text
<packageId>#<actionId>
```

例如：

```text
creator.alice.sleepy-pig#custom.throw-paper
```

## 8.2 动作类型

v0.9.5 支持：

1. **clip action**：直接播放一个 clip；
2. **graph action**：执行一个声明式动作图。

## 8.3 `actions/custom.json`

```json
{
  "actions": [
    {
      "id": "custom.throw-paper",
      "nameKey": "action.throwPaper.name",
      "descriptionKey": "action.throwPaper.description",
      "type": "graph",
      "ref": "actions/graphs/throw-paper.json",
      "tags": ["error", "comedy", "document"],
      "priority": 45,
      "interrupt": {
        "policy": "atMarker",
        "markers": ["action.safeToInterrupt"]
      },
      "maxDurationMs": 12000,
      "fallback": "core.error"
    }
  ]
}
```

## 8.4 自定义动作字段

| 字段 | 规则 |
|---|---|
| `id` | 必须为 `custom.*` |
| `type` | `clip` 或 `graph` |
| `ref` | 对应 clip ID 或动作图路径 |
| `tags` | 可选，用于搜索、随机选择和市场展示 |
| `priority` | 10–60，超出时宿主必须夹紧到允许范围或拒绝 |
| `interrupt.policy` | `immediate`、`atMarker`、`afterIteration` |
| `maxDurationMs` | 100–60000 ms |
| `fallback` | 必须引用一个已存在动作，通常为核心动作 |

自定义动作不得声明真正不可中断。宿主在安全提示、退出、崩溃恢复和用户直接交互时始终拥有强制中止权。

---

# 9. Action Graph v0.9.5

## 9.1 设计原则

复杂动作必须使用声明式 Action Graph，不得包含脚本、表达式执行器或任意代码。

## 9.2 支持节点

| 节点 | 作用 |
|---|---|
| `sequence` | 按顺序执行子节点 |
| `parallel` | 同时执行子节点 |
| `playClip` | 播放动画 clip |
| `wait` | 等待固定时间 |
| `waitMarker` | 等待正在播放的 clip 到达 marker |
| `movePet` | 移动宠物窗口 |
| `transformPet` | 缩放、旋转或水平翻转 |
| `playSound` | 播放包内音频 |
| `spawnEffect` | 生成受控本地特效 |
| `branch` | 根据允许的上下文字段选择分支 |
| `random` | 按权重选择一个子节点 |
| `loop` | 有上限的循环 |
| `finish` | 显式结束动作 |

## 9.3 明确禁止的节点

```text
executeCode
shell
openApplication
openUrl
networkRequest
readFile
writeFile
clipboard
screenCapture
keyboardInput
mouseControl
invokeCodex
```

任何未知节点必须使该动作验证失败。

## 9.4 复杂动作示例

```json
{
  "id": "graph.throw-paper",
  "root": {
    "type": "sequence",
    "children": [
      {
        "type": "playClip",
        "clip": "clip.notice-document",
        "await": "complete"
      },
      {
        "type": "parallel",
        "children": [
          {
            "type": "playClip",
            "clip": "clip.run",
            "loopUntil": "movement.run1.completed"
          },
          {
            "type": "movePet",
            "id": "movement.run1",
            "target": {
              "type": "screenNormalized",
              "x": 0.75,
              "y": 0.85
            },
            "durationMs": 900,
            "easing": "ease-in-out"
          }
        ]
      },
      {
        "type": "playClip",
        "clip": "clip.throw-paper",
        "awaitMarker": "release"
      },
      {
        "type": "playSound",
        "asset": "audio/paper-throw.ogg",
        "volume": 0.7
      },
      {
        "type": "finish",
        "result": "success"
      }
    ]
  }
}
```

## 9.5 运行约束

- `loop` 必须设置 `maxIterations` 或 `maxDurationMs`；
- `parallel` 必须声明完成策略：`all`、`any` 或 `firstFailure`；
- `branch` 只能读取 PPAS 允许的归一化上下文字段；
- 动作图不得读取系统事件原始载荷；
- 单个动作图不得超过 256 个节点；
- 嵌套深度不得超过 16；
- 宿主必须在达到动作最大时长时终止并执行 fallback。

## 9.6 允许的动作上下文

```text
context.platform
context.triggerId
context.triggerCategory
context.pet.facing
context.pet.scale
context.pet.screenEdge
context.task.status
context.task.progress
context.randomBucket
```

任何文件名、路径、窗口标题、剪贴板内容、键盘输入或屏幕内容不得进入动作上下文。

---

# 10. 触发器模型

## 10.1 基本语义

一个触发器表示：

```text
当某个事件发生
并且满足声明的条件
在通过权限和频率限制后
执行某个动作
```

触发器只能使用 PipiPong 提供的 Event Provider，不得携带自定义监听代码。

## 10.2 触发器结构

```json
{
  "id": "trigger.vscode-foreground",
  "nameKey": "trigger.vscodeForeground.name",
  "descriptionKey": "trigger.vscodeForeground.description",
  "enabledByDefault": true,
  "capabilityRefs": ["cap.vscode-foreground"],
  "when": {
    "provider": "application.foreground",
    "event": "entered"
  },
  "match": {
    "resourceRefs": ["app.vscode"]
  },
  "conditions": {
    "all": []
  },
  "then": {
    "action": "custom.start-coding",
    "mode": "replaceAmbient",
    "priority": 50
  },
  "debounceMs": 500,
  "cooldownMs": 3000,
  "probability": 1.0,
  "maxRunsPerHour": 30,
  "onUnavailable": "disableTrigger"
}
```

## 10.3 触发模式

`then.mode` 支持：

| 模式 | 含义 |
|---|---|
| `enqueue` | 在当前动作结束后执行 |
| `replaceAmbient` | 只替换低优先级待机或环境动作 |
| `beforeCore` | 在对应核心反馈前执行 |
| `afterCore` | 在对应核心反馈后执行 |
| `parallelVisual` | 仅允许与不冲突的视觉特效并行 |
| `randomAlternative` | 按概率替代一个允许被替代的非安全核心动作 |

宠物包不得替代安全确认、权限提示、删除确认、严重错误和用户直接操作反馈。

## 10.4 条件运算符

v0.9.5 支持：

```text
eq
neq
lt
lte
gt
gte
in
notIn
between
changedTo
```

字符串匹配仅可用于宿主允许的固定枚举或包内资源 ID。对文件名、窗口标题等敏感字段的匹配必须在宿主侧执行，匹配结果不得把原始字符串传入宠物运行时。

条件组合支持：

```text
all
any
not
```

最大嵌套深度为 4。

## 10.5 频率与防打扰

每个触发器必须允许配置：

- `debounceMs`：事件抖动过滤；
- `cooldownMs`：触发后冷却；
- `probability`：0–1；
- `maxRunsPerHour`：每小时最大次数；
- `maxRunsPerDay`：可选；
- `enabledByDefault`：安装后默认是否启用。

宿主可以基于全局防打扰设置进一步降低频率，但不得提高包声明的频率。

---

# 11. v0.9.5 Event Provider Catalogue

## 11.1 PipiPong 与用户交互事件

这些事件不需要额外系统观察权限，但仍必须在触发器列表中对用户可见。

### `pet.interaction`

```text
clicked
doubleClicked
longPressed
hoverStarted
hoverEnded
dragStarted
dragEnded
dropped
```

### `pipipong.task`

```text
started
thinkingStarted
executionStarted
progressChanged
completed
failed
cancelled
```

### `pipipong.voice`

```text
listeningStarted
listeningEnded
transcriptReady
```

## 11.2 无敏感数据的系统事件

### `time.schedule`

用于固定时间段、星期或周期触发。

```text
matched
```

允许条件：

```text
daysOfWeek
localTimeRange
intervalMinutes
```

### `system.power`

```text
batteryLevelChanged
chargingStarted
chargingStopped
lowPowerEntered
lowPowerExited
```

允许的归一化字段：

```text
battery.levelPercent
battery.charging
```

### `system.session`

```text
locked
unlocked
sleeping
woke
```

### `system.network`

```text
online
offline
```

不得读取网络流量、域名、通信内容或连接列表。

### `display.configuration`

```text
displayConnected
displayDisconnected
primaryDisplayChanged
```

不得读取屏幕内容。

## 11.3 需要显式包级授权的系统事件

### `system.idle`

```text
thresholdReached
activityResumed
```

允许字段：

```text
idle.durationSeconds
```

包必须声明具体阈值，不得持续接收高频原始活动数据。

### `application.lifecycle`

```text
launched
terminated
```

只能观察包中声明的指定应用集合。

### `application.foreground`

```text
entered
left
```

只能判断指定应用是否进入或离开前台。不得读取窗口正文、文档内容或键盘输入。

应用资源可声明为：

```json
{
  "id": "app.vscode",
  "type": "application",
  "matchers": {
    "macos": {
      "bundleIds": ["com.microsoft.VSCode"]
    },
    "windows": {
      "processNames": ["Code.exe"],
      "appUserModelIds": []
    }
  },
  "displayName": "Visual Studio Code"
}
```

### `filesystem.watch`

v0.9.5 仅允许观察用户在授权界面中主动选择的目录。

支持事件：

```text
fileCreated
fileRenamed
fileDeleted
fileModified
```

默认建议只开放 `fileCreated`。

允许过滤：

```text
fileExtensions
restrictedGlob
recursive
settleMs
```

限制：

- 不得读取文件内容；
- 不得把真实路径或文件名传入动画运行时；
- 创作者不得硬编码用户主目录、桌面或整个磁盘；
- `restrictedGlob` 只允许简单 glob，不允许正则表达式；
- 宿主在本地完成过滤后，只发送 `trigger.matched`；
- 目录权限必须绑定到用户选择后生成的 opaque token。

### `media.playback`（平台可选）

```text
started
paused
stopped
```

v0.9.5 可以把该 Provider 标记为平台可选。若当前平台不支持，相关触发器必须被禁用并向用户说明，不得导致整个包崩溃。

## 11.4 v0.9.5 明确禁止的 Provider

```text
keyboard.global
clipboard.content
screen.content
camera
microphone.background
window.content
window.title.raw
browser.history
browser.cookies
file.content
shell
process.inject
network.traffic
```

---

# 12. 系统能力与权限声明

## 12.1 权限分类

`capabilities.json` 必须将能力分为三类：

1. `declarations`：需要展示，但不需要额外包级授权；
2. `required`：拒绝后包不能完整启用；
3. `optional`：拒绝后仅禁用相关触发器。

所有电脑系统事件能力，无论是否需要操作系统弹窗，都必须在下载前向用户展示。

## 12.2 `capabilities.json` 示例

```json
{
  "resources": [
    {
      "id": "app.vscode",
      "type": "application",
      "displayName": "Visual Studio Code",
      "matchers": {
        "macos": { "bundleIds": ["com.microsoft.VSCode"] },
        "windows": { "processNames": ["Code.exe"] }
      }
    },
    {
      "id": "dir.pdf-inbox",
      "type": "userSelectedDirectory",
      "labelKey": "resource.pdfInbox.label"
    }
  ],
  "declarations": [
    {
      "id": "cap.power-status",
      "capability": "system.power.observe",
      "purposeKey": "capability.power.purpose",
      "dataLevel": "statusOnly",
      "retention": "none",
      "networkTransmission": "none"
    }
  ],
  "required": [
    {
      "id": "cap.vscode-foreground",
      "capability": "application.foreground.observe",
      "resourceRefs": ["app.vscode"],
      "purposeKey": "capability.vscode.purpose",
      "dataLevel": "hostFilteredMatchOnly",
      "retention": "none",
      "networkTransmission": "none",
      "onDenied": "disablePackage"
    }
  ],
  "optional": [
    {
      "id": "cap.pdf-inbox",
      "capability": "filesystem.directoryChanges.observe",
      "resourceRefs": ["dir.pdf-inbox"],
      "allowedEvents": ["fileCreated"],
      "filters": {
        "fileExtensions": ["pdf"],
        "recursive": false,
        "settleMs": 1500
      },
      "purposeKey": "capability.pdfInbox.purpose",
      "dataLevel": "hostFilteredMatchOnly",
      "retention": "none",
      "networkTransmission": "none",
      "onDenied": "disableDependentTriggers"
    }
  ]
}
```

## 12.3 必需声明字段

每项能力必须写清楚：

| 字段 | 含义 |
|---|---|
| `capability` | 需要的系统观察能力 |
| `resourceRefs` | 具体应用、目录或资源范围 |
| `purposeKey` | 为什么需要，用普通语言说明 |
| `dataLevel` | 宿主会检查到什么粒度 |
| `retention` | 是否保存原始数据 |
| `networkTransmission` | 是否传出设备；v0.9.5 必须为 `none` |
| `onDenied` | 用户拒绝后的行为 |

仅写“为了改善体验”“为了提供完整功能”不构成有效目的说明。目的必须描述具体触发和具体动作，例如：

> 当 Visual Studio Code 进入前台时，宠物会戴上眼镜并开始敲键盘。

## 12.4 下载和启用前置条件

### 市场下载

PipiPong 市场必须在下载按钮前显示由宿主根据机器可读声明生成的摘要：

```text
这个宠物包需要：

必需
• 判断 Visual Studio Code 是否进入前台
  用途：进入编程软件时播放“开始写代码”动作
  数据：只判断是否匹配，不读取窗口内容
  保存：不保存
  上传：不上传

可选
• 观察你选择的一个文件夹中新建的 PDF
  用途：新 PDF 出现时播放“捡文件”动作
  数据：PipiPong 在本地匹配；宠物只收到“条件已满足”
  保存：不保存
  上传：不上传
```

用户必须先确认已查看这些条件，才能开始下载。

### 启用

用户必须明确接受全部 required 能力，包才能激活。

可选按钮：

```text
[允许并启用]
[仅启用基础模式] 仅当包声明了可用降级模式
[取消]
```

本地导入包可以先读取 manifest 进行预检，但在用户授权前不得启动任何 watcher、播放自动触发动作或访问系统事件提供器。

## 12.5 按包虚拟权限

操作系统通常只知道 PipiPong 获得了权限，不知道具体宠物包。因此 PipiPong 必须实现按包隔离：

```text
OS 权限
  └─ PipiPong Permission Broker
       ├─ 包 A：允许观察 VS Code 前台状态
       ├─ 包 B：无应用观察权限
       └─ 包 C：允许观察用户选择的 PDF 目录
```

权限必须至少绑定：

```text
packageId
packageVersion
capabilityId
resourceScope
grantState
grantedAt
```

包不得继承其他包的授权。

## 12.6 宿主侧过滤

系统原始信息不得直接进入宠物渲染进程。

正确流程：

```text
系统原始事件
→ PipiPong Provider
→ 权限与范围检查
→ 本地条件匹配
→ 只发送 triggerId 和最小归一化上下文
→ 动作执行
```

动作运行时收到：

```json
{
  "event": "trigger.matched",
  "triggerId": "trigger.vscode-foreground",
  "category": "application.foreground"
}
```

不得收到：

```text
完整窗口标题
真实文件路径
文件名列表
应用使用历史
键盘输入
剪贴板内容
```

---

# 13. 权限更新与撤销

## 13.1 包更新

更新时必须比较旧版和新版能力声明。

| 变化 | 处理方式 |
|---|---|
| 只增加动画或修复资源 | 正常更新 |
| 删除权限 | 自动撤销不再需要的 watcher |
| 新增 optional 权限 | 默认关闭，用户主动开启 |
| 新增 required 权限 | 暂停新版激活，重新确认 |
| 扩大应用或目录范围 | 视为新增权限，重新确认 |
| 提高数据粒度 | 重新确认 |
| 修改用途说明 | 若为实质变化，重新确认 |

不得通过把旧 capability ID 保持不变来绕过范围变化检测。

## 13.2 用户撤销

用户必须能在：

```text
Settings → Appearance → 当前宠物 → 权限与触发器
```

执行：

- 撤销单项权限；
- 禁用单项触发器；
- 更换用户选择目录；
- 查看最近触发记录；
- 恢复默认设置；
- 完全禁用该包的系统观察能力。

撤销权限后，相关 watcher 必须立即停止，且依赖触发器必须显示为不可用。

---

# 14. 数据与隐私规则

## 14.1 v0.9.5 固定要求

所有 PPAS v0.9.5 包必须满足：

```json
{
  "localProcessingOnly": true,
  "networkTransmission": "none",
  "rawEventStorage": "none"
}
```

宠物包不得声明其他值。

## 14.2 允许保存的宿主状态

PipiPong 可以保存以下最小状态：

- 用户是否启用某项权限；
- 用户为某资源选择的 opaque token；
- 触发器是否启用；
- 冷却时间戳；
- 每小时或每日执行计数；
- 当前选中的宠物包与版本。

不得保存：

- 文件名历史；
- 完整路径历史；
- 应用使用时间线；
- 窗口标题；
- 屏幕内容；
- 原始系统事件流。

## 14.3 日志

调试日志必须默认脱敏。允许记录：

```text
trigger.vscode-foreground matched
custom.start-coding started
custom.start-coding completed
```

不得记录：

```text
用户打开了 /Users/name/SecretProject/客户A.pdf
窗口标题为 Confidential Acquisition.docx
```

---

# 15. 动作调度、优先级与打断

## 15.1 宿主优先级

PipiPong 必须使用以下优先级区间：

| 优先级 | 类别 |
|---:|---|
| 100 | 安全提示、严重错误、应用退出 |
| 90 | 用户直接拖动、点击确认等 |
| 80 | 语音监听和录音反馈 |
| 70 | 任务开始、工作、成功、失败等核心反馈 |
| 40–60 | 用户启用的系统触发或自定义动作 |
| 20–39 | 随机人格动作、环境动作 |
| 10–19 | idle、低优先级待机 |

包定义的 `priority` 只能在 10–60 之间。

## 15.2 打断策略

支持：

- `immediate`：可以立即切换；
- `atMarker`：到达指定安全 marker 后切换；
- `afterIteration`：当前循环轮次结束后切换。

无论包声明什么，以下情况宿主可以强制立即终止：

- 安全确认；
- 权限提示；
- 应用退出；
- 宠物包崩溃；
- 用户开始拖动；
- 超过最大运行时间；
- 资源占用超限。

## 15.3 调度规则

- 同一时刻只允许一个主动作控制宠物主体；
- `parallelVisual` 只能用于不改变主动作状态的特效；
- 高优先级动作可以抢占低优先级动作；
- 被抢占动作默认取消，不自动恢复，除非宿主明确支持 resume；
- 触发器重复到达时必须应用 debounce 和 cooldown；
- 动作失败时必须执行 fallback；
- fallback 失败时必须回到内置安全 idle；
- 宠物包不得阻止用户关闭、拖动或禁用宠物。

---

# 16. 安全模型

## 16.1 包是非可信输入

所有第三方包必须按不可信输入处理。

## 16.2 禁止内容

导入器必须拒绝：

```text
.exe .dll .dylib .so .node .app .bat .cmd .ps1 .sh
.js .mjs .cjs .ts .py .lua .wasm
宏、脚本化 SVG、外部 URL、远程字体、远程图片
符号链接、路径穿越、加密压缩包、ZIP bomb
```

## 16.3 Electron 运行边界

宠物渲染进程必须满足：

```text
nodeIntegration: false
contextIsolation: true
sandbox: true
```

不得向宠物渲染进程暴露通用 IPC、文件路径、Shell、网络或 Node.js API。

预加载桥只能提供白名单能力，例如：

```text
pet.playClip
pet.move
pet.transform
pet.playSound
pet.spawnEffect
pet.reportActionResult
```

## 16.4 安全 Host Capability

v0.9.5 动作图只能请求：

```text
pet.window.move
pet.window.transform
pet.window.flip
audio.local.play
effect.local.spawn
```

这些请求仍需由主进程验证参数、范围和频率。

---

# 17. 安装与验证流程

## 17.1 验证阶段

### L0：容器验证

- ZIP 格式；
- 路径安全；
- 文件数量和大小；
- 禁止文件类型；
- 无符号链接；
- 无 ZIP bomb。

### L1：JSON Schema 验证

必须提供并使用：

```text
manifest.schema.json
animations.schema.json
core-actions.schema.json
custom-actions.schema.json
action-graph.schema.json
triggers.schema.json
capabilities.schema.json
integrity.schema.json
```

### L2：语义验证

- 所有必备核心动作存在；
- 所有引用的 clip、动作图、音频和特效存在；
- fallback 存在且不会形成无限 fallback 环；
- trigger 引用的 capability 存在；
- trigger scope 不得超过 capability scope；
- required extension 可用；
- ID 唯一；
- loop 有退出上限；
- 动作图可终止或有最大时长；
- 平台匹配器格式有效。

### L3：资产验证

- 图片可解码；
- 透明通道和尺寸有效；
- 音频可解码；
- 帧时长和总时长有效；
- 内存预算不明显超限。

### L4：权限预检

- 生成用户可读权限摘要；
- 检查当前平台是否支持所需 Provider；
- 检查 required 能力是否可申请；
- 检查数据声明是否符合 v0.9.5 固定规则。

### L5：沙箱烟雾测试

- 逐个播放 8 个必备核心动作；
- 可选播放 Standard 动作；
- 执行每个自定义动作的测试模式；
- 模拟 trigger.matched；
- 验证超时、取消和 fallback；
- 不启动真实系统 watcher。

## 17.2 结果状态

```text
VALID
VALID_WITH_WARNINGS
INCOMPATIBLE
BLOCKED_UNSAFE
PERMISSION_REQUIRED
```

## 17.3 常用错误码

```text
PPAS_E_ARCHIVE_INVALID
PPAS_E_PATH_TRAVERSAL
PPAS_E_SIZE_LIMIT
PPAS_E_FORBIDDEN_FILE
PPAS_E_SCHEMA_INVALID
PPAS_E_MISSING_CORE_ACTION
PPAS_E_ASSET_MISSING
PPAS_E_ACTION_GRAPH_INVALID
PPAS_E_TRIGGER_SCOPE_EXCEEDS_CAPABILITY
PPAS_E_CAPABILITY_UNSUPPORTED
PPAS_E_PERMISSION_DENIED
PPAS_E_EXTENSION_UNSUPPORTED
PPAS_E_RUNTIME_TIMEOUT
PPAS_E_RENDERER_CRASH
PPAS_E_INTEGRITY_MISMATCH
```

错误信息必须同时包含机器码和用户可理解说明。

---

# 18. 包完整性、签名与版本回退

## 18.1 `integrity.json`

```json
{
  "algorithm": "sha256",
  "files": {
    "manifest.json": "<sha256>",
    "animations/index.json": "<sha256>",
    "actions/core.json": "<sha256>"
  },
  "signature": {
    "status": "unsigned"
  }
}
```

本地包可以未签名，但必须显示“未验证的本地宠物包”。

市场包必须：

- 通过服务器静态扫描；
- 使用 SHA-256；
- 带平台签名；
- 记录发布者、版本和审核结果；
- 下载后再次校验；
- 允许撤销有问题的版本。

## 18.2 安装目录

建议使用内容寻址存储：

```text
pets/
  <packageId>/
    <packageVersion>/
      <contentHash>/
```

更新时必须保留至少一个可回退版本，直到新版成功完成验证和首次启动。

---

# 19. 创作者工作流

## 19.1 Settings 入口

PipiPong v0.9.5 应在：

```text
Settings → Appearance → 孵化新的桌宠
```

提供 PPAS 创建和导入入口。

## 19.2 官方孵化流程

官方孵化工具必须：

1. 获取或生成宠物基础形象；
2. 生成 Core Standard 的 10 个动作；
3. 自动统一画布、透明背景、anchor 和尺寸；
4. 生成 `animations/index.json`；
5. 生成核心动作映射；
6. 允许用户预览并重生成单个动作；
7. 生成无系统权限的基础 `.pppet`；
8. 通过 PPAS Validator 后才能安装。

不应在界面上强调底层是否使用 Codex。

## 19.3 自定义动作编辑

v0.9.5 的 Pet Package Builder 应至少提供：

- 导入序列帧；
- 设置动作名称和说明；
- 设置单次或循环；
- 设置帧速率或逐帧时长；
- 设置 anchor；
- 添加 marker；
- 设置动作优先级和打断方式；
- 将多个 clip、移动、等待、声音和特效组合为复杂动作；
- 预览动作；
- 测试中止、超时和 fallback。

用户不应被要求手写 JSON；高级模式可以显示和导出 JSON。

## 19.4 触发器编辑

Builder 必须使用表单而不是代码：

```text
当：[Visual Studio Code 进入前台]
条件：[无]
执行：[开始敲键盘]
概率：[100%]
冷却：[3 秒]
权限：[必需 / 可选]
用途说明：[进入编程软件时，宠物会开始工作]
```

对于目录事件：

```text
观察范围：[由下载者选择一个文件夹]
事件：[新文件]
文件类型：[PDF]
是否递归：[否]
```

Builder 必须实时显示最终用户将看到的权限说明卡片。

## 19.5 测试模式

Builder 必须提供“模拟事件”，例如：

```text
模拟低电量
模拟 VS Code 进入前台
模拟新 PDF 出现
模拟任务完成
模拟连续点击三次
```

测试模式不得实际扫描用户电脑或启动系统 watcher。

---

# 20. 用户端管理界面

每个安装包必须有详情页，至少显示：

- 名称、作者、版本、许可证；
- Core Minimum / Core Standard 兼容状态；
- 所有核心动作预览；
- 自定义动作列表；
- 系统事件触发器列表；
- required、optional 和 declaration-only 能力；
- 每项能力的数据范围、用途、保存和上传说明；
- 当前授权状态；
- 每个触发器的开关；
- 最近触发记录，记录中不得包含敏感原始值；
- 更新权限差异；
- 一键恢复基础模式；
- 卸载与清理。

---

# 21. 跨平台行为

## 21.1 平台支持声明

包必须声明：

```json
{
  "platforms": ["macos", "windows"]
}
```

如果某个自定义 trigger 只支持单一平台，必须在 trigger 上单独声明：

```json
{
  "platforms": ["windows"]
}
```

## 21.2 不支持时的处理

- optional trigger：禁用并说明；
- required capability：包不能在该平台完整启用；
- 包声明降级模式时，可以启用基础动作；
- 不得因为单个 optional Provider 不可用导致应用崩溃；
- 不得在 Windows 上猜测使用 macOS 标识，反之亦然。

---

# 22. 运行时接口建议

以下 TypeScript 接口为实现建议，字段可按现有仓库结构调整，但语义不得削弱。

```ts
export interface PPASManifest {
  ppasVersion: "0.9.5";
  packageId: string;
  packageVersion: string;
  defaultLocale: string;
  nameKey: string;
  descriptionKey: string;
  author: {
    name: string;
    creatorId?: string;
  };
  license: string;
  compatibility: {
    minAppVersion: string;
    platforms: Array<"macos" | "windows">;
    requiredRendererProfiles: string[];
  };
  coreProfile: "PPAS-Core-Minimum-0.9.5" | "PPAS-Core-Standard-0.9.5";
  entryPoints: {
    animations: string;
    coreActions: string;
    customActions: string;
    triggers: string;
    capabilities: string;
  };
}
```

```ts
export interface PetActionRuntime {
  play(actionId: string, context: SafeActionContext): Promise<ActionResult>;
  cancel(reason: ActionCancelReason): Promise<void>;
  listActions(): PetActionDescriptor[];
  validateAction(actionId: string): ActionValidationResult;
}
```

```ts
export interface SystemEventProvider {
  readonly id: string;
  isSupported(): Promise<boolean>;
  requestHostPermission(scope: ProviderScope): Promise<HostPermissionResult>;
  subscribe(
    grant: PackagePermissionGrant,
    filter: HostSideFilter,
    onMatched: (event: SanitizedMatchedEvent) => void
  ): Promise<Unsubscribe>;
}
```

```ts
export interface PackagePermissionBroker {
  preflight(packageId: string, capabilities: CapabilityManifest): Promise<ConsentSummary>;
  grant(request: CapabilityGrantRequest): Promise<PackagePermissionGrant>;
  revoke(packageId: string, capabilityId: string): Promise<void>;
  diff(oldManifest: CapabilityManifest, nextManifest: CapabilityManifest): PermissionDiff;
}
```

---

# 23. 完整示例：系统触发动作

## 23.1 VS Code 进入前台

能力声明：

```json
{
  "id": "cap.vscode-foreground",
  "capability": "application.foreground.observe",
  "resourceRefs": ["app.vscode"],
  "purposeKey": "capability.vscode.purpose",
  "dataLevel": "hostFilteredMatchOnly",
  "retention": "none",
  "networkTransmission": "none",
  "onDenied": "disablePackage"
}
```

触发器：

```json
{
  "id": "trigger.vscode-foreground",
  "capabilityRefs": ["cap.vscode-foreground"],
  "when": {
    "provider": "application.foreground",
    "event": "entered"
  },
  "match": {
    "resourceRefs": ["app.vscode"]
  },
  "then": {
    "action": "custom.start-coding",
    "mode": "replaceAmbient",
    "priority": 50
  },
  "cooldownMs": 3000,
  "maxRunsPerHour": 30
}
```

## 23.2 低电量

```json
{
  "id": "trigger.low-battery",
  "capabilityRefs": ["cap.power-status"],
  "when": {
    "provider": "system.power",
    "event": "batteryLevelChanged"
  },
  "conditions": {
    "all": [
      {
        "field": "battery.levelPercent",
        "operator": "lte",
        "value": 20
      },
      {
        "field": "battery.charging",
        "operator": "eq",
        "value": false
      }
    ]
  },
  "then": {
    "action": "custom.hungry",
    "mode": "replaceAmbient",
    "priority": 45
  },
  "cooldownMs": 1800000,
  "maxRunsPerDay": 6
}
```

## 23.3 用户选择目录出现 PDF

能力声明：

```json
{
  "id": "cap.pdf-inbox",
  "capability": "filesystem.directoryChanges.observe",
  "resourceRefs": ["dir.pdf-inbox"],
  "allowedEvents": ["fileCreated"],
  "filters": {
    "fileExtensions": ["pdf"],
    "recursive": false,
    "settleMs": 1500
  },
  "purposeKey": "capability.pdfInbox.purpose",
  "dataLevel": "hostFilteredMatchOnly",
  "retention": "none",
  "networkTransmission": "none",
  "onDenied": "disableDependentTriggers"
}
```

触发器：

```json
{
  "id": "trigger.new-pdf",
  "capabilityRefs": ["cap.pdf-inbox"],
  "when": {
    "provider": "filesystem.watch",
    "event": "fileCreated"
  },
  "match": {
    "resourceRefs": ["dir.pdf-inbox"]
  },
  "then": {
    "action": "custom.pick-up-document",
    "mode": "enqueue",
    "priority": 45
  },
  "debounceMs": 1500,
  "cooldownMs": 10000,
  "maxRunsPerHour": 12
}
```

宠物动画只收到：

```json
{
  "event": "trigger.matched",
  "triggerId": "trigger.new-pdf",
  "category": "filesystem.watch"
}
```

不得收到 PDF 文件名或真实目录路径。

---

# 24. v0.9.5 实施模块

Codex 应将 PPAS 实现拆成以下模块，避免把系统权限、渲染和包解析混在一起。

## 24.1 Shared

```text
src/shared/ppas/
  types/
  schemas/
  constants/
  error-codes/
  versioning/
```

职责：

- 类型；
- JSON Schema；
- Core Action 常量；
- Provider 和 Capability 常量；
- 统一错误码；
- 版本比较和权限差异。

## 24.2 Main Process

```text
src/main/pet-packages/
  archive-validator/
  semantic-validator/
  installer/
  integrity/
  package-store/

src/main/system-events/
  provider-registry/
  macos/
  windows/
  host-filter/

src/main/permissions/
  package-permission-broker/
  consent-summary/
  grant-store/
```

职责：

- 解压和安全验证；
- 安装和回退；
- 系统事件监听；
- 按包权限；
- 宿主侧过滤；
- watcher 生命周期管理。

## 24.3 Renderer / Pet Runtime

```text
src/renderer/pet-runtime/
  sprite-renderer/
  action-runtime/
  action-scheduler/
  trigger-client/
  safe-host-bridge/
  fallback-pet/
```

职责：

- 序列帧播放；
- 动作图执行；
- 优先级、队列和中止；
- 接收已过滤的 `trigger.matched`；
- 不直接接触系统 API。

## 24.4 Settings / Builder

```text
src/renderer/settings/pet-packages/
  package-library/
  package-details/
  action-preview/
  permissions-and-triggers/
  package-import/
  package-update-diff/

src/renderer/pet-builder/
  core-action-generator/
  clip-editor/
  action-composer/
  trigger-editor/
  consent-preview/
  validator-report/
  exporter/
```

---

# 25. 验收标准

## 25.1 包格式

- 可以导入一个合法 `.pppet`；
- ZIP 路径穿越、脚本、超大资源和哈希不一致会被拒绝；
- 缺少任意 Core Minimum 动作时不得启用；
- 缺少 wake/sleep 的 Minimum 包可以使用 fallback；
- 包更新失败时可以回退旧版。

## 25.2 动画与动作

- 8 个必备核心动作均可独立预览；
- 官方孵化包默认包含 10 个动作；
- 自定义 clip action 可执行；
- sequence、parallel、move、wait、sound、branch、random 和 loop 可执行；
- 无限循环和超时动作会被终止；
- 动作失败后回到 fallback；
- 安全提示和用户拖动可以抢占包动作。

## 25.3 触发器

- 未授权前不得启动 watcher；
- required 权限被拒绝时包不得完整激活；
- optional 权限被拒绝时仅禁用依赖触发器；
- 触发器不能超出 capability scope；
- debounce、cooldown、概率和执行上限有效；
- 不支持的平台 Provider 能正确降级；
- 用户能逐项关闭触发器。

## 25.4 隐私

- 动画进程无法获取文件路径、文件名、窗口标题或应用历史；
- 目录触发只发送 `triggerId`；
- 包无法发起网络请求；
- 包无法读取 Node.js、Shell、剪贴板、屏幕、摄像头和麦克风；
- 日志不含敏感原始值；
- 撤销权限后 watcher 立即停止。

## 25.5 更新

- 新增 optional 权限默认关闭；
- 新增 required 权限会阻止自动激活并要求重新确认；
- scope 扩大被识别为权限变化；
- 删除权限会清理 watcher 和 grant；
- 用户能查看新旧权限差异。

## 25.6 跨平台

- macOS 和 Windows 使用同一 PPAS 包结构；
- 应用标识使用平台匹配器；
- 平台不可用能力不会造成崩溃；
- 现有 macOS 安全边界不得因 Windows 适配而削弱；
- Windows 实现不得通过任意 UI 自动化绕过 PPAS 权限模型。

---

# 26. 测试矩阵

至少覆盖：

| 测试类型 | 场景 |
|---|---|
| Schema | 缺字段、错误类型、未知必需扩展 |
| Archive | 路径穿越、ZIP bomb、符号链接、禁止文件 |
| Core Actions | 缺少每一个必备动作 |
| Graph | 无限循环、深度超限、未知节点、fallback 环 |
| Permission | required 拒绝、optional 拒绝、撤销、scope 扩大 |
| Provider | 应用启动/退出、前台切换、空闲、电量、目录事件 |
| Privacy | 验证 renderer 不接收原始事件 |
| Scheduler | 高优先级抢占、marker 打断、冷却、队列 |
| Update | 权限无变化、新权限、范围扩大、版本回退 |
| Platform | macOS 与 Windows 标识和不可用 Provider |
| Recovery | renderer 崩溃、资源损坏、动作超时、应用重启 |

所有关键安全测试必须纳入 CI。

---

# 27. v0.9.5 发布定义

PPAS v0.9.5 可以视为完成，必须同时满足：

1. 包结构、JSON Schema 和错误码稳定；
2. Core Minimum 8 动作可运行；
3. 官方孵化流程输出 Core Standard 10 动作；
4. 自定义 clip 和 Action Graph 可运行；
5. 至少实现 `system.power`、`system.session`、`system.idle`、`application.lifecycle`、`application.foreground` 和 `filesystem.watch` 的统一 Provider 接口；
6. macOS 和 Windows 均使用按包权限；
7. 系统事件经过宿主侧过滤；
8. 下载和启用前展示 required/optional 条件；
9. 包更新权限变化会重新征求同意；
10. 第三方包不能执行任意代码或直接访问电脑；
11. 设置中可预览动作、管理权限和禁用触发器；
12. 安全、权限和跨平台测试进入 CI。

---

# 28. 未来兼容方向（不属于 v0.9.5 强制范围）

未来版本可以通过扩展增加：

```text
PPAS_rive_renderer
PPAS_dotlottie_renderer
PPAS_spine_renderer
PPAS_live2d_renderer
PPAS_lip_sync
PPAS_accessory_slots
PPAS_multi_pet
PPAS_physics
PPAS_marketplace_signature
PPAS_creator_revenue
```

未来扩展不得改变以下基础原则：

- 核心动作使用语义 ID；
- 系统观察能力必须事先声明；
- 用户按包授权；
- 宿主侧过滤；
- 普通宠物包不得执行任意代码；
- 动画运行时不得直接访问系统资源。

---

# 29. 最终架构决策

PPAS v0.9.5 的正式技术路线为：

```text
.pppet ZIP 容器
+ 8 个必备 / 10 个官方标准核心动作
+ sprite-sequence-v1
+ 自定义 clip 与声明式 Action Graph
+ PipiPong / 用户 / 电脑系统 Event Providers
+ required / optional Capability Manifest
+ 下载前披露、启用前同意
+ 按包虚拟权限
+ 宿主侧过滤
+ 动作调度、打断、超时和 fallback
+ 严格沙箱与 fail-closed 验证
```

该模型允许创作者设计具有完整人格、复杂动作和真实电脑环境反应的宠物，同时保证下载者在使用前明确知道它会观察什么、为什么观察、观察到什么粒度，以及拒绝后会发生什么。
