Skip to content

Commit 730468f

Browse files
authored
Merge pull request #1150 from Teeeyanaa/fixes-branch
Complete AI-generated audit fixes
2 parents 7058bcd + b050cda commit 730468f

15 files changed

Lines changed: 2474 additions & 723 deletions
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
/**
4+
* Creates the `admin_correction_ledger` table.
5+
*
6+
* The table is intentionally designed to be append-only:
7+
* - No UPDATE or DELETE permissions should be granted to the application role.
8+
* - A database-level trigger can optionally enforce this; the application
9+
* layer already guards it via AdminLedgerService.
10+
*
11+
* Down migration is a simple DROP — this is safe because the table only ever
12+
* holds audit history and is never a FK dependency for operational tables.
13+
*/
14+
export class CreateAdminCorrectionLedger1803000000000
15+
implements MigrationInterface
16+
{
17+
name = 'CreateAdminCorrectionLedger1803000000000';
18+
19+
public async up(queryRunner: QueryRunner): Promise<void> {
20+
// Enum type for correction category
21+
await queryRunner.query(`
22+
CREATE TYPE "admin_correction_type_enum" AS ENUM (
23+
'BALANCE_CREDIT',
24+
'BALANCE_DEBIT',
25+
'FEE_WAIVER',
26+
'FEE_ADJUSTMENT',
27+
'INTEREST_CORRECTION',
28+
'OTHER'
29+
)
30+
`);
31+
32+
await queryRunner.query(`
33+
CREATE TABLE "admin_correction_ledger" (
34+
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
35+
"targetId" VARCHAR(255) NOT NULL,
36+
"targetType" VARCHAR(64) NOT NULL,
37+
"adminId" UUID NOT NULL,
38+
"correctionType" "admin_correction_type_enum" NOT NULL,
39+
"delta" VARCHAR(64) NOT NULL,
40+
"previousValue" TEXT,
41+
"newValue" TEXT,
42+
"reason" TEXT NOT NULL,
43+
"requestId" VARCHAR(255),
44+
"workflowId" VARCHAR(255),
45+
"metadata" JSONB,
46+
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
47+
CONSTRAINT "pk_admin_correction_ledger" PRIMARY KEY ("id")
48+
)
49+
`);
50+
51+
// Indexes for the most common query patterns
52+
await queryRunner.query(`
53+
CREATE INDEX "idx_acl_target_id"
54+
ON "admin_correction_ledger" ("targetId")
55+
`);
56+
57+
await queryRunner.query(`
58+
CREATE INDEX "idx_acl_admin_id"
59+
ON "admin_correction_ledger" ("adminId")
60+
`);
61+
62+
await queryRunner.query(`
63+
CREATE INDEX "idx_acl_request_id"
64+
ON "admin_correction_ledger" ("requestId")
65+
WHERE "requestId" IS NOT NULL
66+
`);
67+
68+
await queryRunner.query(`
69+
CREATE INDEX "idx_acl_workflow_id"
70+
ON "admin_correction_ledger" ("workflowId")
71+
WHERE "workflowId" IS NOT NULL
72+
`);
73+
74+
await queryRunner.query(`
75+
CREATE INDEX "idx_acl_created_at"
76+
ON "admin_correction_ledger" ("createdAt")
77+
`);
78+
79+
// Composite index useful for reconciliation queries
80+
await queryRunner.query(`
81+
CREATE INDEX "idx_acl_target_type_correction_type"
82+
ON "admin_correction_ledger" ("targetId", "correctionType")
83+
`);
84+
}
85+
86+
public async down(queryRunner: QueryRunner): Promise<void> {
87+
await queryRunner.query(`DROP TABLE IF EXISTS "admin_correction_ledger"`);
88+
await queryRunner.query(
89+
`DROP TYPE IF EXISTS "admin_correction_type_enum"`,
90+
);
91+
}
92+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
/**
4+
* Creates the `malformed_blockchain_events` quarantine table (#1133).
5+
*
6+
* Events that fail schema validation or cannot be safely parsed are persisted
7+
* here instead of silently discarded, enabling forensic investigation and
8+
* manual reprocessing.
9+
*/
10+
export class CreateMalformedBlockchainEvents1803100000000
11+
implements MigrationInterface
12+
{
13+
name = 'CreateMalformedBlockchainEvents1803100000000';
14+
15+
public async up(queryRunner: QueryRunner): Promise<void> {
16+
await queryRunner.query(`
17+
CREATE TYPE "quarantine_reason_enum" AS ENUM (
18+
'MISSING_REQUIRED_FIELDS',
19+
'TYPE_MISMATCH',
20+
'INVALID_SCHEMA',
21+
'UNPARSEABLE_AMOUNT',
22+
'MISSING_PUBLIC_KEY',
23+
'XDR_DECODE_ERROR',
24+
'HANDLER_ERROR',
25+
'UNKNOWN'
26+
)
27+
`);
28+
29+
await queryRunner.query(`
30+
CREATE TYPE "quarantine_status_enum" AS ENUM (
31+
'PENDING',
32+
'UNDER_REVIEW',
33+
'RESOLVED',
34+
'DISCARDED'
35+
)
36+
`);
37+
38+
await queryRunner.query(`
39+
CREATE TABLE "malformed_blockchain_events" (
40+
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
41+
"eventType" VARCHAR(64),
42+
"ledgerSequence" BIGINT,
43+
"txHash" VARCHAR(255),
44+
"eventId" VARCHAR(255),
45+
"reason" "quarantine_reason_enum" NOT NULL,
46+
"errorDetails" TEXT NOT NULL,
47+
"rawEvent" TEXT NOT NULL,
48+
"status" "quarantine_status_enum" NOT NULL DEFAULT 'PENDING',
49+
"resolutionNotes" TEXT,
50+
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
51+
CONSTRAINT "pk_malformed_blockchain_events" PRIMARY KEY ("id")
52+
)
53+
`);
54+
55+
await queryRunner.query(`
56+
CREATE INDEX "idx_mbe_reason"
57+
ON "malformed_blockchain_events" ("reason")
58+
`);
59+
60+
await queryRunner.query(`
61+
CREATE INDEX "idx_mbe_status"
62+
ON "malformed_blockchain_events" ("status")
63+
`);
64+
65+
await queryRunner.query(`
66+
CREATE INDEX "idx_mbe_event_type"
67+
ON "malformed_blockchain_events" ("eventType")
68+
WHERE "eventType" IS NOT NULL
69+
`);
70+
71+
await queryRunner.query(`
72+
CREATE INDEX "idx_mbe_ledger_sequence"
73+
ON "malformed_blockchain_events" ("ledgerSequence")
74+
WHERE "ledgerSequence" IS NOT NULL
75+
`);
76+
77+
await queryRunner.query(`
78+
CREATE INDEX "idx_mbe_created_at"
79+
ON "malformed_blockchain_events" ("createdAt")
80+
`);
81+
}
82+
83+
public async down(queryRunner: QueryRunner): Promise<void> {
84+
await queryRunner.query(
85+
`DROP TABLE IF EXISTS "malformed_blockchain_events"`,
86+
);
87+
await queryRunner.query(`DROP TYPE IF EXISTS "quarantine_reason_enum"`);
88+
await queryRunner.query(`DROP TYPE IF EXISTS "quarantine_status_enum"`);
89+
}
90+
}

0 commit comments

Comments
 (0)