Skip to content

feat(repo): host-side repo_publish, approval-gated push (#735) - #778

Merged
oxoxDev merged 3 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/735-repo-publish
Aug 12, 2026
Merged

feat(repo): host-side repo_publish, approval-gated push (#735)#778
oxoxDev merged 3 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/735-repo-publish

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Sub-issue B of the #247 write tier. Adds repo_publish: an agent publishes the branch it committed in a checkout, host-side, gated by operator approval — without ever holding a writable credential.

Design (verified against the runtime, not just the issue text): repo_publish is a harness tool, but it deliberately does not rely on the #243 approve→re-dispatch path that #247's own scoping comment flagged as broken for a publish (the task-scoped checkout — carrying the agent's commits — is purged at turn end, before the re-dispatch fires). Instead, execute() stages the agent's committed HEAD into the mirror immediately (durable), then records an agent: None repo.publish effect that the runtime performs on approval — the same mechanism email.send uses. So the commits are never lost, there is no re-dispatch and no commit regeneration, and a denied or expired approval leaves the remote untouched.

  • RepoManager::stage_publish / push_published — the crate's two git write directions. stage_publish is a fetch of the checkout's HEAD into the mirror on a host-owned oc/<company>/<task> ref, so it never invokes the mirror's receive-pack; feat(repo): bind real repositories into agent workspaces with native PR/diff fetch #245's pre-receive push-refusal hook and its no-push contract test are untouched. push_published pushes that ref to the real remote, credentialed, host-side — never a force push, never a + refspec.
  • Structural refusals live in RepoManager: the branch is host-generated (oc/<company>/<task>, company = the manager's own id, task validated to a single safe segment) and re-validated before the push, so a prompt-injected --force origin main has no code path to reach one.
  • repo_publish tool stages, then queues the native approval; perform_effect performs the push once the operator approves. Wired behind four fail-closed gates: an explicit repo.write grant, a wired manager, a binding, and a push-capable credential.
  • Agent-attributed commits — the checkout's git identity is set to the agent seat at materialize, so a published branch's git log answers "which agent wrote this".

Acceptance criteria (#735)

  • Wired only under repo.write and a push-capable credential; repo / * never confer it; a read-only credential is fail-closed.
  • The agent's workspace clone still has no credentialed remote — feat(repo): bind real repositories into agent workspaces with native PR/diff fetch #245's credential-non-exposure assertion passes with the write tier enabled.
  • Push target is always oc/<company>/<task>, host-generated; an agent-supplied branch, a force flag, the default branch, or any ref outside oc/ is rejected in RepoManager, with a test per refusal.
  • Parks through the approval gate; a denied or expired approval leaves the remote untouched.
  • Approving a parked publish completes it — the runtime performs the push directly, no re-ask and no regenerated commits.
  • Commits pushed by an agent are attributed to that agent seat, distinguishable in git log.
  • feat(repo): bind real repositories into agent workspaces with native PR/diff fetch #245's no-push contract test passes unchanged.

Notes for reviewers

Test plan

Closes #735

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added repository publishing for task-specific branches.
    • Changes are staged locally and require explicit approval before being pushed.
    • Publishing uses the approved commit and prevents force-pushes.
  • Bug Fixes

    • Repository publishing is blocked for non-task activity, read-only access, unsupported credentials, and insecure secret storage.
    • Added validation for task IDs, branches, and approved commits.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@oxoxDev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e255c75-39dc-4fbf-b12f-5cf602f0787b

📥 Commits

Reviewing files that changed from the base of the PR and between bca5bfa and 80dcef7.

📒 Files selected for processing (1)
  • src/harness/build.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cfdbe526-f3d3-43d7-bc93-40395a73051b

📥 Commits

Reviewing files that changed from the base of the PR and between 95add1c and bca5bfa.

📒 Files selected for processing (3)
  • src/harness/brain.rs
  • src/harness/build.rs
  • src/policy/consequence.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/harness/brain.rs
  • src/policy/consequence.rs
  • src/harness/build.rs

📝 Walkthrough

Walkthrough

The PR adds an approval-gated repo_publish tool. Task turns set checkout context, eligible write tiers expose the tool, host code stages commits on namespaced branches, and approved effects push the exact staged commit without force.

Changes

Repository publishing

Layer / File(s) Summary
Grant, task context, and write-tier wiring
src/harness/brain.rs, src/harness/build.rs, src/harness/repo.rs
Task turns set or clear checkout task state. Repository tools receive agent and approval context. repo_publish is available only with the required write grant, push capability, repository configuration, binding, and safe secret storage.
Task-scoped publish tool and approval effect
src/harness/repo.rs, src/harness/repo/test.rs, src/policy/consequence.rs
The tool validates task and repository state, stages checkout HEAD on oc/<company>/<task>, queues a native approval, and reports a pending result. Tests cover missing task context and read-only bindings.
Host-side staging and non-force push
src/runtime/cycle.rs, src/runtime/repo_manager.rs, src/runtime/repo_manager/test.rs
Approved effects push the bound commit through RepoManager. Branch and task validation, non-force updates, and approval-bound commit selection are tested.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant repo_publish
  participant ApprovalRequestQueue
  participant perform_effect
  participant RepoManager
  participant RemoteRepository

  Agent->>repo_publish: submit repository and message
  repo_publish->>RepoManager: stage checkout HEAD
  RepoManager-->>repo_publish: return task branch and commit
  repo_publish->>ApprovalRequestQueue: queue repo.publish approval
  repo_publish-->>Agent: return pending publish
  ApprovalRequestQueue->>perform_effect: deliver approved effect
  perform_effect->>RepoManager: push approved commit
  RepoManager->>RemoteRepository: push without force
Loading

Possibly related issues

Possibly related PRs

Suggested labels: cluster:approvals, priority: p2

Suggested reviewers: oxoxdev

Poem

I’m a rabbit with commits in a row,
Task branches bloom where safe refs grow.
Approvals wait by the burrow door,
Then exact commits hop to the store.
No force-push thunder shakes the ground—
Just tidy Git tracks, approval-bound.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the host-side repo_publish feature and its approval-gated push behavior.
Linked Issues check ✅ Passed The changes implement task-scoped staging, structural push refusals, credential-gated wiring, approval-gated publishing, and agent attribution required by #735.
Out of Scope Changes check ✅ Passed The changes remain focused on repo_publish staging, approval, runtime execution, wiring, and related tests required by #735.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/735-repo-publish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 12, 2026 11:57
@coderabbitai coderabbitai Bot added cluster:approvals Human-in-the-loop boundary, policy, and approval surfaces priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. labels Aug 12, 2026

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The architectural call is the right one and it is the part that would have been easy to get wrong: not routing this through #243's approve→re-dispatch path, because the task-scoped checkout carrying the agent's commits is purged at turn end, before the re-dispatch fires. Staging HEAD into the mirror immediately and recording an agent: None effect the runtime performs on approval — the email.send shape — means the commits are never lost, nothing is regenerated, and a denied or expired approval leaves the remote untouched. Verifying that against the runtime rather than against the issue text is what found it.

Three more things I checked and liked:

  • stage_publish is a fetch, not a push. Pulling the checkout's HEAD into the mirror on a host-owned ref means receive-pack is never invoked, so #245's pre-receive refusal hook and its no-push contract test stay untouched rather than being weakened to accommodate a new writer. That is the difference between adding a capability and eroding a guard.
  • The refusals are structural, not validated. The branch is host-generated from the manager's own company id and a task segment re-validated before the push, so a prompt-injected --force origin main has no code path to a ref it could reach. Confirmed the refspec is refs/heads/{branch}:refs/heads/{branch} — no leading +, so force is not expressible rather than merely rejected.
  • The credential stays out of the error surface. The missing-credential path names the binding key, not the token. Worth keeping that discipline as this grows: #614 is the local precedent for a credential reaching a durable record through an error string.

Agent-attributed commits, so a published branch's git log answers which agent wrote it, is the detail that makes this auditable after the fact rather than only gated before it.

0 major. 1 sequencing item, and acting on it saves real work. Approving.

Land #758 first — you are carrying the same grant twice

grants_repo_write_explicit is not in main, is in this branch, and is in #758 — and the two conflict in src/company/mod.rs, which is the same re-export block #758 has just had hand-resolved.

They are independent branches, not a stack: feat/734-repo-write-grant is not an ancestor of feat/735-repo-publish. This branch touches every file #758 touches plus four more, so sub-issue B is carrying a copy of sub-issue A rather than building on it.

Whichever lands second re-conflicts on that block. The cheaper order is clear:

  1. #758 first — it is the smaller, foundational half, its conflict is already resolved, and it needs only cargo fmt --all to clear a red Rust lane.
  2. Then rebase this branch, at which point the duplicated grant should fall out as already-present and the conflict disappears rather than needing a second hand-resolution.

Merging this one first inverts that: #758 would then conflict harder, on work it originated, for no benefit.

Before merging: base is current and the branch is MERGEABLE — but only against main, not against #758. Worth re-checking mergeable after that lands rather than treating today's green as settled; main has gone red twice this week from branches that were each green against a base lacking the other.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/spec/runtime/repos.md (1)

440-455: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the repository write-tier documentation.

docs/spec/runtime/repos.md still claims that no push path exists and that no tool consumes repo.write. Document RepoManager::stage_publish, RepoManager::push_published, and repo_publish. Add repo_publish to the agent-surface table. Preserve the accurate repo.write and can_push fail-closed rules.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/spec/runtime/repos.md` around lines 440 - 455, Update the “Not in this
tier” section to document the existing write path through
RepoManager::stage_publish, RepoManager::push_published, and repo_publish
instead of claiming no push path or consumers exist. Add repo_publish to the
agent-surface table, while preserving the documented repo.write separation and
can_push fail-closed behavior.

Source: Learnings

🧹 Nitpick comments (5)
src/runtime/cycle.rs (1)

1234-1260: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add focused coverage for the approved-publish arm.

The layer's changed test files cover RepoManager staging and pushing. This dispatch arm carries its own behavior that no listed test exercises: the kind match, the repo and branch extraction, and the Unimplemented branch when rt.repos() is None. The repository guideline for **/*.rs requires focused tests with every behavior change.

As per coding guidelines: "**/*.rs: Add focused tests with every behavior change."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/cycle.rs` around lines 1234 - 1260, Add focused tests for the
approved-publish dispatch arm in the surrounding cycle test module, covering
REPO_PUBLISH_EFFECT matching, repo and branch payload extraction passed to
push_published, and the Unimplemented error when rt.repos() is None. Keep
existing RepoManager staging/pushing tests unchanged and use the established
runtime/test helpers.

Source: Coding guidelines

src/harness/mod.rs (1)

5237-5248: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the declaration check cover repo_publish.

can_push: None keeps repo_publish off this belt, so every_registered_tool_is_declared never checks it against policy::consequence::DECLARED. The comment at lines 5226-5231 names this exact failure mode for repo_checkout.

Set can_push: Some(true) here and add repo.write to the grant list at line 5334, then add repo_publish to the vacuity guard at lines 5344-5354.

♻️ Proposed change to wire the publish tool onto the coverage belt
                 size_bytes: 0,
                 bound_at_millis: 1,
-                can_push: None,
+                // Push-capable so the `repo.write` tier wires `repo_publish`
+                // onto this belt; otherwise the declaration check below never
+                // looks at it.
+                can_push: Some(true),
             }];

Then widen the grant list and the vacuity guard:

             (
-                &["workspace", "search", "media", "composio", "repo"][..],
+                &[
+                    "workspace",
+                    "search",
+                    "media",
+                    "composio",
+                    "repo",
+                    "repo.write",
+                ][..],
                 false,
                 true,
             ),
             "describe_workflow",
             "repo_checkout",
+            "repo_publish",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/harness/mod.rs` around lines 5237 - 5248, Update the RepoBinding fixture
to set can_push to Some(true), add repo.write to the grant list, and include
repo_publish in the vacuity guard so every_registered_tool_is_declared validates
the publish tool against policy::consequence::DECLARED.
src/runtime/repo_manager.rs (1)

631-644: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the discarded re-probe error.

Line 640 uses .ok(), so a probe failure is dropped without a trace. The capability then stays unknown and the write tier stays unwired, but the only operator-visible message is the build_agent warning "no bound repository has a push-capable credential". That message names the wrong cause.

Add a debug or warn line on the failure branch so an operator can tell a read-only credential apart from a probe that never answered.

♻️ Proposed change to record the probe failure
             match (self.host.as_ref(), token.as_deref()) {
                 (Some(host), Some(tok)) => {
                     let coords = RepoCoordinates {
                         owner: binding.owner.clone(),
                         repo: binding.repo.clone(),
                     };
-                    host.repo_meta(&coords, tok).await.ok().map(|m| m.can_push)
+                    match host.repo_meta(&coords, tok).await {
+                        Ok(meta) => Some(meta.can_push),
+                        Err(err) => {
+                            tracing::debug!(
+                                company = %self.company,
+                                key,
+                                error = %err,
+                                "[repo] could not probe push capability; it stays unknown \
+                                 (cannot-push) and the next fetch retries"
+                            );
+                            None
+                        }
+                    }
                 }
                 _ => None,
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/repo_manager.rs` around lines 631 - 644, Update the reprobed_push
logic in RepoManager to preserve the existing successful capability mapping
while explicitly logging repo_meta probe failures instead of discarding them
with .ok(). Add a debug or warn message on the error branch that distinguishes a
failed probe from a confirmed read-only credential.
src/harness/repo/test.rs (1)

934-943: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin the staged commit, not just the ref's existence.

git_at panics on a non-zero exit, so rev-parse on a missing ref already fails the test. The !staged.is_empty() assertion at line 994 therefore adds nothing. It also does not prove the branch points at the agent's commit.

Return the HEAD SHA from committed_checkout and compare against it. src/runtime/repo_manager/test.rs line 1042 uses this stronger form.

💚 Proposed change to assert the staged commit
-async fn committed_checkout(ctx: &RepoToolContext, mirror: &Path, key: &str) {
+/// Returns the checkout's HEAD SHA, so a caller can assert the staged branch
+/// points at the agent's commit rather than merely existing.
+async fn committed_checkout(ctx: &RepoToolContext, mirror: &Path, key: &str) -> String {
     let dest = ctx.workspace.join(CHECKOUT_SUBDIR).join(key);
     materialize(mirror, &dest, Some("main"), None)
         .await
         .expect("materialize");
     identify(&dest);
     std::fs::write(dest.join("FIX.md"), "the fix\n").unwrap();
     git_at(&dest, &["add", "FIX.md"]);
     git_at(&dest, &["commit", "--quiet", "-m", "the fix"]);
+    git_at(&dest, &["rev-parse", "HEAD"])
 }
-    committed_checkout(&ctx, &mirror, &binding.key).await;
+    let head = committed_checkout(&ctx, &mirror, &binding.key).await;
     let staged = git_at(&mirror, &["rev-parse", "refs/heads/oc/acme/card-1"]);
-    assert!(!staged.is_empty(), "the mirror carries the staged branch");
+    assert_eq!(
+        staged, head,
+        "the staged branch must point at the agent's commit"
+    );

The no-task test at line 952 does not bind the return value, so it needs no change.

Also applies to: 992-994

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/harness/repo/test.rs` around lines 934 - 943, Update committed_checkout
to return the SHA of the commit created after committing FIX.md, then capture
that return value in the applicable test and compare the staged branch’s
resolved commit against it. Remove the redundant non-empty staged assertion;
preserve the no-task test’s unbound return behavior.
src/runtime/repo_manager/test.rs (1)

1081-1111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for a binding with no stored credential.

push_published refuses with InvalidRequest when token_for returns None (src/runtime/repo_manager.rs lines 863-867). No test covers that branch. A bind_local binding starts with no credential, so the case costs one short test.

Add it beside this table so a future change cannot make a credential-less binding attempt a push.

💚 Proposed test for the missing-credential refusal
/// A binding with no stored credential cannot publish: the refusal happens
/// before git runs, rather than failing inside an unauthenticated push.
#[tokio::test]
async fn a_push_without_a_stored_credential_is_refused() {
    let scratch = Scratch::new("no-credential-push");
    let url = fixture_remote(&scratch);
    let (mgr, _secrets) = manager(&scratch);
    // `bind_local` stores no token at all.
    mgr.bind_local(&url, "fixture", vec!["main".into()])
        .await
        .unwrap();

    let checkout = scratch.join("checkout");
    let mirror = mgr.mirror_path("fixture");
    checkout_with_commit(&scratch, &mirror, &checkout, "FIX.md", "the fix\n");
    let branch = mgr
        .stage_publish("fixture", &checkout, "task-1")
        .await
        .unwrap();

    let err = mgr.push_published("fixture", &branch).await.unwrap_err();
    assert!(
        matches!(err, OpenCompanyError::InvalidRequest(_)),
        "a credential-less binding must be refused before the push: {err:?}"
    );
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/repo_manager/test.rs` around lines 1081 - 1111, Add a neighboring
async test for the credential-less binding path, using bind_local without
setting a secret, then create a staged publish via checkout_with_commit and
stage_publish before calling push_published. Assert it returns
OpenCompanyError::InvalidRequest, confirming the push is refused before git
runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/harness/build.rs`:
- Around line 527-543: Update RepoPublishTool::execute to validate the selected
binding’s can_push value is Some(true) before calling stage_publish, rejecting
read-only bindings before staging or approval creation. Preserve existing
behavior for push-capable bindings, and add a test covering mixed push-capable
and read-only bindings.

In `@src/harness/repo.rs`:
- Around line 998-1045: Update the publish flow around stage_publish and
perform_effect so staging returns the exact commit HEAD, includes that commit
identifier in the approval effect payload, and the later push validates and
sends exactly the approved commit rather than the current mirror ref. Preserve
force-update staging while preventing restaging or descendant changes from
altering an existing approval, and add a focused regression test covering this
scenario.

In `@src/runtime/cycle.rs`:
- Line 1240: Update the REPO_PUBLISH_EFFECT reference in the cycle effect branch
so the default build does not depend on the feature-gated crate::harness::repo
module: either move the constant to an always-compiled module and reference it
there, or gate the branch with #[cfg(feature = "openhuman")].

---

Outside diff comments:
In `@docs/spec/runtime/repos.md`:
- Around line 440-455: Update the “Not in this tier” section to document the
existing write path through RepoManager::stage_publish,
RepoManager::push_published, and repo_publish instead of claiming no push path
or consumers exist. Add repo_publish to the agent-surface table, while
preserving the documented repo.write separation and can_push fail-closed
behavior.

---

Nitpick comments:
In `@src/harness/mod.rs`:
- Around line 5237-5248: Update the RepoBinding fixture to set can_push to
Some(true), add repo.write to the grant list, and include repo_publish in the
vacuity guard so every_registered_tool_is_declared validates the publish tool
against policy::consequence::DECLARED.

In `@src/harness/repo/test.rs`:
- Around line 934-943: Update committed_checkout to return the SHA of the commit
created after committing FIX.md, then capture that return value in the
applicable test and compare the staged branch’s resolved commit against it.
Remove the redundant non-empty staged assertion; preserve the no-task test’s
unbound return behavior.

In `@src/runtime/cycle.rs`:
- Around line 1234-1260: Add focused tests for the approved-publish dispatch arm
in the surrounding cycle test module, covering REPO_PUBLISH_EFFECT matching,
repo and branch payload extraction passed to push_published, and the
Unimplemented error when rt.repos() is None. Keep existing RepoManager
staging/pushing tests unchanged and use the established runtime/test helpers.

In `@src/runtime/repo_manager.rs`:
- Around line 631-644: Update the reprobed_push logic in RepoManager to preserve
the existing successful capability mapping while explicitly logging repo_meta
probe failures instead of discarding them with .ok(). Add a debug or warn
message on the error branch that distinguishes a failed probe from a confirmed
read-only credential.

In `@src/runtime/repo_manager/test.rs`:
- Around line 1081-1111: Add a neighboring async test for the credential-less
binding path, using bind_local without setting a secret, then create a staged
publish via checkout_with_commit and stage_publish before calling
push_published. Assert it returns OpenCompanyError::InvalidRequest, confirming
the push is refused before git runs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f5da330-2820-42e8-805f-005710b5fb62

📥 Commits

Reviewing files that changed from the base of the PR and between bc44f51 and b50cefe.

📒 Files selected for processing (15)
  • docs/spec/runtime/repos.md
  • src/company/mod.rs
  • src/company/types.rs
  • src/harness/brain.rs
  • src/harness/build.rs
  • src/harness/mod.rs
  • src/harness/repo.rs
  • src/harness/repo/test.rs
  • src/policy/consequence.rs
  • src/runtime/cycle.rs
  • src/runtime/repo_manager.rs
  • src/runtime/repo_manager/github.rs
  • src/runtime/repo_manager/test.rs
  • src/runtime/repo_manager/types.rs
  • src/server/ops/repos.rs

Comment thread src/harness/build.rs
Comment thread src/harness/repo.rs
Comment thread src/runtime/cycle.rs Outdated
Reconcile the write tier with two changes that landed on main:

- tinyhumansai#758 (tinyhumansai#734) merged, so the diff shrinks to just the tinyhumansai#735 changes.
- tinyhumansai#752 added a fourth wiring gate: repo tools are refused on a plaintext secret
  backend, where the credential is readable by the agent shell. Both features
  added a "fourth gate" test helper; unified them into one
  built_tool_names_with_repos_full(grants, bindings, push_capable, storage_kind)
  with the two convenience wrappers on top.

Also extend the tinyhumansai#752 gate to the write tier: repo_publish uses the credential
host-side to push, so it is refused on a plaintext backend exactly like the read
tools — added the gate and a test.

Submodule pointers taken from origin/main (this branch touches no vendored code).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot removed cluster:approvals Human-in-the-loop boundary, policy, and approval surfaces priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. labels Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/runtime/cycle.rs (1)

1251-1277: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add an effect-execution test.

src/runtime/repo_manager/test.rs tests repository staging and pushing, but it does not execute perform_effect. Add a focused test that approves repo.publish, verifies the exact repo, branch, and head reach the repository manager, and verifies that an absent manager returns Unimplemented.

As per coding guidelines, “**/*.rs: Add focused tests with every behavior change.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/cycle.rs` around lines 1251 - 1277, Add a focused test covering
the REPO_PUBLISH_EFFECT branch in perform_effect: approve a repo.publish effect,
assert the repository manager receives the exact repo, branch, and head values,
and assert execution without a configured manager returns
OpenCompanyError::Unimplemented. Place it with the existing repository manager
tests and reuse their test helpers or mock manager setup.

Source: Coding guidelines

src/runtime/repo_manager.rs (1)

893-925: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Authorization Bypass (CWE-367): Time-of-check Time-of-use (TOCTOU) Race Condition

Reachability: Internal

Serialize stage_publish per publish branch.

stage_publish updates the mirror ref and reads it in separate awaits. Concurrent calls for the same key and task can bind the first approval to the second call’s commit. Protect fetch and rev-parse with a task-scoped lock and add a concurrent regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/repo_manager.rs` around lines 893 - 925, Serialize the fetch and
subsequent rev-parse in stage_publish for each task and publish branch using a
task-scoped lock, keeping the lock held across both awaits so the staged ref
cannot be replaced between them. Reuse the existing task/branch lock
infrastructure if available, and add a concurrent regression test proving each
approval remains bound to its own staged commit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/runtime/cycle.rs`:
- Around line 1251-1277: Add a focused test covering the REPO_PUBLISH_EFFECT
branch in perform_effect: approve a repo.publish effect, assert the repository
manager receives the exact repo, branch, and head values, and assert execution
without a configured manager returns OpenCompanyError::Unimplemented. Place it
with the existing repository manager tests and reuse their test helpers or mock
manager setup.

In `@src/runtime/repo_manager.rs`:
- Around line 893-925: Serialize the fetch and subsequent rev-parse in
stage_publish for each task and publish branch using a task-scoped lock, keeping
the lock held across both awaits so the staged ref cannot be replaced between
them. Reuse the existing task/branch lock infrastructure if available, and add a
concurrent regression test proving each approval remains bound to its own staged
commit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 43330070-f123-4438-bc2a-cd7662899230

📥 Commits

Reviewing files that changed from the base of the PR and between b50cefe and 95add1c.

📒 Files selected for processing (7)
  • src/harness/build.rs
  • src/harness/repo.rs
  • src/harness/repo/test.rs
  • src/policy/consequence.rs
  • src/runtime/cycle.rs
  • src/runtime/repo_manager.rs
  • src/runtime/repo_manager/test.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/policy/consequence.rs
  • src/harness/repo/test.rs
  • src/harness/repo.rs

@coderabbitai coderabbitai Bot added cluster:approvals Human-in-the-loop boundary, policy, and approval surfaces priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. labels Aug 12, 2026
tinyhumansai#784 merged, which carried this branch's own lineage into main along with
a resolution of the same conflict — so the two sides now hold two names
for one helper: `built_tool_names_with_repos_full` here and
`built_tool_names_with_repos_on_cap` there, identical in signature and
body.

Converged on main's name rather than this branch's, because main is the
side every future rebase merges against and a second name would collide
again on each one. Kept this branch's doc wording, which says the thing
more plainly: three wrappers, each defaulting the axis it does not vary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cluster:approvals Human-in-the-loop boundary, policy, and approval surfaces priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(repo): host-side repo_publish — namespaced branches, structural push refusals, approval-gated

2 participants