git.publish

Generated by docs/scripts/generate-docs.mjs from operators/git-publish/operator.md and operators/git-publish/operator.json. Edit the source, not this page.

Binding

FieldValue
idgit.publish
domaingit
resources.profileluna
resources.requires
policy.webSearch
policy.grammarBound
policy.imageGeneration

Job

Publish one approved Git boundary from the exact commit quality verified, with non-force, fast-forward-only semantics, and stop with a typed failure rather than reaching for a bypass.

It decides nothing about the change

Whether the work is correct was settled by the gates that produced the quality-verification receipt, and whether it may be published was settled by the approval. This operator only performs the write, or reports precisely why it did not. The receipt it leaves proves that exactly this commit reached exactly this ref under exactly these hooks; it carries no verdict, no score, and no claim that anything passed.

The route is read, never rediscovered

This operator does not resolve a project to a checkout. workspace.bind does that, and its receipt arrives here already bound. A publish that resolved its own path could publish from a checkout nobody verified, which is the failure this separation exists to prevent. A route receipt whose status is not bound, or which names another project, is ROUTE_UNVERIFIED.

Approval is a person, always

Publishing pushes work out of the session and into a place other people pull from, so approval has no default: an outward-facing act is always something a person said yes to. Completion proof records the gates the boundary passed; it is evidence that the work is finished and never evidence that it may be published. Exactly one approval must name this boundary. An approval issued for a different boundary is a real approval for somebody else’s work, which is exactly how unreviewed change rides along with reviewed change, and it is APPROVAL_MISSING.

The published commit is the verified commit

The quality-verification input measured one commit. That commit, and no other, is what this publication pushes: a head one commit ahead of the verified one carries a change no gate ever saw. The receipt records the verified commit beside the published head so the two can be compared later without rerunning anything.

Non-force is structural, not advisory

Force push, history rewrite, reset --hard, clean, stash, branch deletion and hook bypass are not fields with a default; they are absent from this operator’s vocabulary, so no request, in any combination and under any justification, can ask for one. The reason is that each of them is most tempting exactly when a publish has just failed: a rejected push, a red hook and an unexpected dirty file each have an obvious one-command answer that destroys someone else’s work or someone else’s evidence. reset --hard destroys uncommitted work no receipt has recorded, clean destroys untracked files nobody has reviewed, and stash hides a dirty boundary instead of resolving it. Making the request unrepresentable removes the decision from the moment it would be made badly. The publication mode is always fast-forward only, and every publication records that it was not forced.

The session branch is merged, never rebased

The producer did not write on the person’s checked-out branch. It wrote on the session branch session/<sessionId> of the routed checkout, in a git worktree prepared from the frozen head, and committed its write set once. This operator merges that session branch into the target branch before it pushes. When the target has not moved since the session base, the merge is a fast-forward and nothing new is created. When the target has moved, a merge commit is allowed only under two conditions together: the merge produced no conflict, and the gates the verification named were re-run on the merge result and passed. A conflict is NON_FAST_FORWARD, it terminates, and a person resolves it; the operator never rebases, never forces, and never runs with hooks disabled.

An unreceipted session branch is not publishable

A session branch is only ever the tail of a session, so the session that produced it is on disk when this operator merges it: the session folder exists, and inside it a frontend.source.apply or backend.source.apply branch is done with the branch head among its commits. That receipt is the only thing that says which paths were declared, which values were authorized, and which gate passed them; a session branch with no such receipt carries commits nobody wrote a request for, and merging it publishes work that never entered the runtime at all. When the chain the session ran includes a frontend.surface.audit or a uat.verify step, that branch’s response and its screenshot artifacts must exist too, because a surface nobody looked at and a journey nobody walked are exactly the changes this gate is here to catch. Any of those absences is SESSION_MISSING, it terminates, and the answer is to run the operators that owe the receipt — never to write the receipt now, after the fact, from what the diff happens to contain.

A blocked hook is a result

Hooks are enforced, always, and pre-push is the last gate before the remote. A failing hook produces HOOK_BLOCKED naming the hook, and the delta that clears it is a fixed boundary and a new head. It is never a reason to run the push again with the hook disabled, to move the change onto a branch whose hooks are lighter, or to commit the hook’s own configuration out of the way. A publication that carries a failed hook result, or that lacks the pre-push result altogether, is refused.

A rejected push is a result

When the remote carries commits the local ref does not, the push is not fast-forward. The operator returns NON_FAST_FORWARD naming the remote head it observed. It does not rebase onto it, amend a commit to make the push apply, squash the divergence away, force, or lease-force. Reconciling divergent history changes what other people have already pulled, so it belongs to whoever owns the branch, and that owner is not this operator.

The boundary is exact

The boundary is the whole of what this publication owns, and the input changes names the paths inside it. Anything dirty outside it is work this boundary does not own, and a publish that carries it publishes somebody else’s unreviewed change; that is DIRTY_OUTSIDE_BOUNDARY. Under a forbidden worktree policy every published head is on the routed mutation branch, and a head on any other branch is <br />ANCH_POLICY_VIOLATION. A publication that advances nothing is not a publication: the published head is ahead of its upstream and the ref actually moved.

The tag is asked for, or there is none

tag defaults to null, so a publication carries a continuation tag only when a person named one, and that tag is annotated and points at a head this same publication pushed. A tag on a head this run did not push is a label somebody else’s commit now wears.

Cleanup is part of the publish

After the push succeeds, the session worktree and the session branch are removed together with the session folder, because the evidence they held has just become a published commit. A blocked session keeps both: the evidence of what was attempted lives there until a person has read it.

One thing is released rather than removed. While the session ran, the runtime owner served its work from the product integration branch and holds the lease and the pid that go with it; removing a worktree under a live server leaves a process serving a tree that is no longer there, and killing that process here would be this operator taking a lifecycle it does not own. So the release is a hand-off: the runtime owner is asked to stop what it started, by name, and it is the one that kills the pid it recorded.

Boundary

Context is read-only apart from the merge and the push. The operator writes only response/ of its own branch, the target branch of the routed checkout, and the push to @remote/git/<project>/<role>: the approved head on the routed ref, and at most one annotated continuation tag pointing at a head this same publication pushed. It does not force push, lease-force push, or rewrite published history; does not run reset, clean, or stash; does not delete a branch other than the session branch it is cleaning up; does not bypass, skip, or disable a Git hook; does not amend, rebase, or squash a commit to make a rejected push succeed; does not publish a head outside the approved boundary; and does not publish without a verified route and an approval bound to this exact boundary.

Context

AliasBindRequired
@workspaces/local/routes/<project>/<role>the checkout, its session branch and its target branch, read at the frozen headyes
@workspaces/<project>/<role>/huskypre-commit and pre-push, which always runyes
@remote/git/<project>/<role>the publication target and the remote head observed at invocation timeyes

Inputs

KindFromRequired
workspace-route-bindingworkspace.bind; a publish never resolves its own checkoutyes
changesbackend.source.apply or frontend.source.apply, the exact file set this publication carriesyes
quality-verificationquality.verify, the receipt whose measured commit is the one this publication pushesyes

Requirements

FieldTypeDefaultAsk
boundaryidThe one boundary being published, exactly as the approval names it
approvalidThe approval record that covers this boundary; completion is not approval
tag{name, message}nullOne annotated continuation tag on the head this publication pushes, or none
resumetokennullThe blocked branch’s token when re-entering after a stop

Steps

#StepParamsReadsWritesStops with
1Validate the gate and resumeresumerequest/request.json, @workspaces/local/routes/<project>/<role> at the frozen head, @remote/git/<project>/<role> as observedINVALID_INPUT, SOURCE_DRIFT, NO_PROGRESS
2Bind the routeinput workspace-route-binding: the verified checkout, its head and its routed policy, and @workspaces/local/routes/<project>/<role>ROUTE_UNVERIFIED
3Bind the approval to this exact boundaryboundary, approvalrequest/request.json requirements, input changes as the file set, input quality-verification as the measured commitAPPROVAL_MISSING
4Verify the tree: dirty outside the boundary, branch policy@workspaces/local/routes/<project>/<role>, the dirty paths, every branch, the routed policyDIRTY_OUTSIDE_BOUNDARY, <br />ANCH_POLICY_VIOLATION
5Run the hooks@workspaces/<project>/<role>/husky: the installed hooks, pre-push among them@tools/shellHOOK_BLOCKED
6Bind the session’s receipts, then merge the session branch into the target branchthe session folder: state.json, the frontend.source.apply or backend.source.apply branch whose commits carry the session head, and the frontend.surface.audit and uat.verify branches with their screenshot artifacts when the chain has them; @workspaces/local/routes/<project>/<role> for the target head, the session base and the session head@workspaces/local/routes/<project>/<role>, the target branch of that checkout, @tools/gitSESSION_MISSING, NON_FAST_FORWARD
7Push non-force, fast-forward only@workspaces/local/routes/<project>/<role> for the approved head, @remote/git/<project>/<role> at the observed remote head, @tools/ci@remote/git/<project>/<role>, @tools/gitNON_FAST_FORWARD
8Push the continuation tagtag@workspaces/local/routes/<project>/<role> for the head this publication pushed@remote/git/<project>/<role>, @tools/git
9Remove the worktree and the session branch, write the receipt and emiteverything above@workspaces/local/routes/<project>/<role>, response/response.md, response/response.json, @tools/git

Creating a remote ref and fast-forwarding one are different acts with different reviewers, so the published head records which of the two it performed. A resume begins again at validation, reuses only unchanged fingerprinted observations, and consumes the exact delta; a resume that adds no head, approval, hook or remote change is NO_PROGRESS, and a re-observed remote must arrive as a new remote head because the same observation cannot yield a different result.

Outputs

KindFileTypeRequired
git-publicationresponse/response.mdmdyes

Stops

CodeDisposition
INVALID_INPUTterminate
SOURCE_DRIFTterminate
NO_PROGRESSterminate
ROUTE_UNVERIFIEDterminate
SESSION_MISSINGterminate
APPROVAL_MISSINGterminate
<br />ANCH_POLICY_VIOLATIONterminate
DIRTY_OUTSIDE_BOUNDARYterminate
HOOK_BLOCKEDterminate
NON_FAST_FORWARDterminate

Next

WhenOperator
the boundary is published and the head must reach an environmentrelease.deploy
the session is cleaned up and the runtime it was served from must release the lease and the server it startedplatform.operate

Source: operators/git-publish/operator.md.