首页
/ Entry: 2026-07-01

Entry: 2026-07-01

2026-09-06 17:50:24作者:郦嵘贵Just

Entry: 2026-07-01

  • Outcome Type: interview_progress
  • Canonical State: Interview
  • Stage Reached: Tech Screen
  • Verbatim Feedback:

Great feedback on system design and architecture.

  • Notes: Stage updated: Tech Screen

默认备注的生成规则是:有 `--note` 用 `--note`;否则有 `--stage` 时为 `{defaultNote}: {stage}`;否则直接用 `defaultNote`。追加前会检查日志中是否已存在同一条目的完整内容(幂等去重)。[tests/outcome.test.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/tests/outcome.test.mjs?utm_source=gitcode_repo_files) 验证了:第二次运行(`offer_received`)后,`outcome.md` 仍保留第一阶段的 `Tech Screen` 条目,且追踪器状态被更新为 `Offer`。

## 规则与约束:逐字、append-only、幂等、防分叉

[modes/outcome.md](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/modes/outcome.md?utm_source=gitcode_repo_files) 列出四条硬约束,前两条在源码中有明确对应:

1. **Verbatim Feedback(逐字反馈)**:反馈必须原样记录,不制造、不推断、不润色——日志里以引用块 `> ` 原样落盘;
2. **Append-Only History(只追加历史)**:`outcome.md` 与追踪器备注严格 append-only,重跑只新增条目,不改动旧日志;
3. **Idempotency(幂等)**:相同参数的重复运行产生无重复、安全的输出与追踪器更新;
4. **Posting Archiving Stub**:无法归档时写显式 stub(见上节)。

### 一个应用一份日志:目录解析的防分叉设计

"append-only" 还有一个前提:日志必须始终落在同一个目录里。[lib/outcome-dir.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/lib/outcome-dir.mjs?utm_source=gitcode_repo_files) 的模块注释记录了此前的缺陷:目录名曾按追踪器行的**当前文本**每次重建,用户在两次记录之间把 Role 列从 "Senior Backend Engineer" 规范成 "Sr. Backend Engineer"(普通编辑),第二次条目就被写进了另一个目录——一个应用被拆成两份半截日志,且所有读端按前缀 `{num}_` 取目录时,读到哪一半取决于字母序而非时间。

修复后的规则是:**行号 `num` 才是稳定身份,slug 只是人类可读标签**。`resolveOutcomeDir()` 先按前缀 `{num}_` 枚举已有目录(按 `outcome.md` 的 mtime 新者在前排序),有则复用;只有在完全不存在时才用当前 slug 生成新名。若历史上已经分裂出多个目录,[outcome.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/outcome.mjs?utm_source=gitcode_repo_files) 不会擅自合并(移动用户数据不是这条命令的职责),但会打印警告,指出哪个目录在被追加、其余半截需要手工合并。该行为由 [tests/outcome-journal-fork.test.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/tests/outcome-journal-fork.test.mjs?utm_source=gitcode_repo_files) 回归验证:角色改名前后两次记录后,`data/outcomes/` 下只允许存在 `1_acme_senior-backend-engineer` 一个目录,且其中同时包含两条按顺序排列的条目。

## 纵深解析:`--clean-output` 的字节级安全删除

[modes/outcome.md](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/modes/outcome.md?utm_source=gitcode_repo_files) 专节讨论了 `--clean-output`(issue #2653)。`output/` 会累积每次评估时生成的 tailored PDF/HTML,包括早已结束的申请。该选项的作用是在记录结果之后,把**本行**在 `output/` 里的 tailored PDF/HTML 删掉——但严格限定为"归档已验证"之后的删除,绝不裸删:

1. 先把 CV PDF 归档为 `submitted_cv.pdf`(默认行为),且仅当设置了 `--clean-output` 时才把配套的 HTML 归档为 `submitted_cv.html`;
2. 逐个校验归档副本存在于 `data/outcomes/{num}_{company_slug}_{role_slug}/` 且与 `output/` 原件**逐字节一致(sha256)**——只比大小不够,两份长度相同的渲染可能完全不同,因此与 `tracker.mjs`、`seed-fixture.mjs` 使用相同的哈希方式;
3. 校验通过才删除 `output/` 原件;校验失败或归档副本缺失时,原件原地保留并报告为 refused——绝不删除一个无法确认"唯一副本已幸存"的文件;
4. `--dry-run --clean-output` 只列出"将归档然后删除"的文件清单,不触碰任何文件。

**为什么坚持 opt-in 而非默认开启**:`output/*` 属于用户层(见 [DATA_CONTRACT.md](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/DATA_CONTRACT.md?utm_source=gitcode_repo_files)),系统从不删除它,除非每一次都被显式要求。`outcome.mjs` 没有 `--clean-output` 就不删任何东西;文档还论证了 `--no-clean-output` 逃生门没有意义——需要它的人只能在文件已经没了之后才发现。

**资格边界是 `output/`,而不是"哪个 flag 解析出了这个路径"**。从 [outcome.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/outcome.mjs?utm_source=gitcode_repo_files) 的源码看:

- 只要 PDF 解析后落在 `output/` 内,它就是清理候选——无论是追踪器 PDF 列自动检测(Case B)、`data/pdf-index.tsv` 查得(Case C),还是用户用 `--cv` 显式指定(Case A);
- 指向 `output/` 之外(如用户主目录)的显式 `--cv` 永不触碰——`--cv` 接受任意路径,没有这条边界,删除的爆炸半径就从"output 目录"变成"用户指哪儿删哪儿";
- `cv.md` 回退(Case D,未解析到任何 PDF)同样不是候选;
- 配套 HTML 来自 `data/pdf-index.tsv` 的 `html` 列。该清单是主机可写数据(host-writable data),本身不是可信边界——一个畸形或被篡改的 `html` 列不会因为它的配对 PDF 合法就被视为在 `output/` 内;**每条路径都独立做包含检查**,两者都通过后才可能成为删除候选。

**包含检查的实现**使用 `path.relative()` 加 Node 自带的 `path.isAbsolute()`(跨平台一致,兼容 Windows 盘符绝对路径与 UNC 路径),而非手写字符串前缀比较。更关键的是 [tracker-utils.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/tracker-utils.mjs?utm_source=gitcode_repo_files) 中的 `pathIsInsideCanonical()`:它在词汇级包含(`pathIsInside`)之外再做**规范级**包含——对父子路径做 `realpathSync` 解析符号链接后再比较一次。因为 `resolve()` 不跟随符号链接,`output/` 内一个指向别处的链接会让路径"拼写出来"像在内部、实际指向外部;删除不可恢复,所以这条边界先解析符号链接再信任它。

对应的测试在 [tests/outcome.test.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/tests/outcome.test.mjs?utm_source=gitcode_repo_files)(#2653、#2911 相关用例):

- Test 10:`--dry-run --clean-output` 列出 PDF/HTML 两个候选但不删除;
- Test 11:归档后从 `output/` 删除已验证的一对文件,`cleanup.removed` 为 2、`cleanup.refused` 为 0;
- Test 12:预置**同尺寸不同内容**的归档副本(正是只看大小会误判通过的用例),sha256 校验使其被拒绝删除,原件保留;
- Test 13/14:显式 `--cv` 在 `output/` 内可清理,在 `output/` 外(`external-cv.pdf`)原样保留且 removed/refused 均为 0;
- Test 15 起:清单里越界的 `html` 列不被当作候选(CodeRabbit 对 #2911 的发现)。

## 追踪器同步与失败处理

归档完成后,`outcome.mjs` 通过 `execFileSync` 调用 [set-status.mjs](https://gitcode.com/GitHub_Trending/ca/career-ops/blob/afc5275d10ca22749d3d7528167961bb97d95939/set-status.mjs?utm_source=gitcode_repo_files):

node set-status.mjs --note "" --force --json [--role ]

登录后查看全文
热门项目推荐
相关项目推荐