Skip to content

Commit 8cec734

Browse files
committed
feat(gate): let an operator-forced release waive the local journal
Dependency advisories published during the two-hour local proof refused three stable 0.6.4 dispatches in a row; each fix was a lockfile bump that then needed its own full proof. The release commands' force argument now waives the local `just test` journal as well as the clean-tree check. It still runs the citadel guards, the release contracts and the live advisory audit before dispatch, publishes under sandbox enforcement, and the hosted lane qualifies its artifacts before publication.
1 parent 17404b3 commit 8cec734

6 files changed

Lines changed: 42 additions & 19 deletions

File tree

‎AGENTS.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ a release to find out what is broken: the hosted lane takes hours per attempt
281281
and stops at the first failure, so it finds one bug per attempt. The 0.6.4
282282
stable release spent four dispatches, one per bug, that a local run catches in
283283
minutes. Nothing runs in CI that `just test` has not run locally first.
284+
The one escape is the release command's `force` argument, which the user
285+
asks for explicitly: it waives the local journal, never the citadel guards,
286+
release contracts, live advisory audit or hosted qualification.
284287

285288
A proof covers the tested commit and any commit with the identical Git tree.
286289
Fast-forward `main` to the tested commit (or merge only when the merge commit's

‎build_system/builder/gate/qualificationevidence.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def missing(commit: SourceCommit) -> GateError:
262262
"branch head a pull request merged), because the hosted lane takes hours to "
263263
"find what the local glow-up and functional lanes find in minutes. Failed or "
264264
f'interrupted attempts do not count; an approved `just test {commit} force "<reason>"` '
265-
"retry that passes does."
265+
"retry that passes does. An operator-approved release may pass `force` instead."
266266
)
267267

268268

‎build_system/builder/gate/releasedispatch.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,22 @@ class QualifiedRelease:
4343
machine lock, or any tag, push, or dispatch.
4444
4545
The unattended nightly scheduler is the exception: it runs on a fresh
46-
hosted runner with no journal and no way to make one. Declared as a class
47-
default and narrowed per instance, because sandbox enforcement reads the
48-
class before a command exists.
46+
hosted runner with no journal and no way to make one. `--force` is the
47+
operator's: a dependency advisory published during the two-hour proof
48+
refused three stable dispatches in a row, each fixed by a lockfile bump
49+
that then needed another full proof. A forced release still runs the
50+
citadel guards, the release contracts and the live advisory audit here,
51+
and the hosted lane qualifies its artifacts before publishing. Declared
52+
as a class default and narrowed per instance, because sandbox enforcement
53+
reads the class before a command exists.
4954
"""
5055

5156
def __init__(self, *args, **kwargs) -> None:
5257
super().__init__(*args, **kwargs)
53-
if getattr(self._args, "channel", None) in self._config.release.unattended_channels:
58+
if (
59+
getattr(self._args, "channel", None) in self._config.release.unattended_channels
60+
or self._forced()
61+
):
5462
self.qualification_policy = QualificationPolicy.NONE
5563

5664
def _qualification_steps(self, plan: Plan, commit: SourceCommit, *, after: tuple) -> tuple:
@@ -108,8 +116,8 @@ def _forced(self) -> bool:
108116
def _forced_source_proof(self, plan: Plan, *, after: tuple) -> tuple:
109117
"""The cheap proof a forced release still owes.
110118
111-
`--force` bypasses the developer checkout's clean-tree refusal; it does
112-
not waive the local `just test` journal or the hosted lane's product
119+
`--force` bypasses the developer checkout's clean-tree refusal and the
120+
local `just test` journal; it does not waive the hosted lane's product
113121
qualification. It used to waive
114122
*everything*, so a forced release could dispatch source that fails a
115123
six-second guard -- and did, three times in one afternoon, each costing

‎build_system/tests/gate/test_gate_release_isolation.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,8 @@ def test_only_networked_release_edges_cross_the_kernel_boundary(name, args) -> N
184184
)
185185

186186

187-
def test_force_does_not_invent_a_local_qualification_waiver() -> None:
188-
"""`--force` adds source guards; the local journal remains mandatory."""
187+
def test_force_waives_the_journal_without_a_waiver_step() -> None:
188+
"""`--force` drops the journal and the clean-tree check, and adds source guards."""
189189
from helpers.gate import built_command
190190

191191
commit = "f" * 40
@@ -195,8 +195,9 @@ def test_force_does_not_invent_a_local_qualification_waiver() -> None:
195195
(("channel", "stable"), ("source_commit", commit), ("force", "true")),
196196
None,
197197
)._describe()
198-
assert forced.labels[0] == "qualification.accept"
198+
assert "qualification.accept" not in forced.labels
199199
assert "qualification.waived" not in forced.labels
200+
assert "citadel" in forced.labels
200201
assert "source.worktree-clean" not in forced.labels
201202

202203

‎build_system/tests/gate/test_gate_release_requires_local_proof.py‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,20 @@ def test_a_failed_local_test_is_not_proof(config, monkeypatch, name) -> None:
196196

197197

198198
@pytest.mark.parametrize("name", sorted(RELEASES))
199-
def test_force_does_not_waive_the_local_test(config, monkeypatch, name) -> None:
200-
"""`--force` excuses a dirty outer checkout, never a missing proof."""
201-
_refused(_release(config, name, force="true"), monkeypatch, COMMIT)
199+
def test_force_waives_the_local_test(config, monkeypatch, name) -> None:
200+
"""`--force` is the operator's escape from the journal, not from the gate:
201+
the forced source proof and the hosted lane still run."""
202+
command = _release(config, name, force="true")
203+
204+
assert command.qualification_policy is QualificationPolicy.NONE
205+
assert _execute(command, monkeypatch) == ["prefix"]
206+
207+
208+
@pytest.mark.parametrize("name", sorted(RELEASES))
209+
def test_a_forced_release_still_publishes_under_enforcement(config, name) -> None:
210+
from capsem_builder.gate.enforcement import enforcement_required
211+
212+
assert enforcement_required(_release(config, name, force="true"))
202213

203214

204215
@pytest.mark.parametrize("name", sorted(RELEASES))

‎build_system/tests/release/test_release_refuses_a_dirty_tree.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,15 @@ def test_the_check_gates_publication_and_force_replaces_it_with_source_proof(nam
111111

112112
assert "source.worktree-clean" not in forced
113113

114-
# Force drops the clean-tree refusal, not every check. It still consumes
115-
# the exact local journal, and proves the source it is about to publish
116-
# with the Citadel and release contracts before the release lanes perform
117-
# their artifact qualification.
118-
assert forced[0] == "qualification.accept"
114+
# Force drops the clean-tree refusal and the local journal, not every
115+
# check: it proves the source it is about to publish with the Citadel and
116+
# release contracts before the release lanes perform their artifact
117+
# qualification.
118+
assert "qualification.accept" not in forced
119119
assert "qualification.waived" not in forced
120120
for proof in ("citadel", "contracts.release"):
121121
assert proof in forced, f"a forced release skips {proof}"
122122
assert forced.index(proof) < forced.index("source.publish-ref"), (
123123
f"{proof} must run before anything is published"
124124
)
125-
assert set(guarded) - set(forced) == {"source.worktree-clean"}
125+
assert set(guarded) - set(forced) == {"source.worktree-clean", "qualification.accept"}

0 commit comments

Comments
 (0)