Publication topology and local merge

Part of the reintegration policy. Its Markdown source preserves the detailed contract for this domain.

Provider inventory and retry deadlines

Publication inventory uses REST pull-request list/detail reads through project-routed gh api instead of GraphQL for exact-head, existing-generation, create-race, and merged/open-generation preflights. Observations preserve draft readiness, fork identity, exact head/base SHAs, labels, ownership metadata, and merged-versus-closed state. Bounded pagination, ambiguous identity, malformed responses, and transport failures remain fail-closed; none proves absence. Operation-local ETags permit fresh conditional revalidation without cross-account response sharing.

Rate limits return github_rate_limited with typed status, retry_after_secs, reset_at_unix, and resource metadata. Delays come from Retry-After or an exhausted budget's X-RateLimit-Reset; missing deadlines remain unknown rather than guessed. Publication authority and required CI are unchanged.

Policy resolution

Project config declares Git topology under projects[].remotes and policy under projects[].integration. integration.default_intent selects direct, review, artifact_only, upstream_draft_pr, or none; integration.backend selects local_merge or pull_request. upstream_draft_pr is the blessed draft-PR-to-upstream, no-fork-reintegration mode: product work lands only as a draft PR against the upstream pr_base while the fork keeps only .cacophony/ changes, and it currently resolves to pr_review against the upstream base with the draft flag, split enforcement, and upstream-merge bead lifecycle as follow-ups. Profile frontmatter reintegration.backend overrides the project backend for that agent only, enabling per-agent PR opt-in without flipping the project — and the inverse, a release profile pinning local_merge under a pull_request default. caco agent reintegrate --backend <pull_request|local_merge> overrides the resolved backend for one invocation, so any agent can choose PR vs direct merge per land without editing config and --backend local_merge lands off a broken PR backend. Topology stays in remote, remotes, reintegrate_target, and pr_base.

projects:
  example:
    remote: ssh://git@ssh.github.com:443/user/fork.git
    default_branch: main
    remotes:
      fork:
        url: ssh://git@ssh.github.com:443/user/fork.git
        push: allowed
      upstream:
        url: ssh://git@ssh.github.com:443/org/upstream.git
        push: forbidden
        pr_target: true
    integration:
      reintegrate_target: fork/main
      pr_base: upstream/main
      default_intent: review
      backend: pull_request

Direct / local merge

caco agent reintegrate runs the project's before_reintegration hooks, then asks the daemon to land. The daemon refreshes the target branch, checks the agent's branch is clean and current, and writes a backup ref of the agent's head. It builds the squash in a separate integration checkout, never touching the agent's own. Only then does it publish with a compare-and-swap on the target branch and check the new commit is reachable on the forge itself; only that verified success records the summary and closes the bead.
Direct reintegrationPublish or refuse: the squash is built in a separate checkout, and nothing is published until the compare-and-swap succeeds and the forge shows the commit.

The default local backend is publish-or-refuse: it refreshes the configured target branch, verifies the agent checkout is clean and on the expected branch, refuses stale branches, squash-merges only when safety can be proven, and reports success only after the target commit is reachable from the configured remote. When origin is a daemon-local canonical mirror, verification observes its upstream rather than trusting either cached target ref. New direct outcomes retain full commit IDs, so an exact live-tip match needs no history fetch. An advanced target uses ancestry against that observed OID; missing proof objects are imported into the worker under existing object-store admission without changing branches, tracking refs or FETCH_HEAD. Canonical dirt, ref locks and interrupted writers remain preserved. An unavailable observation or failed proof import is unconfirmed, not evidence that published work is stranded or permission to republish. Already-on-main reconciliation additionally proves the actual worker's non-artefact content against the observed target. The CLI's recent-landed-commit recheck retains its separate true-forge refresh and bounded settle/retry behavior.

Both backends also refuse to publish a HEAD that still contains unresolved git conflict markers (<<<<<<< / >>>>>>>): direct-mode validates this inline, and the PR backend hard-gates it before the gh pr create push (rather than swallowing it in the open-or-update step), so a conflict-marker-bearing land cannot reach main through either path.

Branch-scoped materialisation and preserved children

A managed checkout selects its configured project branch before its first working-tree, smudge, or submodule materialisation. Slash-containing branch names are retained verbatim; a missing configured branch fails rather than falling back to remote HEAD or creating and pushing a replacement. Disabled submodules are not initialised, recursively updated, or forcibly deinitialised as a policy migration. Existing child repositories remain preserved.

Reintegration distinguishes recorded superproject gitlinks from materialised child state. Unchanged recorded pointers permit unrelated child file dirt, untracked or nested dirt, and checked-out child HEAD drift. Staged pointers, root changes and conflicts remain blocking, and a transition that changes a child's pointer, path, mode or declaration must protect affected child work. No child cleanup or accidental staging is required to make unrelated parent work publishable.

Warm integration workspaces are scoped by repository, project and exact target, and verify their stored identity before reuse. Dirty caches and caches containing materialised children are preserved while the operation uses a fresh isolated checkout. Periodic cache reclamation also skips those preserved worktrees. Validation must use the prepared recorded inputs, never unrelated dirty child bytes; required checks and target-concurrency guards still apply. Legacy URL-only workspaces are not automatically deleted or migrated.

Integration target fetches and local cache seeding

A reused full integration checkout borrows the healthy canonical checkout's object directory through a destination-only Git alternate before fetching the target. This lets Git negotiate locally available history without downloading it again when the warm checkout is weeks stale. Existing alternates are preserved, and partial/promisor sources retain their authoritative blobless path. The canonical supplies objects, not target authority: the bounded upstream fetch and its SSH fallback still select the target before merge preparation. No canonical refs, packs, configuration, or working-tree files are changed by seeding.

Integration target fetch admission and a durable receipt are scoped to the destination object store, including linked worktrees and separate daemon processes. Ordinary failures defer another attempt with capped exponential backoff measured from completion. A timeout or interrupted caller preserves an unfinished-writer fence across restart instead of starting another multi-gigabyte download. The fetch path never removes temporary packs, because child termination alone does not prove that every descendant writer stopped.

Controllers and operators recover that fence through the existing exact-source caco checkout object-store-maintenance preview and apply. Only a successful apply, after integrity/preservation checks, a durable maintenance receipt, and a final no-active-writer/no-held-temporary-pack proof, settles the fetch receipt. Preview, refused apply, unknown holder state, and failed proofs preserve the fence. State-branch fetch admission and receipts remain separate and unchanged.

Agent-head transfer and shared object stores

Managed agent, canonical, and integration checkouts share one pack set. When the integration checkout can already read the exact reviewed head, prepare uses a local update-ref to the source SHA instead of fetching; promisor/blobless sources, missing objects, incomplete probes, and dropped-tag recovery fall back to bounded fetch. Daemon-created checkouts disable Git auto-maintenance (gc.auto=0, maintenance.auto=false), emit content-free object-store health diagnostics, and may quarantine only old, unheld, provably abandoned integration-checkout tmp_pack_* files. The reintegration path itself never runs broad GC, deletes a pack, or mutates a daemon-owned object store.

For a prepare-destination store wedge, controllers and operators use caco checkout object-store-maintenance --project <project> --agent <agent> --expected-head <full-sha> to preview it. The daemon derives the exact deterministic stable integration checkout from that source agent's origin, rather than accepting a caller-supplied object-store path. --apply applies it and refuses if the canonical reintegration/refresh lock or stable warm-target lock is held, a Git process or pack holder is active, or the source head changed. The operation only quarantines old temporary-pack evidence; it snapshots refs, dirty state, and worktree registrations, runs bounded connectivity and pack-health checks before and after, proves an exact-head fetch within the normal 180-second bound, and writes a durable receipt under the destination Git directory. It never runs GC/repack, deletes pack data, or changes refs, backup refs, worktrees, or dirty/local-only bytes.

caco agent rebase --id "${CACOPHONY_AGENT:?set agent id}"
# resolve conflicts if any, git rebase --continue, then retry complete/reintegrate

The first-party rebase path fetches the forge upstream when origin is a daemon-local mirror, rebases onto the same authoritative target that reintegration validates, and aligns the visible tracking ref. Canonical preflight, isolated-checkout fetches, target refreshes, and final publish share bounded transient-network retry plus the reviewed SSH-over-443 fallback; deterministic Git/auth failures, unsafe locks, dirt, and unrebasable commits refuse. A successful fallback persists the working default-port command for later verification.

Refusals name the failing stage, checkout, remote, branch/head, target/upstream refs, divergence, dirty paths, transport overrides, exact decision, and safe next action. The guard may reconcile only tracked paths already equal to the fetched target with no local-only target commits. It may restore a staged-only or worktree-only HEAD^ rollback only when every dirty path is exactly a path changed by HEAD and index/worktree proofs match the documented rollback shape; arbitrary or unproved dirt remains a loud checkout-health refusal.

Managed-Git preflight may remove only a stale .git/index.lock in a managed Cacophony Git root when it is at least five minutes old, unheld, and no index-mutating Git process is active in that exact root. Fresh, held, active-process, and foreign-root locks are preserved as expected checkout backpressure with path, age, holder/process checks, and action in diagnostics. During caco agent rebase, an empty managed index may be rebuilt with git read-tree HEAD only when no rebase is in progress; rebase-merge or rebase-apply preserves it for conflict inspection.

Corrupt-ref repair during caco agent rebase is deliberately narrow: remote agent refs, tags, the configured state branch ref, and local refs/heads/caco-backup/pre-rebase-* safety refs may be repaired when Git reports them as bad objects, while unrelated branch refs remain loud failures.

Transient Git lock contention during lifecycle commands is different from stale lock cleanup. If caco agent rebase, caco agent complete, or caco agent reintegrate sees Git output mentioning index.lock, .git/index, or a failed HEAD detach while no unmerged files were captured, it retries the same first-party operation with short bounded backoff. If retries are exhausted, the error is reported as transient lock contention with retry guidance rather than a content merge conflict.

caco agent reintegrate --dry-run and its --preview alias are no-publish previews: they exit before merge/publish work and render the codeowner-aware preflight matrix when CODEOWNERS or .github/ownership/paths.yaml exists, otherwise reporting that no ownership config was found. That covers explicit --branch and --branch + --create-pr routes, which return before any fetch, gate, push, state publication, or gh call and report the resolved remote/target, planned push source/refspec, optional PR base, and open-or-reuse policy with would_publish:false and would_invoke_gh:false. Dry-run artifact summaries and hook-gate renderers turn computed merge summaries into text/JSON envelopes with would_reintegrate:false and would_publish:false; those helpers are operator text only and never rebase, merge, publish, run hooks, or mutate the checkout.

Before rebasing, that path runs a fetch preflight with bounded local-ref repair: it may delete and refetch only Cacophony-owned corrupt remote-tracking refs (refs/remotes/origin/agent/* and the configured state branch such as refs/remotes/origin/cacophony-state) plus corrupt local tags, never arbitrary remote-tracking branches. The intentional target-branch update above is a normal fetch refspec, not manual cleanup. After a successful rebase, if every local commit ahead of the fetched authoritative target changes only .cacophony/agent/** state or summary artefacts, the command resets to that target and reports the state-only cleanup, so persistent agents stop carrying already-published summary commits while any ahead commit touching code or other repository paths is preserved.

A persistent checkout left on pre-squash commits may re-anchor when a fresh target-containment and worktree-free merge-tree proof shows the complete source patch is already represented; exact immutable PR metadata or a successful synchronous reintegration receipt remains preferred audit evidence but is no longer a prerequisite. Dirty/untracked bytes, active Git state, locks, target drift, and partial containment refuse before mutation.

caco agent reset-checkout --soft|--mixed|--hard --target <ref> --reason <why> exposes the same self-owned reset discipline explicitly:

Human-readable complete/reintegrate output exposes an outcome: and direct/local receipt distinguishing target landing, open PR, mirror/named-branch staging, state-only publication, and checkout reconciliation/no-op. Mid-flight output separately reports target alignment and warns when a persistent runtime was not recreated.