fix(audit): preserve source event timestamps
This commit is contained in:
parent
eee122506c
commit
fdf9286e34
@ -31,7 +31,9 @@ async function request<T = unknown>(opts: FetchOptions): Promise<T> {
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
throw new Error(`platform-service ${opts.method} ${opts.path} → ${res.status}: ${text.slice(0, 200)}`);
|
||||
throw new Error(
|
||||
`platform-service ${opts.method} ${opts.path} → ${res.status}: ${text.slice(0, 200)}`
|
||||
);
|
||||
}
|
||||
|
||||
return res.json() as Promise<T>;
|
||||
@ -43,11 +45,14 @@ export interface AuditEntry {
|
||||
userId: string;
|
||||
action: string;
|
||||
category?: string;
|
||||
timestamp?: string;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** POST /audit — fire-and-forget audit write. Returns { accepted: true }. */
|
||||
export async function postAuditEvents(entries: AuditEntry[]): Promise<{ posted: number; errors: number }> {
|
||||
export async function postAuditEvents(
|
||||
entries: AuditEntry[]
|
||||
): Promise<{ posted: number; errors: number }> {
|
||||
let posted = 0;
|
||||
let errors = 0;
|
||||
for (const entry of entries) {
|
||||
@ -73,7 +78,7 @@ export interface TelemetryEvent {
|
||||
|
||||
/** POST /telemetry/events — batch ingest telemetry events. */
|
||||
export async function postTelemetryEvents(
|
||||
events: TelemetryEvent[],
|
||||
events: TelemetryEvent[]
|
||||
): Promise<{ accepted: number; rejected: number }> {
|
||||
if (events.length === 0) return { accepted: 0, rejected: 0 };
|
||||
return request<{ accepted: number; rejected: number }>({
|
||||
@ -102,7 +107,9 @@ export async function postUsageRecord(record: UsageRecord): Promise<unknown> {
|
||||
}
|
||||
|
||||
/** Post multiple usage records (budget flush). */
|
||||
export async function postUsageRecords(records: UsageRecord[]): Promise<{ posted: number; errors: number }> {
|
||||
export async function postUsageRecords(
|
||||
records: UsageRecord[]
|
||||
): Promise<{ posted: number; errors: number }> {
|
||||
let posted = 0;
|
||||
let errors = 0;
|
||||
for (const record of records) {
|
||||
@ -120,7 +127,7 @@ export async function postUsageRecords(records: UsageRecord[]): Promise<{ posted
|
||||
|
||||
/** GET /flags/poll — poll current flag values for this product. */
|
||||
export async function pollFlags(
|
||||
platform?: string,
|
||||
platform?: string
|
||||
): Promise<{ flags: Record<string, boolean>; productId: string }> {
|
||||
const params = new URLSearchParams();
|
||||
if (platform) params.set('platform', platform);
|
||||
|
||||
@ -52,6 +52,30 @@ describe('auditRoutes', () => {
|
||||
expect(repoMock.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('POST /audit preserves source timestamp when provided', async () => {
|
||||
repoMock.create.mockResolvedValue(undefined);
|
||||
const app = await buildApp();
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/audit',
|
||||
payload: {
|
||||
userId: 'user_1',
|
||||
action: 'tool_executed',
|
||||
category: 'agent',
|
||||
timestamp: '2026-04-04T17:45:00.000Z',
|
||||
details: { eventId: 'evt_cowork_1', taskId: 'task_1' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(202);
|
||||
expect(repoMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
timestamp: '2026-04-04T17:45:00.000Z',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('POST /audit returns 400 on invalid payload', async () => {
|
||||
const app = await buildApp();
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ export async function auditRoutes(app: FastifyInstance) {
|
||||
id: `aud_${crypto.randomUUID()}`,
|
||||
productId,
|
||||
...input,
|
||||
timestamp: input.timestamp ?? new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
// Fire-and-forget — don't await
|
||||
|
||||
@ -11,6 +11,7 @@ export interface AuditDoc {
|
||||
action: string;
|
||||
category: string;
|
||||
details: Record<string, unknown>;
|
||||
timestamp?: string;
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
createdAt: string;
|
||||
@ -22,6 +23,7 @@ export const CreateAuditSchema = z.object({
|
||||
action: z.string().min(1),
|
||||
category: z.string().default('general'),
|
||||
details: z.record(z.unknown()).default({}),
|
||||
timestamp: z.string().datetime().optional(),
|
||||
ipAddress: z.string().optional(),
|
||||
userAgent: z.string().optional(),
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user