Repository navigation
feat(sync): gate a peer's publishing on review - #11
Merged
Merged
Conversation
A peer whose committed .sidecar sets `review = "required"` publishes nothing until a review approves exactly what would leave the machine. Redaction catches secrets that look like secrets; a reviewer, usually a coding agent, catches what pattern matching cannot. - Gated peers never push local history: each approved review publishes one commit of the reviewed tree on the public base it was measured from, and the canonical merge is built only from what the remote already holds. Local main is never reset and is joined to each publish by a link merge, so later edits merge without spurious forks. Reviews are per repo family and cover local main, so a rejection holds every sibling worktree. - Publishing git runs hermetically (withPublicGit): fixed identity, no hooks, rerere, signing, push options, replacement objects, or local attributes, drivers, and filters. "Already public" comes only from sidecar's own refs/sidecar-public fetch. - Review records and packets live in the state dir. A detached runner starts the machine-local reviewer command; verdicts come from `sidecar review approve|reject`, taken under the sync lock, so a reviewer can hand a review off to a discussion and decide later. Reviews undecided past `review-timeout`, rejected, failed, or without a reviewer need attention in sync, status, and the health heartbeat, which never carries a reason. - .sidecar parsing is now strict: unknown keys and other versions are refused, so a policy key an older CLI doesn't know fails closed. - Add `sidecar review`, `init --review/--review-timeout`, optional `.sidecar-review` guidance, docs/review.md, and tests.
Fixes from a joint review (Claude and Codex) of the review gate. - A shared store is bound to review: a gated peer marks it (`sidecar.review` in its git config) and an ungated peer refuses to snapshot, merge, or push through it. Families never link across review modes, so an ungated sibling can no longer publish gated work. - One review launches its reviewer once. `review retry` retires the review and opens a new one of the same change, so a stale reviewer can only decide a closed review. The attempt files and resetReview are gone; a launch that never records a run counts as failed. - Every review has a public base. A remote main that was started over is joined to local main by a link commit, never merged or reset. Publishing checks every parent of the cut or resumed commit is public, and keeps the prepared commit reachable under refs/sidecar-publishing/<id>. The empty-tree packet and parentless publish paths are removed. - Gated failure details reach only the terminal: once a process touches a gated (or unreadable) config or a review packet, cli.ts logs failures without a message; the heartbeat carries the stage only; the daemon logs a gated peer's exit status and omits watcher error details. - Re-running init keeps existing settings for omitted flags, so it can no longer drop the review gate. `snapshot --push` on a gated peer is refused before anything is snapshotted. `review reviewer` takes the command as one argument. The gated heartbeat reads its previous record from public refs. Refresh points gated work at review, and publishing skips a redundant fetch.
Retry is for a reviewer that broke. On an approved review not yet publishing, it superseded the approval and opened a pending review in its place, quietly withdrawing a decision. It now refuses; `reject` is the way to withdraw an approval before it publishes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Redaction catches secrets that look like secrets. It can't tell that a note names a customer or describes an unreleased plan. This adds an opt-in review gate: a peer whose committed
.sidecarsetsreview = "required"publishes nothing until a review — usually a coding agent, sometimes a person — approves exactly what would leave the machine. Sync on the machine itself is unchanged; sibling worktrees still settle within seconds.Policy is committed; the reviewer is machine-local (
sidecar review reviewer "<command>"), because a committed command would be one any clone could make a daemon run. A review's verdict comes fromsidecar review approve|reject, not the reviewer's exit status, so a reviewer can hand the review to a discussion thread and decide later. Seedocs/review.md.Changes:
refs/sidecar-publishing/<id>until it lands. Local main is never reset and is joined to each publish by an-s ourslink merge, so edits made after an approval don't fork.sidecar.reviewin its git config), and an ungated peer refuses to snapshot, merge, or push through it; working copies with different review policies never share a store.withPublicGit: fixeduser@hostidentity, UTF-8, no hooks, rerere, signing, push options, replacement objects, or local attributes/drivers/filters;--no-follow-tags;remote.origin.pushurlelsewhere is refused. "Already public" comes only from sidecar's ownrefs/sidecar-public/*fetch, neverorigin/*.review retryretires the review and opens a new one of the same change, so a stale reviewer can only ever decide a closed review. Approved reviews can't be retried, and a publish in progress can't be rejected. Verdicts are written under the sync lock. Rejected, failed, never-started, reviewer-less, or past-review-timeoutreviews need attention insync,status, and the health heartbeat, which carries the state but never a reason. Optional.sidecar-reviewguidance is committed policy beside the config..sidecarnow refuses unknown keys and other versions, so a policy key an older CLI doesn't know fails closed instead of being dropped. Re-runninginitkeeps existing settings for omitted flags, so it can't drop the gate.sidecar review [show|approve|reject|retry|reviewer],init --review/--review-timeout,snapshot --pushrefused on gated peers before anything is snapshotted, gatedmergeandrefreshpaths, docs/review.md, and updates to README, commands, sync, rules, agent guide, and skill.Review and testing:
bun run checkclean; fast suite 225 passed;bun run test:integration368 passed on the hardening commit, including new gated-publishing tests; an out-of-tree smoke run of 94 real-git scenarios (two machines, sibling worktrees, rejection→fix, retry, binary/non-UTF-8 content, forged refs, hooks, merge drivers, replacement objects, timeouts) passed on the final commit.Known limits are listed in docs/review.md: machines on a sidecar from before this change ignore
review(update every machine first), heartbeat metadata publishes without review,url.*.insteadOfrouting still applies, and turning review off requires unsettingsidecar.reviewon the clone, after which the next sync publishes the local history review kept back.