Skip to content

Commit 8bb794c

Browse files
authored
Merge pull request #489 from DecapodLabs/agent/unknown/bugs-01kk3wktkzarkbqb
feat(validate): skip git workspace gates inside container
2 parents 08820a8 + a584af0 commit 8bb794c

3 files changed

Lines changed: 51 additions & 32 deletions

File tree

constitution/interfaces/CLAIMS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Columns:
7979
| claim.lcm.summary_deterministic | Same originals in timestamp order produce the same summary hash across runs. | `interfaces/LCM.md` | enforced | `decapod lcm summarize` produces stable hash | Deterministic by construction. |
8080
| claim.map.scope_reduction_invariant | Agentic map delegation MUST declare retained scope; empty retain is rejected. | `interfaces/LCM.md` | enforced | `decapod map agentic --retain` required | Enforced in CLI argument parsing. |
8181
| claim.todo.claim_before_work | Agents must claim a TODO before substantive implementation work on that task. | `interfaces/CONTROL_PLANE.md` | partially_enforced | `decapod todo claim` ownership records + procedural review | Enforced by process today; future validate gate may enforce ownership-before-mutation traces. |
82-
| claim.git.container_workspace_required | Git-tracked implementation work must execute in Docker-isolated git workspaces, not direct host worktree edits. | `specs/GIT.md` | enforced | `decapod validate` (Git Workspace Context Gate) | Enforced via validate gate checking container signals and worktree isolation. |
82+
| claim.git.container_workspace_required | Git-tracked implementation work must execute in Docker-isolated git workspaces rooted at `.decapod/workspaces/*`, not by directly editing the host repository working tree. Inside containers, `validate` only verifies build correctness (compile, test, lint) - git workspace gates are skipped. Host-side Git operations (commit, push, PR) happen after exiting the container. | `specs/GIT.md` | enforced | `decapod validate` (Git Workspace Context Gate, skipped in container) | Container validate is build-only; git ops happen on host. |
8383
| claim.git.no_direct_main_push | Direct commits/pushes to protected branches (master/main/production/stable/release/*) are forbidden; work must happen in working branches. | `specs/GIT.md` | enforced | `decapod validate` (Git Protected Branch Gate) | Enforced via validate gate checking current branch and unpushed commits. |
8484
| claim.git.container_runtime_preflight_required | Container workspace runs must pass runtime-access preflight and fail loudly with elevated-permission remediation when access is denied. | `specs/GIT.md` | partially_enforced | `container.run` runtime `info` preflight + permission-aware error diagnostics | Enforced in container runtime preflight; broader policy-level enforcement remains future work. |
8585
| claim.session.agent_password_required | Session access requires agent identity plus an ephemeral per-session password; expired sessions trigger cleanup and assignment eviction. | `specs/SECURITY.md` | partially_enforced | `session.acquire` credential issuance + `ensure_session_valid` password check + stale-session cleanup hook | Enforced for active command auth path; stronger cryptographic hardening may be added later. |

constitution/plugins/CONTAINER.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ Container subsystem runs agent actions in ephemeral Docker/Podman containers wit
3636
- Add only stack packages inferred from repo markers (`Cargo.toml`, `package.json`, `pyproject.toml`, `go.mod`).
3737
- Accept operator overrides via `DECAPOD_CONTAINER_APK_PACKAGES`.
3838

39+
## Validation Scope Inside Container
40+
41+
**Container validate is for build verification only.** When running `decapod validate` inside a Docker container:
42+
43+
- **Intended purpose:** Verify code compiles, tests pass, lint passes - confirm the work is legitimate and built correctly
44+
- **NOT enforced inside container:** Git workspace context gates (container signals, worktree isolation, commit-often)
45+
- **Exit then push:** After validate passes inside container, exit the container and perform Git operations (commit, push, PR) on the host
46+
47+
This ensures reproducible builds in the clean container environment while keeping Git operations (which require host git config, SSH keys, gh CLI) outside the container where they belong.
48+
3949
## Operator Runbook
4050
1. Run isolated task worktree from master:
4151
`decapod auto container run --agent clawdious --task-id R_01ABC --cmd "cargo test -q"`

src/core/validate.rs

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,27 @@ fn is_inside_git_work_tree(repo_root: &Path) -> bool {
4242
.unwrap_or(false)
4343
}
4444

45+
fn container_signal_reasons(repo_root: &Path) -> Vec<&'static str> {
46+
[
47+
(
48+
std::env::var("DECAPOD_CONTAINER").ok().as_deref() == Some("1"),
49+
"DECAPOD_CONTAINER=1",
50+
),
51+
(repo_root.join(".dockerenv").exists(), ".dockerenv marker"),
52+
(
53+
repo_root.join(".devcontainer").exists(),
54+
".devcontainer marker",
55+
),
56+
(
57+
std::env::var("DOCKER_CONTAINER").is_ok(),
58+
"DOCKER_CONTAINER env",
59+
),
60+
]
61+
.into_iter()
62+
.filter_map(|(signal, name)| signal.then_some(name))
63+
.collect()
64+
}
65+
4566
/// Spawn a validation gate in a rayon scope with timing and error capture.
4667
///
4768
/// Replaces ~10 lines of boilerplate per gate with a single invocation.
@@ -3433,36 +3454,17 @@ fn validate_git_workspace_context(
34333454
return Ok(());
34343455
}
34353456

3436-
let signals_container = [
3437-
(
3438-
std::env::var("DECAPOD_CONTAINER").ok().as_deref() == Some("1"),
3439-
"DECAPOD_CONTAINER=1",
3440-
),
3441-
(repo_root.join(".dockerenv").exists(), ".dockerenv marker"),
3442-
(
3443-
repo_root.join(".devcontainer").exists(),
3444-
".devcontainer marker",
3445-
),
3446-
(
3447-
std::env::var("DOCKER_CONTAINER").is_ok(),
3448-
"DOCKER_CONTAINER env",
3449-
),
3450-
];
3451-
3452-
let in_container = signals_container.iter().any(|(signal, _)| *signal);
3453-
if in_container {
3454-
let reasons: Vec<&str> = signals_container
3455-
.iter()
3456-
.filter(|(signal, _)| *signal)
3457-
.map(|(_, name)| *name)
3458-
.collect();
3459-
pass(
3457+
let container_reasons = container_signal_reasons(repo_root);
3458+
if !container_reasons.is_empty() {
3459+
skip(
34603460
&format!(
3461-
"Running in container workspace (signals: {})",
3462-
reasons.join(", ")
3461+
"Container-detected: git workspace gates skipped (signals: {}; validate verifies build only - commit/push/PR happens on host after container exit)",
3462+
container_reasons.join(", ")
34633463
),
34643464
ctx,
34653465
);
3466+
validate_commit_often_gate(ctx, repo_root)?;
3467+
return Ok(());
34663468
} else {
34673469
fail(
34683470
"Not running in container workspace - git-tracked work must execute in Docker-isolated workspace (claim.git.container_workspace_required)",
@@ -3478,11 +3480,6 @@ fn validate_git_workspace_context(
34783480

34793481
if is_worktree {
34803482
pass("Running in git worktree (isolated branch)", ctx);
3481-
} else if in_container {
3482-
pass(
3483-
"Container workspace detected (worktree check informational)",
3484-
ctx,
3485-
);
34863483
} else {
34873484
fail(
34883485
"Not running in isolated git worktree - must use container workspace for implementation work",
@@ -3650,6 +3647,18 @@ fn validate_git_protected_branch(
36503647
return Ok(());
36513648
}
36523649

3650+
let container_reasons = container_signal_reasons(repo_root);
3651+
if !container_reasons.is_empty() {
3652+
skip(
3653+
&format!(
3654+
"Git protected branch gate skipped inside container (signals: {}; host performs commit/push/PR checks after container exit)",
3655+
container_reasons.join(", ")
3656+
),
3657+
ctx,
3658+
);
3659+
return Ok(());
3660+
}
3661+
36533662
let protected_patterns = ["master", "main", "production", "stable"];
36543663

36553664
let current_branch = {

0 commit comments

Comments
 (0)