Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/governance-control-plane.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,38 @@ The Make targets are `governance-pull`, `governance-apply`, and
`governance-rollback`. Hosted fetching and central approval workflow are still
future Phase 2 work; the current protocol proves the local broker behavior.

## Rollout Controller

The rollout controller turns `local_simulation_only` output into an
append-only action ledger. It records canary, staged, broad, rollback, and hold actions
under local runtime state while preserving `changed_runtime_state: false`.

```bash
mcp-broker governance rollout-control \
--simulation rollout-simulation.json \
--state-dir ~/mcp/mcp-broker/state \
--operator release-operator \
--bundle-id governance-bundle \
--bundle-version ${PACKAGE_VERSION} \
--bundle-channel stable \
--bundle-digest sha256:abc123
```

The controller writes individual action records under
`governance-rollout/actions/` and appends every record to
`governance-rollout/action-log.jsonl`. Each record includes the operator,
bundle version and digest, source simulation state, broker id, stage, action,
approval requirement, reasons, and the original simulator decision.

`approval_required` and `compatibility_rejection` simulations become hold
records. `rollback` simulations become rollback records for the affected
brokers. `ready` simulations become canary, staged, or broad rollout records.
The command rejects non-local simulation modes, does not fetch bundles, does
not update deployment pointers, does not contact a hosted service, and does not
call upstream tools.

The Make target is `governance-rollout-control`.

## Fleet Status Export And Collection

Fleet status is a redacted export derived from the local
Expand Down
2 changes: 1 addition & 1 deletion docs/phase-foundation-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ status collection, rollout control, approvals, and a reference control plane.
compatibility check, local approval, apply, and rollback.
- [x] Add fleet-status collection with auth, retention, failure handling, and
no-secret validation.
- [ ] Add a rollout controller that turns simulator decisions into auditable
- [x] Add a rollout controller that turns simulator decisions into auditable
actions.
- [ ] Add operator approval workflow for mutating rollout, rollback, policy
override, and break-glass.
Expand Down
26 changes: 25 additions & 1 deletion mk/runtime.mk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: runtime-layout doctor broker-secrets-sync broker-start broker-stop broker-status broker-wait broker-reap broker-smoke tools-count facade-smoke codex-facade-smoke claude-facade-smoke agy-facade-smoke profile-validation codex-profile-validation claude-profile-validation agy-profile-validation discovery-parity codex-claude-discovery-parity codex-deferred-acceptance secret-import-env deployment-stage deployment-rollback deployment-recover governance-pull governance-apply governance-rollback fleet-status-collect break-glass-create break-glass-status service-plan launchagent-install launchagent-load launchagent-uninstall launchagent-unload systemd-install systemd-load systemd-uninstall systemd-unload windows-install windows-load windows-unload linux-container-smoke linux-release-gate windows-powershell-smoke config-backup config-render codex-app-policy project-mcp-audit project-mcp-migrate config-rollback profile-snippet
.PHONY: runtime-layout doctor broker-secrets-sync broker-start broker-stop broker-status broker-wait broker-reap broker-smoke tools-count facade-smoke codex-facade-smoke claude-facade-smoke agy-facade-smoke profile-validation codex-profile-validation claude-profile-validation agy-profile-validation discovery-parity codex-claude-discovery-parity codex-deferred-acceptance secret-import-env deployment-stage deployment-rollback deployment-recover governance-pull governance-apply governance-rollback governance-rollout-control fleet-status-collect break-glass-create break-glass-status service-plan launchagent-install launchagent-load launchagent-uninstall launchagent-unload systemd-install systemd-load systemd-uninstall systemd-unload windows-install windows-load windows-unload linux-container-smoke linux-release-gate windows-powershell-smoke config-backup config-render codex-app-policy project-mcp-audit project-mcp-migrate config-rollback profile-snippet

runtime-layout: ## Create configured runtime directories
$(call log_step,"Runtime layout")
Expand Down Expand Up @@ -160,6 +160,30 @@ governance-rollback: runtime-layout ## Roll back active governance deployment st
@PYTHONPATH="$(PYTHONPATH)" $(PYTHON) -m mcp_broker.cli governance rollback --state-dir "$(STATE_DIR)"
$(call log_success,"Governance rollback completed")

governance-rollout-control: runtime-layout ## Record local rollout-control audit actions from simulation output
@test -n "$(GOVERNANCE_ROLLOUT_SIMULATION)" || { $(call log_error,"Set GOVERNANCE_ROLLOUT_SIMULATION"); exit 2; }
@test -f "$(GOVERNANCE_ROLLOUT_SIMULATION)" || { $(call log_error,"Missing GOVERNANCE_ROLLOUT_SIMULATION=$(GOVERNANCE_ROLLOUT_SIMULATION)"); exit 1; }
@test -n "$(GOVERNANCE_ROLLOUT_OPERATOR)" || { $(call log_error,"Set GOVERNANCE_ROLLOUT_OPERATOR"); exit 2; }
@test -n "$(GOVERNANCE_ROLLOUT_BUNDLE_ID)" || { $(call log_error,"Set GOVERNANCE_ROLLOUT_BUNDLE_ID"); exit 2; }
@test -n "$(GOVERNANCE_ROLLOUT_BUNDLE_VERSION)" || { $(call log_error,"Set GOVERNANCE_ROLLOUT_BUNDLE_VERSION"); exit 2; }
@test -n "$(GOVERNANCE_ROLLOUT_BUNDLE_CHANNEL)" || { $(call log_error,"Set GOVERNANCE_ROLLOUT_BUNDLE_CHANNEL"); exit 2; }
@test -n "$(GOVERNANCE_ROLLOUT_BUNDLE_DIGEST)" || { $(call log_error,"Set GOVERNANCE_ROLLOUT_BUNDLE_DIGEST"); exit 2; }
@if [[ -n "$(GOVERNANCE_ROLLOUT_CREATED_AT)" ]]; then \
CREATED_AT_ARG="--created-at $(GOVERNANCE_ROLLOUT_CREATED_AT)"; \
else \
CREATED_AT_ARG=""; \
fi; \
PYTHONPATH="$(PYTHONPATH)" $(PYTHON) -m mcp_broker.cli governance rollout-control \
--simulation "$(GOVERNANCE_ROLLOUT_SIMULATION)" \
--state-dir "$(STATE_DIR)" \
--operator "$(GOVERNANCE_ROLLOUT_OPERATOR)" \
--bundle-id "$(GOVERNANCE_ROLLOUT_BUNDLE_ID)" \
--bundle-version "$(GOVERNANCE_ROLLOUT_BUNDLE_VERSION)" \
--bundle-channel "$(GOVERNANCE_ROLLOUT_BUNDLE_CHANNEL)" \
--bundle-digest "$(GOVERNANCE_ROLLOUT_BUNDLE_DIGEST)" \
$$CREATED_AT_ARG
$(call log_success,"Governance rollout-control completed")

fleet-status-collect: runtime-layout ## Prepare a redacted fleet-status collection envelope without uploading
@test -n "$(FLEET_STATUS_TARGET)" || { $(call log_error,"Set FLEET_STATUS_TARGET"); exit 2; }
@test -n "$(FLEET_STATUS_AUTH_REF)" || { $(call log_error,"Set FLEET_STATUS_AUTH_REF"); exit 2; }
Expand Down
44 changes: 44 additions & 0 deletions src/mcp_broker/cli_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path

from mcp_broker.governance_pull import main as governance_pull_main
from mcp_broker.governance_rollout_controller import main as rollout_controller_main


def add_governance_parser(
Expand All @@ -22,9 +23,12 @@ def add_governance_parser(
_add_pull_parser(governance_subparsers)
_add_apply_parser(governance_subparsers)
_add_rollback_parser(governance_subparsers)
_add_rollout_control_parser(governance_subparsers)


def handle_governance(args: argparse.Namespace) -> int:
if args.governance_command == "rollout-control":
return _handle_rollout_control(args)
argv = [args.governance_command, "--state-dir", str(args.state_dir.expanduser())]
if args.governance_command == "pull":
argv.extend(
Expand All @@ -51,6 +55,28 @@ def handle_governance(args: argparse.Namespace) -> int:
return governance_pull_main(argv)


def _handle_rollout_control(args: argparse.Namespace) -> int:
argv = [
"--simulation",
str(args.simulation.expanduser()),
"--state-dir",
str(args.state_dir.expanduser()),
"--operator",
args.operator,
"--bundle-id",
args.bundle_id,
"--bundle-version",
args.bundle_version,
"--bundle-channel",
args.bundle_channel,
"--bundle-digest",
args.bundle_digest,
]
if args.created_at:
argv.extend(["--created-at", args.created_at])
return rollout_controller_main(argv)


def _add_pull_parser(
governance_subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
) -> None:
Expand Down Expand Up @@ -88,3 +114,21 @@ def _add_rollback_parser(
)
rollback_parser.add_argument("--state-dir", required=True, type=Path)
rollback_parser.set_defaults(handler=handle_governance)


def _add_rollout_control_parser(
governance_subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
) -> None:
rollout_parser = governance_subparsers.add_parser(
"rollout-control",
help="Record local rollout-control audit actions from a simulation result",
)
rollout_parser.add_argument("--simulation", required=True, type=Path)
rollout_parser.add_argument("--state-dir", required=True, type=Path)
rollout_parser.add_argument("--operator", required=True)
rollout_parser.add_argument("--bundle-id", required=True)
rollout_parser.add_argument("--bundle-version", required=True)
rollout_parser.add_argument("--bundle-channel", required=True)
rollout_parser.add_argument("--bundle-digest", required=True)
rollout_parser.add_argument("--created-at")
rollout_parser.set_defaults(handler=handle_governance)
Loading