feat(os): bind bounded restart grants and rollback

This commit is contained in:
冰朔 2026-08-07 14:36:57 +08:00
commit e94c4f608f
3 changed files with 164 additions and 17 deletions

View file

@ -45,9 +45,11 @@
2. 每个适配器在代码中定义动作类型和参数形状。
3. 节点策略声明允许操作的精确资源。
4. 变更动作必须匹配当前授权和回滚检查点。
5. 命令退出码只表示执行器观察,目标侧读回才决定 `PASS_100`
6. Linux 管理入口与光湖正常入口分离;紧急入口启用必须留下维护回执。
7. 密钥、令牌和模型 API 凭据保留在节点受保护边界,不进入语言记录或执行请求。
5. 变更授权必须由节点策略精确绑定授权编号、动作、资源和回滚检查点,不能只凭请求自行声明。
6. 重启动作前必须先读回服务为 active动作失败后执行固定的 `reset-failed → start → active` 恢复链,并在回执中分别记录动作失败和回滚结果。
7. 命令退出码只表示执行器观察,目标侧读回才决定 `PASS_100`
8. Linux 管理入口与光湖正常入口分离;紧急入口启用必须留下维护回执。
9. 密钥、令牌和模型 API 凭据保留在节点受保护边界,不进入语言记录或执行请求。
## 京东节点落地顺序

View file

@ -67,6 +67,16 @@ pub struct ExecutionPolicy {
pub allowed_services: Vec<String>,
pub allow_status: bool,
pub allow_restart: bool,
#[serde(default)]
pub mutation_grants: Vec<MutationGrant>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct MutationGrant {
pub authorization_id: String,
pub allowed_action: ActionKind,
pub resource: String,
pub rollback_checkpoint_id: String,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
@ -101,6 +111,8 @@ pub struct ExecutionReceipt {
pub stdout: String,
pub stderr: String,
pub rollback_checkpoint_id: Option<String>,
pub rollback_attempted: bool,
pub rollback_succeeded: Option<bool>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -207,6 +219,7 @@ pub fn compile_request(
"service restart is disabled by policy",
)?;
validate_mutation_references(request)?;
validate_mutation_grant(request, policy)?;
(
vec!["restart".to_owned(), request.action.resource.clone()],
true,
@ -268,6 +281,29 @@ fn validate_mutation_references(request: &ExecutionRequest) -> Result<(), Bridge
)
}
fn validate_mutation_grant(
request: &ExecutionRequest,
policy: &ExecutionPolicy,
) -> Result<(), BridgeError> {
let authorization = request
.authorization
.as_ref()
.ok_or_else(|| BridgeError::new("mutating action requires authorization"))?;
let rollback = request
.rollback
.as_ref()
.ok_or_else(|| BridgeError::new("mutating action requires rollback reference"))?;
require(
policy.mutation_grants.iter().any(|grant| {
grant.authorization_id == authorization.authorization_id
&& grant.allowed_action == request.action.kind
&& grant.resource == request.action.resource
&& grant.rollback_checkpoint_id == rollback.checkpoint_id
}),
"mutation authorization is not granted by the execution policy",
)
}
pub fn execute_plan(
plan: &ExecutionPlan,
executor: &dyn CommandExecutor,
@ -288,27 +324,80 @@ pub fn execute_plan(
"compiled plan is not an allowlisted Linux systemd adapter",
)?;
if plan.action.kind == ActionKind::ServiceRestart {
let precheck_arguments = vec!["is-active".to_owned(), plan.action.resource.clone()];
let precheck = executor
.execute(&plan.program, &precheck_arguments)
.map_err(BridgeError::new)?;
require(
precheck.exit_code == Some(0) && precheck.stdout.trim() == "active",
"restart precondition failed: service was not active",
)?;
}
let result = executor
.execute(&plan.program, &plan.arguments)
.map_err(BridgeError::new)?;
let command_succeeded = result.exit_code == Some(0);
let (target_state_verified, final_state, stdout, stderr, exit_code) = if plan.action.kind
== ActionKind::ServiceRestart
&& command_succeeded
{
let (
target_state_verified,
final_state,
stdout,
stderr,
exit_code,
rollback_attempted,
rollback_succeeded,
) = if plan.action.kind == ActionKind::ServiceRestart {
let verification_arguments = vec!["is-active".to_owned(), plan.action.resource.clone()];
let verification = executor
.execute(&plan.program, &verification_arguments)
.map_err(BridgeError::new)?;
let verified = verification.exit_code == Some(0) && verification.stdout.trim() == "active";
(
verified,
if verified { "PASS_100" } else { "FAIL_0" }.to_owned(),
join_observations(&result.stdout, &verification.stdout),
join_observations(&result.stderr, &verification.stderr),
verification.exit_code,
)
let verified = command_succeeded
&& verification.exit_code == Some(0)
&& verification.stdout.trim() == "active";
if verified {
(
true,
"PASS_100".to_owned(),
join_observations(&result.stdout, &verification.stdout),
join_observations(&result.stderr, &verification.stderr),
verification.exit_code,
false,
None,
)
} else {
let reset_arguments = vec!["reset-failed".to_owned(), plan.action.resource.clone()];
let reset = executor
.execute(&plan.program, &reset_arguments)
.map_err(BridgeError::new)?;
let start_arguments = vec!["start".to_owned(), plan.action.resource.clone()];
let start = executor
.execute(&plan.program, &start_arguments)
.map_err(BridgeError::new)?;
let recovery_verification = executor
.execute(&plan.program, &verification_arguments)
.map_err(BridgeError::new)?;
let recovered = reset.exit_code == Some(0)
&& start.exit_code == Some(0)
&& recovery_verification.exit_code == Some(0)
&& recovery_verification.stdout.trim() == "active";
(
false,
"FAIL_0".to_owned(),
join_observations(
&join_observations(&result.stdout, &verification.stdout),
&join_observations(&start.stdout, &recovery_verification.stdout),
),
join_observations(
&join_observations(&result.stderr, &verification.stderr),
&join_observations(&reset.stderr, &start.stderr),
),
verification.exit_code,
true,
Some(recovered),
)
}
} else {
let verified = command_succeeded
&& (plan.action.kind != ActionKind::ServiceStatus || result.stdout.trim() == "active");
@ -318,6 +407,8 @@ pub fn execute_plan(
result.stdout,
result.stderr,
result.exit_code,
false,
None,
)
};
@ -336,6 +427,8 @@ pub fn execute_plan(
stdout,
stderr,
rollback_checkpoint_id: plan.rollback_checkpoint_id.clone(),
rollback_attempted,
rollback_succeeded,
})
}

View file

@ -33,6 +33,12 @@ fn policy() -> ExecutionPolicy {
allowed_services: vec!["guanghu-broadcast-tower.service".to_owned()],
allow_status: true,
allow_restart: true,
mutation_grants: vec![guanghu_execution_bridge::MutationGrant {
authorization_id: "AUTH-001".to_owned(),
allowed_action: ActionKind::ServiceRestart,
resource: "guanghu-broadcast-tower.service".to_owned(),
rollback_checkpoint_id: "CHECKPOINT-001".to_owned(),
}],
}
}
@ -95,6 +101,23 @@ fn restart_requires_matching_authorization_and_rollback() {
);
}
#[test]
fn restart_rejects_an_authorization_not_bound_by_policy() {
let mut request = request(ActionKind::ServiceRestart);
request.authorization = Some(AuthorizationReference {
authorization_id: "AUTH-INVENTED".to_owned(),
allowed_action: ActionKind::ServiceRestart,
target_node_id: "JD-FD-PRIMARY".to_owned(),
});
request.rollback = Some(RollbackReference {
checkpoint_id: "CHECKPOINT-001".to_owned(),
recovery_action: "restore previous active service state".to_owned(),
});
let error = compile_request(&request, &policy()).expect_err("invented grant must fail");
assert!(error.to_string().contains("not granted"));
}
struct FakeExecutor {
results: RefCell<VecDeque<CommandResult>>,
calls: RefCell<Vec<Vec<String>>>,
@ -135,6 +158,11 @@ fn restart_receipt_requires_target_side_active_readback() {
});
let plan = compile_request(&request, &policy()).expect("compile restart");
let executor = FakeExecutor::new(vec![
CommandResult {
exit_code: Some(0),
stdout: "active".to_owned(),
stderr: String::new(),
},
CommandResult {
exit_code: Some(0),
stdout: String::new(),
@ -150,8 +178,10 @@ fn restart_receipt_requires_target_side_active_readback() {
let receipt = execute_plan(&plan, &executor).expect("execute plan");
assert_eq!(receipt.final_state, "PASS_100");
assert!(receipt.target_state_verified);
assert_eq!(executor.calls.borrow().len(), 2);
assert_eq!(executor.calls.borrow()[1][1], "is-active");
assert!(!receipt.rollback_attempted);
assert_eq!(executor.calls.borrow().len(), 3);
assert_eq!(executor.calls.borrow()[0][1], "is-active");
assert_eq!(executor.calls.borrow()[2][1], "is-active");
}
#[test]
@ -168,6 +198,11 @@ fn successful_restart_command_without_active_readback_is_fail_zero() {
});
let plan = compile_request(&request, &policy()).expect("compile restart");
let executor = FakeExecutor::new(vec![
CommandResult {
exit_code: Some(0),
stdout: "active".to_owned(),
stderr: String::new(),
},
CommandResult {
exit_code: Some(0),
stdout: String::new(),
@ -178,11 +213,28 @@ fn successful_restart_command_without_active_readback_is_fail_zero() {
stdout: "inactive".to_owned(),
stderr: String::new(),
},
CommandResult {
exit_code: Some(0),
stdout: String::new(),
stderr: String::new(),
},
CommandResult {
exit_code: Some(0),
stdout: String::new(),
stderr: String::new(),
},
CommandResult {
exit_code: Some(0),
stdout: "active".to_owned(),
stderr: String::new(),
},
]);
let receipt = execute_plan(&plan, &executor).expect("execute plan");
assert_eq!(receipt.final_state, "FAIL_0");
assert!(!receipt.target_state_verified);
assert!(receipt.rollback_attempted);
assert_eq!(receipt.rollback_succeeded, Some(true));
}
#[test]