Commit Graph

4500 Commits

Author SHA1 Message Date
Molecule AI Frontend Engineer
848b745b6e feat(canvas): expose effort + task_budget in ConfigTab (#608)
Adds two new Claude API primitives (Opus 4.7+) as configurable workspace
fields in the Config tab form:

  effort: 'low' | 'medium' | 'high' | 'xhigh'
    Maps to output_config.effort in the Anthropic Messages API.
    Controls thinking depth — xhigh enables extended thinking mode.

  task_budget: integer (token count, 0 = unset)
    Maps to output_config.task_budget.total; requires beta header
    task-budgets-2026-03-13. Lets operators cap token spend per task.

Both fields are stored as top-level keys in config.yaml and read by
claude_sdk_executor.py (workspace-template side, tracked in #608).

Canvas changes:
- form-inputs.tsx: effort?: string, task_budget?: number added to
  ConfigData; DEFAULT_CONFIG initialises them to "" / 0
- yaml-utils.ts: toYaml() emits effort + task_budget (omits when
  empty/zero); parseYaml() already handles plain string/integer keys
- ConfigTab.tsx: new collapsible "Claude Settings" section (defaultOpen=false)
  shown when runtime === "claude-code" OR model name contains "claude"
  or "anthropic". Dropdown for effort (4 options + unset), number input
  for task_budget (step 1000, 0 = unset).

Tests (25 cases in ClaudeSettings.test.tsx):
  - toYaml serialises all four effort values + omits empty/undefined
  - toYaml serialises task_budget + omits 0/undefined
  - effort appears before task_budget in YAML output
  - parseYaml round-trips both fields correctly
  - DEFAULT_CONFIG shape assertions
  - Source assertions for section guards + option values
  - React rendering: section visible for claude-code/claude model,
    hidden for non-Claude runtime (crewai + gpt-4o)

640/640 tests pass. Build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:24:36 +00:00
molecule-ai[bot]
d7c247a985 Merge pull request #636 from Molecule-AI/fix/issue-631-migration-gap
Merge gate passed. Pure file renames (+0/-0): 026→025 (workspace_token_usage), 027→026 (org_plugin_allowlist). Closes migration numbering gap so sequential runners proceed past 024. Schema migration — CEO explicit authorization in chat. NOTE: if production DB recorded old filenames 026/027 as applied, verify runner idempotency before restart to avoid double-application.
2026-04-17 06:23:05 +00:00
molecule-ai[bot]
ea43256a87
Merge pull request #636 from Molecule-AI/fix/issue-631-migration-gap
Merge gate passed. Pure file renames (+0/-0): 026→025 (workspace_token_usage), 027→026 (org_plugin_allowlist). Closes migration numbering gap so sequential runners proceed past 024. Schema migration — CEO explicit authorization in chat. NOTE: if production DB recorded old filenames 026/027 as applied, verify runner idempotency before restart to avoid double-application.
2026-04-17 06:23:05 +00:00
Molecule AI Backend Engineer
c1443bf52f fix(migrations): renumber budget migration 025→027 to follow gap fix (#631)
Rebase on origin/fix/issue-631-migration-gap which inserts token_usage
(025) and org_plugin_allowlist (026); bump workspace_budget from 025 to
027 so the sequential runner applies all three in the correct order.
Update workspace_budget_test.go and workspace_test.go to match the
transaction-wrapped INSERT (BeginTx/Commit) introduced on main and the
resulting 10-arg WithArgs call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:22:09 +00:00
Molecule AI Backend Engineer
f1fa92ad84 fix(migrations): renumber budget migration 025→027 to follow gap fix (#631)
Rebase on origin/fix/issue-631-migration-gap which inserts token_usage
(025) and org_plugin_allowlist (026); bump workspace_budget from 025 to
027 so the sequential runner applies all three in the correct order.
Update workspace_budget_test.go and workspace_test.go to match the
transaction-wrapped INSERT (BeginTx/Commit) introduced on main and the
resulting 10-arg WithArgs call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:22:09 +00:00
Molecule AI Frontend Engineer
0adf5d5a5f fix(canvas): mock WorkspaceUsage in BudgetLimit.DetailsTab test
DetailsTab renders WorkspaceUsage alongside BudgetSection. The test suite
sets api.get to return [] (a valid empty peers list) but WorkspaceUsage
calls api.get for metrics and crashes on undefined input_tokens when the
mock returns an array instead of a WorkspaceMetrics object.

Add a stub vi.mock following the same pattern already used for BudgetSection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:22:07 +00:00
Molecule AI Frontend Engineer
28dfa185aa fix(canvas): mock WorkspaceUsage in BudgetLimit.DetailsTab test
DetailsTab renders WorkspaceUsage alongside BudgetSection. The test suite
sets api.get to return [] (a valid empty peers list) but WorkspaceUsage
calls api.get for metrics and crashes on undefined input_tokens when the
mock returns an array instead of a WorkspaceMetrics object.

Add a stub vi.mock following the same pattern already used for BudgetSection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:22:07 +00:00
molecule-ai[bot]
ea26e89064 Merge pull request #635 from Molecule-AI/chore/eco-watch-2026-04-17-clean
Merge gate passed. Docs-only — ecosystem-watch.md entries only, no code/schema/auth. UNSTABLE = known App token scope gap.
2026-04-17 06:21:03 +00:00
molecule-ai[bot]
40a8e41808
Merge pull request #635 from Molecule-AI/chore/eco-watch-2026-04-17-clean
Merge gate passed. Docs-only — ecosystem-watch.md entries only, no code/schema/auth. UNSTABLE = known App token scope gap.
2026-04-17 06:21:03 +00:00
Molecule AI Backend Engineer
08be2ccb17 fix(#611): remove budget_limit from PATCH /workspaces/:id and strip financial fields from GET
Security Auditor findings on PR #611:

Fix 1 (BLOCKING): Remove budget_limit handling from Update() entirely.
PATCH /workspaces/:id uses ValidateAnyToken — any enrolled workspace bearer
could self-clear its own spending ceiling. The dedicated AdminAuth-gated
PATCH /workspaces/:id/budget is the only authorised write path.

Fix 2 (MEDIUM): Strip budget_limit and monthly_spend from Get() response
before c.JSON(). GET /workspaces/:id is on the open router — any caller
with a valid UUID must not read billing data.

Also updates four existing tests in workspace_budget_test.go that encoded
the old (insecure) behaviour, and adds three new regression tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
fce0be30fd fix(#611): remove budget_limit from PATCH /workspaces/:id and strip financial fields from GET
Security Auditor findings on PR #611:

Fix 1 (BLOCKING): Remove budget_limit handling from Update() entirely.
PATCH /workspaces/:id uses ValidateAnyToken — any enrolled workspace bearer
could self-clear its own spending ceiling. The dedicated AdminAuth-gated
PATCH /workspaces/:id/budget is the only authorised write path.

Fix 2 (MEDIUM): Strip budget_limit and monthly_spend from Get() response
before c.JSON(). GET /workspaces/:id is on the open router — any caller
with a valid UUID must not read billing data.

Also updates four existing tests in workspace_budget_test.go that encoded
the old (insecure) behaviour, and adds three new regression tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
b2f8997afe fix(issue-541): move PATCH /budget to adminAuth — workspace must not self-clear ceiling
Workspace agents could previously call PATCH /workspaces/:id/budget with their
own bearer token and set budget_limit=null, defeating the entire spend enforcement
feature. GET stays on wsAuth (reading own budget is legitimate); PATCH moves to
inline AdminAuth using the same pattern as /approvals/pending.

No existing tests needed updating — all budget PATCH tests call the handler
directly and are unaffected by router-level middleware changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
dd0b282c79 fix(issue-541): move PATCH /budget to adminAuth — workspace must not self-clear ceiling
Workspace agents could previously call PATCH /workspaces/:id/budget with their
own bearer token and set budget_limit=null, defeating the entire spend enforcement
feature. GET stays on wsAuth (reading own budget is legitimate); PATCH moves to
inline AdminAuth using the same pattern as /approvals/pending.

No existing tests needed updating — all budget PATCH tests call the handler
directly and are unaffected by router-level middleware changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
9223486756 fix(issue-541): correct stale 429 comment to 402 in checkWorkspaceBudget
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
4e6e3745f2 fix(issue-541): correct stale 429 comment to 402 in checkWorkspaceBudget
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
72521866cc fix(#541): change budget enforcement status from 429 to 402
Budget limit exceeded on A2A proxy now returns HTTP 402 PaymentRequired
instead of 429 TooManyRequests, matching the issue spec and the FE amber
banner check. Updates a2a_proxy.go, workspace_budget_test.go (renamed
ExceededReturns429 → ExceededReturns402, AboveLimitReturns429 →
AboveLimitReturns402), and migration comment. All go test ./... pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
2fb0aacd41 fix(#541): change budget enforcement status from 429 to 402
Budget limit exceeded on A2A proxy now returns HTTP 402 PaymentRequired
instead of 429 TooManyRequests, matching the issue spec and the FE amber
banner check. Updates a2a_proxy.go, workspace_budget_test.go (renamed
ExceededReturns429 → ExceededReturns402, AboveLimitReturns429 →
AboveLimitReturns402), and migration comment. All go test ./... pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
47cb81f2bc feat(#541): add dedicated GET/PATCH /workspaces/:id/budget endpoints
- New BudgetHandler with GetBudget and PatchBudget methods
- GET returns budget_limit (null or int64 USD cents), monthly_spend,
  and computed budget_remaining (null when no limit, can be negative
  when over-budget so callers can see the magnitude of the overage)
- PATCH accepts {budget_limit: int64|null}; null clears the ceiling;
  validates non-negative values; re-reads DB to echo final state
- Both handlers are wired in router.go under the WorkspaceAuth group
- 14 unit tests covering happy paths, 404, 400 validation, DB errors,
  over-budget state, zero limit, and clear-limit round-trip
- All 20 packages pass go test ./... and go build ./... is clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
22af070ef3 feat(#541): add dedicated GET/PATCH /workspaces/:id/budget endpoints
- New BudgetHandler with GetBudget and PatchBudget methods
- GET returns budget_limit (null or int64 USD cents), monthly_spend,
  and computed budget_remaining (null when no limit, can be negative
  when over-budget so callers can see the magnitude of the overage)
- PATCH accepts {budget_limit: int64|null}; null clears the ceiling;
  validates non-negative values; re-reads DB to echo final state
- Both handlers are wired in router.go under the WorkspaceAuth group
- 14 unit tests covering happy paths, 404, 400 validation, DB errors,
  over-budget state, zero limit, and clear-limit round-trip
- All 20 packages pass go test ./... and go build ./... is clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
f99d8b0a1a feat(platform): add per-workspace budget_limit field and A2A enforcement (#541)
- Migration 025: ADD COLUMN budget_limit BIGINT DEFAULT NULL and
  monthly_spend BIGINT NOT NULL DEFAULT 0 to workspaces table
- Models: BudgetLimit *int64 in CreateWorkspacePayload;
  MonthlySpend int64 in HeartbeatPayload
- workspace.go: scanWorkspaceRow, workspaceListQuery, Get, Create, and
  Update all handle budget_limit/monthly_spend; budget_limit is gated
  as a sensitiveUpdateField
- registry.go: heartbeat conditionally writes monthly_spend only when
  payload.MonthlySpend > 0 (avoids overwriting with zero)
- a2a_proxy.go: checkWorkspaceBudget() returns 429 when
  monthly_spend >= budget_limit (NULL = no limit; fail-open on DB error)
- Tests: 8 new workspace_budget_test.go tests + patched existing tests
  for the 20-column scanWorkspaceRow and 10-param CREATE INSERT

Field type: BIGINT (int64), units: USD cents (budget_limit=500 = $5.00/month)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
Molecule AI Backend Engineer
f8106b35be feat(platform): add per-workspace budget_limit field and A2A enforcement (#541)
- Migration 025: ADD COLUMN budget_limit BIGINT DEFAULT NULL and
  monthly_spend BIGINT NOT NULL DEFAULT 0 to workspaces table
- Models: BudgetLimit *int64 in CreateWorkspacePayload;
  MonthlySpend int64 in HeartbeatPayload
- workspace.go: scanWorkspaceRow, workspaceListQuery, Get, Create, and
  Update all handle budget_limit/monthly_spend; budget_limit is gated
  as a sensitiveUpdateField
- registry.go: heartbeat conditionally writes monthly_spend only when
  payload.MonthlySpend > 0 (avoids overwriting with zero)
- a2a_proxy.go: checkWorkspaceBudget() returns 429 when
  monthly_spend >= budget_limit (NULL = no limit; fail-open on DB error)
- Tests: 8 new workspace_budget_test.go tests + patched existing tests
  for the 20-column scanWorkspaceRow and 10-param CREATE INSERT

Field type: BIGINT (int64), units: USD cents (budget_limit=500 = $5.00/month)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:18:41 +00:00
molecule-ai[bot]
efc9bc0244 Merge pull request #629 from Molecule-AI/fix/issue-614-security-headers
Merge gate passed (all 7 gates). Adds /orgs to apiPrefixes so PR #610's allowlist routes get nosniff + X-Frame-Options headers. One-line fix + 50 lines of regression tests. UNSTABLE = known App token scope gap.
2026-04-17 06:18:25 +00:00
molecule-ai[bot]
5b42bd76b5
Merge pull request #629 from Molecule-AI/fix/issue-614-security-headers
Merge gate passed (all 7 gates). Adds /orgs to apiPrefixes so PR #610's allowlist routes get nosniff + X-Frame-Options headers. One-line fix + 50 lines of regression tests. UNSTABLE = known App token scope gap.
2026-04-17 06:18:25 +00:00
Hongming Wang
abb05c7ef9 Merge pull request #630 from Molecule-AI/fix/issue-615-cap-token-counts
fix(platform): cap token counts before upsert to prevent NUMERIC overflow (#615)
2026-04-16 23:17:37 -07:00
Hongming Wang
44cef47763
Merge pull request #630 from Molecule-AI/fix/issue-615-cap-token-counts
fix(platform): cap token counts before upsert to prevent NUMERIC overflow (#615)
2026-04-16 23:17:37 -07:00
Molecule AI Backend Engineer
f5c9132413 fix(migrations): close 024→026 gap — rename 026→025 token_usage, 027→026 allowlist (#631)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:17:36 +00:00
Molecule AI Backend Engineer
3329370b1c fix(migrations): close 024→026 gap — rename 026→025 token_usage, 027→026 allowlist (#631)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:17:36 +00:00
molecule-ai[bot]
3159e03ec1 Merge pull request #627 from Molecule-AI/feat/issue-592-wire-metrics-api
Merge gate passed (all 7 gates). Conflicts were mechanical: WorkspaceUsage.tsx full implementation over scaffold (backend #593 is live), RevealToggle.tsx 'use client' deduplicated. UNSTABLE = known GitHub App token scope gap.
2026-04-17 06:17:00 +00:00
molecule-ai[bot]
9bac2d20f9
Merge pull request #627 from Molecule-AI/feat/issue-592-wire-metrics-api
Merge gate passed (all 7 gates). Conflicts were mechanical: WorkspaceUsage.tsx full implementation over scaffold (backend #593 is live), RevealToggle.tsx 'use client' deduplicated. UNSTABLE = known GitHub App token scope gap.
2026-04-17 06:17:00 +00:00
bb18f79343 fix(gate-1): resolve merge conflicts with main
Three add/add + content conflicts, all mechanical:
- WorkspaceUsage.tsx: HEAD (full live-metrics implementation wired
  to GET /workspaces/:id/metrics) over main's scaffold placeholder;
  #593 backend is now live so the TODO is fulfilled
- WorkspaceUsage.test.tsx: HEAD (full mock-api test suite, 10 tests)
  over main's scaffold tests (tested placeholder — values now stale)
- RevealToggle.tsx: both sides independently added 'use client'; kept
  main's double-quote variant ("use client") for codebase consistency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:16:36 +00:00
040f674a6a fix(gate-1): resolve merge conflicts with main
Three add/add + content conflicts, all mechanical:
- WorkspaceUsage.tsx: HEAD (full live-metrics implementation wired
  to GET /workspaces/:id/metrics) over main's scaffold placeholder;
  #593 backend is now live so the TODO is fulfilled
- WorkspaceUsage.test.tsx: HEAD (full mock-api test suite, 10 tests)
  over main's scaffold tests (tested placeholder — values now stale)
- RevealToggle.tsx: both sides independently added 'use client'; kept
  main's double-quote variant ("use client") for codebase consistency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:16:36 +00:00
Molecule AI Backend Engineer
19d863b052 fix(platform): cap monthly_spend on heartbeat upsert (#615)
A malicious or buggy agent could report MonthlySpend = math.MaxInt64
causing NUMERIC overflow in the DB or incorrect budget-enforcement
comparisons downstream.

Changes:
- Add MonthlySpend int64 field to HeartbeatPayload (json:"monthly_spend")
- Clamp negative values to 0 and values above $10B (1_000_000_000_000
  cents) to the cap before any DB write
- The two-path UPDATE: when MonthlySpend > 0 after clamping, include
  monthly_spend = $7 in the UPDATE; otherwise skip to avoid accidentally
  clearing a previously-reported spend value
- 5 regression tests covering: within-bounds passthrough, negative
  clamp, math.MaxInt64 overflow clamp, exact-cap boundary, and
  zero/omitted no-update path

Note: this branch introduces MonthlySpend to HeartbeatPayload; it will
need trivial conflict resolution when feat/issue-541-budget-limit-backend
merges, as that branch also adds the field (without the cap). Keep this
branch's clamping logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:16:06 +00:00
Molecule AI Backend Engineer
668c93e513 fix(platform): cap monthly_spend on heartbeat upsert (#615)
A malicious or buggy agent could report MonthlySpend = math.MaxInt64
causing NUMERIC overflow in the DB or incorrect budget-enforcement
comparisons downstream.

Changes:
- Add MonthlySpend int64 field to HeartbeatPayload (json:"monthly_spend")
- Clamp negative values to 0 and values above $10B (1_000_000_000_000
  cents) to the cap before any DB write
- The two-path UPDATE: when MonthlySpend > 0 after clamping, include
  monthly_spend = $7 in the UPDATE; otherwise skip to avoid accidentally
  clearing a previously-reported spend value
- 5 regression tests covering: within-bounds passthrough, negative
  clamp, math.MaxInt64 overflow clamp, exact-cap boundary, and
  zero/omitted no-update path

Note: this branch introduces MonthlySpend to HeartbeatPayload; it will
need trivial conflict resolution when feat/issue-541-budget-limit-backend
merges, as that branch also adds the field (without the cap). Keep this
branch's clamping logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:16:06 +00:00
molecule-ai[bot]
19396fc55a Merge pull request #628 from Molecule-AI/fix/issue-623-adminauth-origin-bypass
Merge gate passed (all 7 gates). Security fix: removes canvasOriginAllowed + isSameOriginCanvas Origin bypass from AdminAuth — bearer token is now the only accepted credential on admin routes. 3 regression tests cover forged-localhost, forged-tenant-domain, and bearer+Origin golden path. Auth PR — CEO explicit approval confirmed in chat. UNSTABLE = known GitHub App token scope gap.
2026-04-17 06:13:33 +00:00
molecule-ai[bot]
398c1e9f68
Merge pull request #628 from Molecule-AI/fix/issue-623-adminauth-origin-bypass
Merge gate passed (all 7 gates). Security fix: removes canvasOriginAllowed + isSameOriginCanvas Origin bypass from AdminAuth — bearer token is now the only accepted credential on admin routes. 3 regression tests cover forged-localhost, forged-tenant-domain, and bearer+Origin golden path. Auth PR — CEO explicit approval confirmed in chat. UNSTABLE = known GitHub App token scope gap.
2026-04-17 06:13:33 +00:00
molecule-ai[bot]
67ee0366e0 Merge pull request #606 from Molecule-AI/feat/issue-541-budget-limit-frontend
Merge gate passed (all 7 gates). All merge conflicts were mechanically additive (BudgetSection + WorkspaceUsage both kept; hydrating spinner + error banner combined; useId import preserved; WCAG a11y tests kept). UNSTABLE = known GitHub App token scope gap, not a test failure.
2026-04-17 06:10:53 +00:00
molecule-ai[bot]
deecd01a8d
Merge pull request #606 from Molecule-AI/feat/issue-541-budget-limit-frontend
Merge gate passed (all 7 gates). All merge conflicts were mechanically additive (BudgetSection + WorkspaceUsage both kept; hydrating spinner + error banner combined; useId import preserved; WCAG a11y tests kept). UNSTABLE = known GitHub App token scope gap, not a test failure.
2026-04-17 06:10:53 +00:00
Molecule AI Frontend Engineer
6b5248e173 fix(canvas): move vi.mock to module top level in ZoomShortcut.test (#632)
The vi.mock("../../../store/canvas") call was nested inside an it()
block. Vitest hoists all vi.mock calls to module scope at runtime
regardless, so the code never matched its actual execution order —
prompting the "not at top level" warning that Vitest will make a hard
error in a future version.

Move the mock to after the imports, remove the now-redundant inline
call from the it() body, and add a comment explaining the hoisting rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:09:39 +00:00
Molecule AI Frontend Engineer
bfe4e09b7e fix(canvas): move vi.mock to module top level in ZoomShortcut.test (#632)
The vi.mock("../../../store/canvas") call was nested inside an it()
block. Vitest hoists all vi.mock calls to module scope at runtime
regardless, so the code never matched its actual execution order —
prompting the "not at top level" warning that Vitest will make a hard
error in a future version.

Move the mock to after the imports, remove the now-redundant inline
call from the it() body, and add a comment explaining the hoisting rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:09:39 +00:00
Molecule AI Frontend Engineer
6668a95d6f fix(canvas): use explicit empty-string check in BudgetSection to preserve zero-credit budget
parseInt("0", 10) || null evaluates to null, silently converting a
zero-credit budget to unlimited. Switch to raw !== "" ? parseInt() : null
so budget_limit: 0 is sent correctly. Adds regression test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:07:08 +00:00
Molecule AI Frontend Engineer
a60ece77c6 fix(canvas): use explicit empty-string check in BudgetSection to preserve zero-credit budget
parseInt("0", 10) || null evaluates to null, silently converting a
zero-credit budget to unlimited. Switch to raw !== "" ? parseInt() : null
so budget_limit: 0 is sent correctly. Adds regression test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:07:08 +00:00
Molecule AI Frontend Engineer
e22d2d2257 fix(canvas): WCAG SC 1.3.1 — programmatic label/input association in InputField
Adds useId() to the InputField helper in CreateWorkspaceDialog so every
<label> is wired to its <input> via htmlFor/id. Without this, screen readers
announced only the placeholder text, not the field name (WCAG 2.1 SC 1.3.1
Level A violation, build 4JIwTGVMjDGNLO8iMGJeC).

Affected fields: Name (required), Role, Budget limit (USD), Template.
The Hermes provider fields were already correctly wired.

Adds 6 new tests in CreateWorkspaceDialog.a11y.test.tsx verifying htmlFor/id
round-trips for each field and unique-id non-collision (602 total, all pass;
build clean; 'use client' grep empty).

Note: #554 (hydration error UI) and #556 (tier radio arrow-key nav) are
confirmed fixed in commit e70bb94 — audit cycle 2 was run against the
pre-fix build. #557 (zoom-to-team Z key) is a false positive — the handler
IS implemented; closing via Dev Lead once token is refreshed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:07:08 +00:00
Molecule AI Frontend Engineer
c064200164 fix(canvas): WCAG SC 1.3.1 — programmatic label/input association in InputField
Adds useId() to the InputField helper in CreateWorkspaceDialog so every
<label> is wired to its <input> via htmlFor/id. Without this, screen readers
announced only the placeholder text, not the field name (WCAG 2.1 SC 1.3.1
Level A violation, build 4JIwTGVMjDGNLO8iMGJeC).

Affected fields: Name (required), Role, Budget limit (USD), Template.
The Hermes provider fields were already correctly wired.

Adds 6 new tests in CreateWorkspaceDialog.a11y.test.tsx verifying htmlFor/id
round-trips for each field and unique-id non-collision (602 total, all pass;
build clean; 'use client' grep empty).

Note: #554 (hydration error UI) and #556 (tier radio arrow-key nav) are
confirmed fixed in commit 76defba — audit cycle 2 was run against the
pre-fix build. #557 (zoom-to-team Z key) is a false positive — the handler
IS implemented; closing via Dev Lead once token is refreshed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:07:08 +00:00
Molecule AI Frontend Engineer
b522484e2e feat(#541): budget settings UI with usage stats and 402 handling
Adds a dedicated BudgetSection component to the workspace details panel:
- GET /workspaces/:id/budget on mount — populates live stats (used/limit/remaining)
- Stats row + blue-500 progress bar (capped at 100%; hidden when unlimited)
- PATCH /workspaces/:id/budget for saving; input blank → budget_limit: null
- "Budget exceeded — messages blocked" amber/zinc-950 banner on any 402 response
  (GET or PATCH); banner clears on a successful subsequent save
- 'use client'; dark zinc theme throughout (zinc-800/700 inputs, blue-500 accents)

DetailsTab refactored: inline budget_limit fields removed; BudgetSection mounted
as a self-contained section between Workspace and Skills. PATCH /workspaces/:id
body no longer includes budget_limit — that concern is isolated to BudgetSection.

Tests: 21 new cases in BudgetSection.test.tsx (loading, stats, progress bar,
save, 402 GET, 402 PATCH, banner clear, non-402 errors). BudgetLimit.DetailsTab
rewritten to mock BudgetSection and verify the DetailsTab/BudgetSection
integration contract (596 total, all pass; build clean; 'use client' grep empty).

API shape: GET/PATCH /workspaces/:id/budget → {budget_limit: int64|null,
budget_used: int64, budget_remaining: int64|null}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:07:07 +00:00
Molecule AI Frontend Engineer
2152323cd1 feat(#541): budget settings UI with usage stats and 402 handling
Adds a dedicated BudgetSection component to the workspace details panel:
- GET /workspaces/:id/budget on mount — populates live stats (used/limit/remaining)
- Stats row + blue-500 progress bar (capped at 100%; hidden when unlimited)
- PATCH /workspaces/:id/budget for saving; input blank → budget_limit: null
- "Budget exceeded — messages blocked" amber/zinc-950 banner on any 402 response
  (GET or PATCH); banner clears on a successful subsequent save
- 'use client'; dark zinc theme throughout (zinc-800/700 inputs, blue-500 accents)

DetailsTab refactored: inline budget_limit fields removed; BudgetSection mounted
as a self-contained section between Workspace and Skills. PATCH /workspaces/:id
body no longer includes budget_limit — that concern is isolated to BudgetSection.

Tests: 21 new cases in BudgetSection.test.tsx (loading, stats, progress bar,
save, 402 GET, 402 PATCH, banner clear, non-402 errors). BudgetLimit.DetailsTab
rewritten to mock BudgetSection and verify the DetailsTab/BudgetSection
integration contract (596 total, all pass; build clean; 'use client' grep empty).

API shape: GET/PATCH /workspaces/:id/budget → {budget_limit: int64|null,
budget_used: int64, budget_remaining: int64|null}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:07:07 +00:00
Molecule AI Frontend Engineer
07240e7095 feat(canvas): budget_limit input in workspace creation and settings UI (#541)
- Adds optional Budget limit (USD) numeric field to CreateWorkspaceDialog;
  blank = null (unlimited), populated = parsed float sent as budget_limit in
  POST /workspaces body
- Adds budget_limit field to DetailsTab edit form; saves via
  PATCH /workspaces/:id; pre-fills from current WorkspaceNodeData
- Shows 'Budget limit exceeded' warning badge when budgetUsed > budgetLimit
  (forward-compatible — badge hidden when budgetUsed is absent)
- Extends WorkspaceData, WorkspaceNodeData, and buildNodesAndEdges to carry
  budgetLimit / budgetUsed fields ready for backend hydration (issue #541 BE PR)
- Ships 22 new tests across CreateWorkspaceDialog and BudgetLimit.DetailsTab
  suites (575 total, all passing); npm run build clean; 'use client' grep empty

API shape confirmed from workspace.go and CreateWorkspacePayload struct:
  field name: budget_limit | type: number | null | units: USD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:06:36 +00:00
Molecule AI Frontend Engineer
5d081769e5 feat(canvas): budget_limit input in workspace creation and settings UI (#541)
- Adds optional Budget limit (USD) numeric field to CreateWorkspaceDialog;
  blank = null (unlimited), populated = parsed float sent as budget_limit in
  POST /workspaces body
- Adds budget_limit field to DetailsTab edit form; saves via
  PATCH /workspaces/:id; pre-fills from current WorkspaceNodeData
- Shows 'Budget limit exceeded' warning badge when budgetUsed > budgetLimit
  (forward-compatible — badge hidden when budgetUsed is absent)
- Extends WorkspaceData, WorkspaceNodeData, and buildNodesAndEdges to carry
  budgetLimit / budgetUsed fields ready for backend hydration (issue #541 BE PR)
- Ships 22 new tests across CreateWorkspaceDialog and BudgetLimit.DetailsTab
  suites (575 total, all passing); npm run build clean; 'use client' grep empty

API shape confirmed from workspace.go and CreateWorkspacePayload struct:
  field name: budget_limit | type: number | null | units: USD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:06:36 +00:00
Molecule AI Backend Engineer
cc660e5185 fix(platform): cap token counts before upsert to prevent NUMERIC overflow (#615)
Adversarial or buggy agents can report INT64_MAX token counts via A2A
responses. Without clamping, upsertTokenUsage would pass these directly to
Postgres NUMERIC(12,6), causing a silent upsert failure that corrupts the
workspace's cost accounting.

Fix: clamp input_tokens/output_tokens to [0, 10_000_000] before any
arithmetic or DB write. 10M tokens/call is well above any real LLM API
response; clamped values still produce valid cost rows.

Adds 4 regression tests:
- TestUpsertTokenUsage_615_CapsInt64Max      — INT64_MAX → maxTokensPerCall
- TestUpsertTokenUsage_615_CapsNegative      — negative → 0 (no DB call)
- TestUpsertTokenUsage_615_NormalValuesUnchanged — passthrough for normal counts
- TestUpsertTokenUsage_615_ExactlyAtCap      — at-cap value accepted unchanged

Closes #615

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:03:40 +00:00
Molecule AI Backend Engineer
13b8965c99 fix(platform): cap token counts before upsert to prevent NUMERIC overflow (#615)
Adversarial or buggy agents can report INT64_MAX token counts via A2A
responses. Without clamping, upsertTokenUsage would pass these directly to
Postgres NUMERIC(12,6), causing a silent upsert failure that corrupts the
workspace's cost accounting.

Fix: clamp input_tokens/output_tokens to [0, 10_000_000] before any
arithmetic or DB write. 10M tokens/call is well above any real LLM API
response; clamped values still produce valid cost rows.

Adds 4 regression tests:
- TestUpsertTokenUsage_615_CapsInt64Max      — INT64_MAX → maxTokensPerCall
- TestUpsertTokenUsage_615_CapsNegative      — negative → 0 (no DB call)
- TestUpsertTokenUsage_615_NormalValuesUnchanged — passthrough for normal counts
- TestUpsertTokenUsage_615_ExactlyAtCap      — at-cap value accepted unchanged

Closes #615

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:03:40 +00:00
Molecule AI Backend Engineer
26215cf721 fix(platform): pin X-Content-Type-Options nosniff + add /orgs API prefix (#614)
SecurityHeaders() middleware already sets X-Content-Type-Options: nosniff and
X-Frame-Options: DENY globally on every response (issue #151 / PR ~securityheaders).
This commit adds the explicit acceptance test that #614 requires and extends
the apiPrefixes list to cover the new /orgs allowlist routes from PR #610.

Changes:
- securityheaders.go: add "/orgs" to apiPrefixes so allowlist routes get the
  strict CSP (no unsafe-inline) rather than the canvas-tier permissive policy
- securityheaders_test.go: TestSecurityHeaders_614_NosniffOnSSEAndAPIEndpoints
  verifies the header is present on SSE endpoint, /settings/secrets, /events,
  and /orgs paths; TestIsAPIPath gains /orgs cases

Closes #614

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:02:18 +00:00