chore: restore unrelated task report

This commit is contained in:
Jesse_Chen
2026-07-21 04:03:04 +08:00
parent 2f08940717
commit bd7c9830bb
+67 -129
View File
@@ -1,146 +1,84 @@
# Task 3 — Encrypted Local Staging Backups
# Task 3 — TypeScript Engine Adapter and Trust Boundary
## Scope delivered
## Final design
- Added `deploy/backup-staging-postgres.sh DATABASE_ENV_FILE BACKUP_DIRECTORY`.
- Validates the private database environment before reading required values without sourcing or executing the env file.
- Refuses backup destinations with disk usage at or above 70%, creates the explicit destination with mode `0700`, writes encrypted custom PostgreSQL dumps with mode `0600`, and keeps only the newest three exact staging dump names.
- Uses a PID-scoped `.partial` file, `pipefail`, an exit cleanup trap, and same-directory hard-link/no-clobber publication so failed dump/encryption pipelines leave no completed partial archive and successful archives are published atomically.
- Emits only final path/count on success; credentials use environment variables and never command-line arguments.
- `BirthTimeJourneyEngine` requires both dynamic operations: `buildDifferencePacket` and `scoreChoices`. Existing scan/score consumers depend on the explicit `LegacyBirthTimeJourneyEngine` pick instead of weakening the primary interface.
- The server-only engine factory owns `JYOTISH_DYNAMIC_RECTIFICATION_TOKEN`. Both dynamic endpoints use an authenticated POST and a 45-second abort signal; all three legacy endpoints remain unauthenticated.
- Request serializers expose only server-resolved choice evidence. Client option IDs, confidence, applicability, and model-controlled safety gates are never sent as scoring authority.
- Dynamic v2 responses have a dedicated strict adapter. Root objects, ranges, opportunities, partitions, winning segments, score-map keys, counts, versions, modes, and duplicate identifiers are validated before mapping.
- Public difference packets omit private candidate score vectors. Private scoring partitions retain the exact server vector used by the later deterministic scoring call.
- Legacy response parsing remains compatibility-oriented: unknown server metadata is accepted and stripped. Only the shared result representation supports up to ten effective items; the legacy dated-event request remains capped at six.
- The HTTP wire accepts injected fetch and timeout-signal factories for executable contract tests. Production still defaults to `AbortSignal.timeout`.
## TDD evidence
## Files changed
RED (before the script existed):
Production:
```text
cd frontend && npm run test:db
FAIL database-backup.test.ts: bash: .../deploy/backup-staging-postgres.sh: No such file or directory
```
- `frontend/src/lib/birth-time-evidence.ts`
- `frontend/src/lib/birth-time-journey-service.ts`
- `frontend/src/lib/birth-time-journey-engine.ts`
- `frontend/src/lib/birth-time-journey-engine-model.ts`
- `frontend/src/lib/birth-time-journey-adapters.ts`
- `frontend/src/lib/birth-time-journey-dynamic-adapters.ts`
- `frontend/src/lib/birth-time-journey-assessment.ts`
GREEN focused integration:
Tests and support:
```text
./node_modules/.bin/tsx --test --test-concurrency=1 tests/database-backup.test.ts
pass 1, fail 0
```
- `frontend/tests/birth-time-journey-engine.test.ts`
- `frontend/tests/birth-time-journey-adapters.test.ts`
- `frontend/tests/birth-time-journey-dynamic-adapters.test.ts`
- `frontend/tests/birth-time-journey-memory-store.ts`
- `frontend/tests/birth-time-journey-test-support.ts`
- `frontend/tests/birth-time-journey-service.test.ts`
- `frontend/tests/birth-time-agent-flow-test-support.ts`
The integration starts the real fixture, creates four encrypted dumps at deterministic timestamps, verifies the final three names and modes, decrypts an archive with OpenSSL, and runs `pg_restore --list` (falling back to the fixture container when necessary).
Documentation:
- `docs/superpowers/plans/2026-07-18-dynamic-choice-birth-time-rectification.md`
- `.superpowers/sdd/task-3-report.md`
## RED evidence
1. The initial focused run failed to load because the dynamic response parsers did not exist.
2. Adapter regressions then exposed acceptance of malformed score keys, keys outside the submitted range, duplicate opportunity/partition identifiers, nested extra fields, and legacy evidence metadata incompatibility.
3. Interface and wire review probes exposed optional primary dynamic methods, source-regex authentication assertions, and a missing executable proof for exact URLs, bodies, authorization, and timeout behavior.
4. The final wire cleanup test injected a timeout factory and failed with `[]` instead of `[45000, 45000]`, proving that the seam was initially ignored.
## Final verification
```text
bash -n deploy/backup-staging-postgres.sh exit 0
cd frontend && npm run test:db 12 passed, 0 failed
cd frontend && npm test 488 passed, 0 failed
cd frontend && npm run lint exit 0
git diff --check exit 0
```
1. Focused adapter, wire, and evidence suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time-journey-engine.test.ts tests/birth-time-journey-adapters.test.ts tests/birth-time-journey-dynamic-adapters.test.ts tests/birth-time-evidence.test.ts`
- 29 passed, 0 failed.
2. Complete birth-time frontend suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/birth-time*.test.ts`
- 208 passed, 0 failed.
3. Full frontend suite:
- `/Users/jesse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test tests/*.test.ts`
- 283 passed, 0 failed.
4. ESLint across every changed production/test TypeScript module:
- Passed with no diagnostics.
5. TypeScript diagnostic:
- No Task 3 diagnostics. The only result is the known baseline `tests/profile-persistence.test.ts:7 TS1501`, caused by an ES2018 regex flag under the project's ES2017 target.
6. Pure-LOC audit:
- Every changed TypeScript file is at or below 250 pure LOC. The largest is `frontend/src/lib/birth-time-journey-service.ts` at 239; the split test-support modules are 171 and 111.
7. `git diff --check`:
- Passed with no whitespace errors.
## Pre-scan race follow-up (2026-07-21)
## Pre-work gate
- Lexical component validation now completes before ancestor scanning. Once scanning finds the first absent component, it stops constructing deeper absolute pathnames immediately; creation continues only from the already pinned deepest existing directory.
- A deterministic `BASH_ENV` DEBUG-hook regression inserts a symlink after the first missing component is recorded. The script rejects it from the pinned parent and creates no `backup` path under the symlink target. Creation uses `./$component` for test, mkdir, validation, and `cd -P`, so legal names beginning with `-` are handled as names rather than options.
- `/Users/jesse/Downloads/Copse/astrology/yinduzhanxing/.venv/bin/python scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` remained red only on the unrelated known fragment-governance baseline: `candidate_count` expected `0`, observed `1`.
- Remote visibility was blocked, so no cloud-sync claim is made.
```text
RED:
cd frontend && npm run test:db
FAIL stops absolute pre-scan traversal when a symlink appears after the first missing component
FAIL creates every absent backup path component privately despite a permissive caller umask
## Self-review
GREEN:
bash -n deploy/backup-staging-postgres.sh exit 0
cd frontend && npm run test:db 21 passed, 0 failed
cd frontend && npm test 497 passed, 0 failed
cd frontend && npm run lint exit 0
git diff --check exit 0
```
- Dynamic secrets and candidate score vectors remain behind the server boundary.
- Wire tests use independent literal request bodies rather than production serializers and directly assert two `45_000` timeout calls and the exact injected signals.
- Missing-token tests prove both dynamic operations fail before fetch. Executable legacy tests prove no Authorization header reaches any legacy endpoint.
- Dynamic parsing is fail-closed; legacy parsing preserves its prior accept-and-strip behavior.
- Cross-midnight ranges enumerate minutes modulo 24 hours and bind score keys to the exact submitted interval.
- The extracted memory store has no dependency on the fixture module, so its re-export does not create a runtime cycle.
- No dependency, logging field, client response field, or persistence write was added.
## Atomic component-creation follow-up (2026-07-20)
## Known unrelated baseline
- The sticky root-owned exception now applies only to ancestors. An existing requested backup leaf is separately required to be current-user owned, non-symlink, and non-group/world-writable before the script can chmod or write it; direct canonical `/tmp` is rejected without calling `chmod` or changing its mode.
- The script no longer uses `mkdir -p` for backup paths. It pins the deepest validated existing ancestor, creates each missing component with one plain relative `mkdir`, validates it, and `cd -P`s into it before creating the next. If a concurrent actor creates any component first, including a symlink, the script rejects it without descending through it.
```text
RED:
cd frontend && npm run test:db
FAIL rejects a direct canonical sticky shared backup directory before chmod
FAIL fails safely when a racer inserts a symlink during nested backup path creation
GREEN:
bash -n deploy/backup-staging-postgres.sh exit 0
cd frontend && npm run test:db 20 passed, 0 failed
cd frontend && npm test 496 passed, 0 failed
cd frontend && npm run lint exit 0
git diff --check exit 0
```
## Secure directory-creation follow-up (2026-07-20)
- `umask 077` now executes at script startup, before any possible `mkdir`. The script records the first absent component, then re-walks every component after `mkdir -p`; all newly created directories must be current-user owned, non-symlink directories without group/world write permission before `cd -P` pins the target.
- Existing root-owned sticky shared ancestors, including canonical `/tmp`, are allowed; ordinary group/world-writable ancestors remain rejected. The shared-temporary-root tests canonicalize `/tmp` with `realpathSync`, so they prove validation reaches their deliberately nested symlink and `0777` parent instead of stopping at the standard sticky ancestor.
```text
RED:
cd frontend && npm run test:db
FAIL rejects destructive backup directory aliases and symlink components before mutation
FAIL creates every absent backup path component privately despite a permissive caller umask
The prior policy rejected canonical /tmp and only set umask after mkdir.
GREEN:
bash -n deploy/backup-staging-postgres.sh exit 0
cd frontend && npm run test:db 18 passed, 0 failed
cd frontend && npm test 494 passed, 0 failed
cd frontend && npm run lint exit 0
git diff --check exit 0
```
## TOCTOU follow-up (2026-07-20)
- Before `mkdir`/`cd`, the script walks all existing absolute-target ancestors, rejects symlinks, requires current-user-or-root ownership, and rejects group/world-writable modes. The unsafe-parent regression uses `realpathSync` for the macOS temporary root, so its nested symlink and `0777` parent are the components actually reached by validation.
- After creation it enters the directory with `cd -P`, verifies the canonical path and directory identity, and keeps the working directory pinned. Disk check, lock, partial/final publication, inventory, `find`, and rotation all use `.` or relative filenames; success still prints the canonical absolute archive path.
```text
RED:
cd frontend && npm run test:db
FAIL rejects unsafe writable backup parents before creating the target
The old script created the target and then failed only at the disk check, rather than rejecting the unsafe ancestor.
GREEN:
bash -n deploy/backup-staging-postgres.sh exit 0
cd frontend && npm run test:db 17 passed, 0 failed
cd frontend && npm test 493 passed, 0 failed
cd frontend && npm run lint exit 0
git diff --check exit 0
```
## Self-review and caveats
- Reviewed the final diff for secret exposure, output scope, filename filtering, rotation boundaries, portable shell options, and atomic/cleanup behavior; no task-scope finding remained.
- This Mac's actual filesystem usage is at least 70%, which correctly makes the production script refuse a backup. The integration test supplies a controlled `df` executable reporting 10% usage so it can exercise the real Docker/PostgreSQL/OpenSSL/`pg_restore` flow without weakening the production guard.
- The mandatory repository pre-work gate remains host-blocked for its known unrelated reason: system Python 3.9 lacks pytest (and cannot import the project's Python 3.10+ syntax). No Task 3 change touches that gate.
## Follow-up safety hardening (2026-07-20)
Implementation:
- Rejects `/`, relative paths, repeated/trailing separators, `.`/`..` traversal, and every existing symlink component before the script can create, chmod, write, or delete under the requested backup path. After `mkdir -p`, it resolves physically and requires the exact non-root input before chmod.
- Acquires a per-archive directory lock using portable exclusive `mkdir`; publishes with a same-directory hard link (`ln`) rather than overwrite-capable `mv`; removes partial and owned lock artifacts on every handled exit.
- Captures `find | LC_ALL=C sort` into a variable under `pipefail` and fails before retention deletion if enumeration fails. Retention remains the newest exact three archives.
- The collision regression no longer mocks `mv`. It runs two same-timestamp processes through a slow, successful dump producer, proves exactly one success/archive, decrypts it, and verifies no lock/partial remains. The existing integration still uses real PostgreSQL dump, OpenSSL decryption, and `pg_restore --list`.
Exact RED/GREEN evidence:
```text
RED against 6008d2f (before this implementation):
cd frontend && npm run test:db -- --test-name-pattern='rejects destructive|same-second|find enumeration|refuses full'
tests 16; pass 14; fail 2
- root/alias handling returned the old root-only message instead of the required pre-mutation rejection
- two same-second invocations both succeeded (2 !== 1)
GREEN after implementation:
bash -n deploy/backup-staging-postgres.sh exit 0
cd frontend && npm run test:db 16 passed, 0 failed
cd frontend && npm test 492 passed, 0 failed
cd frontend && npm run lint exit 0
git diff --check exit 0
```
- A clean TypeScript run is still blocked by `tests/profile-persistence.test.ts:7 TS1501`; Task 3 introduces no additional diagnostic.