Skip to content

Add a usage/event ledger for per-namespace metering, audit, and on-chain attribution #10

Description

@harrymove-ctrl

Context

The hosted Worker has zero metering or audit. No usage table exists in D1 (migrations 00010006 cover accounts, namespaces, growth loops, schedules — none record per-action usage). Authed users are hardcoded quota: { ... unlimited: true } (apps/api/src/worker.ts:1503) and the only counter is the 1/day demo-IP quota (consumeDemoQuota, worker.ts:4296; table contextmem_demo_limits). The roadmap calls this ledger "the shared spine feeding billing, observability, AND on-chain attribution — build it first."

Goal / user story

As the platform owner, I want every billable/auditable action (extract, recall, remember, mcp_read, ai_query, chat) recorded as a durable per-namespace event so I can meter usage, power an observability dashboard, enforce quotas, and later anchor on-chain attribution receipts — built once.

Acceptance criteria

  • New migration 0007_usage_events.sql creates contextmem_usage_events (id, ts, owner_id, namespace, action with a CHECK on the enum, units INTEGER, request_id, source, metadata_json) + indexes on (namespace, ts) and (owner_id, ts).
  • A single recordUsage(env, { ownerId, namespace, action, units, requestId, source, metadata }) helper inserts one row, never throws (try/catch + ctx.waitUntil), and is unit-tested.
  • Events are emitted from the hosted handlers: extraction/build (createNamespaceBuildJob), recall (/api/memwal/recall, worker.ts:631), chat (memwalChat, worker.ts:634/3498), ai-query (aiQueryRun, worker.ts:1349), hosted MCP reads (/mcp, isMcpPath worker.ts:800), and demo extractions.
  • Each event carries a stable request_id for idempotency so retries don't double-count.
  • GET /api/usage?namespace=&from=&to= returns aggregated counts/units per action (owner-scoped) using the index, not a full-table scan.
  • No request bodies or secrets are stored: a test asserts Authorization/delegate keys never leak into metadata_json.

Implementation notes

Reuse the D1 upsert pattern from consumeDemoQuota (worker.ts:4304). Wrap every emit in ctx.waitUntil(recordUsage(...)) so logging never blocks responses. Define the action union in a shared module so packages/mcp tool calls can emit the same events. Keep the table append-only; aggregate with SELECT action, COUNT(*), SUM(units) ... GROUP BY action. A daily rollup table can come later if volume grows — not required now. No wrangler.jsonc change (uses the existing CONTEXTMEM_DB binding).

Sui Overflow angle

This ledger is the precondition for on-chain attribution receipts: each metered extract/recall/remember becomes the exact payload a Talus/Nexus node anchors on Sui (caller, namespace, action, artifactDigest, ts). Building it now turns the later receipt work into a thin write-through over existing rows rather than a from-scratch capture layer during the demo crunch.

Dependencies

Owner-scoping of /api/usage depends on the "hosted accounts + owner-scoped authorization" issue (otherwise ownerId is spoofable). The on-chain attribution-receipt issue consumes this ledger.

Part of the ContextMEM roadmap (#4) • Sui Overflow build.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Demo-blocking: required for a working Sui Overflow demoagent-talusTalus/Nexus agent integration and attribution receiptsfeatureUser- or agent-facing capabilityplatformBackend platform plumbing: Worker, D1, queues, secrets, metering

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions