Commit graph

960 commits

Author SHA1 Message Date
e057255f4f fix(24): address code-review findings — PATCH identity guard, empty-array tombstone, health-check timeout, record-key normalization
Two critical issues from the post-phase code review:

- PATCH /api/route53/zones/[zoneId]/records/[recordId] never verified the
  request body's name/type/setIdentifier matched the record identified by
  the URL. A mismatch would silently UPSERT a brand-new AWS recordset
  (leaving the original live and untouched) while corrupting the mirror's
  record_key invariant. Now rejects with 400 if any of those three fields
  differ from the existing record — renaming/retyping is delete-plus-create,
  not an update.

- route53-sync-service.ts's syncZones()/syncRecords() tombstone queries used
  "id <> ALL(seenIds)" style queries with no empty-array guard — a
  successful-but-empty AWS response would soft-delete every previously
  synced zone/record in one shot. Same bug class already fixed in
  pax8-sync-service.ts; now guarded the same way here.

Two smaller fixes:

- checkRoute53()'s AWS auth probe had no timeout, unlike every other
  integration's liveCheck() (8s AbortController). Added the same bound via
  the SDK's abortSignal option.
- buildRecordKey() relied on every caller to pre-normalize name/type case
  before calling it. Now normalizes internally (lowercase name, uppercase
  type) so the record_key invariant holds regardless of caller discipline.

Full REVIEW.md findings in 24-REVIEW.md. Two remaining Warnings (alias
records un-editable/undeletable, no admin-UI surface for route53_audit_log)
deliberately left as backlog items for a follow-up phase — out of scope for
a post-execution fix pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 23:15:27 -04:00
52afdca8a1 docs(24-07): resolve live end-to-end checkpoint — all 9 steps passed
Ran the phase-closing 9-step live verification against the real
wulfconsulting.com hosted zone. All steps confirmed passing:
sync, create/update/delete round-trip, audit completeness, NS-write
rejection + genuine AWS failure logging, admin/user auth gating,
drift detection, and D-12/D-10 health-check behavior.

One real bug found and fixed along the way (commit 4da5664): TXT
record values weren't RFC-1035-quoted, so AWS rejected every TXT
create/update with InvalidCharacterString. Fixed in
route53-change-submit.ts, 4 regression tests added, retried
successfully.

Logged one pre-existing, unrelated finding: /admin/integrations page
load is slow overall, confirmed not specific to Route 53's own health
check (which is bounded to a few seconds) — likely another
integration's untimeouts liveCheck() probe. Deferred, not fixed.

24-VALIDATION.md's three Manual-Only Verifications rows updated with
observed outcomes; status marked complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 22:55:27 -04:00
4da5664184 fix(24-05): quote TXT record values per RFC 1035 character-string format
AWS Route 53 rejects an unquoted TXT Value with:
'InvalidCharacterString (Value should be enclosed in quotation marks)'
— discovered during plan 24-07's live checkpoint (step 2, create) against
a real hosted zone. buildChangeBatch now wraps TXT values in escaped
double quotes, splitting into 255-character segments per RFC 1035's
character-string limit. A/AAAA/CNAME/MX/SRV values pass through
unchanged (only TXT uses the quoted-string wire format).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 21:08:51 -04:00
b9d5a4823f chore: merge executor worktree (worktree-agent-ad21740c19f57464b) — plan 24-07 2026-08-05 20:54:43 -04:00
31eb5e0321 docs(24-07): partial plan summary — Tasks 1-2 complete, Task 3 checkpoint pending
- Task 1 (page shell) and Task 2 (record editor dialog) committed and
  verified (tsc + build + acceptance-criteria greps all green)
- Task 3 is a blocking checkpoint:human-verify requiring a live 9-step
  round-trip against a real AWS account — not run, not fabricated
- Marked 24-07-T1/24-07-T2 rows green in 24-VALIDATION.md; 24-07-T3 and
  the three Manual-Only Verifications rows it covers remain pending
  human execution
2026-08-05 20:54:14 -04:00
fadfcb8345 feat(24-07): record editor dialog with create, edit, and immediate delete
- RecordEditorDialog: plain-state form (name/type/ttl/values), type
  selector offers exactly the six D-01 writable types, name/type
  read-only in edit mode
- Submit disabled while in flight (T-24-20); 400/409/502 responses
  render the server message inline and keep the dialog open
- RecordDeleteConfirm: single misclick-guard confirmation, deletes
  immediately with no staged/approval state (D-03)
- Wired into the Records tab actions cell from the prior commit
2026-08-05 20:52:34 -04:00
b3048be769 feat(24-07): zones, records, and history page shell for /admin/sync/route53
- Four-tab detail page (Zones / Records / History / Schedule) following
  the veeam/pax8 sync-detail-page shape
- Sync Now trigger with bounded polling and 409/503 handling
- Records tab: zone selector, type/search filters, actions cell gated on
  the D-01 writable-type allowlist (NS/SOA render read-only)
- History tab: per-record change ledger with pulse_crud vs
  sync_detected_drift source badges (D-06)
- Zone/history detail drill-downs via the existing DetailModal component
2026-08-05 20:52:25 -04:00
493d6daf6c docs(phase-24): update tracking after wave 3 (plans 24-05, 24-06 complete)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 20:39:58 -04:00
c47de2a91c chore: merge executor worktree (worktree-agent-a8fd5f9961335cf8d) — plan 24-05 2026-08-05 20:38:35 -04:00
78e534cafc chore: merge executor worktree (worktree-agent-a09d891460d61dc86) — plan 24-06 2026-08-05 20:38:35 -04:00
5e1b8aae75 docs(24-05): add plan summary
Route 53 API surface — read routes, sync trigger, CRUD write routes with
pending/committed/failed audit lifecycle.
2026-08-05 20:37:55 -04:00
a7d6a04110 feat(24-05): CRUD write routes with pending/committed/failed audit lifecycle
- app/api/route53/zones/[zoneId]/records/route.ts: add POST (create)
- app/api/route53/zones/[zoneId]/records/[recordId]/route.ts: PATCH (update), DELETE
- All three write handlers: requireAdmin() first (D-04), validateRecordWrite() before
  any AWS command (D-01), createPendingAuditLog() before submitRecordChange() (D-07/SC-3)
- Committed path: markAuditCommitted -> insertPulseCrudHistory (pulse_crud, SC-4) ->
  mirror refresh; failed path: sanitizeAwsError -> markAuditFailed -> 502, no history row
- DELETE submits the exact mirror-read recordset (name/type/ttl/resourceRecords), never
  client-supplied values, per Route 53's exact-match delete requirement
- recordId zone-prefix mismatch guard (T-24-17): 400 before any audit row or AWS call
- No staged-approval mechanism anywhere (D-03) — mutation executes on first request
- tsc clean; npm test 554/556 passing (2 pre-existing itglue-search failures, unrelated,
  logged in deferred-items.md, already documented by plans 24-01/24-03)
2026-08-05 20:36:54 -04:00
53ec51c5e0 feat(24-05): read routes for zones, records, history, and sync status
- app/api/route53/sync/route.ts: POST (requireAdmin, fire-and-forget) + GET (requireAuth, status/history)
- app/api/route53/zones/route.ts: GET (requireAuth) list mirrored hosted zones
- app/api/route53/zones/[zoneId]/records/route.ts: GET (requireAuth) list records with type/search filters
- app/api/route53/zones/[zoneId]/records/[recordId]/history/route.ts: GET (requireAuth) append-only change ledger
- None gated on integration_settings disable toggle (D-10 — route53 is not a PAX8-style exception)
- /api/route53 confirmed absent from middleware.ts public-route list
- tsc clean
2026-08-05 20:33:58 -04:00
82408733a4 docs(24-06): append self-check results to plan summary 2026-08-05 20:33:26 -04:00
8fcf587a30 docs(24-06): create plan summary 2026-08-05 20:33:08 -04:00
f4e151dedd feat(24-05): implement route53-change-submit (ChangeBatch, retry, bounded poll)
- buildChangeBatch: CREATE/UPSERT/DELETE, SetIdentifier omission, NS/SOA guard (D-01 defence in depth)
- isRetryableAwsError: classifies ThrottlingException/Throttling/PriorRequestNotComplete/ServiceUnavailable
- submitRecordChange: sends ChangeResourceRecordSetsCommand with bounded 2-retry backoff (750ms/1500ms)
- pollChangeStatus: bounded 15s/2s GetChange poll, no 30-minute SDK waiter
- 20/20 tests passing, tsc clean
2026-08-05 20:31:43 -04:00
6627cee925 feat(24-06): add Route 53 tile to /admin/sync integration list
- Hand-authored public/logos/route53.svg (globe/DNS glyph, primitive
  shapes only, no script/external refs/raster data)
- New INTEGRATIONS entry linking to /admin/sync/route53 (built in
  plan 24-07), color: orange (already used by datto-rmm)
2026-08-05 20:31:26 -04:00
fee1f9962b feat(24-06): wire route53-incremental/route53-full into sync scheduler
- Add route53-incremental and route53-full to the sync_type union
- Seed both schedules disabled (*/15 * * * * incremental, 0 1 * * * full)
- Dispatch branches gate on isRoute53Configured() only (D-10 — no
  integration_settings check, unlike the pax8-daily exception)
- Both branches use dynamic import to keep the AWS SDK out of the
  scheduler's eager module graph
2026-08-05 20:30:52 -04:00
9a9e691cd4 test(24-05): add failing test for route53-change-submit
- ChangeBatch construction (CREATE/UPSERT/DELETE, SetIdentifier omission, NS/SOA guard)
- isRetryableAwsError classification
- submitRecordChange retry-with-backoff behavior
- pollChangeStatus bounded polling (INSYNC / timeout / per-attempt error swallow)
2026-08-05 20:29:57 -04:00
2b009573c4 docs(phase-24): update tracking after wave 2 (plans 24-02, 24-03, 24-04 complete)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 20:27:11 -04:00
e727ddca77 refactor(24): consolidate duplicate sanitizeAwsError into single source
Plan 24-04's isolated worktree didn't have plan 24-03's
route53-record-validation.ts available (parallel wave, no direct
dependency), so it carried a local copy of the identical AWS error
redaction logic — flagged in its own SUMMARY for consolidation once
24-03 merged. Both plans are now merged; importing the shared
implementation instead of keeping two copies in sync.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 20:27:01 -04:00
d00c47ecb1 chore: merge executor worktree (worktree-agent-a30d26dba3410e0da) — plan 24-04 2026-08-05 20:26:13 -04:00
2dbb1e13e2 chore: merge executor worktree (worktree-agent-ab77d007f6dbb3578) — plan 24-03 2026-08-05 20:26:13 -04:00
d7f72f8507 chore: merge executor worktree (worktree-agent-a6d587bad9f0efdb8) — plan 24-02 2026-08-05 20:26:13 -04:00
416abe98a0 docs(24-04): create plan summary
- Route 53 registered in integration health with D-12 live NS-delegation check
- Records EGRESS-OK path taken (Node dns module, no DoH fallback needed)
- Documents local sanitizeAwsError duplication vs plan 24-03 (parallel worktree gap)
2026-08-05 20:25:33 -04:00
ea04672e5b feat(24-04): register checkRoute53() in the integration health aggregator
- checkRoute53(): config gate + ListHostedZonesCommand auth probe, mirrors
  checkDattoRmm()'s custom-body shape (key: 'route53', category: 'network')
- Auth-probe errors classified via isAwsAuthError (InvalidClientTokenId,
  SignatureDoesNotMatch, AccessDenied, UnrecognizedClientException, 401/403)
  and redacted through a local sanitizeAwsError before reaching IntegrationHealth.error
- D-12: queries route53_zones (is_deleted=false, capped at 50 by name),
  feeds checkAllZoneDelegations(); mismatches downgrade status to a new
  'degraded' HealthStatus member; lookup failures reported separately via
  nsDelegationErrors, never counted as mismatches
- Whole delegation step wrapped in try/catch so a Postgres failure or
  blocked resolver can never abort checkIntegrationHealth()'s Promise.all (T-24-16)
- summarize() updated so 'degraded' counts toward failed/hasIssues instead
  of falling through uncounted (Rule 1 fix)
- No changes to applyDisableOverlay() — route53 covered by the existing
  generic by-key overlay (D-10)
2026-08-05 20:24:36 -04:00
6df084cb98 docs(24-02): create plan summary
Route 53 sync + drift detection plan complete: 3 tasks, 4 files, all
verification passing (22 new tests, tsc clean, full suite green aside from
pre-existing unrelated itglue-search failures).
2026-08-05 20:24:18 -04:00
3d2680c47f docs(24-03): add plan summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 20:23:24 -04:00
0acf1fa24f test(24-02): cover buildDriftHistoryRows update/delete/create/no-change cases
- 6 unit tests exercising the pure drift-classification wiring in Route53SyncService
- Confirms whole-recordset before/after payloads, not per-field deltas
- Confirms CRUD-originated changes still get tagged sync_detected_drift (sync cannot distinguish actor)
- Full suite green aside from 2 pre-existing itglue-search.test.ts failures logged in deferred-items.md (unrelated to this plan)
2026-08-05 20:23:16 -04:00
d8c0912f4b feat(24-02): add Route53SyncService — zones and records mirror sync
- fullSync/incrementalSync + getRoute53SyncService() singleton
- Paginated ListHostedZonesCommand + GetHostedZoneCommand (delegation set for D-12)
- Paginated ListResourceRecordSetsCommand per live zone
- Soft-delete reconciliation for zones and records (never hard-delete)
- sync_history bookkeeping with entity_type='route53', literal full/incremental sync_type
- Drift detection wired via buildDriftHistoryRows, writing sync_detected_drift history rows
- No integration_settings gating anywhere (D-10)
2026-08-05 20:22:34 -04:00
8b5e926bb1 feat(24-03): add Route 53 audit lifecycle and pulse_crud history persistence
- createPendingAuditLog/markAuditCommitted/markAuditFailed implement the pending -> committed/failed lifecycle (D-07, SC-3); markAuditFailed always sanitizes via sanitizeAwsError
- insertPulseCrudHistory writes 'pulse_crud' history rows, documented as callable only after a committed write
- upsertMirrorRecord/softDeleteMirrorRecord/loadMirrorRecord manage the route53_records mirror; mirror writes are best-effort and soft-delete only (D-08), audit/history writes are not best-effort
- log same pre-existing itglue-search.test.ts failures (unrelated, out of scope) in deferred-items.md
2026-08-05 20:22:24 -04:00
06ebae5a5c feat(24-04): implement NS normalization and delegation-comparison module
- normalizeNsList: lowercase, strip trailing dot, dedupe, sort, [] for non-arrays
- compareNsDelegation: set-diff mismatch with unjudgeable-empty-authoritative guard
- resolveLiveNs: dedicated dns.Resolver() pinned to 1.1.1.1/8.8.8.8, never touches
  the process-global resolver (D-12, T-24-13)
- checkAllZoneDelegations: bounded-concurrency batch check, lookup errors reported
  separately from mismatches (T-24-14)
2026-08-05 20:21:30 -04:00
c18271dda9 test(24-02): add Route 53 record-key, normalization, and drift-classification helpers
- buildRecordKey, normalizeRecordSet, recordSetsEqual, classifyDrift, toHistoryPayload
- Pure, dependency-free module (no pg, no AWS client construction)
- 16 unit tests covering every behavior bullet from the plan
2026-08-05 20:21:05 -04:00
4be4a191a5 feat(24-03): add D-01 record-write validator and AWS error sanitizer
- validateRecordWrite enforces closed allowlist (A/AAAA/CNAME/MX/TXT/SRV), rejects NS/SOA case-insensitively with a delegation-specific reason
- sanitizeAwsError redacts AWS access key ids, ARNs, and 12-digit account ids, truncates to 500 chars (T-24-03)
- no AWS SDK or Postgres dependency; fully unit-tested (23 assertions)
2026-08-05 20:20:46 -04:00
7396f07f2e test(24-04): add failing test for NS normalization and delegation comparison
- normalizeNsList: lowercase, strip trailing dot, dedupe, sort, [] for non-arrays
- compareNsDelegation: set-diff mismatch detection with unjudgeable-empty-authoritative guard
2026-08-05 20:20:41 -04:00
ecd4dabd67 docs(phase-24): update tracking after wave 1 (plan 24-01 complete)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 20:17:42 -04:00
2eb6c2884c chore: merge executor worktree (worktree-agent-aa690b9b15c5b0f8e) — plan 24-01 2026-08-05 20:16:56 -04:00
b81ad3ecc2 docs(24-01): resolve checkpoint — BWS key rename confirmed, DNS egress OK
Checkpoint task 3 resolved: BWS secret keys renamed in Bitwarden
(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY confirmed present), DNS egress
to public resolvers confirmed OK, credentials confirmed reaching the
Node process. IAM scope left as an open operational item for the
developer to confirm via AWS console.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 20:16:45 -04:00
97ec5722b1 docs(24-01): create partial SUMMARY, checkpoint pending
Tasks 1-2 complete (AWS SDK + schema migration, types + credential factory).
Task 3 is a blocking human-verify checkpoint requiring BWS/AWS credential
confirmation and live docker/DNS-egress verification — not fabricated, not
run unilaterally. Execution stops here pending developer response.
2026-08-05 19:26:46 -04:00
fadae68fe4 docs(24-01): log pre-existing itglue-search test failures as out-of-scope
Unrelated to Route 53 factory/schema work; not fixed per scope boundary rule.
2026-08-05 19:25:39 -04:00
210f84d343 feat(24-01): implement Route 53 credential factory
- lib/services/route53-factory.ts: isRoute53Configured() / getRoute53Client()
  / resetRoute53Client(), following the veeam-factory.ts singleton shape
- No explicit credentials option passed to Route53Client — relies on the AWS
  SDK's default credential chain reading AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY
  from process.env, exactly how BWS injects them at the container entrypoint
- CLAUDE.md: document the AWS_* env-prefix exception in the integration table
- All 7 route53-factory.test.ts assertions pass; npx tsc --noEmit clean
2026-08-05 19:25:36 -04:00
4dd9d5dab8 test(24-01): add failing test for Route 53 credential factory
- lib/types/route53.ts: camelCase interfaces for zones/records/history/audit-log/sync-result
- lib/services/route53-factory.test.ts: isRoute53Configured() + getRoute53Client() behavior
  cases — fails RED, factory module does not exist yet
2026-08-05 19:24:28 -04:00
b9df27b656 feat(24-01): install AWS Route 53 SDK and create dedicated schema migration
- Add @aws-sdk/client-route-53 dependency (official aws-sdk-js-v3 package)
- Add migrations/102_route53_tables.sql: route53_zones, route53_records,
  route53_record_history (D-06 change ledger), route53_audit_log
  (D-03/D-07 attempt audit log with pending/committed/failed status)
- Seed integration_settings row for key='route53' (D-10, display-only toggle)
- Unbounded retention by design (D-08) — no purge job, no TTL, no DELETE
2026-08-05 19:23:43 -04:00
602c3bb7b5 docs(24): create phase plan 2026-08-05 19:18:13 -04:00
0eab4996e9 docs(24): add pattern map 2026-08-05 19:18:00 -04:00
998c4b1a36 docs(24): research phase domain for AWS Route 53 DNS sync 2026-08-05 19:17:59 -04:00
52affe348c docs(24): cite D-02/D-05/D-08/D-09 in plan must_haves 2026-08-05 19:17:35 -04:00
15e52280b5 docs(24): create phase plan — 7 plans in 4 waves for AWS Route 53 DNS sync 2026-08-05 19:09:15 -04:00
6ad27fcbb6 docs(phase-24): add validation strategy 2026-08-05 18:46:43 -04:00
e289f2d24f docs(state): record phase 24 context session 2026-08-05 18:31:11 -04:00