<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary
- New src/private-local-artifact.ts module with three reusable async primitives that force 0700 directories and 0600 files for locally-persisted drone evidence on POSIX, regardless of the process umask: ensurePrivateArtifactDir, writePrivateArtifactFileAtomic, and appendPrivateArtifactFile.
- Wired through the four named integration points: telemetry.ts::persistRunRecord's receipt directory, receipt-s3.ts::writeAtomicFile (which also covers its own stampLocalS3Upload / writeHydratedReceiptWithStamp callers, plus every other writeAtomicFile caller — trace-mirror.ts, heartbeat.ts, heartbeat-mirror.ts), events.ts::appendEvent, and traces.ts::appendTraceSpan.
- Platform/mode operations are isolated behind an injectable deps.platform / deps.modeOps seam so both the POSIX repair path and the Windows no-chmod contract are deterministic unit tests — no it.skipIf, no broad test skips.
- No process-global umask changes; a non-directory/symlink artifact path or a mode-repair failure fails closed using each caller's pre-existing error contract (propagates — never a fallback path).
## Why it's needed
runs/*.json, events/*.jsonl, and traces/*.jsonl hold unredacted prompts, tool-call payloads, and PR content (per receipt-s3.ts and traces.ts's own "sensitive data" callouts), but every write site previously relied on fs.mkdir / fs.writeFile / fs.appendFile defaults. A permissive host umask (e.g. 000) silently widened those to 0777 dirs / 0666 files, letting any other local account on a shared host read the evidence. Explicit modes on every create call, plus a repair pass for artifacts that already exist with a broader mode, close that gap without depending on the umask at all.
## Changes
- New module — src/private-local-artifact.ts:
- ensurePrivateArtifactDir(dir, deps?) — mkdir with explicit 0700 mode; on POSIX, verifies the path is a real directory via lstat (never a followed symlink) and chmods it to 0700 even if it predates this call.
- writePrivateArtifactFileAtomic(finalPath, contents, deps?) — unique same-directory temp file at creation mode 0600, then rename over the final path (unchanged unique-temp/rename/"last-rename-wins" semantics from the pre-existing receipt-s3.ts writer).
- appendPrivateArtifactFile(path, contents, deps?) — append FileHandle opened at creation mode 0600; on POSIX, fchmods the already-open handle to 0600 before writing, repairing a pre-existing broad file without a second path lookup (no TOCTOU window).
- PrivateArtifactDeps (platform + modeOps) is the injectable seam — Windows ("win32") never calls or simulates a POSIX chmod.
- src/receipt-s3.ts — writeAtomicFile now delegates to writePrivateArtifactFileAtomic; hydrateReceiptsFromS3's runsDir creation now goes through ensurePrivateArtifactDir.
- src/telemetry.ts — persistRunRecord's mkdir(runsDir, …) replaced with ensurePrivateArtifactDir(runsDir).
- src/events.ts — appendEvent's directory creation + JSONL append replaced with ensurePrivateArtifactDir + appendPrivateArtifactFile.
- src/traces.ts — appendTraceSpan's directory creation + JSONL append replaced with ensurePrivateArtifactDir + appendPrivateArtifactFile.
- ARCHITECTURE.md — documents the new module (arch-drift coverage).
- docs/decisions/ — new append-only AI-633 entry (separate commit).
Contract surface affected: receipt-s3.ts's exported writeAtomicFile(finalPath, contents) keeps its exact signature and unique-temp/rename/cleanup-on-failure behavior; its implementation now enforces 0700/0600 modes instead of relying on default mkdir/writeFile modes. Every existing caller (stampLocalS3Upload, writeHydratedReceiptWithStamp, telemetry.ts, trace-mirror.ts, heartbeat.ts, heartbeat-mirror.ts) is unchanged at the call site and inherits the tightened mode automatically.
## Breaking changes
None. Schema bytes for receipts/events/traces are unchanged; only filesystem permission bits on the artifact directories/files change (narrower, never wider). Windows behavior (create/write/append/atomic replacement) is unaffected — it simply never receives a POSIX chmod call.
## Test plan
- [x] pnpm exec vitest run src/private-local-artifact.test.ts src/telemetry.test.ts src/lifecycle.test.ts src/traces.test.ts src/receipt-s3.test.ts → 152 passed
- [x] pnpm typecheck → clean
- [x] pnpm test (full vitest + Python suite) → 174 test files / 5984 tests passed, Python suite green
- [x] New src/private-local-artifact.test.ts (26 tests) covers: platform seam defaults/overrides; directory create/tighten/idempotent; symlink and non-directory refusal (fails closed, no fallback); chmod call-count assertions; Windows-seam tests proving zero chmod calls (both for directory tighten and for append-repair) while writes/appends still succeed; atomic write mode + parent-dir tightening + temp-file cleanup on a forced rename failure + concurrent-writer "last write wins" convergence; append creation mode, append order preservation, and in-place repair of a pre-existing broad-mode file via the open handle. Two tests spawn a throwaway tsx child process (via the existing spawnTsx fixture) to exercise "even under a permissive umask 0, the result lands at exactly 0700/0600" without mutating this worker's own process-wide umask.
## Verification artifact
$ pnpm exec vitest run src/private-local-artifact.test.ts src/telemetry.test.ts src/lifecycle.test.ts src/traces.test.ts src/receipt-s3.test.ts✓ src/traces.test.ts (19 tests)
✓ src/private-local-artifact.test.ts (26 tests)
✓ src/lifecycle.test.ts (3 tests)
✓ src/telemetry.test.ts (13 tests)
✓ src/receipt-s3.test.ts (91 tests)
Test Files 5 passed (5)
Tests 152 passed (152)
$ pnpm typecheck
> tsc --noEmit
(clean)
$ pnpm test
Test Files 174 passed (174)
Tests 5984 passed (5984)
(Python unittest suite: green, no failures)
## Impact estimate
Business value: Stops local receipt, event, and trace artifacts from relying on host umask for confidentiality while keeping Windows execution functional.
Pre-AI estimate: 2 points — helper, four integrations, POSIX/symlink/Windows fixtures, atomic/append compatibility, and decision.
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
<!-- drones:impact-actual:begin -->
Agent time: 0 m (excludes reviewer) (implementer 0 m · reviewer not measured · addresser 0 m)
<!-- drones:impact-actual:end -->
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
<!-- drones:impact-actual:begin -->
Agent time: 0 m (excludes reviewer) (implementer 0 m · reviewer not measured · addresser 0 m)
<!-- drones:impact-actual:end -->
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
<!-- drones:impact-actual:begin -->
Agent time: 0 m (excludes reviewer) (implementer 0 m · reviewer not measured · addresser 0 m)
<!-- drones:impact-actual:end -->
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
<!-- drones:impact-actual:begin -->
Agent time: 0 m (excludes reviewer) (implementer 0 m · reviewer not measured · addresser 0 m)
<!-- drones:impact-actual:end -->
Closes AI-633
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-d46c9f71-1a52-4880-ab24-d2e2090982d4?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d46c9f71-1a52-4880-ab24-d2e2090982d4&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>