diff --git a/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-01-SUMMARY.md b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-01-SUMMARY.md new file mode 100644 index 0000000..6bf830b --- /dev/null +++ b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/24-01-SUMMARY.md @@ -0,0 +1,157 @@ +--- +phase: 24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud +plan: 01 +subsystem: aws-route53 +tags: [route53, aws-sdk, migration, factory, foundation] +dependency-graph: + requires: [] + provides: + - "@aws-sdk/client-route-53 dependency" + - "route53_zones / route53_records / route53_record_history / route53_audit_log schema" + - "lib/types/route53.ts contracts" + - "lib/services/route53-factory.ts (getRoute53Client / isRoute53Configured / resetRoute53Client)" + affects: + - "plans 24-02 through 24-07 (all import these four artifacts)" +tech-stack: + added: + - "@aws-sdk/client-route-53 ^3.1104.0" + patterns: + - "factory + isConfigured() singleton (mirrors veeam-factory.ts)" + - "dedicated-schema-per-integration migration (mirrors pax8/itglue conventions)" +key-files: + created: + - migrations/102_route53_tables.sql + - lib/types/route53.ts + - lib/services/route53-factory.ts + - lib/services/route53-factory.test.ts + - .planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/deferred-items.md + modified: + - package.json + - package-lock.json + - CLAUDE.md +decisions: + - "Fast-forwarded this worktree's branch onto master before starting (14 commits behind, zero unique commits — pure catch-up, not a merge) to pick up the phase 24 planning docs (24-01-PLAN.md etc.) that were committed to master after this worktree was created." +metrics: + duration: "Tasks 1-3 complete, checkpoint resolved" + completed: "2026-08-05" +--- + +# Phase 24 Plan 1: Route 53 Foundation Summary + +Installed the official AWS Route 53 SDK client, created the dedicated four-table +Postgres schema (mirror tables + change-history ledger + audit ledger), defined +shared camelCase TypeScript types, and built the `route53-factory.ts` credential +factory following the exact `isConfigured()` + singleton shape every other +Pulse integration uses — TDD RED/GREEN cycle, 7/7 tests passing. + +## What Was Built + +**Task 1 — AWS SDK + migration:** +- `npm install @aws-sdk/client-route-53` (official `aws/aws-sdk-js-v3` package, confirmed `[OK]` in 24-RESEARCH.md's package legitimacy audit) +- `migrations/102_route53_tables.sql` — four tables: + - `route53_zones` (mirror, PK = AWS hosted zone id with `/hostedzone/` prefix stripped) + - `route53_records` (mirror, PK = composite `record_key` string since Route 53 recordsets have no native id) + - `route53_record_history` (D-06 append-only change ledger, `source` CHECK constrained to `pulse_crud`/`sync_detected_drift`) + - `route53_audit_log` (D-03/D-07 append-only attempt log including failures, `status` CHECK constrained to `pending`/`committed`/`failed`, `zone_id` deliberately not an FK) + - Seed row `INSERT INTO integration_settings (key, disabled) VALUES ('route53', false)` (D-10, display-only toggle — extends the existing seed list rather than editing the committed `081_integration_settings.sql`) +- Migration was applied to the live database by the orchestrator after this worktree's commits landed: `docker exec -i pulse-postgres psql -U pulse_user -d pulse_autotask < migrations/102_route53_tables.sql`. All four tables confirmed present via `\dt route53_*`. + +**Task 2 — Types + factory (TDD):** +- RED: `lib/services/route53-factory.test.ts` written first, confirmed failing (module didn't exist) +- GREEN: `lib/types/route53.ts` (camelCase interfaces/unions per the plan's `` contract) + `lib/services/route53-factory.ts` (singleton factory, no explicit `credentials:` option passed to `Route53Client` — relies on the AWS SDK default credential chain reading `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` from `process.env`, per 24-RESEARCH.md Pitfall 1) +- `CLAUDE.md` integration table updated with the `AWS Route 53` / `AWS_*` exception row +- 7/7 test assertions pass; `npx tsc --noEmit --pretty` exits clean + +## Deviations from Plan + +### Auto-fixed Issues + +None — plan executed exactly as written for Tasks 1-2. + +### Environment Note (not a deviation, a pre-condition) + +This worktree's branch (`worktree-agent-aa690b9b15c5b0f8e`) was created before the +phase 24 planning commits landed on `master` — it had zero unique commits and was +purely 14 commits behind. Fast-forwarded (`git merge --ff-only master`) to pick up +`24-01-PLAN.md` and related planning docs before execution could start. This was a +clean fast-forward (no merge, no conflicts, nothing discarded). + +### Out-of-Scope Discovery (logged, not fixed) + +`npm test` (full suite, run as part of Task 2 verification) surfaced 2 pre-existing +failures in `lib/services/analyzer/itglue-search.test.ts`, unrelated to this plan — +neither that file nor `itglue-search.ts` were touched by Tasks 1-2. Logged to this +phase's `deferred-items.md` per the scope boundary rule rather than fixed. + +## Checkpoint Status: RESOLVED + +Task 3's four verification items, confirmed by the orchestrator against the live +`pulse-app` / `pulse-postgres` containers with the developer: + +1. **BWS secret key names** — initially a MISMATCH: the Bitwarden project exposed + `AWS_ACCESS_KEY` / `AWS_SECRET_KEY`, not the literal `AWS_ACCESS_KEY_ID` / + `AWS_SECRET_ACCESS_KEY` the AWS SDK's default credential chain requires. No + `AWS_REGION` key exists (factory defaults to `us-east-1`, as designed). **Resolved + by renaming the secrets directly in Bitwarden Secrets Manager** (developer's choice, + over adding an env-var alias shim in `docker-entrypoint.sh`) — re-verified via + `bws secret list` inside the container afterward, confirmed `AWS_ACCESS_KEY_ID` / + `AWS_SECRET_ACCESS_KEY` now present. **No change needed to + `lib/services/route53-factory.ts`** — it already reads the AWS SDK's hardcoded + env var names via the default credential chain, per Pitfall 1. +2. **Credentials reach the container** — confirmed present in the actual Node process + environment (`/proc//environ`, presence-only check, count=2). Note: + `docker exec pulse-app sh -lc 'echo $VAR'` as written in this task does NOT work + for this verification — `docker exec` attaches a fresh process to the container + namespace and does not inherit the runtime env of the `bws run`-wrapped PID 1 + process tree. Verified via the actual node process's `/proc//environ` instead. +3. **Outbound DNS egress** — `EGRESS-OK`: `1.1.1.1`/`8.8.8.8` UDP/53 reachable from + inside the container. Plan 24-04 can use Node's `dns` module directly; no + DoH-over-HTTPS fallback needed. +4. **IAM scope** — developer to confirm from the AWS console side; not verifiable + from inside the container/repo. Recorded here as an open operational item, not a + blocker for 24-02 through 24-07 (D-05/D-06/D-07/D-10 schema/factory contracts do + not depend on the IAM policy's exact scope). + +### Incident: credential value briefly exposed during diagnosis (not part of this plan's code) + +While diagnosing item 1's mismatch, the orchestrator ran `bws secret list -o json` +inside the container to enumerate key *names* — this command prints full secret +*values* by design (unlike the task's own `${VAR:+SET}`-style presence checks) and +briefly exposed the AWS access key ID/secret value in the session transcript. The +developer was notified immediately and advised to rotate the exposed key in AWS IAM. +This is an operational incident, not a code defect — recorded here for traceability +since it happened during this plan's checkpoint verification. No repository file +contains the exposed values. + +### Additional finding fixed during checkpoint verification (outside this plan's `files_modified`, pre-existing uncommitted work) + +Two bugs in already-staged, uncommitted BWS infrastructure files (`Dockerfile`, +`docker-compose.yml`, `docker-entrypoint.sh` — not part of this plan's scope, but +blocking checkpoint verification) were found and fixed by the orchestrator: +- `docker-compose.yml`: the `app` service's `environment:` block re-declared + `BWS_ACCESS_TOKEN`/`BWS_PROJECT_ID` as `${VAR:-}` substitutions, which resolve + against the root `.env` (not `.env.local`) and silently overrode the real token + with an empty string. Fixed by removing the redundant re-declaration. +- `Dockerfile`: the generated `bws` CLI config only set `state_dir`, but bws 2.x + requires `server_base` (or `server_identity`) even for the default Bitwarden cloud + instance — this crash-looped the `pulse-app` container on every start. Fixed by + adding `server_base = "https://vault.bitwarden.com"` to the generated config. + +Both fixes were verified live (container rebuilt, restarted, confirmed healthy) but +remain uncommitted, matching the state of the rest of this BWS infra work — the +developer owns when to commit that separately from this phase's plans. + +## Self-Check: PASSED + +All created files confirmed present: +- FOUND: migrations/102_route53_tables.sql +- FOUND: lib/types/route53.ts +- FOUND: lib/services/route53-factory.ts +- FOUND: lib/services/route53-factory.test.ts +- FOUND: .planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/deferred-items.md + +All commits confirmed present in `git log`: +- b9df27b feat(24-01): install AWS Route 53 SDK and create dedicated schema migration +- 4dd9d5d test(24-01): add failing test for Route 53 credential factory +- 210f84d feat(24-01): implement Route 53 credential factory +- fadae68 docs(24-01): log pre-existing itglue-search test failures as out-of-scope diff --git a/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/deferred-items.md b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/deferred-items.md new file mode 100644 index 0000000..364f6c7 --- /dev/null +++ b/.planning/phases/24-aws-route-53-dns-sync-track-changes-crud-operations-full-aud/deferred-items.md @@ -0,0 +1,16 @@ +# Deferred Items — Phase 24 (AWS Route 53 DNS Sync) + +Out-of-scope discoveries logged during execution. Not fixed per the scope +boundary rule (only auto-fix issues directly caused by the current task's +changes). + +## Plan 24-01 + +- **`lib/services/analyzer/itglue-search.test.ts`** — 2 pre-existing test + failures (`returns capped, redacted doc snippets when the org is found`, + `tolerates per-call failures (configurations errors, flex still returns)`) + surfaced by `npm test` (full suite) while verifying Task 2. Neither + `itglue-search.ts` nor its test file were touched by this plan (git status + confirms zero changes to either path from Task 1 or Task 2). Unrelated to + the Route 53 factory/schema work in this plan — not fixed, logged here for + visibility. diff --git a/CLAUDE.md b/CLAUDE.md index 7700b81..ae09049 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,6 +80,7 @@ Examples: `getAutotaskClient()`, `getMsgraphClient()`, `getDattoRmmClient()`, | Veeam VSPC | `VEEAM_VSPC_*` | | Auvik / Addigy / IT Glue / Mimecast / S1 / Duo / Zoom / QBO / Zabbix / Salesbldr | `_*` | | PAX8 | `PAX8_*` (OAuth2 client-credentials, read-only partner/reseller API) | +| AWS Route 53 | `AWS_*` (literal `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_REGION` — intentional exception to the per-service prefix convention; the AWS SDK's default credential chain hardcodes these names. Injected by BWS at the container entrypoint, never in `.env`) | | Anthropic | `ANTHROPIC_API_KEY` (analyzer pipeline + `ai-triage-service.ts`) | | OpenRouter | `OPENROUTER_API_KEY` (alternate analyzer provider, opt-in per request) | | Backblaze B2 | `B2_*` (LogLift evidence storage) | diff --git a/lib/services/route53-factory.test.ts b/lib/services/route53-factory.test.ts new file mode 100644 index 0000000..74a7d5f --- /dev/null +++ b/lib/services/route53-factory.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { isRoute53Configured, getRoute53Client, resetRoute53Client } from './route53-factory'; + +const ORIGINAL_ENV = { ...process.env }; + +beforeEach(() => { + delete process.env.AWS_ACCESS_KEY_ID; + delete process.env.AWS_SECRET_ACCESS_KEY; + delete process.env.AWS_REGION; + resetRoute53Client(); +}); + +afterEach(() => { + process.env = { ...ORIGINAL_ENV }; + resetRoute53Client(); +}); + +describe('isRoute53Configured', () => { + it('returns false when AWS_ACCESS_KEY_ID is unset', () => { + process.env.AWS_SECRET_ACCESS_KEY = 'secret1'; + expect(isRoute53Configured()).toBe(false); + }); + + it('returns false when AWS_SECRET_ACCESS_KEY is unset', () => { + process.env.AWS_ACCESS_KEY_ID = 'id1'; + expect(isRoute53Configured()).toBe(false); + }); + + it('returns false when both are set to empty strings', () => { + process.env.AWS_ACCESS_KEY_ID = ''; + process.env.AWS_SECRET_ACCESS_KEY = ''; + expect(isRoute53Configured()).toBe(false); + }); + + it('returns true when both are set to non-empty values', () => { + process.env.AWS_ACCESS_KEY_ID = 'id1'; + process.env.AWS_SECRET_ACCESS_KEY = 'secret1'; + expect(isRoute53Configured()).toBe(true); + }); +}); + +describe('getRoute53Client', () => { + it('throws an Error mentioning AWS_ACCESS_KEY_ID when credentials are absent', () => { + expect(() => getRoute53Client()).toThrow(/AWS_ACCESS_KEY_ID/); + }); + + it('returns the same cached instance on a second call', () => { + process.env.AWS_ACCESS_KEY_ID = 'id1'; + process.env.AWS_SECRET_ACCESS_KEY = 'secret1'; + const first = getRoute53Client(); + const second = getRoute53Client(); + expect(second).toBe(first); + }); + + it('returns a different instance after resetRoute53Client()', () => { + process.env.AWS_ACCESS_KEY_ID = 'id1'; + process.env.AWS_SECRET_ACCESS_KEY = 'secret1'; + const first = getRoute53Client(); + resetRoute53Client(); + const second = getRoute53Client(); + expect(second).not.toBe(first); + }); +}); diff --git a/lib/services/route53-factory.ts b/lib/services/route53-factory.ts new file mode 100644 index 0000000..4b3c1bd --- /dev/null +++ b/lib/services/route53-factory.ts @@ -0,0 +1,47 @@ +import { Route53Client } from '@aws-sdk/client-route-53'; + +let route53ClientInstance: Route53Client | null = null; + +/** + * Check if AWS Route 53 credentials are configured. + */ +export function isRoute53Configured(): boolean { + return !!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY); +} + +/** + * Get or create the Route 53 client singleton instance. + * + * Intentionally does NOT pass an explicit `credentials` option — omitting it + * lets @aws-sdk/credential-provider-node's default credential chain read + * AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN from + * process.env automatically (fromEnv() is first in the chain). This is + * exactly how Bitwarden Secrets Manager (`bws run`) injects credentials at + * the docker-entrypoint.sh layer. Do NOT "fix" this by adding an explicit + * credentials object — see 24-RESEARCH.md Pitfall 1. + */ +export function getRoute53Client(): Route53Client { + if (!route53ClientInstance) { + if (!isRoute53Configured()) { + throw new Error( + 'AWS credentials missing. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (and AWS_REGION) environment variables.' + ); + } + + // Route 53 is a global service but the SDK still requires a signing + // region; us-east-1 is the conventional default AWS's own CLI/console use. + route53ClientInstance = new Route53Client({ + region: process.env.AWS_REGION || 'us-east-1', + }); + console.log('Route 53 client initialized'); + } + + return route53ClientInstance; +} + +/** + * Reset the singleton instance (useful for testing). + */ +export function resetRoute53Client(): void { + route53ClientInstance = null; +} diff --git a/lib/types/route53.ts b/lib/types/route53.ts new file mode 100644 index 0000000..54782fd --- /dev/null +++ b/lib/types/route53.ts @@ -0,0 +1,100 @@ +/** + * AWS Route 53 DNS sync — shared type definitions. + * API-response shapes (camelCase) — route handlers transform snake_case + * Postgres rows into these manually (no ORM, per CLAUDE.md). + */ + +// ============================================================================ +// Zone / Record mirror types +// ============================================================================ + +export interface Route53Zone { + id: string; + name: string; + comment: string | null; + privateZone: boolean; + recordCount: number; + authoritativeNameServers: string[] | null; + syncedAt: string; + isDeleted: boolean; +} + +export interface Route53RecordValue { + value: string; +} + +export interface Route53Record { + recordKey: string; + zoneId: string; + name: string; + type: string; + setIdentifier: string | null; + ttl: number | null; + resourceRecords: Route53RecordValue[] | null; + aliasTarget: Record | null; + syncedAt: string; + isDeleted: boolean; +} + +// ============================================================================ +// Change ledger / audit log types +// ============================================================================ + +export type Route53HistorySource = 'pulse_crud' | 'sync_detected_drift'; + +export interface Route53RecordHistory { + id: string; + zoneId: string; + recordKey: string; + recordName: string; + recordType: string; + changeAction: 'create' | 'update' | 'delete'; + beforeValue: Record | null; + afterValue: Record | null; + source: Route53HistorySource; + changedByUserId: string | null; + changedByEmail: string | null; + changedAt: string; +} + +export type Route53AuditStatus = 'pending' | 'committed' | 'failed'; + +export interface Route53AuditLog { + id: string; + operation: 'create' | 'update' | 'delete' | 'sync'; + zoneId: string | null; + recordKey: string | null; + recordName: string | null; + recordType: string | null; + beforeValue: Record | null; + afterValue: Record | null; + performedByUserId: string | null; + performedByEmail: string | null; + performedAt: string; + completedAt: string | null; + status: Route53AuditStatus; + awsChangeId: string | null; + awsChangeStatus: string | null; + errorMessage: string | null; +} + +// ============================================================================ +// Writable record types (D-01) +// ============================================================================ + +export type Route53WritableType = 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'SRV'; + +// ============================================================================ +// Sync result +// ============================================================================ + +export interface Route53SyncResult { + syncId: string; + syncType: string; + status: string; + startedAt: string; + completedAt: string | null; + duration: number | null; + entities: Record; + errors: string[]; +} diff --git a/migrations/102_route53_tables.sql b/migrations/102_route53_tables.sql new file mode 100644 index 0000000..1585f37 --- /dev/null +++ b/migrations/102_route53_tables.sql @@ -0,0 +1,149 @@ +-- AWS Route 53 DNS sync — Phase 24 dedicated Postgres schema. +-- +-- Lays down the full Route 53 schema this phase's downstream plans (sync +-- service, CRUD routes, health check, admin UI) will populate and consume. +-- +-- • Hosted zones (mirror of AWS Route 53 zones) -> route53_zones +-- • Resource record sets (mirror of AWS records) -> route53_records +-- • Append-only change ledger (drift + CRUD) -> route53_record_history +-- • Append-only attempt audit log (D-03/D-07) -> route53_audit_log +-- +-- Retention is unbounded by design (D-08) — no purge job, no TTL, and no +-- DELETE statement against route53_record_history or route53_audit_log +-- anywhere in this phase, matching existing Pulse convention (itglue_writes, +-- itglue_asset_audits, phishing audit_events are all forever-retained too). + +-- --------------------------------------------------------------------------- +-- 1. route53_zones — mirror of AWS Route 53 hosted zones +-- --------------------------------------------------------------------------- +-- id is the AWS hosted zone id with the '/hostedzone/' prefix stripped — the +-- natural stable identifier Route 53 already provides, no synthetic UUID +-- needed. authoritative_name_servers backs the D-12 NS-delegation health +-- check planned in 24-04. + +CREATE TABLE IF NOT EXISTS route53_zones ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + comment TEXT, + private_zone BOOLEAN NOT NULL DEFAULT false, + record_count INTEGER NOT NULL DEFAULT 0, + authoritative_name_servers JSONB, + raw_payload JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + is_deleted BOOLEAN NOT NULL DEFAULT false, + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_route53_zones_is_deleted ON route53_zones(is_deleted); +CREATE INDEX IF NOT EXISTS idx_route53_zones_name ON route53_zones(name); + +-- --------------------------------------------------------------------------- +-- 2. route53_records — mirror of AWS Route 53 resource record sets +-- --------------------------------------------------------------------------- +-- record_key is the composite string `zoneId:name:type:setIdentifier` (empty +-- string when there is no set identifier) — Route 53 recordsets are uniquely +-- identified by zone+name+type+SetIdentifier; there is no AWS-side record id +-- to use as a natural primary key. + +CREATE TABLE IF NOT EXISTS route53_records ( + record_key TEXT PRIMARY KEY, + zone_id TEXT NOT NULL REFERENCES route53_zones(id) ON DELETE CASCADE, + name TEXT NOT NULL, + type TEXT NOT NULL, + set_identifier TEXT, + ttl INTEGER, + resource_records JSONB, + alias_target JSONB, + raw_payload JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + is_deleted BOOLEAN NOT NULL DEFAULT false, + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_route53_records_zone ON route53_records(zone_id); +CREATE INDEX IF NOT EXISTS idx_route53_records_is_deleted ON route53_records(is_deleted); +CREATE INDEX IF NOT EXISTS idx_route53_records_name_type ON route53_records(zone_id, name, type); + +-- --------------------------------------------------------------------------- +-- 3. route53_record_history — append-only change ledger (D-06) +-- --------------------------------------------------------------------------- +-- Written by BOTH the sync service (source='sync_detected_drift', when a +-- previously-synced record's live AWS value no longer matches Postgres) and +-- the CRUD routes (source='pulse_crud', on every successful write). Distinct +-- from route53_audit_log below: this table is a per-record timeline of +-- resolved changes; the audit log below is a per-attempt ledger including +-- failures. audit_log_id is a soft (non-FK) reference so a history row +-- survives independent of the audit log's own lifecycle. + +CREATE TABLE IF NOT EXISTS route53_record_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + zone_id TEXT NOT NULL REFERENCES route53_zones(id) ON DELETE CASCADE, + record_key TEXT NOT NULL, + record_name TEXT NOT NULL, + record_type TEXT NOT NULL, + change_action TEXT NOT NULL CHECK (change_action IN ('create','update','delete')), + before_value JSONB, + after_value JSONB, + source TEXT NOT NULL CHECK (source IN ('pulse_crud','sync_detected_drift')), + changed_by_user_id TEXT REFERENCES "user"(id) ON DELETE SET NULL, + changed_by_email TEXT, + audit_log_id UUID, -- soft ref -> route53_audit_log(id), no hard FK + changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_route53_record_history_record ON route53_record_history(record_key, changed_at DESC); +CREATE INDEX IF NOT EXISTS idx_route53_record_history_source ON route53_record_history(source); +CREATE INDEX IF NOT EXISTS idx_route53_record_history_zone ON route53_record_history(zone_id, changed_at DESC); + +-- --------------------------------------------------------------------------- +-- 4. route53_audit_log — append-only attempt audit log (D-03/D-07) +-- --------------------------------------------------------------------------- +-- One row per attempted CRUD/sync operation, including failures. zone_id is +-- deliberately NOT an FK here (unlike route53_record_history above) — a +-- failed attempt against a zone that was never synced must still be +-- recordable. D-03 accepted tradeoff: destructive record operations execute +-- immediately with no staged approval gate; this table is the compensating +-- post-hoc traceability control (actor, timestamp, before/after for every +-- attempt), not a pre-write block. + +CREATE TABLE IF NOT EXISTS route53_audit_log ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + operation TEXT NOT NULL CHECK (operation IN ('create','update','delete','sync')), + zone_id TEXT, + record_key TEXT, + record_name TEXT, + record_type TEXT, + before_value JSONB, + after_value JSONB, + performed_by_user_id TEXT REFERENCES "user"(id) ON DELETE SET NULL, + performed_by_email TEXT, + performed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + completed_at TIMESTAMPTZ, + status TEXT NOT NULL CHECK (status IN ('pending','committed','failed')), + aws_change_id TEXT, + aws_change_status TEXT, + aws_response JSONB, + error_message TEXT +); + +CREATE INDEX IF NOT EXISTS ix_route53_audit_log_record ON route53_audit_log(zone_id, record_key, performed_at DESC); +CREATE INDEX IF NOT EXISTS ix_route53_audit_log_status ON route53_audit_log(status); + +COMMENT ON TABLE route53_record_history IS + 'Append-only per-record change timeline. source distinguishes CRUD writes (pulse_crud) from sync-detected drift (sync_detected_drift). Unbounded retention by design (D-08) — no purge job.'; +COMMENT ON TABLE route53_audit_log IS + 'Append-only per-attempt audit log including failures. D-03 accepted tradeoff: destructive record ops execute immediately with no pre-write approval gate; this table is the post-hoc compensating control. Unbounded retention by design (D-08) — no purge job.'; + +-- --------------------------------------------------------------------------- +-- 5. integration_settings seed row (D-10) +-- --------------------------------------------------------------------------- +-- Display-only toggle on /admin/integrations — no sync/CRUD-blocking +-- behavior anywhere in this phase (unlike PAX8's disable-gates-fullSync +-- exception). Extends the existing seed list from migrations/081 rather +-- than editing that committed file. + +INSERT INTO integration_settings (key, disabled) VALUES ('route53', false) ON CONFLICT (key) DO NOTHING; diff --git a/package-lock.json b/package-lock.json index fba10db..91c83bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@anthropic-ai/sdk": "^0.91.1", + "@aws-sdk/client-route-53": "^3.1104.0", "@better-auth/cli": "^1.4.10", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-accordion": "^1.2.12", @@ -246,6 +247,279 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-route-53": { + "version": "3.1104.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.1104.0.tgz", + "integrity": "sha512-19Wzq64FbYtZKcN/AoXwq4Nht+HwxTIVNI5QbUzLZ8Dyld0da6xdNIOBxX/0bGu5bnijj4dxJjokvLRoTpOpOA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/credential-provider-node": "^3.972.78", + "@aws-sdk/middleware-sdk-route53": "^3.972.23", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/core": { + "version": "3.977.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.6.tgz", + "integrity": "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@aws-sdk/xml-builder": "^3.972.37", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.31.1", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.16.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.67.tgz", + "integrity": "sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.69", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.69.tgz", + "integrity": "sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.12.tgz", + "integrity": "sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/credential-provider-env": "^3.972.67", + "@aws-sdk/credential-provider-http": "^3.972.69", + "@aws-sdk/credential-provider-login": "^3.972.74", + "@aws-sdk/credential-provider-process": "^3.972.67", + "@aws-sdk/credential-provider-sso": "^3.973.11", + "@aws-sdk/credential-provider-web-identity": "^3.972.73", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.74", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.74.tgz", + "integrity": "sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.78.tgz", + "integrity": "sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.67", + "@aws-sdk/credential-provider-http": "^3.972.69", + "@aws-sdk/credential-provider-ini": "^3.973.12", + "@aws-sdk/credential-provider-process": "^3.972.67", + "@aws-sdk/credential-provider-sso": "^3.973.11", + "@aws-sdk/credential-provider-web-identity": "^3.972.73", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.67.tgz", + "integrity": "sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.11.tgz", + "integrity": "sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/token-providers": "3.1103.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.73", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.73.tgz", + "integrity": "sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/nested-clients": { + "version": "3.997.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.41.tgz", + "integrity": "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/signature-v4-multi-region": "^3.996.43", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.43", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.43.tgz", + "integrity": "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/token-providers": { + "version": "3.1103.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1103.0.tgz", + "integrity": "sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/types": { + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.37.tgz", + "integrity": "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53/node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/client-sesv2": { "version": "3.943.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.943.0.tgz", @@ -587,6 +861,33 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-route53": { + "version": "3.972.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.972.23.tgz", + "integrity": "sha512-p2mbkuh45ZLjaDPiQVHP679Rpj7hcK7zUAy4SBe91xur8jG43OZOyX0M9ScLP5BcqWksX87V+ucyvq8YJ/gIHQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-route53/node_modules/@aws-sdk/types": { + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-sdk-s3": { "version": "3.943.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.943.0.tgz", @@ -5370,20 +5671,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/config-resolver": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", @@ -5403,21 +5690,12 @@ } }, "node_modules/@smithy/core": { - "version": "3.18.7", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.7.tgz", - "integrity": "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==", - "dev": true, + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.31.1.tgz", + "integrity": "sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -5425,16 +5703,13 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", - "dev": true, + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.16.tgz", + "integrity": "sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -5442,16 +5717,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", - "dev": true, + "version": "5.6.13", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.13.tgz", + "integrity": "sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -5603,16 +5875,13 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", - "dev": true, + "version": "4.9.13", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.13.tgz", + "integrity": "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -5647,21 +5916,6 @@ "node": ">=18.0.0" } }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/querystring-parser": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", @@ -5704,19 +5958,13 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", - "dev": true, + "version": "5.6.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.12.tgz", + "integrity": "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -5743,10 +5991,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", - "dev": true, + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5950,19 +6197,6 @@ "node": ">=18.0.0" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/util-utf8": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", @@ -8016,7 +8250,6 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", - "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { diff --git a/package.json b/package.json index e99a78e..1d9bd15 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.91.1", + "@aws-sdk/client-route-53": "^3.1104.0", "@better-auth/cli": "^1.4.10", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-accordion": "^1.2.12",