Skip to content

Meter statically imported package export calls (package_static_call) - #1047

Merged
kody-bot merged 6 commits into
mainfrom
cursor/static-call-metering-b5b5
Jul 30, 2026
Merged

kody-bot merged 6 commits into
mainfrom
cursor/static-call-metering-b5b5

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 30, 2026 •

Copy link
Copy Markdown
Member

What

Static imports (import fn from 'kody:@scope/pkg/export') are the default for package reuse, but calls through them were invisible to usage metering. This PR adds a new member of the closed UsageEventType union, package_static_call, and emits one non-blocking usage event per call of a statically imported, function-valued package export:

  • userId = the calling user (host-side, from caller context)
  • entityId = the callee package id (the package whose export was imported)
  • durationMs = wall time of the call (through settlement for async functions)
  • outcome = success | error (whether the wrapped call threw/rejected — the error still propagates to the caller unchanged)

The metric also renders on the admin usage pages (curated adminUsageMetrics list, insights + per-user charts).

How

  • Bundler stamping (provenance, never sandbox strings). ensurePackageProxy knows which saved package a kody:@… specifier resolved to; it now emits a metered proxy (createMeteredPackageImportProxySource) that bakes the callee package id in and wraps function-valued exports (default + statically discovered named exports) in a transparent Proxy from the virtual runtime module. Only [[Call]] is trapped, so new on a wrapped class constructs the real class. export * from stays as a passthrough, so any export name the bundler cannot statically discover keeps working (it is just unmetered). Non-function exports pass through unchanged; dynamic-import proxies and root self-imports keep the plain proxy.
  • Export-name discovery. New module-export-names.ts collects runtime export names (Babel AST, skipping type-only exports, following relative export * from chains inside the file map). Missing a name is always safe (passthrough).
  • Sandbox → host bridge with host validation. Each call does one synchronous buffer push (~0 overhead, capped at 200 events per run to fit Analytics Engine's 250 writeDataPoint-per-invocation budget); the run wrapper in runBundledModuleWithRegistry flushes the batch over a new runtime-helper bridge while the sandbox RPC dispatchers are still live (a fire-and-forget RPC per call races dispatcher teardown and loses events — verified empirically), bounded by a short timeout so metering never owns the run's tail latency. The host (createPackageStaticCallMeterTools) only records events whose stamped id is one of the bundle's static dependency package ids recorded at build time — a strict subset of the packageStorage() grant set, which additionally grants the run's own package id and dynamic-import dependencies, neither of which the bundler ever stamps into a metered proxy. Everything else is silently dropped. Trust model stated honestly in code + docs: generated code still runs in the sandbox realm, so forgery can at worst inflate counts for packages the bundle already statically depends on.
  • Never blocks, never throws into the call path (same contract as recordUsage); flush failures are swallowed.

Testing

  • module-export-names.node.test.ts — declaration/specifier/destructuring/type-only/star-chain/cycle/parse-failure cases.
  • package-static-call-usage.node.test.ts — one event per batch entry with correct entityId/outcome/durationMs, forged/unknown ids dropped, invalid outcomes dropped, durations normalized, cumulative per-run cap, never-throws.
  • static-call-metering.workers.test.ts — real bundles through runBundledModuleWithRegistry against local D1 rollups: 5 calls ⇒ event_count: 5 with error_count: 1; a throwing export records error and still throws to the caller; new on a wrapped class constructs correctly and is not metered; non-function exports untouched; published-artifact imports metered; an in-sandbox forged stamp for a non-dependency package records nothing (with a post-settle re-check).
  • Full npm run validate green locally (fresh Playwright E2E run: 19 passed); re-validated after rebasing onto main @ c54a2686 (lean-invoke + deprecation shims) — all 138 overlapping unit test files pass post-rebase.
System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ c54a2686 · Head: a10ac251

Classification: extends — no new primitives; the package runtime's import rewriting, the execute runtime wrapper, the runtime-helper bridge surface, usage metering, and the admin usage read model each gain behavior.

Primitives touched

Primitive Group Impact
package-runtime runtime extends — static import proxies stamp callee package id and wrap function exports
capabilities-execute runtime extends — run wrapper flushes buffered call events; meter validated against static deps
mcp-server surfaces extends — new internal runtime-helper bridge (staticCallMeter) in the helper manifest
usage-metering storage extends — new package_static_call event type + validated host-side sink
app-ui surfaces composes — package_static_call added to admin usage chart series (new teal color)
rbac auth composes — admin-user-usage capability output schema lists the new metric

System map

A statically imported package export call flows from the sandbox call-metering wrapper through the run-end bridge flush into validated usage events, which the admin usage pages now chart.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	packageRuntime["package-runtime<br/>Package runtime"]:::extended
	capabilitiesExecute["capabilities-execute<br/>Capabilities execute runtime"]:::extended
	mcpServer["mcp-server<br/>MCP endpoint"]:::extended
	usageMetering["usage-metering<br/>Usage metering"]:::extended
	d1AppDb["d1-app-db<br/>D1 app database"]:::untouched
	appUi["app-ui<br/>Browser app (Remix 3)"]:::touched
	packageRuntime -->|"stamped metered import proxy + call wrapper"| capabilitiesExecute
	capabilitiesExecute -->|"buffered events flushed via staticCallMeter bridge"| mcpServer
	mcpServer -->|"validate stamped id against static bundle.dependencies ids"| usageMetering
	usageMetering -->|"package_static_call events (AE / usage_rollups fallback)"| d1AppDb
	appUi -->|"admin usage charts render package_static_call series"| d1AppDb
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Invariants

Per-user isolation holds: events are attributed to the calling user only, and stamped callee ids are validated host-side against the bundle's static dependency ids recorded at build time for that user's own bundle — sandbox-supplied ids can never attribute usage across users or to non-dependency packages.

Program report

Status: MERGED + DEPLOY-VERIFIED (agent side).

  • Merged: squash-merged by the conductor at 2026-07-30T12:09:59Z as f0d0b2b89bcec1cab35b1775ac6d94a33d088d87.
  • Deploy verified (agent half of the split):
    • ✅ Validate on main @ f0d0b2b8: success (run 30541425455).
    • 🚀 Deploy (production) on f0d0b2b8: success (run 30541718572).
    • https://heykody.dev/health → {"ok":true,"commitSha":"f0d0b2b89bcec1cab35b1775ac6d94a33d088d87"} — matches the merge commit.
  • Awaiting (conductor half): authenticated production probes — run a static package export call in production, then query kody_usage_events for package_static_call rows (blobs: [userId, eventType, entityId, outcome, timestamp], doubles: [durationMs, cpuMs, bytes]) — and confirm here.
  • Pre-merge summary: rebased cleanly onto main @ c54a2686 (lean-invoke + deprecation shims), no semantic conflicts; CI fully green on head a10ac251 (one Node round flaked on runner load in files outside this diff, retriggered and green). QA follow-ups all landed: static-deps-only validation set, package_static_call on admin usage pages, Analytics Engine capacity note. Reviewer findings triaged: two fixed (bounded flush rounds; cumulative 200-event cap for the AE budget), three declined with evidence (Proxy construct claim — pinned by test; thenable subscription — lazy side-effect risk; late-settling-events rehash — documented delivery-window limit), one low-value nitpick skipped (shared constant for the metric key; string literals with the closed union are the codebase pattern).
  • Blocked: nothing.
  • Remaining work: none on the agent side; track closes once the conductor's authenticated production probes confirm package_static_call rows in kody_usage_events.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added usage metering for statically imported package export calls with duration and success/error outcomes.
    • Introduced buffering/flushing runtime support and metered export proxying for non-root package imports.
    • Added admin insights support for the new metric, including chart series and theme color.
  • Documentation
    • Updated the usage metering spec for the new package_static_call event type, validation rules, and nesting guidance.
  • Tests
    • Added/expanded coverage for static-call metering behavior, forged-stamp dropping, export name discovery, and proxy/bundling behavior.

Comment thread packages/worker/src/package-runtime/runtime-source-modules.ts
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds package_static_call usage metering for statically imported saved-package exports, including export discovery, stamped proxies, runtime buffering and flushing, host-side grant validation, reporting support, integration tests, and architecture documentation.

Changes

Static package call metering

Layer / File(s) Summary
Usage contract, host sink, and reporting
packages/worker/src/usage/*, packages/worker/src/admin/..., packages/worker/client/charts/*, docs/contributing/architecture/usage-metering.md
Adds the package_static_call event, capped host recording and validation, admin/chart metric support, and metering documentation.
Export discovery and stamped proxies
packages/worker/src/package-runtime/module-export-names.*, packages/worker/src/package-runtime/module-graph-import-rewriting.ts, packages/worker/src/package-runtime/runtime-source-modules.ts
Discovers runtime exports and generates package-stamped proxies that wrap saved-package function exports.
Runtime metering and provider bridge
packages/worker/src/mcp/runtime-helper-manifest.ts, packages/worker/src/mcp/run-kody-registry.ts, packages/worker/src/package-runtime/runtime-source-modules.ts
Buffers call events, forwards them through a runtime bridge, and flushes them after sandbox execution.
End-to-end metering validation
packages/worker/src/package-runtime/static-call-metering.workers.test.ts
Verifies successful, failing, asynchronous, and forged-stamp scenarios against usage rollups.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BundledModule
  participant StaticCallMeter
  participant RuntimeBridge
  participant PackageStaticCallMeterTools
  participant UsageSink
  BundledModule->>StaticCallMeter: report packageId, durationMs, outcome
  BundledModule->>StaticCallMeter: flush buffered events
  StaticCallMeter->>RuntimeBridge: record event batch
  RuntimeBridge->>PackageStaticCallMeterTools: forward event payload
  PackageStaticCallMeterTools->>UsageSink: validate and record package_static_call
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: metering statically imported package export calls.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/static-call-metering-b5b5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-1047.kody-a99.workers.dev

Worker: kody-pr-1047
D1: kody-pr-1047-db
KV: kody-pr-1047-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/worker/src/package-runtime/module-graph.node.test.ts (1)

821-824: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten the named-export assertion.

toContain('as add') passes on almost any re-export shape. Asserting the emitted export { … as add } line (or matching the generated local name) makes the test actually pin the codegen contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/package-runtime/module-graph.node.test.ts` around lines
821 - 824, Update the named-export assertion in the module-graph test to match
the complete emitted export statement, including the generated local export
expression and its alias “add,” rather than asserting only the generic substring
“as add.” Keep the existing proxy generation assertion unchanged.
packages/worker/src/package-runtime/static-call-metering.workers.test.ts (1)

351-364: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The forged-stamp drop assertion is order-dependent.

waitFor succeeds the instant the count reaches 2, so a forged event recorded even slightly later would not fail the test — the negative assertion relies entirely on flush ordering. Re-reading once after the wait settles (or asserting on a per-entity query for pkg-not-a-dependency) makes this security-relevant behavior actually pinned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/package-runtime/static-call-metering.workers.test.ts`
around lines 351 - 364, The forged-event assertion in the static call metering
test is order-dependent because waitFor can finish before the forged event is
processed. After the rollup wait settles, perform a follow-up read or per-entity
query for “pkg-not-a-dependency” and assert that the forged event was not
recorded, while preserving the existing granted-event count assertions.
packages/worker/src/mcp/run-kody-registry.ts (1)

1063-1070: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Awaited flush has no timeout — a stalled bridge holds the run open.

The finally awaits flush() unbounded, so a slow/hung metering RPC extends every run until the executor timeout. This contradicts the never-block-the-observed-path contract stated for run records (Line 826). Bounding it keeps the semantics (flush while dispatchers are live) without letting metering own the run's tail latency.

Note also that events reported by promises settling after the entrypoint resolves land after the flush and are silently lost — worth stating in usage-metering.md alongside the overflow-drop note.

🔧 Proposed fix
     if (typeof __kodyStaticCallMeter !== 'undefined' && __kodyStaticCallMeter != null) {
-      try { await __kodyStaticCallMeter.flush(); } catch {}
+      try {
+        await Promise.race([
+          __kodyStaticCallMeter.flush(),
+          new Promise((resolve) => setTimeout(resolve, 2000)),
+        ]);
+      } catch {}
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/mcp/run-kody-registry.ts` around lines 1063 - 1070, Bound
the await of __kodyStaticCallMeter.flush() in the finally block of the run flow
so a stalled metering bridge cannot hold the run open, while retaining the
best-effort flush and swallowing timeout or flush errors. Document in
usage-metering.md that events from promises settling after entrypoint completion
occur after the final flush and are therefore dropped, alongside the existing
overflow-drop behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/src/package-runtime/runtime-source-modules.ts`:
- Around line 265-273: Replace the instanceof Promise check in the
result-handling flow with thenable detection by validating that result is
non-null and has a callable then property. Attach success and error settlement
callbacks through that then method, return the original result, and keep
synchronous success recording only for non-thenable values.

In `@packages/worker/src/usage/package-static-call-usage.ts`:
- Line 36: Reduce the maxEventsPerBatch cap from 1000 to a value below the 250
writeDataPoint limit, leaving headroom for other usage writes in the same
invocation. Preserve the existing batching behavior and per-event writes while
ensuring a batch cannot exhaust the Analytics Engine write allowance.

---

Nitpick comments:
In `@packages/worker/src/mcp/run-kody-registry.ts`:
- Around line 1063-1070: Bound the await of __kodyStaticCallMeter.flush() in the
finally block of the run flow so a stalled metering bridge cannot hold the run
open, while retaining the best-effort flush and swallowing timeout or flush
errors. Document in usage-metering.md that events from promises settling after
entrypoint completion occur after the final flush and are therefore dropped,
alongside the existing overflow-drop behavior.

In `@packages/worker/src/package-runtime/module-graph.node.test.ts`:
- Around line 821-824: Update the named-export assertion in the module-graph
test to match the complete emitted export statement, including the generated
local export expression and its alias “add,” rather than asserting only the
generic substring “as add.” Keep the existing proxy generation assertion
unchanged.

In `@packages/worker/src/package-runtime/static-call-metering.workers.test.ts`:
- Around line 351-364: The forged-event assertion in the static call metering
test is order-dependent because waitFor can finish before the forged event is
processed. After the rollup wait settles, perform a follow-up read or per-entity
query for “pkg-not-a-dependency” and assert that the forged event was not
recorded, while preserving the existing granted-event count assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bcb9023-f326-4780-ba02-4ff2979d18ff

📥 Commits

Reviewing files that changed from the base of the PR and between 60badba and cc211ee.

📒 Files selected for processing (13)
  • docs/contributing/architecture/usage-metering.md
  • packages/worker/src/mcp/run-kody-registry.node.test.ts
  • packages/worker/src/mcp/run-kody-registry.ts
  • packages/worker/src/mcp/runtime-helper-manifest.ts
  • packages/worker/src/package-runtime/module-export-names.node.test.ts
  • packages/worker/src/package-runtime/module-export-names.ts
  • packages/worker/src/package-runtime/module-graph-import-rewriting.ts
  • packages/worker/src/package-runtime/module-graph.node.test.ts
  • packages/worker/src/package-runtime/runtime-source-modules.ts
  • packages/worker/src/package-runtime/static-call-metering.workers.test.ts
  • packages/worker/src/usage/package-static-call-usage.node.test.ts
  • packages/worker/src/usage/package-static-call-usage.ts
  • packages/worker/src/usage/record-usage.ts

Comment on lines +265 to +273
if (result instanceof Promise) {
result.then(
() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'success'),
() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'error'),
);
return result;
}
__kodyRecordStaticPackageCall(packageId, startedAtMs, 'success');
return result;

@coderabbitai coderabbitai Bot Jul 30, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

instanceof Promise misses thenables and cross-realm promises.

Exports returning a custom thenable (or a promise from another realm — bundled artifact modules can be evaluated in a different module context) get recorded as success synchronously, with durationMs excluding settlement and later rejections counted as successes. Duck-typing then matches the documented "through settlement" semantics.

🔧 Proposed fix
-			if (result instanceof Promise) {
-				result.then(
+			if (result != null && typeof result.then === 'function') {
+				Promise.resolve(result).then(
 					() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'success'),
 					() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'error'),
 				);
 				return result;
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (result instanceof Promise) {
result.then(
() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'success'),
() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'error'),
);
return result;
}
__kodyRecordStaticPackageCall(packageId, startedAtMs, 'success');
return result;
if (result != null && typeof result.then === 'function') {
Promise.resolve(result).then(
() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'success'),
() => __kodyRecordStaticPackageCall(packageId, startedAtMs, 'error'),
);
return result;
}
__kodyRecordStaticPackageCall(packageId, startedAtMs, 'success');
return result;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/package-runtime/runtime-source-modules.ts` around lines
265 - 273, Replace the instanceof Promise check in the result-handling flow with
thenable detection by validating that result is non-null and has a callable then
property. Attach success and error settlement callbacks through that then
method, return the original result, and keep synchronous success recording only
for non-thenable values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional, and now documented in the code (46290ea): subscribing to an arbitrary user thenable would invoke its then() from the metering path, which can trigger lazy side effects — query-builder-style thenables execute work when first awaited, so Promise.resolve(result).then(...) could run user work the caller never requested. The wrapper's hard rule is that it must never change call-path behavior, and that outranks settlement-accurate durations for exotic return values. Cross-realm promises don't arise here: all bundle modules (including composed published artifacts) evaluate in one isolate/module context, so async exports return same-realm native promises. Non-promise thenables record at return time, which is the safe degradation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped: comment is from another GitHub bot.

Comment thread packages/worker/src/usage/package-static-call-usage.ts Outdated
cursor Bot pushed a commit that referenced this pull request Jul 30, 2026
…gling-promise limit

Async calls that settle while a flush RPC is in flight now get delivered
by a follow-up flush round; calls still pending when the run's entrypoint
finishes remain unmetered by design (metering never extends a run's
lifetime). Addresses the Bugbot review on PR #1047.
Comment thread packages/worker/src/package-runtime/runtime-source-modules.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d14159f. Configure here.

const events = __kodyStaticCallMeterEvents.splice(0, __kodyStaticCallMeterEvents.length);
await ${staticCallMeterRuntimeBridgeProviderName}.record({ events });
}
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flush drops remaining buffered events

Medium Severity

The sandbox flush helper stops after three bridge rounds and returns immediately when the buffer is empty between rounds. Static calls whose metering report runs during a later in-flight round (or after the third) can remain in the buffer when flush finishes, so those calls consume the per-run cap but never become package_static_call usage on the host.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d14159f. Configure here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By design, and a restatement of the delivery-window limit already documented in usage-metering.md and the prelude comment: events reported after the bounded flush window (calls whose promises settle later than the entrypoint plus in-flight flush rounds) are intentionally not delivered — metering never extends a run's lifetime, and the whole flush is additionally capped by the wrapper's 2s race. The round bound must stay finite: an unbounded "loop until empty" could be held open indefinitely by a pathological stream of late-settling promises, which is exactly the tail-latency ownership the previous review round asked us to prevent. The cap-slot consumption by undelivered events is harmless — the cap exists to bound the Analytics Engine write budget, and undelivered events consume none of it.

Stamp the callee saved-package id into the bundler-generated static import
proxy, wrap function-valued exports (default + statically discovered named
exports) in a transparent call-metering Proxy, and report one non-blocking
usage event per call through a sandbox->host runtime bridge. The host
validates the stamped id against the same bundler-provenance grant set as
packageStorage before recording, and drops mismatches silently.
Per-call fire-and-forget RPCs race dispatcher teardown (rejected with
'Execution has already completed'), so each metered call now does one
synchronous buffer push and the run wrapper flushes the batch over the
bridge while the dispatchers are still live. Adds node + workers E2E
coverage and documents the event type in usage-metering.md.
…gling-promise limit

Async calls that settle while a flush RPC is in flight now get delivered
by a follow-up flush round; calls still pending when the run's entrypoint
finishes remain unmetered by design (metering never extends a run's
lifetime). Addresses the Bugbot review on PR #1047.
Analytics Engine allows 250 writeDataPoint calls per invocation, so the
per-run cap (enforced cumulatively sandbox-side and host-side) leaves
headroom for the run's other usage events. The wrapper's flush await is
raced against a short timeout so a slow metering bridge never owns the
run's tail latency. Pins Proxy construct passthrough with a workers test
(new on a wrapped class) and tightens review-flagged assertions.
…y note

- Validate stamped static-call ids against the bundle's static dependency
  package ids only (strictly tighter than the packageStorage grant set,
  which also includes the run's own package id and dynamic-import deps);
  code comments and usage-metering.md now state the exact set.
- Surface package_static_call on admin usage pages (curated metric list,
  AdminUsageMetric union, admin-user-usage capability schema, chart
  series with a new teal palette entry).
- Document that the 200-events-per-run cap shares the ~250
  writeDataPoint-per-invocation Analytics Engine budget with other usage
  events like outbound_fetch; aggregation is the eventual fix.
@cursor
cursor Bot force-pushed the cursor/static-call-metering-b5b5 branch from d14159f to 20353bf Compare July 30, 2026 11:52
execute-typecheck startup-budget tests and the disaster-recovery
wrangler contract test timed out on a loaded runner; both pass locally
on this head in seconds.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/worker/src/package-runtime/static-call-metering.workers.test.ts (1)

126-138: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a shared exported constant for the package static-call metric key.

'package_static_call' appears in both the test SQL and the usage/record/admin type lists; exporting it from one usage definition would prevent this query from drifting away from the source of truth if the key is renamed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/package-runtime/static-call-metering.workers.test.ts`
around lines 126 - 138, The readStaticCallRollup query should use a shared
exported constant for the package static-call metric key instead of the
hardcoded 'package_static_call' string. Export the constant from the canonical
usage definition, import it into this test, and interpolate or bind it in the
metric filter so all references remain synchronized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/worker/src/package-runtime/static-call-metering.workers.test.ts`:
- Around line 126-138: The readStaticCallRollup query should use a shared
exported constant for the package static-call metric key instead of the
hardcoded 'package_static_call' string. Export the constant from the canonical
usage definition, import it into this test, and interpolate or bind it in the
metric filter so all references remain synchronized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ae7b20f2-f294-42fe-8693-1009562c46cc

📥 Commits

Reviewing files that changed from the base of the PR and between 3c4de38 and 20353bf.

📒 Files selected for processing (18)
  • docs/contributing/architecture/usage-metering.md
  • packages/worker/client/charts/chart-theme.ts
  • packages/worker/client/charts/usage-metric-series.ts
  • packages/worker/src/admin/user-usage-data.ts
  • packages/worker/src/app/loader-data.ts
  • packages/worker/src/mcp/capabilities/admin/admin-user-usage.ts
  • packages/worker/src/mcp/run-kody-registry.node.test.ts
  • packages/worker/src/mcp/run-kody-registry.ts
  • packages/worker/src/mcp/runtime-helper-manifest.ts
  • packages/worker/src/package-runtime/module-export-names.node.test.ts
  • packages/worker/src/package-runtime/module-export-names.ts
  • packages/worker/src/package-runtime/module-graph-import-rewriting.ts
  • packages/worker/src/package-runtime/module-graph.node.test.ts
  • packages/worker/src/package-runtime/runtime-source-modules.ts
  • packages/worker/src/package-runtime/static-call-metering.workers.test.ts
  • packages/worker/src/usage/package-static-call-usage.node.test.ts
  • packages/worker/src/usage/package-static-call-usage.ts
  • packages/worker/src/usage/record-usage.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/worker/src/usage/record-usage.ts
  • docs/contributing/architecture/usage-metering.md

@kody-bot
kody-bot merged commit f0d0b2b into main Jul 30, 2026
10 checks passed
@kody-bot
kody-bot deleted the cursor/static-call-metering-b5b5 branch July 30, 2026 12:10
@kody-bot

Copy link
Copy Markdown
Member

Conductor production verification ✅ (deployed in f0d0b2b): package_static_call is emitting to kody_usage_events with correct attribution and duration — a probe static call of sentry-triage/get-issue-state produced exactly one event (outcome success, 22 ms, matching the caller-side measurement), and organic traffic (5 static google calls, avg 441.6 ms) is already being metered. Metering track complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants