# Manual Invoice Plugin：架构与执行流程

> 文档状态：Current implementation  
> 插件版本：0.8.0+codex.20260724071113  
> 更新时间：2026-08-19

## 1. 产品定位

Manual Invoice 是面向手工出账与对账场景的 Codex 插件。它把自然语言出账规则、BigQuery/O11Y 数据查询、确定性计算、结果校验和对账报告组织成可复现、可审计的工作流。

核心原则：

- Codex 负责理解需求、选择工具和编排步骤，不直接充当金额计算器。
- 查询、计算和报告相互解耦。数据源查询可以调整，但已冻结的计算逻辑不会被隐式修改。
- 所有参与出账的数据查询必须落盘，保存原始数据、查询语句、元数据和 hash。
- 最终金额来自确定性脚本生成的 result.json，报告只引用结果，不在文案中重新计算。
- 每次执行生成新的 run 和独立交付包，不覆盖已经审核的结果。

当前插件以 Skill 为核心。BigQuery、O11Y Metrics 和 O11Y Logs 已形成正式数据源适配；Ops Bills 浏览器下载已经验证可行，但尚未封装为正式 Skill/脚本能力。

## 2. 当前能力边界

| 能力 | 状态 | 说明 |
|---|---|---|
| Codex 插件安装与发现 | 已实现 | 通过 Marketplace 安装，在新任务中加载 manual-invoice Skill |
| 持久化配置 | 已实现 | 支持项目级 .manual-invoice.env 和用户级配置 |
| Python 依赖 bootstrap | 已实现 | 在插件缓存之外维护持久化 venv，并用 requirements hash 检测变化 |
| 系统 CLI 检测/安装 | 已实现 | 检测后展示安装计划，必须二次确认才安装 |
| BigQuery 动态查询 | 已实现 | 先发现表和 Schema，再生成参数化 SQL，执行验证、dry run 和查询 |
| O11Y Metrics | 已实现 | 自动解析 cluster 上下文，保存 PromQL、原始响应和标准化数据 |
| O11Y Loki Logs | 已实现 | 保存 LogQL、原始分页响应、标准化日志和截断状态 |
| 固化出账 Case | 已实现 | 规则、查询、Schema、计算脚本分别版本化 |
| 对账报告与私有 bundle | 已实现 | 输出 Markdown、结果、证据、lineage、validation 和 manifest |
| 发布到飞书文档 | 已实现 | 检测 lark-cli；必须提供目标链接并明确确认 |
| Ops Bills CSV 下载 | 实验能力 | 已验证浏览器登录、页面下载和文件留痕，尚未纳入正式工作流 |
| 审批与自动填单 | M2 规划 | 适合通过 MCP 暴露受控工具并在服务端管理认证与审批 |

## 3. 总体架构

~~~mermaid
flowchart TB
    U["User / Billing Operator"] --> C["Codex Orchestrator"]
    C --> S["manual-invoice Skill"]

    S --> CFG["Configuration & Bootstrap"]
    S --> DISC["Source Discovery & Query Planning"]
    S --> CASE["Versioned Billing Case"]
    S --> RUN["Run Orchestration"]

    CFG --> ENV["Private config"]
    CFG --> VENV["Persistent Python runtime"]
    CFG --> O11YS["Clinic/O11Y Skills"]

    DISC --> BQ["BigQuery Adapter"]
    DISC --> MET["O11Y Metrics Adapter"]
    DISC --> LOG["O11Y Logs Adapter"]
    DISC -. "experimental" .-> OPS["Ops Browser Export"]

    CASE --> RULE["rule.yaml"]
    CASE --> QM["query-manifest.yaml"]
    CASE --> SCH["schemas/"]
    CASE --> CALC["calculation/"]

    BQ --> RAW["Raw Evidence"]
    MET --> RAW
    LOG --> RAW
    OPS --> RAW
    RAW --> NORM["Normalized Inputs"]
    SCH --> NORM
    NORM --> CALC
    CALC --> RESULT["result.json"]
    RESULT --> VALID["validation + lineage"]
    VALID --> REPORT["Markdown Report"]
    REPORT --> BUNDLE["Private Reconciliation Bundle"]
    REPORT -. "explicit confirmation" .-> LARK["Feishu/Lark Document"]
~~~

### 3.1 插件层

- .codex-plugin/plugin.json 定义插件身份、版本和 Skill 入口。
- .agents/plugins/marketplace.json 让仓库可以作为本地或 Git Marketplace 安装。
- skills/manual-invoice/SKILL.md 是 Codex 的主要编排协议，定义调用时机、执行顺序和安全边界。

### 3.2 工具层

插件使用 Python 和 Shell 脚本执行确定性操作：

- 配置与依赖：configure.py、bootstrap.py、bootstrap_system_tools.py
- BigQuery：bq_catalog.py、bq_validate.py、query_bigquery.py
- O11Y：resolve_o11y_cluster.py、query_o11y_metrics.py、query_o11y_loki.py
- 交付：publish_report_to_lark.py、build_reconciliation_bundle.py

脚本负责实际的数据访问、格式转换、计算与文件写入。Codex 不应只在回答文本中完成这些关键步骤。

### 3.3 Case 层

一个可重复使用的出账流程保存在 cases/<case-id>/：

~~~text
cases/<case-id>/
├── rule.yaml              # 已审核的业务规则解释
├── query-manifest.yaml    # 数据需求、查询和输出映射
├── queries/               # SQL / PromQL / LogQL
├── schemas/               # 查询与计算之间的数据契约
└── calculation/           # 稳定、确定性的计算代码
~~~

关键边界：

- Query 可以变化，但新输出必须继续满足既有 Schema。
- 仅调整查询时，不修改 calculation/。
- 计算规则变更必须由用户明确提出，并升级 calculation version。
- 计算代码只读取 normalized 文件，不直接访问 BigQuery/O11Y 或原始业务文档。

### 3.4 Run 与交付层

每次执行创建新的 runs/<run-id>/：

~~~text
runs/<run-id>/
├── input/                 # 用户请求和非敏感参数
├── evidence/              # 查询、原始响应、Job 元数据
├── normalized/            # 通过 Schema 约束的计算输入
├── calculation/           # 本次使用的计算代码快照
├── result.json            # 金额及明细的唯一结果来源
├── validation.json        # 完整性与一致性检查
├── lineage.json           # 规则、查询、输入、脚本和结果的血缘
└── manual-invoice.md      # 人可读报告
~~~

完成后构建私有交付目录 manual-invoice-output/<run-id>/。其中包含必要材料及 manifest.json，记录每个文件的大小和 SHA-256。

## 4. 首次配置流程

用户安装插件并开启新任务后执行：

~~~text
$manual-invoice configure
~~~

~~~mermaid
sequenceDiagram
    actor U as User
    participant C as Codex
    participant T as Tool Detector
    participant CFG as Configure
    participant P as Python Bootstrap
    participant O as O11Y Skills
    participant V as Connectivity Checks

    U->>C: Configure plugin
    C->>T: Check system tools
    T-->>C: Installed and missing list
    C-->>U: Select optional tools
    U->>C: Selected list
    C->>T: Generate exact install plan
    C-->>U: Show commands and request final approval
    U->>C: Explicit approval
    C->>T: Install approved list
    C->>CFG: Save private configuration
    C->>P: Create or reuse persistent venv
    C->>O: Install Clinic/O11Y Skills
    C->>V: Validate BigQuery and O11Y
    V-->>C: Redacted readiness result
    C-->>U: Configuration summary
~~~

通常需要用户提供：

- GOOGLE_APPLICATION_CREDENTIALS：当本机没有其他 ADC 时，指向 Service Account JSON。插件只保存路径，不复制凭证内容。
- GITHUB_PAT：使用 O11Y 时需要 read:user 和 read:org，并需具备读取私有 nutshell-skills 仓库的能力。

可留空并自动推断：

- BQ_JOB_PROJECT：默认从 ADC 推断，但实际使用的 Job Project 会写入证据。
- BQ_LOCATION：默认根据查询引用的表推断；Location 是每个 query 的属性。

配置加载顺序：

1. MANUAL_INVOICE_CONFIG 指定的文件。
2. 从当前目录向上找到的最近 .manual-invoice.env。
3. 用户目录下的 .config/manual-invoice/config.env。

## 5. BigQuery 查询流程

BigQuery 不依赖预先写死的完整 Warehouse Schema。插件在允许范围内动态发现表和字段：

~~~mermaid
flowchart LR
    Q["Natural-language question"] --> E{"Known reviewed entity?"}
    E -- Yes --> R["resolve-entity"]
    E -- No --> L["list-tables"]
    L --> SC["search-columns"]
    R --> D["describe-table"]
    SC --> D
    D --> SQL["Generate parameterized SQL"]
    SQL --> AST["Read-only and allowlist validation"]
    AST --> DRY["BigQuery dry run"]
    DRY --> LIMIT{"Within byte limit?"}
    LIMIT -- No --> STOP["Stop and report"]
    LIMIT -- Yes --> EXEC["Execute query"]
    EXEC --> EV["Save SQL, params, Job metadata, JSONL"]
    EV --> N["Normalize and validate schema"]
~~~

执行约束：

- 使用完整的 project.dataset.table。
- 用户提供的 tenant、日期等值必须使用 named parameters，不能拼接进 SQL。
- 仅允许 read-only GoogleSQL；禁止 DML、DDL、脚本和外部联邦查询。
- 查询前 dry run，并执行 BQ_MAX_BYTES_BILLED 限制。
- 时间范围使用半开区间：大于等于开始时间，小于结束时间。
- 不在同一个 query 中 join 不同 Location 的 Dataset。
- 保存 SQL hash、Job ID、Job Project、Location、bytes processed/billed、cache 状态和结果 Schema。
- NUMERIC/BIGNUMERIC 与金额计算全程保持精确十进制。

当前内置策略允许动态查询 gcp-digital-bi.metadb_new.*。其他 Dataset 需要通过项目级 semantic model 明确加入 allowlist。

## 6. O11Y Metrics 与 Logs

O11Y 查询由 nutshell Skills 提供底层访问能力，Manual Invoice 在外层增加文件化证据和出账安全约束。

### 6.1 Cluster 解析

1. 如果已有 Clinic API 认证，先通过 clinic-api 获取精确 metadata。
2. 否则或 Clinic 不可用，使用 o11y-data 的有限 common-region GS 扫描。
3. 自动得到 provider、region、deploy type、bizType 和 O11Y target ID。
4. 如果用户提供 tenant ID，校验 cluster 归属；不一致则停止。
5. 只有两种自动解析都失败后，才询问 provider/region/deploy type。

### 6.2 Metrics

- PromQL 必须保存到文件。
- 保存 cluster context、起止时间、step、原始 Prometheus JSON 和 normalized samples。
- partial result 被视为阻断性错误。
- 原始字符串值在计算前不转为二进制浮点。

### 6.3 Logs

- LogQL 必须保存到文件。
- Loki 查询按时间分页，保存所有原始页面和 normalized JSONL。
- 如果结果可能被截断，禁止进入正式出账计算。
- 日志解析和聚合必须由版本化脚本完成，不能只依赖临时 grep 或 Codex 文本推理。

## 7. 标准手工出账流程

~~~mermaid
sequenceDiagram
    actor U as Billing Operator
    participant C as Codex + Skill
    participant DS as Data Sources
    participant FS as Run Evidence
    participant CALC as Frozen Calculation
    participant OUT as Report / Bundle

    U->>C: Tenant, period and billing rules
    C->>C: Resolve ambiguity and create/reuse case
    C->>FS: Save rule source and request snapshot
    C->>DS: Discover schemas/cluster context
    C->>DS: Execute file-backed queries
    DS-->>FS: Raw results and metadata
    C->>FS: Normalize and validate schemas
    C->>CALC: Run calculation on normalized inputs
    CALC-->>FS: result.json
    C->>FS: Generate validation and lineage
    C->>OUT: Render Markdown from result.json
    C->>C: Scan secrets and symlinks
    C->>OUT: Build private bundle and manifest
    C-->>U: Totals, exceptions and bundle path
    opt User explicitly confirms Lark publication
        U->>C: Provide target document URL
        C->>OUT: Append report and save receipt
    end
~~~

人工控制点：

- 财务含义不明确时必须询问，例如金额字段、币种、计费粒度或关键 join 关系。
- 新规则必须经过用户审阅后才能冻结为 rule.yaml。
- 系统软件安装必须经过“选择”和“最终确认”两次操作。
- 调整计算逻辑必须得到明确请求，并升级计算版本。
- 发布飞书文档必须由用户提供目标链接并明确同意。
- 已审核 run 和 bundle 不得覆盖；重跑必须生成新 ID。

## 8. 证据、审计与安全

一次完整出账可以回答：

- 使用了哪一版业务规则？
- 查询了哪些数据源、表、cluster 和时间范围？
- 实际执行的 SQL/PromQL/LogQL 是什么？
- BigQuery Job ID、Location、Job Project 和数据量是多少？
- 使用了哪一版计算脚本？
- 报告中的金额来自哪个 result.json？
- 交付包内每个文件的 hash 是什么？

以下内容不得进入 case、run、报告或 bundle：

- Service Account JSON 内容
- GitHub PAT
- Authorization/Bearer/Auth0 Token
- Cookie、临时云凭证和 Lark 认证输出

配置文件使用 0600；最终 bundle 目录使用 0700，文件使用 0600。Bundle builder 会拒绝 symlink 和疑似包含 secret 的内容。

数据与计算之间有明确边界：

~~~text
Raw Source → Normalization → Versioned Schema → Frozen Calculation → Result
~~~

这使数据源查询能够迭代而不影响既有计算逻辑。只有字段名称、类型、粒度、唯一性或业务含义发生变化时，才需要重新审查 Schema 或计算代码。

## 9. Ops Bills 浏览器导出原型

已经验证的流程：

1. Codex 打开 ops.tidbcloud.com。
2. 用户在内置浏览器中亲自完成 SSO/MFA。
3. Codex 导航到 Tenant 的 Business Info → Invoice And Bill → Bills。
4. 选择月份并点击 Download CSV。
5. 浏览器未上报标准 download event 时，通过下载目录的新文件进行 fallback 检测。
6. 将 CSV 复制到 run evidence，记录 tenant、周期、文件大小、SHA-256 和明细行数。
7. 不读取或保存 Authorization Header、Cookie 或浏览器 Token。

当前限制：

- 尚未写入 manual-invoice Skill，也没有专用 normalize/validate 脚本。
- 页面结构变化可能导致浏览器定位失效。
- 登录和 MFA 需要用户交互。
- 暂不支持无人值守或批量执行。

建议演进：

- M1：封装为正式的 browser-assisted source adapter。
- M2：通过 MCP 封装 export_monthly_bill(tenant_id, year, month)，由服务端管理 OAuth、权限和审计，只向 Codex 返回文件与脱敏元数据。

## 10. M1 与 M2

### M1：本地可审计手工出账

- 自然语言输入规则或通过 lark-cli 读取规则。
- BigQuery、O11Y 和 Ops CSV 作为数据来源。
- 固化查询、Schema 和计算脚本。
- 生成本地 Markdown 报告与完整 reconciliation bundle。
- 保留人工审阅、软件安装确认和飞书发布确认。

### M2：服务化数据访问与审批

- 将内部数据源访问封装为 MCP tools。
- 使用 OAuth/受控 credential store，Token 不进入 Codex 上下文。
- 自动选择 Active billing config 并导出月度详单。
- 自动填充手工出账文档和金额。
- 发起审批、回写审批状态并记录审计日志。
- 复用现有 case/schema/calculation/bundle 合同。

## 11. 典型使用方式

配置插件：

~~~text
$manual-invoice configure
~~~

自然语言 BigQuery 查询：

~~~text
使用 $manual-invoice 查询 tenant 1372813089190491281 在 2026-04-01 到 2026-07-10 每个月的 invoice。
~~~

创建完整手工出账：

~~~text
使用 $manual-invoice 为 tenant <tenant-id> 生成 2026 年 7 月手工账单。
数据源包括 BigQuery、指定 cluster 的 O11Y metrics 和相关 Loki logs。
按以下合同规则计算，并输出对账报告和完整证据包：<billing rules>。
~~~

调整查询但保持计算不变：

~~~text
更新 <case-id> 的数据源查询以适配新表，但保持 normalized schema 和 calculation 不变，并用新 run 验证结果。
~~~

## 12. 一句话总结

Manual Invoice 不是一个“让大模型直接算账”的工具，而是一套由 Codex 编排、由确定性脚本执行、以文件化证据和版本化契约保证可审计性的手工出账框架。
