Onboard Jimeng (火山视觉) as a `platform = "jimeng"` entry in the existing
sites/accounts console, reusing the CRUD/list/status UI while branching every
OpenAI-specific path so no other platform is affected. Jimeng is not
OpenAI-compatible (Volcengine V4 HMAC signing, async two-step generation, no
`/models` endpoint), so each branch keys off the single source of truth
`routes::admin::accounts::is_jimeng_platform` (case-insensitive).
Backend (src/routes/admin, src/services/jimeng):
- create_account → create_jimeng_account: AK lands in accounts.access_token; SK
+ region/host/sessionToken nest under extra_config.jimeng.*; credentialMode
forced to "apikey". No spawn_route_rebuild — Jimeng never enters the route
table.
- Secret protection: SK/sessionToken masked with •••• on list/get/update
responses (mask_account_extra_config); update_account rejects a masked
extra_config write-back so an edit round-trip can't clobber the real secret.
- verify_token → verify_jimeng_token: signs a sentinel GetResult probe via
JimengProvider::from_credentials(...).verify(). The Volcengine gateway checks
the V4 signature before the business layer, so a valid AK/SK is Authenticated
(business layer reports "not found") and a bad one yields an auth-class Code
(classify_verify_response / is_auth_error_code). Never calls /models, never
triggers generation.
- build_probe_plan short-circuits Jimeng (probe-now/-stream) with a BadRequest
pointing operators at the connection "验证" flow.
- runtimeHealth: Jimeng has no proxy_logs, so derive_jimeng_runtime_health reads
the latest jimeng background_tasks row; account_response_value_with_jimeng
applies it only when the site is Jimeng and ignores the override otherwise.
Frontend (src/web):
- Sites.tsx: add jimeng platform option; prefill visual.volcengineapi.com;
hide URL pool + auto-probe sections.
- Accounts.tsx: AK + SK(required) + region/host/session fields under the API Key
segment; "验证 AK/SK 签名" gates add; skip model sync / route rebuild.
- api.ts: verify-token payload carries secretKey/region/host/sessionToken.
Tests: unit coverage for credential nesting, secret masking, verify-response
classification, and regression guards proving non-Jimeng accounts ignore the
Jimeng health override and keep their proxy-log-derived health.
Also: stop tracking the stray root package-lock.json (repo installs via pnpm)
and gitignore it.
Exercise the decision logic and async driver beyond the happy path:
- decide() consults the business code before data.status (fatal and
retryable codes win over a bogus done status)
- not_found is terminal
- exponential backoff grows 1,2,4,8 and caps at 8x the interval
- backoff multiplier resets after a normal generating wait
- a retryable code followed by a fatal one stops the loop
Round out request-body validation and pin the polling parameters:
- all five allowed aspect ratios and both frame counts build
- prompt + image_urls together both land in the video body
- whitespace-only prompt is dropped but an image still satisfies one-of
- optional video fields are omitted when absent
- default poll interval/timeout values (image 2s/120s, video 8s/600s)
Add unit tests for error-code classification boundaries and response
decoding that the existing cases left open:
- 50516/50517 are capability-independent (retryable for image too)
- adjacent copyright codes 50518 (fatal) vs 50519 (retryable)
- full 50520..=50522 service-exception range is fatal
- 50500/50501 fatal-for-image / retryable-for-video via classify directly
- negative codes are fatal (no poll loop)
- base64 image output and absent (not just null) data field decode safely
A freshly registered user's auto-provisioned default downstream key must
start with a zero spend allowance, not unlimited. Two parts:
- DEFAULT_NEW_USER_MAX_COST_USD changes from 1.0 to 0.0, so new users have no
free spend until they top up or an admin raises the cap.
- The constant's docs now spell out the failure mode it guards: a NULL
max_cost is treated as *unlimited* by the proxy, so the column must always
be bound on INSERT. The registration path (routes::auth::register) and the
admin create-user path (routes::admin::users) both already include the
max_cost column and bind this constant, so both now persist 0.0 rather than
leaving NULL.
Tests (cargo test --lib routes::auth::tests, 10 passed): assert the constant
is 0.0, and run the registration key INSERT against an in-memory DB to verify
the stored max_cost is non-NULL and exactly 0.0. The agent suites
(approval/sandbox/bash_sandbox/toctou) stay green.
The test AppConfig initializer in routes::auth listed pixelle_video twice
(a merge artifact from stacking the Gemini and comfy-cloud branches), which
made the lib test binary fail to compile with E0062 and blocked `cargo test
--lib` for the whole crate. Remove the duplicate; lib tests build again.
Point the project-structure section at desktop/, clarify it is an
independent Cargo workspace (excluded from the backend crate), and link
to desktop/README.md for build steps.
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.
desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.
Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
The desktop/ subproject (added next) carries its own node_modules,
dist, and Cargo target dirs once built. Ignore them from the metapi
root so the 6.5G+ of generated output never enters the repo.
classify_tool_risk missed execute-class tools whose names use "run",
"terminal", "spawn" or "process" rather than bash/shell/exec/command — most
notably "RunInTerminal". Such a tool fell through to ReadOnly and would
therefore auto-allow without approval in Default/AcceptEdits mode, a real gap
in the permission gate. Broaden the Execute branch to catch them; over-
classifying as Execute only ever asks for more approval, so it is fail-safe.
This was already asserted by an existing unit test (classifies_execute_tools
expects RunInTerminal -> Execute); the test only became runnable after the
rebase onto refactor/rust-backend pulled in the AppConfig test-fixture repair
that had been blocking the lib test binary. Full agent_workbench unit suite
now 32/32; the four integration suites stay green.
The file tools (Read/Write/Edit) open paths inside the SDK process after the
can_use_tool callback validates them, leaving a check-vs-open window. Fully
closing it atomically needs openat2(RESOLVE_BENEATH) file tools replacing the
SDK's in-process ones (tracked as follow-up; the Edit tool's file-state-cache
contract makes that a non-trivial reimplementation).
This test pins the fact that makes the window unreachable under the current
model: an exploit needs a concurrent mutator, the only agent-controlled source
of concurrency is a backgrounded process, and bwrap's PID namespace
(--unshare-all) makes the bash command PID 1 — so the kernel reaps any
backgrounded job when the single bash call exits. The test backgrounds a writer
that would keep appending for ~2s and asserts its output stops the moment the
call returns. With sequential tool execution and per-session bind isolation,
no surviving concurrent mutator exists to win the race.
This is an honest mitigation with evidence, not a claim that the in-process
open is atomic.