Skip to content

fix(workspace): permissive-by-default git mutation gates#43

Merged
chubes4 merged 1 commit intomainfrom
fix-workspace-git-policy
Apr 21, 2026
Merged

fix(workspace): permissive-by-default git mutation gates#43
chubes4 merged 1 commit intomainfrom
fix-workspace-git-policy

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented Apr 21, 2026

Summary

The datamachine_workspace_git_policies WordPress option gates git mutation ops (pull / add / commit / push) and git_add's path allowlist in the workspace wrapper. Before this change, every unconfigured repo failed every git mutation with git_write_disabled or no_allowed_paths, making the wrapper's git-family subcommands unusable out of the box.

Repro

On a fresh install (no datamachine_workspace_git_policies option set):

$ wp datamachine-code workspace git pull data-machine --allow-primary-mutation
Error: Git write operations are disabled for repo "data-machine".

The --allow-primary-mutation flag (the override documented in AGENTS.md) never gets evaluated because an earlier, undocumented gate fires first. The error message doesn't reference the option key or suggest a fix.

Workaround before this PR: git -C /var/lib/datamachine/workspace/<repo> pull — bypassing the wrapper entirely.

Root cause

ensure_git_mutation_allowed() treated "no entry in policy array" identically to "explicitly disabled":

// Before:
$repo = $policies['repos'][ $repo_name ] ?? null;
if ( ! is_array( $repo ) || empty( $repo['write_enabled'] ) ) {
    return new WP_Error( 'git_write_disabled', ... );
}

Combined with:

  • No CLI to populate the option (workspace --help has no policy subcommands)
  • No admin UI
  • No filter (until this PR)
  • No docs describing the option

…this was a hidden hard gate with no configuration surface.

A second, analogous gate lived in git_add(): the allowed_paths check returned no_allowed_paths when the list was empty, which it always is by default.

Fix

Permissive-by-default. The policy option becomes an opt-in restriction layer instead of a hidden requirement:

  1. ensure_git_mutation_allowed()null entry → allow. Entry present → honor explicit flags. write_enabled / push_enabled default to true when the flag itself is missing, so a partial config (e.g. only setting allowed_paths) doesn't accidentally disable writes.

  2. git_add() — empty allowed_paths → no restriction. When configured, the allowlist still gates. Sensitive-path + traversal checks are always enforced regardless of policy.

  3. Error messages now reference the policy option by name, so if someone does hit a deny, they know what to look at.

  4. New filter datamachine_workspace_git_policies for runtime injection of policy (e.g. env-specific, or driven by site config managed elsewhere) without writing to the option.

  5. get_workspace_git_policies() docblock documents the schema (write_enabled, push_enabled, allowed_paths, fixed_branch) and the permissive defaults.

What doesn't change

The primary-vs-worktree gate (ensure_primary_mutation_allowed + --allow-primary-mutation override) is the documented default safety mechanism per AGENTS.md:

"Primary is read-only by default: mutating ops on bare <repo> handles require --allow-primary-mutation."

That gate is untouched. It's the intended protection layer — the one users read about and expect.

To explicitly deny a repo

Set an option entry:

update_option( 'datamachine_workspace_git_policies', array(
    'repos' => array(
        'sensitive-repo' => array(
            'write_enabled' => false,
            'push_enabled'  => false,
        ),
    ),
) );

Or via the filter:

add_filter( 'datamachine_workspace_git_policies', function( $policies ) {
    $policies['repos']['sensitive-repo'] = array( 'write_enabled' => false );
    return $policies;
} );

Files

  • inc/Workspace/Workspace.php — gate logic + filter + docblock
  • data-machine-code.php — version bump 0.6.2 → 0.6.3
  • docs/CHANGELOG.md — entry added

Testing

No existing unit tests cover the workspace git mutation gates (only tests/smoke-gitsync.php covers the separate GitSync subsystem, which uses its own policy storage via GitSync::updatePolicy() and is unaffected by this change).

Manual verification: with this PR applied, workspace git pull <repo> --allow-primary-mutation and workspace git commit/push <repo>@<branch> succeed on an unconfigured workspace, matching AGENTS.md's documented contract.

Related

Not blocking but discovered during a data-machine release cascade that involves pulling main via the workspace wrapper. With this fix, the wrapper becomes usable for its intended workflows without requiring manual option configuration first.

The `datamachine_workspace_git_policies` option gates git mutation
ops (pull / add / commit / push) and git_add's path allowlist. Before
this change, unconfigured repos (the default state — no option row,
no CLI to populate it, no docs describing it) would fail every git
mutation with `git_write_disabled` / `no_allowed_paths`, making
the workspace wrapper's git-family subcommands unusable out of the
box and forcing callers back to plain `git -C <path>` shells.

Root cause: the policy check treated "no entry" the same as
"explicitly disabled". Combined with the lack of any way to
configure the option (no CLI subcommand, no admin UI, no docs),
this was effectively a hidden hard gate.

Change:
- `ensure_git_mutation_allowed()`: null entry → permissive (true).
  Entry present → honor explicit flags, with `write_enabled` /
  `push_enabled` defaulting to true when the flag itself is
  missing (so a partial config e.g. only `allowed_paths` doesn't
  accidentally lock out writes).
- `git_add()`: empty allowlist → no restriction (sensitive-path
  and traversal checks still enforced unconditionally).
- Error messages clarified to point at the policy option.
- New `datamachine_workspace_git_policies` filter for runtime
  policy injection alongside the existing option.
- `get_workspace_git_policies()` docblock documents schema +
  defaults.

The primary-vs-worktree gate (`ensure_primary_mutation_allowed` +
`--allow-primary-mutation` override) is the documented default
safety mechanism (per AGENTS.md) and is unchanged. That's the
intended protection layer.

To restrict a repo explicitly: add an entry under
`datamachine_workspace_git_policies.repos[<name>]` with
`write_enabled: false` / `push_enabled: false` / an
`allowed_paths` list / a `fixed_branch` constraint.
@chubes4 chubes4 merged commit dcb17b8 into main Apr 21, 2026
@chubes4 chubes4 deleted the fix-workspace-git-policy branch April 21, 2026 01:21
chubes4 added a commit that referenced this pull request Apr 21, 2026
Fold all unreleased work (GitSync Phases 1-3, WorktreeCleanupTask,
workspace git policy fix, AGENTS.md changes) under a single
[0.7.0] heading so homeboy's release pipeline can promote it.

Previous fix PR (#43) manually labeled the changelog [0.6.3] which
didn't match homeboy's auto-detected minor bump from the accumulated
feat: commits since v0.6.2. No functional code change — just
changelog labeling + version constant alignment.

Going forward: use 'homeboy changelog add -m ... -t ...' to append
unreleased entries and let 'homeboy release' assign the version.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant