writeback

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package writeback lands the work product of an isolated executor in the project's repository on the hub.

It is the receiving half of pkg/executor/gitwriteback, and the trust boundary between them is total. Everything it is handed — a branch name, a commit SHA, a bundle — was produced inside a sandbox running model-authored code. This package's job is to get that work into pkg/mergequeue, which is where local parallel work already goes, without letting a sandbox use the trip to write somewhere it was never given.

Quarantine before branch

Objects are fetched into refs/cloop/writeback/<branch>, never straight into refs/heads/<branch>, and the ref is only promoted after the commit range has passed inspection. The ordering is the point: a ref under refs/heads is a branch — it shows up in `git branch`, it can be checked out by a person or by the merge queue, and a checkout is what makes a .git/hooks entry execute. A ref that has not been vetted must never be reachable by any of that, so it spends its whole life somewhere `git checkout` will not find it, and a rejection deletes it.

Two layers of content checks, and why neither may be assumed

Every fetch here runs with fetch.fsckObjects and core.protectNTFS/protectHFS set, so git refuses malformed objects and — the part that matters — trees containing a path named .git in any of the spellings a case-insensitive filesystem would fold back to it.

Whether that layer fires on the bundle path depends on the git in $PATH, so it is not something this package may rely on. Git runs its fsck rules from index-pack, and which of index-pack or unpack-objects handles a bundle fetch changed under us. Measured, with a bundle carrying a tree entry named .git and both fsck settings on:

git 2.43, 2.45  fetch succeeds  — objects unpack loose, fsck never runs
git 2.47, 2.54  fetch fails     — hasDotgit, "fsck error in packed object"

The push path has always run through index-pack and so has always had it.

This is why executor.InspectWriteBack is not a second opinion: on any git that unpacks loose it is the only opinion, on the transport that needs one most — the one requiring no credential. It is also the layer that knows things fsck does not care about: that a symlink pointing outside the project root turns every later write through it into a write anywhere on the control plane, and that a submodule entry names a URL rather than content.

Because either layer may be the one that speaks, both classify a content refusal the same way — as ErrWriteBackRejected, never as the retryable ErrWriteBackUnavailable. A hostile write-back must not be reported as an outage on one git and a rejection on another. tests/security/writeback_bundle_test.go asserts the refusals over both transports for exactly this reason — the two paths do not have the same defences, so neither may be assumed to cover the other.

Both transports are inspected

A pushed branch goes through exactly the same vetting as a bundle. A sandbox holding a push credential can push a hook as easily as it can bundle one, and securing only the path that needs no credential would be securing the wrong one.

Index

Constants

View Source
const DefaultTimeout = 2 * time.Minute

DefaultTimeout bounds each git invocation when a Request names none.

View Source
const QuarantineRefPrefix = "refs/cloop/writeback/"

QuarantineRefPrefix is where an unvetted write-back lives between arriving and being accepted. Deliberately outside refs/heads: see the package comment.

Variables

This section is empty.

Functions

func Record

func Record(db Recorder, taskID int, taskTitle string, reported executor.WriteBackResult,
	applied Result, applyErr error) error

Record writes a write-back's outcome to the task record and the event journal.

It records failures as loudly as successes. A run whose work was lost looks exactly like a run that changed nothing — same transcript, same exit code, same green tick — so the case that most needs a row is the one where there is no branch to name.

Errors are returned for a caller that wants to log them and safe to ignore for one that does not: nothing here is load-bearing for the work itself, which is already on a branch by the time this runs.

func RecordOutcome added in v0.0.5

func RecordOutcome(skipped bool, err error)

RecordOutcome counts one landing in the hub's metrics, for a caller that vets with Vet and lands the result itself (a feature's write-back, Task 20367) rather than through Apply, which counts its own.

Types

type Merger

type Merger interface {
	Submit(req mergequeue.Request) *mergequeue.Result
}

Merger is the subset of pkg/mergequeue.Queue this package needs.

It is an interface so a test can prove the handoff happens without starting a queue goroutine, and so the caller keeps ownership of the real queue's lifecycle — a package that lands one branch has no business deciding when the project's merge queue starts and stops.

type Recorder

type Recorder interface {
	LoadTask(id int) (*pm.Task, error)
	UpsertTask(t *pm.Task) error
	RecordEvent(row statedb.EventRow) error
}

Recorder is the subset of *statedb.DB this package needs.

An interface so a caller can pass a real database, a fake, or nothing at all. It is deliberately the two narrowest methods rather than the DB: a package that writes a task's branch name has no business being able to delete a plan.

type Request

type Request struct {
	// RepoDir is the project's git repository on the hub.
	RepoDir string
	// Reported is what the executor said the sandbox produced.
	Reported executor.WriteBackResult
	// Bundle is the git bundle received for mode bundle. Ignored for push.
	Bundle []byte
	// Remote is the name of the git remote the branch was pushed to, for mode
	// push. Empty means "origin".
	Remote string
	// TaskID and TaskTitle attribute the merge commit. They are the merge
	// queue's, not git's — nothing here trusts them.
	TaskID    int
	TaskTitle string
	// Queue receives the vetted branch. Nil means "land the branch and stop",
	// which is what a caller wants when the operator merges by hand.
	Queue Merger
	// MergeTimeout bounds the wait for the queue's verdict. 0 means ten
	// minutes: a merge may sit behind other tasks' merges.
	MergeTimeout time.Duration
	// Timeout bounds each git invocation. 0 means DefaultTimeout.
	Timeout time.Duration
	// Emit receives progress. May be nil.
	Emit func(string)
}

Request is one write-back to land.

type Result

type Result struct {
	// Branch and CommitSHA are the vetted ref, once it has been promoted.
	Branch    string
	CommitSHA string
	// Commits and FilesChanged describe the range that was accepted.
	Commits      int
	FilesChanged int
	// Merged and MergeSHA report the merge queue's outcome. Merged is false
	// when no queue was supplied or when the merge conflicted; MergeErr
	// carries the reason in the latter case.
	Merged   bool
	MergeSHA string
	MergeErr error
	// Skipped reports that there was nothing to land.
	Skipped bool
}

Result reports what landed.

func Apply

func Apply(ctx context.Context, req Request) (Result, error)

Apply lands a reported write-back in RepoDir and hands the branch to the merge queue.

A rejection is a *executor.WriteBackRejection wrapping executor.ErrWriteBackRejected, and it leaves nothing behind: the quarantine ref is deleted, no branch is created, and the merge queue is never told about it. A merge conflict is *not* an error — the branch landed, the merge did not, and Result.MergeErr says so — because losing the branch would throw away the work the conflict is about.

func (Result) Delivered

func (r Result) Delivered() bool

Delivered reports whether Apply produced a branch worth pointing at.

type Vetted added in v0.0.5

type Vetted struct {
	// Branch, CommitSHA and BaseSHA are what the executor reported and the
	// checks confirmed.
	Branch    string
	CommitSHA string
	BaseSHA   string
	// Quarantine is the ref the objects were fetched into.
	Quarantine string
	// Commits and Entries describe the range base..commit; Entries is every
	// changed path, as InspectWriteBack saw it.
	Commits int
	Entries []executor.BundleEntry
	// contains filtered or unexported fields
}

Vetted is a write-back that passed every check and sits in quarantine, not yet reachable from any branch. The caller decides what becomes of it and must call Discard when done, whatever it decided: the quarantine ref is not meant to outlive the landing.

func Vet added in v0.0.5

func Vet(ctx context.Context, req Request) (*Vetted, error)

Vet lands a reported write-back in quarantine and runs every check Apply runs, without promoting it to a branch or merging it. It returns nil and no error when there is nothing to land (the executor skipped).

A rejection leaves nothing behind, exactly as from Apply.

func (*Vetted) Discard added in v0.0.5

func (v *Vetted) Discard()

Discard deletes the quarantine ref. Idempotent.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL