diff --git a/acceptance/API_COVERAGE.md b/acceptance/API_COVERAGE.md index 4c665d93a..f53fab73a 100644 --- a/acceptance/API_COVERAGE.md +++ b/acceptance/API_COVERAGE.md @@ -35,15 +35,15 @@ These run in the `wire` profile in addition to smoke coverage: These require target-specific capabilities. They run when the capability is enabled or derived from the configured target, and the selected profile includes the spec: -| Capability | Enable with | Covered APIs / behavior | -| ---------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Admin | `ACCEPTANCE_ENABLE_ADMIN=true` plus admin URL/API key | admin status, API key protection, tenant read/create/patch/upsert/delete/health, tenant migration run/reset-current/jobs, metrics config read/update, queue migration enabled/disabled contract, queue/migration validation, JWKS validation/add/deactivate/status, orphan scan/sync validation, S3 credential create/authenticate/list/delete | -| CDN | `ACCEPTANCE_ENABLE_CDN=true` | `/cdn/:bucket/*` cache purge for existing objects and documented missing-object error | -| Render | `ACCEPTANCE_ENABLE_RENDER=true` | public, authenticated, and signed image transformation routes, `webp` output format, non-image input errors, invalid transformation validation | -| RLS | `ACCEPTANCE_ENABLE_RLS_SETUP=true` plus anon/authenticated keys and RLS resource config | authenticated allow and anon deny for read/write on configured policies | -| Path edges | Derived from `ACCEPTANCE_TARGET` and `STORAGE_BACKEND` | list-v2 preservation for object names with empty path segments; local S3/MinIO backends skip this case directly | -| Vector | `ACCEPTANCE_ENABLE_VECTOR=true` with local pgvector or S3 Vectors configured | vector bucket pagination, index pagination, vector list pagination, metadata filter keys, non-filterable metadata rejection, default distance omission, cosine and euclidean query behavior, put/get/list/query/delete lifecycle | -| Iceberg | `ACCEPTANCE_ENABLE_ICEBERG=true` | analytics bucket, catalog config, namespace (create/list/load/head/drop, missing load/drop, upsert on re-create, drop blocked when non-empty), table create/list/page-size/load/head/drop, missing load/drop, commit success/conflict | +| Capability | Enable with | Covered APIs / behavior | +| ---------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Admin | `ACCEPTANCE_ENABLE_ADMIN=true` plus admin URL/API key | admin status, API key protection, tenant read/create/patch/upsert/delete/health, tenant migration run/reset-current/jobs, queue migration enabled/disabled contract, queue/migration validation, JWKS validation/add/deactivate/status, orphan scan/sync validation, S3 credential create/authenticate/list/delete | +| CDN | `ACCEPTANCE_ENABLE_CDN=true` | `/cdn/:bucket/*` cache purge for existing objects and documented missing-object error | +| Render | `ACCEPTANCE_ENABLE_RENDER=true` | public, authenticated, and signed image transformation routes, `webp` output format, non-image input errors, invalid transformation validation | +| RLS | `ACCEPTANCE_ENABLE_RLS_SETUP=true` plus anon/authenticated keys and RLS resource config | authenticated allow and anon deny for read/write on configured policies | +| Path edges | Derived from `ACCEPTANCE_TARGET` and `STORAGE_BACKEND` | list-v2 preservation for object names with empty path segments; local S3/MinIO backends skip this case directly | +| Vector | `ACCEPTANCE_ENABLE_VECTOR=true` with local pgvector or S3 Vectors configured | vector bucket pagination, index pagination, vector list pagination, metadata filter keys, non-filterable metadata rejection, default distance omission, cosine and euclidean query behavior, put/get/list/query/delete lifecycle | +| Iceberg | `ACCEPTANCE_ENABLE_ICEBERG=true` | analytics bucket, catalog config, namespace (create/list/load/head/drop, missing load/drop, upsert on re-create, drop blocked when non-empty), table create/list/page-size/load/head/drop, missing load/drop, commit success/conflict | ## Intentionally Gated diff --git a/acceptance/specs/admin.test.ts b/acceptance/specs/admin.test.ts index 2b93564c9..d0a7136b4 100644 --- a/acceptance/specs/admin.test.ts +++ b/acceptance/specs/admin.test.ts @@ -9,15 +9,6 @@ interface TenantSummary { id: string } -interface MetricConfigEntry { - enabled: boolean - name: string -} - -interface MetricsConfigResponse { - metrics: MetricConfigEntry[] -} - interface TenantFeatures { icebergCatalog?: { enabled?: boolean @@ -103,7 +94,7 @@ describeAcceptance( requires: ['admin'], }, () => { - it('covers tenant reads, migration and queue validation, metrics config, JWKS validation, orphan validation, and S3 credential CRUD', async () => { + it('covers tenant reads, migration and queue validation, JWKS validation, orphan validation, and S3 credential CRUD', async () => { const config = getAcceptanceConfig() const client = createAdminClient() const headers = { @@ -129,12 +120,6 @@ describeAcceptance( }) expect(migrations.json).toBeTruthy() - const metrics = await client.request('GET', '/metrics/config', { - expectedStatus: 200, - headers, - }) - expect(Array.isArray(metrics.json?.metrics)).toBe(true) - await client.request('GET', '/migrations/failed?cursor=not-a-number', { expectedStatus: 400, headers, @@ -468,8 +453,6 @@ describeAcceptance( ) } - await expectMetricsConfigMutation(client, headers) - await client.request('DELETE', `/tenants/${createdTenantId}`, { expectedStatus: 204, headers, @@ -515,70 +498,6 @@ async function resolveTenantId( return tenantId } -async function expectMetricsConfigMutation( - client: AcceptanceHttpClient, - headers: Record -) { - const metrics = await client.request('GET', '/metrics/config', { - expectedStatus: 200, - headers, - }) - const metric = metrics.json?.metrics[0] - expect(metric).toBeTruthy() - if (!metric) { - throw new Error('Admin metrics config did not include any registered metrics') - } - - const metricToRestore = { - enabled: metric.enabled, - name: metric.name, - } - const toggledMetric = { - enabled: !metric.enabled, - name: metric.name, - } - - try { - const toggledMetrics = await client.request('PUT', '/metrics/config', { - body: { - metrics: [toggledMetric], - }, - expectedStatus: 200, - headers, - }) - expect( - toggledMetrics.json?.metrics.find((candidate) => candidate.name === metric.name)?.enabled - ).toBe(toggledMetric.enabled) - - const persistedMetrics = await client.request('GET', '/metrics/config', { - expectedStatus: 200, - headers, - }) - expect( - persistedMetrics.json?.metrics.find((candidate) => candidate.name === metric.name)?.enabled - ).toBe(toggledMetric.enabled) - } finally { - const restoredMetrics = await client.request('PUT', '/metrics/config', { - body: { - metrics: [metricToRestore], - }, - expectedStatus: 200, - headers, - }) - expect( - restoredMetrics.json?.metrics.find((candidate) => candidate.name === metric.name)?.enabled - ).toBe(metricToRestore.enabled) - - const persistedRestore = await client.request('GET', '/metrics/config', { - expectedStatus: 200, - headers, - }) - expect( - persistedRestore.json?.metrics.find((candidate) => candidate.name === metric.name)?.enabled - ).toBe(metricToRestore.enabled) - } -} - async function deactivateJwks( client: AcceptanceHttpClient, tenantId: string, diff --git a/src/admin-app.ts b/src/admin-app.ts index 6dbb898ac..b3a481037 100644 --- a/src/admin-app.ts +++ b/src/admin-app.ts @@ -41,7 +41,6 @@ const build = (opts: buildOpts = {}): FastifyInstance => { { name: 'migration', description: 'Database migrations' }, { name: 's3-credentials', description: 'S3 credentials management' }, { name: 'queue', description: 'Queue management' }, - { name: 'metrics', description: 'Metrics configuration' }, ...(isRunningUnderWatt ? [{ name: 'pprof', description: 'Runtime profiling via Watt control APIs' }] : []), @@ -67,7 +66,6 @@ const build = (opts: buildOpts = {}): FastifyInstance => { } app.register(routes.s3Credentials, { prefix: 's3' }) app.register(routes.queue, { prefix: 'queue' }) - app.register(routes.metricsConfig, { prefix: 'metrics' }) // Register /metrics endpoint - uses OTel Prometheus exporter if (prometheusMetricsEnabled) { diff --git a/src/http/routes/admin/index.ts b/src/http/routes/admin/index.ts index 52f76b281..f43d31bfc 100644 --- a/src/http/routes/admin/index.ts +++ b/src/http/routes/admin/index.ts @@ -1,5 +1,4 @@ export { default as jwks } from './jwks' -export { default as metricsConfig } from './metrics' export { default as migrations } from './migrations' export { default as objects } from './objects' export { default as pprof } from './pprof' diff --git a/src/http/routes/admin/metrics.ts b/src/http/routes/admin/metrics.ts deleted file mode 100644 index 72d033071..000000000 --- a/src/http/routes/admin/metrics.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { getMetricsConfig, setMetricsEnabled } from '@internal/monitoring/metrics' -import { FastifyInstance, RequestGenericInterface } from 'fastify' -import { FromSchema } from 'json-schema-to-ts' -import { registerApiKeyAuth } from '../../plugins/apikey' - -const updateMetricsConfigSchema = { - body: { - type: 'object', - properties: { - metrics: { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - enabled: { type: 'boolean' }, - }, - required: ['name', 'enabled'], - }, - }, - }, - required: ['metrics'], - }, -} as const - -interface UpdateMetricsConfigRequest extends RequestGenericInterface { - Body: FromSchema -} - -export default async function routes(fastify: FastifyInstance) { - registerApiKeyAuth(fastify) - - fastify.get('/config', { schema: { tags: ['metrics'] } }, async (_request, reply) => { - return reply.send({ - metrics: getMetricsConfig(), - }) - }) - - fastify.put( - '/config', - { schema: { ...updateMetricsConfigSchema, tags: ['metrics'] } }, - async (request, reply) => { - setMetricsEnabled(request.body.metrics) - return reply.code(200).send({ - metrics: getMetricsConfig(), - }) - } - ) -} diff --git a/src/internal/monitoring/metrics.ts b/src/internal/monitoring/metrics.ts index 970028b5b..0a2d2f938 100644 --- a/src/internal/monitoring/metrics.ts +++ b/src/internal/monitoring/metrics.ts @@ -9,7 +9,7 @@ import { import { HTTP_SIZE_METRICS_MAX_STATES } from './metric-limits' // ============================================================================ -// Metric Registry — tracks all metrics for admin API +// Metric Registry // ============================================================================ export type MetricType = 'histogram' | 'counter' | 'gauge' | 'updowncounter' @@ -28,12 +28,7 @@ const disabledMetrics = new Set( .filter(Boolean) ) -/** Returns all registered metrics with their status */ -export function getMetricsConfig(): MetricRegistryEntry[] { - return Array.from(metricsRegistry.values()) -} - -/** Enable or disable specific metrics by OTel instrument name */ +/** Enable or disable specific metrics by OTel instrument name. */ export function setMetricsEnabled(changes: { name: string; enabled: boolean }[]): void { for (const { name, enabled } of changes) { const entry = metricsRegistry.get(name)