# 需求:cred.py正式纳入凭据读取唯一通道

状态: proposed
归属: 公司运营
来源: 登记缺口(2026-11-02 ops 盘点:凭据护栏 ops/cred.py 未跟踪且未被 ignore,git add -A 就会带进库)
日期: 2026-11-02
建议: 做,凭据护栏已在现场验证(selftest 六项全过),但仍是未跟踪件、且 exec 的占位符替换能打出真值,不收成正式通道就还会散落直读

## 现状

- `ops/cred.py`(315 行 / 10077 字节)是**未跟踪**文件:`git status` 里 `?? ops/cred.py`,`git check-ignore -v ops/cred.py` 退出码 1(不命中),.git/info/exclude 也没有规则——谁跑一次 `git add -A` 它就被带进库。
- 已核实:文件内**没有**真实凭据值,读的是仓外 `~/.pinnadash/credentials.json`,`python ops/cred.py selftest` 六项全过。
- 但它是「未跟踪的私货」:没有 pytest 测试、没接进每日链、`cmd_exec` 的占位符替换能把真值打到子进程命令行上;docstring 第 3 行「物理上打不出值」对 `list` 成立、对 `exec` 不成立。
- 现状兜底(2026-11-02):`.gitignore` 已加 `ops/cred.py` 一行防 `git add -A` 误带。本条需求是把兜底变成**正式纳入**:从「私下验证过的文件」变成有测试、有通道、有闸门的正式工具。

## 方案

1. 补 `ops/test_cred.py`,覆盖三件:`list` 只吐字段名/类型/长度(不吐值);解析失败只吐固定码 `FILE_NOT_FOUND` / `READ_FAIL` / `PARSE_FAIL`(不吐 traceback、不吐对象 dump);`selftest` 能被 pytest 调用。
2. 接进 `ops/run_daily.py` 的每日链,成为**读凭据的唯一通道**(替代散落的 `Get-Content | ConvertFrom-Json`);并把「有人绕过它直读 credentials.json」做成**可检项**(机检能点名到脚本)。
3. 删掉 `cmd_exec` 里的占位符替换(`ops/cred.py` L147-150):那里把模板里的 `%CRED_VALUE%` / `${CRED_VALUE}` 替换成真值再交给子进程,`exec --key X -- echo %CRED_VALUE%` 就能把值打出来;只保留环境变量 `CRED_VALUE` 注入这一条路。
4. 把 docstring 第 3 行「物理上打不出值」改成准确口径:对 `list` 成立、对 `exec` 不成立(exec 的保证是「本进程不打印值」,不是「子进程看不到值」)。

## 验收标准

- [ ] `python -m pytest ops/test_cred.py` 通过,用例覆盖:list 的输出形态、三个固定错误码、selftest 可调用
- [ ] 失败路径用例断言输出只含固定码,不含凭据值与 traceback
- [ ] `ops/run_daily.py` 每日链经 `cred.py` 读凭据,且有一条机检能指出绕过直读 `credentials.json` 的脚本
- [ ] `python ops/cred.py exec --key X -- echo %CRED_VALUE%` 不再打出真值(占位符替换已删,只剩环境变量)
- [ ] `ops/cred.py` 第 3 行口径对 `list`/`exec` 分别成立
- [ ] 票面、测试与任何改动文件里不含真实凭据值
