Ask approval for Admiral expanded scope
This commit is contained in:
1 parent
f2ec1e9437
commit
d500a29186
3 files changed
+35
-4
No files matched your search
@@ -25169,6 +25169,19 @@ Be specific and actionable. Format as structured JSON.`;
|
||||
throw new Error('Approval required for ' + tgt + ' — choose Autonomous mode, or approve in the receipts panel.');
|
||||
}
|
||||
}
|
||||
if (res.status === 403 && Array.isArray(d.approvals) && d.approvals.length) {
|
||||
const approvalIds = d.approvals.map(function(a){ return a && a.id; }).filter(Boolean);
|
||||
const targets = (d.outOfScopeTargets || []).join(', ');
|
||||
if (isLab || autonomous) {
|
||||
await Promise.all(approvalIds.map(function(id){
|
||||
return fetch('/api/approvals/' + id + '/approve', { method:'POST', headers:{'Content-Type':'application/json'}, body:'{}' });
|
||||
}));
|
||||
res = await fetch('/api/admiral/launch', launchOpts({ approvalIds: approvalIds }));
|
||||
d = await res.json();
|
||||
} else {
|
||||
throw new Error('Expanded target approval required for ' + (targets || 'planner-added targets') + ' — approve the receipts or choose Autonomous mode.');
|
||||
}
|
||||
}
|
||||
if (res.status === 409 && d.review && d.review.status === 'hold') throw new Error('Op Admiral plan gate is HOLD — ' + ((d.review && d.review.reason) || 'manual review required before execution'));
|
||||
if (!res.ok || d.error) throw new Error(d.error || ('launch failed (' + res.status + ')'));
|
||||
const nOps = (d.operators || []).length;
|
||||
|
||||
@@ -33,9 +33,10 @@ describe('local API authorization hardening invariants', () => {
|
||||
it('Admiral live launch re-checks every General-produced execution target before mission bring-up', () => {
|
||||
const route = routeBlock("app.post('/api/admiral/launch'", '// =============================================================================\n// BOUNTY PLATFORM INTEGRATIONS');
|
||||
|
||||
expect(route).toMatch(/ensureExecTargetsWithinApprovedTarget\(execConfig\.targets, brief\.target\)/);
|
||||
expect(route).toMatch(/ensureExecTargetsAuthorized\(execConfig\.targets,\s*brief\.target,\s*req\.body/);
|
||||
expect(route).toMatch(/outOfScopeTargets\.length/);
|
||||
expect(route.indexOf('ensureExecTargetsWithinApprovedTarget(execConfig.targets, brief.target)'))
|
||||
expect(route.indexOf('ensureExecTargetsAuthorized(execConfig.targets, brief.target'))
|
||||
.toBeLessThan(route.indexOf('bringUpMissionFromPlan(execConfig, generalConfig)'));
|
||||
expect(route).toMatch(/approvals/);
|
||||
});
|
||||
});
|
||||
+19
-2
@@ -1104,6 +1104,15 @@ function ensureExecTargetsWithinApprovedTarget(targets: string[], approvedTarget
|
||||
.filter(target => hostFromTarget(target) !== approvedHost);
|
||||
}
|
||||
|
||||
function ensureExecTargetsAuthorized(
|
||||
targets: string[],
|
||||
approvedTarget: string,
|
||||
body: Record<string, unknown>,
|
||||
): string[] {
|
||||
return ensureExecTargetsWithinApprovedTarget(targets, approvedTarget)
|
||||
.filter(target => !findApproval(body, 'autonomous_execution', target));
|
||||
}
|
||||
|
||||
function approvalMatchesGateScope(approval: ApprovalRequest, action: GuardAction, operationId: string, target: string): boolean {
|
||||
if (!approvalMatches(approval, action, target)) return false;
|
||||
if (approval.operationId && operationId && approval.operationId !== operationId) return false;
|
||||
@@ -7326,12 +7335,20 @@ app.post('/api/admiral/launch', async (req: Request, res: Response): Promise<voi
|
||||
res.status(409).json({ error: 'General plan gate is HOLD', mode: 'live', plan, review: execConfig.review });
|
||||
return;
|
||||
}
|
||||
const outOfScopeTargets = ensureExecTargetsWithinApprovedTarget(execConfig.targets, brief.target);
|
||||
const outOfScopeTargets = ensureExecTargetsAuthorized(execConfig.targets, brief.target, req.body as Record<string, unknown>);
|
||||
if (outOfScopeTargets.length) {
|
||||
const approvals = outOfScopeTargets.map(target => createApprovalRequest(
|
||||
'autonomous_execution',
|
||||
target,
|
||||
`Admiral plan expanded execution scope from ${brief.target} to ${target}`,
|
||||
req.body as Record<string, unknown>
|
||||
));
|
||||
res.status(403).json({
|
||||
error: 'Admiral LIVE plan contains targets outside the approved brief target',
|
||||
error: 'Admiral LIVE plan needs approval for expanded target scope',
|
||||
approvedTarget: brief.target,
|
||||
outOfScopeTargets,
|
||||
approvals,
|
||||
next: 'Approve the expanded target receipts, then retry /api/admiral/launch with approvalIds.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user