fix: harden P1 manual acceptance audit gates

This commit is contained in:
2026-08-09 23:32:42 +02:00
parent 7d8fb065b5
commit 96362929d7
6 changed files with 398 additions and 121 deletions
+44 -31
View File
@@ -24,47 +24,60 @@ Run these commands from the repository root:
./scripts/p1-manual-acceptance.sh cleanup
```
`prepare` exclusively creates `.artifacts/manual-acceptance/p1/`, with fresh Git history, fixtures,
secret files, concrete request/inspection commands, and `GUIDE.md`. It also creates the single regular
`logs/backend.log` with mode `0600` and records its exact path/device/inode ownership. It creates no
supervisor or readiness-status program/file, leaves status `PENDING` and the server stopped, and
refuses an existing root; use the guarded `stop` and `cleanup` actions rather than deleting or reusing
state manually.
All four actions serialize on the stable repository-root
`.p1-manual-acceptance.lifecycle.lock`; the helper retains and revalidates repository, artifact,
manual-parent, and owned-root identities throughout each transaction. `prepare` acquires that lock
before prerequisite checks and the backend build, exclusively creates
`.artifacts/manual-acceptance/p1/`, and immediately publishes a `PREPARING` ownership record before
populating the lab. That ownership-first record makes an interrupted population cleanable. A
successful prepare atomically advances it to `READY` after creating fresh Git history, fixtures,
secret files, concrete request/inspection commands, `GUIDE.md`, and the single regular
`logs/backend.log` with mode `0600`. It records the log identity and the production entrypoint's
path/device/inode/size/SHA-256, creates no supervisor or readiness-status file, leaves status
`PENDING` and the server stopped, and refuses an existing root. Use guarded `stop` and `cleanup`
rather than deleting or reusing state manually.
`serve` holds the external lifecycle lock, validates the canonical production
`backend/dist/server.js`, every owned root/runtime/log ancestor, the absence of a legacy supervisor,
and the original log identity before spawning. The log is opened with no-follow semantics and its
file descriptor is passed directly to the child. The child is the production Node entrypoint itself:
`node --import data:text/javascript;base64,<immutable-preload> backend/dist/server.js` followed by the
three ownership/control arguments. The immutable preload owns only an authenticated fixed
`127.0.0.1:8792` control channel and a bounded startup watchdog; the application binds
`127.0.0.1:8791` normally. Before writing the `RUNNING` PID record, the parent requires an exact
nonce-bound control STATUS and a 2xx `GET /health`, then sends READY to disarm the watchdog. A startup
or non-2xx failure requests nonce-authenticated STOP (or lets the watchdog self-exit) and leaves no
listener or PID record.
`serve` revalidates the bound `backend/dist/server.js` identity and bytes, every owned
root/runtime/log ancestor, the absence of a legacy supervisor, and the original log identity before
spawning. The log and production entrypoint are opened with no-follow semantics and their descriptors
are passed directly to the child; an immutable preload makes Node load the already verified
entrypoint bytes rather than a later pathname replacement. The child remains the production Node
entrypoint itself: `node --import data:text/javascript;base64,<immutable-preload>
backend/dist/server.js` followed by six ownership, control, and entrypoint-identity arguments. The
preload owns the authenticated fixed `127.0.0.1:8792` control channel and bounded watchdog, and tracks
the HTTP server that this same process successfully binds to `127.0.0.1:8791`. Before publishing the
`RUNNING` PID record, the parent requires exact nonce-bound control acknowledgements that identify
that owned listener, a 2xx `GET /health`, stable listener generation and entrypoint identity, and a
final authenticated status check. A foreign health listener cannot satisfy readiness. A startup or
non-2xx failure requests nonce-authenticated STOP (or lets the watchdog self-exit) and leaves no PID
record after the child exits.
`stop` revalidates the exact executable, immutable preload, production script, arguments, repository
cwd/root, and process start identity, then requests STOP over the nonce-authenticated cooperative
channel and requires the exact acknowledgement. The controlled process acknowledges and exits itself;
the production tool never sends a numeric terminating signal. `serve`, `stop`, and `cleanup` are
serialized; ambiguous, stale, or starting records remain for operator inspection. `cleanup` removes
only the exact stopped owned fixed root. Foreign siblings and automated integration artifacts are
outside its cleanup boundary.
`stop` revalidates the exact executable, immutable preload, bound production entrypoint identity and
bytes, arguments, repository cwd/root, and process start identity, then requests STOP over the
nonce-authenticated cooperative channel and requires the exact acknowledgement. The controlled
process closes its owned listener and exits itself; the tool never sends a numeric terminating
signal. Ambiguous, stale, or starting records remain for operator inspection. `cleanup` uses opened,
no-follow directory identities to rename and remove only the exact stopped owned fixed root. Foreign
siblings and automated integration artifacts are outside its cleanup boundary.
After `prepare`, follow the 14 ordered steps in the generated absolute-path `GUIDE.md`. Personally run each generated `http-01` through `http-14` curl script in numeric order; they save the exact status, three validation, three sequential publication, pull, three read responses, and three ZIP exports. Each publication derives its current base commit with a bounded parser from the preceding saved API response, with no placeholder base. Run the five numbered negative validation scripts separately at checklist step 10. The render commands validate the bounded saved read response,
its commit-addressed owned snapshot path, the saved publish commit, and the installed Git HEAD before
calling the acceptance-only production renderer. The renderer imports the built `ThtRunner`, resolves
bindings from environment paths, copies one lease atomically with mode `0600`, and releases it in
bindings from environment paths, copies one lease with mode `0600` through an opened no-follow
`rendered` directory descriptor, rejects an output-parent identity swap, and releases the lease in
`finally`. For each exported ZIP, invoke the generated extractor with the exact expected workspace ID
(`p1-filesystem`, `p1-http`, or `p1-s3`); its `python3` helper opens the source once, stages and
revalidates its SHA-256, anchors every extraction and cleanup operation to an opened no-follow
`exports/extracted` directory descriptor, and binds both the manifest and parsed descriptor identity
to that expected ID. It verifies exactly four regular entries and publishes only their exact checked
bytes. The generated secret scan reads every bounded filesystem file outside the direct
`fixture-secrets` directory, including Git metadata and arbitrary `.git`-named directories, then
enumerates every reachable or unreachable Git object and scans the bounded raw blob, commit, tree,
and tag bytes. Findings redact canary values. Do not inspect or print raw secret-file contents; only
inspect ownership/mode/path metadata and canary absence outside `fixture-secrets`.
bytes. The generated secret scan reads bounded filesystem content and name/path bytes outside the
direct `fixture-secrets` payload directory, discovers every bounded `.git` repository under the lab
(plus the owned bare remote), and enumerates every reachable or unreachable object. It
scans raw blob, commit, tree, and tag bytes plus loose-ref names. Findings and operational diagnostics
redact canary-bearing paths and values. The absence gate rejects directories as well as files,
including the canonical `artifacts/evidence` tree and preprocessing, materialization, embedding,
Qdrant, ACTIVE, or retention names. Do not inspect or print raw secret-file contents; only inspect
ownership/mode/path metadata and canary absence outside `fixture-secrets`.
## Failures and verdict
@@ -81,4 +94,4 @@ walkthrough, containing:
Passing `bash scripts/test-p1-manual-acceptance.sh` proves only that the tooling guards work. It does
not perform or approve manual acceptance and leaves the project-level manual status PENDING.
Expected safe outcomes are one listener on `127.0.0.1:8791`; 2xx positive responses; non-2xx negative validations without Git or snapshot mutation; an empty render diff; two successful `tht config check` calls; no manifest, Evidence/export, secret, or out-of-scope-artifact finding; and no PID or listener after `stop`.
Expected safe outcomes are one production Node PID owning both listeners on `127.0.0.1:8791` and the authenticated control port `127.0.0.1:8792`; 2xx positive responses; non-2xx negative validations without Git or snapshot mutation; an empty render diff; two successful `tht config check` calls; no manifest, Evidence/export, secret, or out-of-scope-artifact finding; and no PID or listener on either port after `stop`.