@@ -20,6 +20,13 @@ Accounts have fixed roles:
|
|||||||
- `operator`: viewer access plus task submission APIs
|
- `operator`: viewer access plus task submission APIs
|
||||||
- `admin`: all API scopes
|
- `admin`: all API scopes
|
||||||
|
|
||||||
|
Administrators can use **Users & limits** to manage accounts, restrict task
|
||||||
|
submission to explicit Host/Device targets, configure Host self-submission and
|
||||||
|
active-task limits, and inspect non-secret Cloud-proxy usage. Daily token
|
||||||
|
budgets are enforced only for Hosts reporting `AI_PLANNER_TRANSPORT=cloud`;
|
||||||
|
direct-provider Hosts are labelled **unmetered** rather than budget compliant.
|
||||||
|
The configured proxy reservation ceiling must fit within any daily budget.
|
||||||
|
|
||||||
## Local development
|
## Local development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -229,7 +229,9 @@ The repository ships an independent Vue 3 + Vite SPA at `cloud-console/` that
|
|||||||
renders task history, devices, hosts, and plugins. Human operators sign in with
|
renders task history, devices, hosts, and plugins. Human operators sign in with
|
||||||
a username and password; the Cloud API creates an expiring, revocable
|
a username and password; the Cloud API creates an expiring, revocable
|
||||||
`HttpOnly` session cookie and uses a separate CSRF cookie/header for writes.
|
`HttpOnly` session cookie and uses a separate CSRF cookie/header for writes.
|
||||||
The Console has no bearer-token fallback or user-directory view.
|
An administrator can manage accounts, submission policies, Host operational
|
||||||
|
limits, and non-secret Cloud-proxy token usage from **Users & limits**. Bearer
|
||||||
|
tokens remain a compatibility path for API/SDK automation.
|
||||||
|
|
||||||
### HTTPS and session configuration
|
### HTTPS and session configuration
|
||||||
|
|
||||||
@@ -396,6 +398,34 @@ mode:
|
|||||||
`AI_PLANNER_TRANSPORT` unset or `direct` preserves the existing
|
`AI_PLANNER_TRANSPORT` unset or `direct` preserves the existing
|
||||||
direct-to-provider behavior with no change.
|
direct-to-provider behavior with no change.
|
||||||
|
|
||||||
|
### Host governance and Cloud-proxy token budgets
|
||||||
|
|
||||||
|
After deploying the API migration, an administrator can use **Users & limits**
|
||||||
|
in `/console/` to restrict a user's task submission to explicit Host/Device
|
||||||
|
targets, enable or disable Host self-submission, and set a Host active-task
|
||||||
|
limit. Policies are enforced by the API and scheduler; hiding Console controls
|
||||||
|
is not an authorization boundary.
|
||||||
|
|
||||||
|
Cloud-enforced token budgets require `AI_PLANNER_TRANSPORT=cloud`. Before each
|
||||||
|
proxy decision, the API atomically reserves
|
||||||
|
`CLOUD_PLANNER_TOKEN_RESERVATION_CEILING` tokens (default `4096`) for the
|
||||||
|
current UTC day. Set `CLOUD_PLANNER_TOKEN_RESERVATION_TTL_SECONDS` (default
|
||||||
|
`300`) to bound an unknown-usage reservation after provider/transport failure.
|
||||||
|
The daily Host budget must accommodate the reservation ceiling; otherwise the
|
||||||
|
proxy rejects before calling the provider. On a provider response, the
|
||||||
|
reservation is settled to reported usage and the Console retains only timestamp,
|
||||||
|
provider/model, token counts, and optional task/attempt identifiers.
|
||||||
|
|
||||||
|
Hosts reporting `AI_PLANNER_TRANSPORT=direct` are explicitly shown as
|
||||||
|
**unmetered**. Cloud cannot enforce or verify their provider token use. Do not
|
||||||
|
interpret an unmetered Host's absence of usage events as budget compliance.
|
||||||
|
|
||||||
|
Roll out in this order: migrate the Cloud database, deploy the Cloud API,
|
||||||
|
switch a pilot Host to Cloud transport, configure a budget above the reservation
|
||||||
|
ceiling, then review its usage events before enabling budgets fleet-wide. A
|
||||||
|
rollback to direct transport requires valid provider credentials on that Host;
|
||||||
|
preserve usage and policy rows rather than deleting accounting history.
|
||||||
|
|
||||||
## Operational Limitations
|
## Operational Limitations
|
||||||
|
|
||||||
Run exactly one scheduler-enabled Cloud API process. SQLite supports only the
|
Run exactly one scheduler-enabled Cloud API process. SQLite supports only the
|
||||||
|
|||||||
@@ -12,16 +12,16 @@
|
|||||||
|
|
||||||
## 2. Governance domain, persistence, and migration
|
## 2. Governance domain, persistence, and migration
|
||||||
|
|
||||||
- [ ] 2.1 Define target-selector, user-submission-policy, Host-governance-
|
- [x] 2.1 Define target-selector, user-submission-policy, Host-governance-
|
||||||
policy, token-usage-event, and token-reservation domain models with
|
policy, token-usage-event, and token-reservation domain models with
|
||||||
non-secret representations and revision semantics.
|
non-secret representations and revision semantics.
|
||||||
- [ ] 2.2 Extend the Cloud repository port with transactional CRUD/query
|
- [x] 2.2 Extend the Cloud repository port with transactional CRUD/query
|
||||||
operations for policies, active Host capacity, budget reservations,
|
operations for policies, active Host capacity, budget reservations,
|
||||||
settlement, expiry cleanup, and bounded usage summaries/events.
|
settlement, expiry cleanup, and bounded usage summaries/events.
|
||||||
- [ ] 2.3 Add SQLAlchemy rows, indexes, conversion helpers, and concurrency-
|
- [x] 2.3 Add SQLAlchemy rows, indexes, conversion helpers, and concurrency-
|
||||||
safe PostgreSQL/SQLite implementations for governance policies, usage
|
safe PostgreSQL/SQLite implementations for governance policies, usage
|
||||||
events, reservations, and safe policy audits.
|
events, reservations, and safe policy audits.
|
||||||
- [ ] 2.4 Add an Alembic forward/downgrade revision that preserves existing
|
- [x] 2.4 Add an Alembic forward/downgrade revision that preserves existing
|
||||||
users, Hosts, devices, tasks, and attempts; advance schema readiness
|
users, Hosts, devices, tasks, and attempts; advance schema readiness
|
||||||
checks to the new head.
|
checks to the new head.
|
||||||
- [ ] 2.5 Add repository and migration tests for policy revisions, null versus
|
- [ ] 2.5 Add repository and migration tests for policy revisions, null versus
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
credentials and validate any named local Device ownership.
|
credentials and validate any named local Device ownership.
|
||||||
- [x] 4.4 Enforce the Host policy's self-submission and active-task limits in
|
- [x] 4.4 Enforce the Host policy's self-submission and active-task limits in
|
||||||
the Cloud service/scheduler, not only in Host-local code.
|
the Cloud service/scheduler, not only in Host-local code.
|
||||||
- [ ] 4.5 Add Cloud API and Host Agent tests for revision convergence,
|
- [x] 4.5 Add Cloud API and Host Agent tests for revision convergence,
|
||||||
unchanged-policy replies, self-targeted task creation, foreign target
|
unchanged-policy replies, self-targeted task creation, foreign target
|
||||||
rejection, disabled self-submission, and outbound-only compatibility.
|
rejection, disabled self-submission, and outbound-only compatibility.
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
|
|
||||||
## 7. Documentation, verification, and rollout
|
## 7. Documentation, verification, and rollout
|
||||||
|
|
||||||
- [ ] 7.1 Update deployment and Console documentation with the dependency
|
- [x] 7.1 Update deployment and Console documentation with the dependency
|
||||||
order, migration/rollback sequence, policy semantics, Cloud transport
|
order, migration/rollback sequence, policy semantics, Cloud transport
|
||||||
prerequisite, budget reservation behavior, direct-host limitation, and
|
prerequisite, budget reservation behavior, direct-host limitation, and
|
||||||
safe administrator operations.
|
safe administrator operations.
|
||||||
|
|||||||
@@ -253,6 +253,49 @@ def test_heartbeat_and_self_submission_preserve_host_isolation(tmp_path) -> None
|
|||||||
assert foreign.status_code == 403
|
assert foreign.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
def test_host_policy_converges_and_disables_self_submission(tmp_path) -> None:
|
||||||
|
client, pool = _build_client(tmp_path)
|
||||||
|
headers = {"Authorization": "Bearer token-a"}
|
||||||
|
client.put(
|
||||||
|
"/internal/v1/hosts/host-a/heartbeat",
|
||||||
|
headers=headers,
|
||||||
|
json=_heartbeat_payload("host-a", "device-a"),
|
||||||
|
)
|
||||||
|
pool.store.upsert_host_governance_policy(
|
||||||
|
host_id="host-a",
|
||||||
|
self_submission_enabled=False,
|
||||||
|
max_active_tasks=2,
|
||||||
|
daily_token_budget=1000,
|
||||||
|
updated_at=datetime.now(UTC),
|
||||||
|
)
|
||||||
|
|
||||||
|
stale = client.put(
|
||||||
|
"/internal/v1/hosts/host-a/heartbeat",
|
||||||
|
headers=headers,
|
||||||
|
json={**_heartbeat_payload("host-a", "device-a"), "policy_revision": 0},
|
||||||
|
)
|
||||||
|
assert stale.status_code == 200
|
||||||
|
assert stale.json()["policy"] == {
|
||||||
|
"revision": 1,
|
||||||
|
"self_submission_enabled": False,
|
||||||
|
"max_active_tasks": 2,
|
||||||
|
"daily_token_budget": 1000,
|
||||||
|
}
|
||||||
|
current = client.put(
|
||||||
|
"/internal/v1/hosts/host-a/heartbeat",
|
||||||
|
headers=headers,
|
||||||
|
json={**_heartbeat_payload("host-a", "device-a"), "policy_revision": 1},
|
||||||
|
)
|
||||||
|
assert current.status_code == 200
|
||||||
|
assert current.json()["policy"] is None
|
||||||
|
disabled = client.post(
|
||||||
|
"/internal/v1/hosts/host-a/tasks",
|
||||||
|
headers=headers,
|
||||||
|
json={"host_id": "host-a", "goal": "should be rejected"},
|
||||||
|
)
|
||||||
|
assert disabled.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
def test_planner_proxy_reserves_and_enforces_host_daily_token_budget(tmp_path) -> None:
|
def test_planner_proxy_reserves_and_enforces_host_daily_token_budget(tmp_path) -> None:
|
||||||
class FakePlannerClient:
|
class FakePlannerClient:
|
||||||
calls = 0
|
calls = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user