From ecb1dba9ffe85ae25f63fb12853a83575c6348bf Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 14 Jul 2026 17:55:27 +0800 Subject: [PATCH] chore(openspec): archive host-agent-console-task-submission --- .../.openspec.yaml | 2 + .../design.md | 140 ++++++++++++++++++ .../proposal.md | 40 +++++ .../spec.md | 75 ++++++++++ .../tasks.md | 25 ++++ .../spec.md | 79 ++++++++++ 6 files changed, 361 insertions(+) create mode 100644 openspec/changes/archive/2026-07-14-host-agent-console-task-submission/.openspec.yaml create mode 100644 openspec/changes/archive/2026-07-14-host-agent-console-task-submission/design.md create mode 100644 openspec/changes/archive/2026-07-14-host-agent-console-task-submission/proposal.md create mode 100644 openspec/changes/archive/2026-07-14-host-agent-console-task-submission/specs/host-agent-console-task-submission/spec.md create mode 100644 openspec/changes/archive/2026-07-14-host-agent-console-task-submission/tasks.md create mode 100644 openspec/specs/host-agent-console-task-submission/spec.md diff --git a/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/.openspec.yaml b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/.openspec.yaml new file mode 100644 index 0000000..64105fc --- /dev/null +++ b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-14 diff --git a/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/design.md b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/design.md new file mode 100644 index 0000000..35b2cea --- /dev/null +++ b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/design.md @@ -0,0 +1,140 @@ +## Context + +The Host Agent runs a loopback-only-by-default, server-rendered local Console +with local-account session and CSRF protection. Its Tasks page currently lists +Runtime task metadata after local execution; it has no control for creating a +Cloud scheduled task. + +The active cloud-console-governance change already supplies the required +outbound protocol: HostAgentClient.submit_self_task() posts a goal and an +optional Cloud device ID to the authenticated Host's internal endpoint. The +Cloud service derives the Host target from credentials and enforces ownership, +self-submission policy, and scheduler limits. This change only makes that +existing narrow capability available through the local Console. + +create_application() owns one asynchronous HostAgentClient shared by the +heartbeat, claim, lease, and result paths, and closes it after the embedded +Console exits. In enrollment-managed mode, the running DeviceManager is +registered with Cloud-generated device IDs, while DeviceConfigStore retains +the local-to-Cloud mapping. The Console must therefore select from the former, +not the latter. + +## Goals / Non-Goals + +**Goals:** + +- Let a logged-in local operator enqueue a goal task for the current Host, + optionally naming one currently running local device. +- Preserve Cloud-side Host isolation and policy enforcement as the authority. +- Give the operator unambiguous confirmation only after a task ID is returned, + and a safe outcome-unknown message when delivery cannot be confirmed. +- Audit confirmed local submissions without persisting task goals or secrets. + +**Non-Goals:** + +- Add a Cloud public API, public task scope, cross-Host target, workflow + submission, inbound Cloud connection, task cancellation, or a Cloud queue + status browser to the local Console. +- Reuse TaskMetadataStore as a scheduled-task ledger. It represents local + Runtime task IDs and requires a concrete local device, so inserting a queued + Cloud task there would conflate two distinct lifecycles. +- Provide exactly-once delivery across a client crash or a response loss. The + existing internal protocol has no durable idempotency key; this change avoids + automatic duplication rather than making an unsupported guarantee. + +## Decisions + +### D1: Reuse the existing Host self-submission client and its lifecycle + +create_console_app() will receive the already-owned asynchronous +HostAgentClient (or a narrow injectable submission protocol for tests), and +create_application() will pass its existing instance. The Console will not +construct a Cloud SDK client, duplicate credentials, or close the shared +client. The embedded Uvicorn server runs alongside the Host Agent's normal +async work, so the one client can serve all outbound operations under the +application's established lifecycle. + +Using the public Cloud SDK or direct HTTP from the template handler was +rejected because it would require a human submission scope or duplicate the +Host credential path. Creating a second Host client was rejected because it +creates a second ownership and shutdown boundary for the same bearer token. + +### D2: Put a narrow form on the existing Tasks page + +The Tasks page will render a goal textarea, an automatic-device option, and a +select list built from a fresh DeviceManager.list_devices() snapshot. The POST +handler will trim and validate the goal, revalidate an explicit device against +a new snapshot, and call submit_self_task(goal=..., device_id=...). It will +not accept a Host identifier, workflow identifier, arbitrary device +identifier, or arbitrary scheduling constraints from the browser. + +The form uses the existing local session dependency and CSRF dependency. The +cached Host policy remains display-only: disabling a form based on stale local +policy would falsely deny a newly enabled Host, while the Cloud endpoint is +already authoritative for both policy and device ownership. + +Adding a standalone browser API or a general JSON task endpoint was rejected: +the Console's existing mutation pattern is server-rendered form POSTs, and a +new API would broaden the local attack surface without a client need. + +### D3: Use post-redirect-get and bounded local audit entries + +On confirmed success, the handler records a task_submission history event +with the Cloud task ID and optional target device ID, then redirects back to +the Tasks page with a success indicator that contains only the task ID. The +history record deliberately excludes the goal because goals can contain +sensitive operational context. A local audit-write failure is best-effort and +must not turn a Cloud-confirmed submission into a retryable failure. + +The existing TaskMetadataStore is not used for this event: its rows model the +separate local Runtime task created after assignment execution and cannot +represent an automatic-device Cloud queue entry safely. Rendering success in +place was rejected because browser refresh could repeat the POST. + +### D4: Submit task creation once and surface uncertain outcomes + +HostAgentClient.submit_self_task() will opt out of the generic retry loop used +by idempotent or recoverable Host protocol operations. A transport failure, +5xx response, or invalid success payload after the first request has an +unknown Cloud outcome, so the Console will render an explicit message and +will not issue another request. Definitive 4xx rejections remain safe to show +as rejected submissions. + +Adding a new durable Cloud idempotency-key table was rejected for this focused +Console change because it would alter the active cloud-console-governance +protocol and migration surface. The at-most-once client behavior avoids the +known automatic-duplicate failure mode while leaving a future protocol-level +exactly-once design possible. + +## Risks / Trade-offs + +- [Cloud accepts a task but its response is lost] -> Report an unknown outcome + and make exactly one request; the operator must verify the queue before + submitting again. +- [A selected device is removed between page render and POST] -> Revalidate + against the current DeviceManager snapshot before the outbound request; the + Cloud also remains the final ownership validator. +- [Cloud policy changes after the local page renders] -> Do not use the cache + as authorization; display the Cloud's definitive rejection safely. +- [Task goals contain sensitive text] -> Keep the goal out of redirects, + local history, and error messages, and rely on Jinja autoescaping for every + rendered value. +- [Existing backend change is not yet archived] -> Keep this change limited to + the local Console and reconcile its dependency before archive or rollout. + +## Migration Plan + +1. Deploy the Cloud self-submission endpoint from cloud-console-governance + before deploying this Host Agent version. +2. Deploy the compatible Host Agent; no database migration or new environment + variable is required, and the Console remains loopback-only by default. +3. Verify one automatic-device and one explicit-device submission while the + Host policy permits self-submission, then verify a policy-disabled rejection. +4. Roll back by deploying the prior Host Agent version; queued tasks already + accepted by Cloud are retained and continue through the normal scheduler. + +## Open Questions + +- A future Cloud protocol change can add durable idempotency keys and a + Host-scoped task-status query if operators need exactly-once submission and + queue tracking from the local Console. diff --git a/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/proposal.md b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/proposal.md new file mode 100644 index 0000000..bbcae9e --- /dev/null +++ b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/proposal.md @@ -0,0 +1,40 @@ +## Why + +Host Agent 已经能够通过受限的内部协议为自身提交目标任务,但本地 +Console 只能查看已经在本机 Runtime 中留下记录的任务,操作员仍需借助 +Cloud Console 或 SDK 才能发起工作。这使最接近设备的运维入口无法完成 +最基本的“选择本机设备并下发目标”的闭环。 + +## What Changes + +- 在已登录的 Host Agent 本地 Console 的 Tasks 页面提供目标任务提交表单。 +- 支持选择“由本 Host 自动选择设备”或一个当前注册的本地设备;提交始终 + 经既有 Host 自限定内部接口进入 Cloud 队列。 +- 将成功创建的 Cloud task id 和可安全展示的失败原因反馈给本地操作员, + 保持现有任务历史和执行页面的行为不变。 +- 对非幂等的任务创建请求取消自动重试;网络结果不确定时明确提示操作员先 + 查询 Cloud 状态,避免一次点击被客户端重复排队。 +- 复用现有本地账号、cookie session、CSRF 防护和默认 loopback Console + 绑定;不新增 Cloud 公共任务提交权限、跨 Host 目标或入站 Cloud 连接。 + +## Capabilities + +### New Capabilities + +- `host-agent-console-task-submission`: 允许经过本地 Console 身份验证的 + 操作员向当前 Host 的 Cloud 队列提交目标任务,并安全反馈提交结果。 + +### Modified Capabilities + + + +## Impact + +- `apps/device-host-agent/host_agent/web/` 与 `host_agent/client.py`: Tasks + 页面、注入的 Host Agent 客户端依赖,以及安全的一次性提交语义。 +- `apps/device-host-agent/host_agent/app.py`: 将已拥有的异步 Host 客户端 + 交给嵌入式 Console,生命周期仍由 Host Agent 统一管理。 +- `apps/device-host-agent/tests/`: 覆盖登录、CSRF、目标选择、成功反馈、 + Cloud 拒绝和传输失败。 +- 依赖现有 `cloud-console-governance` 中的 Host 自限定任务提交接口;本 + 变更不修改 Cloud API、调度器、数据库或公开 SDK。 diff --git a/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/specs/host-agent-console-task-submission/spec.md b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/specs/host-agent-console-task-submission/spec.md new file mode 100644 index 0000000..b30c2b5 --- /dev/null +++ b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/specs/host-agent-console-task-submission/spec.md @@ -0,0 +1,75 @@ +## ADDED Requirements + +### Requirement: Authenticated local Console submits a Host-scoped goal task +The Host Agent local Console SHALL provide a task-submission form on its +authenticated Tasks page. The form SHALL submit a non-empty goal through the +existing Host Agent self-submission client operation, which remains scoped to +the currently authenticated Host at the Cloud boundary. + +The form SHALL offer an automatic-device option and the IDs of devices that +are currently registered in the running DeviceManager. An automatic-device +submission SHALL omit the device target so that the Cloud scheduler selects an +eligible device owned by the Host. An explicit submission SHALL pass the +selected runtime device ID unchanged; in enrollment-managed deployments this +is the Cloud device ID, not the local configuration ID. + +#### Scenario: Operator submits a task with automatic device selection +- **WHEN** an authenticated local Console operator submits a non-empty goal + and leaves the device selection on automatic +- **THEN** the Console invokes the existing Host self-submission operation + without a device target and the resulting task remains constrained to that + Host + +#### Scenario: Operator submits a task for a listed device +- **WHEN** an authenticated local Console operator selects a currently running + device and submits a non-empty goal +- **THEN** the Console passes that runtime device ID to the Host + self-submission operation without accepting a Host ID or workflow reference + from the form + +### Requirement: Local task submission is protected and locally validated +The Console SHALL require its existing valid local session and CSRF token for +every task-submission POST. It SHALL reject an empty goal or a selected device +that is no longer present in the current DeviceManager snapshot before calling +the Cloud client. + +#### Scenario: Unauthenticated or CSRF-invalid request is rejected +- **WHEN** a task-submission POST has no valid local session or CSRF token +- **THEN** the Console rejects the request and does not invoke the Host + self-submission client + +#### Scenario: Stale device selection is rejected locally +- **WHEN** an operator submits a device ID that is absent from the current + DeviceManager snapshot +- **THEN** the Console returns a validation error and does not enqueue a task + +### Requirement: Console acknowledges a confirmed submission safely +After the Host client confirms task creation, the Console SHALL use a +post-redirect-get response to show the returned Cloud task ID and SHALL write +a bounded local history entry containing the task ID and optional target +device ID. The history entry SHALL NOT persist the task goal, Host credential, +cookie, or lease secret. + +#### Scenario: Confirmed task creation is acknowledged +- **WHEN** the Host self-submission operation returns a task ID +- **THEN** the Console redirects to the Tasks page with a visible confirmation + and records a non-secret local submission audit entry + +#### Scenario: Control plane rejects submission +- **WHEN** the Host self-submission operation returns a definitive rejection + such as a disabled self-submission policy or invalid target +- **THEN** the Console renders a safe error to the operator and does not report + a task ID or record a successful submission audit entry + +### Requirement: Non-idempotent task creation is attempted at most once +The Host Agent client SHALL not automatically retry a Host self-submission +request after a transport failure, server error, or malformed success response. +The Console SHALL report that such a submission has an unknown outcome and +SHALL NOT claim that no task was created. + +#### Scenario: Transport outcome is uncertain +- **WHEN** the task-submission request loses its response or receives a server + failure after the request may have reached the control plane +- **THEN** the client makes no second creation request and the Console informs + the operator that the task may have been queued and must be checked before + submitting again diff --git a/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/tasks.md b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/tasks.md new file mode 100644 index 0000000..151a52a --- /dev/null +++ b/openspec/changes/archive/2026-07-14-host-agent-console-task-submission/tasks.md @@ -0,0 +1,25 @@ +## 1. Host submission safety and local audit + +- [x] 1.1 Reconcile the implemented Host self-submission endpoint/client contract from cloud-console-governance and make HostAgentClient.submit_self_task perform one creation attempt only, with a distinguishable outcome-unknown failure path for transport, 5xx, or malformed-success cases. +- [x] 1.2 Add a bounded ConsoleHistoryStore task-submission event that records only the Cloud task ID and optional target device ID; keep goal text, credentials, cookies, and lease data out of its summary and detail payload. +- [x] 1.3 Add focused HostAgentClient and history-store tests for one-attempt behavior, definitive rejection behavior, unknown outcomes, bounded retention, and audit redaction. + +## 2. Local Console task submission + +- [x] 2.1 Extend create_console_app with an injectable Host self-submission dependency and wire the existing application-owned HostAgentClient into the embedded Console without adding a second client lifecycle. +- [x] 2.2 Extend the Tasks page context and Jinja template with a goal form, automatic-device option, and a device selector populated from the running DeviceManager runtime IDs; preserve the existing local Runtime task list. +- [x] 2.3 Implement the CSRF-protected task-submission POST handler: validate a trimmed goal and fresh device snapshot, invoke the narrow client operation, record a confirmed submission, and use post-redirect-get for task-ID confirmation. +- [x] 2.4 Render safe, autoescaped errors for local validation, definitive Cloud rejection, unavailable client, and outcome-unknown submission without leaking goal text or secrets or recording a false success. + +## 3. Console and integration tests + +- [x] 3.1 Add Console route tests for authenticated automatic and explicit-device submission, including Cloud runtime-ID mapping and successful task-ID confirmation. +- [x] 3.2 Add negative-path tests proving unauthenticated/CSRF-invalid, blank-goal, and stale-device requests never invoke the Host client. +- [x] 3.3 Add tests for Cloud policy/ownership rejection, transport-uncertain response, absent client, best-effort audit failure, and no successful audit entry on failed submission. +- [x] 3.4 Extend Jinja template tests with task goal, device label, task ID, and error XSS probes to preserve autoescape guarantees. + +## 4. Documentation and verification + +- [x] 4.1 Document the local Console task form, automatic versus explicit device behavior, Host self-submission policy prerequisite, Cloud queue semantics, and the outcome-unknown operator procedure. +- [x] 4.2 Run the targeted Host Agent/client/web/history tests, Ruff, compile checks, and strict OpenSpec validation; resolve any regressions. +- [x] 4.3 Manually verify a loopback Console against a compatible Cloud control plane for automatic-device success, explicit-device success, policy-disabled rejection, and lost-response handling. diff --git a/openspec/specs/host-agent-console-task-submission/spec.md b/openspec/specs/host-agent-console-task-submission/spec.md new file mode 100644 index 0000000..3e09ea2 --- /dev/null +++ b/openspec/specs/host-agent-console-task-submission/spec.md @@ -0,0 +1,79 @@ +## Purpose + +Define how an authenticated Host Agent local Console submits Host-scoped goal tasks safely through the existing Cloud control-plane self-submission operation. + +## Requirements + +### Requirement: Authenticated local Console submits a Host-scoped goal task +The Host Agent local Console SHALL provide a task-submission form on its +authenticated Tasks page. The form SHALL submit a non-empty goal through the +existing Host Agent self-submission client operation, which remains scoped to +the currently authenticated Host at the Cloud boundary. + +The form SHALL offer an automatic-device option and the IDs of devices that +are currently registered in the running DeviceManager. An automatic-device +submission SHALL omit the device target so that the Cloud scheduler selects an +eligible device owned by the Host. An explicit submission SHALL pass the +selected runtime device ID unchanged; in enrollment-managed deployments this +is the Cloud device ID, not the local configuration ID. + +#### Scenario: Operator submits a task with automatic device selection +- **WHEN** an authenticated local Console operator submits a non-empty goal + and leaves the device selection on automatic +- **THEN** the Console invokes the existing Host self-submission operation + without a device target and the resulting task remains constrained to that + Host + +#### Scenario: Operator submits a task for a listed device +- **WHEN** an authenticated local Console operator selects a currently running + device and submits a non-empty goal +- **THEN** the Console passes that runtime device ID to the Host + self-submission operation without accepting a Host ID or workflow reference + from the form + +### Requirement: Local task submission is protected and locally validated +The Console SHALL require its existing valid local session and CSRF token for +every task-submission POST. It SHALL reject an empty goal or a selected device +that is no longer present in the current DeviceManager snapshot before calling +the Cloud client. + +#### Scenario: Unauthenticated or CSRF-invalid request is rejected +- **WHEN** a task-submission POST has no valid local session or CSRF token +- **THEN** the Console rejects the request and does not invoke the Host + self-submission client + +#### Scenario: Stale device selection is rejected locally +- **WHEN** an operator submits a device ID that is absent from the current + DeviceManager snapshot +- **THEN** the Console returns a validation error and does not enqueue a task + +### Requirement: Console acknowledges a confirmed submission safely +After the Host client confirms task creation, the Console SHALL use a +post-redirect-get response to show the returned Cloud task ID and SHALL write +a bounded local history entry containing the task ID and optional target +device ID. The history entry SHALL NOT persist the task goal, Host credential, +cookie, or lease secret. + +#### Scenario: Confirmed task creation is acknowledged +- **WHEN** the Host self-submission operation returns a task ID +- **THEN** the Console redirects to the Tasks page with a visible confirmation + and records a non-secret local submission audit entry + +#### Scenario: Control plane rejects submission +- **WHEN** the Host self-submission operation returns a definitive rejection + such as a disabled self-submission policy or invalid target +- **THEN** the Console renders a safe error to the operator and does not report + a task ID or record a successful submission audit entry + +### Requirement: Non-idempotent task creation is attempted at most once +The Host Agent client SHALL not automatically retry a Host self-submission +request after a transport failure, server error, or malformed success response. +The Console SHALL report that such a submission has an unknown outcome and +SHALL NOT claim that no task was created. + +#### Scenario: Transport outcome is uncertain +- **WHEN** the task-submission request loses its response or receives a server + failure after the request may have reached the control plane +- **THEN** the client makes no second creation request and the Console informs + the operator that the task may have been queued and must be checked before + submitting again