Repository navigation
fix(apply): refuse an unusable build id before tofu apply - #803
Merged
Merged
Conversation
`fluid apply --mode amend-and-build` (and replace-and-build) checked contract.id and builds[].id only in the build runner. On the OpenTofu engine the runner starts after `tofu apply`, so an id the schema admits but the build phase refuses (1orders, more than 128 characters, a trailing newline) was refused after the product was provisioned, and rendered as "Unexpected error". The runner's rule is now one function, build_runners/_ids.py validate_build_ids, and apply runs it right after the schema gate in a build mode, ahead of the apply hooks, tofu and the runner, on both engines. The refusal is a typed CLIError, apply_build_identifier_invalid, that names the field and the value and says nothing was applied. The runner keeps its own check as the backstop.
5 of 9 tasks
This was referenced Nov 4, 2026
fas89
added a commit
that referenced
this pull request
Nov 4, 2026
* feat(validate,plan): warn about an id the build phase refuses The schema's $defs.identifier admits ids the build phase refuses: a leading digit (1orders) or more than 128 characters. fluid apply in a build mode refuses such an id before it applies anything (#803), but fluid validate and fluid plan said nothing, so the first sign was a failed apply. The two grammars stay different by decision. For a contract with builds, both now warn and name the id, using the rule apply uses (build_runners._ids.validate_build_ids): - validate adds a ValidationResult warning, so plain validate passes and --strict fails (founder's call). It runs with the other 0.7.x contract rules, on a contract file and on a bundle's resolved contract. If the check itself crashes, that is reported as a warning. - plan prints the warning and still writes plan.json, whatever its --mode (founder's call). It runs after the gate plan shares with apply, not inside it, so apply keeps its typed CLIError. IDENT_RULE states the grammar in words, and both the new warning and apply's hint use it. The hint said validate and plan "accept" such an id; it now says they only warn about it. * fix(validate,plan): keep the build-id warning true on a schema-refused id Review follow-ups to the build-id warning: - validate runs its contract rules on a contract the schema refused too, so the warning could say "The contract schema allows more" about an id the schema had just refused (id: 123). The text now only says what the build phase needs and that fluid apply in a build mode refuses the id. - plan prints the warning the way validate prints its warnings: literal, soft_wrap (the terminal wraps, so Rich never breaks a line inside the id) and no highlighter.
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
fluid apply --mode amend-and-build(andreplace-and-build) checkedcontract.idandbuilds[].idonly in the build runner. On the OpenTofu engine the runner starts aftertofu apply. So an id the schema accepts but the build phase refuses was refused only after the product had been provisioned, and it printed as "Unexpected error" (exit 2).The two rules differ in three ways:
$defs.identifierallows a leading digit (1orders) and any length.build_runners/_ids.py) wants a letter or_first and at most 128 characters.patternstill lets through.This PR runs the runner's own check at the start of the apply, right after the schema gate. It runs in the build modes only, before the apply hooks,
tofuand the runner, on both engines:build_runners/_ids.py::validate_build_ids(contract)is the runner's existing rule as one function. The runner andfluid applyboth call it.IdentifierViolationnow carrieskindandvalue.cli/apply.py::_gate_build_ids_for_applyturns a refusal intoCLIErrorapply_build_identifier_invalid(ERR_APPLY_BUILD_IDENTIFIER_INVALID). The error shows the field, the value, a hint with the rule and "Nothing was applied", and catalog suggestions.Before and after, on the OpenTofu engine with
id: 1orders(whichfluid validateaccepts):The founder decided that the two id grammars stay different.
$defs.identifierbacks 20 schema fields, and_idsis a security guard. Instead,fluid validateandfluid planwill warn about such ids, in a follow-up PR.Type of Change
Documentation
docs/cli/apply.md, and a bullet todocs/upgrading.md.Checklist
pytest)ruff checkandblackwith no errorspython scripts/add_license_headers.py;examples/**exempt)Testing
tests/cli/test_apply_build_ids_before_tofu.pyhas 47 cases. On main 9260686, 34 fail and 13 pass (the 13 are controls). All 47 pass on the branch.tofuand the apply hooks are recorders, and the real build runner runs behind a spy.amend-and-buildandreplace-and-build, a leading digit, 129 characters, a trailing newline, the--build-idfilter, and the CLI rendering throughmain().tests/providers/test_duckdb_sandbox_workspace_root.pyfail the same way on main; they depend on the macOSTMPDIR.fluidCLI, with a recording stand-intofufirst onPATHand no AWS credentials:1orders:fluid validatepasses, thenfluid apply --mode amend-and-buildis refused. There are 0 tofu calls and no.fluid/iacdirectory.orders, the apply reachestofu --versionandtofu init.1ordersis refused, andordersruns its build (exit 0).fluid planwrites aplan.jsonfor1orders; applying that plan is refused before tofu.tests/perf/test_startup_budget.pygives the same result on main and on the branch, with the same module counts. Its 2 module-count failures in this venv predate this change.IdentifierViolationraiser and handler.