Skip to content

Let first hostnames replace a service-created unrouted hostname - #6061

Merged
shiny-code-app[bot] merged 3 commits into
mainfrom
work/lp6059-seeded-domain
Nov 12, 2026
Merged

shiny-code-app[bot] merged 3 commits into
mainfrom
work/lp6059-seeded-domain

Conversation

@shiny-code-app

Copy link
Copy Markdown
Contributor

Why

A first production compose created with domains but no runtime_port keeps those hostnames in its tracked target record, with no provider route. The Director-agent first-hostname continuation (setup_empty_lane_domain) accepted tracked domains only when they were empty or exactly the requested names. So a reviewed request for different names was refused in its first record-only check, before any provider call or record write: HTTP 400 invalid_dokploy_target_setup, "First hostname setup requires an unchanged service-created empty compose."

What changed

The service already holds positive provenance for those hostnames: the completed receipt of its own create-compose apply on /v1/dokploy-targets/setup. Both the durable new-placement path and the existing-environment path store it, and it carries the created target record, target-id record and route_domain_ids.

  • list_completed_target_creation_receipts (store read) finds completed create-compose receipts for one lane's exact target id.
  • The record-only check now also accepts tracked domains when a receipt shows mode=apply, applied, no routes created, an empty env, and target and target-id records equal to the current ones. Anything else keeps the original refusal: no receipt, a receipt for another target, operation, instance or context, a routed creation, or any later change to either record.
  • The service still records exactly the requested names, so a created hostname that was not requested is dropped and never routed. Inside the lane locks, before any provider read or write, a dropped hostname must have no route binding, no other lane's claim and no own-lane profile URL. The existing exact-route matcher refuses any current provider route for it.
  • No other check changed. Owner Live, ownership, sharing, holds, recorded use or custody, deployment history, serving inventory, provider source, settings and work, unresolved original setup, executor locks and hostname claims still apply.

Effects on success are unchanged: per requested hostname, at most one HTTPS web route with the default certificate, or adoption of an exact one, then one compare-and-write of the target's domains. The record's source_label becomes the reconcile label, so the created-hostname allowance cannot apply again. Same-key replay and success-only receipt semantics are unchanged.

Verification

  • Failing first: on unchanged source, the new success and recovery witnesses reproduce the exact refusal (400 invalid_dokploy_target_setup, same message), including two end-to-end tests. Those tests drive the real first-production create-compose producer, both new placement (durable) and existing environment, with a created hostname and then the reviewed two-name continuation.
  • New witnesses: success records only the reviewed pair and creates exactly two routes; empty and exact-pair behavior is unchanged; refusals with no effect cover a missing receipt and eleven non-binding receipt variants (routed creation, adopt, dry-run, not applied, other instance or context, changed target record or target-id record), extra or foreign tracked names, a routed created hostname, a binding or own-lane URL for it, Live, recorded use, unresolved original setup and deployment history. Recovery after a lost second-route response and after a lost receipt continues only the same request.
  • Safety classification: production domain setup guard plus a storage read. Whole modules run locally: tests.test_director_testing_domain_setup tests.test_director_testing_target_creation tests.test_dokploy_compose_domain_outcomes tests.test_dokploy_target_setup_http tests.test_dokploy_empty_automation tests.test_director_testing_source_completion tests.test_dokploy_compose_source_setup tests.test_first_testing_public_ingress tests.test_dokploy_domain_authority_planning tests.test_public_host_authority_fences tests.test_action_effects tests.test_http_app_driver_dokploy tests.test_operation_authorization tests.test_durable_operation_authorization tests.test_disposable_lane_authority tests.test_authz_candidate_preparation tests.test_config_authority_audit tests.test_service: 593 tests OK (7 skipped) at e394768. Later commits changed only tests, docs and a local rename; both touched test modules pass at the head.
  • Ruff check and format, mypy, role words: pass.
  • Native PyCharm inspection, 4 changed Python files: GREEN at 9b9315a, 0 findings on changed lines. An earlier run timed out (UNKNOWN). A later RED run with 6 weak warnings (duplicated recovery steps, shadowed names) was fixed.

Independent review

OpenAI (Codex CLI default; the CLI did not report the model), two rounds.

  • Round 1, P3 (test pinned the error message): fixed. The refusal helper asserts status, code, no effects and no provider contact.
  • Round 1, P2 (record equality does not prove the hostname was never routed): the docs overstated it and now state exactly what is checked. Declined as a safety defect. The reachable sequence needs a service-scoped general reconcile of the created hostname, then a prune whose record write fails. That leaves a never-deployed compose with no current route, which is the end state the prune intended. Failed attempts store no receipt, so no existing evidence could distinguish it further.
  • Round 1, P1 (after a partial outcome, a retry adding the created hostname routes it): declined. A fresh request with those names on the same state qualifies identically. Success-only receipts predate this branch and are shared with the testing continuation, as weighed on Admit first backend routes on an owned never-live production lane #6058.
  • Round 1, query scaling: declined. The query runs only when tracked domains are neither empty nor exact.
  • Round 2 rechecked the changes and both declines independently: none.

Removed: no maintained source; the duplicated recovery steps in the first-hostname tests are now shared fixture helpers.
Net lines: source +126 (+127/−1), docs +9, tests +286 (+316/−30).

Refs #6059

A first production compose created with a hostname but no runtime port keeps
that hostname in its tracked domains with no provider route. The first
hostname continuation accepted only empty tracked domains or exactly the
requested names, so the reviewed request was refused before any effect.

Accept the tracked hostnames only when a completed creation receipt on the
setup route shows the service created this exact target and target-id record
with them and no route, and neither record changed since. Record only the
reviewed hostnames. A created hostname that was routed, bound, claimed by
another lane or named by the lane's own profile URL still refuses.
The docs claimed the hostname never got a route and no record was rewritten;
the check proves the creation receipt, current record equality and no current
route or claim. The refusal tests assert status, code and no effects instead
of the message text.
Native inspection reported duplicated recovery steps and shadowed names; the
shared fixture helpers keep every assertion.
@shiny-code-app

Copy link
Copy Markdown
Contributor Author

Review result: weighed
Head: 9b9315a

Weighed independent review, OpenAI (Codex CLI default; the CLI did not report the model), two rounds, read-only against this branch.

  • Round 1, P3 (test pinned the error message): fixed in f04f150. The refusal helper asserts status, code, no effects and no provider contact.
  • Round 1, P2 (record equality does not prove the created hostname was never routed): the docs overstated it and were corrected in f04f150. Declined as a safety defect. The reachable sequence needs a service-scoped general reconcile of that hostname, then a prune whose record write fails. That leaves a never-deployed compose with no current route, which is the prune's intended end state. Failed attempts store no receipt, so no existing evidence distinguishes it further.
  • Round 1, P1 (after a partial outcome, a retry adding the created hostname routes it): declined. A fresh request with those names on the same current state qualifies identically. Success-only receipts predate this branch and are shared with the testing continuation.
  • Round 1, query scaling: declined. The query runs only when tracked domains are neither empty nor exact.
  • Round 2 covered the changes since round 1 and rechecked both declines independently: none.

Prompts and full results: task evidence lp6059-seeded-domain/review-r1.md and review-r2.md.

@shiny-code-app

Copy link
Copy Markdown
Contributor Author

Ready for the merge train

Head: 9b9315a

@shiny-code-app

shiny-code-app Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Launchplane completed the merge-train step for this pull request.

Launchplane finished the merge-train step for this pull request.

Launchplane manages this comment and will update it as the train moves.

shiny-code-app Bot added a commit that referenced this pull request Nov 12, 2026
@shiny-code-app
shiny-code-app Bot merged commit d3c9723 into main Nov 12, 2026
49 checks passed
@shiny-code-app
shiny-code-app Bot deleted the work/lp6059-seeded-domain branch November 12, 2026 16:13
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.

0 participants