Files
sencho/docs/features
Anso 7a6682ed3f fix: pull the next commit on a manual Git-managed Blueprint (#2130)
* fix: pull the next commit on a manual Git-managed Blueprint

A claimed manual source had no operator fetch: polling skips it, and stack Pull now stays refused. Pull source stages a candidate and leaves acceptance to Accept source, except an automatic policy, which accepts the same way a poll does.

* test: supply a node list to the GitOps sheet mocks

The authority row reads the default node from the node context. These mocks omitted nodes, so the sheet threw before it rendered.

* test: type the Git source permission mock without unused parameters

The default can implementation always returns false. Naming the permission arguments made lint fail.

* fix: send the Blueprint id and report what Pull source did

The claimed sheet posted the internal application id, so the pull never reached the Blueprint. An automatic pull could accept and start a rollout while the result only said the commit was fetched.

* fix: report a continuing automatic pull instead of a failed one

Acceptance can commit before preparation and rollout finish. Answer once that wait reaches its deadline, keep the source locked until the work finishes, and do not describe the deadline as a failed pull.

* fix: report why an automatic pull cannot prepare a commit

A completed preparation reason was replaced by the generic unresolved-artifact skip. Keep that reason, and describe the claimed sheet by the source actions it still offers.

* fix: refuse a pull that would replace an interrupted apply's candidate

Apply recovery stays eligible only while the interrupted generation is still the current candidate. Pull source waits in that case. An interrupted fetch still pulls, and a pull stays available once the candidate has moved on.

* refactor: share the bounded follow-up after a Git-managed acceptance

Accept source and an operator pull each had their own copy of the deadline, the continuation notes, and the slot hold. One follow-up now answers both.

* fix: declare the post-accept deadline timer as const

The deadline id is assigned once. A later assignment made the lint fail.

* fix: confirm Accept source before it can start a rollout

Accept source recorded the fetched commit immediately, including when stored rollout authorization can deploy it, and those actions were not on the sheet Overview. The confirmation names the commit, the approved targets, and whether a rollout can start, and confirm posts the generation that was on screen.

* fix: read placement approval only on a Blueprint projection

The accept preview read approvals on every projection, and the one with nothing to show has no approvals. The Blueprint check comes first so the build can read the placement ref.

* fix: name the approved rollout scope and report settled failures

Two confirmations could describe a destination the action would not use, and
one result reported work as starting when it had already failed.

Accept source named every observed target row as approved placement. Those rows
are where an application runs, not where a rollout goes, and they disagree
whenever placement moved since the last rollout ran: an approved node has no row
until the rollout that places one, and a withdrawn node keeps one. A projection
now carries the scope the placement approval froze, read from the rollout
candidate and accepted only once the approval resolves for that set, which is
the evidence the dispatch boundary already uses. Both confirmations read it
through one reader and name the approved nodes, or say the scope cannot be
read rather than naming a set they did not.

The bounded follow-up used its continuing-work answer for a deadline and for a
rejection alike, so a dispatch that had already failed was reported as
starting, with nothing pending. The three outcomes are now distinct: only a
deadline holds work still running, and a rejection reports the rollout as held
with the failure named.

* fix: bind a source confirmation to the request that carries it

Pull source and Accept source asked the operator to confirm a destination and
then sent nothing that named it. Fetch posted an empty body and acceptance
posted only a generation, so execution read whatever the placement and the
rollout policy happened to say at the moment it dispatched. An operator could
confirm a placement for one node and deploy to another, and a dialog promising
a waiting rollout could start one after another session armed the policy.

Both bodies now carry what the dialog read: the placement approval the scope was
resolved from, and whether the confirmation said a rollout could start. The
description and the claim are frozen together when the overlay opens, so the
words the operator agreed to and the state the server checks are one read of one
projection.

The claim is checked at every consequential boundary rather than only at the
route, because each one is preceded by asynchronous work: at the route and at
the controller entry, again before an automatic acceptance after the reconcile,
and again in the handoff after preparation. A refusal that lands after the
acceptance committed is held with the reason instead of answered, since the
reconciler re-drives any accepted generation with nothing to check it against
and a refusal that recorded nothing would be undone by the next tick. The hold
carries an operator origin, so only a person clears it, and the recovery is the
existing one: review the placement, then resume the rollout.

The destination is compared exactly, because the dialog named specific nodes.
The rollout promise is compared one way: a confirmation that allowed a rollout
and finds the policy waiting for an operator does less than it said, and the
dispatch gate already reports that, while the reverse would start something the
operator was told would wait.

A manual or review pull only fetches, so it asks for nothing, and the poll and
resume paths send no confirmation at all because a cadence is configured rather
than confirmed.

* fix: keep the confirmation hold inside the mode that can clear it

Three ways the refusal did not survive contact with the rest of the model.

The hold was recorded below the rollout authorization policy gate, so a manual
source paused the rollout over a confirmation the handoff could never have acted
on. A manual policy never dispatches from here, and its Authorize is already the
operator's own decision against the placement as it stands, so all that bought
was an extra resume step before an authorize the pause also blocks. The gate is
back first and the confirmation is checked under it, so only an automatic policy,
the one that would have dispatched on its own, is held.

The hold outlived the Blueprint. Every resume route resolves a Git-managed
Blueprint and answers NOT_GIT_MANAGED for the row afterwards, so a pause carried
through Retire or Detach read as a paused rollout nothing could clear. Both
demotion paths now clear it, which is what the Blueprint demotion already does
with every other column it invalidates.

And the module claimed the check ran at every consequential boundary, which is
only true of the boundaries an operator action reaches. A cadence carries no
claim to check, so the poll and the reconciler's re-drive are not among them, and
the hold is what keeps them from undoing a refusal.

Each correction is covered by a test that fails without it: a manual-policy
acceptance whose placement moves during preparation is not held, an operator hold
survives a later acceptance on the policy's own behalf, and retiring a Blueprint
releases a held rollout.

* fix: make a confirmed acceptance keep its placement past the request

A confirmation carried only in a request body stops existing when the request
answers, and two paths outlive it while both still dispatch. Preparation can run
past its deadline and hand the handoff to the reconciler, and a restart re-drives
any accepted generation with no request in the picture. The guard therefore
protected the fast path and nothing else, which is worse than none: the operator
was told to review a change that then deployed on its own.

The acceptance now records the precondition on its own approval row, in the
column that already existed there and was unused for this kind. A null recording
is a real answer rather than missing evidence: an acceptance made by the poll
records none, because a cadence is configured rather than confirmed.

It is enforced in ensureRolloutAuthorization, the single funnel every mint
passes and the only place an unattended dispatch reaches, so it covers the
reconciler's re-drive and the startup preflight backfill as well as the request
that carried the claim. Only a policy mint is bound: an operator mint is the
decision the confirmation deferred, taken against the placement as it stands. A
stale precondition holds rather than merely refuses, because a refusal with
nothing behind it is re-attempted every reconcile and leaves a rollout queued
with no reason attached. The acceptance itself stands, so the content is
recorded and only its destination waits for a person. An application-scoped
resume releases the recording; a target-scoped one does not.

The same function now re-resolves ingredients after the registry preflight
await and refuses when the placement approval or the frozen node set moved. The
mint re-reads ingredients inside its write transaction, so a placement
re-approved mid-preflight produced a rollout naming the replacement nodes with
readiness evidence that described the old ones. That gap predates this work, but
a request-local guard cannot see past an await, and a rollout generation is
immutable once written.

* fix: compare the whole ingredient record, and let an operator mint decide

Two gaps in the durable guard, both found by reading it against the paths that
reach it.

The post-await comparison watched the placement approval and the frozen node set
and nothing else. A source acceptance landing during the same preflight window
changes the generation, the artifact set and the acceptance without moving
either of those, so the check passed and the mint named the new generation with
readiness evidence evaluated for the old one, which is the same defect the
comparison was added to close. Every field the mint copies onto an immutable
rollout generation is compared now.

An operator mint is exempt from the guard, because asking to authorize the
rollout is the decision the confirmation deferred. Nothing moved the recording
on that path, so once the authorization lapsed the automatic policy held a
rollout asking the operator to resume a placement they had deliberately
authorized. An operator mint now re-points the recording at what was decided,
inside the mint's own transaction, which also keeps the guard working for the
next replacement.

* fix: keep the whole source confirmation after the request ends

A stored placement pointer let a later re-drive start a rollout the operator was told would wait, or deploy to a destination the confirmation never named. The acceptance now stores both facts, and unattended authorization reads that record rather than the request that already finished.

* fix: show source confirmations and start a ready automatic resume

Operators were asked to confirm a sentence they could not read, and Resume reported that an automatic policy would start a rollout it never dispatched.
2026-10-15 18:35:56 -04:00
..