Repository navigation
fix(den-api): roll expired usage buckets forward on inference status reads - #3648
benjaminshafii wants to merge 2 commits into
Conversation
…reads The dashboard read the policy's current bucket verbatim, so an exhausted activity-based five-hour bucket kept showing 0% remaining with a reset time in the past for days after it expired. Reuse syncInferenceLimitPolicies during getInferenceStatus so status reads never report a stale window.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
There was a problem hiding this comment.
Warden security clearance: clear. No blocking findings (security or desktop↔den sync) in this diff (8cb0fafb498769cbe302101201b3c01bdbcd690a). Automated clearance satisfies the required-review gate only — a human still reviews and merges. Analysis run
…bjects on desktop Smoke health endpoint ran the image with DATABASE_URL pointing at 127.0.0.1 inside the container, so the DB was never reachable. den-api binds :8788, then Better Auth's oauth-provider seedResources queries oauthResource during createAuthContext and the unhandled rejection kills the process — so docker port never resolved and the failure surfaced as 'No such container'. Pre-existing on dev (verified by building the origin/dev image and reproducing the identical DrizzleQueryError) and on PRs #3648/#3640; dev pushes look green only because the step is gated on pull_request. Not fixed by suppressing the crash: better-auth caches the context promise (auth/base.mjs:8 initFn(options), awaited per request), so a rejected init stays rejected for the process lifetime. Swallowing it would yield a container that passes health checks with permanently dead auth, when crashing lets the orchestrator restart into a healthy state. The test premise was wrong, not the app. The den-api smoke now stands up mysql:8.4 on a dedicated network, waits for a real mysqladmin ping, applies the schema with the image's own bootstrap.js (the asset the preceding step already asserts exists), and only then boots den-api against it — so the smoke additionally proves migrations apply, including 0057. Containers are no longer --rm, so logs survive a crash and get dumped on failure. Other matrix images keep the original path. Also fixes Warden's third finding: the desktop parser rejected the new 'script' config object type, silently dropping saved scripts from the Connect inventory.
…sable, behind an org flag (#3645) * feat(codemode): vendor @openwork/codemode confined interpreter with input bindings Vendored from anomalyco/opencode@38e10eb (MIT; NOTICE + UPSTREAM.md record provenance and divergences). Drops the OpenAPI adapter. Adds a local divergence: CodeMode.execute({ bindings }) seeds read-only root-scope values through the plain-data boundary, enabling saved parameterized scripts. 244 upstream + bindings tests pass via bun test. * feat(den): execute_capability_script + saved script capabilities behind org flag - codemodeScripts org capability (metadata.capabilities, default off), wired like mcpConnections: admin routes, GET /v1/org, rollout helper. - /mcp/agent registers execute_capability_script only when enabled: builds a per-call tool tree (tools.den.<op> via invokeMcpOperation in-process + tools.<connection>.<tool> via executeExternalCapability, tool policy applied at build), runs the confined interpreter with host limits, and search_capabilities matches now carry scriptPath. - New 'script' config object type: executeMarketplaceCapability executes saved scripts with Ajv-validated typed input, least-authority tree restricted to declared requiredCapabilities, and fail-closed capability_unavailable (providerCallAttempted: false) when a declared tool is missing or policy-disabled. - codemode_run receipts for every run (source adhoc|plugin:<id>) + GET /v1/codemode-runs (admin: org-wide, member: own) with safe MCP tag. - drizzle migration 0057: codemode_run table + object_type enum value. * feat(den-web): Codemode scripts admin toggle + Script runs dashboard - den-admin-panel: codemodeScripts capability checkbox beside Connect. - New Script runs screen (nav gated by capabilities.codemodeScripts): status, truncated source, tool calls, duration, when. * test(evals): codemode-scripts voiceover spec + testkit fixes - codemode-scripts.slow.test.ts: the approved 7-frame narration as observable assertions — org flag gating, deterministic rail probes, a real model turn answering idle-workers as one script step, parallel cross-connection fan-out with witness proof, save through the capability rail, teammate discovery + typed re-run, receipts, dashboard vision check, and fail-closed tool-policy proof. - testkit: locally booted Den seeds DEN_BOOTSTRAP_ADMIN_EMAILS so specs can flip /v1/admin capabilities; MySQL probe honors OPENWORK_EVAL_MYSQL_URL instead of hardcoding 3306. * fix(codemode): close OAuth-review and prototype-pollution gaps, repair lockfile churn Warden findings on #3645: - buildExternalMcpToolTree listed tools for connections pending admin OAuth issuer review, constructing the OAuth provider (and possible token refresh) against a configuration an admin had blocked. Filtering is now the exported pure predicate isCodemodeEligibleConnection, which mirrors probeExternalMcpConnection: skip allDisabled AND oauthIssuerReviewRequiredAt. - restrictCodemodeToolTree used `tools[ns] ??= {}`, so a connection named __proto__ wrote a tool definition onto Object.prototype. Namespaces now reserve __proto__/constructor/prototype, and the tree is accumulated in Maps and materialized with Object.fromEntries (defineProperty semantics). CI repair — the earlier pnpm install churned the lockfile: - restored the xlsx CDN tarball integrity hash (ERR_PNPM_MISSING_TARBALL_INTEGRITY broke openwork-tests on both runners under --frozen-lockfile) - pinned codemode's acorn to 8.16.0, the version already resolved in the workspace, removing an unrelated 8.16.0 -> 8.18.0 bump; lockfile is now additions only (167/0, was 184/15) - reverted an unrelated msgpackr-extract allowBuilds entry; pnpm-workspace.yaml is now identical to dev - Dockerfile.den copies packages/codemode (den-api depends on it) Simplification: deleted buildCodemodeManifest, which was dead outside its own test — both tree builders already return their own manifest. * fix(deps): declare msgpackr-extract in allowBuilds effect -> msgpackr -> msgpackr-extract ships an optional native accelerator with a build script. Without an explicit allowBuilds decision, CI's `pnpm install --frozen-lockfile` fails with ERR_PNPM_IGNORED_BUILDS. msgpackr falls back to pure JS, so this is declared false rather than compiling native code in every CI run and image build. Reverting this in the previous commit was my error: it is a direct consequence of adding effect, not unrelated churn. * chore: trigger Warden analysis after draft to ready The PR was opened as a draft, so Warden skipped it and never re-analyzed the head that fixes its two findings (OAuth-review bypass, prototype pollution). Warden does not support the ready_for_review event by design — .github/workflows/warden.yml notes that draft-to-ready PRs get analyzed on their next push — so this empty commit is that push. * fix(ci): smoke den-api against a real migrated MySQL; accept script objects on desktop Smoke health endpoint ran the image with DATABASE_URL pointing at 127.0.0.1 inside the container, so the DB was never reachable. den-api binds :8788, then Better Auth's oauth-provider seedResources queries oauthResource during createAuthContext and the unhandled rejection kills the process — so docker port never resolved and the failure surfaced as 'No such container'. Pre-existing on dev (verified by building the origin/dev image and reproducing the identical DrizzleQueryError) and on PRs #3648/#3640; dev pushes look green only because the step is gated on pull_request. Not fixed by suppressing the crash: better-auth caches the context promise (auth/base.mjs:8 initFn(options), awaited per request), so a rejected init stays rejected for the process lifetime. Swallowing it would yield a container that passes health checks with permanently dead auth, when crashing lets the orchestrator restart into a healthy state. The test premise was wrong, not the app. The den-api smoke now stands up mysql:8.4 on a dedicated network, waits for a real mysqladmin ping, applies the schema with the image's own bootstrap.js (the asset the preceding step already asserts exists), and only then boots den-api against it — so the smoke additionally proves migrations apply, including 0057. Containers are no longer --rm, so logs survive a crash and get dumped on failure. Other matrix images keep the original path. Also fixes Warden's third finding: the desktop parser rejected the new 'script' config object type, silently dropping saved scripts from the Connect inventory. * fix(ci): pin the smoke MySQL image to an immutable digest Warden flagged mysql:8.4 as a mutable tag: a registry-side replacement would execute unreviewed code inside the PR workflow, sharing the smoke test's docker network. Pinned to the current multi-arch index digest (verified to contain linux/amd64 for CI runners, and confirmed to pull, boot, and pass the same mysqladmin readiness probe the step uses). Worth noting the tag had already moved: the digest cached locally from an earlier pull no longer matched the registry's 8.4, which is exactly the drift being closed.
Deploying openwork with
|
| Latest commit: |
c87cddd
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://86f0e8ac.openwork.pages.dev |
| Branch Preview URL: | https://fix-stale-inference-usage-bu.openwork.pages.dev |
|
Deployment failed for project openwork-diagnostics with the following error: Learn More: https://vercel.com/docs/limits#rate-limits |
|
Deployment failed for project openwork-app with the following error: Learn More: https://vercel.com/docs/limits#rate-limits |
|
Deployment failed for project openwork-den with the following error: Learn More: https://vercel.com/docs/limits#rate-limits |
There was a problem hiding this comment.
Warden security clearance: clear. No blocking findings (security or desktop↔den sync) in this diff (c87cddd9e592f44bfc2e3031d053afbfdbc6d52c). Automated clearance satisfies the required-review gate only — a human still reviews and merges. Analysis run
Problem
A user report (Victor, via openwork-web-app feedback) showed
/dashboard/inferencestuck at 0% remaining with a reset time days in the past. Verified against prod data: his five-hour bucket expired 2026-08-05 14:21 UTC ("Resets 10:21 PM" in UTC+8), but the dashboard kept reporting that exhausted, expired bucket four days later.Root cause:
getActiveUsageBucketsreadsinference_org_limit_policies.current_bucket_idverbatim. Buckets only roll forward lazily in the inference proxy (ensureBucket) on an actual request, so status reads can serve an expired activity-based bucket indefinitely. Enforcement was never wrong — the next request would have started a fresh window — but the dashboard told users they were locked out.Fix
getInferenceStatusalready treats status reads as a repair point (member key repair). This reuses the existingsyncInferenceLimitPoliciesthere, so expired buckets are rolled forward beforegetActiveUsageBucketsreports them. 8-line diff, no new logic — anchored windows chain fromanchor_at, activity-based windows restart at now, both exactly as the proxy already does.Tests
ee/apps/den-api/test/inference-status-stale-bucket.test.ts— seeds an org whose five-hour policy points at an exhausted bucket that expired 4 days ago, then asserts the status read reports a window containing now withusedAmount 0.bun test test/inference-status-stale-bucket.test.ts(den-api, local MySQL viaDATABASE_URL, schema pushed with drizzle-kit): 1 pass.tsc --noEmiton den-api: clean.test/delete-organization.test.tspasses;test/member-removal-rejoin.test.tshas 1 pre-existing failure that reproduces identically on pristineorigin/dev(role "admin" vs "member", unrelated).Validation lane
Local (bun + dockerized MySQL). Daytona not used for this server-side unit-level fix; coverage follows the existing
ee/apps/den-api/test/**real-DB pattern rather than a newevals/specsapp-driving spec, since the change is a single den-api status-read repair with no desktop surface. No testkit tape for that reason — repro steps above are exact.