Skip to content

fix(deploy): report the real error, and undo a seed whose commit failed - #132

Merged
pparage merged 1 commit into
devfrom
fix/integrity-and-commit-compensation
Aug 6, 2026
Merged

fix(deploy): report the real error, and undo a seed whose commit failed#132
pparage merged 1 commit into
devfrom
fix/integrity-and-commit-compensation

Conversation

@pparage

@pparage pparage commented Aug 6, 2026

Copy link
Copy Markdown
Member

Two P2s from Codex on the atomicity fix (#131). Both reproduce.

1. A bad reference was reported as "already exists"

PRAGMA foreign_keys=ON (app/core/db.py:24) means an unknown project_id or target_host_id raises IntegrityError from the same flush() as a name clash — and got translated into 409 DEPLOYMENT_EXISTS:

unknown project -> 409 DEPLOYMENT_EXISTS | A deployment named BRAVO/demo_lab already exists
unknown host    -> 409 DEPLOYMENT_EXISTS | A deployment named CHARLIE/demo_lab already exists

Now:

unknown project -> 404 NOT_FOUND | Project DOES-NOT-EXIST not found
unknown host    -> 404 NOT_FOUND | ProxmoxHost NOPE not found

Both references are validated up front (matching how patch_project handles a missing project), and the IntegrityError handler now translates only a uniqueness violation into 409 — anything else keeps its own detail instead of being dressed up as a name clash.

2. A failed commit left the secret behind

If commit() failed after the password was written, the row was gone but vault_pass.txt remained in the workspace. The next create for that name without a password skips the seed branch entirely — so it would silently adopt a secret belonging to a deployment that never existed, and the orphaned secret would otherwise sit on disk indefinitely.

The prior file is now snapshotted before writing and restored on commit failure — removed when there was none, put back when an operator had seeded one. Restoration is best-effort and logs rather than raising, so it cannot mask the original failure.

Tests

Three, all failing against the previous commit:

FAILED test_unknown_project_and_host_report_what_is_actually_wrong
FAILED test_commit_failure_does_not_leave_a_stale_secret
FAILED test_commit_failure_restores_an_operator_seeded_secret

477 passed, ruff clean.

This is the fourth round on this one handler. Each round found something real, but that is a signal in itself: create-deployment now carries workspace creation, name reservation, secret seeding and best-effort token provisioning. Worth extracting the workspace-side effects behind a single unit with its own tests rather than continuing to patch the route — happy to open an issue if you agree.

Two more from Codex review of the atomicity fix.

Foreign keys are enforced at DB level (PRAGMA foreign_keys=ON), so an
unknown project_id or target_host_id reached the flush and raised the
same IntegrityError as a name clash — the API answered 'A deployment
named BRAVO/demo_lab already exists' for a project that does not exist.
Validate both references up front with a proper 404, and translate only
a uniqueness violation into 409; anything else keeps its own detail.

If the commit failed after the password was written, the row was gone
but vault_pass.txt remained. A later create that supplies no password
skips the seed branch entirely, so it would silently adopt a secret
belonging to a deployment that never existed. Snapshot the prior file
and restore it — or remove it when there was none — when the commit
fails.

All three new tests fail against the previous commit.
@pparage
pparage merged commit 4ec3739 into dev Aug 6, 2026
3 checks passed
@pparage
pparage deleted the fix/integrity-and-commit-compensation branch August 6, 2026 10:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4f723fc6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +180 to +181
if vault_pass_file.is_file():
prior_secret = vault_pass_file.read_text()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the existing secret as bytes

When an operator-seeded password file contains non-UTF-8 bytes, read_text() raises UnicodeDecodeError (which is not caught by the OSError handler), so a request supplying a replacement password fails with an unhandled 500 before the existing file can be overwritten. Text-mode snapshotting can also normalize line endings when the old secret is restored after a commit failure. Snapshotting and restoring the file with read_bytes()/write_bytes() preserves arbitrary password-file contents and permits replacement of non-UTF-8 secrets.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant