Files
araharah-executor dea6261deb docs(07-03): complete the consolidated privacy-audit plan — SUMMARY
- 12-vector TDD suite, 4 source gates + bundle gate + 2 live probes, the
  0/1/2 exit contract, and docs/PRIVACY-AUDIT.md signed-record scaffold
- PRIVACY AUDIT CLEAN — 5 gate(s) passed, 0 violations on the audited tree
- baselines intact: bot 125/125, web 165/165
2026-10-25 04:41:12 +08:00

150 lines
10 KiB
Markdown

---
phase: 07-hardening-private-launch
plan: 03
subsystem: testing
tags: [privacy-audit, grep-gates, comment-stripping, kv-ttl, zero-telemetry, allowlist, wrangler, cloudflare-kv, curl-probe]
# Dependency graph
requires:
- phase: 03-mini-app-shell-geolocation
provides: the memory-only client law + the canonical comment-stripper (web/test/no-storage.test.mjs, D-07 origin)
- phase: 02-bot-skeleton-initdata-security
provides: the PG1/PG2 TTL-only KV write law (bot/test/privacy.test.mjs) and the D-14 category-only log vocabulary (bot/src/webhook.ts)
- phase: 05/06 routing + guidance
provides: the route-privacy RP1/RP2 allowlist style and the TTL key families the audit probes (bot/src/kv/*)
provides:
- "The ONE consolidated audit CLI: npm run privacy-audit (scripts/privacy-audit.mjs) — 5 static gates + 2 --live probes, exit codes 0/1/2 (never a silent pass)"
- "scripts/lib/source-gates.mjs — storage-api / kv-ttl / log-vocabulary / runtime-hosts gates extended repo-wide over ALL five src trees"
- "scripts/lib/bundle-gates.mjs — PRODUCTION web/dist telemetry scan (analytics/beacon tokens, positive host allowlist, deep-link crypto, storage tokens)"
- "scripts/lib/cloudflare-probes.mjs — live kv-expiry (list_complete/cursor/expiration parsing) + live asset-headers probes, degrading to notRun without credentials"
- "docs/PRIVACY-AUDIT.md — the signed written audit record citing the ACTUAL run evidence (D-01), with a human signature block for the launch gate"
affects: [07-04 (budget-audit shares the _headers/live-probe surface), 07-05 (limitations doc), 07-06 (launch gate — the privacy-audit-signed checklist leg), any fork onboarding]
# Tech tracking
tech-stack:
added: [] # zero new dependencies — node:fs/node:child_process/curl/npx wrangler only
patterns:
- "Gate-as-function returning {id, pass, evidence, violations} (07-RESEARCH Pattern 4 shape)"
- "Canonical comment-stripped source introspection with documented prefer-fail adjudications (theme-audit 'Scope adjudications' precedent)"
- "Positive host allowlist with the inventory PRINTED, never assumed (A4)"
- "notRun as the exit-2 class: a skipped probe is never a pass (T-07-16)"
- "PRIVACY_AUDIT_ROOT env override — cwd-independent gates, fork/test injectability"
key-files:
created:
- scripts/privacy-audit.mjs
- scripts/lib/source-gates.mjs
- scripts/lib/bundle-gates.mjs
- scripts/lib/cloudflare-probes.mjs
- scripts/test/privacy-audit.test.mjs
- docs/PRIVACY-AUDIT.md
modified:
- package.json (the root "privacy-audit" script)
key-decisions:
- "Bundle host scan adjudication: MapLibre's own static-text URLs (maplibre.org, wiki.openstreetmap.org, github.com diagnostics, maplibre.invalid) + our ODbL license strings (opendatacommons.org) form a DOCUMENTED static-text class alongside the 2-host fetch allowlist — each host enumerated with its reason; any other host fails"
- "W3C namespace identifiers (svg/xhtml) excepted as spec constants, never fetched — documented in both gate files"
- "Line-numbered gates scan raw lines with only the // tail removed (honest line numbers); block-comment prose mentioning a scanned token fails prefer-fail"
- "Asset-headers probe reads HTTP status BEFORE interpreting headers — a stale-deploy 404 is deployment drift (notRun, exit-2 class), never a cache violation"
- "The audit suite is a hand-rolled 12-vector TDD file (scripts/test/privacy-audit.test.mjs) with fixture-root injection, proving each gate can FAIL — a gate that cannot fail is decoration"
patterns-established:
- "PRIVACY AUDIT CLEAN/FAILED/NOT RUN — the consolidated audit's summary vocabulary + 0/1/2 exit contract"
- "Gates are cwd-INDEPENDENT (module-URL root) so a stray subdirectory run cannot false-pass"
requirements-completed: [PX-04] # PX-04 claimed here: SC3 is a Phase-7 success criterion under the PX-04 phase; PRIV-02 + GEO-03 are re-verified end-to-end (D-01), not re-claimed
# Metrics
duration: 9min
completed: 2026-10-24
---
# Phase 7 Plan 03: Consolidated privacy audit + signed record Summary
**One consolidated `npm run privacy-audit` CLI — 5 static gates over all five src trees + the production bundle, 2 credential-gated live probes, honest 0/1/2 exit codes — plus `docs/PRIVACY-AUDIT.md`, the signed audit record citing the real run's evidence (D-01/D-02).**
## Performance
- **Duration:** 9 min (20:32Z → 20:41Z)
- **Started:** 2026-10-24T20:32:07Z
- **Completed:** 2026-10-24T20:41:00Z
- **Tasks:** 3 (TDD RED→GREEN)
- **Files modified:** 7 (6 created, 1 modified)
## Accomplishments
- The repo's gate lineage consolidated, not duplicated: the phase-3 memory-only law, the PG1 same-line TTL law, the D-14 category-only log law, and the bundle laws are now enforced REPO-WIDE by one tool — the existing unit-level suites stay as the pins
- All 5 static gates PASS on the audited tree (commit `9c55e9a`): 72 files scanned, 4 KV writes all TTL-named, 28 log call sites category-verified, host inventories printed (source + bundle), ZERO analytics/beacon/crypto/storage tokens in `web/dist`
- `docs/PRIVACY-AUDIT.md` carries the ACTUAL evidence strings from the run, the four KV-family storage inventory, the log vocabulary, the runtime-network inventory with D-02's zero-telemetry posture, and the blank human signature block (D-09's launch gate signs it)
- Live probes verified honest-by-construction: without credentials both return notRun and the CLI exits 2 — never a silent pass (T-07-16)
## task Commits
Each task was committed atomically (TDD: RED gate commit → GREEN feat commits):
1. **task 1: the source-gates library** — `87fcb28` (test: RED — 12-vector suite) + `430993b` (feat)
2. **task 2: bundle-gates + cloudflare-probes** — `b959e23` (feat)
3. **task 3: the CLI + npm script + signed record** — `9c55e9a` (feat) + `8229e36` (docs)
## Files Created/Modified
- `scripts/lib/source-gates.mjs` — runStorageGate / runKvTtlGate / runLogVocabularyGate / runRuntimeHostGate over web/bot/routing/search/theme src
- `scripts/lib/bundle-gates.mjs` — runBundleTelemetryGate (PRODUCTION `web/dist` ONLY; `dist-harness` forbidden)
- `scripts/lib/cloudflare-probes.mjs` — runKvExpiryProbe / runAssetHeaderProbe / probeUnavailable (namespace id read from `bot/wrangler.jsonc` at runtime)
- `scripts/privacy-audit.mjs` — the orchestrator: Pattern-4 ordering, per-gate evidence lines, CLEAN/FAILED/NOT RUN summaries, process.exit 0/1/2
- `scripts/test/privacy-audit.test.mjs` — the 12-vector TDD suite (SA1-5, BG1-2, CP1-2, AU1-3)
- `package.json` — root `"privacy-audit": "node scripts/privacy-audit.mjs"`
- `docs/PRIVACY-AUDIT.md` — the signed audit record (D-01)
## Decisions Made
- **Bundle static-text adjudication:** the plan's 2-host allowlist fails on MapLibre's own embedded attribution/diagnostic URLs in the shipped bundle (they are library constants and our dataset's ODbL metadata — never fetched). Followed the repo's own "Scope adjudications (documented, deliberate)" precedent (theme/scripts/audit.mjs): an explicitly enumerated static-text class, each host with its reason, full inventory printed as evidence. Any host outside both lists fails.
- **Header probe honesty:** the first live run exposed the probe interpreting a 404 as "no cache-control" — the probe now reads HTTP status first and treats origin/build mismatch as deployment drift (exit-2 class). The immutable `/assets/*` policy ASSERTION belongs to 07-04's budget-audit (its `_headers` file); this probe quotes, never interprets.
- **Test injectability:** gates accept an opts-only roots/dist override (production callers pass nothing), letting the suite prove each gate FAILS on synthetic violations in temp fixtures.
- **cwd-independent root** with the `PRIVACY_AUDIT_ROOT` override — a stray subdirectory run must not scan the wrong tree and false-pass.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Asset-header probe misread a stale-deploy 404 as a cache violation**
- **Found during:** task 3 (first `--live` run)
- **Issue:** the probe fetched the local dist's hashed asset from the deployed origin without checking HTTP status; a 404 printed "no cache-control header observed" and FAILED the gate with `undefined violation(s)` (also a `${violations.length}` bug on a number)
- **Fix:** status line read before header interpretation; non-2xx = notRun (deployment drift) with observed headers carried in evidence; the count bug fixed
- **Files modified:** scripts/lib/cloudflare-probes.mjs
- **Verification:** `npm run privacy-audit -- --live` now exits 2 with `PRIVACY AUDIT NOT RUN — deployment drift …`; CP2 vector pins the shape
- **Committed in:** b959e23/9c55e9a follow-ups (folded into the task-2/3 commits)
---
**Total deviations:** 1 auto-fixed (1 bug)
**Impact on plan:** The fix makes the live probe's honesty contract hold end-to-end. The live legs were recorded NOT RUN in docs/PRIVACY-AUDIT.md — honest, exit-2 class, re-run at the launch gate.
## Issues Encountered
- The deployed Worker (`araharah-bot.workers.dev`) serves a bundle older than the current `web/dist` (its hashed asset 404s) — correctly reported as deployment drift by the probe; a redeploy (07-04/07-06) plus shipped `_headers` will turn the live legs green.
## Known Stubs
None — every gate/probe is fully wired; no placeholder data flows anywhere.
## User Setup Required
None — no external service configuration required. (The `--live` probes need `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` in the environment; absent credentials degrade to exit 2 by design, never a pass.)
## Next Phase Readiness
- 07-04 (budget-audit) re-runs the live header probe against a deployed build carrying its `_headers` policy — the two plans share the live-probe surface
- 07-06 (launch gate) signs `docs/PRIVACY-AUDIT.md`'s signature block as the privacy-audit checklist leg; the `--live` re-run protocol is documented in the record
- Baselines intact: bot 125/125, web 165/165; `npm run privacy-audit` exits 0 (PRIVACY AUDIT CLEAN — 5 gate(s) passed, 0 violations)
---
*Phase: 07-hardening-private-launch*
*Completed: 2026-10-24*
## Self-Check: PASSED
All 7 created files verified on disk; all 5 task commits verified in git log
(`87fcb28` test-RED, `430993b`, `b959e23`, `9c55e9a`, `8229e36`). STATE.md and
ROADMAP.md untouched (orchestrator-owned).