This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-07-13
|
||||
@@ -0,0 +1,212 @@
|
||||
## Context
|
||||
|
||||
The Cloud Control Plane already persists scheduled tasks, pooled devices,
|
||||
enrolled Hosts, user accounts, and user sessions. Public task submission has
|
||||
only driver and capability-tag constraints, while the Console is primarily a
|
||||
read dashboard. The Host Agent uses only outbound internal routes and can
|
||||
heartbeat, claim, renew, and report work; it cannot submit a task. No durable
|
||||
policy or AI usage ledger exists.
|
||||
|
||||
`cloud-planner-proxy` is the prerequisite for enforceable AI budgets. It
|
||||
places every planner call from a Host using `AI_PLANNER_TRANSPORT=cloud` behind
|
||||
an authenticated Cloud endpoint and leaves direct provider calls supported.
|
||||
This change is deliberately scheduled after that change: a direct provider
|
||||
call cannot be authoritatively measured or stopped by the Cloud.
|
||||
|
||||
The Cloud Platform remains the durable application layer. Public/internal
|
||||
HTTP adapters, the Vue Console, and the Host Agent are outer adapters; no
|
||||
HTTP, Cloud, or LLM dependency is introduced into `core`, `driver`, `device`,
|
||||
or `tools`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Let authorized Console users create goal or workflow tasks for an explicit
|
||||
Host and optionally an explicit Device, without a fallback target.
|
||||
- Make human task-submission permissions and target restrictions durable,
|
||||
auditable, and API-enforced.
|
||||
- Let an authenticated Host receive its effective versioned policy and create
|
||||
goal tasks that are irrevocably constrained to itself.
|
||||
- Account for provider-reported token use and enforce hard per-Host budgets
|
||||
for Cloud-proxied planner calls, including concurrent requests.
|
||||
- Display effective restrictions, budget status, and non-secret usage in the
|
||||
Console.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Custom roles, tenant isolation, task-level read ACLs, invitations, or an
|
||||
external identity provider. Existing fixed roles remain the coarse access
|
||||
control layer.
|
||||
- General remote Host control, inbound connections, or allowing one Host to
|
||||
enqueue work for another Host.
|
||||
- Charging, invoice generation, provider price catalogues, prompt/screenshot
|
||||
retention, or an attempt to hard-limit direct-to-provider planner calls.
|
||||
- Cloud workflow-definition distribution. Host-self submission is goal-only
|
||||
because a workflow definition is still stored locally on each Host.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: Use explicit target selectors inside task constraints
|
||||
|
||||
`TaskConstraints` gains an optional target object with `host_id` and optional
|
||||
`device_id`. A device target is invalid without its Host; a Host-only target
|
||||
means any eligible device on that Host; an absent target preserves broad pool
|
||||
scheduling. The existing serialized constraints field stores this additive
|
||||
shape, while API status/list responses expose it directly.
|
||||
|
||||
The scheduler applies target filtering before the named assignment strategy,
|
||||
in addition to driver type and capability tags. A missing, stale, busy, or
|
||||
temporarily incompatible selected device leaves the task queued. It never
|
||||
falls back to another target. The submission service validates a named Host
|
||||
and Device against the known pool/enrollment ownership at creation time.
|
||||
|
||||
Using only a globally unique `device_id` was rejected because repository and
|
||||
pool ownership are Host/device pairs; retaining the Host in the target makes
|
||||
authorization and audit unambiguous. Duplicating target columns outside the
|
||||
existing constraints payload was rejected for v1 because matching is already
|
||||
performed after task retrieval and there is no target-indexed query path.
|
||||
|
||||
### D2: Compose fixed role scopes with a separate restrictive user policy
|
||||
|
||||
Keep `viewer`, `operator`, and `admin` roles and their existing scope mapping.
|
||||
Add an optional `UserSubmissionPolicy` keyed by user id with a version,
|
||||
`submission_enabled`, and tri-state Host/Device allow-lists (`null` means
|
||||
unrestricted; an empty stored list means no permitted targets). A restrictive
|
||||
policy requires an explicit target within its allow-list, preventing an
|
||||
un-targeted task from escaping a user's permitted Hosts.
|
||||
|
||||
Public task submission first requires `tasks:submit`, then evaluates the
|
||||
authenticated human principal's policy. The same policy applies to an
|
||||
administrator's ordinary task submission; an administrator can change policy
|
||||
through governance administration but does not silently bypass it. Policies
|
||||
do not change task-read visibility in this increment.
|
||||
|
||||
Storing arbitrary policy JSON was rejected: it makes safe validation,
|
||||
migration, and Console editing ambiguous. Adding a custom role/permission
|
||||
model was rejected because fixed roles plus a narrow restrictive policy cover
|
||||
the requested near-term controls without lockout complexity.
|
||||
|
||||
### D3: Store versioned Host policies and synchronize them in heartbeat responses
|
||||
|
||||
`HostGovernancePolicy` is keyed by Host id and has a monotonic revision. V1
|
||||
contains an optional maximum active-task count and an optional UTC-day token
|
||||
budget. The Cloud scheduler enforces active-task limits; the budget is
|
||||
enforced only by the planner proxy. No policy record means no new limit,
|
||||
preserving existing deployments.
|
||||
|
||||
Heartbeat requests carry the last policy revision applied by the Host. A
|
||||
heartbeat response includes the effective complete policy when the revision is
|
||||
different, otherwise only confirms its current revision. The Host persists a
|
||||
safe cached policy snapshot for local status visibility, but the Cloud remains
|
||||
the authority for task assignment and AI spend. This pull-on-existing-
|
||||
heartbeat design keeps the outbound-only Host model and avoids a parallel
|
||||
long-poll channel.
|
||||
|
||||
Pushing policy through a new inbound Host listener was rejected because it
|
||||
breaks NAT deployments. Requiring a policy fetch before every planner call
|
||||
was rejected because the proxy already performs the authoritative budget check
|
||||
and would add avoidable latency.
|
||||
|
||||
### D4: Meter Cloud-proxy usage with reservation then settlement
|
||||
|
||||
After `cloud-planner-proxy` is complete, provider clients return a
|
||||
non-secret usage object together with the existing tool-call decision. It
|
||||
contains provider/model identity and provider-reported input, output, and
|
||||
total token counts when available; `AIPlanner` continues to consume only the
|
||||
decision.
|
||||
|
||||
Before the proxy invokes a provider for a Host with a token budget, it atomically
|
||||
creates a budget reservation for a conservative per-decision ceiling within the
|
||||
Host's UTC-day bucket. The Cloud configuration also applies a matching
|
||||
provider output ceiling. If used plus reserved tokens would exceed the
|
||||
budget, the endpoint rejects before invoking the provider. On a provider
|
||||
response, the reservation is settled to actual reported usage and an immutable
|
||||
usage event is recorded. If the transport outcome leaves usage unknown, the
|
||||
reservation remains until a bounded expiry cleanup rather than being released
|
||||
optimistically.
|
||||
|
||||
The proxy request carries Host execution context (task id, attempt, and lease
|
||||
when one exists). A Host-Agent-local context adapter supplies that metadata to
|
||||
the cloud client without changing `AIPlanner` or introducing Host imports into
|
||||
`runtime`. Locally initiated/non-assignment calls record Host-level usage with
|
||||
the assignment fields absent.
|
||||
|
||||
Post-hoc usage-only accounting was rejected because concurrent calls can
|
||||
overspend a limit before aggregation. Client-side budget checks were rejected
|
||||
because the Host is not the trust boundary. Direct transport is represented
|
||||
as `unmetered`, not as zero usage or a compliant hard-budget path.
|
||||
|
||||
### D5: Give Hosts a narrow self-submission operation
|
||||
|
||||
Add `POST /internal/v1/hosts/{host_id}/tasks` under existing host-scoped
|
||||
authentication. The route requires the authenticated Host to equal the URL
|
||||
Host, accepts a goal and optional local Device target, and constructs the
|
||||
Cloud-side target itself. A supplied Device must be enrolled/owned by that
|
||||
Host. Host policy can disable self-submission; human user policies do not
|
||||
apply because there is no human principal.
|
||||
|
||||
The endpoint accepts no workflow reference and no arbitrary Host target.
|
||||
Giving an enrolled Host the public `tasks:submit` scope was rejected because
|
||||
it would allow cross-Host task submission and blur service credentials with
|
||||
human authorization.
|
||||
|
||||
### D6: Keep governance administration explicit in the Console and SDK
|
||||
|
||||
Add public, scope-protected governance routes for reading/updating user
|
||||
submission policy, Host policy, and non-secret Host AI usage summaries/events.
|
||||
`governance:read` and `governance:admin` are new scopes; fixed user roles need
|
||||
no mapping change because administrators retain `*`, while scoped bearer
|
||||
automation can receive just these scopes. Console task creation renders only
|
||||
when `tasks:submit` is present. Policy and usage administration render only
|
||||
for `governance:admin`; backend scope and policy checks are authoritative.
|
||||
|
||||
Every policy change records a safe audit event with actor, target, old/new
|
||||
revision, and non-secret values. The Console clears any password inputs from
|
||||
the existing Users flow and never displays provider credentials, prompts,
|
||||
screenshots, cookies, or lease secrets.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Planner proxy is incomplete or a Host remains on direct transport] →
|
||||
Token limits are not activated for that Host; the Console labels it
|
||||
unmetered and the implementation gate prevents treating it as compliant.
|
||||
- [Provider usage is unavailable after a transport failure] → Retain the
|
||||
conservative reservation until expiry; this favors cost safety over
|
||||
temporary under-utilization.
|
||||
- [Policy migration or stale Console state changes a user's permitted target]
|
||||
→ Enforce against current Cloud policy at every submission and return a
|
||||
clear authorization failure; UI state is only advisory.
|
||||
- [Targeted Hosts/devices are offline] → Keep the task queued and visible as
|
||||
targeted rather than rerouting it; operators can edit or cancel only when a
|
||||
later lifecycle capability supplies those operations.
|
||||
- [Policy tables increase schema and operational complexity] → Use one normal
|
||||
forward migration, transactional repository operations, and the existing
|
||||
SQLite/PostgreSQL contract.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Do not start implementation until `cloud-planner-proxy` has completed its
|
||||
tasks, passed strict validation and its runtime tests, and the active
|
||||
Cloud Console user-authentication change has been reconciled and verified.
|
||||
2. Add the forward schema migration for policies, audit records, usage events,
|
||||
and reservations. Existing tasks remain valid because no target and no
|
||||
policy continue to mean unrestricted behavior.
|
||||
3. Deploy the Cloud API with policy/target routes and scheduler checks, then
|
||||
deploy compatible Hosts. Hosts learn policies on their next heartbeat;
|
||||
older Hosts continue normal assignment but cannot self-submit.
|
||||
4. Enable Cloud planner transport on a pilot Host, configure a budget, and
|
||||
verify proxy reservations and settlement before assigning budgets across
|
||||
the fleet. Direct Hosts remain visibly unmetered.
|
||||
5. Deploy the Console after the API. Rollback removes policy assignments or
|
||||
returns affected Hosts to direct transport only when local provider
|
||||
credentials are available; preserve governance and usage rows for a
|
||||
forward fix rather than destructively downgrading production data.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- V1 uses UTC-day token budgets. A later change can add calendar-month or
|
||||
rolling windows without weakening the reservation/settlement contract.
|
||||
- Token cost is intentionally excluded because provider pricing and cached
|
||||
token semantics vary; usage events retain provider/model fields for a later
|
||||
pricing layer.
|
||||
@@ -0,0 +1,79 @@
|
||||
## Why
|
||||
|
||||
The Cloud Console can currently inspect distributed work but cannot direct
|
||||
that work to a selected Host or Device, govern who may submit it, or explain
|
||||
and control the AI spend generated by each Host. Now that
|
||||
`cloud-planner-proxy` is planned to centralize the LLM call path, the next
|
||||
control-plane increment can make those operational decisions enforceable at
|
||||
the Cloud boundary instead of trusting every edge process to self-govern.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add Console task creation for authorized operators, with explicit target
|
||||
selectors for a Host and optionally one of that Host's Devices. A selected
|
||||
target is a hard scheduling constraint and is never silently rerouted to a
|
||||
different Host or Device.
|
||||
- Add persisted Cloud governance policies: user submission restrictions
|
||||
(submission enablement and allowed Host/Device targets) and versioned
|
||||
per-Host operational/AI-budget policies. The API remains authoritative;
|
||||
Console visibility is not an authorization boundary.
|
||||
- Let a Host retrieve its current policy through the authenticated outbound
|
||||
protocol, with version-aware heartbeat synchronization and a safe cached
|
||||
representation for local visibility.
|
||||
- Add a Host-scoped internal task-submission operation. An authenticated
|
||||
Host can create a task only for itself and, when specified, one of its own
|
||||
enrolled Devices; it never receives general public `tasks:submit` power.
|
||||
- After `cloud-planner-proxy` is implemented and a Host uses its `cloud`
|
||||
planner transport, record provider-reported token usage per Host/task/
|
||||
attempt and enforce configured token budgets with atomic reservation and
|
||||
settlement. Direct-to-provider Hosts remain explicitly unmetered and
|
||||
cannot be represented as hard-budget-enforced.
|
||||
- Extend the Console with task-creation, user-policy, Host-policy, and
|
||||
per-Host AI-usage/budget views. Existing user lifecycle management remains
|
||||
role-gated and gains policy editing rather than a new custom-role system.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `cloud-governance`: Durable, versioned user and Host policies that restrict
|
||||
task submission/targets and define Host operational and AI-budget limits.
|
||||
- `host-scoped-task-submission`: Authenticated internal operation that lets a
|
||||
Host submit tasks constrained to itself.
|
||||
- `cloud-ai-usage-governance`: Durable AI usage metering, budget reservations,
|
||||
settlement, and non-secret reporting for Cloud-proxied planner calls.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `cloud-console-ui`: add governed task creation and operational governance
|
||||
views to the existing Cloud Console experience. This capability is still
|
||||
defined by active Console changes and will be reconciled before archive.
|
||||
- `platform-sdk`: extend public task submission/status and `CloudClient`
|
||||
parity with explicit task targets and safe governance-facing responses.
|
||||
- `task-scheduler`: make explicit Host/Device target selectors part of task
|
||||
constraints and enforce them during matching.
|
||||
- `host-agent-protocol`: return versioned Host policy through the outbound
|
||||
protocol and add Host-self task submission while retaining host binding.
|
||||
- `cloud-planner-proxy`: enforce Host budget reservations and emit
|
||||
provider-reported usage only after the proxy change is implemented. This
|
||||
capability is currently an active, unarchived change.
|
||||
- `agent-runtime`: carry provider-reported non-secret token usage alongside a
|
||||
tool-call decision so the Cloud proxy can meter the call without changing
|
||||
`AIPlanner` decision semantics. This capability is also active and
|
||||
unarchived.
|
||||
|
||||
## Impact
|
||||
|
||||
- `packages/cloud-platform/cloud`: governance and usage domain models,
|
||||
repository port/SQLAlchemy implementation, Alembic migration, scheduler
|
||||
matching, public/internal Pydantic contracts, policy-aware authorization,
|
||||
and proxy metering.
|
||||
- `apps/cloud-api`: router composition and configuration only; all durable
|
||||
policy and accounting behavior remains in the Cloud Platform package.
|
||||
- `apps/device-host-agent`: policy-aware client/heartbeat state and
|
||||
self-targeted task submission; no inbound Cloud connection is introduced.
|
||||
- `cloud-console/`: task composer plus user/Host policy and AI-usage views.
|
||||
- Dependencies and rollout: implementation is blocked until
|
||||
`cloud-planner-proxy` is completed, its migration is deployed, and affected
|
||||
Hosts are using `AI_PLANNER_TRANSPORT=cloud`; it also follows completion of
|
||||
the active Cloud Console user-authentication work.
|
||||
@@ -0,0 +1,50 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Pluggable dual-provider tool-calling abstraction
|
||||
The system SHALL support at least two interchangeable LLM providers
|
||||
(Anthropic native tool use and OpenAI function calling) for the AI Planner's
|
||||
decision calls, selectable via configuration, with both providers constrained
|
||||
to return exactly one tool call per request. Independently of provider
|
||||
selection, the system SHALL support at least two transports for making that
|
||||
decision call -- direct-to-provider (the tool-calling client calls the
|
||||
provider's SDK itself, using locally configured credentials) and cloud-proxy
|
||||
(the tool-calling client calls the Cloud Control Plane's planner-decision
|
||||
endpoint, which calls the provider using cloud-held credentials) -- selectable
|
||||
via configuration without requiring any change to `AIPlanner`'s own decision
|
||||
logic. A provider decision result SHALL also carry available non-secret
|
||||
provider token-usage metadata so outer Cloud adapters can meter calls without
|
||||
changing that decision logic.
|
||||
|
||||
#### Scenario: Provider selected via configuration
|
||||
- **WHEN** the AI Planner is configured with a given provider identifier
|
||||
- **THEN** it constructs and uses the tool-calling client for that provider
|
||||
without requiring any change to `AIPlanner`'s own decision logic
|
||||
|
||||
#### Scenario: Provider response resolves to a single decision
|
||||
- **WHEN** either supported provider returns a response to a tool-calling
|
||||
request
|
||||
- **THEN** the response is parsed into exactly one tool name and one arguments
|
||||
object, regardless of which provider produced it
|
||||
|
||||
#### Scenario: Transport selected via configuration
|
||||
- **WHEN** the Host Agent is configured with a given transport (direct or
|
||||
cloud-proxy)
|
||||
- **THEN** `AIPlanner` is constructed with the tool-calling client for that
|
||||
transport, and its own decision logic is unchanged regardless of which
|
||||
transport is in effect
|
||||
|
||||
#### Scenario: Direct transport remains available and default
|
||||
- **WHEN** no transport is explicitly configured
|
||||
- **THEN** the AI Planner uses the direct-to-provider transport, matching its
|
||||
behavior before the cloud-proxy transport existed
|
||||
|
||||
#### Scenario: Cloud-proxy transport resolves a decision without a local provider client
|
||||
- **WHEN** the Host Agent is configured with the cloud-proxy transport
|
||||
- **THEN** its tool-calling client sends the decision request to the Cloud
|
||||
Control Plane's planner-decision endpoint instead of constructing a local
|
||||
Anthropic or OpenAI SDK client
|
||||
|
||||
#### Scenario: Provider reports token usage
|
||||
- **WHEN** a provider response includes input or output token counts
|
||||
- **THEN** the tool-calling result makes those non-secret counts available to
|
||||
its outer adapter while `AIPlanner` selects the same single decision
|
||||
@@ -0,0 +1,52 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Cloud-proxied planner calls produce durable non-secret usage events
|
||||
After the Cloud planner proxy resolves a provider call, the system SHALL record
|
||||
a durable usage event with Host, provider/model, provider-reported token
|
||||
counts, request outcome, and known task-attempt context, while excluding raw
|
||||
prompts, screenshots, provider credentials, cookies, and lease secrets.
|
||||
|
||||
#### Scenario: Provider returns token usage
|
||||
- **WHEN** a Cloud-proxied planner call completes with provider-reported token
|
||||
counts
|
||||
- **THEN** the system stores those counts against the authenticated Host and
|
||||
returns the planner decision without exposing secret request content
|
||||
|
||||
#### Scenario: Assignment context is available
|
||||
- **WHEN** the Host makes a proxied planner call while executing a claimed
|
||||
assignment
|
||||
- **THEN** the recorded event includes that task id and attempt identifier
|
||||
|
||||
### Requirement: Host token budgets use atomic reservation and settlement
|
||||
The Cloud planner proxy SHALL atomically reserve a conservative per-call token
|
||||
amount before a provider invocation for every Host with an effective AI token
|
||||
budget using Cloud planner transport, SHALL reject over-budget calls before
|
||||
invoking the provider, and SHALL settle the reservation to provider-reported
|
||||
usage when available.
|
||||
|
||||
#### Scenario: Remaining budget permits a call
|
||||
- **WHEN** used tokens plus active reservations and the next conservative
|
||||
reservation are within the Host's UTC-day budget
|
||||
- **THEN** the proxy reserves budget, invokes the provider, and settles the
|
||||
reservation after the provider response
|
||||
|
||||
#### Scenario: Remaining budget is insufficient
|
||||
- **WHEN** the next reservation would exceed the Host's effective budget
|
||||
- **THEN** the proxy rejects the planner call without invoking the provider
|
||||
|
||||
#### Scenario: Provider outcome has unknown usage
|
||||
- **WHEN** a transport failure prevents the proxy from determining actual
|
||||
provider usage after reservation
|
||||
- **THEN** the reservation remains active until bounded expiry cleanup rather
|
||||
than being released optimistically
|
||||
|
||||
### Requirement: Direct planner transport is explicitly unmetered
|
||||
The system SHALL represent a Host using direct-to-provider planner transport as
|
||||
unmetered for Cloud token accounting and SHALL not report it as complying with
|
||||
a Cloud-enforced token budget.
|
||||
|
||||
#### Scenario: Console inspects a direct-transport Host
|
||||
- **WHEN** an authorized operator views AI usage for a Host not using Cloud
|
||||
planner transport
|
||||
- **THEN** the system reports the Host as unmetered rather than zero usage or
|
||||
hard-budget-enforced
|
||||
@@ -0,0 +1,50 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Console composes explicitly targeted tasks
|
||||
The Console SHALL provide a task-creation flow to principals with
|
||||
`tasks:submit`, exposing goal/workflow input, Host selection, Device selection
|
||||
scoped to the selected Host, and visible driver/capability constraints.
|
||||
|
||||
#### Scenario: Operator creates a Device-targeted task
|
||||
- **WHEN** an authorized operator selects a Host and one of its Devices and
|
||||
submits a valid task
|
||||
- **THEN** the Console sends the explicit target to the public task API and
|
||||
displays the resulting queued task with that target
|
||||
|
||||
#### Scenario: Restricted operator has no permitted target
|
||||
- **WHEN** the Cloud API reports that the operator's policy disallows the
|
||||
selected target or targetless submission
|
||||
- **THEN** the Console shows the authorization error and does not imply that
|
||||
the task was accepted or rerouted
|
||||
|
||||
### Requirement: Console administers governance policy within effective scopes
|
||||
The Console SHALL expose user-submission and Host-policy views only to a
|
||||
principal with `governance:admin`, and SHALL display effective non-secret
|
||||
policy revisions and mutations without treating hidden controls as security.
|
||||
|
||||
#### Scenario: Administrator changes a Host budget
|
||||
- **WHEN** a governance administrator saves a valid Host policy containing an
|
||||
AI budget or active-task limit
|
||||
- **THEN** the Console displays the returned newer policy revision and its
|
||||
effective values
|
||||
|
||||
#### Scenario: Non-governance user opens the Console
|
||||
- **WHEN** the current principal lacks `governance:admin`
|
||||
- **THEN** the Console hides governance administration navigation while the
|
||||
backend remains responsible for rejecting unauthorized requests
|
||||
|
||||
### Requirement: Console reports Host AI budget state without sensitive prompts
|
||||
The Console SHALL render per-Host budget state and non-secret token usage for
|
||||
authorized governance users, including an explicit unmetered state for Hosts
|
||||
using direct planner transport.
|
||||
|
||||
#### Scenario: Administrator views a Cloud-proxied Host
|
||||
- **WHEN** a governance administrator selects a Host with recorded proxy usage
|
||||
- **THEN** the Console displays configured budget, used/reserved/remaining
|
||||
tokens, provider/model, and non-secret event metadata
|
||||
|
||||
#### Scenario: Administrator views a direct-transport Host
|
||||
- **WHEN** a governance administrator selects a Host that does not use Cloud
|
||||
planner transport
|
||||
- **THEN** the Console labels the Host unmetered and does not display zero as
|
||||
a budget-compliant usage value
|
||||
@@ -0,0 +1,53 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User task-submission policy is durable and authoritative
|
||||
The system SHALL persist an optional versioned submission policy for each
|
||||
human user, containing submission enablement and explicit Host/Device target
|
||||
allow-lists, and SHALL enforce that policy after scope authentication on every
|
||||
public task-submission request.
|
||||
|
||||
#### Scenario: Restricted user submits to an allowed Device
|
||||
- **WHEN** a principal with `tasks:submit` has a policy allowing a Host/Device
|
||||
target and submits a task for that exact target
|
||||
- **THEN** the system accepts the task subject to normal validation and records
|
||||
the authenticated principal as its submitter
|
||||
|
||||
#### Scenario: Restricted user omits or exceeds a target allow-list
|
||||
- **WHEN** a principal with a restrictive policy submits without a target or
|
||||
names a Host/Device outside its allow-list
|
||||
- **THEN** the system rejects the request before creating a task
|
||||
|
||||
#### Scenario: Submission is disabled
|
||||
- **WHEN** a principal with `tasks:submit` has a policy with submission
|
||||
disabled
|
||||
- **THEN** the system rejects task submission without changing the queue
|
||||
|
||||
### Requirement: Host governance policy is versioned and enforceable
|
||||
The system SHALL persist a monotonic revisioned policy for each Host with an
|
||||
optional active-task limit and optional UTC-day AI token budget, and SHALL
|
||||
enforce active-task limits during Cloud scheduling.
|
||||
|
||||
#### Scenario: Host active-task limit is reached
|
||||
- **WHEN** a Host already has its configured maximum number of assigned or
|
||||
dispatched attempts and another queued task targets that Host
|
||||
- **THEN** the scheduler leaves the later task queued until capacity becomes
|
||||
available
|
||||
|
||||
#### Scenario: Host policy is updated
|
||||
- **WHEN** an authorized governance administrator updates a Host policy
|
||||
- **THEN** the system stores a strictly newer revision and later Cloud
|
||||
authorization, scheduling, and proxy operations use the new policy
|
||||
|
||||
### Requirement: Governance mutations and reads use dedicated scopes
|
||||
The system SHALL protect governance policy and AI-usage operations with
|
||||
`governance:read` or `governance:admin` as appropriate, and SHALL record safe
|
||||
audits for policy mutations.
|
||||
|
||||
#### Scenario: Administrator changes a policy
|
||||
- **WHEN** a principal with `governance:admin` changes a user or Host policy
|
||||
- **THEN** the system records actor, target, old/new revision, timestamp, and
|
||||
non-secret policy metadata without recording credentials or request secrets
|
||||
|
||||
#### Scenario: Non-governance principal attempts a policy mutation
|
||||
- **WHEN** a principal lacking `governance:admin` updates a governance policy
|
||||
- **THEN** the system rejects the request before changing durable policy state
|
||||
@@ -0,0 +1,38 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Planner proxy enforces effective Host token budgets before provider invocation
|
||||
The Cloud planner-decision endpoint SHALL atomically reserve a conservative
|
||||
bounded token amount before invoking its provider for an authenticated Host
|
||||
using Cloud planner transport with an effective token budget, and SHALL reject
|
||||
the request without invoking the provider when the reservation would exceed
|
||||
the remaining budget.
|
||||
|
||||
#### Scenario: Budget permits a planner decision
|
||||
- **WHEN** the Host has sufficient budget after used and reserved tokens are
|
||||
considered
|
||||
- **THEN** the endpoint creates a reservation and invokes the configured
|
||||
provider exactly once
|
||||
|
||||
#### Scenario: Budget is exhausted
|
||||
- **WHEN** a next planner decision would exceed the Host's effective budget
|
||||
- **THEN** the endpoint returns a structured failure without invoking the
|
||||
configured provider
|
||||
|
||||
### Requirement: Planner proxy settles provider-reported token usage without persisting prompts
|
||||
The Cloud planner-decision endpoint SHALL settle its reservation to the
|
||||
provider-reported token usage when available and SHALL retain only non-secret
|
||||
metering metadata, never the raw prompt, screenshot, provider credentials, or
|
||||
session/lease secret.
|
||||
|
||||
#### Scenario: Provider response includes usage
|
||||
- **WHEN** the configured provider returns a valid tool-call decision and
|
||||
token-usage metadata
|
||||
- **THEN** the endpoint records and returns the decision, settles the Host's
|
||||
reservation to the reported usage, and does not durably store request text
|
||||
or screenshot bytes
|
||||
|
||||
#### Scenario: Usage is indeterminate after failure
|
||||
- **WHEN** a reservation exists but the endpoint cannot determine provider
|
||||
usage after a transport failure
|
||||
- **THEN** the reservation remains until bounded expiry cleanup rather than
|
||||
being released as unused
|
||||
@@ -0,0 +1,61 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Host identity is authenticated and bound to one host id
|
||||
The internal Host Agent API SHALL require a host-scoped bearer principal and
|
||||
SHALL reject any request that attempts to act for a `host_id` different from
|
||||
the authenticated principal's bound host.
|
||||
|
||||
#### Scenario: Host authenticates as itself
|
||||
- **WHEN** a Host Agent presents valid credentials bound to its requested
|
||||
`host_id`
|
||||
- **THEN** the internal API authorizes permitted heartbeat, claim, renewal,
|
||||
result, policy retrieval, and self-submission operations
|
||||
|
||||
#### Scenario: Host attempts to impersonate another host
|
||||
- **WHEN** valid credentials bound to host A are used on a request for host B
|
||||
- **THEN** the internal API rejects the request without reading or modifying
|
||||
host B's state
|
||||
|
||||
### Requirement: Host Agent synchronizes heartbeat and complete device snapshots
|
||||
The Host Agent SHALL periodically submit its complete local device snapshot
|
||||
and last applied policy revision to the control plane, and the control plane
|
||||
SHALL atomically refresh the host heartbeat, replace only that host's
|
||||
pooled-device records, and return the effective Host policy whenever its
|
||||
revision differs.
|
||||
|
||||
#### Scenario: Host reports devices
|
||||
- **WHEN** a Host Agent submits a valid heartbeat containing its current devices
|
||||
- **THEN** the control plane updates the host's last-seen time and exposes the
|
||||
submitted devices through the aggregated pool
|
||||
|
||||
#### Scenario: Host policy revision changed
|
||||
- **WHEN** a Host heartbeat presents a revision older than the effective
|
||||
Cloud-host policy
|
||||
- **THEN** the response includes the complete newer policy and revision for the
|
||||
Host to cache
|
||||
|
||||
#### Scenario: Host policy revision is current
|
||||
- **WHEN** a Host heartbeat presents the current effective policy revision
|
||||
- **THEN** the response confirms that revision without resending an unrelated
|
||||
policy representation
|
||||
|
||||
#### Scenario: Host reports no devices
|
||||
- **WHEN** a previously populated host submits an empty device snapshot
|
||||
- **THEN** only that host's prior device records are removed while devices
|
||||
owned by other hosts remain unchanged
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Host can submit a self-targeted goal through the outbound protocol
|
||||
The Host Agent SHALL be able to submit a goal-only task through its existing
|
||||
outbound authenticated protocol, and the control plane SHALL derive its target
|
||||
from the authenticated Host rather than accepting another Host identifier.
|
||||
|
||||
#### Scenario: Host submits a local goal
|
||||
- **WHEN** an authenticated Host submits a valid goal through its internal
|
||||
self-submission operation
|
||||
- **THEN** the control plane returns a queued task targeted to that Host
|
||||
|
||||
#### Scenario: Host submits an ineligible local Device
|
||||
- **WHEN** a Host names a Device that is not owned by its authenticated Host
|
||||
- **THEN** the control plane rejects the request without creating a task
|
||||
@@ -0,0 +1,30 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Host can submit a goal task only for itself
|
||||
The internal Host Agent API SHALL allow an authenticated Host to submit a
|
||||
goal-based task that the Cloud Control Plane irrevocably targets to that same
|
||||
Host, with an optional Device target owned by that Host.
|
||||
|
||||
#### Scenario: Host submits a goal for one of its Devices
|
||||
- **WHEN** Host A presents valid Host A credentials and submits a valid goal
|
||||
naming one of Host A's enrolled Devices
|
||||
- **THEN** the control plane creates a queued task targeted to Host A and that
|
||||
Device without granting Host A public task-submission authority
|
||||
|
||||
#### Scenario: Host omits a Device target
|
||||
- **WHEN** an authenticated Host submits a valid goal without a Device target
|
||||
- **THEN** the control plane creates a queued task targeted to that Host and
|
||||
lets the scheduler select only an eligible Device owned by it
|
||||
|
||||
### Requirement: Host self-submission preserves host isolation
|
||||
The internal Host task-submission operation SHALL reject a foreign Host,
|
||||
foreign Device, workflow reference, or disabled self-submission policy before
|
||||
creating any task.
|
||||
|
||||
#### Scenario: Host attempts a foreign target
|
||||
- **WHEN** Host A submits a request naming Host B or a Device not owned by A
|
||||
- **THEN** the control plane rejects the request and does not create a task
|
||||
|
||||
#### Scenario: Host self-submission is disabled
|
||||
- **WHEN** a Host policy disables Host self-submission
|
||||
- **THEN** the control plane rejects that Host's self-submission request
|
||||
@@ -0,0 +1,84 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Task submission and status via the SDK
|
||||
The system SHALL allow an external integrator to submit a task (goal or
|
||||
workflow reference plus constraints and an optional explicit Host/Device
|
||||
target) through the platform SDK's API, and to query that task's current
|
||||
status by id, backed by the `task-scheduler` capability. A Device target MUST
|
||||
include its owning Host, and the status representation SHALL expose any
|
||||
explicit target separately from the eventual assignment.
|
||||
|
||||
#### Scenario: Submit a task via the API
|
||||
- **WHEN** an integrator calls the task-submission endpoint with a valid goal
|
||||
and optional constraints
|
||||
- **THEN** the API returns a task id that can be used to poll status, and the
|
||||
underlying `task-scheduler` records a new `queued` `ScheduledTask`
|
||||
|
||||
#### Scenario: Submit a task for one Host and Device
|
||||
- **WHEN** a `tasks:submit` principal submits a valid target containing an
|
||||
eligible Host and Device it is permitted to use
|
||||
- **THEN** the API records that exact target and the scheduler cannot assign
|
||||
the task outside it
|
||||
|
||||
#### Scenario: Device target lacks its Host
|
||||
- **WHEN** an integrator submits a Device target without an owning Host
|
||||
- **THEN** the API rejects the request before creating a task
|
||||
|
||||
#### Scenario: Query status of a known task
|
||||
- **WHEN** an integrator requests status for a task id that exists
|
||||
- **THEN** the API returns that task's current status (`queued`, `assigned`,
|
||||
`dispatched`, `done`, or `failed`) and its explicit target when present
|
||||
|
||||
#### Scenario: Query status of an unknown task
|
||||
- **WHEN** an integrator requests status for a task id that does not exist
|
||||
- **THEN** the API returns a not-found response rather than an unhandled server
|
||||
error
|
||||
|
||||
### Requirement: Python SDK client mirrors the REST API
|
||||
The system SHALL provide a Python client (`CloudClient`) exposing methods
|
||||
corresponding to every `/v1/...` resource, user-authentication,
|
||||
user-administration, and governance route, including targeted task submission
|
||||
and read-only Host AI-usage access, so integrators do not need to
|
||||
hand-construct HTTP requests.
|
||||
|
||||
#### Scenario: Client submits a targeted task and retrieves status
|
||||
- **WHEN** a caller uses `CloudClient` to submit a task with an explicit target
|
||||
and then fetches status by the returned id
|
||||
- **THEN** the client produces the same target and lifecycle result as direct
|
||||
REST calls
|
||||
|
||||
#### Scenario: Client administers governance with a scoped bearer
|
||||
- **WHEN** a caller configures `CloudClient` with `governance:admin` and
|
||||
invokes a policy operation
|
||||
- **THEN** the client sends that authentication and returns the corresponding
|
||||
non-secret policy representation
|
||||
|
||||
### Requirement: Public API operations enforce scopes
|
||||
The public platform API SHALL require operation-specific scopes for task
|
||||
submission, task reading, pool reading, plugin reading, plugin
|
||||
administration, user administration, governance reading, and governance
|
||||
administration, regardless of whether the principal came from a bearer
|
||||
credential or user session.
|
||||
|
||||
#### Scenario: Submit principal has task scope
|
||||
- **WHEN** a bearer or user principal with `tasks:submit` calls the
|
||||
task-submission endpoint
|
||||
- **THEN** the request is authorized subject to normal task validation and
|
||||
any effective user-submission policy
|
||||
|
||||
#### Scenario: Non-admin principal attempts plugin registration
|
||||
- **WHEN** an authenticated principal without `plugins:admin` calls plugin
|
||||
registration
|
||||
- **THEN** the API rejects the request before resolving or loading the plugin
|
||||
target
|
||||
|
||||
#### Scenario: Non-admin principal attempts user administration
|
||||
- **WHEN** an authenticated principal without `users:admin` calls a
|
||||
user-administration endpoint
|
||||
- **THEN** the API rejects the request before reading or changing protected
|
||||
user state
|
||||
|
||||
#### Scenario: Principal lacks governance administration scope
|
||||
- **WHEN** an authenticated principal without `governance:admin` changes a
|
||||
user or Host policy
|
||||
- **THEN** the API rejects the request before changing policy or usage state
|
||||
@@ -0,0 +1,58 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Task submission enqueues a scheduled task
|
||||
The system SHALL allow a caller to submit a task (a goal string, or a
|
||||
reference to a `WorkflowDefinition`, plus optional device constraints:
|
||||
`driver_type`, required capability tags, and an explicit Host/Device target)
|
||||
and SHALL enqueue it as a `ScheduledTask` with status `queued`, returning a
|
||||
stable task id the caller can poll. A Device target MUST name its owning Host;
|
||||
an explicit target remains attached to the task for its entire lifecycle.
|
||||
|
||||
#### Scenario: Successful submission
|
||||
- **WHEN** a caller submits a task with a goal and no constraints
|
||||
- **THEN** the scheduler creates a `ScheduledTask` with status `queued`,
|
||||
assigns it a unique id, and returns that id to the caller without blocking
|
||||
for a device to become available
|
||||
|
||||
#### Scenario: Targeted submission
|
||||
- **WHEN** a caller submits a valid task targeted to a Host and one of its
|
||||
Devices
|
||||
- **THEN** the queued task retains that Host/Device target until it is assigned
|
||||
or reaches a terminal lifecycle state
|
||||
|
||||
#### Scenario: Queue depth limit reached
|
||||
- **WHEN** a caller submits a task while the queue already holds
|
||||
`config.max_queue_depth` queued tasks
|
||||
- **THEN** the scheduler rejects the submission with a clear error rather than
|
||||
accepting an unbounded backlog
|
||||
|
||||
### Requirement: Assignment matches a queued task to an idle, constraint-matching device
|
||||
The system SHALL assign a queued `ScheduledTask` to an idle `PooledDevice`
|
||||
(as reported by the `device-pool` capability) whose explicit target, if any,
|
||||
`driver_type`, and capability tags satisfy the task's constraints, using a
|
||||
named, registrable `AssignmentStrategy`.
|
||||
|
||||
#### Scenario: Matching idle device available
|
||||
- **WHEN** `assign()` runs and at least one idle `PooledDevice` matches the
|
||||
head-of-queue task's constraints
|
||||
- **THEN** the scheduler selects one such device via the configured
|
||||
`AssignmentStrategy`, transitions the task to status `assigned`, and records
|
||||
the chosen `device_id`/`host_id`
|
||||
|
||||
#### Scenario: Targeted Device is unavailable
|
||||
- **WHEN** `assign()` runs and the only explicitly targeted Device is busy,
|
||||
stale, absent, or otherwise ineligible
|
||||
- **THEN** the task remains `queued` and the scheduler does not assign a
|
||||
different Device or Host
|
||||
|
||||
#### Scenario: No matching device available
|
||||
- **WHEN** `assign()` runs and no idle `PooledDevice` matches the head-of-queue
|
||||
task's constraints
|
||||
- **THEN** the task remains `queued` (not failed), and `assign()` returns
|
||||
without error, ready to be retried on a later call
|
||||
|
||||
#### Scenario: Unknown assignment strategy configured
|
||||
- **WHEN** `TaskScheduler` is configured with an `AssignmentStrategy` name
|
||||
that is not registered
|
||||
- **THEN** the scheduler raises a clear configuration error at startup/first-
|
||||
assign rather than silently falling back to a default strategy
|
||||
@@ -0,0 +1,113 @@
|
||||
## 1. Preconditions and dependency reconciliation
|
||||
|
||||
- [x] 1.1 Confirm `cloud-planner-proxy` is fully implemented, passes its
|
||||
strict validation and relevant runtime tests, and its deployed schema/API
|
||||
contract is the base for this change.
|
||||
- [ ] 1.2 Complete and reconcile `cloud-console-user-authentication`, including
|
||||
the documented Console Users experience and its remaining PostgreSQL and
|
||||
HTTPS/Host-Agent verification tasks.
|
||||
- [ ] 1.3 Rebase this change's delta specs against the then-current canonical
|
||||
`platform-sdk`, `cloud-console-ui`, `cloud-planner-proxy`, and
|
||||
`agent-runtime` specifications before implementation begins.
|
||||
|
||||
## 2. Governance domain, persistence, and migration
|
||||
|
||||
- [ ] 2.1 Define target-selector, user-submission-policy, Host-governance-
|
||||
policy, token-usage-event, and token-reservation domain models with
|
||||
non-secret representations and revision semantics.
|
||||
- [ ] 2.2 Extend the Cloud repository port with transactional CRUD/query
|
||||
operations for policies, active Host capacity, budget reservations,
|
||||
settlement, expiry cleanup, and bounded usage summaries/events.
|
||||
- [ ] 2.3 Add SQLAlchemy rows, indexes, conversion helpers, and concurrency-
|
||||
safe PostgreSQL/SQLite implementations for governance policies, usage
|
||||
events, reservations, and safe policy audits.
|
||||
- [ ] 2.4 Add an Alembic forward/downgrade revision that preserves existing
|
||||
users, Hosts, devices, tasks, and attempts; advance schema readiness
|
||||
checks to the new head.
|
||||
- [ ] 2.5 Add repository and migration tests for policy revisions, null versus
|
||||
empty allow-list semantics, active-task capacity, reservation races,
|
||||
settlement, expiry cleanup, audit redaction, and SQLite/PostgreSQL
|
||||
parity.
|
||||
|
||||
## 3. Targeted scheduling and public governance API
|
||||
|
||||
- [x] 3.1 Extend task constraints, SDK request/response models, persistence
|
||||
serialization, status/list representations, and `CloudClient` for an
|
||||
optional Host/Device target; reject a Device without its Host.
|
||||
- [x] 3.2 Enforce target ownership/existence at public submission and filter
|
||||
scheduler candidates by target before strategy selection; keep an
|
||||
unavailable targeted task queued with no fallback assignment.
|
||||
- [x] 3.3 Add `governance:read` and `governance:admin` scopes and policy-aware
|
||||
public task authorization that combines `tasks:submit` with the
|
||||
authenticated human user's effective submission policy.
|
||||
- [ ] 3.4 Add bounded, non-secret public governance routes and `CloudClient`
|
||||
methods for user policy, Host policy, Host AI-budget summaries, and
|
||||
paginated usage events; audit every policy mutation.
|
||||
- [ ] 3.5 Add public API/SDK tests for targeted submission, target-policy
|
||||
denial, scope enforcement, status exposure, governance CRUD, and
|
||||
absence of secret fields in responses/audits.
|
||||
|
||||
## 4. Host policy synchronization and self-submission
|
||||
|
||||
- [x] 4.1 Extend shared internal heartbeat request/response models with the
|
||||
Host's last policy revision and a revision-aware effective-policy reply.
|
||||
- [ ] 4.2 Update Host heartbeat synchronization to persist only safe cached
|
||||
policy state and expose it through local status/history without adding an
|
||||
inbound Cloud connection.
|
||||
- [x] 4.3 Add the Host-scoped goal-only task-submission route and
|
||||
`HostAgentClient` method; derive Host targeting from authenticated
|
||||
credentials and validate any named local Device ownership.
|
||||
- [ ] 4.4 Enforce the Host policy's self-submission and active-task limits in
|
||||
the Cloud service/scheduler, not only in Host-local code.
|
||||
- [ ] 4.5 Add Cloud API and Host Agent tests for revision convergence,
|
||||
unchanged-policy replies, self-targeted task creation, foreign target
|
||||
rejection, disabled self-submission, and outbound-only compatibility.
|
||||
|
||||
## 5. Cloud-proxy usage metering and hard budget enforcement
|
||||
|
||||
- [x] 5.1 Extend the dual-provider tool-calling result with optional
|
||||
non-secret provider usage fields while preserving `AIPlanner`'s existing
|
||||
single-decision behavior and direct transport compatibility.
|
||||
- [ ] 5.2 Extend planner-proxy request context and Host-Agent-local context
|
||||
binding so Cloud-proxied calls carry known task/attempt metadata without
|
||||
importing Host or Cloud concerns into `runtime`.
|
||||
- [ ] 5.3 Add Cloud proxy preflight reservation, configured conservative
|
||||
per-call ceiling, provider invocation, actual-usage settlement, and
|
||||
bounded unknown-usage reservation expiry.
|
||||
- [ ] 5.4 Record non-secret usage events and expose accurate
|
||||
used/reserved/remaining UTC-day budget summaries; explicitly report
|
||||
direct transport as unmetered.
|
||||
- [ ] 5.5 Add provider-fake, repository concurrency, Cloud API, and Host
|
||||
Agent tests for usage parsing, over-budget rejection before provider
|
||||
invocation, concurrent reservations, settlement, timeout uncertainty,
|
||||
task-attempt attribution, and direct-transport labeling.
|
||||
|
||||
## 6. Cloud Console governance experience
|
||||
|
||||
- [x] 6.1 Add task composer API bindings and a scope-aware Console form for
|
||||
goal/workflow submission, Host/Device target selection, and target/policy
|
||||
validation errors.
|
||||
- [ ] 6.2 Complete or reconcile the admin Users view, then add user-
|
||||
submission-policy editing with safe refresh and conflict/error handling.
|
||||
- [ ] 6.3 Add Host policy administration and AI-usage/budget views, including
|
||||
revision display, unmetered direct Hosts, and no rendering of prompts,
|
||||
screenshots, provider credentials, cookies, or lease secrets.
|
||||
- [ ] 6.4 Add frontend tests for task composer scope/policy failures,
|
||||
governance navigation, policy mutations, usage rendering, unmetered
|
||||
status, CSRF writes, and retained session behavior on `403`.
|
||||
|
||||
## 7. Documentation, verification, and rollout
|
||||
|
||||
- [ ] 7.1 Update deployment and Console documentation with the dependency
|
||||
order, migration/rollback sequence, policy semantics, Cloud transport
|
||||
prerequisite, budget reservation behavior, direct-host limitation, and
|
||||
safe administrator operations.
|
||||
- [ ] 7.2 Run formatting, Ruff, compile checks, secret/redaction review, the
|
||||
complete non-integration workspace test suite, Cloud Console unit tests,
|
||||
type-check, and production build.
|
||||
- [ ] 7.3 Run real PostgreSQL migration/concurrency coverage and a Compose HTTPS
|
||||
manual flow covering user restriction, targeted scheduling, Host policy
|
||||
delivery, Host self-submission, Cloud-proxy budget exhaustion, and a
|
||||
direct Host labelled unmetered.
|
||||
- [x] 7.4 Run `openspec validate cloud-console-governance --strict` and resolve
|
||||
all proposal, design, specification, and task validation errors.
|
||||
Reference in New Issue
Block a user