Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions acceptance/API_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
83 changes: 1 addition & 82 deletions acceptance/specs/admin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ interface TenantSummary {
id: string
}

interface MetricConfigEntry {
enabled: boolean
name: string
}

interface MetricsConfigResponse {
metrics: MetricConfigEntry[]
}

interface TenantFeatures {
icebergCatalog?: {
enabled?: boolean
Expand Down Expand Up @@ -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 = {
Expand All @@ -129,12 +120,6 @@ describeAcceptance(
})
expect(migrations.json).toBeTruthy()

const metrics = await client.request<MetricsConfigResponse>('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,
Expand Down Expand Up @@ -468,8 +453,6 @@ describeAcceptance(
)
}

await expectMetricsConfigMutation(client, headers)

await client.request('DELETE', `/tenants/${createdTenantId}`, {
expectedStatus: 204,
headers,
Expand Down Expand Up @@ -515,70 +498,6 @@ async function resolveTenantId(
return tenantId
}

async function expectMetricsConfigMutation(
client: AcceptanceHttpClient,
headers: Record<string, string>
) {
const metrics = await client.request<MetricsConfigResponse>('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<MetricsConfigResponse>('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<MetricsConfigResponse>('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<MetricsConfigResponse>('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<MetricsConfigResponse>('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,
Expand Down
2 changes: 0 additions & 2 deletions src/admin-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }]
: []),
Expand All @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion src/http/routes/admin/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
49 changes: 0 additions & 49 deletions src/http/routes/admin/metrics.ts

This file was deleted.

9 changes: 2 additions & 7 deletions src/internal/monitoring/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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)
Expand Down