PHASE_THREE_RELEASE.md 4.0 KB

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:

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:

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.