# Phase 3 release gate Phase 3 keeps the generic Agent state machine unchanged. The Host owns Root delivery, publication, recovery, authorization, and the legacy HTTP projection. The implementation baseline is `main@1495d063`; migration `0003_phase_three_publication` is the first schema revision that permits final publication. ## Database deployment Use MySQL 8.0.16 or newer. Verify `migrations/CHECKSUMS.sha256`, back up the legacy schema, and run migrations only with the deployment administrator: ```bash SCRIPT_BUILD_WRITE_DATABASE_URL='mysql+asyncmy://migration_admin:...@db/script_build' \ python -m alembic upgrade head ``` The 0003 preflight refuses missing or duplicate `script_build_record.reson_trace_id` values and duplicate publication identities. The revision adds `DATETIME(6)` ownership fencing, the Root manifest artifact kind, the HTTP command journal, one final publication identity per build, and candidate/runtime views with `WITH CASCADED CHECK OPTION`. Its revision identifier deliberately fits Alembic's MySQL `version_num VARCHAR(32)`. Production must use three different database principals: - read: `SELECT` on authorized legacy/Host tables and views only; - runtime: Host-table mutations, candidate-view writes, and only the legacy build mutations required before final success; it must not be able to write branch 0 or set `status=success`; - final publisher: the minimum branch-0, artifact, binding, publication, and build update grants required by `SqlAlchemyFinalPublicationUnitOfWork`. Grant names are deployment-specific and therefore are not embedded in Alembic. Revoke the old unrestricted writer before enabling Phase 3. Configure `SCRIPT_BUILD_READ_DATABASE_URL`, `SCRIPT_BUILD_WRITE_DATABASE_URL`, and `SCRIPT_BUILD_FINAL_DATABASE_URL`; production startup rejects reused usernames. MySQL sessions are explicitly opened at `READ COMMITTED`. Use `migrations/mysql_phase_three_grants.sql.example` as the reviewed least-privilege template. ## Publication and recovery Publication is allowed only after one frozen Manifest, passed Root Validation, and explicit Root ACCEPT. A single final AsyncSession replaces branch 0, reads the canonical legacy projection back, publishes the Manifest and StructuredScript, sets the accepted pointer, records the publication, and changes the build to `success`. Any pre-commit error rolls back all of those writes. A lost commit acknowledgement is classified by the unique final publication identity and is read back; it is never blindly replayed. Use per-build endpoints only; startup does not scan the database: - `POST .../{id}/phase-three/advance` enters the frozen Root delivery continuation; - `POST .../{id}/resume` classifies durable state and performs only a safe continuation; - `POST .../{id}/finalize` retries the same immutable final identity; - `POST .../{id}/reconcile` is inspection-only unless explicitly retrying finalize; - `GET .../{id}/publication?type=direction|final` reports publication state. If recovery reports manual reconciliation, do not edit an Artifact, digest, Decision, or accepted pointer. Preserve the task ledger and database facts for inspection. ## Required verification Run all of the following before release: ```bash python -m pytest SCRIPT_BUILD_TEST_MYSQL_DSN='mysql+asyncmy://.../disposable_db' python -m pytest -m mysql python -m ruff check src migrations tests python -m ruff format --check src migrations tests python -m mypy src/script_build_host python -m compileall -q src git diff --check ``` The MySQL database must first contain the legacy tables and be upgraded from 0001 through 0003. The gate checks MySQL version, `READ COMMITTED`, `DATETIME(6)`, enforced CHECK constraints, candidate/runtime view check options, the Alembic head, and a real atomic final publication. `tests/fixtures/legacy_http_goldens_manifest.json` records the two legacy Run 454 responses that were actually captured, including route, status, redaction policy, comparison mode, and SHA-256. Uncaptured legacy routes are listed explicitly rather than represented by invented fixtures.