Appearance
audit-service Agent Tasks
Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.
Dispatch type: per-service
Dispatch ID: SVC-021
Current implementation: services/audit-service foundation exists for internal append-only audit/security events. Admin audit compatibility still exists in services/admin-service.
Owns: compliance/security append-only event history, source idempotency, retention/legal-hold metadata, and compliance query filters.
Must read:
docs/agents/handoff-phase-9-admin-foundation.md- legacy audit/admin operation log modules:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/admin/operations.controller.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/admin/feature-maintenance.service.ts,/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma.
Legacy source evidence:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/admin/**- audit/security/event rows in
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma.
Writable files:
services/audit-service/**contracts/openapi/services/audit-service.yamldocs/api/audit-service.md
Orchestrator-owned files:
- producer event contracts in owner services, admin-service compatibility routes, gateway route cutovers, and shared deploy manifests unless delegated.
Delegated gateway verification scope:
- Agents may maintain only the non-default audit compatibility rehearsal in
deploy/gateway/routes.audit-admin-compat-native-example.jsonanddeploy/gateway/routes.audit-admin-compat-native-localhost-example.json; the default/api/admin/operations/auditroute remains admin-service backed until orchestrator promotion evidence exists. - Route proof must cover
GET /api/admin/operations/auditto/v1/admin/operations/audit-compatthroughscripts/test/audit-admin-compat-route-coverage.sh, with QA evidence underdocs/qa/audit-admin-compat-*.md. - Makefile target:
test-audit-admin-compat-routes.
Initial tasks:
- Done: define event ingestion, retention metadata, and native OpenAPI contract.
- Done: add audit/security append/list APIs, source event replay handling, and Postgres append-only triggers.
- Done: add optional IAM security-event projection sink wiring. IAM posts redacted security events to audit-service with
sourceService=iam-service,sourceEventId=<iam security event id>,metadataSchemaVersion=1, andretentionPolicy=securitywhile keeping IAM local logs authoritative and best-effort projection non-blocking. - Add remaining producer adapter slices for organization membership sync and admin broadcast/gateway route operations. Each producer must send versioned payloads with
sourceService,sourceEventId, request/correlation ids, actor/account ids, action/entity/outcome, and metadata schema version. - Done: add the first admin operations producer slice. When configured with
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL, admin-service posts new native admin audit logs to/v1/audit-eventsusingsourceService=admin-service,sourceEventId=<admin audit log id>,category=ADMIN,severity=INFO,retentionPolicy=admin-operations,metadataSchemaVersion=1, andcreatedAt -> occurredAtwithout blocking the admin write path. - Done: add the first wallet producer slice. When configured with
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL, wallet-service posts successful ledger entries to/v1/audit-eventsusingsourceService=wallet-service,sourceEventId=<ledger entry id>,category=DOMAIN,severity=INFO,outcome=SUCCESS,retentionPolicy=wallet-ledger,metadataSchemaVersion=1, andcreatedAt -> occurredAtwithout blocking wallet balance movements. - Done: add the first billing producer slice. When configured with
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL, billing-service posts billing state events to/v1/audit-eventsusingsourceService=billing-service,sourceEventId=<billing event id>,category=DOMAIN,severity=INFO,outcome=SUCCESS,retentionPolicy=billing-events,metadataSchemaVersion=1, andcreatedAt -> occurredAt. This stays separate from the usage/wallet outbox so audit-service failures do not trigger entitlement or wallet delivery retries. - Done: add the first payment producer slice. When configured with
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL, payment-service posts persisted payment events to/v1/audit-eventsusingsourceService=payment-service,sourceEventId=<payment event id>,category=DOMAIN,severity=INFO,retentionPolicy=payment-events,metadataSchemaVersion=1, andcreatedAt -> occurredAt. This stays separate from the billing/wallet event outbox so audit-service failures do not retry monetization fanout. - Done: add the first notification producer slice. When configured with
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL, notification-service posts newly persisted notification events to/v1/audit-eventsusingsourceService=notification-service,sourceEventId=<notification event id>,category=DOMAIN,severity=INFO,retentionPolicy=notification-events,metadataSchemaVersion=1, andcreatedAt -> occurredAt. Source event replays return the original notification event and do not publish another audit row; provider delivery attempts stay separate diagnostics. - Done: add the first profile producer slice. When configured with
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL, profile-service posts audit-shaped profile mutation events to/v1/audit-eventsusingsourceService=profile-service,sourceEventId=<profile event id>,category=DATA,severity=INFO,retentionPolicy=profile-events,metadataSchemaVersion=1, request/correlation ids, optional gateway actor context, and localoccurredAt. The slice addsprofile.snapshot.upsertedwithout making profile role/status projections authoritative for access control. - Done: add the first gateway route-operation producer slice. When configured with
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL, api-gateway posts matched client-facing route outcomes to/v1/audit-eventsusingsourceService=api-gateway,sourceEventId=gateway:route:<requestId>,category=OPERATIONS,action=gateway.route.request,entity=GatewayRoute,severity=INFO|WARNING|ERROR,outcome=SUCCESS|DENIED|FAILED|ERROR,retentionPolicy=gateway-route-operations,metadataSchemaVersion=1, and redacted route metadata containing query keys only. Disabled or failed audit projection does not block gateway responses, and shadow reads record only the client-facing route decision. - Done: add a native legacy
AuditLogcompliance projection surface before public route migration.POST /v1/audit-events/legacy-admin-logmapsaction,entity,entityId,organizationId,actorId,metadata, andcreatedAt -> occurredAtintoaudit_events, usessourceService=admin-serviceplussourceEventId=<legacy AuditLog id>for idempotency, and keeps the legacy database read-only. - Done: add a legacy backfill report wrapper around the projection route.
POST /v1/audit-events/legacy-admin-log/batchaccepts already-shaped legacy rows, reports processed/created/replayed/failed counts and per-row failures, and never mutates the legacy database. - Done: add the external legacy reader command
services/audit-service/cmd/legacy-admin-audit-backfill. It reads legacy"AuditLog"rows in read-only transactions, posts already-shaped rows to audit-service, prints the batch report, supports-dry-run, and never mutates the legacy database. - Done: add a separate admin audit compatibility preview before moving
/api/admin/operations/auditfrom admin-service to audit-service.GET /v1/admin/operations/audit-compatpreservesaction,entity,limit,skip, newest-first{items,total,skip,limit}, legacy/sourceid, Vietnamese metadata labels, andcreatedAtmapped fromoccurredAtwhile the public route still rolls back to the existing admin-service rehearsal. - Done locally: add a non-default audit-service route-table and gateway rehearsal for
GET /api/admin/operations/auditto/v1/admin/operations/audit-compat.make test-audit-admin-compat-routesvalidates the Compose and localhost examples, keeps the default route table legacy, requires anADMINgateway principal, and leaves writes/nested admin operations routes on legacy. Backfill evidence and rollback rehearsal are still required before promoting the public route to audit-service. - Done locally:
GET /v1/admin/operations/audit-compatnow accepts onlyapi-gatewaywith the shared internal token and a forwardedADMINprincipal.make test-admin-audit-owner-browsercreates a fresh audit-only Postgres cluster, runs temporary audit-service/Gateway/Admin Vite processes, proves one ADMIN row reaches the Admin Operations audit tab through the exact candidate route, rejects STUDENT and anonymous Gateway requests, and removes the temporary resources on exit. This guard is intentionally limited to the compatibility read; producer write routes are unchanged in this slice. - Keep future IAM/security/gateway projection slices focused on outbox-backed retry, richer actor/request context, organization-membership audit semantics, backfill reports, and runtime evidence; the current sinks are best-effort and do not replace those cutover requirements.
- Add backfill/report tasks for legacy security-event rows without mutating the legacy database.
- Done: add retention/legal-hold scan filters separately from public admin route compatibility.
GET /v1/audit-eventsandGET /v1/security-eventsacceptretentionBeforeandlegalHoldBeforefor compliance review jobs without mutating append-only rows.
Acceptance:
- Audit is append-only and queryable by actor/entity/action/date.
- Domain services emit audit events without sharing DBs.
- Split from admin-service has a migration/compatibility plan.
- Producer adapters are idempotent by
sourceService+sourceEventId, and replay does not mutate existing audit/security rows. - Admin-service can retain temporary compatibility reads, but compliance-grade audit storage remains audit-service-owned.
- IAM-sourced security events never include raw passwords, reset tokens, Google tokens, refresh tokens, or provider secrets.
- Public
/api/admin/operations/auditremains on admin-service compatibility routes until audit-service has rollback evidence, legacy response parity, and real or production-like backfill/report evidence for the promoted slice.
Verification:
- Current:
GOTOOLCHAIN=go1.25.11 go test ./services/audit-service/... make test-audit-admin-compat-routesmake test-admin-audit-owner-browserAUDIT_REPLAY_RETENTION_POSTGRES_CONFIRM=disposable-postgres make test-audit-replay-retention-postgres- The confirmed Postgres runner retains only a focused Go test log and non-secret
summary.jsonunderoutput/qa/audit-replay-retention-postgres-*. - Optional Postgres: set
AUDIT_SERVICE_POSTGRES_TEST_DATABASE_URL, then runGOTOOLCHAIN=go1.25.11 go test ./services/audit-service/internal/repository -run TestPostgresAuditAndSecurityIntegration -count=1 -v - Future producer/backfill/compatibility slices must add focused replay and filter tests plus route-table guards before any public audit route cutover.