feat: certify chat operator turns through loop-run
This commit is contained in:
@@ -56,12 +56,12 @@ Approval inbox / HITL:
|
||||
- `POST /api/v1/approvals/decide` — approve/reject with SoD and reason; approved
|
||||
settings proposals apply through `control-plane-settings.py`.
|
||||
|
||||
Governed Chat (Plan-18 MVP-0/1 + MVP-2 Track 4 foundation):
|
||||
Governed Chat (Plan-18 MVP-0/1 + MVP-2 Track 4 and Operator Track 5/6):
|
||||
|
||||
- `POST /api/v1/chat/ask` — Ask CASAN endpoint. The API only wraps harness
|
||||
`chat-turn.py`; router verdicts, H4 input/output scan, action-gate decisions,
|
||||
H5 chat audit, H6 token metrics, evidence source selection, and operator action
|
||||
execution remain harness-owned.
|
||||
Plan-17 loop-run certification, H5 chat audit, H6 token metrics, evidence source
|
||||
selection, and operator action execution remain harness-owned.
|
||||
- `GET /api/v1/chat/actions` — list registered operator actions from
|
||||
`operator-actions.yaml`; no free-command execution is exposed.
|
||||
- `GET /api/v1/chat/agents` — list governed agents from `agent-registry.yaml`
|
||||
@@ -69,9 +69,10 @@ Governed Chat (Plan-18 MVP-0/1 + MVP-2 Track 4 foundation):
|
||||
`chat-agent-resolver.py`.
|
||||
- `GET /api/v1/chat/audit/verify` — verifies the chat audit hash chain.
|
||||
- `/chat` UI shows actor/role scope, `mode/risk/decision` badges, certified answer,
|
||||
evidence sources, registered operator actions, agent binding, action-gate status,
|
||||
router details, and audit hash. Side-effect requests outside registered actions
|
||||
return governed `BLOCK` or `NOT_SUPPORTED` responses.
|
||||
evidence sources, registered operator actions, agent binding, loop certification,
|
||||
action-gate status, router details, and audit hash. Side-effect requests outside
|
||||
registered actions return governed `BLOCK` or `NOT_SUPPORTED` responses; operator
|
||||
side effects are held until the loop draft is certified.
|
||||
|
||||
FinOps/SLO:
|
||||
|
||||
|
||||
@@ -85,6 +85,10 @@ test('chat ask executes registered operator action through action-gate', () => {
|
||||
assert.equal(res.action_gate.outcome, 'ALLOW');
|
||||
assert.equal(res.agent_binding.agent_selected, 'ops-operator');
|
||||
assert.equal(res.agent_binding.skill_selected, 'registered-actions');
|
||||
assert.equal(res.loop_run.draft_certified, true);
|
||||
assert.equal(res.loop_run.side_effect_released, true);
|
||||
assert.equal(res.loop_run.trace_verify.ok, true);
|
||||
assert.equal(res.loop_run.replay.ok, true);
|
||||
assert.equal(res.audit_verify.ok, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -121,6 +121,19 @@ export interface ChatAnswer {
|
||||
requires_approval: boolean;
|
||||
audit?: { record_hash?: string; head?: string; seq?: number };
|
||||
};
|
||||
loop_run?: {
|
||||
success: boolean;
|
||||
decision: string;
|
||||
reason: string;
|
||||
run_id: string;
|
||||
draft_ref?: string;
|
||||
draft_certified?: boolean;
|
||||
side_effect_released?: boolean;
|
||||
artifact?: string;
|
||||
success_criteria?: string;
|
||||
trace_verify?: { ok: boolean; output: string };
|
||||
replay?: { ok: boolean; output: string };
|
||||
};
|
||||
actor: SettingsActor;
|
||||
}
|
||||
|
||||
|
||||
@@ -173,6 +173,7 @@ export function Chat() {
|
||||
<StatusBadge value={last.decision} />
|
||||
{last.agent_binding && <StatusBadge value={last.agent_binding.agent_selected} />}
|
||||
{last.agent_binding && <StatusBadge value={`L${last.agent_binding.delegation_level}`} />}
|
||||
{last.loop_run && <StatusBadge value={last.loop_run.draft_certified ? 'loop certified' : 'loop held'} />}
|
||||
<StatusBadge value={last.audit_verify.ok ? 'audit ok' : 'audit fail'} />
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap text-sm leading-6 text-gray-800">{last.answer}</div>
|
||||
@@ -234,6 +235,18 @@ export function Chat() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{last.loop_run && (
|
||||
<div className="rounded border border-gray-200 p-3">
|
||||
<div className="text-xs font-semibold uppercase text-gray-400">Loop run</div>
|
||||
<div className="mt-1 font-medium text-gray-800 break-all">{last.loop_run.run_id}</div>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
<StatusBadge value={last.loop_run.decision} />
|
||||
<StatusBadge value={last.loop_run.draft_certified ? 'draft certified' : 'draft held'} />
|
||||
<StatusBadge value={last.loop_run.side_effect_released ? 'released' : 'held'} />
|
||||
<StatusBadge value={last.loop_run.replay?.ok ? 'replay ok' : 'replay pending'} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="text-xs font-semibold uppercase text-gray-400">Matched rules</div>
|
||||
<div className="mt-1 flex flex-wrap gap-2">
|
||||
|
||||
Reference in New Issue
Block a user