feat(seatbelt): add system power access - #1066
feat(seatbelt): add system power access#1066Carlos Alexandro Becker (caarlos0) wants to merge 7 commits into
Conversation
Add an opt-in capability for sleep/wake notifications and power assertions while keeping the default sandbox profile restricted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 342a9bf7-a27a-457f-aaac-c910b8cd5535 Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 2
New issues introduced by this change (3)
| Severity | Finding |
|---|---|
src/core/wxc_common/src/config_parser.rs — systemPowerAccess is mapped without any schema-version check. The production loaders still… |
|
src/core/mxc_engine/src/policy.rs — This public setter bypasses schema validation because build_request has already parsed the… |
|
docs/schema.md — This example places systemPowerAccess under experimental.seatbelt, but the parser explicitly… |
What changed in this PR
Adds opt-in macOS Seatbelt permissions for sleep/wake notifications and power assertions.
Changes:
- Extends schema, parser, Rust SDK, and Node SDK contracts.
- Emits narrowly scoped Seatbelt power-service rules.
- Adds unit tests, generated artifacts, and documentation.
| File | Description |
|---|---|
src/core/wxc_common/src/wire.rs |
Adds the wire field. |
src/core/wxc_common/src/models.rs |
Adds the domain setting and default. |
src/core/wxc_common/src/config_parser.rs |
Maps and tests the setting. |
src/core/wxc_common/src/config_contract_adapters/v0_8.rs |
Defaults the unavailable field. |
src/core/wxc_common/src/config_contract_adapters/v0_7.rs |
Defaults the unavailable field. |
src/core/wxc_common/src/config_contract_adapters/dev/one_shot.rs |
Adapts the 0.9 contract field. |
src/core/wxc_common/src/config_contract_adapters/dev/one_shot_tests/stable_candidate.rs |
Tests adapter mappings. |
src/core/mxc-sdk/README.md |
Documents Rust SDK usage. |
src/core/mxc_engine/src/policy.rs |
Adds the Rust SDK setter. |
src/core/mxc_config_contract/tests/version_boundaries/seatbelt.rs |
Tests 0.9 introduction. |
src/core/mxc_config_contract/tests/v0_9_0_alpha/seatbelt.rs |
Tests field typing. |
src/core/mxc_config_contract/tests/v0_9_0_alpha/optional_fields.rs |
Tests null rejection. |
src/core/mxc_config_contract/tests/v0_9_0_alpha/fixtures/one_shot/valid/seatbelt_complete.json |
Extends the valid fixture. |
src/core/mxc_config_contract/src/dev/stable.rs |
Adds the exact contract field. |
src/backends/seatbelt/common/src/profile_builder.rs |
Generates and tests power rules. |
sdk/node/src/types.ts |
Exposes the Node SDK option. |
sdk/node/src/generated/wire.ts |
Updates rolling generated types. |
sdk/node/src/generated/v0_9_0_alpha/wire.ts |
Updates exact generated types. |
sdk/node/README.md |
Documents Node SDK usage. |
schemas/dev/mxc-config.schema.0.9.0-dev.json |
Updates the rolling schema. |
schemas/dev/mxc-config.schema.0.9.0-alpha.json |
Updates the exact schema. |
docs/seatbelt/seatbelt-backend.md |
Documents backend behavior. |
docs/schema.md |
Updates the configuration example. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 2
Pre-existing issues (3)
| Severity | Finding |
|---|---|
src/core/mxc_engine/src/policy.rs — This public setter bypasses schema validation because build_request has already parsed the… View comment |
|
src/core/wxc_common/src/config_parser.rs — systemPowerAccess is mapped without any schema-version check. The production loaders still… View comment |
|
docs/schema.md — This example places systemPowerAccess under experimental.seatbelt, but the parser explicitly… View comment |
Suppressed comments (3)
src/core/mxc_engine/src/policy.rs:688
- This mutator bypasses the advertised 0.9 schema boundary: callers can build and validate a 0.7/0.8 request, then set this model field after parsing, and the Seatbelt runner applies it without checking
schema_version. The new test even exercises that path with0.7.0-alpha. Make the setter reject unsupported versions (or add an execution-time invariant covering directly constructed requests) so the Rust SDK cannot enable a 0.9-only capability under an older contract.
pub fn set_seatbelt_system_power_access(&mut self, allow: bool) -> &mut Self {
self.inner
.seatbelt
.get_or_insert_default()
.system_power_access = allow;
docs/schema.md:209
- This example places the new option under
experimental.seatbelt, but the parser explicitly rejects that section with “has moved to the stable section”; the supported path is top-levelseatbelt.systemPowerAccess. Move the Seatbelt example out of theexperimentalobject so readers can copy a valid 0.9 configuration.
"systemPowerAccess": false // Allow sleep/wake notifications and power assertions
src/core/wxc_common/src/config_parser.rs:623
systemPowerAccessis not version-gated in the production parser. Both production loaders still deserialize the rollingwire::MxcConfigdirectly, and the exact-contract adapters are explicitly not reachable from production, so a request declaring schema 0.7 or 0.8 can set this field totrueand reach the profile builder. The contract boundary test alone does not enforce the runtime boundary. Add a raw field-version check to both loader paths (similar to directional networking) and a production parser regression test that rejects this field before 0.9.
system_power_access: system_power_access.unwrap_or(false),
Gate systemPowerAccess at every one-shot loader and again before Seatbelt execution so post-parse SDK mutations cannot bypass schema 0.9. Move the documented Seatbelt block to its supported top-level location. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 342a9bf7-a27a-457f-aaac-c910b8cd5535 Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
docs/schema.md — This “Full Schema” example still declares "version": "0.6.0-alpha" at line 87, but the parser… |
Issues resolved since last review (3)
| Severity | Finding |
|---|---|
docs/schema.md — This example places systemPowerAccess under experimental.seatbelt, but the parser explicitly… View resolved comment |
|
src/core/mxc_engine/src/policy.rs — This public setter bypasses schema validation because build_request has already parsed the… View resolved comment |
|
src/core/wxc_common/src/config_parser.rs — systemPowerAccess is mapped without any schema-version check. The production loaders still… View resolved comment |
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 1
Pre-existing issues (1)
| Severity | Finding |
|---|---|
docs/schema.md — This “Full Schema” example still declares "version": "0.6.0-alpha" at line 87, but the parser… View comment |
Keep the full schema example consistent with its systemPowerAccess field. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 342a9bf7-a27a-457f-aaac-c910b8cd5535 Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
docs/schema.md — This “Full Schema” example still declares "version": "0.6.0-alpha" at line 87, but the parser… View resolved comment |
Resolve the Seatbelt policy move by keeping execution invariants in the backend and raw schema-version gates in wxc_common. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 342a9bf7-a27a-457f-aaac-c910b8cd5535 Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
| Ok(()) | ||
| } | ||
|
|
||
| /// Emit the narrow Seatbelt capability needed for system power notifications |
There was a problem hiding this comment.
What is a narrow capability?
| /// Emit the narrow Seatbelt capability needed for system power notifications | ||
| /// and assertions. Off by default because opening `RootDomainUserClient` also | ||
| /// exposes host power-management operations beyond notification registration. | ||
| fn write_system_power_rules(out: &mut String, request: &ExecutionRequest) { |
There was a problem hiding this comment.
Most methods have the out parameter as the last parameter. Can the out variable be moved to the end? Also, why have an out param instead of a return value?
| fn system_power_access_default_off_omits_power_services() { | ||
| let r = req(); | ||
| let p = build_profile(&r).unwrap(); | ||
| assert!(!p.contains("systemPowerAccess")); |
There was a problem hiding this comment.
Can these strings ever change? If they do will this test catch that change?
| }); | ||
| let p = build_profile(&r).unwrap(); | ||
| assert!(p.contains("systemPowerAccess")); | ||
| assert!(p.contains("(global-name \"com.apple.PowerManagement.control\")")); |
There was a problem hiding this comment.
Kinda odd that the names here have a quite around them but the previous test has no quotes. Is this delibrate?
|
|
||
| #[test] | ||
| fn system_power_access_support_starts_at_v09() { | ||
| assert_eq!(system_power_access_support("0.8.0-alpha"), Some(false)); |
There was a problem hiding this comment.
Why only go down to 0.8.0?
| } | ||
|
|
||
| #[test] | ||
| fn rejects_system_power_access_before_v09() { |
There was a problem hiding this comment.
Looks like this is the same test as system_power_access_validation_rejects_pre_v09_requests?
| pub keychain_access: OptionalField<bool>, | ||
| /// Whether system sleep/wake notifications and power assertions are allowed. | ||
| #[serde(default)] | ||
| pub system_power_access: OptionalField<bool>, |
There was a problem hiding this comment.
The comment states power assertions but the variable is power_access
|
|
||
| #[test] | ||
| fn rejects_non_boolean_system_power_access_values() { | ||
| for system_power_access in ["0", "1", "\"string\"", "[]", "{}"] { |
There was a problem hiding this comment.
I think Copilot went a bit too in-depth with its unit testing.
| Ok(()) | ||
| } | ||
|
|
||
| fn validate_one_shot_field_versions(config: &serde_json::Value) -> Result<(), WxcError> { |
There was a problem hiding this comment.
These tests belong in seatbelt not wxc. power access is a seatbelt specific functionality.
Branden Bonaby (bbonaby)
left a comment
There was a problem hiding this comment.
We should be careful with the features we're adding before 1.0 is locked down. We don't want to overly add features to the backends that we haven't discussed a need for. Let's chat to see where this requirement is coming from.
|
talked with Anis, will hold this for now. Will move this to draft. |


📖 Description
Add opt-in
seatbelt.systemPowerAccesssupport for macOS workloads that needsleep/wake notifications or power assertions.
The capability is off by default. When enabled, the generated Seatbelt profile
allows only:
RootDomainUserClientcom.apple.PowerManagement.controlcom.apple.iokit.powerdxpcThe new field is available in schema
0.9.0-alphaand later. This change wiresit through the versioned contract, parser, Rust and Node SDKs, generated
artifacts, tests, and documentation. Published 0.7 and 0.8 schemas remain
unchanged.
🔗 References
None.
🔍 Validation
systemPowerAccess: falseandallowed with
systemPowerAccess: truethroughmxc-exec-mac.mainand reran the full PR validation.✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes (see docs/pull-requests.md)📋 Issue Type
GitHub Actions runs the PR validation build automatically. The ADO pipeline
(
MXC-PR-Build) is the Azure version of the PR pipeline, kept in parity with the GitHubActions build; it runs on merge to
main, and Microsoft reviewers with write access can trigger iton a PR with
/azp run. See docs/pull-requests.md.If the
dependency-feed-checkcheck fails on a new dependency, the crate must be added tothe feed before the PR can pass. See docs/pull-requests.md
for the steps.
Microsoft Reviewers: Open in CodeFlow