You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
GA 6/11 · tenant-wide spend cap + runtime SDK namespaces (TS/Python) + dashboard pause/resume/fork - #270
Part 6 of 11 of the GA next-gen stack. Replaces #264/#255–#263: same changes, history rewritten to drop a 43 MB build binary committed by mistake, plus part 11 fixing every confirmed Codex cross-audit finding. Base: ga2/05-manager-authz. Merge in order with a merge commit (not squash).
Context, verification and needs-human list: #254 and docs/roadmap/2026-10-nextgen-sandbox-and-harness.md.
packages/sdk-ts/src/client.ts and page.tsx: the PR exposes/calls POST /v1/runtime/vms/{id}/pause, /resume, and /fork, but the server only registers schedule/list/get/logs/delete/exec routes in main.go. There are no corresponding RuntimeHandler methods. These UI buttons and SDK methods will 404 in production.
budgets.go: CheckBudget returns immediately for any tenant-wide overage, even when the tenant cap is soft (HardFail=false). Callers only block on !Allowed && HardFail, so a soft tenant cap can mask a hard agent cap that would otherwise block. Example: tenant soft cap exceeded plus agent hard daily/run limit exceeded now continues instead of returning 402. Evaluate the agent budget when the tenant violation is soft, or otherwise preserve hard-fail precedence.
Other Risk
budgets.go: * is introduced as a reserved sentinel agent name, but agent creation only rejects empty names. A real/auto-created agent named * can collide with the tenant budget row and be hidden from ListBudgets. Add validation/reservation or use a separate table/column.
🔒 Reviewer verdict: BLOCKING — Tenant-wide budget cap looks solid, but the new runtime pause/resume/fork feature is client-only — no matching control-plane endpoints exist, so every SDK/dashboard call will 404.
Defect: The web UI and both SDKs implement calls to:
POST /v1/runtime/vms/{id}/pause
POST /v1/runtime/vms/{id}/resume
POST /v1/runtime/vms/{id}/fork
None of these endpoints exist in services/control-plane (cmd/server/main.go or internal/handlers/runtime.go). There are no RPCs or state transitions for pause/resume/fork in packages/proto/lantern/v1/runtime.proto (enum VmState has no paused), nor in runtime-scheduler or runtime-manager.
Impact: Every button click in the cockpit ("Pause", "Resume", "Fork") and every SDK method invocation will fail with HTTP 404 Not Found in any non-mocked environment.
Lineage Data Loss: Fields snapshotId and parentVmId added to VmRow / RuntimeVm / VmDetail do not exist in the runtime_vms database schema and are neither queried nor returned by GET /v1/runtime/vms/{id}.
2. Soft Tenant Budget Breach Disables All Hard-Fail Per-Agent Budgets
iftc:=CheckTenantBudget(ctx, pool, tenantID, estCostUsd); !tc.Allowed {
returntc
}
returncheckAgentBudget(ctx, pool, tenantID, agentName, estCostUsd)
When a tenant configures a tenant budget with hard_fail = false (soft cap) and daily spend exceeds max_cost_usd_per_day, CheckTenantBudget returns BudgetCheckResult{Allowed: false, HardFail: false, ...}.
Because !tc.Allowed evaluates to true, CheckBudget returns tc immediately without ever calling checkAgentBudget.
Impact: Any caller checking budgets (rest.go for runs, sessions.go for interactive messages, voice.go for calls, llm_proxy.go for completions) checks if !bc.Allowed && bc.HardFail. Since bc.HardFail is false, execution proceeds. Exceeding a soft tenant budget bypasses all strict per-agent hard-fail limits (daily spend, cost-per-run, max runs/day), allowing runaway agent spend.
Fix:CheckBudget should only return tc early if tc.HardFail is true. If tc is a soft breach (!tc.Allowed && !tc.HardFail), it must continue to checkAgentBudget so agent hard-fail policies take precedence.
Defect:ForecastHandler.loadBudget only queries agent_budgets for agent_name = $2 (req.AgentName) and checks usage for that specific agent. It does not invoke CheckTenantBudget.
Impact: When a tenant-wide hard-fail budget is exhausted, POST /v1/runs/forecast returns wouldExceedBudget: false, falsely indicating the run is permitted. The subsequent POST /v1/runs immediately fails with HTTP 402, breaking Wedge WhatsApp: agent avatar + 'my voice' persona + handoff disclosure #1 pre-run budget predictability.
Defect:TenantBudgetAgent = "*" is stored directly in agent_budgets. However, UpsertBudget (PUT /v1/agents/{name}/budget), GetBudget, and DeleteBudget do not guard against r.PathValue("name") == "*", nor does CreateAgent in agents.go restrict * as an agent name.
Impact:
A caller with standard agent budget scopes can call DELETE /v1/agents/*/budget to silently delete the tenant-wide budget.
A caller can call PUT /v1/agents/*/budget with {"hardFail": false} (omitting maxCostUsdPerDay), which updates the * row setting max_cost_usd_per_day = NULL and effectively disables the tenant spend limit.
GET /v1/agents/*/budget returns the tenant budget disguised as an agent budget.
Fix: Explicitly reject agentName == TenantBudgetAgent ("*") in UpsertBudget, GetBudget, DeleteBudget, and CreateAgent.
Defect: In LanternClient._Runtime.exec_stream and rawSSEWithEvent:
Python: elif line.startswith("data:"): data = line[5:].strip()
TS: else if (line.startsWith("data:")) { pendingData = line.slice(5).trim(); }
Impact: Per the W3C SSE standard, consecutive data: lines within the same event block must append with a newline (\n). Reassigning the variable overwrites earlier lines, corrupting multiline payloads if chunking or proxy buffering emits multiple data: lines.
Defect: While control-plane endpoints PUT/GET/DELETE /v1/budgets/tenant are introduced, neither SDK exposes client.budgets.getTenant() / client.budgets.upsertTenant(), and the dashboard budgets page has no UI to inspect or set the tenant cap.
Superseded: the history rewrite on this stack accidentally re-signed master's commits (base diverged). Replaced by a range-limited rewrite on ga3/* — see the new GA 1/11 PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
agent:needs-fixReviewer found Blocking issues -> triggers the auto-fix loopdomain:apiTouches API / SDK / public contracts
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 6 of 11 of the GA next-gen stack. Replaces #264/#255–#263: same changes, history rewritten to drop a 43 MB build binary committed by mistake, plus part 11 fixing every confirmed Codex cross-audit finding. Base:
ga2/05-manager-authz. Merge in order with a merge commit (not squash).Context, verification and needs-human list: #254 and
docs/roadmap/2026-10-nextgen-sandbox-and-harness.md.Commits in this part: