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
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
---
|
||||||
|
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).
|
||||||
Reference in New Issue
Block a user