Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import { createDisputesRouter } from './routes/disputes.routes';
import { createMetricsRouter } from './routes/metrics.routes';
import { metricsAuthMiddleware } from './middleware/metricsAuth';
import reputationRouter, { createReputationRouter } from './routes/reputation.routes';

Check warning on line 17 in src/app.ts

View workflow job for this annotation

GitHub Actions / Lint

'createReputationRouter' is defined but never used. Allowed unused vars must match /^_/u
import authRouter from './routes/auth.routes';
import configRouter from './routes/config.routes';
import dependencyScanRouter from './routes/dependency-scan.routes';
Expand All @@ -28,6 +28,7 @@
import { ReputationService } from './services/reputation.service';
import { getDb } from './db/database';
import { requestContextMiddleware } from './context';
import { createOverrideRequestsRouter } from './modules/overrideRequests/overrideRequest.routes';

interface AppFactoryOptions {
includeTerminalHandlers?: boolean;
Expand Down Expand Up @@ -88,6 +89,7 @@
app.use('/api/v1/webhook-subscriptions', webhookSubscriptionRouter);
}
app.use('/api/v1/metrics', metricsAuthMiddleware, createMetricsRouter(metricsService));
app.use('/api/v1/override-requests', createOverrideRequestsRouter());

if (includeTerminalHandlers) {
attachTerminalHandlers(app);
Expand Down
3 changes: 3 additions & 0 deletions src/audit/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
'AUTH_LOCKOUT_TRIGGERED', 'AUTH_LOCKOUT_RELEASED',
'ADMIN_ACTION',
'ENDPOINT_ACCESS', 'ENDPOINT_MUTATION',
// Override request workflow (#1221)
'OVERRIDE_REQUESTED', 'OVERRIDE_APPROVED', 'OVERRIDE_REJECTED',
'OVERRIDE_APPLIED', 'OVERRIDE_EXPIRED',
]);

export const VALID_SEVERITIES = new Set<AuditSeverity>(['INFO', 'WARNING', 'CRITICAL']);
Expand Down Expand Up @@ -110,7 +113,7 @@
if (cursor) {
try {
decodeCursor(cursor);
} catch (_error) {

Check warning on line 116 in src/audit/service.ts

View workflow job for this annotation

GitHub Actions / Lint

'_error' is defined but never used
throw new Error('Invalid cursor format');
}
}
Expand Down
14 changes: 13 additions & 1 deletion src/audit/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export const AUDIT_ACTIONS = [
'ENDPOINT_MUTATION',
'DEPLOYMENT_PROMOTED',
'DEPLOYMENT_ROLLED_BACK',
// Override request workflow (#1221)
'OVERRIDE_REQUESTED',
'OVERRIDE_APPROVED',
'OVERRIDE_REJECTED',
'OVERRIDE_APPLIED',
'OVERRIDE_EXPIRED',
] as const;

/** Categories of sensitive state changes that must be audited. */
Expand Down Expand Up @@ -69,7 +75,13 @@ export type AuditAction =
| 'DEPLOYMENT_ROLLED_BACK'
| 'MILESTONES_CREATED'
| 'MILESTONES_UPDATED'
| 'MILESTONES_DELETED';
| 'MILESTONES_DELETED'
// Override request workflow (#1221)
| 'OVERRIDE_REQUESTED'
| 'OVERRIDE_APPROVED'
| 'OVERRIDE_REJECTED'
| 'OVERRIDE_APPLIED'
| 'OVERRIDE_EXPIRED';

export const AUDIT_SEVERITIES = ['INFO', 'WARNING', 'CRITICAL'] as const;

Expand Down
76 changes: 76 additions & 0 deletions src/db/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,3 +818,79 @@ MIGRATIONS.push({
}
},
});

// Version 18: override_requests table for high-impact override approval workflow.
//
// Each row tracks one override request through its state machine:
// requested → approved → applied (happy path)
// requested → rejected (denied)
// requested/approved → expired (TTL elapsed before apply)
//
// Security notes:
// - requester_id and approver_id must be different (enforced at service layer).
// - tenant_id provides row-level isolation between tenants.
// - expires_at is written at creation time and evaluated on every read/apply.
// - applied_at records when the override was actually executed.
MIGRATIONS.push({
version: 18,
name: "create_override_requests_table",
checksumSource: [
"CREATE TABLE IF NOT EXISTS override_requests (",
"id TEXT PRIMARY KEY,",
"tenant_id TEXT NOT NULL,",
"resource_type TEXT NOT NULL,",
"resource_id TEXT NOT NULL,",
"action TEXT NOT NULL,",
"requester_id TEXT NOT NULL,",
"approver_id TEXT,",
"status TEXT NOT NULL DEFAULT 'requested',",
"reason TEXT NOT NULL,",
"rejection_reason TEXT,",
"expires_at TEXT NOT NULL,",
"approved_at TEXT,",
"applied_at TEXT,",
"rejected_at TEXT,",
"metadata TEXT NOT NULL DEFAULT '{}',",
"created_at TEXT NOT NULL,",
"updated_at TEXT NOT NULL",
].join("\n"),
up: (db) => {
db.exec(`
CREATE TABLE IF NOT EXISTS override_requests (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id TEXT NOT NULL,
action TEXT NOT NULL,
requester_id TEXT NOT NULL,
approver_id TEXT,
status TEXT NOT NULL DEFAULT 'requested'
CHECK (status IN ('requested', 'approved', 'rejected', 'applied', 'expired')),
reason TEXT NOT NULL CHECK (length(reason) >= 10 AND length(reason) <= 5000),
rejection_reason TEXT,
expires_at TEXT NOT NULL,
approved_at TEXT,
applied_at TEXT,
rejected_at TEXT,
metadata TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_override_requests_tenant_id
ON override_requests(tenant_id);

CREATE INDEX IF NOT EXISTS idx_override_requests_status
ON override_requests(status);

CREATE INDEX IF NOT EXISTS idx_override_requests_requester_id
ON override_requests(requester_id);

CREATE INDEX IF NOT EXISTS idx_override_requests_resource
ON override_requests(resource_type, resource_id);

CREATE INDEX IF NOT EXISTS idx_override_requests_expires_at
ON override_requests(expires_at);
`);
},
});
3 changes: 2 additions & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export type Resource =
| "settings"
| "disputes"
| "health"
| "reputation";
| "reputation"
| "override-requests";

export type Action = "create" | "read" | "update" | "delete" | "list" | "correct";

Expand Down
Loading
Loading