Skip to content

monitoring-ru-consumption: add Node.js / @azure/cosmos v4 example #117

@jaydestro

Description

@jaydestro

#2 — Rubric Gap Analysis — monitoring-ru-consumption enhancement: Add Node.js requestCharge example

Field Value
Type Rule Enhancement
Target Rule monitoring-ru-consumption
Severity HIGH
Source SCOPE Node.js AI Food Delivery — Monitoring & Diagnostics, Criterion q_9_metrics, Phase 3 static + Phase 6a validation (2026-04-17)
Labels SCOPE, enhancement, rule:monitoring
Related Closed #56 (C#-only examples added)

Summary

The monitoring-ru-consumption rule has solid C# examples (operation-level response.RequestCharge, per-page query tracking, custom RequestHandler, Azure Monitor KQL) but no Node.js / TypeScript example. 64 of 65 SCOPE Node.js runs ship Cosmos DB code with zero RU instrumentation, including 24/25 AK runs. This is the Node.js analogue of #56, which closed the .NET path.

Rubric Gap Analysis

Hit rate by cohort (criterion q_9_metrics — HIT = requestCharge captured in at least one code path). Measured 2026-04-17 by scanning all .js/.ts/.mjs/.cjs files under each of the 65 run directories (node_modules excluded) for the literal string requestCharge:

Cohort Hits Misses Hit rate
AK (25 runs) 1 24 4%
Control (40 runs) 0 40 0%
All 1 64 1.5%

The single AK hit was P04/R04 (Claude Sonnet 4.6 + CosmosDB Agent Kit), which emits 12 requestCharge references across cosmosService.js (create / read / query / patch / fetchNext) and 7 across orders.test.js. This run demonstrates what adoption looks like when the rule lands — every operation type wraps into a logRu(op, ru) helper.

The skill does not land in the other 24 AK runs because the rule never shows the Node.js API surface. Agents treat requestCharge as a concern of the .NET runtime.

Evidence

Existing Rule Coverage

The current rule (skills/cosmosdb-best-practices/rules/monitoring-ru-consumption.md, verified against AzureCosmosDB/cosmosdb-agent-kit@main on 2026-04-17) contains 4 C# code blocks (operation-level tracking, per-page query tracking, RequestHandler middleware, Azure Monitor KQL) and 0 blocks in any other language. Full-category gap — all monitoring-* and sdk-diagnostics rules are C#-only:

Rule C# blocks Node.js blocks Java blocks Python blocks
monitoring-ru-consumption 4 0 0 0
monitoring-azure-monitor 4 0 0 0
monitoring-diagnostic-logs 4 0 0 0
sdk-diagnostics 3 0 0 0

Existing C# headline:

// existing C# example from the rule
var response = await _container.ReadItemAsync<Order>(orderId, new PartitionKey(customerId));
_logger.LogDebug("Read order {OrderId}: {RU} RU, {Latency}ms",
    orderId, response.RequestCharge,
    response.Diagnostics.GetClientElapsedTime().TotalMilliseconds);

Missing Anti-Pattern (Node.js — representative of 64/65 runs)

// instance 1 — control run
const { resources } = await ordersContainer.items
  .query(ordersQuery, { partitionKey: userId })
  .fetchAll();
return resources;   // requestCharge not logged, not tracked, not alerted
// instance 2 — agent-kit run
const response = await this.container.items.query<OrderDocument>(spec, opts).fetchAll();
return response.resources;   // response.requestCharge available but unused
// instance 3 — agent-kit run, point read
const { resource } = await container.item(orderId, userId).read();
return resource;   // no RU, no latency, no telemetry

Correct Pattern (proposed Node.js block to add to the rule)

import { Container, FeedResponse } from '@azure/cosmos';
import { logger, metrics } from './telemetry';

// ❌ Anti-pattern: discard requestCharge and diagnostics
export async function getOrderBad(container: Container, id: string, userId: string) {
  const { resource } = await container.item(id, userId).read();
  return resource;
}

// ✅ Preferred: capture requestCharge on every call
export async function getOrder(container: Container, id: string, userId: string) {
  const response = await container.item(id, userId).read();
  logger.debug({
    op: 'ReadItem',
    container: container.id,
    ru: response.requestCharge,
    statusCode: response.statusCode,
    activityId: response.activityId,
  }, 'cosmos.readItem');
  metrics.histogram('cosmos.readItem.ru').record(response.requestCharge, { container: container.id });
  return response.resource;
}

// ✅ Queries: accumulate RU across pages (single-page tracking undercounts paged results)
export async function getCustomerOrders(container: Container, userId: string) {
  const iterator = container.items.query<OrderSummary>({
    query: 'SELECT c.id, c.userId, c.status, c.total, c.createdAt FROM c WHERE c.userId = @u ORDER BY c.createdAt DESC',
    parameters: [{ name: '@u', value: userId }],
  }, { partitionKey: userId });

  const results: OrderSummary[] = [];
  let totalRU = 0;
  while (iterator.hasMoreResults()) {
    const page: FeedResponse<OrderSummary> = await iterator.fetchNext();
    results.push(...page.resources);
    totalRU += page.requestCharge;
  }
  logger.info({ op: 'Query', container: container.id, count: results.length, totalRU }, 'cosmos.query.total');
  if (totalRU > 100) {
    logger.warn({ totalRU, count: results.length }, 'cosmos.query.expensive');
  }
  metrics.histogram('cosmos.query.ru').record(totalRU, { container: container.id });
  return results;
}

// ✅ Writes: create/upsert/replace/patch/delete all expose requestCharge
export async function createOrder(container: Container, order: Order) {
  const response = await container.items.create(order);
  logger.debug({ op: 'CreateItem', ru: response.requestCharge }, 'cosmos.createItem');
  return response.resource;
}

requestCharge surface in @azure/cosmos v4

Operation Result type RU field
container.item(id, pk).read() ItemResponse<T> response.requestCharge
container.items.create(doc) ItemResponse<T> response.requestCharge
container.items.upsert(doc) ItemResponse<T> response.requestCharge
container.item(id, pk).replace(doc) ItemResponse<T> response.requestCharge
container.item(id, pk).patch(ops) ItemResponse<T> response.requestCharge
container.item(id, pk).delete() ItemResponse<T> response.requestCharge
container.items.query(...).fetchAll() FeedResponse<T> response.requestCharge
container.items.query(...).fetchNext() FeedResponse<T> per page sum across pages
container.items.bulk(ops) OperationResponse[] op.requestCharge per operation

Runtime Validation

Executed _agent_files/phase-6a/scope-node-002-validation.js against the Cosmos DB emulator (@azure/cosmos v4.5.1, emulator @ https://localhost:8081, 2026-04-17). Confirms every operation in the surface table returns a numeric requestCharge:

Operation RU returned typeof requestCharge === 'number'
container.items.create(doc) 6.67
container.items.upsert(doc) 10.67
container.item(id, pk).read() 1.00
container.item(id, pk).replace(doc) 10.67
container.item(id, pk).patch(ops) 10.79
container.items.query(...).fetchAll() 2.82
container.items.query(...).fetchNext() — page 1 2.82
container.items.query(...).fetchNext() — summed across pages 2.82
container.items.bulk([op1, op2]) [6.29, 6.29] per op
container.item(id, pk).delete() 6.67

All 10 API shapes in the proposed surface table return requestCharge as a top-level numeric property. No operation requires a separate diagnostics call to obtain the RU charge, which keeps the correct-pattern block concise.

Runner + raw output: _agent_files/phase-6a/scope-node-002-validation.js and scope-node-002-validation.json.

Rule Contradiction Scan

Rule Interaction
sdk-diagnostics Complementary — Node.js analogue is response.diagnostics; cross-link
query-use-projections (#1 draft) Reinforcing — requestCharge is how you verify a projection helped on YOUR data
query-point-reads (#3 draft) Reinforcing — point-read RU savings visible only via requestCharge
sdk-retry-429 Complementary

No contradictions.

Documentation Cross-reference

Recommended cosmosdb-agent-kit Fix

  1. Add a Node.js / TypeScript example block alongside the existing C# examples.
  2. Add the requestCharge surface table above to the rule.
  3. Cross-link to sdk-diagnostics for the Node.js response.diagnostics pattern.

Metadata

Metadata

Assignees

Labels

SCOPEIssues generated by SCOPE toolenhancementNew feature or requestrule:monitoringMonitoring rules (monitoring-*)

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions