From d4f936325784ed50d1e0f0195e5acef4f2564fc7 Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Tue, 7 Jul 2026 17:03:56 +1000 Subject: [PATCH 01/12] Phase 0: scaffold CLI, dbconn layer, PG 14-18 harness, and TCB partition Kong CLI stubs, pgx pool with bounded session timeouts, retry classification, targeted blocker termination, testcontainers harness, plus TCB.md/AGENTS.md so agents know the trusted-core boundary from the first commit. --- .github/workflows/ci.yml | 43 +++++++ .gitignore | 5 + .golangci.yml | 7 ++ AGENTS.md | 44 +++++++ Makefile | 20 ++++ README.md | 32 +++++ TCB.md | 68 +++++++++++ cmd/pg-sprite/main.go | 16 +++ go.mod | 68 +++++++++++ go.sum | 166 ++++++++++++++++++++++++++ internal/cli/cli.go | 57 +++++++++ internal/testutil/postgres.go | 73 +++++++++++ pkg/dbconn/dbconn.go | 105 ++++++++++++++++ pkg/dbconn/dbconn_integration_test.go | 115 ++++++++++++++++++ pkg/dbconn/retry.go | 66 ++++++++++ pkg/dbconn/retry_test.go | 90 ++++++++++++++ pkg/dbconn/terminate.go | 36 ++++++ 17 files changed, 1011 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 AGENTS.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 TCB.md create mode 100644 cmd/pg-sprite/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cli/cli.go create mode 100644 internal/testutil/postgres.go create mode 100644 pkg/dbconn/dbconn.go create mode 100644 pkg/dbconn/dbconn_integration_test.go create mode 100644 pkg/dbconn/retry.go create mode 100644 pkg/dbconn/retry_test.go create mode 100644 pkg/dbconn/terminate.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7546aec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - uses: golangci/golangci-lint-action@v6 + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - run: make build + + # The integration suite runs against every Aurora-supported PostgreSQL + # major (see the version-support research doc): the version floor is a + # promise CI enforces, not documentation. + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + pg: ["14", "15", "16", "17", "18"] + env: + PG_VERSION: ${{ matrix.pg }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - run: make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2daff17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +coverage.out +*.test +.idea/ +.vscode/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..31c51ae --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,7 @@ +version: "2" + +linters: + enable: + - bodyclose + - misspell + - nolintlint diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..09389ce --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# AGENTS.md + +Guidance for AI coding agents working on pg-sprite — an online schema-change engine for Aurora +PostgreSQL. Deliberately short: don't restate what you can infer from the code. + +## Read TCB.md first + +This codebase is partitioned into a **trusted computing base** and an untrusted periphery. +[TCB.md](TCB.md) lists which packages are which and the stricter rules that apply inside the +boundary (proof types, bounded everything, `// INV:` locality, the TCB dependency list, the +never-import-`block/spirit` rule). Before touching a `pkg/` package, check its row in TCB.md — +the review bar and the AI-assistance posture differ by side. + +## Build and test + +```sh +make build # build ./... and bin/pg-sprite +make test # full suite; integration tests need Docker +make test-unit # SKIP_INTEGRATION=1, no Docker +make lint # golangci-lint +``` + +- Always run the full `make test` when the scope of a change is unclear. +- Never assume a test failure is unrelated to your change; investigate it. +- Never increase timeouts to fix flakes; find the root cause. +- Integration tests run against real PostgreSQL (testcontainers); `PG_VERSION` selects the + major (default 16), CI runs the matrix 14 → 18. Core logic is validated against a real + database — no mocked-DB tests for core logic. + +## Conventions + +- Use `pkg/dbconn` for connections — never raw `pgx` pools in production code (tests excepted). + Every session runs under bounded `lock_timeout` / `statement_timeout`. +- All SQL parsing goes through `pg_query_go` (once `pkg/statement` exists). No + `strings.Split(";")`, no hand-parsing; a parse failure is an error surfaced to the caller. +- Tests use testify (`require` for setup, `assert` for verification), `t.Context()` (except in + cleanups, which run after the context is cancelled), and named polling deadlines — no bare + `time.Sleep` readiness waits. +- Errors: wrap with context and identifiers (`fmt.Errorf("create slot %s: %w", name, err)`); + never log-and-continue; no silent branch cases; no `nolint`; no `--no-verify`. + +> This file grows with the codebase (see the research build-tracker task for the full +> AGENTS.md derivation from schemabot's). Keep it short: rules earn a line here only when an +> agent can't infer them from the code. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6fe553f --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +GO ?= go + +.PHONY: build test test-unit lint clean + +build: + $(GO) build -o bin/pg-sprite ./cmd/pg-sprite + $(GO) build ./... + +test: + $(GO) test -race ./... + +# Unit tests only (no Docker required). +test-unit: + SKIP_INTEGRATION=1 $(GO) test -race ./... + +lint: + golangci-lint run + +clean: + rm -rf bin diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a04cdd --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# pg-sprite + +> Working name — see the naming task in the research build tracker. + +An online schema-change engine for **Aurora PostgreSQL** (and RDS/community +PostgreSQL 14+): a decoupled **planner → router → executor** design where the +planner classifies each change, the router picks a strategy, and +interchangeable executors carry it out — the cheap native PostgreSQL idiom +when one exists (`CONCURRENTLY`, `NOT VALID` + `VALIDATE`, fast default, +`USING INDEX`), and a log-based, checksum-gated, resumable copy-and-swap when +a genuine table rewrite is unavoidable. + +**Status: Phase 0 (scaffold + test harness).** All subcommands are stubs. The +design docs and the phased build plan currently live in the research repo +(`research/migrations-related/aurora-postgresql/online-schema-change-engine/`) +and will migrate here as part of the open-sourcing work. + +The codebase is partitioned into a small trusted core and an untrusted +periphery — **[TCB.md](TCB.md)** says which packages are which and the rules +that apply inside the boundary. Read it before changing anything under `pkg/`. + +## Development + +```sh +make build # build ./... and the bin/pg-sprite binary +make test # full suite; integration tests need Docker +make test-unit # unit tests only (SKIP_INTEGRATION=1) +make lint # golangci-lint +``` + +Integration tests run against a real PostgreSQL via testcontainers. `PG_VERSION` +selects the major (default 16); CI runs the matrix 14 → 18. diff --git a/TCB.md b/TCB.md new file mode 100644 index 0000000..7273c85 --- /dev/null +++ b/TCB.md @@ -0,0 +1,68 @@ +# Trusted Computing Base + +pg-sprite rewrites production tables — a bug in the wrong place is silent data corruption or an +app-wide outage. The codebase is therefore partitioned into a small **trusted computing base +(TCB)** that enforces the engine's invariants, and an **untrusted periphery** where a bug can +only produce a wrong message, a wasted copy, or a missed optimization. + +**Membership test:** can a bug here corrupt data, lose writes, swap in a wrong table, strand a +replication slot, or take the application down? If yes → TCB. If no → periphery. + +The invariant registry (invariant IDs referenced below) and the full TCB design currently live +in the research corpus (`research/migrations-related/aurora-postgresql/online-schema-change-engine/`, +docs `17-invariants.md` and `18-tcb-model.md`) and migrate here with the open-sourcing work. + +## The partition + +| Package | TCB? | Status | Invariants enforced | +| --- | --- | --- | --- | +| `pkg/dbconn` — pool defaults, advisory lock, terminate-blockers, retries | ✅ TCB | exists (Phase 0) | LK-1, LK-2 primitives | +| `pkg/preflight` — precondition verifier, refusals | ✅ TCB | planned (Phase 1–2) | ST-6, RF-1..RF-5 | +| `pkg/checksum` — chunk verifier, continuous checker, repair | ✅ TCB | planned (Phase 5) | CO-1, CO-2, CO-3 | +| `pkg/copier` — shadow-table chunked copy | ✅ TCB | planned (Phase 4) | CO-4, LK-3 | +| `pkg/applier` — change apply, buffer, flush scheduling | ✅ TCB | planned (Phase 6) | CO-4, CO-5, CO-6, LK-3 | +| `pkg/decode` — logical decoding, LSN/position accounting | ✅ TCB | planned (Phase 6) | ST-4, CO-4 | +| `pkg/checkpoint` — durable resume state | ✅ TCB | planned (Phase 8) | ST-1, ST-2 | +| slot lifecycle (in `pkg/decode`) — create, reap, lag ceiling | ✅ TCB | planned (Phase 8) | ST-3 | +| `pkg/migration` — orchestrator, **cutover swap + fidelity gate** | ✅ TCB | planned (Phase 7) | LK-2, LK-4, ST-5 | +| `pkg/statement`, `pkg/planner`, `pkg/schemadiff`, `pkg/lint` — classify/diff/route | ❌ periphery¹ | planned (Phase 1–2) | (CO-7 holds at the parse boundary) | +| `internal/cli` — CLI, flags, help, prompts | ❌ periphery | exists (stubs) | — | +| status / progress / advisory rendering, metrics | ❌ periphery | planned | — | +| SchemaBot adapter | ❌ periphery | planned (Phase 11) | OC-* hold *at* the boundary | +| `internal/testutil` | ❌ test-only | exists | — | + +¹ **The planner is deliberately outside.** Its verdicts are *requests*, not permissions: a wrong +"native-safe" verdict is capped by the executor's own `lock_timeout` bound; a wrong "copy" +verdict produces a wasteful but *correct* migration (the checksum still gates). The TCB +executors re-verify their own preconditions and never trust that the planner checked. + +## Rules inside the TCB + +The short version — the full rules live in the research doc 18: + +- **Never trust callers.** Every dangerous operation re-verifies its preconditions, whoever the + requester is (CLI, planner, SchemaBot). Untrusted code may request; the TCB enforces. +- **Domain types make illegal states unrepresentable.** Validating passages return proof types + with package-private constructors (`statement.Classified`, `PreflightedTable`, + `VerifiedShadow`, `CleanWatermark`, `TableLock`); dangerous APIs accept only proof types — + e.g. the cutover swap accepts only a `VerifiedShadow`. +- **Put a limit on everything.** Every loop bounded, every queue bounded, every retry counted, + every wait deadlined. An unbounded anything in a TCB package is a review-blocking defect. +- **Assert the positive and the negative space; pair assertions across boundaries.** Invariant + violations use a distinct error class (`ErrInvariantViolation`) naming the invariant ID, and + always abort fail-closed — never a warning, never retried. +- **Locality of behavior.** The enforcement point of an invariant carries a `// INV: ` + comment so a reviewer or agent can grep the ID and see the whole enforcement in one screen. +- **Dependencies inside the TCB become part of the TCB.** Current TCB dependency list: `pgx/v5`, + `pglogrepl`, stdlib. Adding one requires a recorded decision (see the rubric in doc 18 — + copy small things, take pinned dependencies only for load-bearing expertise). pg-sprite + **never imports `block/spirit` as a module**: we port ideas with citations, not code. +- **Priorities when trade-offs are hard:** Correctness → Readability → Ease of use → + Performance. + +## Working here with AI assistance + +- **Inside the TCB: less AI, more steering.** Spec first (the design docs + invariant IDs), + test-first with the invariant's named test obligation, small diffs, careful review. +- **Outside the TCB: more AI, less steering.** Iterate at inference speed; the boundary means a + bug in the periphery cannot corrupt data. diff --git a/cmd/pg-sprite/main.go b/cmd/pg-sprite/main.go new file mode 100644 index 0000000..421257e --- /dev/null +++ b/cmd/pg-sprite/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/alecthomas/kong" + + "github.com/block/pg-sprite/internal/cli" +) + +func main() { + k := kong.Parse(cli.New(), + kong.Name("pg-sprite"), + kong.Description("An online schema-change engine for Aurora PostgreSQL."), + kong.UsageOnError(), + ) + k.FatalIfErrorf(k.Run()) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1f75851 --- /dev/null +++ b/go.mod @@ -0,0 +1,68 @@ +module github.com/block/pg-sprite + +go 1.26 + +require ( + github.com/alecthomas/kong v1.15.0 + github.com/jackc/pgx/v5 v5.10.0 + github.com/stretchr/testify v1.11.1 + github.com/testcontainers/testcontainers-go v0.43.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.43.0 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/moby/api v1.54.2 // indirect + github.com/moby/moby/client v0.4.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/shirou/gopsutil/v4 v4.26.5 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3f1ddff --- /dev/null +++ b/go.sum @@ -0,0 +1,166 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.15.0 h1:BVJstKbpO73zKpmIu+m/aLRrNmWwxXPIGTNin9VmLVI= +github.com/alecthomas/kong v1.15.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= +github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= +github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM= +github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/testcontainers/testcontainers-go v0.43.0 h1:oEQx5MW2DGd9z3AeEQfB2lPM0eLs7ztyaGRu75bFo5A= +github.com/testcontainers/testcontainers-go v0.43.0/go.mod h1:+VxkT2NQnKOZPKi6praMuMKYHYyOGXr0XSBSlSMCzFo= +github.com/testcontainers/testcontainers-go/modules/postgres v0.43.0 h1:ShNOFYAF4lKHvdIG258hi69bSxC88uXnxJkJvNs/IVs= +github.com/testcontainers/testcontainers-go/modules/postgres v0.43.0/go.mod h1:vdq5/RqmGfWeefzyfcVI/pID1rzmc1TDvqXa15bPJks= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..f5fd0d7 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,57 @@ +// Package cli defines the pg-sprite command tree. All subcommands are Phase 0 +// stubs; each later build-plan phase fills one in. +package cli + +import "fmt" + +// CLI is the root command tree. +type CLI struct { + Migrate MigrateCmd `cmd:"" help:"Run a schema change safely."` + Diff DiffCmd `cmd:"" help:"Diff a desired-state schema file against the live schema."` + Fmt FmtCmd `cmd:"" help:"Canonicalize a schema file."` + Lint LintCmd `cmd:"" help:"Lint DDL for unsafe patterns."` + Status StatusCmd `cmd:"" help:"Report the status of a running migration."` +} + +// New returns an empty command tree for kong.Parse. +func New() *CLI { return &CLI{} } + +func notImplemented(cmd string) error { + return fmt.Errorf("%s: not implemented yet (Phase 0 stub)", cmd) +} + +// MigrateCmd runs a schema change (imperative front-end). +type MigrateCmd struct { + Alter string `help:"Imperative ALTER statement to run." name:"alter"` +} + +// Run implements the migrate subcommand. +func (c *MigrateCmd) Run() error { return notImplemented("migrate") } + +// DiffCmd derives statements from a desired-state schema (declarative front-end). +type DiffCmd struct { + Desired string `help:"Path to the desired-state CREATE TABLE .sql file." name:"desired" type:"existingfile"` +} + +// Run implements the diff subcommand. +func (c *DiffCmd) Run() error { return notImplemented("diff") } + +// FmtCmd canonicalizes a schema file. +type FmtCmd struct { + Path string `arg:"" optional:"" help:"Schema file to format." type:"existingfile"` +} + +// Run implements the fmt subcommand. +func (c *FmtCmd) Run() error { return notImplemented("fmt") } + +// LintCmd checks DDL for unsafe or unsupported patterns. +type LintCmd struct{} + +// Run implements the lint subcommand. +func (c *LintCmd) Run() error { return notImplemented("lint") } + +// StatusCmd reports migration progress. +type StatusCmd struct{} + +// Run implements the status subcommand. +func (c *StatusCmd) Run() error { return notImplemented("status") } diff --git a/internal/testutil/postgres.go b/internal/testutil/postgres.go new file mode 100644 index 0000000..8742f39 --- /dev/null +++ b/internal/testutil/postgres.go @@ -0,0 +1,73 @@ +// Package testutil is the integration-test harness: a real PostgreSQL in a +// container plus per-test throwaway schemas. Integration tests are the +// workhorse of this repo — core logic is validated against a real database, +// not mocks. +package testutil + +import ( + "context" + "fmt" + "os" + "sync/atomic" + "testing" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres" +) + +// DefaultPGVersion is the major used when PG_VERSION is unset. CI overrides +// it across the full supported matrix (14 → 18). +const DefaultPGVersion = "16" + +// PGVersion returns the PostgreSQL major version under test. +func PGVersion() string { + if v := os.Getenv("PG_VERSION"); v != "" { + return v + } + return DefaultPGVersion +} + +// StartPostgres starts a disposable PostgreSQL container for the test and +// returns its connection URL. The container is terminated when the test ends. +// Set SKIP_INTEGRATION=1 to skip tests that need Docker. +func StartPostgres(t *testing.T) string { + t.Helper() + if os.Getenv("SKIP_INTEGRATION") != "" { + t.Skip("SKIP_INTEGRATION set; skipping test that needs Docker") + } + // The container must outlive t.Context (which is cancelled before + // cleanups run), so use Background and terminate via t.Cleanup. + ctx := context.Background() + ctr, err := tcpostgres.Run(ctx, "postgres:"+PGVersion(), tcpostgres.BasicWaitStrategies()) + require.NoError(t, err, "start postgres container") + t.Cleanup(func() { + if err := testcontainers.TerminateContainer(ctr); err != nil { + t.Logf("terminate postgres container: %v", err) + } + }) + url, err := ctr.ConnectionString(ctx, "sslmode=disable") + require.NoError(t, err, "container connection string") + return url +} + +var schemaSeq atomic.Int64 + +// NewSchema creates a unique throwaway schema on pool, sets it up for +// cleanup, and returns its name. Tests qualify their objects with it so +// parallel tests on one container never collide. +func NewSchema(t *testing.T, pool *pgxpool.Pool) string { + t.Helper() + name := fmt.Sprintf("t_%d_%d", os.Getpid(), schemaSeq.Add(1)) + _, err := pool.Exec(t.Context(), fmt.Sprintf("CREATE SCHEMA %s", name)) + require.NoError(t, err, "create throwaway schema") + t.Cleanup(func() { + // t.Context is done by cleanup time; use a fresh context. + _, err := pool.Exec(context.Background(), fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", name)) + if err != nil { + t.Logf("drop throwaway schema %s: %v", name, err) + } + }) + return name +} diff --git a/pkg/dbconn/dbconn.go b/pkg/dbconn/dbconn.go new file mode 100644 index 0000000..872147f --- /dev/null +++ b/pkg/dbconn/dbconn.go @@ -0,0 +1,105 @@ +// Package dbconn is the engine's database connectivity layer: pgx pool +// construction with safe session defaults (lock_timeout, statement_timeout), +// optional RDS/Aurora CA TLS, bounded retries for transient errors, and a +// helper to terminate backends blocking a session's lock acquisition. +package dbconn + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "strconv" + "time" + + "github.com/jackc/pgx/v5/pgxpool" +) + +// Defaults for the session timeouts every pooled connection runs under. Every +// statement the engine issues is bounded: lock_timeout keeps the engine from +// sitting at the head of the lock queue (the lock-queue pile-up), and +// statement_timeout bounds runaway work. +const ( + DefaultLockTimeout = 3 * time.Second + DefaultStatementTimeout = 30 * time.Second +) + +// Config describes a connection target. +type Config struct { + // URL is a libpq connection string or URL (postgres://...). + URL string + // LockTimeout is applied as the session lock_timeout on every connection. + // Zero means DefaultLockTimeout. + LockTimeout time.Duration + // StatementTimeout is applied as the session statement_timeout on every + // connection. Zero means DefaultStatementTimeout. + StatementTimeout time.Duration + // CACertPath, when set, enables verify-full TLS using the given CA bundle + // (e.g. the RDS/Aurora global bundle). + CACertPath string + // MaxConns caps the pool size. Zero keeps the pgxpool default. + MaxConns int32 +} + +// NewPool builds a pgx pool from cfg, applies the session defaults, and +// verifies connectivity with a ping before returning. +func NewPool(ctx context.Context, cfg Config) (*pgxpool.Pool, error) { + pc, err := pgxpool.ParseConfig(cfg.URL) + if err != nil { + return nil, fmt.Errorf("parse connection config: %w", err) + } + + lockTimeout := cfg.LockTimeout + if lockTimeout == 0 { + lockTimeout = DefaultLockTimeout + } + stmtTimeout := cfg.StatementTimeout + if stmtTimeout == 0 { + stmtTimeout = DefaultStatementTimeout + } + rp := pc.ConnConfig.RuntimeParams + // A bare integer is interpreted by PostgreSQL as milliseconds. + rp["lock_timeout"] = strconv.FormatInt(lockTimeout.Milliseconds(), 10) + rp["statement_timeout"] = strconv.FormatInt(stmtTimeout.Milliseconds(), 10) + rp["application_name"] = "pg-sprite" + + if cfg.CACertPath != "" { + tlsCfg, err := caTLSConfig(cfg.CACertPath, pc.ConnConfig.Host) + if err != nil { + return nil, err + } + pc.ConnConfig.TLSConfig = tlsCfg + } + if cfg.MaxConns > 0 { + pc.MaxConns = cfg.MaxConns + } + + pool, err := pgxpool.NewWithConfig(ctx, pc) + if err != nil { + return nil, fmt.Errorf("create pool: %w", err) + } + if err := pool.Ping(ctx); err != nil { + pool.Close() + return nil, fmt.Errorf("ping after connect: %w", err) + } + return pool, nil +} + +// caTLSConfig builds a verify-full TLS config trusting only the given CA +// bundle, verifying the server certificate against host. +func caTLSConfig(caCertPath, host string) (*tls.Config, error) { + pem, err := os.ReadFile(caCertPath) + if err != nil { + return nil, fmt.Errorf("read CA bundle: %w", err) + } + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("CA bundle %s contains no usable certificates", caCertPath) + } + return &tls.Config{ + RootCAs: roots, + ServerName: host, + MinVersion: tls.VersionTLS12, + }, nil +} diff --git a/pkg/dbconn/dbconn_integration_test.go b/pkg/dbconn/dbconn_integration_test.go new file mode 100644 index 0000000..2ba2152 --- /dev/null +++ b/pkg/dbconn/dbconn_integration_test.go @@ -0,0 +1,115 @@ +package dbconn_test + +import ( + "fmt" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgconn" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/block/pg-sprite/internal/testutil" + "github.com/block/pg-sprite/pkg/dbconn" +) + +func TestPoolIntegration(t *testing.T) { + url := testutil.StartPostgres(t) + + pool, err := dbconn.NewPool(t.Context(), dbconn.Config{ + URL: url, + LockTimeout: 300 * time.Millisecond, + StatementTimeout: 500 * time.Millisecond, + }) + require.NoError(t, err) + t.Cleanup(pool.Close) + + t.Run("session timeouts are applied", func(t *testing.T) { + var lockTimeout, stmtTimeout string + require.NoError(t, pool.QueryRow(t.Context(), "SHOW lock_timeout").Scan(&lockTimeout)) + require.NoError(t, pool.QueryRow(t.Context(), "SHOW statement_timeout").Scan(&stmtTimeout)) + assert.Equal(t, "300ms", lockTimeout) + assert.Equal(t, "500ms", stmtTimeout) + }) + + t.Run("statement_timeout cancels runaway work", func(t *testing.T) { + _, err := pool.Exec(t.Context(), "SELECT pg_sleep(5)") + require.Error(t, err) + var pgErr *pgconn.PgError + require.ErrorAs(t, err, &pgErr) + assert.Equal(t, "57014", pgErr.Code, "expected query_canceled from statement_timeout") + }) + + t.Run("lock wait beyond lock_timeout is a retryable error", func(t *testing.T) { + schema := testutil.NewSchema(t, pool) + table := schema + ".locked" + _, err := pool.Exec(t.Context(), "CREATE TABLE "+table+" (id int primary key)") + require.NoError(t, err) + + holder, err := pool.Acquire(t.Context()) + require.NoError(t, err) + defer holder.Release() + tx, err := holder.Begin(t.Context()) + require.NoError(t, err) + defer func() { _ = tx.Rollback(t.Context()) }() + _, err = tx.Exec(t.Context(), "LOCK TABLE "+table+" IN ACCESS EXCLUSIVE MODE") + require.NoError(t, err) + + _, err = pool.Exec(t.Context(), "INSERT INTO "+table+" VALUES (1)") + require.Error(t, err) + assert.True(t, dbconn.Retryable(err), "lock_not_available must classify as retryable, got: %v", err) + }) + + t.Run("TerminateBlockers evicts exactly the blocking backend", func(t *testing.T) { + schema := testutil.NewSchema(t, pool) + table := schema + ".swap_target" + _, err := pool.Exec(t.Context(), "CREATE TABLE "+table+" (id int primary key)") + require.NoError(t, err) + + // Backend A holds ACCESS EXCLUSIVE in an open transaction. + connA, err := pool.Acquire(t.Context()) + require.NoError(t, err) + defer connA.Release() + var aPid int + require.NoError(t, connA.QueryRow(t.Context(), "SELECT pg_backend_pid()").Scan(&aPid)) + txA, err := connA.Begin(t.Context()) + require.NoError(t, err) + defer func() { _ = txA.Rollback(t.Context()) }() + _, err = txA.Exec(t.Context(), "LOCK TABLE "+table+" IN ACCESS EXCLUSIVE MODE") + require.NoError(t, err) + + // Backend B queues behind A with a generous lock_timeout. + connB, err := pool.Acquire(t.Context()) + require.NoError(t, err) + defer connB.Release() + var bPid int + require.NoError(t, connB.QueryRow(t.Context(), "SELECT pg_backend_pid()").Scan(&bPid)) + _, err = connB.Exec(t.Context(), "SET lock_timeout = '30s'") + require.NoError(t, err) + insertDone := make(chan error, 1) + go func() { + _, err := connB.Exec(t.Context(), "INSERT INTO "+table+" VALUES (1)") + insertDone <- err + }() + + const blockedDeadline = 10 * time.Second + require.Eventually(t, func() bool { + var blocked bool + err := pool.QueryRow(t.Context(), + "SELECT cardinality(pg_blocking_pids($1::int)) > 0", bPid).Scan(&blocked) + return err == nil && blocked + }, blockedDeadline, 50*time.Millisecond, "backend B never showed up as blocked behind A") + + terminated, err := dbconn.TerminateBlockers(t.Context(), pool, bPid) + require.NoError(t, err) + assert.Equal(t, []int{aPid}, terminated, "only the holder should be terminated") + + const insertDeadline = 10 * time.Second + select { + case err := <-insertDone: + require.NoError(t, err, "B's insert should succeed once the blocker is evicted") + case <-time.After(insertDeadline): + t.Fatal(fmt.Sprintf("B's insert still blocked %s after terminating the blocker", insertDeadline)) + } + }) +} diff --git a/pkg/dbconn/retry.go b/pkg/dbconn/retry.go new file mode 100644 index 0000000..72455bc --- /dev/null +++ b/pkg/dbconn/retry.go @@ -0,0 +1,66 @@ +package dbconn + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/jackc/pgx/v5/pgconn" +) + +// SQLSTATE codes the engine treats as transient. lock_not_available is the +// expected outcome of every bounded lock acquisition (the lock-queue +// mitigation), so it must be retryable by design. +const ( + codeLockNotAvailable = "55P03" + codeDeadlockDetected = "40P01" + codeSerializationFailure = "40001" + classConnectionException = "08" +) + +// Retryable reports whether err is transient: a bounded lock wait that timed +// out, a deadlock or serialization failure, or a connection-level error. +func Retryable(err error) bool { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + switch pgErr.Code { + case codeLockNotAvailable, codeDeadlockDetected, codeSerializationFailure: + return true + } + return strings.HasPrefix(pgErr.Code, classConnectionException) + } + return pgconn.SafeToRetry(err) +} + +// Retry runs fn up to attempts times with linear backoff, retrying only +// errors Retryable classifies as transient. Non-transient errors return +// immediately; context cancellation always wins. +func Retry(ctx context.Context, attempts int, backoff time.Duration, fn func(context.Context) error) error { + if attempts < 1 { + return fmt.Errorf("retry: attempts must be >= 1, got %d", attempts) + } + var last error + for i := range attempts { + if err := ctx.Err(); err != nil { + return err + } + last = fn(ctx) + if last == nil { + return nil + } + if !Retryable(last) { + return last + } + if i == attempts-1 { + break + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(backoff * time.Duration(i+1)): + } + } + return fmt.Errorf("retries exhausted after %d attempts: %w", attempts, last) +} diff --git a/pkg/dbconn/retry_test.go b/pkg/dbconn/retry_test.go new file mode 100644 index 0000000..091b450 --- /dev/null +++ b/pkg/dbconn/retry_test.go @@ -0,0 +1,90 @@ +package dbconn_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgconn" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/block/pg-sprite/pkg/dbconn" +) + +func TestRetryable(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + {"nil is not retryable", nil, false}, + {"lock_not_available is retryable", &pgconn.PgError{Code: "55P03"}, true}, + {"deadlock_detected is retryable", &pgconn.PgError{Code: "40P01"}, true}, + {"serialization_failure is retryable", &pgconn.PgError{Code: "40001"}, true}, + {"connection_exception class is retryable", &pgconn.PgError{Code: "08006"}, true}, + {"syntax_error is not retryable", &pgconn.PgError{Code: "42601"}, false}, + {"undefined_table is not retryable", &pgconn.PgError{Code: "42P01"}, false}, + {"plain error is not retryable", errors.New("boom"), false}, + {"wrapped pg error is classified", wrap(&pgconn.PgError{Code: "55P03"}), true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, dbconn.Retryable(tt.err)) + }) + } +} + +func wrap(err error) error { return errors.Join(errors.New("context"), err) } + +func TestRetry(t *testing.T) { + t.Run("succeeds after transient failures", func(t *testing.T) { + calls := 0 + err := dbconn.Retry(t.Context(), 3, time.Millisecond, func(context.Context) error { + calls++ + if calls < 3 { + return &pgconn.PgError{Code: "55P03"} + } + return nil + }) + require.NoError(t, err) + assert.Equal(t, 3, calls) + }) + + t.Run("returns non-transient error immediately", func(t *testing.T) { + calls := 0 + wantErr := &pgconn.PgError{Code: "42601"} + err := dbconn.Retry(t.Context(), 3, time.Millisecond, func(context.Context) error { + calls++ + return wantErr + }) + require.ErrorIs(t, err, wantErr) + assert.Equal(t, 1, calls) + }) + + t.Run("exhausts attempts on persistent transient error", func(t *testing.T) { + calls := 0 + err := dbconn.Retry(t.Context(), 3, time.Millisecond, func(context.Context) error { + calls++ + return &pgconn.PgError{Code: "55P03"} + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "retries exhausted after 3 attempts") + assert.Equal(t, 3, calls) + }) + + t.Run("respects context cancellation", func(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + cancel() + err := dbconn.Retry(ctx, 3, time.Millisecond, func(context.Context) error { + return &pgconn.PgError{Code: "55P03"} + }) + require.ErrorIs(t, err, context.Canceled) + }) + + t.Run("rejects zero attempts", func(t *testing.T) { + err := dbconn.Retry(t.Context(), 0, time.Millisecond, func(context.Context) error { return nil }) + require.Error(t, err) + }) +} diff --git a/pkg/dbconn/terminate.go b/pkg/dbconn/terminate.go new file mode 100644 index 0000000..ffa0f09 --- /dev/null +++ b/pkg/dbconn/terminate.go @@ -0,0 +1,36 @@ +package dbconn + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" +) + +// Querier is the query surface TerminateBlockers needs; *pgxpool.Pool and +// *pgx.Conn both satisfy it. +type Querier interface { + Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) +} + +// TerminateBlockers terminates every backend currently blocking pid's lock +// acquisition (per pg_blocking_pids) and returns the pids it terminated. +// +// This is the bounded-cutover escape hatch: it targets only the backends +// standing in front of a specific waiting session (e.g. the cutover swap), +// never a broad sweep. Callers decide whether evicting those backends is +// acceptable; this function only does the targeted termination. +func TerminateBlockers(ctx context.Context, q Querier, pid int) ([]int, error) { + rows, err := q.Query(ctx, ` + SELECT b.pid + FROM unnest(pg_blocking_pids($1::int)) AS b(pid) + WHERE pg_terminate_backend(b.pid)`, pid) + if err != nil { + return nil, fmt.Errorf("terminate backends blocking pid %d: %w", pid, err) + } + terminated, err := pgx.CollectRows(rows, pgx.RowTo[int]) + if err != nil { + return nil, fmt.Errorf("collect terminated pids: %w", err) + } + return terminated, nil +} From 329199949c8bd41eb2edda374e7161cbd5b05083 Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Tue, 7 Jul 2026 18:27:24 +1000 Subject: [PATCH 02/12] dbconn: auto-TLS for RDS/Aurora via embedded global bundle; Go maxims in AGENTS.md Mirror spirit's TLS model: embed the AWS RDS global CA bundle, detect *.rds.amazonaws.com endpoints (anchored against subdomain spoofing), default them to verify-full with no plaintext fallback, and honor an explicit sslmode while injecting the RDS roots when verification is requested without a bundle. --- AGENTS.md | 26 + pkg/dbconn/dbconn.go | 40 +- pkg/dbconn/rds.go | 47 + pkg/dbconn/rdsGlobalBundle.pem | 2736 ++++++++++++++++++++++++++++++++ pkg/dbconn/rds_test.go | 92 ++ 5 files changed, 2935 insertions(+), 6 deletions(-) create mode 100644 pkg/dbconn/rds.go create mode 100644 pkg/dbconn/rdsGlobalBundle.pem create mode 100644 pkg/dbconn/rds_test.go diff --git a/AGENTS.md b/AGENTS.md index 09389ce..6de5cfa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,6 +39,32 @@ make lint # golangci-lint - Errors: wrap with context and identifiers (`fmt.Errorf("create slot %s: %w", name, err)`); never log-and-continue; no silent branch cases; no `nolint`; no `--no-verify`. +## Go maxims + +- **"A little copying is better than a little dependency."** Small mechanics (retry/backoff, CA + loading, keepalives, tiny helpers) are hand-written or copied with an attributing comment — + never imported. Take pinned dependencies only for load-bearing expertise (the parser, the wire + protocol); a dependency inside a TCB package needs a recorded decision (see + [TCB.md](TCB.md)). **Never import `github.com/block/spirit` as a module** — port ideas with + citations, not code. +- **Expose the smallest interface that does the job.** Export domain types and their validating + constructors, not internals; no re-exports or plain-delegation wrappers — callers import the + source package. +- **Clear is better than clever.** No clever SQL, no dense compound predicates — extract a named + helper for any 3+-term or state-machine conditional. Separate error handling from state + decisions. This code gets read during incidents; readability outranks ease of use and + performance here (correctness outranks both). +- **Minimize state; derive rather than store.** If a value can be recomputed from the database + or the checkpoint, don't persist it. +- **Don't conflate causes.** No `if err != nil || value == nil` when the cases mean different + things; no deduping unrelated branches with `||` — separate branches calling a shared helper. +- Concurrency: `wg.Go(...)`; `context.WithoutCancel(ctx)` for background goroutines that must + outlive a request; snapshot shared state under one lock acquisition, not several. +- Cleanup: close errors are logged, not discarded — no bare `_ = x.Close()` (one exception: a + redundant safety closer on a handle someone else owns discards its guaranteed + already-closed error). +- State comparisons use typed constants and helpers, never raw string matching. + > This file grows with the codebase (see the research build-tracker task for the full > AGENTS.md derivation from schemabot's). Keep it short: rules earn a line here only when an > agent can't infer them from the code. diff --git a/pkg/dbconn/dbconn.go b/pkg/dbconn/dbconn.go index 872147f..e0f7218 100644 --- a/pkg/dbconn/dbconn.go +++ b/pkg/dbconn/dbconn.go @@ -11,6 +11,7 @@ import ( "fmt" "os" "strconv" + "strings" "time" "github.com/jackc/pgx/v5/pgxpool" @@ -64,12 +65,8 @@ func NewPool(ctx context.Context, cfg Config) (*pgxpool.Pool, error) { rp["statement_timeout"] = strconv.FormatInt(stmtTimeout.Milliseconds(), 10) rp["application_name"] = "pg-sprite" - if cfg.CACertPath != "" { - tlsCfg, err := caTLSConfig(cfg.CACertPath, pc.ConnConfig.Host) - if err != nil { - return nil, err - } - pc.ConnConfig.TLSConfig = tlsCfg + if err := configureTLS(pc, cfg); err != nil { + return nil, err } if cfg.MaxConns > 0 { pc.MaxConns = cfg.MaxConns @@ -86,6 +83,37 @@ func NewPool(ctx context.Context, cfg Config) (*pgxpool.Pool, error) { return pool, nil } +// configureTLS decides the pool's TLS setup: an explicit CA bundle wins; +// otherwise RDS/Aurora endpoints get TLS automatically via the embedded RDS +// global bundle (see rds.go); everything else keeps whatever the connection +// string asked for. +func configureTLS(pc *pgxpool.Config, cfg Config) error { + switch { + case cfg.CACertPath != "": + tlsCfg, err := caTLSConfig(cfg.CACertPath, pc.ConnConfig.Host) + if err != nil { + return err + } + pc.ConnConfig.TLSConfig = tlsCfg + case IsRDSHost(pc.ConnConfig.Host): + if strings.Contains(cfg.URL, "sslmode=") { + // The caller chose an sslmode; honor it — but when verification + // was requested without a root bundle, supply the embedded RDS + // roots, which are not in system trust stores. + if tc := pc.ConnConfig.TLSConfig; tc != nil && !tc.InsecureSkipVerify && tc.RootCAs == nil { + tc.RootCAs = rdsRootPool() + } + } else { + // No explicit sslmode on an RDS/Aurora endpoint: auto-enable + // verify-full with the embedded bundle, and drop the plaintext + // fallbacks the default sslmode would otherwise allow. + pc.ConnConfig.TLSConfig = rdsTLSConfig(pc.ConnConfig.Host) + pc.ConnConfig.Fallbacks = nil + } + } + return nil +} + // caTLSConfig builds a verify-full TLS config trusting only the given CA // bundle, verifying the server certificate against host. func caTLSConfig(caCertPath, host string) (*tls.Config, error) { diff --git a/pkg/dbconn/rds.go b/pkg/dbconn/rds.go new file mode 100644 index 0000000..0134718 --- /dev/null +++ b/pkg/dbconn/rds.go @@ -0,0 +1,47 @@ +package dbconn + +import ( + "crypto/tls" + "crypto/x509" + _ "embed" + "regexp" +) + +// rdsGlobalBundle is the AWS RDS/Aurora global certificate bundle from +// https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem, embedded +// so RDS/Aurora connections verify out of the box with no bundle to install. +// +//go:embed rdsGlobalBundle.pem +var rdsGlobalBundle []byte + +// rdsHostPattern matches Amazon RDS/Aurora hostnames with an optional :port +// suffix. The leading `\.` ensures only legitimate *.rds.amazonaws.com +// subdomains match, so a hostname like fake-rds.amazonaws.com cannot spoof +// its way into the auto-TLS path. +var rdsHostPattern = regexp.MustCompile(`\.rds\.amazonaws\.com(:\d+)?$`) + +// IsRDSHost reports whether host is an Amazon RDS/Aurora endpoint. +func IsRDSHost(host string) bool { + return rdsHostPattern.MatchString(host) +} + +// rdsRootPool returns a cert pool holding the embedded RDS global bundle. +func rdsRootPool() *x509.CertPool { + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(rdsGlobalBundle) { + // The bundle is embedded at compile time, so failing to parse it is + // a build defect, not an operating error. + panic("embedded RDS global bundle contains no usable certificates") + } + return pool +} + +// rdsTLSConfig returns a verify-full TLS config for an RDS/Aurora host using +// the embedded global bundle. +func rdsTLSConfig(host string) *tls.Config { + return &tls.Config{ + RootCAs: rdsRootPool(), + ServerName: host, + MinVersion: tls.VersionTLS12, + } +} diff --git a/pkg/dbconn/rdsGlobalBundle.pem b/pkg/dbconn/rdsGlobalBundle.pem new file mode 100644 index 0000000..99351b7 --- /dev/null +++ b/pkg/dbconn/rdsGlobalBundle.pem @@ -0,0 +1,2736 @@ +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQdOCSuA9psBpQd8EI368/0DANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTgwNjI2WhgPMjA2MTA1MTkxOTA2MjZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgc2EtZWFzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6ftL6w8v3dB2yW +LjCxSP1D7ZsOTeLZOSCz1Zv0Gkd0XLhil5MdHOHBvwH/DrXqFU2oGzCRuAy+aZis +DardJU6ChyIQIciXCO37f0K23edhtpXuruTLLwUwzeEPdcnLPCX+sWEn9Y5FPnVm +pCd6J8edH2IfSGoa9LdErkpuESXdidLym/w0tWG/O2By4TabkNSmpdrCL00cqI+c +prA8Bx1jX8/9sY0gpAovtuFaRN+Ivg3PAnWuhqiSYyQ5nC2qDparOWuDiOhpY56E +EgmTvjwqMMjNtExfYx6Rv2Ndu50TriiNKEZBzEtkekwXInTupmYTvc7U83P/959V +UiQ+WSMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU4uYHdH0+ +bUeh81Eq2l5/RJbW+vswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBhxcExJ+w74bvDknrPZDRgTeMLYgbVJjx2ExH7/Ac5FZZWcpUpFwWMIJJxtewI +AnhryzM3tQYYd4CG9O+Iu0+h/VVfW7e4O3joWVkxNMb820kQSEwvZfA78aItGwOY +WSaFNVRyloVicZRNJSyb1UL9EiJ9ldhxm4LTT0ax+4ontI7zTx6n6h8Sr6r/UOvX +d9T5aUUENWeo6M9jGupHNn3BobtL7BZm2oS8wX8IVYj4tl0q5T89zDi2x0MxbsIV +5ZjwqBQ5JWKv7ASGPb+z286RjPA9R2knF4lJVZrYuNV90rHvI/ECyt/JrDqeljGL +BLl1W/UsvZo6ldLIpoMbbrb5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQUfVbqapkLYpUqcLajpTJWzANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA2MjMyMDA5WhgPMjA2MjA1MDcwMDIwMDlaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJIeovu3 +ewI9FVitXMQzvkh34aQ6WyI4NO3YepfJaePiv3cnyFGYHN2S1cR3UQcLWgypP5va +j6bfroqwGbCbZZcb+6cyOB4ceKO9Ws1UkcaGHnNDcy5gXR7aCW2OGTUfinUuhd2d +5bOGgV7JsPbpw0bwJ156+MwfOK40OLCWVbzy8B1kITs4RUPNa/ZJnvIbiMu9rdj4 +8y7GSFJLnKCjlOFUkNI5LcaYvI1+ybuNgphT3nuu5ZirvTswGakGUT/Q0J3dxP0J +pDfg5Sj/2G4gXiaM0LppVOoU5yEwVewhQ250l0eQAqSrwPqAkdTg9ng360zqCFPE +JPPcgI1tdGUgneECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +/2AJVxWdZxc8eJgdpbwpW7b0f7IwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQBYm63jTu2qYKJ94gKnqc+oUgqmb1mTXmgmp/lXDbxonjszJDOXFbri +3CCO7xB2sg9bd5YWY8sGKHaWmENj3FZpCmoefbUx++8D7Mny95Cz8R32rNcwsPTl +ebpd9A/Oaw5ug6M0x/cNr0qzF8Wk9Dx+nFEimp8RYQdKvLDfNFZHjPa1itnTiD8M +TorAqj+VwnUGHOYBsT/0NY12tnwXdD+ATWfpEHdOXV+kTMqFFwDyhfgRVNpTc+os +ygr8SwhnSCpJPB/EYl2S7r+tgAbJOkuwUvGT4pTqrzDQEhwE7swgepnHC87zhf6l +qN6mVpSnQKQLm6Ob5TeCEFgcyElsF5bH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAOxu0I1QuMAhIeszB3fJIlkwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjIwNjU5WhgPMjEyMTA1MjQyMzA2NTlaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEz4bylRcGqqDWdP7gQIIoTHdBK6FNtKH1 +4SkEIXRXkYDmRvL9Bci1MuGrwuvrka5TDj4b7e+csY0llEzHpKfq6nJPFljoYYP9 +uqHFkv77nOpJJ633KOr8IxmeHW5RXgrZo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQQikVz8wmjd9eDFRXzBIU8OseiGzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwf06Mcrpw1O0EBLBBrp84m37NYtOkE/0Z0O+C7D41wnXi +EQdn6PXUVgdD23Gj82SrAjEAklhKs+liO1PtN15yeZR1Io98nFve+lLptaLakZcH ++hfFuUtCqMbaI8CdvJlKnPqT +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRALyWMTyCebLZOGcZZQmkmfcwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyODAzWhgPMjEyMTA1MjQyMTI4MDNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +wGFiyDyCrGqgdn4fXG12cxKAAfVvhMea1mw5h9CVRoavkPqhzQpAitSOuMB9DeiP +wQyqcsiGl/cTEau4L+AUBG8b9v26RlY48exUYBXj8CieYntOT9iNw5WtdYJa3kF/ +JxgI+HDMzE9cmHDs5DOO3S0uwZVyra/xE1ymfSlpOeUIOTpHRJv97CBUEpaZMUW5 +Sr6GruuOwFVpO5FX3A/jQlcS+UN4GjSRgDUJuqg6RRQldEZGCVCCmodbByvI2fGm +reGpsPJD54KkmAX08nOR8e5hkGoHxq0m2DLD4SrOFmt65vG47qnuwplWJjtk9B3Z +9wDoopwZLBOtlkPIkUllWm1P8EuHC1IKOA+wSP6XdT7cy8S77wgyHzR0ynxv7q/l +vlZtH30wnNqFI0y9FeogD0TGMCHcnGqfBSicJXPy9T4fU6f0r1HwqKwPp2GArwe7 +dnqLTj2D7M9MyVtFjEs6gfGWXmu1y5uDrf+CszurE8Cycoma+OfjjuVQgWOCy7Nd +jJswPxAroTzVfpgoxXza4ShUY10woZu0/J+HmNmqK7lh4NS75q1tz75in8uTZDkV +be7GK+SEusTrRgcf3tlgPjSTWG3veNzFDF2Vn1GLJXmuZfhdlVQDBNXW4MNREExS +dG57kJjICpT+r8X+si+5j51gRzkSnMYs7VHulpxfcwECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU4JWOpDBmUBuWKvGPZelw87ezhL8wDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBRNLMql7itvXSEFQRAnyOjivHz +l5IlWVQjAbOUr6ogZcwvK6YpxNAFW5zQr8F+fdkiypLz1kk5irx9TIpff0BWC9hQ +/odMPO8Gxn8+COlSvc+dLsF2Dax3Hvz0zLeKMo+cYisJOzpdR/eKd0/AmFdkvQoM +AOK9n0yYvVJU2IrSgeJBiiCarpKSeAktEVQ4rvyacQGr+QAPkkjRwm+5LHZKK43W +nNnggRli9N/27qYtc5bgr3AaQEhEXMI4RxPRXCLsod0ehMGWyRRK728a+6PMMJAJ +WHOU0x7LCEMPP/bvpLj3BdvSGqNor4ZtyXEbwREry1uzsgODeRRns5acPwTM6ff+ +CmxO2NZ0OktIUSYRmf6H/ZFlZrIhV8uWaIwEJDz71qvj7buhQ+RFDZ9CNL64C0X6 +mf0zJGEpddjANHaaVky+F4gYMtEy2K2Lcm4JGTdyIzUoIe+atzCnRp0QeIcuWtF+ +s8AjDYCVFNypcMmqbRmNpITSnOoCHSRuVkY3gutVoYyMLbp8Jm9SJnCIlEWTA6Rm +wADOMGZJVn5/XRTRuetVOB3KlQDjs9OO01XN5NzGSZO2KT9ngAUfh9Eqhf1iRWSP +nZlRbQ2NRCuY/oJ5N59mLGxnNJSE7giEKEBRhTQ/XEPIUYAUPD5fca0arKRJwbol +l9Se1Hsq0ZU5f+OZKQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +5zT5+Bs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Hing +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAI+asxQA/MB1cGyyrC0MPpkwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBjYS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIzMDkxMzIwMjEzNFoYDzIwNjMwOTEzMjEyMTMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMHvQITTZcfl2O +yfzRIAPKwzzlc8eXWdXef7VUsbezg3lm9RC+vArO4JuAzta/aLw1D94wPSRm9JXX +NkP3obO6Ql80/0doooU6BAPceD0xmEWC4aCFT/5KWsD6Sy2/Rjwq3NKBTwzxLwYK +GqVsBp8AdrzDTmdRETC+Dg2czEo32mTDAA1uMgqrz6xxeTYroj8NTSTp6jfE6C0n +YgzYmVQCEIjHqI49j7k3jfT3P2skCVKGJwQzoZnerFacKzXsDB18uIqU7NaMc2cX +kOd0gRqpyKOzAHU2m5/S4jw4UHdkoI3E7nkayuen8ZPKH2YqWtTXUrXGhSTT34nX +yiFgu+vTAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHzz1NTd +TOm9zAv4d8l6XCFKSdJfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAodBvd0cvXQYhFBef2evnuI9XA+AC/Q9P1nYtbp5MPA4aFhy5v9rjW8wwJX14 +l+ltd2o3tz8PFDBZ1NX2ooiWVlZthQxKn1/xDVKsTXHbYUXItPQ3jI5IscB5IML8 +oCzAbkoLXsSPNOVFP5P4l4cZEMqHGRnBag7hLJZvmvzZSBnz+ioC2jpjVluF8kDX +fQGNjqPECik68CqbSV0SaQ0cgEoYTDjwON5ZLBeS8sxR2abE/gsj4VFYl5w/uEBd +w3Tt9uGfIy+wd2tNj6isGC6PcbPMjA31jd+ifs2yNzigqkcYTTWFtnvh4a8xiecm +GHu2EgH0Jqzz500N7L3uQdPkdg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRALnItUH64VieFPvDUCOG5E0wDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjQwNTE1MjE1MDQxWhgPMjEyNDA1MTUyMjUwNDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTUgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +u2Bc0RjN0vB7EM+h0yts1jSqzDd1v5FcxCDbC7vKPVCq/1pYNTIxQj77HiMcYtvL +Bfi9AQFibU1C9gN62kUmSe0QaNGqQL2g/6YpB4qI8psIsCt3aIigbhwEEpebhIU/ +vhr/pvLKhkQOSLxJVlX0j18hU5RVqOefCdFm9FmjFLge/m1Yzv2aFifRKIzdtkfp +4VZBzh7EzP6lxkU3SAcW9yRu/t4oY274ICnGisv2TR15hHlP0wUP6p5S3ot2q/xJ +57x8nzI3kQyC6a+n+kSzZzITboKWrsx3Jd2PdB4VC84P/YoAC3cwfmacmQVT01c8 +io1eO+BxCtWUNbwCv0Hd10bHI18rVzJhJPb3xg1i1Sc3sbcrADOONsuhxqwffjUe +XdVMdsjX2mYxQ520qnh5DwQkx3JyW6QwI/ueU9xbMuPTwAauXil7B9qx1IDViYUw +BvMDnxYbYHlDezYIc4WoNoA2KflMnNtN2WDiM7tvQKmWI8yYZrNdnqBD0HYR+neP +z69Tqy8i24CDoR9o3s5LxR58SgFPqu9RWu8uL6vfNLL2M8qQ4VueOWSyzRs9b3W5 +GVjA4U1CxlF3EirHEjciq6UEXr5+ZVf79iXGOwBVDzuim1LYfoTBgkMXKxyEzWYT +QCzf6VPW4x7eMQIriLl18YocHrqJQ7+BfMziYjOh0rcCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUQdyu9F6eLFuxe437iU/GXyFHU1owDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCbbNUJRQ1gy1lKxCoszcyujCI5 +df0EGdadQL6BgaXws/uCFvHepB5lO62InAMTURCREeRtrCNyn1rEKnsqhAW0UokQ +Z+YOHcclgPsXmSQVjIUgnlE45mrPS/9mO8TzhCI3wyiELp6oa67RSiJ1Qcsypa4z +zHDkYdhFW3sxY8i2p2tqdkJz1ZEQd7FIpX+vrBVIkoqtGAn4urLaMq4CTNJCNepR +s4OGaoQVY43q2kcguRPDZVOFK5+GlrC2AzHMSVt5fFSCchgYxBZsS3UIVKm8YJ7v +1h85RwtNCHwwDt1uP43yLp5qfUmsfeaNmZiOk9AawxPCmy6XaSkQcLz+CQhG9T4W +siQMg6tagIUw1e4zFm7GXmeOCPc//ycGNDXgprMQzjK+AT4ed8iK+JnWlheMq5uf +XxQDSfakuAIEgJWPAzebjCo33O2j1PQfzbt1Ahs7f+gFczizfpatYkXcOTmLfG1l +QKj9jVNOIQSJt5PxH+QTDWQtkX/tGp/HS5a3dWusW/TnC3yakGqqfGx3cB/E00gF +geg0LYo1uOBjIYQbkp3Z6NKfcc/nb0ksV7feKm5f3rSO8NnA0Ou8YHb84LYDLYDf +VSR9SwSBhGw31otMTAsJdNTHJwfCcxfGtIvUfAsWUAh6qSo4es/hUV60pj01VWpq +Er+ItMFHuoSTmx18bw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRANxgyBbnxgTEOpDul2ZnC0UwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNjEwMTgxOTA3WhgPMjA2MTA2MTAxOTE5MDda +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xnwSDAChrMkfk5TA4Dk8hKzStDlSlONzmd3fTG0Wqr5+x3EmFT6Ksiu/WIwEl9J2 +K98UI7vYyuZfCxUKb1iMPeBdVGqk0zb92GpURd+Iz/+K1ps9ZLeGBkzR8mBmAi1S +OfpwKiTBzIv6E8twhEn4IUpHsdcuX/2Y78uESpJyM8O5CpkG0JaV9FNEbDkJeBUQ +Ao2qqNcH4R0Qcr5pyeqA9Zto1RswgL06BQMI9dTpfwSP5VvkvcNUaLl7Zv5WzLQE +JzORWePvdPzzvWEkY/3FPjxBypuYwssKaERW0fkPDmPtykktP9W/oJolKUFI6pXp +y+Y6p6/AVdnQD2zZjW5FhQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBT+jEKs96LC+/X4BZkUYUkzPfXdqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIGQqgqcQ6XSGkmNebzR6DhadTbfDmbYeN5N0Vuzv+Tdmufb +tMGjdjnYMg4B+IVnTKQb+Ox3pL9gbX6KglGK8HupobmIRtwKVth+gYYz3m0SL/Nk +haWPYzOm0x3tJm8jSdufJcEob4/ATce9JwseLl76pSWdl5A4lLjnhPPKudUDfH+1 +BLNUi3lxpp6GkC8aWUPtupnhZuXddolTLOuA3GwTZySI44NfaFRm+o83N1jp+EwD +6e94M4cTRzjUv6J3MZmSbdtQP/Tk1uz2K4bQZGP0PZC3bVpqiesdE/xr+wbu8uHr +cM1JXH0AmXf1yIkTgyWzmvt0k1/vgcw5ixAqvvE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIRAMhw98EQU18mIji+unM2YH8wDQYJKoZIhvcNAQELBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQyMjJaGA8yMDYyMDYwNjIyNDIyMlowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeeRoLfTm+7 +vqm7ZlFSx+1/CGYHyYrOOryM4/Z3dqYVHFMgWTR7V3ziO8RZ6yUanrRcWVX3PZbF +AfX0KFE8OgLsXEZIX8odSrq86+/Th5eZOchB2fDBsUB7GuN2rvFBbM8lTI9ivVOU +lbuTnYyb55nOXN7TpmH2bK+z5c1y9RVC5iQsNAl6IJNvSN8VCqXh31eK5MlKB4DT ++Y3OivCrSGsjM+UR59uZmwuFB1h+icE+U0p9Ct3Mjq3MzSX5tQb6ElTNGlfmyGpW +Kh7GQ5XU1KaKNZXoJ37H53woNSlq56bpVrKI4uv7ATpdpFubOnSLtpsKlpLdR3sy +Ws245200pC8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUp0ki +6+eWvsnBjQhMxwMW5pwn7DgwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4IBAQB2V8lv0aqbYQpj/bmVv/83QfE4vOxKCJAHv7DQ35cJsTyBdF+8pBczzi3t +3VNL5IUgW6WkyuUOWnE0eqAFOUVj0yTS1jSAtfl3vOOzGJZmWBbqm9BKEdu1D8O6 +sB8bnomwiab2tNDHPmUslpdDqdabbkWwNWzLJ97oGFZ7KNODMEPXWKWNxg33iHfS +/nlmnrTVI3XgaNK9qLZiUrxu9Yz5gxi/1K+sG9/Dajd32ZxjRwDipOLiZbiXQrsd +qzIMY4GcWf3g1gHL5mCTfk7dG22h/rhPyGV0svaDnsb+hOt6sv1McMN6Y3Ou0mtM +/UaAXojREmJmTSCNvs2aBny3/2sy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQID5K+9PRwKbkBpe2il3rBjANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG14LWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjQwOTMwMTYxMzU1WhgPMjEyNDA5MzAxNzEzNTVaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbXgtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAI25WbHf +qmz3amzhuAXMOIrt/UZpWk24hjRYVmuY7ePaz01cDJ7K/hrEzhXDBBitF+KKxPXD +XXJ/KCP/joQL4ZaPiYPdeNQcO09wK31HMWsaJeHVHIuoWmKneK/ojpMtirjrdqo+ +3Hy7NMMhnkIy8KUzXTQX8hVFnDVfbjEhWppBFOtGlS8A8I85LALrj3Mvg8BPBl0Z +JZvPhTzQAu5u5wHaJVgVKflLl1wrpNvVyHx5ryar2bSPwLtu8HEFp1TbgiZ5VD/7 +AUCfa2+fh5BRRVl3fF+7DqKtPXR5a9bJsyiieyhQa43ZDPpLQgurSjO4tBH6Mfhc +x6cJ0OLOWK2ZhuGZGF4pGwH2srGrVzzecFyd2YTV8yuCTpo/lIIW74T+aV/udJXr +AwjjOoToYifg3U2ipNCcy+lEit/LE0BVmNNGhggIioitfgOIqhJD12/9f+jewcnI +pN510DngrY2+jRHYtqbS3rVpG6K5zsE1NPYDoJM6AA1iub/sVZzNU/TIt0TtuvY7 +izkg+o6AtUzlgqGaMM2emH+nkMyYWqteWHLeLEYRQ79laaQRKpuZV+cP5sCHSAYr +Fwww1SqtR5ad+OexNp0Hqmrl1LMucmWXSjbmEYfPYCkPV/hEWI/am/FX7VlkSNGy +S7+k0eCBawj/fQj2xlADIQnfa/qf+17e7bypAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFNtXwWHyvClTO1X0qH9DGxcwsgQtMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAjID7uJb3fQzOgjUA24WoBMd784H64geM +IoYIzm0e7f4ZkI/NZoKAA1UPF+kW49A7Y7Un6YHf1WH9Y8EH3zwaDmak3Lzbw9Q6 ++FY9BkynZ3LbGYdo1tdK/7dvRw/+gjMzN70vPmHQINvAt5r+33jwXicsvQtqLTP3 +vYLfR028B94eH73xOUKMoqp0cDNlHoAY413CCYlXUxdPnfQzhaF8EySwa2Ws9HOh +/oDeU4ztaPfnMIH0jx6tf584HzSgW7h0wXLOodtNlI74+wr/42ltFQxRWZd34FaK +R0OOJRBESuWeZUiCYucVn6Vw9jhJ5qVPcGPlSWim15UanTPh0+Ysqb4o5NO8GfWH +wAmLO4MA4BJw1zJSi/7RblwuksdHpQ/sWK+OoGxwhp48KHDTpOJLJ0aJaXQDNF+B +X/9Vl0zo05IQz+0C0L6RgBFiGnJtYW30Um9aHjbpz15Ov0tpajaL3iB7VlSKi/DS +e3CsjQqkh0sUpnU+T31eJiDiHZ5z+Oq4VJtI+gK9dcVP4LjSvlMUcl+scQifXr+X +GlJVyCphLVRtfHUv411ezQGmJFIJRMyfo+SKYWyDAvSrPxs6F6qDr54ShRuWBhrJ +qLJw1abcDEJFlTFsNcWghF4H0iTMg46UAr+RMJBB0uC9ZqY207wT63px7vgDXSCs +Pz5G0lqE904= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMnRxsKLYscJV8Qv5pWbL7swCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTgxNjAxWhgPMjEyMTA1MTkxOTE2MDFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgc2EtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEjFOCZgTNVKxLKhUxffiDEvTLFhrmIqdO +dKqVdgDoELEzIHWDdC+19aDPitbCYtBVHl65ITu/9pn6mMUl5hhUNtfZuc6A+Iw1 +sBe0v0qI3y9Q9HdQYrGgeHDh8M5P7E2ho0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBS5L7/8M0TzoBZk39Ps7BkfTB4yJTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwI43O0NtWKTgnVv9z0LO5UMZYgSve7GvGTwqktZYCMObE +rUI4QerXM9D6JwLy09mqAjEAypfkdLyVWtaElVDUyHFkihAS1I1oUxaaDrynLNQK +Ou/Ay+ns+J+GyvyDUjBpVVW1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +zDY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAK5EYG3iHserxMqgg+0EFjgwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyMzE2WhgPMjA2MTA1MjQyMTIzMTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +s1L6TtB84LGraLHVC+rGPhLBW2P0oN/91Rq3AnYwqDOuTom7agANwEjvLq7dSRG/ +sIfZsSV/ABTgArZ5sCmLjHFZAo8Kd45yA9byx20RcYtAG8IZl+q1Cri+s0XefzyO +U6mlfXZkVe6lzjlfXBkrlE/+5ifVbJK4dqOS1t9cWIpgKqv5fbE6Qbq4LVT+5/WM +Vd2BOljuBMGMzdZubqFKFq4mzTuIYfnBm7SmHlZfTdfBYPP1ScNuhpjuzw4n3NCR +EdU6dQv04Q6th4r7eiOCwbWI9LkmVbvBe3ylhH63lApC7MiiPYLlB13xBubVHVhV +q1NHoNTi+zA3MN9HWicRxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSuxoqm0/wjNiZLvqv+JlQwsDvTPDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAFfTK/j5kv90uIbM8VaFdVbr/6weKTwehafT0pAk1bfLVX+7 +uf8oHgYiyKTTl0DFQicXejghXTeyzwoEkWSR8c6XkhD5vYG3oESqmt/RGvvoxz11 +rHHy7yHYu7RIUc3VQG60c4qxXv/1mWySGwVwJrnuyNT9KZXPevu3jVaWOVHEILaK +HvzQ2YEcWBPmde/zEseO2QeeGF8FL45Q1d66wqIP4nNUd2pCjeTS5SpB0MMx7yi9 +ki1OH1pv8tOuIdimtZ7wkdB8+JSZoaJ81b8sRrydRwJyvB88rftuI3YB4WwGuONT +ZezUPsmaoK69B0RChB0ofDpAaviF9V3xOWvVZfo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +1axqeKpI0F5SBUPsaVNYY2uNFg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQCREfzzVyDTMcNME+gWnTCTANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQyMzNaGA8yMDYxMDUyNDIxNDIzM1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL +1MT6br3L/4Pq87DPXtcjlXN3cnbNk2YqRAZHJayStTz8VtsFcGPJOpk14geRVeVk +e9uKFHRbcyr/RM4owrJTj5X4qcEuATYZbo6ou/rW2kYzuWFZpFp7lqm0vasV4Z9F +fChlhwkNks0UbM3G+psCSMNSoF19ERunj7w2c4E62LwujkeYLvKGNepjnaH10TJL +2krpERd+ZQ4jIpObtRcMH++bTrvklc+ei8W9lqrVOJL+89v2piN3Ecdd389uphst +qQdb1BBVXbhUrtuGHgVf7zKqN1SkCoktoWxVuOprVWhSvr7akaWeq0UmlvbEsujU +vADqxGMcJFyCzxx3CkJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFFk8UJmlhoxFT3PP12PvhvazHjT4MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAfFtr2lGoWVXmWAsIo2NYre7kzL8Xb9Tx7desKxCCz5HOOvIr +8JMB1YK6A7IOvQsLJQ/f1UnKRh3X3mJZjKIywfrMSh0FiDf+rjcEzXxw2dGtUem4 +A+WMvIA3jwxnJ90OQj5rQ8bg3iPtE6eojzo9vWQGw/Vu48Dtw1DJo9210Lq/6hze +hPhNkFh8fMXNT7Q1Wz/TJqJElyAQGNOXhyGpHKeb0jHMMhsy5UNoW5hLeMS5ffao +TBFWEJ1gVfxIU9QRxSh+62m46JIg+dwDlWv8Aww14KgepspRbMqDuaM2cinoejv6 +t3dyOyHHrsOyv3ffZUKtQhQbQr+sUcL89lARsg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQSDZkikDRk3Yz12jYvk5noDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGFwLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjUwMjAxMDAxMDIxWhgPMjA2NTAyMDEwMTEwMjFaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgYXAtZWFzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKF7hTKGtJjPxPd5 +3reviptmh3QhN9tmcff4AIzU53BowyCGPgLJvw0JRSm4rgfDZAQ2rqQPbnWP9UBW +dZED3axk2bLWL6fddURHR5ckdb/Lv0NuNG/vdrG7l2V1jBasZNqAEVtASovdEHPX +Razz3DTedOSSKUttdJZtL4XbVLUMkB3YaT8a1UagQnuKO1pRiy9lvRUdP6hlhFW9 +qGduF/ZsTpFfxHp8VhOpAnMhwd4qHik8GLXdB9ajeKko84MU3tQn52GS4zvXWwY9 +dxeC0QjDcK2dG8m/43e01yDAonaVJPI3R849aNtiBA4U+8au0CNva5n6WNI5LQIO +XZmp5D8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUg6d1N4oJ +YOE1eAtYJexRSiFCpXEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAOOBaSlMo+jhgz0ADaFuvFyaI+EVDB9GFT1TLpAmEK0gfNDI/RlY/n7hSKHJj0 +1ZeGfJ+9THD78QBuK7VyYlUkngYDdvEpg45ZEVKWGUtUmrhkikSsa5N8vioE3xg9 +MeSfgP98ykXVQH1kLSTUhLku8aLkwRdK96kSIKHRMUy41A9lK20X9Rc1dSsFJ/qe +TUFGCxwurKwR6geC+cw8aNjXHZlHZGNJPS+gRTizN1WYjEtJkjd5kQeNPgyppX26 +kRB9Vh54WJNIrs8PB9JsX9exu8f8m3hg5FFZONH5iBg0gdEMvbePP7JsjZD5BbjU +5C26Juxnw77pZbi0m7m3cmxL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAIJLTMpzGNxqHZ4t+c1MlCIwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBhcC1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIxMzAzM1oYDzIwNjEwNTI1MjIzMDMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtdHut0ZhJ9Nn2 +MpVafFcwHdoEzx06okmmhjJsNy4l9QYVeh0UUoek0SufRNMRF4d5ibzpgZol0Y92 +/qKWNe0jNxhEj6sXyHsHPeYtNBPuDMzThfbvsLK8z7pBP7vVyGPGuppqW/6m4ZBB +lcc9fsf7xpZ689iSgoyjiT6J5wlVgmCx8hFYc/uvcRtfd8jAHvheug7QJ3zZmIye +V4htOW+fRVWnBjf40Q+7uTv790UAqs0Zboj4Yil+hER0ibG62y1g71XcCyvcVpto +2/XW7Y9NCgMNqQ7fGN3wR1gjtSYPd7DO32LTzYhutyvfbpAZjsAHnoObmoljcgXI +QjfBcCFpAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJI3aWLg +CS5xqU5WYVaeT5s8lpO0MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAUwATpJOcGVOs3hZAgJwznWOoTzOVJKfrqBum7lvkVH1vBwxBl9CahaKj3ZOt +YYp2qJzhDUWludL164DL4ZjS6eRedLRviyy5cRy0581l1MxPWTThs27z+lCC14RL +PJZNVYYdl7Jy9Q5NsQ0RBINUKYlRY6OqGDySWyuMPgno2GPbE8aynMdKP+f6G/uE +YHOf08gFDqTsbyfa70ztgVEJaRooVf5JJq4UQtpDvVswW2reT96qi6tXPKHN5qp3 +3wI0I1Mp4ePmiBKku2dwYzPfrJK/pQlvu0Gu5lKOQ65QdotwLAAoaFqrf9za1yYs +INUkHLWIxDds+4OHNYcerGp5Dw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQc48r2iRBCPPCj3oRSgZxujAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC03IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTI0MDkxMjE1NTQ0MFoYDzIxMjQwOTEyMTY1NDQwWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC03IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEVSBo1+i/T9w0C7C1qdnl +DfUXpFa+x4QYZvwLtt6m9L96k5irB7Wlw5168uTBW/ssRbv067PBnQEdZfI3iLKK +xWSpDFZN11tRneyDXag/fj1MCzBQ25WG+BitQdbzzuYuo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTiqTOsp/NisMSxMzARIGIKFcol4TAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAJlFgjECsVieUHNKE2Cmbj1wujERebHM +YNqCdnO//DeQ6Rh4SJkNaWB9LRmrmsdvIwIwf2iyQNetoO2JWNt7gzdRjO+7dF2+ +/LdagQCVp4R2N1Q6xzttRqZEK0lyAd0t7wlX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAIO6ldra1KZvNWJ0TA1ihXEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjE0NTA1WhgPMjEyMTA1MjEyMjQ1MDVa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +sDN52Si9pFSyZ1ruh3xAN0nVqEs960o2IK5CPu/ZfshFmzAwnx/MM8EHt/jMeZtj +SM58LADAsNDL01ELpFZATjgZQ6xNAyXRXE7RiTRUvNkK7O3o2qAGbLnJq/UqF7Sw +LRnB8V6hYOv+2EjVnohtGCn9SUFGZtYDjWXsLd4ML4Zpxv0a5LK7oEC7AHzbUR7R +jsjkrXqSv7GE7bvhSOhMkmgxgj1F3J0b0jdQdtyyj109aO0ATUmIvf+Bzadg5AI2 +A9UA+TUcGeebhpHu8AP1Hf56XIlzPpaQv3ZJ4vzoLaVNUC7XKzAl1dlvCl7Klg/C +84qmbD/tjZ6GHtzpLKgg7kQEV7mRoXq8X4wDX2AFPPQl2fv+Kbe+JODqm5ZjGegm +uskABBi8IFv1hYx9jEulZPxC6uD/09W2+niFm3pirnlWS83BwVDTUBzF+CooUIMT +jhWkIIZGDDgMJTzouBHfoSJtS1KpUZi99m2WyVs21MNKHeWAbs+zmI6TO5iiMC+T +uB8spaOiHFO1573Fmeer4sy3YA6qVoqVl6jjTQqOdy3frAMbCkwH22/crV8YA+08 +hLeHXrMK+6XUvU+EtHAM3VzcrLbuYJUI2XJbzTj5g0Eb8I8JWsHvWHR5K7Z7gceR +78AzxQmoGEfV6KABNWKsgoCQnfb1BidDJIe3BsI0A6UCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUABp0MlB14MSHgAcuNSOhs3MOlUcwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCv4CIOBSQi/QR9NxdRgVAG/pAh +tFJhV7OWb/wqwsNKFDtg6tTxwaahdCfWpGWId15OUe7G9LoPiKiwM9C92n0ZeHRz +4ewbrQVo7Eu1JI1wf0rnZJISL72hVYKmlvaWaacHhWxvsbKLrB7vt6Cknxa+S993 +Kf8i2Psw8j5886gaxhiUtzMTBwoDWak8ZaK7m3Y6C6hXQk08+3pnIornVSFJ9dlS +PAqt5UPwWmrEfF+0uIDORlT+cvrAwgSp7nUF1q8iasledycZ/BxFgQqzNwnkBDwQ +Z/aM52ArGsTzfMhkZRz9HIEhz1/0mJw8gZtDVQroD8778h8zsx2SrIz7eWQ6uWsD +QEeSWXpcheiUtEfzkDImjr2DLbwbA23c9LoexUD10nwohhoiQQg77LmvBVxeu7WU +E63JqaYUlOLOzEmNJp85zekIgR8UTkO7Gc+5BD7P4noYscI7pPOL5rP7YLg15ZFi +ega+G53NTckRXz4metsd8XFWloDjZJJq4FfD60VuxgXzoMNT9wpFTNSH42PR2s9L +I1vcl3w8yNccs9se2utM2nLsItZ3J0m/+QSRiw9hbrTYTcM9sXki0DtH2kyIOwYf +lOrGJDiYOIrXSQK36H0gQ+8omlrUTvUj4msvkXuQjlfgx6sgp2duOAfnGxE7uHnc +UhnJzzoe6M+LfGHkVQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQSAG6j2WHtWUUuLGJTPb1nTAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2MzgyNloYDzIxMjEwNTIwMTczODI2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2eqwU4FOzW8RV1W381Bd +olhDOrqoMqzWli21oDUt7y8OnXM/lmAuOS6sr8Nt61BLVbONdbr+jgCYw75KabrK +ZGg3siqvMOgabIKkKuXO14wtrGyGDt7dnKXg5ERGYOZlo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBS1Acp2WYxOcblv5ikZ3ZIbRCCW+zAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAJL84J08PBprxmsAKPTotBuVI3MyW1r8 +xQ0i8lgCQUf8GcmYjQ0jI4oZyv+TuYJAcwIxAP9Xpzq0Docxb+4N1qVhpiOfWt1O +FnemFiy9m1l+wv6p3riQMPV7mBVpklmijkIv3Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRALZLcqCVIJ25maDPE3sbPCIwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjEzOTM5WhgPMjA2MTA1MjEyMjM5Mzla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +ypKc+6FfGx6Gl6fQ78WYS29QoKgQiur58oxR3zltWeg5fqh9Z85K5S3UbRSTqWWu +Xcfnkz0/FS07qHX+nWAGU27JiQb4YYqhjZNOAq8q0+ptFHJ6V7lyOqXBq5xOzO8f ++0DlbJSsy7GEtJp7d7QCM3M5KVY9dENVZUKeJwa8PC5StvwPx4jcLeZRJC2rAVDG +SW7NAInbATvr9ssSh03JqjXb+HDyywiqoQ7EVLtmtXWimX+0b3/2vhqcH5jgcKC9 +IGFydrjPbv4kwMrKnm6XlPZ9L0/3FMzanXPGd64LQVy51SI4d5Xymn0Mw2kMX8s6 +Nf05OsWcDzJ1n6/Q1qHSxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRmaIc8eNwGP7i6P7AJrNQuK6OpFzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIBeHfGwz3S2zwIUIpqEEI5/sMySDeS+3nJR+woWAHeO0C8i +BJdDh+kzzkP0JkWpr/4NWz84/IdYo1lqASd1Kopz9aT1+iROXaWr43CtbzjXb7/X +Zv7eZZFC8/lS5SROq42pPWl4ekbR0w8XGQElmHYcWS41LBfKeHCUwv83ATF0XQ6I +4t+9YSqZHzj4vvedrvcRInzmwWJaal9s7Z6GuwTGmnMsN3LkhZ+/GD6oW3pU/Pyh +EtWqffjsLhfcdCs3gG8x9BbkcJPH5aPAVkPn4wc8wuXg6xxb9YGsQuY930GWTYRf +schbgjsuqznW4HHakq4WNhs1UdTSTKkRdZz7FUQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIRAM2zAbhyckaqRim63b+Tib8wDQYJKoZIhvcNAQELBQAw +gZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv +QW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzEx +EDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA0OTQ1WhgPMjA2MTA1MTgyMTQ5 +NDVaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl +cywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV +BAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4 +IEcxMRAwDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA1ybjQMH1MkbvfKsWJaCTXeCSN1SG5UYid+Twe+TjuSqaXWonyp4WRR5z +tlkqq+L2MWUeQQAX3S17ivo/t84mpZ3Rla0cx39SJtP3BiA2BwfUKRjhPwOjmk7j +3zrcJjV5k1vSeLNOfFFSlwyDiVyLAE61lO6onBx+cRjelu0egMGq6WyFVidTdCmT +Q9Zw3W6LTrnPvPmEyjHy2yCHzH3E50KSd/5k4MliV4QTujnxYexI2eR8F8YQC4m3 +DYjXt/MicbqA366SOoJA50JbgpuVv62+LSBu56FpzY12wubmDZsdn4lsfYKiWxUy +uc83a2fRXsJZ1d3whxrl20VFtLFHFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRC0ytKmDYbfz0Bz0Psd4lRQV3aNTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggEBAGv8qZu4uaeoF6zsbumauz6ea6tdcWt+hGFuwGrb +tRbI85ucAmVSX06x59DJClsb4MPhL1XmqO3RxVMIVVfRwRHWOsZQPnXm8OYQ2sny +rYuFln1COOz1U/KflZjgJmxbn8x4lYiTPZRLarG0V/OsCmnLkQLPtEl/spMu8Un7 +r3K8SkbWN80gg17Q8EV5mnFwycUx9xsTAaFItuG0en9bGsMgMmy+ZsDmTRbL+lcX +Fq8r4LT4QjrFz0shrzCwuuM4GmcYtBSxlacl+HxYEtAs5k10tmzRf6OYlY33tGf6 +1tkYvKryxDPF/EDgGp/LiBwx6ixYMBfISoYASt4V/ylAlHA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtTCCAjqgAwIBAgIRAK9BSZU6nIe6jqfODmuVctYwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjIxMzA5WhgPMjEyMTA1MjEyMzEzMDlaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgY2EtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEUkEERcgxneT5H+P+fERcbGmf +bVx+M7rNWtgWUr6w+OBENebQA9ozTkeSg4c4M+qdYSObFqjxITdYxT1z/nHz1gyx +OKAhLjWu+nkbRefqy3RwXaWT680uUaAP6ccnkZOMo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSN6fxlg0s5Wny08uRBYZcQ3TUoyzAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaQAwZgIxAORaz+MBVoFBTmZ93j2G2vYTwA6T5hWzBWrx +CrI54pKn5g6At56DBrkjrwZF5T1enAIxAJe/LZ9xpDkAdxDgGJFN8gZYLRWc0NRy +Rb4hihy5vj9L+w9uKc9VfEBIFuhT7Z3ljg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQB/57HSuaqUkLaasdjxUdPjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE3NDAzNFoYDzIwNjEwNTE5MTg0MDM0WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbkaoVsUS76o +TgLFmcnaB8cswBk1M3Bf4IVRcwWT3a1HeJSnaJUqWHCJ+u3ip/zGVOYl0gN1MgBb +MuQRIJiB95zGVcIa6HZtx00VezDTr3jgGWRHmRjNVCCHGmxOZWvJjsIE1xavT/1j +QYV/ph4EZEIZ/qPq7e3rHohJaHDe23Z7QM9kbyqp2hANG2JtU/iUhCxqgqUHNozV +Zd0l5K6KnltZQoBhhekKgyiHqdTrH8fWajYl5seD71bs0Axowb+Oh0rwmrws3Db2 +Dh+oc2PwREnjHeca9/1C6J2vhY+V0LGaJmnnIuOANrslx2+bgMlyhf9j0Bv8AwSi +dSWsobOhNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQb7vJT +VciLN72yJGhaRKLn6Krn2TAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAAxEj8N9GslReAQnNOBpGl8SLgCMTejQ6AW/bapQvzxrZrfVOZOYwp/5oV0f +9S1jcGysDM+DrmfUJNzWxq2Y586R94WtpH4UpJDGqZp+FuOVJL313te4609kopzO +lDdmd+8z61+0Au93wB1rMiEfnIMkOEyt7D2eTFJfJRKNmnPrd8RjimRDlFgcLWJA +3E8wca67Lz/G0eAeLhRHIXv429y8RRXDtKNNz0wA2RwURWIxyPjn1fHjA9SPDkeW +E1Bq7gZj+tBnrqz+ra3yjZ2blss6Ds3/uRY6NYqseFTZWmQWT7FolZEnT9vMUitW +I0VynUbShVpGf6946e0vgaaKw20= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQGyUVTaVjYJvWhroVEiHPpDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTkwNDA2WhgPMjA2MTA1MTkyMDA0MDZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhyXpJ0t4nigRDZ +EwNtFOem1rM1k8k5XmziHKDvDk831p7QsX9ZOxl/BT59Pu/P+6W6SvasIyKls1sW +FJIjFF+6xRQcpoE5L5evMgN/JXahpKGeQJPOX9UEXVW5B8yi+/dyUitFT7YK5LZA +MqWBN/LtHVPa8UmE88RCDLiKkqiv229tmwZtWT7nlMTTCqiAHMFcryZHx0pf9VPh +x/iPV8p2gBJnuPwcz7z1kRKNmJ8/cWaY+9w4q7AYlAMaq/rzEqDaN2XXevdpsYAK +TMMj2kji4x1oZO50+VPNfBl5ZgJc92qz1ocF95SAwMfOUsP8AIRZkf0CILJYlgzk +/6u6qZECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm5jfcS9o ++LwL517HpB6hG+PmpBswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAcQ6lsqxi63MtpGk9XK8mCxGRLCad51+MF6gcNz6i6PAqhPOoKCoFqdj4cEQTF +F8dCfa3pvfJhxV6RIh+t5FCk/y6bWT8Ls/fYKVo6FhHj57bcemWsw/Z0XnROdVfK +Yqbc7zvjCPmwPHEqYBhjU34NcY4UF9yPmlLOL8uO1JKXa3CAR0htIoW4Pbmo6sA4 +6P0co/clW+3zzsQ92yUCjYmRNeSbdXbPfz3K/RtFfZ8jMtriRGuO7KNxp8MqrUho +HK8O0mlSUxGXBZMNicfo7qY8FD21GIPH9w5fp5oiAl7lqFzt3E3sCLD3IiVJmxbf +fUwpGd1XZBBSdIxysRLM6j48 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrTCCAjOgAwIBAgIQU+PAILXGkpoTcpF200VD/jAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMTQ1MTFaGA8yMTIxMDUyNTIyNDUxMVowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBhcC1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAT3tFKE8Kw1sGQAvNLlLhd8OcGhlc7MiW/s +NXm3pOiCT4vZpawKvHBzD76Kcv+ZZzHRxQEmG1/muDzZGlKR32h8AAj+NNO2Wy3d +CKTtYMiVF6Z2zjtuSkZQdjuQbe4eQ7qjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFAiSQOp16Vv0Ohpvqcbd2j5RmhYNMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNoADBlAjBVsi+5Ape0kOhMt/WFkANkslD4qXA5uqhrfAtH29Xzz2NV +tR7akiA771OaIGB/6xsCMQCZt2egCtbX7J0WkuZ2KivTh66jecJr5DHvAP4X2xtS +F/5pS+AUhcKTEGjI9jDH3ew= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQT5mGlavQzFHsB7hV6Mmy6TAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTAxNVoYDzIxMjEwNTI0MjE1MDE1WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEcm4BBBjYK7clwm0HJRWS +flt3iYwoJbIXiXn9c1y3E+Vb7bmuyKhS4eO8mwO4GefUcXObRfoHY2TZLhMJLVBQ +7MN2xDc0RtZNj07BbGD3VAIFRTDX0mH9UNYd0JQM3t/Oo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRrd5ITedfAwrGo4FA9UaDaGFK3rjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPBNqmVv1IIA3EZyQ6XuVf4gj79/DMO8 +bkicNS1EcBpUqbSuU4Zwt2BYc8c/t7KVOQIxAOHoWkoKZPiKyCxfMtJpCZySUG+n +sXgB/LOyWE5BJcXUfm+T1ckeNoWeUUMOLmnJjg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJcDeinvdNrDQBeJ8+t38WQwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY0OTE2WhgPMjA2MjA1MjUxNzQ5MTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +k8DBNkr9tMoIM0NHoFiO7cQfSX0cOMhEuk/CHt0fFx95IBytx7GHCnNzpM27O5z6 +x6iRhfNnx+B6CrGyCzOjxvPizneY+h+9zfvNz9jj7L1I2uYMuiNyOKR6FkHR46CT +1CiArfVLLPaTqgD/rQjS0GL2sLHS/0dmYipzynnZcs613XT0rAWdYDYgxDq7r/Yi +Xge5AkWQFkMUq3nOYDLCyGGfQqWKkwv6lZUHLCDKf+Y0Uvsrj8YGCI1O8mF0qPCQ +lmlfaDvbuBu1AV+aabmkvyFj3b8KRIlNLEtQ4N8KGYR2Jdb82S4YUGIOAt4wuuFt +1B7AUDLk3V/u+HTWiwfoLQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSNpcjz6ArWBtAA+Gz6kyyZxrrgdDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGJEd7UgOzHYIcQRSF7nSYyjLROyalaIV9AX4WXW/Cqlul1c +MblP5etDZm7A/thliZIWAuyqv2bNicmS3xKvNy6/QYi1YgxZyy/qwJ3NdFl067W0 +t8nGo29B+EVK94IPjzFHWShuoktIgp+dmpijB7wkTIk8SmIoe9yuY4+hzgqk+bo4 +ms2SOXSN1DoQ75Xv+YmztbnZM8MuWhL1T7hA4AMorzTQLJ9Pof8SpSdMHeDsHp0R +01jogNFkwy25nw7cL62nufSuH2fPYGWXyNDg+y42wKsKWYXLRgUQuDVEJ2OmTFMB +T0Vf7VuNijfIA9hkN2d3K53m/9z5WjGPSdOjGhg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQRiwspKyrO0xoxDgSkqLZczANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI0MjE1OTAwWhgPMjA2MTA1MjQyMjU5MDBaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL53Jk3GsKiu+4bx +jDfsevWbwPCNJ3H08Zp7GWhvI3Tgi39opfHYv2ku2BKFjK8N2L6RvNPSR8yplv5j +Y0tK0U+XVNl8o0ibhqRDhbTuh6KL8CFINWYzAajuxFS+CF0U6c1Q3tXLBdALxA7l +FlXJ71QrP06W31kRe7kvgrvO7qWU3/OzUf9qYw4LSiR1/VkvvRCTqcVNw09clw/M +Jbw6FSgweN65M9j7zPbjGAXSHkXyxH1Erin2fa+B9PE4ZDgX9cp2C1DHewYJQL/g +SepwwcudVNRN1ibKH7kpMrgPnaNIVNx5sXVsTjk6q2ZqYw3SVHegltJpLy/cZReP +mlivF2kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUmTcQd6o1 +CuS65MjBrMwQ9JJjmBwwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAKSDSIzl956wVddPThf2VAzI8syw9ngSwsEHZvxVGHBvu5gg618rDyguVCYX9L +4Kw/xJrk6S3qxOS2ZDyBcOpsrBskgahDFIunzoRP3a18ARQVq55LVgfwSDQiunch +Bd05cnFGLoiLkR5rrkgYaP2ftn3gRBRaf0y0S3JXZ2XB3sMZxGxavYq9mfiEcwB0 +LMTMQ1NYzahIeG6Jm3LqRqR8HkzP/Ztq4dT2AtSLvFebbNMiWqeqT7OcYp94HTYT +zqrtaVdUg9bwyAUCDgy0GV9RHDIdNAOInU/4LEETovrtuBU7Z1q4tcHXvN6Hd1H8 +gMb0mCG5I393qW5hFsA/diFb +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAIgSQsm7XtddDiXpo3j09qYwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjQwOTEyMTU1NDM2WhgPMjEyNDA5MTIxNjU0MzZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTcgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +jLfyuFmUeGd3WSDotJVQ+XjY20Yt75KWDFjXNLNZ7+/97RshOjZ0M3dZ+CcKmIQz +37vjb80kk0p+jeuaLxprWjc5kLJjaFZNPA8mxM/UmARvMvBRrO4uRRRQvYFyXqi+ +Frsl46t/+nyarL09ICx/1reZIzsOsI9BcDM0CK1hqQSwrIjOK2mXuHVfrufXjLnR +wZA2rOonQJJAXgOo1RvD11xmOUUIglP2ljAZZskxL+zU8d5k9Ed4HkGsOY3ywbP0 +/E+Fd33Gli3EF01wAbIaZL0vuW2rW4oxjh8QW7O7Sfnsr9fdNU2Tye1jSCxle5e3 +2Sfq+0Iw5TiT0KYTmcpcKzvljd1wwHdiNKeo7ZCVIer/lGl62PoAb2NWeLepk0IO +IwTZ+Dq76hs9XZJbYxWhXpVcT31b9e3++jROMPz7Mzi/wMg+RBWXgjDuuj1Z16sF +MFfK6QEmD8SOKL3GAKn7PBQxZea+j6iF7G1nGz1/Wfvzz7I8DocnAmQZabKzEnpR +LTEU2LZ86pkeLkM3uI9jm/VWeV4xLv/dC7trDkTUSHpJ4J5/ItMecdAlhCEJ2qM2 +x46OUbGYsf9gvacLkqQoS/XFL8R4bxR01URlHU+98yfODBCehulmnKOz3dQOth2c +sF9v2spYG2gmY3jg7N8suvPbzYKWmL7pVCQtqVOZPkkCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUy2Eg468T4F9w7gg9AxeZBj+gjKYwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCGLMyGrhwhqoDRtJ6gGx1SkJd4 +uxG3R4VFKS23JhDJKoDISjcCrVQu5Z3xSj157HmDnc/TEHgW/t1y472QOagzN2RD +9xgFBGfU1sHxKX79WBSf93L3alM+oE1tS+drDlBPV4gzxvCc+zvUYQKWjz/W6q9f +bdrk16J2/yOyli3XpEqf1o4c9FVnAbi9c3zdugfARskwypo2LHDuo8z+u/Ab7j1y +NZ8HtLg/hwVjfSoPLD8guYSZHXYn6ed7AmOUUJjlauWUfCCvU/F+Dk51JMLlVVK+ +y9ZgHXa8YDsGsCmpdgfC3MLSEVkx87mxj1rkFLI03q9i6L2BOWQtgd6NmvYd7pJx +cqx6bmSh43CscGDEngIB50XWqXd4QlVKmPPgDnd55szfnNQzzG9q8I9KTEv+B/Ck +apl2XxVAEUdb26Wi7RH+9Wms6HQeU4i7cDuWpm+EfZZmdjwvA4bs3Ox07SkenHqu +Ti6RJf1PiHoE9SHiFUiBnd++YsNByjGqn4Vxla1MlVdkaUMlvvc3/9oky4KfcRdQ +AK7L9cQqf7g6G4Ne5PNvosG3KaS009xSN1AAalTqS9eqrDYR2yZSixmUYqP57Jm5 +5ERI+pZi2aKpl0ONC7vKIyH+gxjsRPct4DKoxZnt4/KJYhINOlQz8i/j0jfqftmg +G1bYpbRRbfWoGHjGJA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAPQAvihfjBg/JDbj6U64K98wDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYyODQxWhgPMjA2MTA1MjAxNzI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +vJ9lgyksCxkBlY40qOzI1TCj/Q0FVGuPL/Z1Mw2YN0l+41BDv0FHApjTUkIKOeIP +nwDwpXTa3NjYbk3cOZ/fpH2rYJ++Fte6PNDGPgKppVCUh6x3jiVZ1L7wOgnTdK1Q +Trw8440IDS5eLykRHvz8OmwvYDl0iIrt832V0QyOlHTGt6ZJ/aTQKl12Fy3QBLv7 +stClPzvHTrgWqVU6uidSYoDtzHbU7Vda7YH0wD9IUoMBf7Tu0rqcE4uH47s2XYkc +SdLEoOg/Ngs7Y9B1y1GCyj3Ux7hnyvCoRTw014QyNB7dTatFMDvYlrRDGG14KeiU +UL7Vo/+EejWI31eXNLw84wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQkgTWFsNg6wA3HbbihDQ4vpt1E2zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGz1Asiw7hn5WYUj8RpOCzpE0h/oBZcnxP8wulzZ5Xd0YxWO +0jYUcUk3tTQy1QvoY+Q5aCjg6vFv+oFBAxkib/SmZzp4xLisZIGlzpJQuAgRkwWA +6BVMgRS+AaOMQ6wKPgz1x4v6T0cIELZEPq3piGxvvqkcLZKdCaeC3wCS6sxuafzZ +4qA3zMwWuLOzRftgX2hQto7d/2YkRXga7jSvQl3id/EI+xrYoH6zIWgjdU1AUaNq +NGT7DIo47vVMfnd9HFZNhREsd4GJE83I+JhTqIxiKPNxrKgESzyADmNPt0gXDnHo +tbV1pMZz5HpJtjnP/qVZhEK5oB0tqlKPv9yx074= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuTCCAj6gAwIBAgIRAKp1Rn3aL/g/6oiHVIXtCq8wCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjQyMDMyMTdaGA8yMTIxMDUyNDIxMzIxN1owgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGTYWPILeBJXfcL3Dz4z +EWMUq78xB1HpjBwHoTURYfcMd5r96BTVG6yaUBWnAVCMeeD6yTG9a1eVGNhG14Hk +ZAEjgLiNB7RRbEG5JZ/XV7W/vODh09WCst2y9SLKsdgeAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUoE0qZHmDCDB+Bnm8GUa/evpfPwgwDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCnil5MMwhY3qoXv0xvcKZGxGPaBV15 +0CCssCKn0oVtdJQfJQ3Jrf3RSaEyijXIJsoCMQC35iJi4cWoNX3N/qfgnHohW52O +B5dg0DYMqy5cNZ40+UcAanRMyqNQ6P7fy3umGco= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQPXnDTPegvJrI98qz8WxrMjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxODIxNDAxMloYDzIxMjEwNTE4MjI0MDEyWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEI0sR7gwutK5AB46hM761 +gcLTGBIYlURSEoM1jcBwy56CL+3CJKZwLLyJ7qoOKfWbu5GsVLUTWS8MV6Nw33cx +2KQD2svb694wi+Px2f4n9+XHkEFQw8BbiodDD7RZA70fo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTQSioOvnVLEMXwNSDg+zgln/vAkjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAMwu1hqm5Bc98uE/E0B5iMYbBQ4kpMxO +tP8FTfz5UR37HUn26nXE0puj6S/Ffj4oJgIwXI7s2c26tFQeqzq6u3lrNJHp5jC9 +Uxlo/hEJOLoDj5jnpxo8dMAtCNoQPaHdfL0P +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQEM1pS+bWfBJeu/6j1yIIFzANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjMwOTE5MjIwMTM5WhgPMjEyMzA5MTkyMzAxMzlaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgY2Etd2VzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Pyp8p5z6HnlGB +daOj78gZ3ABufxnBFiu5NdFiGoMrS+eY//xxr2iKbnynJAzjmn5A6VKMNxtbuYIZ +WKAzDb/HrWlIYD2w7ZVBXpylfPhiz3jLNsl03WdPNnEruCcivhY2QMewEVtzjPU0 +ofdbZlO2KpF3biv1gjPuIuE7AUyQAbWnWTlrzETAVWLboJJRRqxASSkFUHNLXod7 +ow02FwlAhcnCp9gSe1SKRDrpvvEvYQBAFB7owfnoQzOGDdd87RGyYfyuW8aFI2Z0 +LHNvsA0dTafO4Rh986c72kDL7ijICQdr5OTgZR2OnuESLk1DSK4xYJ4fA6jb5dJ5 ++xsI6tCPykWCW98aO/pha35OsrVNifL/5cH5pdv/ecgQGdffJB+Vdj6f/ZMwR6s/ +Rm37cQ9l3tU8eu/qpzsFjLq1ZUzDaVDWgMW9t49+q/zjhdmbPOabZDao7nHXrVRw +rwPHWCmEY4OmH6ikEKQW3AChFjOdSg4me/J0Jr5l5jKggLPHWbNLRO8qTTK6N8qk +ui3aJDi+XQfsTPARXIw4UFErArNImTsoZVyqfX7I4shp0qZbEhP6kRAbfPljw5kW +Yat7ZlXqDanjsreqbLTaOU10P0rC0/4Ctv5cLSKCrzRLWtpXxhKa2wJTQ74G6fAZ +1oUA79qg3F8nyM+ZzDsfNI854+PNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLRWiDabEQZNkzEPUCr1ZVJV6xpwMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEATkVVzkkGBjEtLGDtERi+fSpIV0MxwAsA4PAeBBmb +myxo90jz6kWkKM1Wm4BkZM8/mq5VbxPef1kxHfb5CHksCL6SgG5KujfIvht+KT2a +MRJB+III3CbcTy0HtwCX5AlPIbXWydhQFoJTW/OkpecUWoyFM6SqYeYZx1itJpxl +sXshLjYOvw+QgvxRsDxqUfkcaC/N2yhu/30Zo2P8msJfAFry2UmA/TBrWOQKVQxl +Ee/yWgp4U/bC/GZnjWnWDTwkRFGQtI4wjxbVuX6V4FTLCT7kIoHBhG+zOSduJRn3 +Axej7gkEXEVc/PAnwp/kSJ/b0/JONLWdjGUFkyiMn1yJlhJ2sg39vepBN5r6yVYU +nJWoZAuupRpoIKfmC3/cZanXqYbYl4yxzX/PMB4kAACfdxGxLawjnnBjSzaWokXs +YVh2TjWpUMwLOi0RB2mtPUjHdDLKtjOTZ1zHZnR/wVp9BmVI1BXYnz5PAqU5XqeD +EmanyaAuFCeyol1EtbQhgtysThQ+vwYAXMm2iKzJxq0hik8wyG8X55FhnGEOGV3u +xxq7odd3/8BXkc3dGdBPQtH+k5glaQyPnAsLVAIUvyzTmy58saL+nJnQY4mmRrwV +1jJA7nnkaklI/L5fvfCg0W+TMinCOAGd+GQ4hK2SAsJLtcqiBgPf2wJHO8wiwUh9 +Luw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQGKVv+5VuzEZEBzJ+bVfx2zAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTc1MDU5WhgPMjEyMTA1MTkxODUwNTlaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMqdLJ0tZF/DGFZTKZDrGRJZID8ivC2I +JRCYTWweZKCKSCAzoiuGGHzJhr5RlLHQf/QgmFcgXsdmO2n3CggzhA4tOD9Ip7Lk +P05eHd2UPInyPCHRgmGjGb0Z+RdQ6zkitKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUC1yhRgVqU5bR8cGzOUCIxRpl4EYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMG0c/zLGECRPzGKJvYCkpFTCUvdP4J74YP0v/dPvKojL +t/BrR1Tg4xlfhaib7hPc7wIwFvgqHes20CubQnZmswbTKLUrgSUW4/lcKFpouFd2 +t2/ewfi/0VhkeUW+IiHhOMdU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOXxJuyXVkbfhZCkS/dOpfEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1OTEwWhgPMjEyMTA1MjUyMjU5MTBa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +xiP4RDYm4tIS12hGgn1csfO8onQDmK5SZDswUpl0HIKXOUVVWkHNlINkVxbdqpqH +FhbyZmNN6F/EWopotMDKe1B+NLrjNQf4zefv2vyKvPHJXhxoKmfyuTd5Wk8k1F7I +lNwLQzznB+ElhrLIDJl9Ro8t31YBBNFRGAGEnxyACFGcdkjlsa52UwfYrwreEg2l +gW5AzqHgjFfj9QRLydeU/n4bHm0F1adMsV7P3rVwilcUlqsENDwXnWyPEyv3sw6F +wNemLEs1129mB77fwvySb+lLNGsnzr8w4wdioZ74co+T9z2ca+eUiP+EQccVw1Is +D4Fh57IjPa6Wuc4mwiUYKkKY63+38aCfEWb0Qoi+zW+mE9nek6MOQ914cN12u5LX +dBoYopphRO5YmubSN4xcBy405nIdSdbrAVWwxXnVVyjqjknmNeqQsPZaxAhdoKhV +AqxNr8AUAdOAO6Sz3MslmcLlDXFihrEEOeUbpg/m1mSUUHGbu966ajTG1FuEHHwS +7WB52yxoJo/tHvt9nAWnh3uH5BHmS8zn6s6CGweWKbX5yICnZ1QFR1e4pogxX39v +XD6YcNOO+Vn+HY4nXmjgSYVC7l+eeP8eduMg1xJujzjrbmrXU+d+cBObgdTOAlpa +JFHaGwYw1osAwPCo9cZ2f04yitBfj9aPFia8ASKldakCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUqKS+ltlior0SyZKYAkJ/efv55towDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAdElvp8bW4B+Cv+1WSN87dg6TN +wGyIjJ14/QYURgyrZiYpUmZpj+/pJmprSWXu4KNyqHftmaidu7cdjL5nCAvAfnY5 +/6eDDbX4j8Gt9fb/6H9y0O0dn3mUPSEKG0crR+JRFAtPhn/2FNvst2P82yguWLv0 +pHjHVUVcq+HqDMtUIJsTPYjSh9Iy77Q6TOZKln9dyDOWJpCSkiUWQtMAKbCSlvzd +zTs/ahqpT+zLfGR1SR+T3snZHgQnbnemmz/XtlKl52NxccARwfcEEKaCRQyGq/pR +0PVZasyJS9JY4JfQs4YOdeOt4UMZ8BmW1+BQWGSkkb0QIRl8CszoKofucAlqdPcO +IT/ZaMVhI580LFGWiQIizWFskX6lqbCyHqJB3LDl8gJISB5vNTHOHpvpMOMs5PYt +cRl5Mrksx5MKMqG7y5R734nMlZxQIHjL5FOoOxTBp9KeWIL/Ib89T2QDaLw1SQ+w +ihqWBJ4ZdrIMWYpP3WqM+MXWk7WAem+xsFJdR+MDgOOuobVQTy5dGBlPks/6gpjm +rO9TjfQ36ppJ3b7LdKUPeRfnYmlR5RU4oyYJ//uLbClI443RZAgxaCXX/nyc12lr +eVLUMNF2abLX4/VF63m2/Z9ACgMRfqGshPssn1NN33OonrotQoj4S3N9ZrjvzKt8 +iHcaqd60QKpfiH2A3A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrTCCAjOgAwIBAgIQOsXM3iPpLzFtjuRCpLsrlDAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGFwLWVhc3QtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yNTAyMDEwMDEwMzFaGA8yMTI1MDIwMTAxMTAzMVowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBhcC1lYXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQ+7GUElCbaFRRjg6gT4QTBuontLCF4sLso +/sHZ60qQI8SVEsDTF7C3zebh+VlNtPhgF1Le61HW86KlDQYf49pVN2guXBrdf3qb +RQCwSHSKs4S2bMyRMZ6xjwoGOrEKFEKjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKc0lLMy1OaSxQ+rtD7tyY3bFlv5MA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNoADBlAjEA5Rhu3YN+PyIHg3NXSw1DbowJIHNZgnieTD4BPkZLy4rs +8le5S3JjQQGyh+bsJf7RAjBPUMBF4CJDLE5nmetgXLlOl3oXwd/gHTls7YDr+R18 +cTBI0+RV4Z6yT5p1OF9AcnA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQPaVGRuu86nh/ylZVCLB0MzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMDMxNloYDzIxMjEwNTI1MjMwMzE2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEexNURoB9KE93MEtEAlJG +obz4LS/pD2hc8Gczix1WhVvpJ8bN5zCDXaKdnDMCebetyRQsmQ2LYlfmCwpZwSDu +0zowB11Pt3I5Avu2EEcuKTlKIDMBeZ1WWuOd3Tf7MEAMo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBSaYbZPBvFLikSAjpa8mRJvyArMxzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOEJkuh3Zjb7Ih/zuNRd1RBqmIYcnyw0 +nwUZczKXry+9XebYj3VQxSRNadrarPWVqgIxAMg1dyGoDAYjY/L/9YElyMnvHltO +PwpJShmqHvCLc/mXMgjjYb/akK7yGthvW6j/uQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQChu3v5W1Doil3v6pgRIcVzANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MTgyMTM0MTVaGA8yMTIxMDUxODIyMzQxNVow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1 +FUGQ5tf3OwpDR6hGBxhUcrkwKZhaXP+1St1lSOQvjG8wXT3RkKzRGMvb7Ee0kzqI +mzKKe4ASIhtV3UUWdlNmP0EA3XKnif6N79MismTeGkDj75Yzp5A6tSvqByCgxIjK +JqpJrch3Dszoyn8+XhwDxMZtkUa5nQVdJgPzJ6ltsQ8E4SWLyLtTu0S63jJDkqYY +S7cQblk7y7fel+Vn+LS5dGTdRRhMvSzEnb6mkVBaVzRyVX90FNUED06e8q+gU8Ob +htvQlf9/kRzHwRAdls2YBhH40ZeyhpUC7vdtPwlmIyvW5CZ/QiG0yglixnL6xahL +pbmTuTSA/Oqz4UGQZv2WzHe1lD2gRHhtFX2poQZeNQX8wO9IcUhrH5XurW/G9Xwl +Sat9CMPERQn4KC3HSkat4ir2xaEUrjfg6c4XsGyh2Pk/LZ0gLKum0dyWYpWP4JmM +RQNjrInXPbMhzQObozCyFT7jYegS/3cppdyy+K1K7434wzQGLU1gYXDKFnXwkX8R +bRKgx2pHNbH5lUddjnNt75+e8m83ygSq/ZNBUz2Ur6W2s0pl6aBjwaDES4VfWYlI +jokcmrGvJNDfQWygb1k00eF2bzNeNCHwgWsuo3HSxVgc/WGsbcGrTlDKfz+g3ich +bXUeUidPhRiv5UQIVCLIHpHuin3bj9lQO/0t6p+tAQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBSFmMBgm5IsRv3hLrvDPIhcPweXYTAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAAa2EuozymOsQDJlEi7TqnyA2OhT +GXPfYqCyMJVkfrqNgcnsNpCAiNEiZbb+8sIPXnT8Ay8hrwJYEObJ5b7MHXpLuyft +z0Pu1oFLKnQxKjNxrIsCvaB4CRRdYjm1q7EqGhMGv76se9stOxkOqO9it31w/LoU +ENDk7GLsSqsV1OzYLhaH8t+MaNP6rZTSNuPrHwbV3CtBFl2TAZ7iKgKOhdFz1Hh9 +Pez0lG+oKi4mHZ7ajov6PD0W7njn5KqzCAkJR6OYmlNVPjir+c/vUtEs0j+owsMl +g7KE5g4ZpTRShyh5BjCFRK2tv0tkqafzNtxrKC5XNpEkqqVTCnLcKG+OplIEadtr +C7UWf4HyhCiR+xIyxFyR05p3uY/QQU/5uza7GlK0J+U1sBUytx7BZ+Fo8KQfPPqV +CqDCaYUksoJcnJE/KeoksyqNQys7sDGJhkd0NeUGDrFLKHSLhIwAMbEWnqGxvhli +E7sP2E5rI/I9Y9zTbLIiI8pfeZlFF8DBdoP/Hzg8pqsiE/yiXSFTKByDwKzGwNqz +F0VoFdIZcIbLdDbzlQitgGpJtvEL7HseB0WH7B2PMMD8KPJlYvPveO3/6OLzCsav ++CAkvk47NQViKMsUTKOA0JDCW+u981YRozxa3K081snhSiSe83zIPBz1ikldXxO9 +6YYLNPRrj3mi9T/f +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMkvdFnVDb0mWWFiXqnKH68wCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkxMzI0WhgPMjEyMTA1MTkyMDEzMjRaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEy86DB+9th/0A5VcWqMSWDxIUblWTt/R0 +ao6Z2l3vf2YDF2wt1A2NIOGpfQ5+WAOJO/IQmnV9LhYo+kacB8sOnXdQa6biZZkR +IyouUfikVQAKWEJnh1Cuo5YMM4E2sUt5o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQ8u3OnecANmG8OoT7KLWDuFzZwBTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwQ817qkb7mWJFnieRAN+m9W3E0FLVKaV3zC5aYJUk2fcZ +TaUx3oLp3jPLGvY5+wgeAjEA6wAicAki4ZiDfxvAIuYiIe1OS/7H5RA++R8BH6qG +iRzUBM/FItFpnkus7u/eTkvo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQS/+Ryfgb/IOVEa1pWoe8oTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNjA2MjE1NDQyWhgPMjEyMjA2MDYyMjU0NDJaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDsX6fhdUWBQpYTdseBD/P3s96Dtw2Iw +OrXKNToCnmX5nMkUGdRn9qKNiz1pw3EPzaPxShbYwQ7LYP09ENK/JN4QQjxMihxC +jLFxS85nhBQQQGRCWikDAe38mD8fSvREQKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUIh1xZiseQYFjPYKJmGbruAgRH+AwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMFudS4zLy+UUGrtgNLtRMcu/DZ9BUzV4NdHxo0bkG44O +thnjl4+wTKI6VbyAbj2rkgIxAOHps8NMITU5DpyiMnKTxV8ubb/WGHrLl0BjB8Lw +ETVJk5DNuZvsIIcm7ykk6iL4Tw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQDcEmNIAVrDpUw5cH5ynutDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA3MDA0MDIzWhgPMjEyMjA1MDcwMTQwMjNaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvADk8t +Fl9bFlU5sajLPPDSOUpPAkKs6iPlz+27o1GJC88THcOvf3x0nVAcu9WYe9Qaas+4 +j4a0vv51agqyODRD/SNi2HnqW7DbtLPAm6KBHe4twl28ItB/JD5g7u1oPAHFoXMS +cH1CZEAs5RtlZGzJhcBXLFsHNv/7+SCLyZ7+2XFh9OrtgU4wMzkHoRNndhfwV5bu +17bPTwuH+VxH37zXf1mQ/KjhuJos0C9dL0FpjYBAuyZTAWhZKs8dpSe4DI544z4w +gkwUB4bC2nA1TBzsywEAHyNuZ/xRjNpWvx0ToWAA2iFJqC3VO3iKcnBplMvaUuMt +jwzVSNBnKcoabXCZL2XDLt4YTZR8FSwz05IvsmwcPB7uNTBXq3T9sjejW8QQK3vT +tzyfLq4jKmQE7PoS6cqYm+hEPm2hDaC/WP9bp3FdEJxZlPH26fq1b7BWYWhQ9pBA +Nv9zTnzdR1xohTyOJBUFQ81ybEzabqXqVXUIANqIOaNcTB09/sLJ7+zuMhp3mwBu +LtjfJv8PLuT1r63bU3seROhKA98b5KfzjvbvPSg3vws78JQyoYGbqNyDfyjVjg3U +v//AdVuPie6PNtdrW3upZY4Qti5IjP9e3kimaJ+KAtTgMRG56W0WxD3SP7+YGGbG +KhntDOkKsN39hLpn9UOafTIqFu7kIaueEy/NAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHAems86dTwdZbLe8AaPy3kfIUVoMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOBHpp0ICx81kmeoBcZTrMdJs2gnhcd85 +FoSCjXx9H5XE5rmN/lQcxxOgj8hr3uPuLdLHu+i6THAyzjrl2NA1FWiqpfeECGmy +0jm7iZsYORgGQYp/VKnDrwnKNSqlZvOuRr0kfUexwFlr34Y4VmupvEOK/RdGsd3S ++3hiemcHse9ST/sJLHx962AWMkN86UHPscJEe4+eT3f2Wyzg6La8ARwdWZSNS+WH +ZfybrncMmuiXuUdHv9XspPsqhKgtHhcYeXOGUtrwQPLe3+VJZ0LVxhlTWr9951GZ +GfmWwTV/9VsyKVaCFIXeQ6L+gjcKyEzYF8wpMtQlSc7FFqwgC4bKxvMBSaRy88Nr +lV2+tJD/fr8zGUeBK44Emon0HKDBWGX+/Hq1ZIv0Da0S+j6LbA4fusWxtGfuGha+ +luhHgVInCpALIOamiBEdGhILkoTtx7JrYppt3/Raqg9gUNCOOYlCvGhqX7DXeEfL +DGabooiY2FNWot6h04JE9nqGj5QqT8D6t/TL1nzxhRPzbcSDIHUd/b5R+a0bAA+7 +YTU6JqzEVCWKEIEynYmqikgLMGB/OzWsgyEL6822QW6hJAQ78XpbNeCzrICF4+GC +7KShLnwuWoWpAb26268lvOEvCTFM47VC6jNQl97md+2SA9Ma81C9wflid2M83Wle +cuLMVcQZceE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQAhAteLRCvizAElaWORFU2zANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE3MDkxNloYDzIwNjEwNTIwMTgwOTE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+qg7JAcOVKjh +N83SACnBFZPyB63EusfDr/0V9ZdL8lKcmZX9sv/CqoBo3N0EvBqHQqUUX6JvFb7F +XrMUZ740kr28gSRALfXTFgNODjXeDsCtEkKRTkac/UM8xXHn+hR7UFRPHS3e0GzI +iLiwQWDkr0Op74W8aM0CfaVKvh2bp4BI1jJbdDnQ9OKXpOxNHGUf0ZGb7TkNPkgI +b2CBAc8J5o3H9lfw4uiyvl6Fz5JoP+A+zPELAioYBXDrbE7wJeqQDJrETWqR9VEK +BXURCkVnHeaJy123MpAX2ozf4pqk0V0LOEOZRS29I+USF5DcWr7QIXR/w2I8ws1Q +7ys+qbE+kQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQFJ16n +1EcCMOIhoZs/F9sR+Jy++zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAOc5nXbT3XTDEZsxX2iD15YrQvmL5m13B3ImZWpx/pqmObsgx3/dg75rF2nQ +qS+Vl+f/HLh516pj2BPP/yWCq12TRYigGav8UH0qdT3CAClYy2o+zAzUJHm84oiB +ud+6pFVGkbqpsY+QMpJUbZWu52KViBpJMYsUEy+9cnPSFRVuRAHjYynSiLk2ZEjb +Wkdc4x0nOZR5tP0FgrX0Ve2KcjFwVQJVZLgOUqmFYQ/G0TIIGTNh9tcmR7yp+xJR +A2tbPV2Z6m9Yxx4E8lLEPNuoeouJ/GR4CkMEmF8cLwM310t174o3lKKUXJ4Vs2HO +Wj2uN6R9oI+jGLMSswTzCNV1vgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtjCCAj2gAwIBAgIQM+ObZzo0HZj7HpGdeMmx/zAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC01IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTI0MDUxNTIxNTA0NloYDzIxMjQwNTE1MjI1MDQ2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC01IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhSrJY/MXuQyTqK1dnLK6 +uWUx/KxsGCMCBXKthi0spP90CjfOYYxDcGD7zgUtk+LCEK2vneuewAPhlUgqXzaZ +PYDzk2WUznIPiIBvVo32U4vUnV/vSWqzhSKevsOakiPso0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRw/PJZ4fwnZo25vVSB80KtyKWqmTAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDZwAwZAIwLNcaZNOvCLTumHlJydm+9lB6bcxnaLmb +esoToveXQABKl84kGNI1gaDKOvvLsPbWAjBIqfDMb83RXw7q2C501W5hzsbZ1ZQs +8+tffIuCrMMGWDLqoUksWJHiocLOfe9gwm4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj6gAwIBAgIRAOocLeZWjYkG/EbHmscuy8gwCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjEyMTUwMDFaGA8yMTIxMDUyMTIyNTAwMVowgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABCEr3jq1KtRncnZfK5cq +btY0nW6ZG3FMbh7XwBIR6Ca0f8llGZ4vJEC1pXgiM/4Dh045B9ZIzNrR54rYOIfa +2NcYZ7mk06DjIQML64hbAxbQzOAuNzLPx268MrlL2uW2XaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUln75pChychwN4RfHl+tOinMrfVowDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMGiyPINRU1mwZ4Crw01vpuPvxZxb2IOr +yX3RNlOIu4We1H+5dQk5tIvH8KGYFbWEpAIxAO9NZ6/j9osMhLgZ0yj0WVjb+uZx +YlZR9fyFisY/jNfX7QhSk+nrc3SFLRUNtpXrng== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAKiaRZatN8eiz9p0s0lu0rQwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDIzNVoYDzIwNjEwNTIxMjMwMjM1WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCygVMf +qB865IR9qYRBRFHn4eAqGJOCFx+UbraQZmjr/mnRqSkY+nhbM7Pn/DWOrRnxoh+w +q5F9ZxdZ5D5T1v6kljVwxyfFgHItyyyIL0YS7e2h7cRRscCM+75kMedAP7icb4YN +LfWBqfKHbHIOqvvQK8T6+Emu/QlG2B5LvuErrop9K0KinhITekpVIO4HCN61cuOe +CADBKF/5uUJHwS9pWw3uUbpGUwsLBuhJzCY/OpJlDqC8Y9aToi2Ivl5u3/Q/sKjr +6AZb9lx4q3J2z7tJDrm5MHYwV74elGSXoeoG8nODUqjgklIWAPrt6lQ3WJpO2kug +8RhCdSbWkcXHfX95AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FOIxhqTPkKVqKBZvMWtKewKWDvDBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEAqoItII89lOl4TKvg0I1EinxafZLXIheLcdGCxpjRxlZ9QMQUN3yb +y/8uFKBL0otbQgJEoGhxm4h0tp54g28M6TN1U0332dwkjYxUNwvzrMaV5Na55I2Z +1hq4GB3NMXW+PvdtsgVOZbEN+zOyOZ5MvJHEQVkT3YRnf6avsdntltcRzHJ16pJc +Y8rR7yWwPXh1lPaPkxddrCtwayyGxNbNmRybjR48uHRhwu7v2WuAMdChL8H8bp89 +TQLMrMHgSbZfee9hKhO4Zebelf1/cslRSrhkG0ESq6G5MUINj6lMg2g6F0F7Xz2v +ncD/vuRN5P+vT8th/oZ0Q2Gc68Pun0cn/g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAJYlnmkGRj4ju/2jBQsnXJYwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIzMDQ0NFoYDzIwNjEwNTIyMDAwNDQ0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74V3eigv+pCj5 +nqDBqplY0Jp16pTeNB06IKbzb4MOTvNde6QjsZxrE1xUmprT8LxQqN9tI3aDYEYk +b9v4F99WtQVgCv3Y34tYKX9NwWQgwS1vQwnIR8zOFBYqsAsHEkeJuSqAB12AYUSd +Zv2RVFjiFmYJho2X30IrSLQfS/IE3KV7fCyMMm154+/K1Z2IJlcissydEAwgsUHw +edrE6CxJVkkJ3EvIgG4ugK/suxd8eEMztaQYJwSdN8TdfT59LFuSPl7zmF3fIBdJ +//WexcQmGabaJ7Xnx+6o2HTfkP8Zzzzaq8fvjAcvA7gyFH5EP26G2ZqMG+0y4pTx +SPVTrQEXAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIWWuNEF +sUMOC82XlfJeqazzrkPDMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAgClmxcJaQTGpEZmjElL8G2Zc8lGc+ylGjiNlSIw8X25/bcLRptbDA90nuP+q +zXAMhEf0ccbdpwxG/P5a8JipmHgqQLHfpkvaXx+0CuP++3k+chAJ3Gk5XtY587jX ++MJfrPgjFt7vmMaKmynndf+NaIJAYczjhJj6xjPWmGrjM3MlTa9XesmelMwP3jep +bApIWAvCYVjGndbK9byyMq1nyj0TUzB8oJZQooaR3MMjHTmADuVBylWzkRMxbKPl +4Nlsk4Ef1JvIWBCzsMt+X17nuKfEatRfp3c9tbpGlAE/DSP0W2/Lnayxr4RpE9ds +ICF35uSis/7ZlsftODUe8wtpkQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQTcWg4evi5wHEIHLNrHCqQjANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC03IFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yNDA5MTIxNTU0MzFaGA8yMDY0MDkxMjE2NTQzMVow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCS +X21nYtSipOhpdF6QfLE4BTwbtrXCR3EBhOHB1MfwwjNh25uex3X/gHW/sUTgC/oe +Oboi+3I/HN6a5MneTcVVps8AL8rJGym0ShSIYza/3MyT+PtfqDNmYfmF8VRIhNSR +CoYW93F/BoZF7bFk4ljOrBSrRbfb1qmEtkTPNGBRnJ0jh05Fwq5a5XnkGcOGNDyH +kGt9ZaUm+cJmzAa1omHspCjgg6854CNs4k5ovT2vaZ/0yAogdSpkB9INLM0ZMqJ+ +QoKAyN6hdISjwEEZrl+0OKymc2jR+NpQcd3378bDIfg3iYvuvTp84kKpZ4gRaukm +mSUg/PQqmGdBlAivOiknAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFDsAilCg2DzFiZheGlKVb74AubP2MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEACUGKd2GVv87xnVnPajqbi7GkMd+XGwyG1P8Nkon9rfUgwgHR +dHDsO6jIKf3ZEzNcMgMyV5sXs2944WRhGkYxQ62wbkEaqtjNExmlmUiS3vvrGOfg +BmRhJvAOM5HbNMqmi5BC4GlhHWQKhRbStwhvbVYnARBVfR/Wz4Qb4fizaXbuNWAo +V0XBQc+67lto2eBQ84KQXTp60FdwSBzltbM7sZmp6dMNgnfPNUrdxurVboF+VsxS +3aUJnmEi6TJAGL2SXLermB6HiTgxtxOJUiefu4Ipv0JEg41OzbKWsJXTaSH0QDb1 +nSeMyoXqF3ixcAhQbw/7NrzMMlf3NmMaVlohVA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQS7vMpOTVq2Jw457NdZ2ffjAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMzA5MTkyMjExNDNaGA8yMTIzMDkxOTIzMTE0M1owgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBjYS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARdgGSs/F2lpWKqS1ZpcmatFED1JurmNbXG +Sqhv1A/geHrKCS15MPwjtnfZiujYKY4fNkCCUseoGDwkC4281nwkokvnfWR1/cXy +LxfACoXNxsI4b+37CezSUBl48/5p1/OjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFFhLokGBuJGwKJhZcYSYKyZIitJtMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA8aQQlzJRHbqFsRY4O3u/cN0T8dzjcqnYn4NV1w+jvhzt +QPJLB+ggGyQhoFR6G2UrAjEA0be8OP5MWXD8d01KKbo5Dpy6TwukF5qoJmkFJKS3 +bKfEMvFWxXoV06HNZFWdI80u +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAPvvd+MCcp8E36lHziv0xhMwDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIzMTEwNloYDzIxMjEwNTIyMDAxMTA2WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbvwekKIKGcV/s +lDU96a71ZdN2pTYkev1X2e2/ICb765fw/i1jP9MwCzs8/xHBEQBJSxdfO4hPeNx3 +ENi0zbM+TrMKliS1kFVe1trTTEaHYjF8BMK9yTY0VgSpWiGxGwg4tshezIA5lpu8 +sF6XMRxosCEVCxD/44CFqGZTzZaREIvvFPDTXKJ6yOYnuEkhH3OcoOajHN2GEMMQ +ShuyRFDQvYkqOC/Q5icqFbKg7eGwfl4PmimdV7gOVsxSlw2s/0EeeIILXtHx22z3 +8QBhX25Lrq2rMuaGcD3IOMBeBo2d//YuEtd9J+LGXL9AeOXHAwpvInywJKAtXTMq +Wsy3LjhuANFrzMlzjR2YdjkGVzeQVx3dKUzJ2//Qf7IXPSPaEGmcgbxuatxjnvfT +H85oeKr3udKnXm0Kh7CLXeqJB5ITsvxI+Qq2iXtYCc+goHNR01QJwtGDSzuIMj3K +f+YMrqBXZgYBwU2J/kCNTH31nfw96WTbOfNGwLwmVRDgguzFa+QzmQsJW4FTDMwc +7cIjwdElQQVA+Gqa67uWmyDKAnoTkudmgAP+OTBkhnmc6NJuZDcy6f/iWUdl0X0u +/tsfgXXR6ZovnHonM13ANiN7VmEVqFlEMa0VVmc09m+2FYjjlk8F9sC7Rc4wt214 +7u5YvCiCsFZwx44baP5viyRZgkJVpQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBQgCZCsc34nVTRbWsniXBPjnUTQ2DAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBAAQas3x1G6OpsIvQeMS9BbiHG3+kU9P/ba6Rrg+E +lUz8TmL04Bcd+I+R0IyMBww4NznT+K60cFdk+1iSmT8Q55bpqRekyhcdWda1Qu0r +JiTi7zz+3w2v66akofOnGevDpo/ilXGvCUJiLOBnHIF0izUqzvfczaMZGJT6xzKq +PcEVRyAN1IHHf5KnGzUlVFv9SGy47xJ9I1vTk24JU0LWkSLzMMoxiUudVmHSqJtN +u0h+n/x3Q6XguZi1/C1KOntH56ewRh8n5AF7c+9LJJSRM9wunb0Dzl7BEy21Xe9q +03xRYjf5wn8eDELB8FZPa1PrNKXIOLYM9egdctbKEcpSsse060+tkyBrl507+SJT +04lvJ4tcKjZFqxn+bUkDQvXYj0D3WK+iJ7a8kZJPRvz8BDHfIqancY8Tgw+69SUn +WqIb+HNZqFuRs16WFSzlMksqzXv6wcDSyI7aZOmCGGEcYW9NHk8EuOnOQ+1UMT9C +Qb1GJcipjRzry3M4KN/t5vN3hIetB+/PhmgTO4gKhBETTEyPC3HC1QbdVfRndB6e +U/NF2U/t8U2GvD26TTFLK4pScW7gyw4FQyXWs8g8FS8f+R2yWajhtS9++VDJQKom +fAUISoCH+PlPRJpu/nHd1Zrddeiiis53rBaLbXu2J1Q3VqjWOmtj0HjxJJxWnYmz +Pqj2 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +KDPtFEk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQVZ5Y/KqjR4XLou8MCD5pOjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyNTE2NTgzM1oYDzIxMjIwNTI1MTc1ODMzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo473OmpD5vkckdJajXg +brhmNFyoSa0WCY1njuZC2zMFp3zP6rX4I1r3imrYnJd9pFH/aSiV/r6L5ACE5RPx +4qdg5SQ7JJUaZc3DWsTOiOed7BCZSzM+KTYK/2QzDMApo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTmogc06+1knsej1ltKUOdWFvwgsjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAIs7TlLMbGTWNXpGiKf9DxaM07d/iDHe +F/Vv/wyWSTGdobxBL6iArQNVXz0Gr4dvPAIwd0rsoa6R0x5mtvhdRPtM37FYrbHJ +pbV+OMusQqcSLseunLBoCHenvJW0QOCQ8EDY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBTCCA+2gAwIBAgIRAO9dVdiLTEGO8kjUFExJmgowDQYJKoZIhvcNAQEMBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIyMTIwMjIwMjYwOFoYDzIxMjIxMjAyMjEyNjA4WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDkVHmJ +bUc8CNDGBcgPmXHSHj5dS1PDnnpk3doCu6pahyYXW8tqAOmOqsDuNz48exY7YVy4 +u9I9OPBeTYB9ZUKwxq+1ZNLsr1cwVz5DdOyDREVFOjlU4rvw0eTgzhP5yw/d+Ai/ ++WmPebZG0irwPKN2f60W/KJ45UNtR+30MT8ugfnPuSHWjjV+dqCOCp/mj8nOCckn +k8GoREwjuTFJMKInpQUC0BaVVX6LiIdgtoLY4wdx00EqNBuROoRTAvrked0jvm7J +UI39CSYxhNZJ9F6LdESZXjI4u2apfNQeSoy6WptxFHr+kh2yss1B2KT6lbwGjwWm +l9HODk9kbBNSy2NeewAms36q+p8wSLPavL28IRfK0UaBAiN1hr2a/2RDGCwOJmw6 +5erRC5IIX5kCStyXPEGhVPp18EvMuBd37eLIxjZBBO8AIDf4Ue8QmxSeZH0cT204 +3/Bd6XR6+Up9iMTxkHr1URcL1AR8Zd62lg/lbEfxePNMK9mQGxKP8eTMG5AjtW9G +TatEoRclgE0wZQalXHmKpBNshyYdGqQZhzL1MxCxWzfHNgZkTKIsdzxrjnP7RiBR +jdRH0YhXn6Y906QfLwMCaufwfQ5J8+nj/tu7nG138kSxsu6VUkhnQJhUcUsxuHD/ +NnBx0KGVEldtZiZf7ccgtRVp1lA0OrVtq3ZLMQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ2WC3p8rWeE2N0S4Om01KsNLpk/jAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAFFEVDt45Obr6Ax9E4RMgsKjj4QjMFB9 +wHev1jL7hezl/ULrHuWxjIusaIZEIcKfn+v2aWtqOq13P3ht7jV5KsV29CmFuCdQ +q3PWiAXVs+hnMskTOmGMDnptqd6/UuSIha8mlOKKAvnmRQJvfX9hIfb/b/mVyKWD +uvTTmcy3cOTJY5ZIWGyzuvmcqA0YNcb7rkJt/iaLq4RX3/ofq4y4w36hefbcvj++ +pXHOmXk3dAej3y6SMBOUcGMyCJcCluRPNYKDTLn+fitcPxPC3JG7fI5bxQ0D6Hpa +qbyGBQu96sfahQyMc+//H8EYlo4b0vPeS5RFFXJS/VBf0AyNT4vVc7H17Q6KjeNp +wEARqsIa7UalHx9MnxrQ/LSTTxiC8qmDkIFuQtw8iQMN0SoL5S0eCZNRD31awgaY +y1PvY8JMN549ugIUjOXnown/OxharLW1evWUraU5rArq3JfeFpPXl4K/u10T5SCL +iJRoxFilGPMFE3hvnmbi5rEy8wRUn7TpLb4I4s/CB/lT2qZTPqvQHwxKCnMm9BKF +NHb4rLL5dCvUi5NJ6fQ/exOoGdOVSfT7jqFeq2TtNunERSz9vpriweliB6iIe1Al +Thj8aEs1GqA764rLVGA+vUe18NhjJm9EemrdIzjSQFy/NdbN/DMaHqEzJogWloAI +izQWYnCS19TJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICvTCCAkOgAwIBAgIQCIY7E/bFvFN2lK9Kckb0dTAKBggqhkjOPQQDAzCBnjEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5BbWF6 +b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUxODIxMDUxMFoYDzIxMjEwNTE4MjIwNTEwWjCB +njELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5B +bWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMI0hzf1JCEOI +Eue4+DmcNnSs2i2UaJxHMrNGGfU7b42a7vwP53F7045ffHPBGP4jb9q02/bStZzd +VHqfcgqkSRI7beBKjD2mfz82hF/wJSITTgCLs+NRpS6zKMFOFHUNo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBS8uF/6hk5mPLH4qaWv9NVZaMmyTjAOBgNV +HQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAO7Pu9wzLyM0X7Q08uLIL+vL +qaxe3UFuzFTWjM16MLJHbzLf1i9IDFKz+Q4hXCSiJwIwClMBsqT49BPUxVsJnjGr +EbyEk6aOOVfY1p2yQL649zh3M4h8okLnwf+bYIb1YpeU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQY+JhwFEQTe36qyRlUlF8ozANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE5MjQxNloYDzIwNjEwNTE5MjAyNDE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIye77j6ev40 +8wRPyN2OdKFSUfI9jB20Or2RLO+RDoL43+USXdrze0Wv4HMRLqaen9BcmCfaKMp0 +E4SFo47bXK/O17r6G8eyq1sqnHE+v288mWtYH9lAlSamNFRF6YwA7zncmE/iKL8J +0vePHMHP/B6svw8LULZCk+nZk3tgxQn2+r0B4FOz+RmpkoVddfqqUPMbKUxhM2wf +fO7F6bJaUXDNMBPhCn/3ayKCjYr49ErmnpYV2ZVs1i34S+LFq39J7kyv6zAgbHv9 ++/MtRMoRB1CjpqW0jIOZkHBdYcd1o9p1zFn591Do1wPkmMsWdjIYj+6e7UXcHvOB +2+ScIRAcnwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGtq2W +YSyMMxpdQ3IZvcGE+nyZqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEgoP3ixJsKSD5FN8dQ01RNHERl/IFbA7TRXfwC+L1yFocKnQh4Mp/msPRSV ++OeHIvemPW/wtZDJzLTOFJ6eTolGekHK1GRTQ6ZqsWiU2fmiOP8ks4oSpI+tQ9Lw +VrfZqTiEcS5wEIqyfUAZZfKDo7W1xp+dQWzfczSBuZJZwI5iaha7+ILM0r8Ckden +TVTapc5pLSoO15v0ziRuQ2bT3V3nwu/U0MRK44z+VWOJdSiKxdnOYDs8hFNnKhfe +klbTZF7kW7WbiNYB43OaAQBJ6BALZsIskEaqfeZT8FD71uN928TcEQyBDXdZpRN+ +iGQZDGhht0r0URGMDSs9waJtTfA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRANtElQUxBpw5GPpq+Tqajs4wCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBteC1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjQwOTMwMTYxNDAwWhgPMjEyNDA5MzAxNzEzNTlaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgbXgtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEB5xlumRhIemjIWjxWQHqDYG7 +I24vWwUWZjcxcqpDHv6ThnJRsN4TCbWiSfjCOTeW9ZMQiMm6xlxug1nXkDGAyuHL +ze0PqtP0rUPi25wmp3F6L4vq96az41xtGa4wc/xSo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSEfpD78dOGnSCKk9cYr2mAc4j9cTAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxANNu+hmW2xOajUvFzdVV4fO0Slj6BRklQfjO +hSF/1NvfZK9pF/QDIyknmK5KS+bY+QIwZCxY71nDC4n4Jw1l0gF+krPg3gNS2KKF +FdrlaY1qdiyvyG6t6tp84q9XmGJ1rOhJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQEbIZbn8kcnd/sTnZkdoDkzANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC01IFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yNDA1MTUyMTUwMzdaGA8yMDY0MDUxNTIyNTAzN1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCK +c15oRFUw/MiZQ/qkfOlrcc/PC9TdxGjUqdZyQGqBWrFauIbsK7U0qTeTibt7t7cL +hBWmqb3eefU8e+JZwJ20/cFfWINEjp9xLKV5pzfcRH+BJF3Sa4iLeLSi8CEp5qvf +k70ADs2kye17q29G01NfCG9T2oMEEJQof1nKcfwjayjx7uyBPHtR0a2SC88QlSl9 +9a009S0pUoISV3Zu/U+B6vUlBnGuIt+EsEFH0r19w/VRSO5mg9ylxh0/X5HXeBK5 +UxpNpXI9rPNd/AMTrv7FTyWsqkeSRS2lyT/8wyatApcCdPLyJDx7wZLY8/wARz7p +zi/uEKlhrrSDhDq2I7JFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFAHKs1jyaNzThRo5XHN/dNJDtVNHMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAVAdR60a4czTpyu3JHj6oNVQUTt1D1jD/y1fZcc5a77fa2Qc6 +ZZEVBadpXAwkUQDbVRu/h6OrPhWKbQNLlTS1xzGuGeVbXSczvj37UB11WQfFN3M9 +Dpe5LTL0MCPO+elHzXrBhjhi9euCHXHDdvv4AZl7tfWuOrBdeBThXIehKniJmAjt +vq2mIHHThw2Wr+E65WerOVU+jepsG//1EkgrKfcGoS646jQXXKabW3cn0ymEV1/M +DhFbV05Jfvu969qcA3+TH1FaN/lAbwuSLFZ4HLFFjq7RVl/X//lyXl1q/coUdQXC +awfL88gOd/cYz0n5xm/S+gJUtOcz/dR6kV36NQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQXY/dmS+72lZPranO2JM9jjANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjEzNDUxWhgPMjEyMTA1MjUyMjM0NTFaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgYXAtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMyW9kBJjD/hx8e8 +b5E1sF42bp8TXsz1htSYE3Tl3T1Aq379DfEhB+xa/ASDZxt7/vwa81BkNo4M6HYq +okYIXeE7cu5SnSgjWXqcERhgPevtAwgmhdE3yREe8oz2DyOi2qKKZqah+1gpPaIQ +fK0uAqoeQlyHosye3KZZKkDHBatjBsQ5kf8lhuf7wVulEZVRHY2bP2X7N98PfbpL +QdH7mWXzDtJJ0LiwFwds47BrkgK1pkHx2p1mTo+HMkfX0P6Fq1atkVC2RHHtbB/X +iYyH7paaHBzviFrhr679zNqwXIOKlbf74w3mS11P76rFn9rS1BAH2Qm6eY5S/Fxe +HEKXm4kjPN63Zy0p3yE5EjPt54yPkvumOnT+RqDGJ2HCI9k8Ehcbve0ogfdRKNqQ +VHWYTy8V33ndQRHZlx/CuU1yN61TH4WSoMly1+q1ihTX9sApmlQ14B2pJi/9DnKW +cwECrPy1jAowC2UJ45RtC8UC05CbP9yrIy/7Noj8gQDiDOepm+6w1g6aNlWoiuQS +kyI6nzz1983GcnOHya73ga7otXo0Qfg9jPghlYiMomrgshlSLDHZG0Ib/3hb8cnR +1OcN9FpzNmVK2Ll1SmTMLrIhuCkyNYX9O/bOknbcf706XeESxGduSkHEjIw/k1+2 +Atteoq5dT6cwjnJ9hyhiueVlVkiDAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLUI+DD7RJs+0nRnjcwIVWzzYSsFMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAb1mcCHv4qMQetLGTBH9IxsB2YUUhr5dda0D2BcHr +UtDbfd0VQs4tux6h/6iKwHPx0Ew8fuuYj99WknG0ffgJfNc5/fMspxR/pc1jpdyU +5zMQ+B9wi0lOZPO9uH7/pr+d2odcNEy8zAwqdv/ihsTwLmGP54is9fVbsgzNW1cm +HKAVL2t/Ope+3QnRiRilKCN1lzhav4HHdLlN401TcWRWKbEuxF/FgxSO2Hmx86pj +e726lweCTMmnq/cTsPOVY0WMjs0or3eHDVlyLgVeV5ldyN+ptg3Oit60T05SRa58 +AJPTaVKIcGQ/gKkKZConpu7GDofT67P/ox0YNY57LRbhsx9r5UY4ROgz7WMQ1yoS +Y+19xizm+mBm2PyjMUbfwZUyCxsdKMwVdOq5/UmTmdms+TR8+m1uBHPOTQ2vKR0s +Pd/THSzPuu+d3dbzRyDSLQbHFFneG760CUlD/ZmzFlQjJ89/HmAmz8IyENq+Sjhx +Jgzy+FjVZb8aRUoYLlnffpUpej1n87Ynlr1GrvC4GsRpNpOHlwuf6WD4W0qUTsC/ +C9JO+fBzUj/aWlJzNcLEW6pte1SB+EdkR2sZvWH+F88TxemeDrV0jKJw5R89CDf8 +ZQNfkxJYjhns+YeV0moYjqQdc7tq4i04uggEQEtVzEhRLU5PE83nlh/K2NZZm8Kj +dIA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAPVSMfFitmM5PhmbaOFoGfUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMzQ1N1oYDzIwNjEwNTI1MjMzNDU3WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDu9H7TBeGoDzMr +dxN6H8COntJX4IR6dbyhnj5qMD4xl/IWvp50lt0VpmMd+z2PNZzx8RazeGC5IniV +5nrLg0AKWRQ2A/lGGXbUrGXCSe09brMQCxWBSIYe1WZZ1iU1IJ/6Bp4D2YEHpXrW +bPkOq5x3YPcsoitgm1Xh8ygz6vb7PsvJvPbvRMnkDg5IqEThapPjmKb8ZJWyEFEE +QRrkCIRueB1EqQtJw0fvP4PKDlCJAKBEs/y049FoOqYpT3pRy0WKqPhWve+hScMd +6obq8kxTFy1IHACjHc51nrGII5Bt76/MpTWhnJIJrCnq1/Uc3Qs8IVeb+sLaFC8K +DI69Sw6bAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE7PCopt +lyOgtXX0Y1lObBUxuKaCMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAFj+bX8gLmMNefr5jRJfHjrL3iuZCjf7YEZgn89pS4z8408mjj9z6Q5D1H7yS +jNETVV8QaJip1qyhh5gRzRaArgGAYvi2/r0zPsy+Tgf7v1KGL5Lh8NT8iCEGGXwF +g3Ir+Nl3e+9XUp0eyyzBIjHtjLBm6yy8rGk9p6OtFDQnKF5OxwbAgip42CD75r/q +p421maEDDvvRFR4D+99JZxgAYDBGqRRceUoe16qDzbMvlz0A9paCZFclxeftAxv6 +QlR5rItMz/XdzpBJUpYhdzM0gCzAzdQuVO5tjJxmXhkSMcDP+8Q+Uv6FA9k2VpUV +E/O5jgpqUJJ2Hc/5rs9VkAPXeA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQW0yuFCle3uj4vWiGU0SaGzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkzNTE2WhgPMjEyMTA1MTkyMDM1MTZaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYWYtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDPiKNZSaXs3Un/J/v+LTsFDANHpi7en +oL2qh0u0DoqNzEBTbBjvO23bLN3k599zh6CY3HKW0r2k1yaIdbWqt4upMCRCcUFi +I4iedAmubgzh56wJdoMZztjXZRwDthTkJKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUWbYkcrvVSnAWPR5PJhIzppcAnZIwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMCESGqpat93CjrSEjE7z+Hbvz0psZTHwqaxuiH64GKUm +mYynIiwpKHyBrzjKBmeDoQIxANGrjIo6/b8Jl6sdIZQI18V0pAyLfLiZjlHVOnhM +MOTVgr82ZuPoEHTX78MxeMnYlw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAIbsx8XOl0sgTNiCN4O+18QwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1NDU4WhgPMjA2MTA1MjUyMjU0NTha +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +tROxwXWCgn5R9gI/2Ivjzaxc0g95ysBjoJsnhPdJEHQb7w3y2kWrVWU3Y9fOitgb +CEsnEC3PrhRnzNVW0fPsK6kbvOeCmjvY30rdbxbc8h+bjXfGmIOgAkmoULEr6Hc7 +G1Q/+tvv4lEwIs7bEaf+abSZxRJbZ0MBxhbHn7UHHDiMZYvzK+SV1MGCxx7JVhrm +xWu3GC1zZCsGDhB9YqY9eR6PmjbqA5wy8vqbC57dZZa1QVtWIQn3JaRXn+faIzHx +nLMN5CEWihsdmHBXhnRboXprE/OS4MFv1UrQF/XM/h5RBeCywpHePpC+Oe1T3LNC +iP8KzRFrjC1MX/WXJnmOVQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBS33XbXAUMs1znyZo4B0+B3D68WFTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBADuadd2EmlpueY2VlrIIPC30QkoA1EOSoCmZgN6124apkoY1 +HiV4r+QNPljN4WP8gmcARnNkS7ZeR4fvWi8xPh5AxQCpiaBMw4gcbTMCuKDV68Pw +P2dZCTMspvR3CDfM35oXCufdtFnxyU6PAyINUqF/wyTHguO3owRFPz64+sk3r2pT +WHmJjG9E7V+KOh0s6REgD17Gqn6C5ijLchSrPUHB0wOIkeLJZndHxN/76h7+zhMt +fFeNxPWHY2MfpcaLjz4UREzZPSB2U9k+y3pW1omCIcl6MQU9itGx/LpQE+H3ZeX2 +M2bdYd5L+ow+bdbGtsVKOuN+R9Dm17YpswF+vyQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +Em4Hy3A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQFn6AJ+uxaPDpNVx7174CpjANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIxMjAyMjAxNDA4WhgPMjA2MjEyMDIyMTE0MDhaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgaWwtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2xGTSJ +fXorki/dkkTqdLyv4U1neeFYEyUCPN/HJ7ZloNwhj8RBrHYhZ4qtvUAvN+rs8fUm +L0wmaL69ye61S+CSfDzNwBDGwOzUm/cc1NEJOHCm8XA0unBNBvpJTjsFk2LQ+rz8 +oU0lVV4mjnfGektrTDeADonO1adJvUTYmF6v1wMnykSkp8AnW9EG/6nwcAJuAJ7d +BfaLThm6lfxPdsBNG81DLKi2me2TLQ4yl+vgRKJi2fJWwA77NaDqQuD5upRIcQwt +5noJt2kFFmeiro98ZMMRaDTHAHhJfWkwkw5f2QNIww7T4r85IwbQCgJVRo4m4ZTC +W/1eiEccU2407mECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +DNhVvGHzKXv0Yh6asK0apP9jJlUwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQCoEVTUY/rF9Zrlpb1Y1hptEguw0i2pCLakcmv3YNj6thsubbGeGx8Z +RjUA/gPKirpoae2HU1y64WEu7akwr6pdTRtXXjbe9NReT6OW/0xAwceSXCOiStqS +cMsWWTGg6BA3uHqad5clqITjDZr1baQ8X8en4SXRBxXyhJXbOkB60HOQeFR9CNeh +pJdrWLeNYXwU0Z59juqdVMGwvDAYdugWUhW2rhafVUXszfRA5c8Izc+E31kq90aY +LmxFXUHUfG0eQOmxmg+Z/nG7yLUdHIFA3id8MRh22hye3KvRdQ7ZVGFni0hG2vQQ +Q01AvD/rhzyjg0czzJKLK9U/RttwdMaV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +jGKc3k6UNxpg +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +0Ws= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQHK008RFz9XJfPKvg2ONLCjANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG14LWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjQwOTMwMTYxMzUwWhgPMjA2NDA5MzAxNzEzNTBaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbXgtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIbH+1jM +u1AF7AnEnUU64au5HHnWlQZ5PJCRXmiG4q8NGooNG5A6Qd8lIboWnH6DKAfl3AL+ +ihAsLl9biZ/A/wvoNtW/EizjeYXmiuuCyY/Vo/yv78TW7YNspLYbhc35Lt5V+Hi1 +jnKgNf5IAPG4yIXuIYT+9pD6uCcHuoyzsI8f45oUX3o9PJKaBFWccsgIao1cmZhe +9ButRjAPPtEXyHhLoBgObn5D4G/RWyqXZg0xYTUHHAq6a61UPW8oQ1eqc0aqH+o8 +ZCekjwvgiqTRbUpcWm12KviLgZT1J+9noB7ZX5uY9N5FbOsuG92kkFwgS7cEqEXs +AUCm/EdPZyj6dyUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +e8h4FODoLdxY0tZkQY5RRjCEQsUwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQA7iEZTjpddHEaTQR3jbR98g8zO5aXKO9gfDmeD1Yuov50rV22gpYgU +Pz2TtFT2ftDmtqKZevzXCS0sE2a1SxP8jTUGTnRlGdMmeKGKfERneit26AQ6A3VW +qTB5Sv11oqTs2bgrM0Zd//DRWboqRuctjTl+AWCmFqTLTxYHefaDW6zHkVL/2jcP +V8MniRMzHKtDKURpl1R9lG3/HvC54XP63pJpmMRwE4b6LUuuwr6AIKKRMCLFIrGh +8g2OMeOMnXawUxQubTLqqT8yMrZwBD2W6yJnHtECJsBVj1rd3EZ/Cq64JbolFk7/ +fVTZlb5aHfj8sie/qmmiP+T45facauKY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAMyaTlVLN0ndGp4ffwKAfoMwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBtZS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNTA3MDA0NDM3WhgPMjEyMjA1MDcwMTQ0MzdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE19nCV1nsI6CohSor13+B25cr +zg+IHdi9Y3L7ziQnHWI6yjBazvnKD+oC71aRRlR8b5YXsYGUQxWzPLHN7EGPcSGv +bzA9SLG1KQYCJaQ0m9Eg/iGrwKWOgylbhVw0bCxoo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS4KsknsJXM9+QPEkBdZxUPaLr11zAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJaRgrYIEfXQMZQQDxMTYS0azpyWSseQooXo +L3nYq4OHGBgYyQ9gVjvRYWU85PXbfgIwdi82DtANQFkCu+j+BU0JBY/uRKPEeYzo +JG92igKIcXPqCoxIJ7lJbbzmuf73gQu5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAJwCobx0Os8F7ihbJngxrR8wDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjAxNzE1MzNaGA8yMTIxMDUyMDE4MTUzM1owgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANukKwlm+ZaI +Y5MkWGbEVLApEyLmlrHLEg8PfiiEa9ts7jssQcin3bzEPdTqGr5jo91ONoZ3ccWq +xJgg1W3bLu5CAO2CqIOXTXHRyCO/u0Ch1FGgWB8xETPSi3UHt/Vn1ltdO6DYdbDU +mYgwzYrvLBdRCwxsb9o+BuYQHVFzUYonqk/y9ujz3gotzFq7r55UwDTA1ita3vb4 +eDKjIb4b1M4Wr81M23WHonpje+9qkkrAkdQcHrkgvSCV046xsq/6NctzwCUUNsgF +7Q1a8ut5qJEYpz5ta8vI1rqFqAMBqCbFjRYlmAoTTpFPOmzAVxV+YoqTrW5A16su +/2SXlMYfJ/n/ad/QfBNPPAAQMpyOr2RCL/YiL/PFZPs7NxYjnZHNWxMLSPgFyI+/ +t2klnn5jR76KJK2qimmaXedB90EtFsMRUU1e4NxH9gDuyrihKPJ3aVnZ35mSipvR +/1KB8t8gtFXp/VQaz2sg8+uxPMKB81O37fL4zz6Mg5K8+aq3ejBiyHucpFGnsnVB +3kQWeD36ONkybngmgWoyPceuSWm1hQ0Z7VRAQX+KlxxSaHmSaIk1XxZu9h9riQHx +fMuev6KXjRn/CjCoUTn+7eFrt0dT5GryQEIZP+nA0oq0LKxogigHNZlwAT4flrqb +JUfZJrqgoce5HjZSXl10APbtPjJi0fW9AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFEfV+LztI29OVDRm0tqClP3NrmEWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAvSNe+0wuk53KhWlRlRf2x/97H2Q76X3anzF0 +5fOSVm022ldALzXMzqOfdnoKIhAu2oVKiHHKs7mMas+T6TL+Mkphx0CYEVxFE3PG +061q3CqJU+wMm9W9xsB79oB2XG47r1fIEywZZ3GaRsatAbjcNOT8uBaATPQAfJFN +zjFe4XyN+rA4cFrYNvfHTeu5ftrYmvks7JlRaJgEGWsz+qXux7uvaEEVPqEumd2H +uYeaRNOZ2V23R009X5lbgBFx9tq5VDTnKhQiTQ2SeT0rc1W3Dz5ik6SbQQNP3nSR +0Ywy7r/sZ3fcDyfFiqnrVY4Ympfvb4YW2PZ6OsQJbzH6xjdnTG2HtzEU30ngxdp1 +WUEF4zt6rjJCp7QBUqXgdlHvJqYu6949qtWjEPiFN9uSsRV2i1YDjJqN52dLjAPn +AipJKo8x1PHTwUzuITqnB9BdP+5TlTl8biJfkEf/+08eWDTLlDHr2VrZLOLompTh +bS5OrhDmqA2Q+O+EWrTIhMflwwlCpR9QYM/Xwvlbad9H0FUHbJsCVNaru3wGOgWo +tt3dNSK9Lqnv/Ej9K9v6CRr36in4ylJKivhJ5B9E7ABHg7EpBJ1xi7O5eNDkNoJG ++pFyphJq3AkBR2U4ni2tUaTAtSW2tks7IaiDV+UMtqZyGabT5ISQfWLLtLHSWn2F +Tspdjbg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJZFh4s9aZGzKaTMLrSb4acwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjEyODQxWhgPMjA2MTA1MTgyMjI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgQmV0YSB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +17i2yoU6diep+WrqxIn2CrDEO2NdJVwWTSckx4WMZlLpkQDoymSmkNHjq9ADIApD +A31Cx+843apL7wub8QkFZD0Tk7/ThdHWJOzcAM3ov98QBPQfOC1W5zYIIRP2F+vQ +TRETHQnLcW3rLv0NMk5oQvIKpJoC9ett6aeVrzu+4cU4DZVWYlJUoC/ljWzCluau +8blfW0Vwin6OB7s0HCG5/wijQWJBU5SrP/KAIPeQi1GqG5efbqAXDr/ple0Ipwyo +Xjjl73LenGUgqpANlC9EAT4i7FkJcllLPeK3NcOHjuUG0AccLv1lGsHAxZLgjk/x +z9ZcnVV9UFWZiyJTKxeKPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRWyMuZUo4gxCR3Luf9/bd2AqZ7CjAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIqN2DlIKlvDFPO0QUZQVFbsi/tLdYM98/vvzBpttlTGVMyD +gJuQeHVz+MnhGIwoCGOlGU3OOUoIlLAut0+WG74qYczn43oA2gbMd7HoD7oL/IGg +njorBwJVcuuLv2G//SqM3nxGcLRtkRnQ+lvqPxMz9+0fKFUn6QcIDuF0QSfthLs2 +WSiGEPKO9c9RSXdRQ4pXA7c3hXng8P4A2ZmdciPne5Nu4I4qLDGZYRrRLRkNTrOi +TyS6r2HNGUfgF7eOSeKt3NWL+mNChcYj71/Vycf5edeczpUgfnWy9WbPrK1svKyl +aAs2xg+X6O8qB+Mnj2dNBzm+lZIS3sIlm+nO9sg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAPAlEk8VJPmEzVRRaWvTh2AwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI1MjI0MTU1WhgPMjEyMTA1MjUyMzQxNTVaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx5xjrup8II4HOJw15NTnS3H5yMrQGlbj +EDA5MMGnE9DmHp5dACIxmPXPMe/99nO7wNdl7G71OYPCgEvWm0FhdvVUeTb3LVnV +BnaXt32Ek7/oxGk1T+Df03C+W0vmuJ+wo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBTGXmqBWN/1tkSea4pNw0oHrjk2UDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAIqqZWCSrIkZ7zsv/FygtAusW6yvlL935YAWYPVXU30m +jkMFLM+/RJ9GMvnO8jHfCgIwB+whlkcItzE9CRQ6CsMo/d5cEHDUu/QW6jSIh9BR +OGh9pTYPVkUbBiKPA7lVVhre +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +nWS0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQKKqVZvk6NsLET+uYv5myCzAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMjEyMDIyMDMyMjBaGA8yMTIyMTIwMjIxMzIyMFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASYwfvj8BmvLAP6UkNQ4X4dXBB/ +webBO7swW+8HnFN2DAu+Cn/lpcDpu+dys1JmkVX435lrCH3oZjol0kCDIM1lF4Cv ++78yoY1Jr/YMat22E4iz4AZd9q0NToS7+ZA0r2yjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFO/8Py16qPr7J2GWpvxlTMB+op7XMA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAwk+rg788+u8JL6sdix7l57WTo8E/M+o3TO5x +uRuPdShrBFm4ArGR2PPs4zCQuKgqAjEAi0TA3PVqAxKpoz+Ps8/054p9WTgDfBFZ +i/lm2yTaPs0xjY6FNWoy7fsVw5oEKxOn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOY7gfcBZgR2tqfBzMbFQCUwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY1NDU5WhgPMjEyMjA1MjUxNzU0NTla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +lfxER43FuLRdL08bddF0YhbCP+XXKj1A/TFMXmd2My8XDei8rPXFYyyjMig9+xZw +uAsIxLwz8uiA26CKA8bCZKg5VG2kTeOJAfvBJaLv1CZefs3Z4Uf1Sjvm6MF2yqEj +GoORfyfL9HiZFTDuF/hcjWoKYCfMuG6M/wO8IbdICrX3n+BiYQJu/pFO660Mg3h/ +8YBBWYDbHoCiH/vkqqJugQ5BM3OI5nsElW51P1icEEqti4AZ7JmtSv9t7fIFBVyR +oaEyOgpp0sm193F/cDJQdssvjoOnaubsSYm1ep3awZAUyGN/X8MBrPY95d0hLhfH +Ehc5Icyg+hsosBljlAyksmt4hFQ9iBnWIz/ZTfGMck+6p3HVL9RDgvluez+rWv59 +8q7omUGsiPApy5PDdwI/Wt/KtC34/2sjslIJfvgifdAtkRPkhff1WEwER00ADrN9 +eGGInaCpJfb1Rq8cV2n00jxg7DcEd65VR3dmIRb0bL+jWK62ni/WdEyomAOMfmGj +aWf78S/4rasHllWJ+QwnaUYY3u6N8Cgio0/ep4i34FxMXqMV3V0/qXdfhyabi/LM +wCxNo1Dwt+s6OtPJbwO92JL+829QAxydfmaMTeHBsgMPkG7RwAekeuatKGHNsc2Z +x2Q4C2wVvOGAhcHwxfM8JfZs3nDSZJndtVVnFlUY0UECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUpnG7mWazy6k97/tb5iduRB3RXgQwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCDLqq1Wwa9Tkuv7vxBnIeVvvFF +ecTn+P+wJxl9Qa2ortzqTHZsBDyJO62d04AgBwiDXkJ9a+bthgG0H1J7Xee8xqv1 +xyX2yKj24ygHjspLotKP4eDMdDi5TYq+gdkbPmm9Q69B1+W6e049JVGXvWG8/7kU +igxeuCYwtCCdUPRLf6D8y+1XMGgVv3/DSOHWvTg3MJ1wJ3n3+eve3rjGdRYWZeJu +k21HLSZYzVrCtUsh2YAeLnUbSxVuT2Xr4JehYe9zW5HEQ8Je/OUfnCy9vzoN/ITw +osAH+EBJQey7RxEDqMwCaRefH0yeHFcnOll0OXg/urnQmwbEYzQ1uutJaBPsjU0J +Qf06sMxI7GiB5nPE+CnI2sM6A9AW9kvwexGXpNJiLxF8dvPQthpOKGcYu6BFvRmt +6ctfXd9b7JJoVqMWuf5cCY6ihpk1e9JTlAqu4Eb/7JNyGiGCR40iSLvV28un9wiE +plrdYxwcNYq851BEu3r3AyYWw/UW1AKJ5tM+/Gtok+AphMC9ywT66o/Kfu44mOWm +L3nSLSWEcgfUVgrikpnyGbUnGtgCmHiMlUtNVexcE7OtCIZoVAlCGKNu7tyuJf10 +Qlk8oIIzfSIlcbHpOYoN79FkLoDNc2er4Gd+7w1oPQmdAB0jBJnA6t0OUBPKdDdE +Ufff2jrbfbzECn1ELg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +fejbXelUSJSzDIdY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +7BP7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAN7Y9G9i4I+ZaslPobE7VL4wDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYzMzIzWhgPMjEyMTA1MjAxNzMzMjNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +4BEPCiIfiK66Q/qa8k+eqf1Q3qsa6Xuu/fPkpuStXVBShhtXd3eqrM0iT4Xxs420 +Va0vSB3oZ7l86P9zYfa60n6PzRxdYFckYX330aI7L/oFIdaodB/C9szvROI0oLG+ +6RwmIF2zcprH0cTby8MiM7G3v9ykpq27g4WhDC1if2j8giOQL3oHpUaByekZNIHF +dIllsI3RkXmR3xmmxoOxJM1B9MZi7e1CvuVtTGOnSGpNCQiqofehTGwxCN2wFSK8 +xysaWlw48G0VzZs7cbxoXMH9QbMpb4tpk0d+T8JfAPu6uWO9UwCLWWydf0CkmA/+ +D50/xd1t33X9P4FEaPSg5lYbHXzSLWn7oLbrN2UqMLaQrkoEBg/VGvzmfN0mbflw ++T87bJ/VEOVNlG+gepyCTf89qIQVWOjuYMox4sK0PjzZGsYEuYiq1+OUT3vk/e5K +ag1fCcq2Isy4/iwB2xcXrsQ6ljwdk1fc+EmOnjGKrhuOHJY3S+RFv4ToQBsVyYhC +XGaC3EkqIX0xaCpDimxYhFjWhpDXAjG/zJ+hRLDAMCMhl/LPGRk/D1kzSbPmdjpl +lEMK5695PeBvEBTQdBQdOiYgOU3vWU6tzwwHfiM2/wgvess/q0FDAHfJhppbgbb9 +3vgsIUcsvoC5o29JvMsUxsDRvsAfEmMSDGkJoA/X6GECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUgEWm1mZCbGD6ytbwk2UU1aLaOUUwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBb4+ABTGBGwxK1U/q4g8JDqTQM +1Wh8Oz8yAk4XtPJMAmCctxbd81cRnSnePWw/hxViLVtkZ/GsemvXfqAQyOn1coN7 +QeYSw+ZOlu0j2jEJVynmgsR7nIRqE7QkCyZAU+d2FTJUfmee+IiBiGyFGgxz9n7A +JhBZ/eahBbiuoOik/APW2JWLh0xp0W0GznfJ8lAlaQTyDa8iDXmVtbJg9P9qzkvl +FgPXQttzEOyooF8Pb2LCZO4kUz+1sbU7tHdr2YE+SXxt6D3SBv+Yf0FlvyWLiqVk +GDEOlPPTDSjAWgKnqST8UJ0RDcZK/v1ixs7ayqQJU0GUQm1I7LGTErWXHMnCuHKe +UKYuiSZwmTcJ06NgdhcCnGZgPq13ryMDqxPeltQc3n5eO7f1cL9ERYLDLOzm6A9P +oQ3MfcVOsbHgGHZWaPSeNrQRN9xefqBXH0ZPasgcH9WJdsLlEjVUXoultaHOKx3b +UCCb+d3EfqF6pRT488ippOL6bk7zNubwhRa/+y4wjZtwe3kAX78ACJVcjPobH9jZ +ErySads5zdQeaoee5wRKdp3TOfvuCe4bwLRdhOLCHWzEcXzY3g/6+ppLvNom8o+h +Bh5X26G6KSfr9tqhQ3O9IcbARjnuPbvtJnoPY0gz3EHHGPhy0RNW8i2gl3nUp0ah +PtjwbKW0hYAhIttT0Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQQRBQTs6Y3H1DDbpHGta3lzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDYxMTAwMTI0M1oYDzIxMjEwNjExMDExMjQzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEs0942Xj4m/gKA+WA6F5h +AHYuek9eGpzTRoLJddM4rEV1T3eSueytMVKOSlS3Ub9IhyQrH2D8EHsLYk9ktnGR +pATk0kCYTqFbB7onNo070lmMJmGT/Q7NgwC8cySChFxbo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ20iKBKiNkcbIZRu0y1uoF1yJTEzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwYv0wTSrpQTaPaarfLN8Xcqrqu3hzl07n +FrESIoRw6Cx77ZscFi2/MV6AFyjCV/TlAjEAhpwJ3tpzPXpThRML8DMJYZ3YgMh3 +CMuLqhPpla3cL0PhybrD27hJWl29C4el6aMO +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrDCCAjOgAwIBAgIQGcztRyV40pyMKbNeSN+vXTAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjEyMzE1NTZaGA8yMTIxMDUyMjAwMTU1NlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyB1cy1lYXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfDcv+GGRESD9wT+I5YIPRsD3L+/jsiIis +Tr7t9RSbFl+gYpO7ZbDXvNbV5UGOC5lMJo/SnqFRTC6vL06NF7qOHfig3XO8QnQz +6T5uhhrhnX2RSY3/10d2kTyHq3ZZg3+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFLDyD3PRyNXpvKHPYYxjHXWOgfPnMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNnADBkAjB20HQp6YL7CqYD82KaLGzgw305aUKw2aMrdkBR29J183jY +6Ocj9+Wcif9xnRMS+7oCMAvrt03rbh4SU9BohpRUcQ2Pjkh7RoY0jDR4Xq4qzjNr +5UFr3BXpFvACxXF51BksGQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQeKbS5zvtqDvRtwr5H48cAjAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTcxOTU1WhgPMjEyMTA1MjAxODE5NTVaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgbWUtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEKjgUaAPmUlRMEQdBC7BScAGosJ1zRV +LDd38qTBjzgmwBfQJ5ZfGIvyEK5unB09MB4e/3qqK5I/L6Qn5Px/n5g4dq0c7MQZ +u7G9GBYm90U3WRJBf7lQrPStXaRnS4A/O6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUNKcAbGEIn03/vkwd8g6jNyiRdD4wDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMHIeTrjenCSYuGC6txuBt/0ZwnM/ciO9kHGWVCoK8QLs +jGghb5/YSFGZbmQ6qpGlSAIwVOQgdFfTpEfe5i+Vs9frLJ4QKAfc27cTNYzRIM0I +E+AJgK4C4+DiyyMzOpiCfmvq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +WT1QcNdw7ntUJP/w +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQARky6+5PNFRkFVOp3Ob1CTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNTIzMTg0MTI4WhgPMjEyMjA1MjMxOTQxMjdaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNVGL5oF7cfIBxKyWd2PVK/S5yQfaJY3 +QFHWvEdt6951n9JhiiPrHzfVHsxZp1CBjILRMzjgRbYWmc8qRoLkgGE7htGdwudJ +Fa/WuKzO574Prv4iZXUnVGTboC7JdvKbh6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUgDeIIEKynwUbNXApdIPnmRWieZwwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMEOOJfucrST+FxuqJkMZyCM3gWGZaB+/w6+XUAJC6hFM +uSTY0F44/bERkA4XhH+YGAIxAIpJQBakCA1/mXjsTnQ+0El9ty+LODp8ibkn031c +8DKDS7pR9UK7ZYdR6zFg3ZCjQw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQJvkWUcYLbnxtuwnyjMmntDAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMjI2MTJaGA8yMTIxMDUyNTIzMjYxMlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBldS13ZXN0LTMgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARENn8uHCyjn1dFax4OeXxvbV861qsXFD9G +DshumTmFzWWHN/69WN/AOsxy9XN5S7Cgad4gQgeYYYgZ5taw+tFo/jQvCLY//uR5 +uihcLuLJ78opvRPvD9kbWZ6oXfBtFkWjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKiK3LpoF+gDnqPldGSwChBPCYciMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA+7qfvRlnvF1Aosyp9HzxxCbN7VKu+QXXPhLEBWa5oeWW +UOcifunf/IVLC4/FGCsLAjEAte1AYp+iJyOHDB8UYkhBE/1sxnFaTiEPbvQBU0wZ +SuwWVLhu2wWDuSW+K7tTuL8p +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAKeDpqX5WFCGNo94M4v69sUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMTgzM1oYDzIwNjEwNTI1MjMxODMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcKOTEMTfzvs4H +WtJR8gI7GXN6xesulWtZPv21oT+fLGwJ+9Bv8ADCGDDrDxfeH/HxJmzG9hgVAzVn +4g97Bn7q07tGZM5pVi96/aNp11velZT7spOJKfJDZTlGns6DPdHmx48whpdO+dOb +6+eR0VwCIv+Vl1fWXgoACXYCoKjhxJs+R+fwY//0JJ1YG8yjZ+ghLCJmvlkOJmE1 +TCPUyIENaEONd6T+FHGLVYRRxC2cPO65Jc4yQjsXvvQypoGgx7FwD5voNJnFMdyY +754JGPOOe/SZdepN7Tz7UEq8kn7NQSbhmCsgA/Hkjkchz96qN/YJ+H/okiQUTNB0 +eG9ogiVFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFjayw9Y +MjbxfF14XAhMM2VPl0PfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAAtmx6d9+9CWlMoU0JCirtp4dSS41bBfb9Oor6GQ8WIr2LdfZLL6uES/ubJPE +1Sh5Vu/Zon5/MbqLMVrfniv3UpQIof37jKXsjZJFE1JVD/qQfRzG8AlBkYgHNEiS +VtD4lFxERmaCkY1tjKB4Dbd5hfhdrDy29618ZjbSP7NwAfnwb96jobCmMKgxVGiH +UqsLSiEBZ33b2hI7PJ6iTJnYBWGuiDnsWzKRmheA4nxwbmcQSfjbrNwa93w3caL2 +v/4u54Kcasvcu3yFsUwJygt8z43jsGAemNZsS7GWESxVVlW93MJRn6M+MMakkl9L +tWaXdHZ+KUV7LhfYLb0ajvb40w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQJ5oxPEjefCsaESSwrxk68DANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNjA2MjExNzA1WhgPMjA2MjA2MDYyMjE3MDVaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTQt5eX +g+VP3BjO9VBkWJhE0GfLrU/QIk32I6WvrnejayTrlup9H1z4QWlXF7GNJrqScRMY +KhJHlcP05aPsx1lYco6pdFOf42ybXyWHHJdShj4A5glU81GTT+VrXGzHSarLmtua +eozkQgPpDsSlPt0RefyTyel7r3Cq+5K/4vyjCTcIqbfgaGwTU36ffjM1LaPCuE4O +nINMeD6YuImt2hU/mFl20FZ+IZQUIFZZU7pxGLqTRz/PWcH8tDDxnkYg7tNuXOeN +JbTpXrw7St50/E9ZQ0llGS+MxJD8jGRAa/oL4G/cwnV8P2OEPVVkgN9xDDQeieo0 +3xkzolkDkmeKOnUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +bwu8635iQGQMRanekesORM8Hkm4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAgN6LE9mUgjsj6xGCX1afYE69fnmCjjb0rC6eEe1mb/QZNcyw4XBIW +6+zTXo4mjZ4ffoxb//R0/+vdTE7IvaLgfAZgFsLKJCtYDDstXZj8ujQnGR9Pig3R +W+LpNacvOOSJSawNQq0Xrlcu55AU4buyD5VjcICnfF1dqBMnGTnh27m/scd/ZMx/ +kapHZ/fMoK2mAgSX/NvUKF3UkhT85vSSM2BTtET33DzCPDQTZQYxFBa4rFRmFi4c +BLlmIReiCGyh3eJhuUUuYAbK6wLaRyPsyEcIOLMQmZe1+gAFm1+1/q5Ke9ugBmjf +PbTWjsi/lfZ5CdVAhc5lmZj/l5aKqwaS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAKKPTYKln9L4NTx9dpZGUjowCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIxMjI1NTIxWhgPMjEyMTA1MjEyMzU1MjFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE/owTReDvaRqdmbtTzXbyRmEpKCETNj6O +hZMKH0F8oU9Tmn8RU7kQQj6xUKEyjLPrFBN7c+26TvrVO1KmJAvbc8bVliiJZMbc +C0yV5PtJTalvlMZA1NnciZuhxaxrzlK1o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBT4i5HaoHtrs7Mi8auLhMbKM1XevDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAK9A+8/lFdX4XJKgfP+ZLy5ySXC2E0Spoy12Gv2GdUEZ +p1G7c1KbWVlyb1d6subzkQIwKyH0Naf/3usWfftkmq8SzagicKz5cGcEUaULq4tO +GzA/AMpr63IDBAqkZbMDTCmH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQTgIvwTDuNWQo0Oe1sOPQEzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjEwNjM4WhgPMjEyMTA1MjQyMjA2MzhaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtbm9ydGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJuzXLU8q6WwSKXBvx8BbdIi3mPhb7Xo +rNJBfuMW1XRj5BcKH1ZoGaDGw+BIIwyBJg8qNmCK8kqIb4cH8/Hbo3Y+xBJyoXq/ +cuk8aPrxiNoRsKWwiDHCsVxaK9L7GhHHAqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUYgcsdU4fm5xtuqLNppkfTHM2QMYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMQDz/Rm89+QJOWJecYAmYcBWCcETASyoK1kbr4vw7Hsg +7Ew3LpLeq4IRmTyuiTMl0gMCMAa0QSjfAnxBKGhAnYxcNJSntUyyMpaXzur43ec0 +3D8npJghwC4DuICtKEkQiI5cSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +u3UBpZI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQenQbcP/Zbj9JxvZ+jXbRnTAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMTA1MjEyMjMzMjRaGA8yMTIxMDUyMTIzMzMyNFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATlBHiEM9LoEb1Hdnd5j2VpCDOU +5nGuFoBD8ROUCkFLFh5mHrHfPXwBc63heW9WrP3qnDEm+UZEUvW7ROvtWCTPZdLz +Z4XaqgAlSqeE2VfUyZOZzBSgUUJk7OlznXfkCMOjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFDT/ThjQZl42Nv/4Z/7JYaPNMly2MA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAnZWmSgpEbmq+oiCa13l5aGmxSlfp9h12Orvw +Dq/W5cENJz891QD0ufOsic5oGq1JAjEAp5kSJj0MxJBTHQze1Aa9gG4sjHBxXn98 +4MP1VGsQuhfndNHQb4V0Au7OWnOeiobq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAMgnyikWz46xY6yRgiYwZ3swDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NDkxMloYDzIwNjEwNTIwMTc0OTEyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi8JYOc9cYSgZH +gYPxLk6Xcc7HqzamvsnjYU98Dcb98y6iDqS46Ra2Ne02MITtU5MDL+qjxb8WGDZV +RUA9ZS69tkTO3gldW8QdiSh3J6hVNJQW81F0M7ZWgV0gB3n76WCmfT4IWos0AXHM +5v7M/M4tqVmCPViQnZb2kdVlM3/Xc9GInfSMCgNfwHPTXl+PXX+xCdNBePaP/A5C +5S0oK3HiXaKGQAy3K7VnaQaYdiv32XUatlM4K2WS4AMKt+2cw3hTCjlmqKRHvYFQ +veWCXAuc+U5PQDJ9SuxB1buFJZhT4VP3JagOuZbh5NWpIbOTxlAJOb5pGEDuJTKi +1gQQQVEFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNXm+N87 +OFxK9Af/bjSxDCiulGUzMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAkqIbkgZ45spvrgRQ6n9VKzDLvNg+WciLtmVrqyohwwJbj4pYvWwnKQCkVc7c +hUOSBmlSBa5REAPbH5o8bdt00FPRrD6BdXLXhaECKgjsHe1WW08nsequRKD8xVmc +8bEX6sw/utBeBV3mB+3Zv7ejYAbDFM4vnRsWtO+XqgReOgrl+cwdA6SNQT9oW3e5 +rSQ+VaXgJtl9NhkiIysq9BeYigxqS/A13pHQp0COMwS8nz+kBPHhJTsajHCDc8F4 +HfLi6cgs9G0gaRhT8FCH66OdGSqn196sE7Y3bPFFFs/3U+vxvmQgoZC6jegQXAg5 +Prxd+VNXtNI/azitTysQPumH7A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAO8bekN7rUReuNPG8pSTKtEwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMjM0N1oYDzIwNjEwNTIxMjMyMzQ3WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTTYds +Tray+Q9VA5j5jTh5TunHKFQzn68ZbOzdqaoi/Rq4ohfC0xdLrxCpfqn2TGDHN6Zi +2qGK1tWJZEd1H0trhzd9d1CtGK+3cjabUmz/TjSW/qBar7e9MA67/iJ74Gc+Ww43 +A0xPNIWcL4aLrHaLm7sHgAO2UCKsrBUpxErOAACERScVYwPAfu79xeFcX7DmcX+e +lIqY16pQAvK2RIzrekSYfLFxwFq2hnlgKHaVgZ3keKP+nmXcXmRSHQYUUr72oYNZ +HcNYl2+gxCc9ccPEHM7xncVEKmb5cWEWvVoaysgQ+osi5f5aQdzgC2X2g2daKbyA +XL/z5FM9GHpS5BJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FBDAiJ7Py9/A9etNa/ebOnx5l5MGMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEALMh/+81fFPdJV/RrJUeoUvFCGMp8iaANu97NpeJyKitNOv7RoeVP +WjivS0KcCqZaDBs+p6IZ0sLI5ZH098LDzzytcfZg0PsGqUAb8a0MiU/LfgDCI9Ee +jsOiwaFB8k0tfUJK32NPcIoQYApTMT2e26lPzYORSkfuntme2PTHUnuC7ikiQrZk +P+SZjWgRuMcp09JfRXyAYWIuix4Gy0eZ4rpRuaTK6mjAb1/LYoNK/iZ/gTeIqrNt +l70OWRsWW8jEmSyNTIubGK/gGGyfuZGSyqoRX6OKHESkP6SSulbIZHyJ5VZkgtXo +2XvyRyJ7w5pFyoofrL3Wv0UF8yt/GDszmg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAMDk/F+rrhdn42SfE+ghPC8wDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIyNTEyMloYDzIxMjEwNTIxMjM1MTIyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2twMALVg9vRVu +VNqsr6N8thmp3Dy8jEGTsm3GCQ+C5P2YcGlD/T/5icfWW84uF7Sx3ezcGlvsqFMf +Ukj9sQyqtz7qfFFugyy7pa/eH9f48kWFHLbQYm9GEgbYBIrWMp1cy3vyxuMCwQN4 +DCncqU+yNpy0CprQJEha3PzY+3yJOjDQtc3zr99lyECCFJTDUucxHzyQvX89eL74 +uh8la0lKH3v9wPpnEoftbrwmm5jHNFdzj7uXUHUJ41N7af7z7QUfghIRhlBDiKtx +5lYZemPCXajTc3ryDKUZC/b+B6ViXZmAeMdmQoPE0jwyEp/uaUcdp+FlUQwCfsBk +ayPFEApTWgPiku2isjdeTVmEgL8bJTDUZ6FYFR7ZHcYAsDzcwHgIu3GGEMVRS3Uf +ILmioiyly9vcK4Sa01ondARmsi/I0s7pWpKflaekyv5boJKD/xqwz9lGejmJHelf +8Od2TyqJScMpB7Q8c2ROxBwqwB72jMCEvYigB+Wnbb8RipliqNflIGx938FRCzKL +UQUBmNAznR/yRRL0wHf9UAE/8v9a09uZABeiznzOFAl/frHpgdAbC00LkFlnwwgX +g8YfEFlkp4fLx5B7LtoO6uVNFVimLxtwirpyKoj3G4M/kvSTux8bTw0heBCmWmKR +57MS6k7ODzbv+Kpeht2hqVZCNFMxoQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRuMnDhJjoj7DcKALj+HbxEqj3r6jAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBALSnXfx72C3ldhBP5kY4Mo2DDaGQ8FGpTOOiD95d +0rf7I9LrsBGVqu/Nir+kqqP80PB70+Jy9fHFFigXwcPBX3MpKGxK8Cel7kVf8t1B +4YD6A6bqlzP+OUL0uGWfZpdpDxwMDI2Flt4NEldHgXWPjvN1VblEKs0+kPnKowyg +jhRMgBbD/y+8yg0fIcjXUDTAw/+INcp21gWaMukKQr/8HswqC1yoqW9in2ijQkpK +2RB9vcQ0/gXR0oJUbZQx0jn0OH8Agt7yfMAnJAdnHO4M3gjvlJLzIC5/4aGrRXZl +JoZKfJ2fZRnrFMi0nhAYDeInoS+Rwx+QzaBk6fX5VPyCj8foZ0nmqvuYoydzD8W5 +mMlycgxFqS+DUmO+liWllQC4/MnVBlHGB1Cu3wTj5kgOvNs/k+FW3GXGzD3+rpv0 +QTLuwSbMr+MbEThxrSZRSXTCQzKfehyC+WZejgLb+8ylLJUA10e62o7H9PvCrwj+ +ZDVmN7qj6amzvndCP98sZfX7CFZPLfcBd4wVIjHsFjSNEwWHOiFyLPPG7cdolGKA +lOFvonvo4A1uRc13/zFeP0Xi5n5OZ2go8aOOeGYdI2vB2sgH9R2IASH/jHmr0gvY +0dfBCcfXNgrS0toq0LX/y+5KkKOxh52vEYsJLdhqrveuZhQnsFEm/mFwjRXkyO7c +2jpC +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +FEv9Bg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICsDCCAjagAwIBAgIRAMgApnfGYPpK/fD0dbN2U4YwCgYIKoZIzj0EAwMwgZcx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwnQW1h +em9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMCAXDTIxMDUxOTE4MzgxMVoYDzIxMjEwNTE5MTkzODExWjCBlzELMAkG +A1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzAR +BgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6b24g +UkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0 +bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfEWl6d4qSuIoECdZPp+39LaKsfsX7 +THs3/RrtT0+h/jl3bjZ7Qc68k16x+HGcHbaayHfqD0LPdzH/kKtNSfQKqemdxDQh +Z4pwkixJu8T1VpXZ5zzCvBXCl75UqgEFS92jQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFFPrSNtWS5JU+Tvi6ABV231XbjbEMA4GA1UdDwEB/wQEAwIBhjAK +BggqhkjOPQQDAwNoADBlAjEA+a7hF1IrNkBd2N/l7IQYAQw8chnRZDzh4wiGsZsC +6A83maaKFWUKIb3qZYXFSi02AjAbp3wxH3myAmF8WekDHhKcC2zDvyOiKLkg9Y6v +ZVmyMR043dscQbcsVoacOYv198c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAPhVkIsQ51JFhD2kjFK5uAkwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBldS1jZW50cmFsLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNjA2MjEyOTE3WhgPMjEyMjA2MDYyMjI5MTdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA5xnIEBtG5b2nmbj49UEwQza +yX0844fXjccYzZ8xCDUe9dS2XOUi0aZlGblgSe/3lwjg8fMcKXLObGGQfgIx1+5h +AIBjORis/dlyN5q/yH4U5sjS8tcR0GDGVHrsRUZCo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBRK+lSGutXf4DkTjR3WNfv4+KeNFTAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJ4NxQ1Gerqr70ZrnUqc62Vl8NNqTzInamCG +Kce3FTsMWbS9qkgrjZkO9QqOcGIw/gIwSLrwUT+PKr9+H9eHyGvpq9/3AIYSnFkb +Cf3dyWPiLKoAtLFwjzB/CkJlsAS1c8dS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +r1o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAKwYju1QWxUZpn6D1gOtwgQwDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NTM1NFoYDzIxMjEwNTIwMTc1MzU0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCKdBP1U4lqWWkc +Cb25/BKRTsvNVnISiKocva8GAzJyKfcGRa85gmgu41U+Hz6+39K+XkRfM0YS4BvQ +F1XxWT0bNyypuvwCvmYShSTjN1TY0ltncDddahTajE/4MdSOZb/c98u0yt03cH+G +hVwRyT50h0v/UEol50VfwcVAEZEgcQQYhf1IFUFlIvKpmDOqLuFakOnc7c9akK+i +ivST+JO1tgowbnNkn2iLlSSgUWgb1gjaOsNfysagv1RXdlyPw3EyfwkFifAQvF2P +Q0ayYZfYS640cccv7efM1MSVyFHR9PrrDsF/zr2S2sGPbeHr7R/HwLl+S5J/l9N9 +y0rk6IHAWV4dEkOvgpnuJKURwA48iu1Hhi9e4moNS6eqoK2KmY3VFpuiyWcA73nH +GSmyaH+YuMrF7Fnuu7GEHZL/o6+F5cL3mj2SJJhL7sz0ryf5Cs5R4yN9BIEj/f49 +wh84pM6nexoI0Q4wiSFCxWiBpjSmOK6h7z6+2utaB5p20XDZHhxAlmlx4vMuWtjh +XckgRFxc+ZpVMU3cAHUpVEoO49e/+qKEpPzp8Xg4cToKw2+AfTk3cmyyXQfGwXMQ +ZUHNZ3w9ILMWihGCM2aGUsLcGDRennvNmnmin/SENsOQ8Ku0/a3teEzwV9cmmdYz +5iYs1YtgPvKFobY6+T2RXXh+A5kprwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBSyUrsQVnKmA8z6/2Ech0rCvqpNmTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBAFlj3IFmgiFz5lvTzFTRizhVofhTJsGr14Yfkuc7 +UrXPuXOwJomd4uot2d/VIeGJpfnuS84qGdmQyGewGTJ9inatHsGZgHl9NHNWRwKZ +lTKTbBiq7aqgtUSFa06v202wpzU+1kadxJJePrbABxiXVfOmIW/a1a4hPNcT3syH +FIEg1+CGsp71UNjBuwg3JTKWna0sLSKcxLOSOvX1fzxK5djzVpEsvQMB4PSAzXca +vENgg2ErTwgTA+4s6rRtiBF9pAusN1QVuBahYP3ftrY6f3ycS4K65GnqscyfvKt5 +YgjtEKO3ZeeX8NpubMbzC+0Z6tVKfPFk/9TXuJtwvVeqow0YMrLLyRiYvK7EzJ97 +rrkxoKnHYQSZ+rH2tZ5SE392/rfk1PJL0cdHnkpDkUDO+8cKsFjjYKAQSNC52sKX +74AVh6wMwxYwVZZJf2/2XxkjMWWhKNejsZhUkTISSmiLs+qPe3L67IM7GyKm9/m6 +R3r8x6NGjhTsKH64iYJg7AeKeax4b2e4hBb6GXFftyOs7unpEOIVkJJgM6gh3mwn +R7v4gwFbLKADKt1vHuerSZMiTuNTGhSfCeDM53XI/mjZl2HeuCKP1mCDLlaO+gZR +Q/G+E0sBKgEX4xTkAc3kgkuQGfExdGtnN2U2ehF80lBHB8+2y2E+xWWXih/ZyIcW +wOx+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +89cEIGdbjsA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQYjbPSg4+RNRD3zNxO1fuKDANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTkyMVoYDzIwNjEwNTI0MjE1OTIxWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA179eQHxcV0YL +XMkqEmhSBazHhnRVd8yICbMq82PitE3BZcnv1Z5Zs/oOgNmMkOKae4tCXO/41JCX +wAgbs/eWWi+nnCfpQ/FqbLPg0h3dqzAgeszQyNl9IzTzX4Nd7JFRBVJXPIIKzlRf ++GmFsAhi3rYgDgO27pz3ciahVSN+CuACIRYnA0K0s9lhYdddmrW/SYeWyoB7jPa2 +LmWpAs7bDOgS4LlP2H3eFepBPgNufRytSQUVA8f58lsE5w25vNiUSnrdlvDrIU5n +Qwzc7NIZCx4qJpRbSKWrUtbyJriWfAkGU7i0IoainHLn0eHp9bWkwb9D+C/tMk1X +ERZw2PDGkwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSFmR7s +dAblusFN+xhf1ae0KUqhWTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAHsXOpjPMyH9lDhPM61zYdja1ebcMVgfUvsDvt+w0xKMKPhBzYDMs/cFOi1N +Q8LV79VNNfI2NuvFmGygcvTIR+4h0pqqZ+wjWl3Kk5jVxCrbHg3RBX02QLumKd/i +kwGcEtTUvTssn3SM8bgM0/1BDXgImZPC567ciLvWDo0s/Fe9dJJC3E0G7d/4s09n +OMdextcxFuWBZrBm/KK3QF0ByA8MG3//VXaGO9OIeeOJCpWn1G1PjT1UklYhkg61 +EbsTiZVA2DLd1BGzfU4o4M5mo68l0msse/ndR1nEY6IywwpgIFue7+rEleDh6b9d +PYkG1rHVw2I0XDG4o17aOn5E94I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQC6W4HFghUkkgyQw14a6JljANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyMzE4MTYzMloYDzIwNjIwNTIzMTkxNjMyWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiM/t4FV2R9Nx +UQG203UY83jInTa/6TMq0SPyg617FqYZxvz2kkx09x3dmxepUg9ttGMlPgjsRZM5 +LCFEi1FWk+hxHzt7vAdhHES5tdjwds3aIkgNEillmRDVrUsbrDwufLaa+MMDO2E1 +wQ/JYFXw16WBCCi2g1EtyQ2Xp+tZDX5IWOTnvhZpW8vVDptZ2AcJ5rMhfOYO3OsK +5EF0GGA5ldzuezP+BkrBYGJ4wVKGxeaq9+5AT8iVZrypjwRkD7Y5CurywK3+aBwm +s9Q5Nd8t45JCOUzYp92rFKsCriD86n/JnEvgDfdP6Hvtm0/DkwXK40Wz2q0Zrd0k +mjP054NRPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRR7yqd +SfKcX2Q8GzhcVucReIpewTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEszBRDwXcZyNm07VcFwI1Im94oKwKccuKYeJEsizTBsVon8VpEiMwDs+yGu +3p8kBhvkLwWybkD/vv6McH7T5b9jDX2DoOudqYnnaYeypsPH/00Vh3LvKagqzQza +orWLx+0tLo8xW4BtU+Wrn3JId8LvAhxyYXTn9bm+EwPcStp8xGLwu53OPD1RXYuy +uu+3ps/2piP7GVfou7H6PRaqbFHNfiGg6Y+WA0HGHiJzn8uLmrRJ5YRdIOOG9/xi +qTmAZloUNM7VNuurcMM2hWF494tQpsQ6ysg2qPjbBqzlGoOt3GfBTOZmqmwmqtam +K7juWM/mdMQAJ3SMlE5wI8nVdx4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAL9SdzVPcpq7GOpvdGoM80IwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTY1ODA3WhgPMjEyMTA1MjAxNzU4MDdaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJWDgXebvwjR+Ce+hxKOLbnsfN5W5dOlP +Zn8kwWnD+SLkU81Eac/BDJsXGrMk6jFD1vg16PEkoSevsuYWlC8xR6FmT6F6pmeh +fsMGOyJpfK4fyoEPhKeQoT23lFIc5Orjo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBSVNAN1CHAz0eZ77qz2adeqjm31TzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAMlQeHbcjor49jqmcJ9gRLWdEWpXG8thIf6zfYQ/OEAg +d7GDh4fR/OUk0VfjsBUN/gIwZB0bGdXvK38s6AAE/9IT051cz/wMe9GIrX1MnL1T +1F5OqnXJdiwfZRRTHsRQ/L00 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQalr16vDfX4Rsr+gfQ4iVFDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNjA2MjEyNTIzWhgPMjEyMjA2MDYyMjI1MjNaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANbHbFg7 +2VhZor1YNtez0VlNFaobS3PwOMcEn45BE3y7HONnElIIWXGQa0811M8V2FnyqnE8 +Z5aO1EuvijvWf/3D8DPZkdmAkIfh5hlZYY6Aatr65kEOckwIAm7ZZzrwFogYuaFC +z/q0CW+8gxNK+98H/zeFx+IxiVoPPPX6UlrLvn+R6XYNERyHMLNgoZbbS5gGHk43 +KhENVv3AWCCcCc85O4rVd+DGb2vMVt6IzXdTQt6Kih28+RGph+WDwYmf+3txTYr8 +xMcCBt1+whyCPlMbC+Yn/ivtCO4LRf0MPZDRQrqTTrFf0h/V0BGEUmMGwuKgmzf5 +Kl9ILdWv6S956ioZin2WgAxhcn7+z//sN++zkqLreSf90Vgv+A7xPRqIpTdJ/nWG +JaAOUofBfsDsk4X4SUFE7xJa1FZAiu2lqB/E+y7jnWOvFRalzxVJ2Y+D/ZfUfrnK +4pfKtyD1C6ni1celrZrAwLrJ3PoXPSg4aJKh8+CHex477SRsGj8KP19FG8r0P5AG +8lS1V+enFCNvT5KqEBpDZ/Y5SQAhAYFUX+zH4/n4ql0l/emS+x23kSRrF+yMkB9q +lhC/fMk6Pi3tICBjrDQ8XAxv56hfud9w6+/ljYB2uQ1iUYtlE3JdIiuE+3ws26O8 +i7PLMD9zQmo+sVi12pLHfBHQ6RRHtdVRXbXRAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFBFot08ipEL9ZUXCG4lagmF53C0/MA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAi2mcZi6cpaeqJ10xzMY0F3L2eOKYnlEQ +h6QyhmNKCUF05q5u+cok5KtznzqMwy7TFOZtbVHl8uUX+xvgq/MQCxqFAnuStBXm +gr2dg1h509ZwvTdk7TDxGdftvPCfnPNJBFbMSq4CZtNcOFBg9Rj8c3Yj+Qvwd56V +zWs65BUkDNJrXmxdvhJZjUkMa9vi/oFN+M84xXeZTaC5YDYNZZeW9706QqDbAVES +5ulvKLavB8waLI/lhRBK5/k0YykCMl0A8Togt8D1QsQ0eWWbIM8/HYJMPVFhJ8Wj +vT1p/YVeDA3Bo1iKDOttgC5vILf5Rw1ZEeDxjf/r8A7VS13D3OLjBmc31zxRTs3n +XvHKP9MieQHn9GE44tEYPjK3/yC6BDFzCBlvccYHmqGb+jvDEXEBXKzimdC9mcDl +f4BBQWGJBH5jkbU9p6iti19L/zHhz7qU6UJWbxY40w92L9jS9Utljh4A0LCTjlnR +NQUgjnGC6K+jkw8hj0LTC5Ip87oqoT9w7Av5EJ3VJ4hcnmNMXJJ1DkWYdnytcGpO +DMVITQzzDZRwhbitCVPHagTN2wdi9TEuYE33J0VmFeTc6FSI50wP2aOAZ0Q1/8Aj +bxeM5jS25eaHc2CQAuhrc/7GLnxOcPwdWQb2XWT8eHudhMnoRikVv/KSK3mf6om4 +1YfpdH2jp30= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQTDc+UgTRtYO7ZGTQ8UWKDDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjI0NjI0WhgPMjA2MTA1MjEyMzQ2MjRaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgZXUtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM1oGtthQ1YiVIC2 +i4u4swMAGxAjc/BZp0yq0eP5ZQFaxnxs7zFAPabEWsrjeDzrRhdVO0h7zskrertP +gblGhfD20JfjvCHdP1RUhy/nzG+T+hn6Takan/GIgs8grlBMRHMgBYHW7tklhjaH +3F7LujhceAHhhgp6IOrpb6YTaTTaJbF3GTmkqxSJ3l1LtEoWz8Al/nL/Ftzxrtez +Vs6ebpvd7sw37sxmXBWX2OlvUrPCTmladw9OrllGXtCFw4YyLe3zozBlZ3cHzQ0q +lINhpRcajTMfZrsiGCkQtoJT+AqVJPS2sHjqsEH8yiySW9Jbq4zyMbM1yqQ2vnnx +MJgoYMcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUaQG88UnV +JPTI+Pcti1P+q3H7pGYwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBAkgr75V0sEJimC6QRiTVWEuj2Khy7unjSfudbM6zumhXEU2/sUaVLiYy6cA/x +3v0laDle6T07x9g64j5YastE/4jbzrGgIINFlY0JnaYmR3KZEjgi1s1fkRRf3llL +PJm9u4Q1mbwAMQK/ZjLuuRcL3uRIHJek18nRqT5h43GB26qXyvJqeYYpYfIjL9+/ +YiZAbSRRZG+Li23cmPWrbA1CJY121SB+WybCbysbOXzhD3Sl2KSZRwSw4p2HrFtV +1Prk0dOBtZxCG9luf87ultuDZpfS0w6oNBAMXocgswk24ylcADkkFxBWW+7BETn1 +EpK+t1Lm37mU4sxtuha00XAi +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQcY44/8NUvBwr6LlHfRy7KjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MjcxOFoYDzIwNjEwNTE5MTkyNzE4WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UaBeC+Usalu +EtXnV7+PnH+gi7/71tI/jkKVGKuhD2JDVvqLVoqbMHRh3+wGMvqKCjbHPcC2XMWv +566fpAj4UZ9CLB5fVzss+QVNTl+FH2XhEzigopp+872ajsNzcZxrMkifxGb4i0U+ +t0Zi+UrbL5tsfP2JonKR1crOrbS6/DlzHBjIiJazGOQcMsJjNuTOItLbMohLpraA +/nApa3kOvI7Ufool1/34MG0+wL3UUA4YkZ6oBJVxjZvvs6tI7Lzz/SnhK2widGdc +snbLqBpHNIZQSorVoiwcFaRBGYX/uzYkiw44Yfa4cK2V/B5zgu1Fbr0gbI2am4eh +yVYyg4jPawIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS9gM1m +IIjyh9O5H/7Vj0R/akI7UzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAF0Sm9HC2AUyedBVnwgkVXMibnYChOzz7T+0Y+fOLXYAEXex2s8oqGeZdGYX +JHkjBn7JXu7LM+TpTbPbFFDoc1sgMguD/ls+8XsqAl1CssW+amryIL+jfcfbgQ+P +ICwEUD9hGdjBgJ5WcuS+qqxHsEIlFNci3HxcxfBa9VsWs5TjI7Vsl4meL5lf7ZyL +wDV7dHRuU+cImqG1MIvPRIlvPnT7EghrCYi2VCPhP2pM/UvShuwVnkz4MJ29ebIk +WR9kpblFxFdE92D5UUvMCjC2kmtgzNiErvTcwIvOO9YCbBHzRB1fFiWrXUHhJWq9 +IkaxR5icb/IpAV0A1lYZEWMVsfQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +rM2p0kk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQYWF2Yb4HiQV2OJlcN2BhUDANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC02IFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yNTA1MjAwMTQzNTdaGA8yMDY1MDUyMDAyNDM1N1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp +7R+Rclm+GQyjwytLA5lg6AH6IzXEsp8Q5ZtYzjYizhzOWhdqYfYQsv43IJjzY3Cd +Wwtp+/+WOegfFntFfEN7Cy2zP+Ib18TT4alkalo45mbTjcKAflDhLuwLZCrXq7Sk +ECm0lveumAlVXXUW0mFEOd48XvTzvY1tNO8hGlN2l1n2wG6TfOU317q+PLF2F79J +iAqVt2ZUIvFgwIS3qfQK0n6c5OKzPrpsvfecFZePhL20YH/2yCNxGb6rNBRErXxV +oNICqsl03LNOcgpm1WvXINWLJT/le6IEAbHMgMeG3DIG3q4sYiEyE5qi8SSR6S5P +ky3+mtWZL/ZWp2HFQ8xZAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFAlH8qgMxdi5cVGO+FTynv4hC3sOMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAS+oW1l/6C3lh4WWE2aBGCdC713mjpBjgTjzpUnyIdQDaBuzD +vHpm+p6bK+a4WP/jtjniUvZhyYD9VbaQhHYa9nSX+LHIkF1cxMTqHMjK8dgnenF7 +KhUbmaERlk7MPAGLoRKngoS8NRjizFiBLUTKn62KvI4DKiswxWJklpd3HIn9qq8q +eR2PLoHHc4WJ/E1UXpCIt9dbWwB22xHp0Gb0vFcdWE76uwR5C4x8eAqct8455NrC +gh3UCCsNEu57aUjNRA+PwvCUDEsJj72mKCzUNGG4bTDCqY1DMlx2TaXByxs7Ctwx +wiK/vWV+ZChc5mMDzf9csrOQzDPPJpiUfp11rg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAKEoQBLrmKhwHJppfnj9miEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjUwNTIwMDE0NDAyWhgPMjEyNTA1MjAwMjQ0MDJa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTYgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +m5LnhN6bm6BRlvFolsYe4EuK0uIWhLFItpCs4GbfAyQMJUEFc9pUIayg9+N6/B7q +4VVRvIDYfqEIjywkWaJwBu58/B2EE47iLTHvAU9HyxojKvucFFHRmrrsQG5Fyn/0 +LNDDiOpik6BTKp4b32JnEe+X5ASd4pU7ibOSec6m+mISIEIAsjaRw8WdcrglJpIu +69NKoM9QJRkiECWe6MSxwuTdNgh5SouuABThg7ROPrxlxt1pkwlN1nA1Czb2GrBe +XV4Be1h1djEC7UoehaVxOs4VFJfOuHg72YHHf3zVhtpJAGR0/HypOcFnXd+DXoXL +wjMUm3mDNyMEJLHQUwYA99QheEl7+aprpe9gM3RQ85sg5XQJ3YUOPuErqDnnVveH +bckOkkTYTfSZUY9fpQmX7YxFv/QN6UpMXh7pzxOeM+VTvsH3JH0HweFNWCSALcbg +/no7Z8mIkOEE9gMCzqoFz0uILQzFM/n5yrAnmCzU+EM5HC3+oxO9mGGwiNp/LQUY +nWqkHHLQjurxmqlLQoBofpYiyi9vdh3IbULtYZJTUT87OA5BsKOH5Vaq6J9vw701 +1zCQnpg8B6tp7+Z2/vCRr1sY7tNmh8gQoiwFfx9Qpkc83/mlyoenvqeevQ42bok8 +0KZvJ/dYKNRZLO7kX8kHZTWkzGOYpfI5djYCWAAlPRUCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUri9+CM+fxgpdvEDFDhb0smAKHy4wDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAUOKOXItwvve85tuPTLBNVP18f +40rf8RPhH1JFKMePXcQ2gav7t9KwxnUk6iqVo/Pt/knUeXJ+uLMRHfjzVpadlUoT +6zH+TndeljwG1QFuYOxwYI+VNwZnb7lF0O0cADkhDfc8/QvG/ZkrAx7s2kWWC8by +fWpsjDt3xDD3ump+Qr+o0jpZyXPbWZQeIo5wmnMOc0PXxeFlIOCeUaJDtaJMRHBq +CpdPT5xkNaRJnZzKJiUZx3NZe4du5lVuz5gT9YmooFGRFHU26hANkHQGxsDTkTx9 +Mg+5QXeDFQ7MnvFmFu2rb2cizZuKL9ZpiLE5f2/Hcd7apY7z8dSJhDWhvmTeeJPr +GbplpepGVt5BIhg395guJ8Kww4Ux7a/8ERdQHkRd+ykspQNfveqLl+IP8s/snLDP +lcILGYvHLq02HGrM8chU5mzNe5lyYmSI3cYxEZdDMiuOVpLlNuNUGadyafFx13Y0 +a6wDpXcot9Qh6W+JFpq0TbgbVezkk+1YzDJK7ShBQqckPlGHl9uyWjX3xuC4KD6g +epx7UmkEP56BhvLMoT+o3KDBts9ujuJIZt+uGoo+rYIZaSuGLKdA60B4ZS3ONrws +G3+o22y0lMvHGMHiwCIH+RQQBt5brmfkjD+QJh5K/1iJaZtFecgeuZnuar5W37k2 +IGstLzLjZODMnBQoSA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQbliDBFi6VBnhtzveX+/ALjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC02IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTI1MDUyMDAxNDQwN1oYDzIxMjUwNTIwMDI0NDA3WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC02IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf1cgi4GHIBFSOQMW6NPm +/MUlTNs34ONxLDenJ/m3tkuMjoDSrimTAwQcCoIUrlwOpauaW4le0aFb+SUw6IEW +axCohHeASRW5P7rV/MBgiSWcKxgAj0XSWXaGMXM9ycfso0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTTHGkZp0gQNJj2xYOEKgB+WoGw/zAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwSZoT6sdk5Duk2oqvI9kbYDH84vut3CmX +EtJwpeusN5Uv0fm//WfNwXZmZ3RB7L9qAjEAqFQK/AKtmyTePw4w+LQVljNOh1UQ +sfb8fO9Mt9eYGJ0i1QYo60u/qIypql6ZsIvW +-----END CERTIFICATE----- diff --git a/pkg/dbconn/rds_test.go b/pkg/dbconn/rds_test.go new file mode 100644 index 0000000..cff768a --- /dev/null +++ b/pkg/dbconn/rds_test.go @@ -0,0 +1,92 @@ +package dbconn + +import ( + "crypto/tls" + "crypto/x509" + "testing" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsRDSHost(t *testing.T) { + tests := []struct { + host string + want bool + }{ + {"mydb.abc123.us-east-1.rds.amazonaws.com", true}, + {"mydb.cluster-abc123.us-west-2.rds.amazonaws.com", true}, + {"mydb.abc123.eu-west-1.rds.amazonaws.com:5432", true}, + {"fake-rds.amazonaws.com", false}, + {"rds.amazonaws.com", false}, + {"mydb.rds.amazonaws.com.evil.example", false}, + {"localhost", false}, + {"127.0.0.1", false}, + {"db.internal.example.com", false}, + } + for _, tt := range tests { + t.Run(tt.host, func(t *testing.T) { + assert.Equal(t, tt.want, IsRDSHost(tt.host)) + }) + } +} + +func TestEmbeddedRDSBundleParses(t *testing.T) { + pool := x509.NewCertPool() + require.True(t, pool.AppendCertsFromPEM(rdsGlobalBundle), + "embedded RDS bundle must contain usable certificates") +} + +func TestRDSTLSConfig(t *testing.T) { + tc := rdsTLSConfig("mydb.abc123.us-east-1.rds.amazonaws.com") + assert.Equal(t, "mydb.abc123.us-east-1.rds.amazonaws.com", tc.ServerName) + assert.NotNil(t, tc.RootCAs) + assert.Equal(t, uint16(tls.VersionTLS12), tc.MinVersion) + assert.False(t, tc.InsecureSkipVerify) +} + +func TestConfigureTLS(t *testing.T) { + const rdsURL = "postgres://user@mydb.abc123.us-east-1.rds.amazonaws.com:5432/app" + + parse := func(t *testing.T, url string) *pgxpool.Config { + t.Helper() + pc, err := pgxpool.ParseConfig(url) + require.NoError(t, err) + return pc + } + + t.Run("RDS host without sslmode gets verify-full with embedded roots and no plaintext fallback", func(t *testing.T) { + pc := parse(t, rdsURL) + require.NoError(t, configureTLS(pc, Config{URL: rdsURL})) + require.NotNil(t, pc.ConnConfig.TLSConfig) + assert.Equal(t, "mydb.abc123.us-east-1.rds.amazonaws.com", pc.ConnConfig.TLSConfig.ServerName) + assert.NotNil(t, pc.ConnConfig.TLSConfig.RootCAs) + assert.False(t, pc.ConnConfig.TLSConfig.InsecureSkipVerify) + assert.Nil(t, pc.ConnConfig.Fallbacks, "plaintext fallbacks must be dropped for RDS hosts") + }) + + t.Run("RDS host with explicit sslmode=disable is honored", func(t *testing.T) { + url := rdsURL + "?sslmode=disable" + pc := parse(t, url) + require.NoError(t, configureTLS(pc, Config{URL: url})) + assert.Nil(t, pc.ConnConfig.TLSConfig) + }) + + t.Run("RDS host with sslmode=verify-full gets the embedded roots injected", func(t *testing.T) { + url := rdsURL + "?sslmode=verify-full" + pc := parse(t, url) + require.NoError(t, configureTLS(pc, Config{URL: url})) + require.NotNil(t, pc.ConnConfig.TLSConfig) + assert.NotNil(t, pc.ConnConfig.TLSConfig.RootCAs, + "verification without a bundle must get the embedded RDS roots") + }) + + t.Run("non-RDS host is left untouched", func(t *testing.T) { + url := "postgres://user@localhost:5432/app" + pc := parse(t, url) + before := pc.ConnConfig.TLSConfig + require.NoError(t, configureTLS(pc, Config{URL: url})) + assert.Equal(t, before, pc.ConnConfig.TLSConfig) + }) +} From c2e4aa9576faa128d81120863fb65b8147ab2c9e Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Tue, 7 Jul 2026 20:12:03 +1000 Subject: [PATCH 03/12] docs: add curated design docs, architecture map, and consolidated orchestrator integration; rename TCB.md to SAFETY.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ten engine-facing docs move in from the research corpus (design, principles, invariants, TCB model, DDL/version/change-capture references) with descriptive filenames, rewritten cross-links, and a sanitization pass — no company-internal references. The broader research set stays internal. architecture.md is the one-screen codebase map; schemabot-integration.md is the single home for the orchestrator story (everywhere else says "the orchestrator" and points there); SAFETY.md is the generic contributor-facing name for the critical-core partition. --- AGENTS.md | 26 +- README.md | 12 +- SAFETY.md | 71 +++ TCB.md | 68 --- docs/README.md | 94 ++++ docs/architecture.md | 99 ++++ docs/change-capture-tradeoff.md | 96 ++++ docs/design-principles.md | 180 +++++++ docs/high-level-design.md | 315 ++++++++++++ docs/invariants.md | 310 ++++++++++++ docs/low-level-design.md | 676 ++++++++++++++++++++++++++ docs/postgres-online-ddl-reference.md | 226 +++++++++ docs/postgresql-version-support.md | 112 +++++ docs/schemabot-integration.md | 121 +++++ docs/tcb-model.md | 251 ++++++++++ 15 files changed, 2571 insertions(+), 86 deletions(-) create mode 100644 SAFETY.md delete mode 100644 TCB.md create mode 100644 docs/README.md create mode 100644 docs/architecture.md create mode 100644 docs/change-capture-tradeoff.md create mode 100644 docs/design-principles.md create mode 100644 docs/high-level-design.md create mode 100644 docs/invariants.md create mode 100644 docs/low-level-design.md create mode 100644 docs/postgres-online-ddl-reference.md create mode 100644 docs/postgresql-version-support.md create mode 100644 docs/schemabot-integration.md create mode 100644 docs/tcb-model.md diff --git a/AGENTS.md b/AGENTS.md index 6de5cfa..efcd234 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,13 +3,13 @@ Guidance for AI coding agents working on pg-sprite — an online schema-change engine for Aurora PostgreSQL. Deliberately short: don't restate what you can infer from the code. -## Read TCB.md first +## Read SAFETY.md first -This codebase is partitioned into a **trusted computing base** and an untrusted periphery. -[TCB.md](TCB.md) lists which packages are which and the stricter rules that apply inside the -boundary (proof types, bounded everything, `// INV:` locality, the TCB dependency list, the -never-import-`block/spirit` rule). Before touching a `pkg/` package, check its row in TCB.md — -the review bar and the AI-assistance posture differ by side. +This codebase is partitioned into a **safety-critical core** and a periphery. +[SAFETY.md](SAFETY.md) lists which packages are which and the stricter rules that apply inside +the core (proof types, bounded everything, `// INV:` locality, the core dependency list, the +never-import-`block/spirit` rule). Before touching a `pkg/` package, check its row in +SAFETY.md — the review bar and the AI-assistance posture differ by side. ## Build and test @@ -44,9 +44,9 @@ make lint # golangci-lint - **"A little copying is better than a little dependency."** Small mechanics (retry/backoff, CA loading, keepalives, tiny helpers) are hand-written or copied with an attributing comment — never imported. Take pinned dependencies only for load-bearing expertise (the parser, the wire - protocol); a dependency inside a TCB package needs a recorded decision (see - [TCB.md](TCB.md)). **Never import `github.com/block/spirit` as a module** — port ideas with - citations, not code. + protocol); a dependency inside a core package needs a recorded decision (see + [SAFETY.md](SAFETY.md)). **Never import `github.com/block/spirit` as a module** — port ideas + with citations, not code. - **Expose the smallest interface that does the job.** Export domain types and their validating constructors, not internals; no re-exports or plain-delegation wrappers — callers import the source package. @@ -65,6 +65,8 @@ make lint # golangci-lint already-closed error). - State comparisons use typed constants and helpers, never raw string matching. -> This file grows with the codebase (see the research build-tracker task for the full -> AGENTS.md derivation from schemabot's). Keep it short: rules earn a line here only when an -> agent can't infer them from the code. +Design docs live in [docs/](docs/) — start at [docs/README.md](docs/README.md); the invariant +registry is [docs/invariants.md](docs/invariants.md). + +> This file grows with the codebase. Keep it short: rules earn a line here only when an agent +> can't infer them from the code. diff --git a/README.md b/README.md index 4a04cdd..5a5f8ee 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ when one exists (`CONCURRENTLY`, `NOT VALID` + `VALIDATE`, fast default, a genuine table rewrite is unavoidable. **Status: Phase 0 (scaffold + test harness).** All subcommands are stubs. The -design docs and the phased build plan currently live in the research repo -(`research/migrations-related/aurora-postgresql/online-schema-change-engine/`) -and will migrate here as part of the open-sourcing work. +design docs and the phased build plan live in [docs/](docs/) — start with +[docs/README.md](docs/README.md). -The codebase is partitioned into a small trusted core and an untrusted -periphery — **[TCB.md](TCB.md)** says which packages are which and the rules -that apply inside the boundary. Read it before changing anything under `pkg/`. +The codebase is partitioned into a small safety-critical core and a +periphery — **[SAFETY.md](SAFETY.md)** says which packages are which and the +rules that apply inside the core. Read it before changing anything under +`pkg/`. ## Development diff --git a/SAFETY.md b/SAFETY.md new file mode 100644 index 0000000..1bd04f8 --- /dev/null +++ b/SAFETY.md @@ -0,0 +1,71 @@ +# The safety-critical core + +pg-sprite rewrites production tables — a bug in the wrong place is silent data corruption or an +app-wide outage. The codebase is therefore partitioned into a small **safety-critical core** +that enforces the engine's invariants, and a **periphery** where a bug can only produce a wrong +message, a wasted copy, or a missed optimization. (The design docs call this partition the +engine's *trusted computing base*; this file is the repo-level map of it.) + +**Membership test:** can a bug here corrupt data, lose writes, swap in a wrong table, strand a +replication slot, or take the application down? If yes → core. If no → periphery. + +The invariant registry (invariant IDs referenced below) lives in +[docs/invariants.md](docs/invariants.md); the full partition design lives in +[docs/tcb-model.md](docs/tcb-model.md). + +## The partition + +| Package | Core? | Status | Invariants enforced | +| --- | --- | --- | --- | +| `pkg/dbconn` — pool defaults, advisory lock, terminate-blockers, retries, RDS TLS | ✅ core | exists (Phase 0) | LK-1, LK-2 primitives | +| `pkg/preflight` — precondition verifier, refusals | ✅ core | planned (Phase 1–2) | ST-6, RF-1..RF-5 | +| `pkg/checksum` — chunk verifier, continuous checker, repair | ✅ core | planned (Phase 5) | CO-1, CO-2, CO-3 | +| `pkg/copier` — shadow-table chunked copy | ✅ core | planned (Phase 4) | CO-4, LK-3 | +| `pkg/applier` — change apply, buffer, flush scheduling | ✅ core | planned (Phase 6) | CO-4, CO-5, CO-6, LK-3 | +| `pkg/decode` — logical decoding, LSN/position accounting | ✅ core | planned (Phase 6) | ST-4, CO-4 | +| `pkg/checkpoint` — durable resume state | ✅ core | planned (Phase 8) | ST-1, ST-2 | +| slot lifecycle (in `pkg/decode`) — create, reap, lag ceiling | ✅ core | planned (Phase 8) | ST-3 | +| `pkg/migration` — orchestrator, **cutover swap + fidelity gate** | ✅ core | planned (Phase 7) | LK-2, LK-4, ST-5 | +| `pkg/statement`, `pkg/planner`, `pkg/schemadiff`, `pkg/lint` — classify/diff/route | ❌ periphery¹ | planned (Phase 1–2) | (CO-7 holds at the parse boundary) | +| `internal/cli` — CLI, flags, help, prompts | ❌ periphery | exists (stubs) | — | +| status / progress / advisory rendering, metrics | ❌ periphery | planned | — | +| orchestrator adapter | ❌ periphery | planned (Phase 11) | OC-* hold *at* the boundary | +| `internal/testutil` | ❌ test-only | exists | — | + +¹ **The planner is deliberately outside the core.** Its verdicts are *requests*, not +permissions: a wrong "native-safe" verdict is capped by the executor's own `lock_timeout` bound; +a wrong "copy" verdict produces a wasteful but *correct* migration (the checksum still gates). +The core executors re-verify their own preconditions and never trust that the planner checked. + +## Rules inside the core + +The short version — the full rules live in [docs/tcb-model.md](docs/tcb-model.md): + +- **Never trust callers.** Every dangerous operation re-verifies its preconditions, whoever the + requester is (CLI, planner, orchestrator). The periphery may request; the core enforces. +- **Domain types make illegal states unrepresentable.** Validating passages return proof types + with package-private constructors (`statement.Classified`, `PreflightedTable`, + `VerifiedShadow`, `CleanWatermark`, `TableLock`); dangerous APIs accept only proof types — + e.g. the cutover swap accepts only a `VerifiedShadow`. +- **Put a limit on everything.** Every loop bounded, every queue bounded, every retry counted, + every wait deadlined. An unbounded anything in a core package is a review-blocking defect. +- **Assert the positive and the negative space; pair assertions across boundaries.** Invariant + violations use a distinct error class (`ErrInvariantViolation`) naming the invariant ID, and + always abort fail-closed — never a warning, never retried. +- **Locality of behavior.** The enforcement point of an invariant carries a `// INV: ` + comment so a reviewer or agent can grep the ID and see the whole enforcement in one screen. +- **Dependencies inside the core become part of the core.** Current core dependency list: + `pgx/v5`, `pglogrepl`, stdlib. Adding one requires a recorded decision (see the rubric in + [docs/tcb-model.md](docs/tcb-model.md) — copy small things, take pinned dependencies only + for load-bearing expertise). + pg-sprite **never imports `block/spirit` as a module**: we port ideas with citations, not + code. +- **Priorities when trade-offs are hard:** Correctness → Readability → Ease of use → + Performance. + +## Working here with AI assistance + +- **Inside the core: less AI, more steering.** Spec first (the design docs + invariant IDs), + test-first with the invariant's named test obligation, small diffs, careful review. +- **Outside the core: more AI, less steering.** Iterate at inference speed; the boundary means + a bug in the periphery cannot corrupt data. diff --git a/TCB.md b/TCB.md deleted file mode 100644 index 7273c85..0000000 --- a/TCB.md +++ /dev/null @@ -1,68 +0,0 @@ -# Trusted Computing Base - -pg-sprite rewrites production tables — a bug in the wrong place is silent data corruption or an -app-wide outage. The codebase is therefore partitioned into a small **trusted computing base -(TCB)** that enforces the engine's invariants, and an **untrusted periphery** where a bug can -only produce a wrong message, a wasted copy, or a missed optimization. - -**Membership test:** can a bug here corrupt data, lose writes, swap in a wrong table, strand a -replication slot, or take the application down? If yes → TCB. If no → periphery. - -The invariant registry (invariant IDs referenced below) and the full TCB design currently live -in the research corpus (`research/migrations-related/aurora-postgresql/online-schema-change-engine/`, -docs `17-invariants.md` and `18-tcb-model.md`) and migrate here with the open-sourcing work. - -## The partition - -| Package | TCB? | Status | Invariants enforced | -| --- | --- | --- | --- | -| `pkg/dbconn` — pool defaults, advisory lock, terminate-blockers, retries | ✅ TCB | exists (Phase 0) | LK-1, LK-2 primitives | -| `pkg/preflight` — precondition verifier, refusals | ✅ TCB | planned (Phase 1–2) | ST-6, RF-1..RF-5 | -| `pkg/checksum` — chunk verifier, continuous checker, repair | ✅ TCB | planned (Phase 5) | CO-1, CO-2, CO-3 | -| `pkg/copier` — shadow-table chunked copy | ✅ TCB | planned (Phase 4) | CO-4, LK-3 | -| `pkg/applier` — change apply, buffer, flush scheduling | ✅ TCB | planned (Phase 6) | CO-4, CO-5, CO-6, LK-3 | -| `pkg/decode` — logical decoding, LSN/position accounting | ✅ TCB | planned (Phase 6) | ST-4, CO-4 | -| `pkg/checkpoint` — durable resume state | ✅ TCB | planned (Phase 8) | ST-1, ST-2 | -| slot lifecycle (in `pkg/decode`) — create, reap, lag ceiling | ✅ TCB | planned (Phase 8) | ST-3 | -| `pkg/migration` — orchestrator, **cutover swap + fidelity gate** | ✅ TCB | planned (Phase 7) | LK-2, LK-4, ST-5 | -| `pkg/statement`, `pkg/planner`, `pkg/schemadiff`, `pkg/lint` — classify/diff/route | ❌ periphery¹ | planned (Phase 1–2) | (CO-7 holds at the parse boundary) | -| `internal/cli` — CLI, flags, help, prompts | ❌ periphery | exists (stubs) | — | -| status / progress / advisory rendering, metrics | ❌ periphery | planned | — | -| SchemaBot adapter | ❌ periphery | planned (Phase 11) | OC-* hold *at* the boundary | -| `internal/testutil` | ❌ test-only | exists | — | - -¹ **The planner is deliberately outside.** Its verdicts are *requests*, not permissions: a wrong -"native-safe" verdict is capped by the executor's own `lock_timeout` bound; a wrong "copy" -verdict produces a wasteful but *correct* migration (the checksum still gates). The TCB -executors re-verify their own preconditions and never trust that the planner checked. - -## Rules inside the TCB - -The short version — the full rules live in the research doc 18: - -- **Never trust callers.** Every dangerous operation re-verifies its preconditions, whoever the - requester is (CLI, planner, SchemaBot). Untrusted code may request; the TCB enforces. -- **Domain types make illegal states unrepresentable.** Validating passages return proof types - with package-private constructors (`statement.Classified`, `PreflightedTable`, - `VerifiedShadow`, `CleanWatermark`, `TableLock`); dangerous APIs accept only proof types — - e.g. the cutover swap accepts only a `VerifiedShadow`. -- **Put a limit on everything.** Every loop bounded, every queue bounded, every retry counted, - every wait deadlined. An unbounded anything in a TCB package is a review-blocking defect. -- **Assert the positive and the negative space; pair assertions across boundaries.** Invariant - violations use a distinct error class (`ErrInvariantViolation`) naming the invariant ID, and - always abort fail-closed — never a warning, never retried. -- **Locality of behavior.** The enforcement point of an invariant carries a `// INV: ` - comment so a reviewer or agent can grep the ID and see the whole enforcement in one screen. -- **Dependencies inside the TCB become part of the TCB.** Current TCB dependency list: `pgx/v5`, - `pglogrepl`, stdlib. Adding one requires a recorded decision (see the rubric in doc 18 — - copy small things, take pinned dependencies only for load-bearing expertise). pg-sprite - **never imports `block/spirit` as a module**: we port ideas with citations, not code. -- **Priorities when trade-offs are hard:** Correctness → Readability → Ease of use → - Performance. - -## Working here with AI assistance - -- **Inside the TCB: less AI, more steering.** Spec first (the design docs + invariant IDs), - test-first with the invariant's named test obligation, small diffs, careful review. -- **Outside the TCB: more AI, less steering.** Iterate at inference speed; the boundary means a - bug in the periphery cannot corrupt data. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8d3c6b0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,94 @@ +# Online schema change engine for Aurora PostgreSQL + +Research and design notes for building an online schema migration engine targeting +**Aurora PostgreSQL**, by deriving and combining the best practices from established tools — +[Spirit](https://github.com/block/spirit) (Aurora MySQL), +[pg_osc](https://github.com/shayonj/pg-osc), [pg_repack](https://github.com/reorg/pg_repack), +and [pgroll](https://github.com/xataio/pgroll) — rather than porting any single one of them. + +## Table of contents + +- [Motivation](#motivation) +- [Documents](#documents) +- [TL;DR recommendation](#tldr-recommendation) + +## Motivation + +[Spirit](https://github.com/block/spirit) (block/spirit) is an excellent online schema +change tool, but it is **MySQL / Aurora MySQL only** — there is no PostgreSQL support. + +On the PostgreSQL side, the existing OSS tools either: + +- use the **expand/contract + views** model (pgroll, Reshape) — great, but the application + must become schema-version aware; or +- use the **shadow-table + swap** model (pg_osc, pg_repack) — but all of them capture + concurrent writes with **triggers**, which add synchronous write amplification to the + source table. + +**Nobody** ships the full **log-based copy-and-swap** combination for Postgres: multi-threaded chunked copy + +**log-based CDC (logical decoding, not triggers)** + checksum-gated atomic cutover + +checkpoint/resume, tuned for Aurora. That is the gap this engine targets. + +## Documents + +| Doc | Contents | +| --- | --- | +| [architecture.md](architecture.md) | The **one-screen codebase map** — the three layers, the package map with build status, the copy-and-swap lifecycle, and where to read more. Start here for orientation. | +| [postgres-online-ddl-reference.md](postgres-online-ddl-reference.md) | The Aurora PostgreSQL equivalent of MySQL's [InnoDB Online DDL Operations](https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html) reference — lock levels, rewrite/scan behaviour, and concurrent-DML safety per operation. | +| [high-level-design.md](high-level-design.md) | The **high-level design** — the conceptual overview: the problem, the planner → router → executor philosophy, the execution patterns and when each is chosen, and coverage at a glance. No package/interface detail. Start here for the architecture. | +| [low-level-design.md](low-level-design.md) | The **low-level design** — the detailed engineering design: package layout, the `Executor` interface, library choices, copy-and-swap lifecycle internals, the full coverage matrix, table requirements, and open decisions. Read this when designing the interfaces and packages. | +| [design-principles.md](design-principles.md) | The canonical **design principles** that govern the engine — safety over speed, decisions-not-options, classify-first, mandatory checksum gate, log-based CDC, and the PostgreSQL/Aurora-specific rules everything else traces back to. | +| [postgresql-version-support.md](postgresql-version-support.md) | The **PostgreSQL version matrix** — which PG majors pgroll, pg_osc, and pg_repack support, which majors Aurora still ships, the minimum PG version each native idiom needs, and the resulting decision to **pivot on PostgreSQL 14+** (validated 14 → 18). | +| [change-capture-tradeoff.md](change-capture-tradeoff.md) | The canonical **triggers vs logical-decoding** trade-off for copy-and-swap — overhead, failover survival, WAL risk, and whether either lets us drop the checksum/checkpoint (answer: keep the checksum; triggers simplify but don't remove the checkpoint). Any doc proposing logical decoding as the default points here. | +| [invariants.md](invariants.md) | The canonical **invariant registry** — testable runtime MUST-statements (correctness, locking, state/resume, refusals, orchestration), each with its enforcement point and source. Mined from this doc set plus [Spirit](https://github.com/block/spirit)'s stated safety invariants and [SchemaBot](https://github.com/block/schemabot)'s control-plane discipline; the build plan's phases carry per-invariant test obligations. | +| [tcb-model.md](tcb-model.md) | The **TCB model** — the trusted-computing-base partition of the engine: which components are the small trusted core that enforces the invariant registry vs the untrusted periphery, the never-trust-callers rule, domain types that make illegal states unrepresentable, the in-TCB engineering rules (from TigerBeetle TIGER_STYLE, s2n-tls, qmail, bitcoin-core), the verification ladder, and the per-side AI-assisted development policy. | +| [schemabot-integration.md](schemabot-integration.md) | The **single home for orchestrator integration** — how SchemaBot (the reference orchestrator) drives the engine: the pluggable-engine overview, the verb mappings, the concrete adapter contract, and the design constraints (OC-* invariants) the integration imposes on the core. | + +## TL;DR recommendation + +Build a Go tool (`pg-sprite`, working name) as a **decoupled planner → router → executor** +engine — **not** a one-to-one Spirit port. The planner decides *what* changes, the router +decides *which strategy*, and interchangeable executors (`native`, **copy-and-swap**, +**expand/contract**) decide *how*. We **derive design philosophies from several tools** — +Spirit (the copy-and-swap lifecycle and operator model), pg_osc (the shadow-table + trigger +fallback shape), and pgroll (the **expand/contract executor**) — rather than copying any one of +them; pg_repack informs the repack path. The +philosophy we adopt: *safety over speed*, *decisions not options* (sensible defaults over +config knobs), a *mandatory checksum correctness gate* before cutover, *dynamic time-based +chunking*, and *checkpoint/resume*. + +1. **Classify first — optimistically, then fully.** The + [classifier](high-level-design.md#two-ways-to-classify-optimistic-vs-full) is the front + door, and we ship it in two forms. **Optimistic classification** (build first, minimal + parsing — a statement-type gate + table-size guard, no schema model) + simply *attempts* the change under a tight `lock_timeout` + `statement_timeout`; if it + completes it was effectively instant/in-place, and if it can't it is cancelled and treated as + a rewrite. This is the analog of Spirit's "attempt INSTANT/INPLACE first" — adapted to a + database with no instant-or-error assertion. **Full classification** (parse-based) comes next + and *predicts* the path up front (`CREATE INDEX CONCURRENTLY`, `ADD ... NOT VALID` + + `VALIDATE`, PG11+ fast default, `ADD PK USING INDEX`, binary-coercible type change), powering + dry-run, advisory, and the declarative diff. +2. **Otherwise copy — refuse honestly until the engine exists.** For genuine table rewrites + (`ALTER COLUMN TYPE` general, volatile-default `ADD COLUMN`, `STORED` generated column, + repack), the **near-term** stance is a clear **refusal with the classification and reason** — + no delegation to external copy tools. The **longer-term** path is our own **log-based, + checksum-gated, resumable copy-and-swap** (shadow table + chunked parallel copy + CDC + catch-up + checksum + atomic transactional cutover) that lifts those refusals. We have the + runway for this because PostgreSQL does far more changes as native instant operations than + MySQL, so refusing the rewrite cases still leaves the tool useful for the majority of changes + from day one. +3. **CDC via a change-capture abstraction** with **logical decoding** as the primary + implementation (the differentiator vs pg_osc) and a **trigger-based** fallback for + environments that cannot enable `rds.logical_replication` or can't accept slot loss on + failover. This default is cluster-dependent, not absolute — see the + [change-capture trade-off](change-capture-tradeoff.md). +4. **Two front-ends, one pipeline — build declarative first.** *Declarative* (`diff`/`fmt`) + lets the user submit a desired `CREATE TABLE` and derives the `ALTER` by diffing against the + live schema (the analog of Spirit's declarative workflow). Build this first; the *imperative* + (`--alter`) path is then a trivial add-on — the **same** classify → native-or-copy pipeline + with the diff step skipped (the user's `ALTER` goes straight into the classifier). + +See [high-level-design.md](high-level-design.md) for the conceptual architecture, then +[low-level-design.md](low-level-design.md) for the package/interface detail and the open +decisions. + diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..2e01c43 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,99 @@ +# Architecture + +The one-screen map of the codebase: what the layers are, where each responsibility lives, and +what exists today versus what each build phase adds. For the design rationale behind this shape +start with [high-level-design.md](high-level-design.md); for interfaces and lifecycle internals +see [low-level-design.md](low-level-design.md). + +## The three layers + +pg-sprite is a decoupled **planner → router → executor** engine. The planner decides *what* +changes, the router decides *which strategy*, interchangeable executors decide *how*: + +``` + user: --alter "..." OR --desired schema.sql + │ + ╭──────────▼───────────╮ + │ CLI: migrate · diff ·│ + │ fmt · lint · status │ + ╰──────────┬───────────╯ + ▼ + ╭───────────────╮ shared front-end: + │ PLANNER │ parse · introspect · + │ (classify) │ diff · classify · lint + ╰───────┬───────╯ + ▼ + ╭───────────────╮ + │ ROUTER │ policy + cluster facts + ╰───────┬───────╯ + ╭────────────────┼────────────────┬───────────────╮ + ▼ ▼ ▼ ▼ + native DDL copy-and-swap expand/contract refuse / + CONCURRENTLY (transparent) (reversible, manual + NOT VALID … later) + ╰────────────────┴────────────────╯ + │ cross-cutting: connection mgmt, + │ lock bounding, Aurora-aware throttling + ▼ + ╭─────────────────────╮ + │ Aurora PostgreSQL │ + ╰─────────────────────╯ +``` + +The planner's verdicts are **requests, not permissions** — executors re-verify their own +preconditions. Which components are safety-critical (and the stricter rules inside that +boundary) is defined in [../SAFETY.md](../SAFETY.md). + +## Package map + +| Package | Role | Status | +| --- | --- | --- | +| `cmd/pg-sprite` | CLI entry point (Kong): `migrate` · `diff` · `fmt` · `lint` · `status` | exists (stubs) | +| `internal/cli` | Command tree and flag handling | exists (stubs) | +| `internal/testutil` | Test harness: containerized PostgreSQL, throwaway schemas | exists | +| `pkg/dbconn` | Pool with bounded session timeouts, retries, RDS/Aurora auto-TLS (embedded CA bundle), terminate-blockers; advisory-lock mutual exclusion lands here | exists | +| `pkg/statement` | `pg_query_go` parsing + classification (never hand-parse SQL) | Phase 1–2 | +| `pkg/preflight` | Precondition verification and refusals before any write | Phase 1–2 | +| `pkg/planner` / `pkg/schemadiff` / `pkg/lint` | Shared front-end: introspect, declarative diff, classify, lint | Phase 2 | +| `pkg/executor` | The `Executor` contract (`Plan`/`Execute`/`Status`/`Abort`) + native executor | Phase 2–3 | +| `pkg/table` | PK-range chunkers (single-column fast path, composite), dynamic time-based sizing | Phase 4 | +| `pkg/copier` | Parallel chunked copy into the shadow table (never overwrites) | Phase 4 | +| `pkg/checksum` | The mandatory correctness gate; continuous checker; repair primitive | Phase 5 | +| `pkg/decode` | Logical-decoding change capture, LSN accounting, slot lifecycle | Phase 6, 8 | +| `pkg/applier` | Change apply onto the shadow (always wins), buffer/dedup, flush scheduling | Phase 6 | +| `pkg/migration` | Orchestrator: lifecycle, cutover swap + fidelity gate, checkpoint/resume | Phase 7–8 | +| `pkg/checkpoint` | Durable single-row resume state | Phase 8 | +| `pkg/throttler` | Aurora reader-lag / slot-lag / WAL throttling | Phase 8 | + +## The copy-and-swap lifecycle + +When the router picks the heavy path: + +``` + create shadow table ─▶ start change capture ─▶ bulk-copy existing rows + with the new schema (logical decoding, in parallel chunks + off the WAL) │ + ▼ + cut over ◀── CHECKSUM GATE ◀── drain the captured-change backlog + (brief ACCESS (must prove onto the shadow + EXCLUSIVE swap, shadow == source + bounded + retried) before cutover) +``` + +The interleaving rules that make the copier and applier converge — and the invariant IDs every +component must uphold — are registered in [invariants.md](invariants.md); the trust boundary +and domain-type design are in [tcb-model.md](tcb-model.md). + +## Where to read more + +- [high-level-design.md](high-level-design.md) — the conceptual design: why one planner and + many executors, when each pattern is chosen, advisory mode. +- [low-level-design.md](low-level-design.md) — interfaces, lifecycle internals, coverage + matrix, open decisions. +- [design-principles.md](design-principles.md) — the principles everything traces back to. +- [invariants.md](invariants.md) — the testable MUST-statements, with per-phase test + obligations. +- [tcb-model.md](tcb-model.md) — the safety-critical-core partition in depth. +- [postgres-online-ddl-reference.md](postgres-online-ddl-reference.md) — per-operation lock / + rewrite behaviour, the classifier's ground truth. +- [../AGENTS.md](../AGENTS.md) and [../SAFETY.md](../SAFETY.md) — how to work in this repo. diff --git a/docs/change-capture-tradeoff.md b/docs/change-capture-tradeoff.md new file mode 100644 index 0000000..8e218f3 --- /dev/null +++ b/docs/change-capture-tradeoff.md @@ -0,0 +1,96 @@ +# Change-capture trade-off: triggers vs logical decoding (copy-and-swap) + +Both approaches build the **same** copy-and-swap migration (shadow table → chunked copy → +catch-up → checksum → atomic swap). They differ only in **how concurrent writes are captured** +during the copy, and that single choice cascades into very different operational properties. This +doc is the canonical comparison; **any doc that proposes logical-decoding copy-and-swap as the +default must point here**, because the right default is cluster-dependent, not absolute. + +> TL;DR: **logical decoding is the default** (near-zero source overhead) and **triggers are the +> robustness fallback** (survive failover, work anywhere). Neither lets us drop the **mandatory +> checksum**; triggers do let us drop the fragile replication-slot checkpoint, but not the +> copy-watermark checkpoint. + +## Table of contents + +- [The two approaches in one line each](#the-two-approaches-in-one-line-each) +- [Side-by-side comparison](#side-by-side-comparison) +- [Does either approach let us drop the checksum or the checkpoint?](#does-either-approach-let-us-drop-the-checksum-or-the-checkpoint) +- [When to default to each](#when-to-default-to-each) +- [Decision: primary + fallback, behind one interface](#decision-primary--fallback-behind-one-interface) + +## The two approaches in one line each + +- **Logical decoding (log-based).** Read committed changes off the WAL via a replication slot — + no code in the write path. The faithful analog of Spirit/gh-ost reading the MySQL binlog. This + is the **differentiator**: no PostgreSQL OSS OSC tool does this today (they all use triggers). +- **Triggers (synchronous capture).** Put `AFTER INSERT/UPDATE/DELETE` triggers on the source so + every write also records the change (directly into the shadow, or into a durable queue table + drained by the applier). The approach pg_osc and pt-online-schema-change use. + +## Side-by-side comparison + +| Dimension | Logical decoding (default) | Triggers (fallback) | +| --- | --- | --- | +| **Source write overhead** | ✅ ~none — capture is off the WAL, outside the write txn | ❌ write amplification — every write does extra work *inside* the txn, plus lock/deadlock risk on hot tables | +| **Prerequisites** | `rds.logical_replication=1` (**reboot**), `rds_replication` role, a replication-protocol connection (no RDS Proxy), `REPLICA IDENTITY` | none — ordinary `TRIGGER` privilege; works on any cluster, no reboot, no params | +| **Survives Aurora failover?** | ⚠️ **no, not guaranteed** — the slot lives on the writer and Aurora doesn't sync it; failover can lose it (see slot loss on failover) | ✅ **yes** — the trigger + queue/shadow are ordinary data, replicated by Aurora storage | +| **WAL / disk-retention risk** | ❌ an abandoned/slow slot pins WAL and can fill the volume | ✅ none (queue-table bloat is a normal, vacuumable concern) | +| **Capture completeness** | ⚠️ wrinkles: unchanged-TOAST omitted unless `REPLICA IDENTITY FULL`; generated cols arrive NULL; DDL not decoded (must lock out concurrent DDL) | ✅ trigger sees the **full new row** synchronously — no TOAST/generated-col gaps | +| **Snapshot ↔ position coordination** | ❌ must seed the copy from the slot's exported snapshot and replay strictly from that LSN | ✅ sidestepped — trigger captures from creation; copy + capture reconcile | +| **Throughput of catch-up** | commit-ordered, effectively serial (mitigated by PG14 streaming / PG16 parallel apply — see [postgresql-version-support](postgresql-version-support.md#the-version-we-pivot-on)) | bounded by trigger/queue apply; also serial-ish, but no slot/LSN machinery | +| **Pause / resume of capture** | ✅ easy — just stop/start consuming the slot | ⚠️ harder — triggers fire regardless; a queue-table design can defer apply, direct-to-shadow cannot | +| **Cutover lock profile** | brief `ACCESS EXCLUSIVE` swap (same for both) | brief swap **plus** trigger creation/drop takes a momentary strong lock | +| **Version floor** | PG 14 for sane large-txn behaviour (our floor anyway) | works far back (pg_osc reaches 9.6) | +| **Net** | low steady-state cost, higher operational complexity + failover fragility | high steady-state cost, much simpler failure model | + +## Does either approach let us drop the checksum or the checkpoint? + +This is the crux, and the honest answer is **no for the checksum, partly yes for the checkpoint**. + +**Checksum stays mandatory either way.** +- It is a **mechanism-independent** gate: copy correctness (chunk boundaries, type coercion, + collation, generated columns) has nothing to do with how changes are captured. +- Triggers remove the *logical-decoding* failure modes (TOAST omission, DDL desync, slot-gap on + failover) but add **their own**: the classic trigger-ordering race where a `DELETE`/`UPDATE` + trigger fires *before* that row has been copied into the shadow, silently diverging the two + tables. pt-online-schema-change has hit exactly this in production. +- So both mechanisms can produce a diverged shadow by different routes. The checksum is the single + place we *prove* equality before the swap — it is non-negotiable in either world. This is the + [mandatory-checksum design principle](design-principles.md), unchanged. + +**Checkpoint/resume is simplified by triggers, not eliminated.** +- A copy-and-swap has **two** kinds of durable progress: the **copied-PK watermark** (how far the + bulk copy got) and the **change-capture position**. +- With **logical decoding**, the capture position is the slot's `confirmed_flush_lsn` — *fragile* + state that must be persisted and that an Aurora failover can destroy, forcing a checksum-repair + reconciliation (see [low-level-design's failover analysis](low-level-design.md#failover-during-migration-what-survives-and-what-doesnt)). +- With **triggers**, the capture is *durable by construction*: a queue table's drain position is + ordinary data, and a direct-to-shadow trigger keeps the shadow continuously live. There is no + slot LSN to checkpoint and **no slot-loss-on-failover restart risk** — the migration simply + resumes. That removes the scariest part of the checkpoint design. +- **But** the **copy-watermark checkpoint is still needed** in both cases: a multi-hour copy of a + large table must resume from where it stopped rather than re-copy from row 0. + +So triggers buy a dramatically simpler *failure model* (no slot, no LSN coordination, survives +failover), at the cost of source write overhead — and they do **not** buy you out of the checksum. + +## When to default to each + +| Situation | Prefer | +| --- | --- | +| Hot, write-heavy table where trigger amplification is unacceptable; logical replication is enabled | **Logical decoding** | +| Cluster where `rds.logical_replication` can't be enabled (no reboot window) or the role/connection constraints can't be met | **Triggers** | +| Multi-day migration on a cluster with realistic failover/maintenance exposure | **Triggers** (failover-safe) — or logical decoding *with* a tested checksum-repair resume | +| Short migration on a quiet table | either; logical decoding has less footprint | +| Sharded fleet running N migrations at once (slot/WAL pressure per cluster) | weigh per-cluster slot budget — see sharded-aurora-postgresql | + +## Decision: primary + fallback, behind one interface + +Define a **single change-capture abstraction** with two implementations selected per migration: +**logical decoding as the default** (the low-overhead differentiator) and **triggers as a +first-class fallback** (the failover-safe, runs-anywhere path) — not a vestige. The planner picks +based on cluster facts (is logical replication enabled? failover exposure? table write rate?), and +the rest of the pipeline (chunked copy, **mandatory checksum**, copy-watermark checkpoint, atomic +cutover) is **identical** regardless of which capture is chosen. See +[low-level-design open decision #1](low-level-design.md#1-cdc-mechanism--logical-decoding-vs-triggers-vs-both). diff --git a/docs/design-principles.md b/docs/design-principles.md new file mode 100644 index 0000000..92062dc --- /dev/null +++ b/docs/design-principles.md @@ -0,0 +1,180 @@ +# Design principles + +The canonical list of principles that govern the engine. They are distilled from Spirit's +philosophy (see spirit-architecture-notes.md) and +adapted for Aurora PostgreSQL. Everything in [low-level-design.md](low-level-design.md) and +the phased build plan should be traceable back to one of these. + +> Principles say what we value; the **testable runtime MUST-statements** that follow from them — +> each with an enforcement point, a source, and a per-phase test obligation — live in the +> [invariant registry](invariants.md). + +## Table of contents + +- [Guiding philosophy (derived from proven OSS tools)](#guiding-philosophy-derived-from-proven-oss-tools) +- [Correctness and safety](#correctness-and-safety) +- [Classify-first (leverage native PostgreSQL)](#classify-first-leverage-native-postgresql) +- [Declarative, review-first workflow](#declarative-review-first-workflow) +- [PostgreSQL / Aurora-specific](#postgresql--aurora-specific) +- [Code and dependency maxims](#code-and-dependency-maxims) +- [Process and delivery](#process-and-delivery) + +## Guiding philosophy (derived from proven OSS tools) + +- **Safety over speed.** The consequences of a bug are data loss in production systems, so a + feature must be *safe* and *safe-by-default* before it is fast. Speed work (parallelism, + watermark optimization) only lands on top of a proven-correct path. +- **Decisions, not options.** Prefer sensible defaults over configuration knobs; non-default + options are poorly tested and a source of surprise. The engine should make the right call + for the user rather than expose another flag. +- **Mirror the design philosophy, not the package layout.** We port *how Spirit thinks* (the + lifecycle, the gates, the refusals), not its directory structure — the code follows whatever + is idiomatic for a Postgres + logical-decoding tool. +- **Operator mental-model parity with Spirit.** Teams that already operate Spirit for Aurora MySQL carry its operator model. This engine deliberately mirrors Spirit's *operator surface* — the same lifecycle + stages, the same verbs (dry-run, defer-cutover, pause/resume, throttle, abort), the same + refusal semantics, and the same status/observability shape — so an operator carries **one + mental model across both engines**. Runbooks, incident response, and intuition transfer; the + PostgreSQL-specific machinery (logical slots, `CONCURRENTLY`, `NOT VALID`) is encoded by the + engine, not relearned by the operator. This parity is itself a reason to build rather than + adopt a tool with a different operational shape (see + tool-pg_osc.md). + +## Correctness and safety + +- **The checksum is a mandatory, non-skippable cutover gate.** A migration that cannot prove + the shadow table equals the source **must refuse to cut over**. Correctness is never traded + for completion. +- **Bound every exclusive lock.** Every `ACCESS EXCLUSIVE` (only the cutover swap in the happy + path) and every catalog-flip runs under `lock_timeout` + bounded retry/backoff, so the + engine never sits at the head of the lock queue and amplifies one slow transaction into an + outage (see 12-mysql-vs-postgresql.md § Why DDL is dangerous: the lock queue). +- **Refuse the unsafe rather than guess.** Lossy conversions, PK changes, FK/trigger tables, + and ambiguous renames are rejected up front with a clear reason — never silently attempted + (see [low-level-design's requirements](low-level-design.md#table-requirements-and-unsupported-operations-aurora-postgresql-analogs)). +- **Fail safe, leave no mess.** On success, failure, *and* crash, the engine cleans up its + artifacts — most importantly the replication slot, which otherwise pins WAL and can fill the + Aurora volume. +- **Preflight before you touch anything.** Validate every prerequisite *before* the first + write and fail fast with a clear, actionable reason — never abort mid-migration on something + knowable up front: `rds.logical_replication` / `wal_level = logical`, slot-creation privilege + (`rds_replication`), a usable primary key and `REPLICA IDENTITY`, + `max_replication_slots` / `max_wal_senders` headroom, and enough free disk for the shadow copy + (copy-and-swap roughly **doubles** the table's storage). See + [low-level-design's preconditions](low-level-design.md#configuration--privilege-preconditions). +- **Long migrations must be resumable.** A multi-hour/-day copy must survive process restarts: + persist a durable checkpoint (`{copied-PK watermark, slot name, confirmed LSN}`) and resume + with minimal lost work rather than restarting from zero — bounded by slot/WAL retention and, + on Aurora Global Database, by region failover (see + [low-level-design § design decisions](low-level-design.md#design-decisions-inherited-from-spirit-safety-over-speed)). +- **Bound the work per step — chunk by target time, not row count.** Size each copy/checksum + chunk to a target duration (~500ms) and adjust it dynamically, so no single statement holds + resources or drives replication lag unpredictably as row width varies. Fixed row-count + batches are not used. +- **Reversibility is a property of the pattern, never a fabricated inverse.** copy-and-swap is + transparent but **not reversible after cutover** — undoing it is a *new forward migration*, + not an "undo," because the old physical table is gone. Only the expand/contract (pgroll) + pattern offers true rollback, and only **within the rollout window** (before `complete`, + while both schema versions are live). The engine therefore exposes a `revert` only where the + chosen executor genuinely supports it and **refuses otherwise** — it never guesses an inverse + `ALTER` that could lose data. Forward-fix is the default for the copy-and-swap path. See the + [execution patterns](high-level-design.md#the-execution-patterns-and-when-each-is-chosen) + and the orchestrator [revert mapping](schemabot-integration.md#verb-mapping-conceptual). + +## Classify-first (leverage native PostgreSQL) + +- **Classify before copy.** Parse the change, decide *native-safe sequence* vs *copy-and-swap* + vs *refuse*, and take the cheapest correct path — the direct analog of Spirit attempting + INSTANT/INPLACE before falling back to a copy. +- **Classify-first means users get the safe PostgreSQL idiom automatically, without knowing + which intricacy applies.** A user who asks for an index, a constraint, or a fast-default + column gets `CREATE INDEX CONCURRENTLY`, `ADD … NOT VALID` then `VALIDATE`, + `ADD PRIMARY KEY USING INDEX`, or the PG11+ fast default — applied correctly and safely — + without having to know that idiom exists. The engine encodes the expertise so the user + doesn't have to. +- **Copy-and-swap is the last resort, not the default.** A full shadow-copy is reserved for + changes that genuinely have no native online path (general `ALTER COLUMN TYPE`, + volatile-default `ADD COLUMN`, `STORED` generated columns, repack). "Needs copy-and-swap? = + No" never means "don't use the engine" — it means the engine runs the native idiom for you. +- **Advise, never silently run the dangerous literal; force is loud and explicit.** When a + submitted statement is risky as written but has a safer native equivalent (`CREATE INDEX` → + `CREATE INDEX CONCURRENTLY`, etc.), the engine surfaces the recommendation and applies the + safe idiom — it does **not** execute the risky literal behind the user's back. Running a + statement exactly as submitted requires an explicit `--force`, gated by prominent DANGER/CAUTION + output, a typed acknowledgement (not a bare `-y`), and an audit log entry. Force is an escape + hatch, not a convenience (see + [high-level-design's advisory mode](high-level-design.md#advisory-mode-suggest-the-safe-rewrite-dont-silently-run-the-risky-one)). +- **One planner, pluggable execution backends — choose the right pattern per migration.** The + shared front-end (classify + declarative diff) decides *what* changes and *which strategy* + fits; interchangeable executors decide *how*: native DDL, log-based copy-and-swap, and + (later) expand/contract via pgroll for reversible breaking changes. We don't pick one pattern + globally — the planner routes each migration to the executor whose tradeoffs fit, behind a + single `Executor` interface (see + [low-level-design's architecture](low-level-design.md#architecture-decoupled-planner-router-and-executors)). + +## Declarative, review-first workflow + +- **One pipeline, two front-ends — declarative first.** Build the declarative desired-state + `diff` as the primary front-end; the imperative `--alter` path is then a thin add-on — the + **same** classify → native-or-copy pipeline with the diff step skipped (the user's `ALTER` + goes straight into the classifier). Both feed the identical executor. +- **Dry-run first.** `diff`/`--dry-run` prints the exact statements and their classification + (native vs copy-and-swap) **without executing** — the natural review and CI hook. +- **Destructive diffs are gated; renames are never guessed.** Dropping a column/constraint + requires explicit confirmation; a missing-plus-new column pair is a drop+add unless a rename + is stated explicitly. Intent is required, not inferred. + +## PostgreSQL / Aurora-specific + +- **Log-based CDC, not triggers — but cluster-dependently so.** Capture concurrent writes via + **logical decoding** (a replication slot), which adds near-zero synchronous overhead to the + source — the key differentiator versus trigger-based tools like pg_osc. A trigger-based path is a + **first-class fallback** (it survives failover and runs anywhere), not a vestige; the default is + chosen per cluster, not absolutely — see the + [change-capture trade-off](change-capture-tradeoff.md). Note neither mechanism removes the + mandatory checksum. +- **Treat the replication slot as a managed, dangerous resource.** Temporary slot + a + name-prefixed reaper + a hard slot-lag ceiling; never leave an orphaned slot retaining WAL. +- **Checksums must be deterministic across PostgreSQL quirks.** TOAST (including the + unchanged-TOAST-on-UPDATE case), `STORED` generated columns, and non-deterministic + collations must produce identical checksums on source and shadow, or the gate is meaningless. +- **Be Aurora-aware, not Aurora-only.** Throttle on Aurora reader lag, replication-slot lag, + and WAL generation; use the RDS/Aurora CA bundle and the writer/reader split — while the + core remains plain-PostgreSQL correct. + +## Code and dependency maxims + +Code-level rules of thumb. Each earns its place by having a pg-sprite-specific consequence — +this is not a proverb collection. The TCB-scoped versions (with the dependency rubric and the +enforcement mechanics) live in [tcb-model](tcb-model.md); the repo-process versions land in the repo's [`AGENTS.md`](../AGENTS.md). + +- **"A little copying is better than a little dependency"** ([Go proverbs](https://go-proverbs.github.io/)) — + small mechanics (retry/backoff, CA loading, keepalives, tiny helpers) are hand-written or + copied with attribution, never imported; and specifically, **pg-sprite never imports + `block/spirit` as a module** — we port ideas with citations, not code. The proverb cuts the + *other* way for load-bearing expertise: the parser and the wire protocol are taken as pinned + dependencies, because a hand-rolled substitute there is the unsafe choice. Full rubric: + [tcb-model § dependencies](tcb-model.md#dependencies-inside-the-tcb-become-part-of-the-tcb). +- **Expose the smallest interface that does the job.** The `Executor` contract is + `Plan`/`Execute`/`Status`/`Abort` and nothing more; packages export domain types and their + validating constructors, not internals — the narrow interface is what keeps the + [TCB boundary](tcb-model.md#the-boundary) small enough to audit. +- **Clear is better than clever.** No clever SQL, no dense compound predicates, no control flow + that requires reconstructing the state machine in your head. + During an incident this code is read under stress; readability is a safety property here, not + taste — it is priority #2 in the [TCB ordering](tcb-model.md#rules-inside-the-tcb), above + ease of use and performance. +- **Minimize state; derive rather than store.** If a value can be recomputed from the database + or the checkpoint, don't persist it; small state is what makes an incident reasoned about by + hand. The checkpoint carries the minimum resumable set and nothing else + ([invariants ST-1](invariants.md#st-1--the-checkpoint-is-a-single-row-written-atomically)). + +## Process and delivery + +- **Test-first, against a real database.** Write the failing test before the implementation; + core logic is validated by integration tests against a real Postgres, not mocks — and a + phase is "done" only when the migration result is validated end-to-end (row counts, + checksum, lock behaviour). +- **Each increment is independently useful.** The build is sequenced so that early phases + (classify/print, then native-path execution) ship value on their own, and the highest-risk + components (CDC, cutover) are added last on a proven foundation (see + build-plan.md). diff --git a/docs/high-level-design.md b/docs/high-level-design.md new file mode 100644 index 0000000..900716f --- /dev/null +++ b/docs/high-level-design.md @@ -0,0 +1,315 @@ +# High-level design: a decoupled schema-migration engine for Aurora PostgreSQL + +The conceptual design. It frames the problem, the architecture philosophy, the three layers and +their responsibilities, the execution patterns and when each is chosen, and the coverage at a +glance — **without** package names, interface signatures, or library choices. Those, plus the +full coverage matrix and the open decisions, live in the +**[low-level design](low-level-design.md)**, which is what you read when designing the +interfaces and packages. + +Working name: **`pg-sprite`**. It is a **separate, purpose-built PostgreSQL tool**, not "Spirit with +PostgreSQL support" — Spirit stays MySQL-only (too many MySQL-isms to retrofit cleanly). pg-sprite +instead **derives design practices** from several proven tools — Spirit (MySQL), pg-osc, +pg_repack, and pgroll — and adapts them to Aurora PostgreSQL. It builds on the +reasons to build. + +## Table of contents + +- [The problem in one paragraph](#the-problem-in-one-paragraph) +- [The core idea: one planner, many executors](#the-core-idea-one-planner-many-executors) +- [The three layers](#the-three-layers) +- [Two ways to classify: optimistic vs full](#two-ways-to-classify-optimistic-vs-full) +- [Architecture at a glance](#architecture-at-a-glance) +- [The execution patterns (and when each is chosen)](#the-execution-patterns-and-when-each-is-chosen) +- [Two front-ends: declarative and imperative](#two-front-ends-declarative-and-imperative) +- [Advisory mode: suggest the safe rewrite, don't silently run the risky one](#advisory-mode-suggest-the-safe-rewrite-dont-silently-run-the-risky-one) +- [The copy-and-swap path, conceptually](#the-copy-and-swap-path-conceptually) +- [What it covers (and what it deliberately does not)](#what-it-covers-and-what-it-deliberately-does-not) +- [Key design choices](#key-design-choices) +- [Where to go next](#where-to-go-next) + +## The problem in one paragraph + +A schema change on a large, busy Aurora PostgreSQL table is dangerous for two different reasons: +some changes take an `ACCESS EXCLUSIVE` lock that, behind a long transaction, can stall the +whole application (the lock queue); +and some changes **rewrite the entire table**, which a single `ALTER` cannot do online. The +engine's job is to take the change the user wants and run it **safely** — using the cheap native +PostgreSQL idiom when one exists, and a controlled table-copy when one doesn't — without the user +having to know which case they are in. + +## The core idea: one planner, many executors + +The engine is **not** a single-purpose copy-and-swap tool. It is deliberately split so that +copy-and-swap is just *one* of several interchangeable strategies. The same front-end +understands every change; a routing decision picks the right strategy per change: + +> **Decide *what* changes once; decide *how* per migration.** A shared planner classifies every +> operation; a router picks an executor; interchangeable executors carry it out. New strategies +> can be added without touching the planner. + +This is the answer to *"why build copy-and-swap when pgroll already wins some cases?"* — we do +not choose one pattern globally. We route each migration to the pattern whose tradeoffs fit. + +## The three layers + +```diagram +╭───────────────────────────────────────────────────────────--──╮ +│ PLANNER decides WHAT changes │ +│ parse the change (imperative ALTER or declarative diff), │ +│ introspect the live schema, classify each operation as │ +│ native-safe · needs-rewrite · refuse, lint for safety │ +╰───────────────────────────────┬─────────────────────────────--╯ + │ a classified Plan +╭───────────────────────────────▼─────────────────────────────--╮ +│ ROUTER decides WHICH strategy │ +│ given policy + cluster facts (reversibility needed? app │ +│ schema-version aware? logical replication available? table │ +│ shape?) assign each change to an executor — the one place │ +│ migration policy lives │ +╰───────────────────────────────┬─────────────────────────────--╯ + │ per-change strategy +╭───────────────────────────────▼─────────────────────────────--╮ +│ EXECUTORS decide HOW (interchangeable) │ +│ native DDL · copy-and-swap · expand/contract · refuse │ +╰─────────────────────────────────────────────────────────────--╯ +``` + +- **Planner** — decides *what* must change. It has no idea how any executor works. +- **Router** — decides *which* strategy, from policy and cluster facts. The single home for + migration policy. +- **Executors** — decide *how*, behind one common contract, so a new strategy slots in without + reworking the front-end. + +The classifier, declarative diff, linting, dry-run, and status reporting are written **once** and +shared by every executor. + +## Two ways to classify: optimistic vs full + +The planner's classifier — the **front door** that decides each change's path — has two +implementations, and we ship them in order: + +- **Optimistic classification (build first, minimal parsing).** No schema model and no + classification logic — just a cheap **statement-type gate** (`ALTER TABLE` only — the + statements the instant path can help; index/constraint statements are refused with the + safe-idiom pointer rather than run as risky literals) and a **table-size guard**, then **attempt the + change directly** under a tight lock/time budget. If it completes within budget it was + effectively an instant / in-place change and we are done; if it can't (the lock isn't granted + quickly, or the work would exceed the budget) we **cancel and treat it as needing a rewrite**. + This mirrors Spirit's original front door, which simply tried `ALGORITHM=INSTANT` and handled + the errors (then tried known-safe `INPLACE` options). It ships an end-to-end useful tool with + almost no parsing logic. +- **Classification (full, parse-based).** Parse the statement and introspect the live schema to + **predict the path up front** — native-safe, copy-and-swap, or refuse — without trial + execution. This is what powers dry-run, advisory suggestions, and the declarative diff, and it + removes the wasted/aborted attempts that optimistic classification can incur. + +> **PostgreSQL caveat.** Unlike MySQL's `ALGORITHM=INSTANT`, PostgreSQL has **no assertion** that +> forces a change to be instant-or-error — a rewrite attempt acquires `ACCESS EXCLUSIVE` and does +> real work until cancelled, **blocking all reads and writes for the whole budget window**, so a +> bounded attempt is not a free probe. Optimistic classification therefore bounds the attempt +> with a tight `lock_timeout` **and** `statement_timeout`, and **skips the attempt entirely above +> a table-size threshold** (`pg_class.relpages`), classifying the change as a rewrite — **refused +> with the reason** until the in-house copy engine lands (see the build plan). + +## Architecture at a glance + +```diagram + user: --alter "..." OR --desired schema.sql + │ + ╭──────────▼──────────-╮ + │ CLI: migrate · diff ·│ + │ fmt · lint · status │ + ╰──────────┬──────────-╯ + ▼ + ╭───────────────╮ shared front-end: + │ PLANNER │ parse · introspect · + │ (classify) │ diff · classify · lint + ╰───────┬───────╯ + ▼ + ╭───────────────╮ + │ ROUTER │ policy + cluster facts + ╰───────┬───────╯ + ╭────────────────┼────────────────┬───────────────╮ + ▼ ▼ ▼ ▼ + native DDL copy-and-swap expand/contract refuse / + CONCURRENTLY (Pattern A, via pgroll manual + NOT VALID … transparent) (Pattern B, + fast default reversible, later) + ╰────────────────┴────────────────╯ + │ cross-cutting: connection mgmt, + │ lock bounding, Aurora-aware throttling + ▼ + ╭─────────────────────╮ + │ Aurora PostgreSQL │ writer (DDL/copy/cutover, + │ writer + readers │ logical slot) · readers (lag signal) + ╰─────────────────────╯ +``` + +The package-level version of this diagram (with the concrete components for each box) is in the +[low-level design](low-level-design.md#proposed-architecture-end-to-end). + +## The execution patterns (and when each is chosen) + +| Pattern | When the router picks it | Key property | Tradeoff | +| --- | --- | --- | --- | +| **native DDL** | The change has a safe online PostgreSQL idiom (most changes) | Cheapest correct path; no copy | None beyond bounding the brief lock | +| **copy-and-swap** (Pattern A) | A genuine table rewrite with **no** native online path (`int→bigint`, repack, volatile-default add) | **Transparent** — same table name, no app changes | Heaviest path; needs logical replication for the low-overhead mode | +| **expand/contract** via pgroll (Pattern B, later) | A **breaking** change where instant reversibility / two live schema versions matter | **Reversible** within the rollout window | Requires the **app to be schema-version aware** | +| **refuse** | Unsafe or unsupported (lossy conversion, PK change, FK/trigger table in v1) | Fails fast and cheaply | n/a — it is the safe outcome | + +The crucial point: **reversibility and transparency are properties of the pattern, not features +you toggle.** copy-and-swap is transparent but not reversible-by-design; pgroll is reversible but +requires app coordination. You pick one *per migration*. To stay faithful to "decisions, not +options", the router has a **clear default with a narrow, signposted opt-in** (e.g. auto-route, +with an explicit strategy flag only when reversibility is requested) rather than a bare menu. + +## Two front-ends: declarative and imperative + +The engine accepts a change two ways, both feeding the **same** planner pipeline: + +- **Declarative** — the user supplies the **desired end-state** (a checked-in `CREATE TABLE` + `.sql` file); the engine **derives** the `ALTER` by diffing desired vs live, then runs it + through the classify → route → execute path. This is the front-end we build first. +- **Imperative** — the user supplies the `ALTER` directly. It is the **same** pipeline with the + diff step skipped, so once declarative works, adding imperative is trivial. + +We **build declarative first** because it does the harder work (introspect + diff + ordering) +and exercises the full classify → route → execute path; the imperative path then falls out +almost for free, since it just hands the user's statement to the *same* classifier. Schemas can +therefore live as reviewed, version-controlled files and CI can compute "what would change". +Destructive diffs are gated and renames are never guessed. The diff algorithm and its safety +rules are detailed in the +[low-level design](low-level-design.md#declarative-mode-desired-state-schema-diff). + +## Advisory mode: suggest the safe rewrite, don't silently run the risky one + +The classifier doesn't only choose an execution path — it can also act as a **suggestion +engine**. When a submitted statement is risky *as written* but has a safer native equivalent, +the engine's default is to **return the recommendation and stop**, rather than execute the +literal statement: + +```diagram + user: CREATE INDEX idx ON orders (customer_id) + │ + ▼ + ╭────────────────────────────╮ + │ classifier: risky literal? │ + │ safer native idiom exists? │ + ╰─────────────┬──────────────╯ + │ yes + ▼ + ┌──────────────────────────────────────────────────────────-┐ + │ RECOMMENDATION (does NOT execute): │ + │ you asked: CREATE INDEX idx ON orders (customer_id) │ + │ run instead: CREATE INDEX CONCURRENTLY idx ON orders … │ + │ why: a plain CREATE INDEX takes SHARE and blocks writes │ + │ for the whole build; CONCURRENTLY does not. │ + └──────────────────────────────────────────────────────────-┘ + │ apply the recommendation │ insist on the literal + ▼ ▼ + engine runs the safe idiom --force ⇒ DANGER prompt + + (classify-first does the work) explicit approval, then runs as-is +``` + +Examples of what it suggests (the same idioms the classifier already knows): + +| You submit | It recommends | Why | +| --- | --- | --- | +| `CREATE INDEX …` | `CREATE INDEX CONCURRENTLY …` | plain build holds `SHARE`, blocks writes for the whole build | +| `ALTER TABLE … ADD CONSTRAINT … CHECK/FK` | `ADD … NOT VALID` then `VALIDATE CONSTRAINT` | avoids a full-table validation scan under a strong lock | +| `ALTER TABLE … ADD PRIMARY KEY (…)` | build a unique index `CONCURRENTLY`, then `ADD PRIMARY KEY USING INDEX` | avoids a blocking build inside the `ALTER` | +| `DROP INDEX …` | `DROP INDEX CONCURRENTLY …` | plain drop takes `ACCESS EXCLUSIVE` briefly | + +Two principles govern this: + +- **Never silently execute the dangerous literal.** If a safer equivalent exists, the engine + surfaces it rather than running the risky form behind the user's back. This is the + transparent, review-friendly counterpart to *classify-first* — the user still doesn't need to + know the idiom (the engine names it), but nothing dangerous runs unannounced. +- **The force route is loud and explicit.** A `--force` (run-as-submitted) flag exists for the + rare case where the operator genuinely wants the literal statement. It is gated behind + prominent **DANGER / CAUTION** output explaining exactly what will block and for how long, and + an **explicit confirmation** (typed acknowledgement, not a bare `-y`), and the override is + logged. Force is an escape hatch, not a convenience. + +In non-interactive contexts (CI), advisory mode is a natural gate: the engine prints the +recommended rewrites and exits non-zero if a submitted statement would need a riskier path than +the policy allows — see the [low-level design](low-level-design.md#advisory-mode-and-the-force-escape-hatch) +for the surfacing/approval mechanics. + +## The copy-and-swap path, conceptually + +When the router picks copy-and-swap, the lifecycle is: + +```diagram + build a shadow table ─▶ capture concurrent writes ─▶ bulk-copy existing rows + with the new schema (log-based, off the WAL) in parallel chunks + │ + ▼ + cut over ◀── CHECKSUM GATE ◀── drain the captured-change backlog + (brief ACCESS (must prove onto the shadow + EXCLUSIVE swap, shadow == source + bounded + retried) before cutover) +``` + +Two things define this path and distinguish it from existing PostgreSQL tools: + +- **The checksum is a mandatory gate** — the engine refuses to cut over unless the shadow + provably equals the source. +- **Cutover timing is controllable** — the swap can be deferred until an operator signal, with a + continuous re-verification loop while it waits. + +The mechanism (logical decoding, chunking, the transactional swap, checkpoint/resume) and the +MySQL→PostgreSQL primitive mapping are in the low-level design and +mysql-vs-postgresql.md. + +## What it covers (and what it deliberately does not) + +No single tool covers every Aurora PostgreSQL topology, configuration, and schema shape, and +being explicit about the supported matrix is part of the "decisions, not options" philosophy. At +a high level, v1 targets: + +- **Topology:** Aurora PostgreSQL provisioned (writer + readers) as the primary target; RDS + PostgreSQL as a bonus; Serverless v2 with caveats. Not Serverless v1, not Babelfish. +- **Schema shape:** a single table that **has a primary key**, **no foreign keys or triggers on + it**, **no PK change**, and **no lossy conversion** — intentionally close to Spirit's supported + surface. +- **Capture:** logical decoding where logical replication is enabled; a trigger-based fallback + otherwise (which inherits the overheads of pg_osc-style tools). + +The full deployment/precondition/schema matrices, the per-constraint *reasons*, and the +Postgres-specific preconditions (logical replication, slot/role privileges, unchanged-TOAST +handling) are in the +[low-level design](low-level-design.md#coverage-and-limitations-does-this-cover-all-of-aurora-postgresql). + +## Key design choices + +The choices that shape everything else (the full categorized list is in +[design-principles.md](design-principles.md)): + +- **Classify-first.** Take the cheapest correct path; reserve copy-and-swap for genuine + rewrites. Users get the safe PostgreSQL idiom automatically. +- **Safety over speed.** Correctness gates (the mandatory checksum) and bounded locks come + before throughput. +- **Decisions, not options.** Sensible defaults over knobs; a second pattern is a signposted + opt-in, not a menu. +- **Log-based capture, not triggers** (where possible) — near-zero source overhead, the key + differentiator vs pg_osc. The trade is robustness: on Aurora a **writer failover can lose the + logical slot mid-migration**, so the bulk copy resumes but the CDC catch-up may need a + checksum-repair reconciliation (not a full re-copy), and the trigger path is the failover-safe + fallback. The default is cluster-dependent — see the + [change-capture trade-off](change-capture-tradeoff.md) and + [low-level-design's failover analysis](low-level-design.md#failover-during-migration-what-survives-and-what-doesnt). +- **Operator mental-model parity with Spirit** — the same lifecycle and verbs, so + operators carry one mental model across MySQL and PostgreSQL. + + +## Where to go next + +- How an orchestrator drives the engine (verbs, adapter contract, constraints) → + **[schemabot-integration.md](schemabot-integration.md)**. +- The detailed interfaces, package layout, libraries, lifecycle internals, full coverage matrix, + and open decisions → **[low-level-design.md](low-level-design.md)**. +- How Spirit (the inspiration) works → spirit-architecture-notes.md. +- The phased plan to build it → build-plan.md. diff --git a/docs/invariants.md b/docs/invariants.md new file mode 100644 index 0000000..75d4154 --- /dev/null +++ b/docs/invariants.md @@ -0,0 +1,310 @@ +# Engine invariants + +The canonical registry of **runtime invariants** — testable MUST-statements the engine enforces +in code. This is the enforceable-rule companion to +[design-principles.md](design-principles.md): the principles say *what we value* (safety over +speed, decisions not options); this doc says *what must never be false at runtime*, where each +rule is enforced, and where it came from. Every invariant cites its source — this doc set, +[Spirit](https://github.com/block/spirit)'s codebase (which states several of these as explicit +`Safety invariant:` comments), or [SchemaBot](https://github.com/block/schemabot)'s AGENTS.md and +control-plane docs — so the lineage survives the port. + +**How to use this doc during the build:** each invariant carries an ID (`CO-*` correctness, +`LK-*` locking/concurrency, `ST-*` state/resume, `RF-*` refusals, `OC-*` orchestration). The +build-plan phase that implements an invariant must land a test named for it; +the [phase mapping](#build-phase-mapping) is at the end. The code that enforces these invariants +is the engine's **trusted computing base** — the boundary, the domain types that make violating +several of these unrepresentable, and the in-TCB engineering rules live in +[tcb-model.md](tcb-model.md). + +## Table of contents + +- [Correctness (CO)](#correctness-co) +- [Locking and concurrency (LK)](#locking-and-concurrency-lk) +- [State, checkpoint, and resume (ST)](#state-checkpoint-and-resume-st) +- [Refusals and preflight (RF)](#refusals-and-preflight-rf) +- [Orchestration / control-plane (OC)](#orchestration--control-plane-oc) +- [Engineering invariants live in AGENTS.md](#engineering-invariants-live-in-agentsmd) +- [Build-phase mapping](#build-phase-mapping) + +## Correctness (CO) + +### CO-1 — The checksum gate is non-skippable + +A migration that cannot prove shadow == source **must refuse to cut over**. No flag, mode, or +capture mechanism removes the gate; it is also the repair primitive for +[slot-loss reconciliation](low-level-design.md#failover-during-migration-what-survives-and-what-doesnt). +*Enforced:* cutover entry condition. *Source:* [design-principles](design-principles.md#correctness-and-safety), +risks-and-mitigations; Spirit's "never skip it". + +### CO-2 — A persisted checksum watermark describes only chunks verified clean on a fresh read + +Spirit states this as an explicit safety invariant (`pkg/migration/runner.go`): a chunk that +needed a **repair** has not been *verified* — only the recopy succeeded — yet the chunker's +low-watermark advances past every chunk it sees feedback for, including repaired ones. So in any +checksum pass where **any** chunk was repaired, the watermark is not a valid resume point until a +later pass re-checks those chunks clean. The engine must persist an **empty** checksum watermark +whenever the current pass has had repairs, forcing a resumed run to re-verify from the start of +the checksum phase. The same rule applies to the continuous (deferred-cutover) checker: resuming +from a stale watermark after a continuous-checker repair would let a re-run "pass" by verifying +only trailing chunks — silently neutralizing a deliberate divergence abort. +*Enforced:* checkpoint writer (watermark dropped unless **all** active checkers are clean). +*Source:* Spirit `pkg/migration/runner.go` + `pkg/move/runner.go` ("Safety invariant"). + +### CO-3 — Divergence policy is an explicit setting, never inferred + +Whether a confirmed, stable source/shadow divergence **aborts** or **self-heals by recopy** is an +explicit per-mode policy (Spirit's `ContinuousCheckerConfig.DivergenceIsFatal`), not something +inferred from whether a recopier happens to be wired up: + +- **Steady-state migration** (CDC keeping the shadow in sync): divergence is a real bug — + `DivergenceIsFatal = true`, abort the cutover. +- **Reconciliation after slot loss** (the checksum-repair pass): divergence is *expected* — + `DivergenceIsFatal = false`, and a recopier is **mandatory** (self-heal without one is treated + as fatal). +- The two knobs stay decoupled: fatal-divergence aborts even if a recopier is supplied. + +*Enforced:* checker configuration per lifecycle mode. *Source:* Spirit AGENTS.md +(block/spirit#994 policy) — maps directly onto our failover-reconcile design. + +### CO-4 — The copy/apply ordering invariants + +The copier **never overwrites** (`ON CONFLICT (pk) DO NOTHING`); the applier **always +overwrites** (`ON CONFLICT (pk) DO UPDATE` + explicit deletes); captured changes above the +copier's watermark may be **discarded only for a monotonic integer PK**, and must be queued for +composite/non-comparable PKs; a delete for a key inside an in-flight chunk must be re-applied +after that chunk lands. Full statement and the races these resolve: +[low-level-design § copy and apply ordering](low-level-design.md#copy-and-apply-ordering-the-core-correctness-subtlety). +*Enforced:* copier/applier SQL shapes + flush scheduling. *Source:* this doc set (Spirit's +model translated). + +### CO-5 — The change buffer is disjoint and current at flush time + +At every flush, each PK appears **at most once** in the change buffer, holding the **latest** row +image (or a delete marker) — dedup is what makes catch-up convergent rather than linear. After a +mode transition (map ↔ FIFO-queue for non-memory-comparable PKs), **only the active store may +hold entries**: the outgoing store is drained inline at the toggle, so no flush ever has to merge +a stale store. *Enforced:* buffer data structure + the mode-toggle transition. *Source:* Spirit +`pkg/change/subscription_buffered.go` (stated invariant). + +### CO-6 — Unique-secondary-key moves must converge (PostgreSQL-specific gap) + +Spirit applies via `REPLACE INTO`, which **deletes** rows that collide on *any* unique key; a +transiently-deleted row converges because its own event re-inserts it (the buffer-disjointness +guarantee, CO-5). PostgreSQL has no REPLACE: `INSERT … ON CONFLICT (pk) DO UPDATE` targets +**one** conflict arbiter, so a batch that legally moves a unique value between rows (set +`slot_id` NULL on row 1, then `'S'` on row 2, in one source transaction) can **error** on the +secondary unique index instead of converging. The applier must define semantics for this — +order-preserving apply within the batch, per-row retry on unique violation, or delete-then-insert +pairs — and prove convergence under test. The checksum (CO-1) backstops, but the applier must +converge without it. *Enforced:* applier batch semantics (design work, Phase 6). *Source:* Spirit +`pkg/change/README.md` (the REPLACE rationale) — the PG translation in +mysql-vs-postgresql +is incomplete without this. + +### CO-7 — Every statement parses, or it is an error + +All SQL the engine processes must parse with `pg_query_go`. No `strings.Split(";")` fallback, no +silently skipping unparseable statements — a parse failure is surfaced to the caller as an error. +*Enforced:* `pkg/statement` boundary. *Source:* SchemaBot AGENTS.md (TiDB-parser hard +requirement, rewritten for our parser); carried in the repo's [AGENTS.md](../AGENTS.md). + +## Locking and concurrency (LK) + +### LK-1 — At most one migration runs per table + +Migrations serialize per table via a **session-scoped advisory lock** (`pg_advisory_lock` on a +key derived from database + table — the analog of Spirit's `GET_LOCK` `MetadataLock`), with +Spirit's hard-won connection rules carried over: + +- The lock is held on a **dedicated pool of exactly one connection**, exempt from client-side + connection recycling (a recycled connection silently releases a session lock — a window in + which a second instance could start a concurrent migration on the same table). +- A **keepalive** re-acquires on an interval strictly shorter than any server/idle timeout that + could kill the session; if the keepalive fails, the connection is torn down and re-established. +- **Losing the lock is fail-closed:** if the lock cannot be confirmed held, the migration aborts + rather than continuing unprotected. + +*Enforced:* `pkg/dbconn` lock type, verified before any write and monitored throughout. +*Source:* Spirit `pkg/dbconn/metadatalock.go` (stated pool invariants). This resolves the +mutual-exclusion gap called out in the validation review. + +### LK-2 — Exactly one `ACCESS EXCLUSIVE` window, and every strong lock is bounded + +The cutover swap is the only `ACCESS EXCLUSIVE` acquisition in the happy path, and **every** +strong-lock acquisition (swap, catalog flips, trigger install in fallback mode) runs under +`lock_timeout` + bounded retry/backoff so the engine never sits at the head of the lock queue +(mysql-vs-postgresql § the lock queue). +**Exception policy required:** `CREATE INDEX CONCURRENTLY` (and `REINDEX CONCURRENTLY`, +`VALIDATE CONSTRAINT`) wait on other transactions via lock waits that a naive `lock_timeout` +cancels — leaving an `INVALID` index. These statements get their own wait policy rather than the +blanket timeout. *Enforced:* every DDL execution path in the native and copy-and-swap executors. +*Source:* [design-principles](design-principles.md#correctness-and-safety), mysql-vs-postgresql; +CIC exception from the validation review. + +### LK-3 — Pending work is claimed exactly once, and Wait means finished + +For the parallel copier/applier: a pending-work entry is **claimed** by removing it from the +pending set **and** incrementing an in-flight counter **in the same critical section** — exactly +one path (success, error, or cancellation cleanup) can claim an entry, so its completion callback +runs exactly once. The claimer invokes the callback **without** holding the lock (callbacks may +be slow or re-enter the applier). `Wait()` returns only when the pending set is empty **and** the +in-flight counter is zero — it can never return while a callback is still running. *Enforced:* +applier/copier concurrency structure. *Source:* Spirit `pkg/applier/single_target.go` + +`sharded.go` ("Completion invariant", block/spirit#765). + +### LK-4 — An ambiguous cutover outcome is resolved by inspection, never assumed + +If the connection drops mid-swap (around `COMMIT`), the engine must determine from the catalog +**which table now bears the source name** before retrying or reporting — never assume the rename +did or didn't commit. PostgreSQL's transactional DDL makes the swap itself atomic, but the +*client's knowledge* of the outcome is not. Retries of the cutover must be written against this +ambiguity. *Enforced:* cutover retry loop. *Source:* Spirit's cutover +(`information_schema` inspection on dropped connection, +spirit-architecture-notes). + +## State, checkpoint, and resume (ST) + +### ST-1 — The checkpoint is a single row, written atomically + +The checkpoint table keeps **one row** (upsert on a fixed key) so a crash can never leave *zero* +checkpoints or a partial pair — there is always exactly one, and it is either the old or the new +one. Unbounded append-style checkpoint history is not used. *Enforced:* `pkg/checkpoint` write +path (`INSERT … ON CONFLICT (id) DO UPDATE`, the REPLACE analog). *Source:* Spirit +`pkg/checkpoint` (single-row REPLACE on `id=1`). + +### ST-2 — An incompatible checkpoint is distinguishable from a transient read error + +Resume must tell apart: (a) a readable, matching checkpoint → resume; (b) a checkpoint written by +an incompatible engine version or for a **different statement** → refuse to resume, start fresh +(never mix state across versions/statements); (c) a *transient* read failure → retry, and never +trigger fresh-start recovery on a blip. *Enforced:* checkpoint read/validation path (version + +statement fingerprint stored with the watermark). *Source:* Spirit `checkpoint.IsIncompatible` + +"resume requires the identical ALTER". + +### ST-3 — Slot cleanup is guaranteed on success, failure, and crash + +Replication slots are created with a recognizable name prefix; a reaper drops orphaned +engine-prefixed slots (including one stranded on a demoted writer after failover); a hard +slot-lag ceiling aborts the migration before an abandoned slot can fill the volume. No exit path +leaves a slot behind silently. *Enforced:* slot lifecycle manager + reaper + throttler ceiling. +*Source:* risks-and-mitigations § logical-decoding risks. + +### ST-4 — Slot loss is a modeled state transition, not a crash + +Losing the slot (Aurora failover) enters **reconcile mode** — keep the shadow and copy watermark, +new slot, checksum-repair pass under the CO-3 self-heal policy — and is handled distinctly from a +process crash (slot survives, clean resume). The engine detects writer-identity changes and slot +disappearance rather than blindly continuing. +*Enforced:* checkpoint/resume state machine (Phase 8). *Source:* +[low-level-design § failover](low-level-design.md#failover-during-migration-what-survives-and-what-doesnt). + +### ST-5 — The swap is gated on a fidelity checklist, not just the checksum + +Before cutover the engine verifies the shadow carries the source's **owner, grants/ACLs, RLS +policies, comments, storage parameters**, that **sequences are re-owned and advanced past the +source's current values** (`setval`), and that indexes are valid (`pg_index.indisvalid`). Data +equality (CO-1) plus metadata fidelity, or no swap. *Enforced:* cutover preconditions. *Source:* +[low-level-design § operational caveats](low-level-design.md#operational-caveats), +risks-and-mitigations. + +### ST-6 — Preflight before the first write + +Every knowable prerequisite is validated before the engine writes anything: logical-replication +enablement and role, PK usability, `REPLICA IDENTITY`, slot/WAL-sender headroom, disk headroom +(~2× the table), lock LK-1 acquired, and the RF-* refusals below. Failing hours into a copy on +something knowable up front is a bug. *Enforced:* preflight stage. *Source:* +[design-principles](design-principles.md#correctness-and-safety). + +## Refusals and preflight (RF) + +Each refusal is a preflight **error with a stated reason** — never a warning, never attempted. + +- **RF-1** — The table must have a usable PK (or `NOT NULL UNIQUE` key), and the migration must + not alter or drop it. The PK is simultaneously chunk key, conflict target, and resume + watermark. *Source:* [low-level-design](low-level-design.md#table-shape-requirements-preconditions-to-even-start), Spirit. +- **RF-2** — No FKs referencing the table, no triggers on it, no dependent **views**, no + **publication membership** (v1) — the OID-bound dependents a rename-swap strands. + *Source:* [low-level-design coverage](low-level-design.md#schema-shapes), risks-and-mitigations. +- **RF-3** — Lossy **or failable** conversions are refused up front (shortening below max data + length, `NOT NULL` without default on null data, `text→jsonb` with unvalidatable rows) rather + than discovered mid-copy. *Source:* Spirit blocklist + validation review. +- **RF-4** — Renames are never guessed: a missing-plus-new column pair is drop+add unless rename + intent is explicit; dangerous rename-overlap patterns are refused. *Source:* + [low-level-design § declarative safety rules](low-level-design.md#safety-rules-inherited-philosophy-surprise-free-decisions-not-options), Spirit. +- **RF-5** — The dangerous literal never runs silently: risky statements with a safer native + idiom get the idiom (reported) or a recommendation; running as-submitted requires the loud, + typed, audited `--force`. *Source:* [high-level-design § advisory mode](high-level-design.md#advisory-mode-suggest-the-safe-rewrite-dont-silently-run-the-risky-one). + +## Orchestration / control-plane (OC) + +From the orchestrator's operational discipline — the integration itself lives in +[schemabot-integration.md](schemabot-integration.md). These bind fully at the integration +phase, but they shape the engine's state and API surface from day one. + +### OC-1 — Fail closed on uncertainty + +Storage uncertainty, engine-state uncertainty, ownership ambiguity, or in-flight ambiguity must +**never** be converted into a passing/ready/succeeded status. Concretely: if the engine cannot +confirm the checksum state or the slot position, `status` reports the uncertainty and `cutover` +refuses — it never rounds up to "ready". *Source:* SchemaBot AGENTS.md ("safety gates first"). + +### OC-2 — Started migrations remain authoritative + +Once a migration has **started** (shadow/slot/triggers exist), a later change of intent — the PR +updated, the desired-state file reverted, a new plan — must not silently mark it succeeded or +clean it up. The started operation blocks until an operator verb (`cancel`, `cutover`) resolves +it and the target is reconciled. Cleanup alone never declares success. *Source:* SchemaBot +AGENTS.md ("started applies remain authoritative"). + +### OC-3 — Control requests are durable operator intent + +`stop` / `start` / `cutover` / `cancel` are stored durably and reconciled to completion or +explicit failure; they are never dropped on a crash, and never retried unboundedly without fresh +operator intent. *Source:* SchemaBot `docs/grpc-control-edge-cases.md`. + +### OC-4 — TOCTOU discipline on all async state + +Wherever two actors can race (scheduler vs engine, two engine instances, operator vs +reconciler), state updates are conditional (compare-and-set / ownership token) and decisions are +made on a **final state reload**, so a stale actor cannot overwrite newer state. LK-1 is the +engine-side anchor; the orchestration layer needs the same at its own store. *Source:* SchemaBot +AGENTS.md ("TOCTOU review"). + +### OC-5 — ID namespaces are never conflated + +The engine's migration identifier is an **opaque external ID** to any orchestrator; the +orchestrator's user-facing identifier is never routed to the engine. Every engine API takes +exactly one of them, by name. *Source:* SchemaBot `docs/grpc-control-edge-cases.md` +("Remote apply ID invariant"). + +### OC-6 — Shared interfaces stay engine-agnostic + +No PostgreSQL-specific fields (slot names, LSNs, `REPLICA IDENTITY` details) in shared +engine/API types — engine-specific data rides in generic `Metadata map[string]string`, and +PG-only machinery stays behind `Apply`/`Stop`/`Cancel`. *Source:* SchemaBot AGENTS.md; +[schemabot-integration.md](schemabot-integration.md). + +## Engineering invariants live in AGENTS.md + +The process-level rules mined from both repos — never silently fail; error early, never swallow; +no silent branch cases; wrap errors with context and identifiers; logs answer the triage +question; one owner closes a handle; tests prove documented behavior; integration tests against +a real database, no mocked-DB core tests; no `nolint`; no `--no-verify` — belong in the repo's [`AGENTS.md`](../AGENTS.md), not in this runtime registry. +This doc holds only invariants about the **database state machine**; that one holds invariants +about **how we write and review the code**. + +## Build-phase mapping + +| Invariant | Landed by phase | Test obligation | +| --- | --- | --- | +| CO-7, RF-1..RF-5 | 1–2 (gate/linter), full at 2 | golden refusal/parse tests | +| LK-1 | 0–1 (before any executing mode ships) | two-instance mutual-exclusion + keepalive-loss test | +| LK-2 | 3 (native), 7 (cutover) | lock-bounding + CIC-exception tests | +| CO-1, CO-2, CO-3 | 5 (gate), 8 (watermark/divergence policy) | inject-divergence, repair-invalidates-watermark | +| CO-4, CO-5, CO-6 | 6 | one convergence test per race, incl. unique-value move | +| LK-3 | 4–6 | cancellation/claim race test | +| LK-4, ST-5 | 7 | dropped-connection cutover, fidelity checklist | +| ST-1, ST-2, ST-3, ST-4 | 8 | kill/resume, cross-version refuse, orphan-slot reap, failover reconcile | +| ST-6 | 1 onward, complete by 8 | preflight matrix | +| OC-1..OC-6 | shape APIs from 2; bind at 11 | engine-contract tests | diff --git a/docs/low-level-design.md b/docs/low-level-design.md new file mode 100644 index 0000000..7954a54 --- /dev/null +++ b/docs/low-level-design.md @@ -0,0 +1,676 @@ +# Low-level design: a decoupled schema-migration engine for Aurora PostgreSQL + +> **This is the detailed / low-level design** — package layout, the `Executor` interface, +> library choices, the copy-and-swap lifecycle internals, the full coverage matrix, table +> requirements, and the open decisions to settle before writing core code. For the conceptual +> overview (the problem, the three-layer architecture, when each pattern is used) start with +> the **[high-level design](high-level-design.md)**. This doc is what you read when +> designing the interfaces and packages. + +Working name: **`pg-sprite`**. It is a **separate, purpose-built PostgreSQL tool**, not a port of +[Spirit](https://github.com/block/spirit) and not "Spirit with PostgreSQL support" (Spirit stays +MySQL-only — too many MySQL-isms to retrofit cleanly). The goal is a **decoupled planner → router → +executor** engine in which **copy-and-swap** is only one of several execution strategies. pg-sprite +**derives design practices from several tools**: the copy-and-swap lifecycle and operator model +from Spirit, the shadow-table approach from pg-osc/pg_repack, and the expand/contract executor +from pgroll. See spirit-architecture-notes.md +for how the Spirit original works and tool-pgroll.md for pgroll. + +## Table of contents + +- [Architecture: decoupled planner, router, and executors](#architecture-decoupled-planner-router-and-executors) + - [Proposed architecture (end-to-end)](#proposed-architecture-end-to-end) + - [Routing view (which executor handles what)](#routing-view-which-executor-handles-what) + - [Why this is the right shape](#why-this-is-the-right-shape) + - [The honest tradeoffs (why this is an *option*, not a free win)](#the-honest-tradeoffs-why-this-is-an-option-not-a-free-win) + - [v1 stance](#v1-stance) +- [Declarative mode (desired-state schema diff)](#declarative-mode-desired-state-schema-diff) +- [Advisory mode and the force escape hatch](#advisory-mode-and-the-force-escape-hatch) +- [Copy-and-swap executor: lifecycle](#copy-and-swap-executor-lifecycle) +- [Copy and apply ordering (the core correctness subtlety)](#copy-and-apply-ordering-the-core-correctness-subtlety) +- [Coverage and limitations (does this cover all of Aurora PostgreSQL?)](#coverage-and-limitations-does-this-cover-all-of-aurora-postgresql) +- [Table requirements and unsupported operations (Aurora PostgreSQL analogs)](#table-requirements-and-unsupported-operations-aurora-postgresql-analogs) +- [Illustrative component layout (one possible structure, not a goal)](#illustrative-component-layout-one-possible-structure-not-a-goal) +- [Library choices (Go)](#library-choices-go) +- [Design decisions inherited from Spirit (safety over speed)](#design-decisions-inherited-from-spirit-safety-over-speed) +- [Postgres-specific risks to design around](#postgres-specific-risks-to-design-around) +- [Failover during migration: what survives and what doesn't](#failover-during-migration-what-survives-and-what-doesnt) +- [Open decisions (need a call before writing core code)](#open-decisions-need-a-call-before-writing-core-code) + - [1. CDC mechanism — logical decoding vs triggers vs both](#1-cdc-mechanism--logical-decoding-vs-triggers-vs-both) + - [2. Scope of v1](#2-scope-of-v1) + - [3. Repo location / language](#3-repo-location--language) + - [4. Expand/contract (pgroll) as a second execution backend](#4-expandcontract-pgroll-as-a-second-execution-backend) +- [Next step](#next-step) + +## Architecture: decoupled planner, router, and executors + +This is **not** a single-purpose Spirit port. The system is deliberately split into three +decoupled layers, so that copy-and-swap is only *one* of several interchangeable execution +strategies rather than the whole product: + +- **Planner** — parse the change (imperative `--alter` or declarative desired-state diff), + introspect the live schema, and **classify** every operation as *native-safe*, + *needs-rewrite*, or *refuse*. The planner decides **what** must change. It has no idea + *how* any executor works. +- **Router** — given the classified plan plus policy and cluster facts (reversibility + required? app schema-version aware? logical replication available? table shape?), **choose + the executor** for each change. The router decides **which strategy**, and is the single + place migration policy lives. +- **Executors** — interchangeable implementations behind one `Executor` interface + (`Plan`/`Execute`/`Status`/`Abort`) that decide **how**: `native` DDL, + **copy-and-swap** (the heavy rewrite executor), and **expand/contract** (pgroll-derived, reversible). + New strategies can be added without touching the planner or router. + +Spirit informs the *philosophy* (safety over speed, checksum gate, decisions-not-options) +and the *copy-and-swap executor specifically* — not the overall shape. The `decode.Source` +seam inside the copy-and-swap executor is the same idea applied one level down. + +### Proposed architecture (end-to-end) + +``` + user: --alter "..." OR --desired schema.sql + │ + ╭────────────────▼─────────────────────────────────────────────────────╮ + │ CLI (Kong) migrate · diff · fmt · lint · status │ + ╰────────────────┬─────────────────────────────────────────────────────╯ + │ + ┌─────────────────────▼──────────────────── PLANNER / front-end (shared) ────┐ + │ pkg/statement parse ALTER/CREATE (pg_query_go) │ + │ pkg/schemadiff introspect live schema → diff vs desired → ordered ALTERs │ + │ classifier per op: native-safe | copy-and-swap | refuse │ + │ pkg/lint reject unsafe/unsupported up front │ + │ │ │ + │ ▼ Plan (ordered steps, classified per operation) │ + └──────┬─────────────────────────────────────────────────────────────────────┘ + ▼ + ┌──────────────────── ROUTER (pick an executor per change) ─────────────────┐ + │ policy + cluster facts: reversibility? app version-aware? logical repl? │ + │ table shape? → assigns each change to native | copy-and-swap | expand/c. │ + └──────┬─────────────────────────────────────────────────────────────────────┘ + │ pkg/executor — Executor{ Plan, Execute, Status, Abort } + ╭──────┴───────────────┬──────────────────────────────┬─────────────────────╮ + ▼ ▼ ▼ ▼ + native copy-and-swap (Pattern A) expand/contract refuse / + executor executor — the heavy path via pgroll (later) manual + ┌──────────────┐ ┌───────────────────────────-┐ ┌──────────────────┐ + │CONCURRENTLY │ │1 create shadow table │ │versioned views, │ + │NOT VALID + │ │2 pkg/decode logical slot │ │dual schema, app │ + │ VALIDATE │ │3 pkg/copier chunked copy │ │coordinated, │ + │USING INDEX │ │4 pkg/applier ON CONFLICT │ │reversible │ + │fast default │ │5 pkg/checksum gate │ └──────────────────┘ + │(lock_timeout │ │6 cutover: ACCESS EXCLUSIVE │ + │ + retry) │ │ swap (lock_timeout+retry)│ + └──────┬───────┘ │ + checkpoint / resume │ + │ └─────────────┬──────────────┘ + │ │ + ┌────┴────────────────────────────┴──── cross-cutting ──────────────────────┐ + │ pkg/dbconn pgx pool · TLS/RDS CA · pg_terminate_backend · retries │ + │ pkg/throttler Aurora reader lag · replication-slot lag · WAL gen │ + └────┬──────────────────────────────────────────────────────────────────────┘ + │ + ╭────▼──────────────────────── Aurora PostgreSQL ───────────────────────────╮ + │ WRITER (DDL + copy + cutover; logical replication slot lives here) │ + │ READERS (lag signal for throttling; reached via reader endpoint) │ + ╰───────────────────────────────────────────────────────────────────────────╯ +``` + +### Routing view (which executor handles what) + +``` + ╭──────────────────────────────────────────────╮ + │ Planner (shared): parse · introspect · │ + │ declarative diff · classify · lint/refuse │ + ╰───────────────────────┬──────────────────────╯ + │ Plan + ▼ + ╭──────────────────────────────────────────────╮ + │ Router: pick an executor per change │ + ╰───────────────────────┬──────────────────────╯ + │ routes each change to the best executor + ╭───────────────┬───────────────┼─────────────────────────┬─────────────╮ + ▼ ▼ ▼ ▼ ▼ + native DDL log-based expand/contract (future refuse / + executor copy-and-swap via pgroll backends) manual + CONCURRENTLY executor (Pattern B, reversible) + NOT VALID … (Pattern A) +``` + +### Why this is the right shape + +This is exactly the answer to *"why build only copy-and-swap when pgroll already wins some +cases?"* — **we don't have to choose globally.** A shared planner lets us pick the best +pattern *per migration*: + +- **native** for the majority (the ➖/❌ rows in [postgres-online-ddl-reference](postgres-online-ddl-reference.md)); +- **log-based copy-and-swap** for transparent, heavy physical rewrites (`int→bigint`, repack) + where the change is invisible to the app — see tool-pgroll's comparison; +- **expand/contract via pgroll** for prod-critical breaking changes where **instant + reversibility** and **two live schema versions** matter more than transparency. + +The classifier, declarative diff, linting, dry-run, and status reporting are written **once** +and shared by every backend. An `Executor` interface (`Plan`, `Execute`, `Status`, `Abort`) +is the contract; copy-and-swap and native are the v1 implementations, pgroll is a strong +candidate to wrap as a third. + +### The honest tradeoffs (why this is an *option*, not a free win) + +Routing to pgroll buys reversibility, but the patterns are not silently interchangeable — the +**operational contract differs by backend**, so the choice surfaces to the user: + +- **App-awareness is pattern-intrinsic, not a tool feature.** copy-and-swap is transparent + (same table name, no app changes); pgroll's reversibility *requires* the app to be + schema-version aware (`search_path`, two versions live, `start`→rollout→`complete`). You get + reversibility **or** transparency per migration — not both at once — because they are + properties of the chosen pattern. +- **Reversibility is bounded to the rollout window.** pgroll's instant rollback applies + *before* `complete`; once contracted, rolling back is another migration — same as + copy-and-swap. The benefit is real but time-boxed. +- **Two lifecycles to unify.** copy-and-swap is one-shot; pgroll is a stateful + start/complete/rollback machine with its own migration history. A shared `status`/resume + layer must model both, which is real complexity. +- **Dependency coupling.** pgroll is Go (reusable), but wrapping it (library or subprocess) + brings its version surface, its trigger-based backfill, and its error model along. +- **Tension with "decisions, not options."** A second pattern is a real user-facing choice. To + stay faithful to the philosophy it needs a **clear default and a narrow, well-signposted + opt-in** (e.g. auto-route, with `--strategy=expand-contract` only when reversibility is + explicitly requested), not a bare menu of equal options. + +### v1 stance + +Build the **planner + router + native + log-based copy-and-swap** first (that is the +differentiated, missing capability). Design the `Executor` interface from day one so the **expand/contract +(pgroll) backend can be added later** without reworking the front-end. See the +build plan — the pgroll backend lands in a later phase. + +## Declarative mode (desired-state schema diff) + +The engine supports **two ways to express a change**, mirroring Spirit's imperative +(`migrate --alter`) and declarative (`diff` / `fmt` over canonical schema files) front-ends: + +- **Imperative** — the user supplies the `ALTER` directly. +- **Declarative** — the user supplies the **desired end-state** as a `CREATE TABLE` (typically + a checked-in `.sql` schema file), and the engine **derives the `ALTER`** by diffing it + against the live table. This is the analog of Spirit's `diff`/declarative schema workflow. + +Declarative mode is a **front-end that produces statements**, which then flow into the exact +same pipeline as imperative input (classify → native-safe DDL, or shadow-table copy): + +``` +desired CREATE TABLE (file) ─┐ + ├─▶ diff engine ─▶ derived ALTER / CREATE INDEX / ... +live schema (introspected) ─┘ │ + ▼ + (same as imperative) classify ─▶ native DDL | shadow copy +``` + +### How the diff is derived + +1. **Parse desired state** with `pg_query_go` into a normalized table model (columns, types, + defaults, nullability, identity/sequences, constraints, indexes). +2. **Introspect live state** from the catalogs (`pg_attribute`, `pg_constraint`, `pg_index`, + `pg_attrdef`, …) into the same normalized model. +3. **Canonicalize both** so the comparison ignores cosmetic differences — type aliases + (`int4` ↔ `integer`, `varchar` ↔ `character varying`), default formatting, column order + where it doesn't matter, implicit names. (This is what a `fmt` subcommand also does to a + schema file on its own.) +4. **Diff** the two models and emit the minimal set of statements: `ADD/DROP/ALTER COLUMN`, + `ADD/DROP CONSTRAINT`, `CREATE/DROP INDEX`, default/nullability changes, etc., in a + **dependency-correct order** (e.g. add a column before an index that references it). +5. **Hand the derived statements to the same classifier**, so a declarative change that turns + out to be, say, a binary-coercible type widening still takes the native fast path, and only + a genuine rewrite triggers a copy. + +### Safety rules (inherited philosophy: surprise-free, decisions-not-options) + +- **Destructive diffs are gated.** Dropping a column or constraint, or anything that loses + data, requires an explicit confirmation flag — never inferred silently from "it's missing in + the desired file". +- **Renames are ambiguous and are not guessed.** A column present in live but absent in desired + plus a new column in desired is, by default, a *drop + add*, not a rename. Rename intent must + be stated explicitly (the engine will not heuristically pair columns), mirroring Spirit's + refusal to auto-handle dangerous rename patterns. +- **Dry-run first.** `diff` prints the derived statements (and whether each takes the native or + copy path) without executing — the same review step as Spirit, and the natural hook for CI. +- **Out-of-band drift is surfaced, not steamrolled.** If the live table differs from what the + desired file's base assumed, the diff makes that visible rather than blindly forcing the + end state. + +### Why this matters + +Declarative mode lets schemas live as **reviewed, version-controlled `.sql` files** and lets +CI compute "what would change" — while reusing the entire safe execution path (classify, +native-vs-copy, checksum, cutover). It is purely additive: the imperative `--alter` path +remains the primitive that everything ultimately runs through. + +## Advisory mode and the force escape hatch + +The [advisory behaviour](high-level-design.md#advisory-mode-suggest-the-safe-rewrite-dont-silently-run-the-risky-one) +is a property of the **planner's classifier output**, not a separate code path. Every classified +operation carries a recommendation, and the CLI decides whether to apply it, suggest it, or +refuse based on mode and flags. + +### What the classifier emits per operation + +For each parsed statement the classifier produces a record along the lines of: + +- `original` — the statement as the user wrote it. +- `class` — `native-safe` · `needs-rewrite` (copy-and-swap) · `refuse`. +- `recommended` — the safe rewrite when the literal is risky but has a native equivalent + (e.g. `CREATE INDEX` → `CREATE INDEX CONCURRENTLY`; `ADD CONSTRAINT` → `ADD … NOT VALID` + + `VALIDATE`; `ADD PRIMARY KEY` → unique index `CONCURRENTLY` + `ADD PRIMARY KEY USING INDEX`). +- `risk` — what the literal would do (lock mode held, whether it blocks reads/writes, expected + duration class) and *why* the recommendation is safer. +- `reversible` / `requires_app_coordination` — populated when a pattern other than native is in + play (feeds the router). + +This is what `pkg/lint` and `pkg/statement` already need to compute in order to route; advisory +mode just **surfaces** it instead of consuming it silently. + +### CLI behaviour (modes) + +| Invocation | Behaviour | +| --- | --- | +| `suggest` / `lint` / `diff --dry-run` | Print `original` → `recommended` + `risk` for every op. **Never executes.** Exit non-zero if any op needs a riskier path than policy allows (the CI gate). | +| `migrate` (default) | If a safer `recommended` form exists, **apply the recommended idiom** (classify-first) and report what was substituted. If the literal is risky with **no** safe equivalent (a genuine rewrite), route to copy-and-swap. If unsafe/unsupported, **refuse** with the reason. The dangerous literal is **never** run by default. | +| `migrate --force` | Run each statement **exactly as submitted**, bypassing the safe rewrite. Gated — see below. | + +The distinction the user cares about: a plain `CREATE INDEX` is never executed verbatim by +default. The engine either applies `CREATE INDEX CONCURRENTLY` for you (and says so) or, in +`suggest`/dry-run, hands back the recommendation without touching the database. + +### The `--force` gate + +`--force` is deliberately high-friction: + +1. Print a prominent **DANGER / CAUTION** block: the exact statement, the lock it will take, what + it blocks (reads? writes?), and the expected/worst-case duration and lock-queue impact + (cross-link to 12-mysql-vs-postgresql.md § the lock queue). +2. Require an **explicit typed acknowledgement** (e.g. type the table name, or + `--i-understand-the-risk`), not a bare `-y`/`--yes`. +3. Still wrap the statement in `lock_timeout` + bounded retry unless the user *also* opts out of + that explicitly (a second, separate flag) — force means "run my statement", not "remove every + guardrail". +4. **Log the override** (who, when, what statement, what the recommendation was) for audit. + +Force exists for the rare legitimate case (e.g. a maintenance window where the table is known +idle and a plain rewrite is acceptable); it is an escape hatch, not a shortcut, consistent with +*decisions, not options*. + +## Copy-and-swap executor: lifecycle + +> This section details the **copy-and-swap executor** (one of the executors above), the +> heavy strategy for genuine table rewrites. The `native` and `expand/contract` +> executors are described in the architecture section and in +> tool-pgroll.md. The per-primitive **Spirit (MySQL) → Aurora +> PostgreSQL mapping** this executor is built on lives in +> 12-mysql-vs-postgresql.md § primitive mapping. + +``` ++----------------------------------------------------------------------+ +| 1. Parse ALTER. If it maps to a SAFE native pattern | +| (CONCURRENTLY / NOT VALID+VALIDATE / fast-default / USING INDEX / | +| binary-coercible type change) -> run it directly. Done. | +| (= Spirit's "attempt INSTANT/INPLACE") | ++----------------------------------------------------------------------+ + | otherwise (table rewrite needed) + v ++--------------+ +-------------------+ +------------------+ +---------------+ +| 2. Create | | 3. Start CDC: | | 4. Chunked copy | | 5. Drain CDC | +| shadow table |->| logical slot |->| source->shadow, |->| backlog + | +| w/ new schema| | (snapshot LSN) + | | N parallel | | checksum gate | +| | | buffer changes | | workers, dynamic | | (correctness) | +| | | from snapshot LSN | | chunk sizing | | | ++--------------+ +-------------------+ +------------------+ +-------+-------+ + v + +-------------------------------------+ + | 6. Cutover (one transaction): | + | SET lock_timeout | + | LOCK src ACCESS EXCLUSIVE | + | final CDC drain | + | RENAME swap | + | COMMIT | + | then: recreate FKs referencing | + | src, fix sequence ownership, drop | + | old table, drop slot | + +-------------------------------------+ +``` + +Key correctness gate (same as Spirit): the **checksum must pass before cutover**. With +`--defer-cutover`, a continuous checksum loop runs while waiting on a sentinel, exactly like +Spirit's deferred-cutover mode. + +## Copy and apply ordering (the core correctness subtlety) + +The copier and the applier write the **same shadow rows concurrently**, and their interleaving — +not the decoding, not the SQL — is where a copy-and-swap engine is most likely to be silently +wrong. This is where Spirit/gh-ost carry their most delicate logic, and the protocol must be +specified and tested explicitly +(build-plan Phase 6), not left +implicit in the implementation. The races to design against: + +- **Stale-image overwrite.** A chunk read from the source at time T₁ lands in the shadow *after* + the applier already applied a newer captured change for the same row. If the copier overwrites, + the shadow regresses to the older image. +- **Ghost-row resurrection.** The copier reads a row, the applier applies that row's `DELETE`, + then the chunk insert lands — re-inserting a row that no longer exists on the source. +- **The same races during reconciliation.** The checksum-repair pass after + [slot loss](#failover-during-migration-what-survives-and-what-doesnt) re-copies divergent + chunks while the new slot's stream is being applied — the same two races, a second exposure. + +The invariants that resolve them (Spirit's model, translated): + +- **The copier never overwrites**: chunks land with `INSERT … ON CONFLICT (pk) DO NOTHING`, so a + newer applied image is never regressed (the `INSERT IGNORE` analog). +- **The applier always overwrites**: captured changes land with + `INSERT … ON CONFLICT (pk) DO UPDATE` plus explicit `DELETE` handling (the `REPLACE INTO` + analog), keyed by PK and deduplicated to the latest image per key before each flush. +- **The watermark orders the two**: captured changes for PK ranges the copier has already passed + are applied; changes *above* the copier's watermark can be discarded for a monotonic integer PK + (the copier will read the current row anyway — the high-watermark optimization) and must be + queued for composite / non-memory-comparable PKs. +- **Deletes must not be lost to an in-flight chunk**: a delete for a key inside a chunk that is + currently being copied must be re-applied *after* that chunk lands (tombstone retention until + the covering chunk completes), or chunk copy and backlog flush must be mutually excluded per + overlapping key range. + +The **mandatory checksum remains the backstop, not the mechanism** — it catches a protocol bug +before cutover, but the protocol must converge without it. The precise rule set (flush scheduling +vs chunk boundaries, tombstone lifetime, the composite-PK queue) is a Phase 6 deliverable with a +dedicated convergence test per race above. The trigger fallback has its own analog of this race — +see risks-and-mitigations § trigger-specific risks; this +section is the logical-decoding counterpart. + +## Coverage and limitations (does this cover all of Aurora PostgreSQL?) + +**No — and no single tool does.** The one-line pitch ("multi-threaded chunked copy + +log-based CDC + checksum-gated atomic cutover + checkpoint/resume, tuned for Aurora") +describes the *happy-path mechanism*, not universal coverage of every Aurora PostgreSQL +deployment topology, configuration, and schema shape. Being explicit about the supported +matrix is part of the "decisions, not options" philosophy. + +### Deployment topologies + +| Topology | v1 support | Notes | +| --- | --- | --- | +| Aurora PostgreSQL provisioned (1 writer + N readers) | ✅ Primary target | Logical decoding runs against the **writer** endpoint | +| Aurora Serverless v2 | ✅ With caveats | Logical replication supported; heavy copy can drive ACU scaling/cost; pin min ACUs | +| Aurora Serverless v1 | ❌ | Logical replication not available; deprecated | +| Aurora Global Database | ⚠️ Writer region only | Slots exist only on the primary region writer; a region failover invalidates the slot → resume not possible across regions | +| RDS PostgreSQL (non-Aurora) | ✅ Bonus | Same engine + `rds.logical_replication`; the engine should work unmodified | +| RDS Proxy in front of the cluster | ⚠️ | The **replication** connection must go **direct** to the instance endpoint, not through the proxy (proxy doesn't support the replication protocol / pinning) | +| Babelfish (TDS/SQL-Server surface) | ❌ | Out of scope | +| Blue/Green Deployments | ⚠️ | Conceptually overlapping; running both at once needs care around slots/triggers | + +### Configuration / privilege preconditions + +| Precondition | Required for | If absent | +| --- | --- | --- | +| `rds.logical_replication = 1` (static → reboot) ⇒ `wal_level = logical` | logical-decoding CDC path | Fall back to **trigger-based** CDC | +| `rds_replication` role granted (Aurora gives no `SUPERUSER`) | creating slot / starting replication | Use trigger fallback, or request the grant | +| Ownership / `CREATE` on the schema | shadow table, triggers, swap | Migration cannot run | +| `max_replication_slots` / `max_wal_senders` headroom | concurrent migrations | Serialize migrations | +| `REPLICA IDENTITY` = PK (default) or `FULL` | correct UPDATE/DELETE capture; unchanged-TOAST columns | v1 requires a PK, so default identity suffices | + +### Schema shapes + +| Schema feature | v1 | Notes | +| --- | --- | --- | +| Single-column integer/`bigint`/`identity` PK | ✅ Fast path | Best watermark + chunking | +| Composite or `uuid`/`text` PK | ✅ Slower path | Composite chunker; weaker watermark optimization | +| **No** primary key / no unique-not-null key | ❌ | Required, same constraint as Spirit | +| Foreign keys **referencing** the table | ❌ v1 | FKs must be re-pointed at cutover — defer to v2 | +| Triggers on the table | ❌ v1 | Must be recreated on the shadow with correct ordering | +| Views defined on the table | ❌ v1 | PostgreSQL binds a view to the table's **OID**, not its name — after the rename-swap the view silently follows the renamed *old* table. Refuse in v1 (pg_repack avoids this only by swapping relfilenodes under one OID; recreating views inside the cutover txn is later work) | +| Table is in a logical **publication** | ❌ v1 (preflight refusal) | Publication membership is also OID-bound; after the swap, downstream consumers (DMS, CDC pipelines) silently stop receiving the real table's changes | +| `STORED` generated columns | ⚠️ | Copy must omit them and let them recompute; checksum must account for them | +| Partitioned (declarative) tables | ❌ v1 | Root vs leaf publication, `publish_via_partition_root`, per-partition swap — complex | +| Large objects (`pg_largeobject`) | ❌ | Not represented in table-level logical decoding | +| Exotic types / domains / non-deterministic collations | ⚠️ | Must produce a deterministic checksum on both source and shadow | + +### Operational caveats + +- **Unchanged-TOAST on UPDATE**: with default replica identity, an `UPDATE` that doesn't + touch a TOASTed column won't emit that column's value. The applier must handle this (carry + forward, or use `REPLICA IDENTITY FULL`) or the shadow can diverge — the checksum is the + backstop, but design for it explicitly. +- **No DDL during migration**: logical decoding does not stream DDL. Concurrent schema + changes to the source mid-migration are unsupported and must be blocked. +- **Multi-TB tables**: a multi-day copy means the slot retains WAL for the whole window → + disk/lag risk (see risks below). +- **Multi-statement / multi-table atomic changes**: out of v1 scope. +- **Shadow-table fidelity beyond columns**: the shadow must explicitly replicate the source's + **owner, GRANTs/ACLs, row-level-security policies, comments, and storage parameters** — none + of which comes along by creating a table with the right columns. Miss the grants and + application roles **lose access at the instant of cutover**. The cutover refuses to swap until + this fidelity checklist passes; OID-bound dependents (views, publications) are refused up + front in v1 (see the schema-shape matrix above). + +### What "tuned for Aurora" actually means here + +Aurora-specific handling (throttle on Aurora reader replica lag and on replication **slot** +lag, RDS CA bundle for TLS, `pg_terminate_backend` to bound the cutover lock, awareness of +the writer/reader split) — **not** a claim that every Aurora edition/topology above is +covered. The unsupported rows are explicit non-goals for v1. + +## Table requirements and unsupported operations (Aurora PostgreSQL analogs) + +Spirit publishes a short, deliberate list of things it **requires** of a table and things it +**refuses to do** (see [its README](https://github.com/block/spirit#unsupported-features) and +spirit-architecture-notes.md). These are not arbitrary — +each maps to a property the copy/CDC/cutover machinery depends on. Below is the faithful +translation of each constraint to Aurora PostgreSQL, **with the Postgres-specific reason** +(not just "because Spirit does it"). The coverage matrix above states *what* is supported; +this section states *why* and pins the analog to the underlying primitive. + +### Table-shape requirements (preconditions to even start) + +| Spirit (MySQL) requirement | Aurora PG analog (v1) | Postgres-specific reason | +| --- | --- | --- | +| Table **must have a PRIMARY KEY** | Require a PK, or a `NOT NULL` `UNIQUE` key usable as one | Chunking needs a deterministic, range-scannable key to slice `WHERE pk BETWEEN …`; the applier needs a stable conflict target for `INSERT … ON CONFLICT (pk) DO UPDATE`; resume needs a watermark. No PK ⇒ would need `REPLICA IDENTITY FULL`, full-row matching on apply/delete, and a synthetic `ctid`-based chunker (unstable across `VACUUM`/rewrite) — unsafe for v1. | +| PK should ideally be a single memory-comparable integer | `bigint`/`identity`/`serial` single-column PK is the fast path; composite / `uuid` / `text` PK is the slower path | The high-watermark optimization (discard captured changes above the copier's position) and the optimistic chunker rely on a monotonic, cheaply-comparable key. `uuid`/`text`/collated keys force the composite/queue path, exactly as in Spirit. | +| `binlog_row_image=FULL` (full before/after image) | `REPLICA IDENTITY` = PK (default) is enough for v1; `FULL` only if no PK | PG only logs the replica-identity columns for `UPDATE`/`DELETE` by default; that is sufficient when a PK exists. The **unchanged-TOAST** wrinkle (a TOASTed column not in the update isn't streamed) is the PG-specific gotcha the applier must handle. | + +### Unsupported / refused operations (mirror Spirit's blocklist) + +| Spirit refuses | Aurora PG engine v1 stance | Postgres-specific reason | +| --- | --- | --- | +| **ALTER / DROP PRIMARY KEY** | Refuse — PK must be unchanged by the migration | The PK is simultaneously the chunk key, the CDC conflict target, and the resume watermark. Changing it mid-flight breaks all three. (A PK *change* can still be done as a separate expand/contract migration.) | +| **FOREIGN KEYS or TRIGGERS on the migrated table** | Refuse in v1 | Inbound FKs (other tables referencing this one) must be re-pointed at cutover under the `ACCESS EXCLUSIVE` window — error-prone and lengthens the lock. Triggers/rules on the source would also have to be recreated on the shadow with exact firing order, and could fire during the copy. Both are deferred, same as Spirit. | +| **RENAME column** (dangerous overlap cases) | Refuse the dangerous cases; allow only simple, unambiguous non-PK renames | A rename that reuses an old name (`RENAME a→b, ADD a …`) makes column identity ambiguous between the source row image and the shadow schema, risking silent data misplacement during apply. Same correctness hazard exists in PG. | +| **Lossy conversions** (shorten `VARCHAR` below longest value, add `NOT NULL` w/o default, add `UNIQUE` on non-unique data) | Refuse; require the data be fixed first | These can fail or truncate *during the copy or the constraint validation*, after work is spent. PG surfaces them as `VALIDATE CONSTRAINT` / cast failures; better to reject up front. | +| Read-replica `<10s` lag fidelity | Not a goal | Like Spirit, the engine prioritizes copy throughput; it observes Aurora reader/slot lag only to throttle and protect DR, not to guarantee replica freshness. | + +### Postgres-only preconditions Spirit has no analog for + +These have **no MySQL counterpart** but are hard requirements for the logical-decoding path: + +- **`rds.logical_replication = 1`** (⇒ `wal_level = logical`); static, needs a reboot. Without + it the engine must fall back to trigger-based CDC. +- **`rds_replication` role** (Aurora grants no `SUPERUSER`) to create the slot and start + replication. +- **Replication-slot / `max_wal_senders` headroom**, and the slot must be on the **writer** + (and reached **directly**, not via RDS Proxy). +- **No concurrent DDL on the source** during the migration — logical decoding does not stream + DDL, so a mid-flight schema change to the source is unsupported. + +> Net: the v1 supported surface is intentionally close to Spirit's — *single table, has a PK, +> no FKs/triggers, no PK change, no lossy change* — with the Postgres-specific additions of +> logical-replication enablement, slot/role privileges, and the unchanged-TOAST handling. + +## Illustrative component layout (one possible structure, not a goal) + +> We intend to mirror Spirit's **design philosophy** (see +> [Design decisions inherited from Spirit](#design-decisions-inherited-from-spirit-safety-over-speed)), +> **not** its package layout. The structure below is just one illustrative way to organise +> the components so the responsibilities are clear; the real code will follow whatever is +> idiomatic for a Postgres + logical-decoding tool. + +``` +cmd/pg-sprite/ -> CLI (migrate, diff, fmt, lint, status) - Kong, like Spirit +pkg/planner/ -> shared front-end: parse/introspect/diff + classify each op -> Plan +pkg/router/ -> picks an Executor per change from policy + cluster facts +pkg/executor/ -> Executor interface (Plan/Execute/Status/Abort) + implementations: + native (CONCURRENTLY / NOT VALID …), copyswap (Pattern A), + later: expandcontract (wraps pgroll, Pattern B / reversible) +pkg/migration/ -> orchestrator + runner + cutover (drives the copyswap executor) +pkg/decode/ -> logical-decoding client (replaces Spirit's pkg/change/binlog) <- the hard part +pkg/copier/ -> parallel chunked copy (INSERT...SELECT...ON CONFLICT) +pkg/applier/ -> ON CONFLICT upsert + delete apply +pkg/table/ -> PK-range chunkers (optimistic + composite), dynamic sizing +pkg/checksum/ -> md5/row-text chunked verification +pkg/dbconn/ -> pgx pool, retries, lock_timeout, RDS CA, pg_terminate_backend +pkg/statement/ -> pg_query_go parsing + "is this natively safe?" classifier +pkg/schemadiff/ -> declarative mode: introspect live schema, diff vs desired + CREATE TABLE, derive ordered ALTER/CREATE statements (+ fmt) +pkg/lint/ -> unsafe-DDL linters (PG flavored) +pkg/throttler/ -> Aurora PG replica-lag / slot-lag throttle +``` + +## Library choices (Go) + +- **`pgx/v5`** — Postgres driver + connection pool; also exposes the low-level + `pgconn`/`pglogrepl` building blocks for logical replication. +- **`github.com/jackc/pglogrepl`** — start replication, parse `pgoutput`/`wal2json` + messages, send standby status (LSN flush) updates. This is the binlog-syncer analog. +- **`github.com/pganalyze/pg_query_go/v5`** — parse `ALTER`/`CREATE TABLE` (libpg_query, + the actual Postgres grammar). Analog of Spirit's TiDB parser. +- **`github.com/alecthomas/kong`** — CLI, same as Spirit. + +## Design decisions inherited from Spirit (safety over speed) + +> The full, categorized list lives in +> [design-principles.md](design-principles.md). The decisions below are the concrete +> v1 engineering choices that follow from those principles. + +- **Primary key required**; PK cannot be changed by the migration (v1). +- **No FKs / triggers on the migrated table** in v1 (Postgres FKs that reference the table + being swapped require careful re-pointing — defer to v2). +- **Checksum is mandatory and cannot be skipped** — it is the cutover correctness gate. +- **Dynamic chunking by target time** (default ~500ms), not a fixed row count. +- **Checkpoint/resume**: persist `{last copied PK watermark, slot name, confirmed LSN}` so an + interrupted migration resumes with ~1 minute of lost work. (Postgres-specific risk: the + slot must survive; see open decisions.) + +## Postgres-specific risks to design around + +The full enumeration of risks — **common to any copy-and-swap**, **logical-decoding-specific**, +and **trigger-specific** — together with the **mitigation** for each, has moved to its own register +so it can be maintained as a single source of truth: see +risks-and-mitigations.md. The two most design-shaping ones, +**slot loss on failover** and the reconcile-don't-recopy recovery it forces, are detailed below +because they drive the checkpoint/resume state machine. + +## Failover during migration: what survives and what doesn't + +> **Short answer to "does a failover mean we resume from scratch?": no for the bulk copy, but +> yes for the logical-decoding catch-up state — on Aurora a failover can cost the slot, and with +> it the incremental progress, forcing a reconciliation pass (not necessarily a full re-copy).** + +A copy-and-swap migration has two kinds of progress, and they have very different durability: + +| State | Where it lives | Survives Aurora failover? | +| --- | --- | --- | +| **Copied-PK watermark** (how far the bulk copy got) | the engine's durable checkpoint store | ✅ yes — it's our own data | +| **Shadow table contents** | a regular table | ✅ yes — replicated by Aurora storage | +| **Logical slot + `confirmed_flush_lsn`** (CDC position) | the writer's replication slot | ⚠️ **not guaranteed** — see risk #7 | +| **In-flight decode buffer / un-applied changes** | engine memory | ❌ no | + +The trap: the slot's LSN is what makes the shadow *trustworthy*. If the slot is lost, you cannot +simply create a new slot and continue — a fresh slot starts at the **current** LSN, leaving a +**gap** of changes between the old `confirmed_flush_lsn` and the new slot, so the shadow is now +silently diverged from the source. So on slot loss the engine has three honest options, in order +of preference: + +1. **Reconcile, don't re-copy (preferred).** Keep the shadow and the durable copy watermark, + create a new slot at the current LSN, then run a **full checksum + repair pass** (re-sync only + the chunks that differ) before resuming CDC from the new slot. This salvages days of bulk-copy + work; the cost is one comparison sweep, bounded by *checksum* cost, not *copy* cost. The + mandatory checksum gate already exists — this reuses it as a repair primitive. +2. **Restart from scratch.** New slot + new snapshot + full re-copy. Always correct, but throws + away all progress; acceptable only for small tables. +3. **Don't use logical decoding here.** For clusters where failover risk during a multi-day + migration is unacceptable, route to the **trigger fallback** (open decision #1): the trigger + + queue table are ordinary data that survive failover, so the migration simply resumes. + +Design implications the rest of the system must honour: +- **Model slot loss as a first-class state transition** in checkpoint/resume (build-plan + Phase 8), distinct + from a process crash (where the slot *does* survive and we resume cleanly). +- **Detect it:** watch for the slot disappearing / becoming inactive and for writer-identity + changes, and enter reconcile mode rather than blindly continuing. +- **Bound the blast radius:** the hard slot-lag ceiling and the name-prefixed reaper (risk #1) + must also clean up the *orphaned* slot a failover can strand on the demoted instance. +- **Make the trade explicit to the operator:** on logical-decoding clusters, a long migration is + exposed to failover/maintenance windows; the trigger path is the robustness escape hatch. + +## Open decisions (need a call before writing core code) + +### 1. CDC mechanism — logical decoding vs triggers vs both + +- **Logical decoding** (recommended primary): faithful Spirit port, **no synchronous write + overhead** on the source; this is what makes the tool better than pg_osc. Costs: needs + `rds.logical_replication=1` (reboot), a slot (WAL-retention risk), `REPLICA IDENTITY`, and the + failover/TOAST/DDL wrinkles in the + logical-decoding-specific risks. +- **Triggers** (pg_osc style): no parameter/reboot, works anywhere; **survives failover** (the + queue table is ordinary data). The cost is not just write amplification: capture lives **inside + the write path**, so a trigger error can abort the application's write (an availability coupling), + and writes made under `session_replication_role = replica` silently bypass capture — see the + trigger-specific risks. It is the + robustness escape hatch for clusters that can't enable logical replication or can't accept + [slot loss on failover](#failover-during-migration-what-survives-and-what-doesnt) during a + multi-day migration — not a strictly-safer option. +- **Recommendation:** define a `decode.Source` interface (mirroring Spirit's `change.Source` + seam) with **logical decoding as primary and trigger-based as fallback** — and treat the + fallback as a first-class robustness path on Aurora, not a vestige. The full comparison + (overhead, failover survival, and why neither lets us drop the checksum) is in + [change-capture-tradeoff.md](change-capture-tradeoff.md). + +### 2. Scope of v1 + +Target the highest-value rewrite cases first: general `ALTER COLUMN TYPE`, volatile-default +`ADD COLUMN`, `STORED` generated column, and full-table repack — plus the **classifier** that +routes natively-safe operations to direct DDL. PK-required, no-FK-on-migrated-table for v1. + +**Build the declarative front-end first; imperative is the thin add-on** — settled, matching the +[README TL;DR](README.md#tldr-recommendation), +[high-level-design](high-level-design.md#two-front-ends-declarative-and-imperative), and +build-plan Phase 2. Declarative +does the harder work (introspect + diff + ordering) and exercises the full +classify → route → execute pipeline; the imperative `--alter` path is the **same** pipeline with +the diff step skipped, so it falls out almost for free. The imperative statement remains the +primitive that everything ultimately executes — declarative only *produces* statements — which is +why building declarative first costs nothing on the execution side. + +### 3. Repo location / language + +Go (reuse `pgx` + `pglogrepl` + `pg_query_go`; matches Spirit's language and idioms). Fresh +standalone repo — this repository. + +### 4. Expand/contract (pgroll) as a second execution backend + +Whether to ship the engine as a **planner + pluggable executors** (see +[Architecture](#architecture-decoupled-planner-router-and-executors)) so prod-critical +breaking changes can route to pgroll's reversible expand/contract pattern while heavy physical +rewrites use copy-and-swap. + +- **Recommendation:** yes in principle — define the `Executor` interface from day one — but + **defer the pgroll backend itself** past v1. The differentiated, currently-missing + capability is the log-based copy-and-swap executor; pgroll already exists and can be used + directly today. Adding it as a backend is mostly about a unified planner/UX, not new + capability. +- **Open sub-questions:** wrap pgroll as a Go library vs subprocess; how to unify the + one-shot vs start/complete/rollback lifecycles under one `status`; and the default routing + policy (auto-route vs explicit `--strategy`) given "decisions, not options". + + +## Next step + +Once the decisions above are settled, scaffold: + +- CLI skeleton (`migrate` subcommand), +- `decode.Source` interface + a first logical-decoding implementation, +- PK-range chunker + parallel copier, +- transactional cutover, +- an end-to-end working path for a single `ALTER COLUMN TYPE` against a local Postgres in + Docker, with an integration test. diff --git a/docs/postgres-online-ddl-reference.md b/docs/postgres-online-ddl-reference.md new file mode 100644 index 0000000..1c07f03 --- /dev/null +++ b/docs/postgres-online-ddl-reference.md @@ -0,0 +1,226 @@ +# Aurora PostgreSQL online DDL operations reference + +The PostgreSQL equivalent of MySQL's +[InnoDB Online DDL Operations](https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html). + +PostgreSQL has **no** `ALGORITHM={INSTANT,INPLACE,COPY}` knob. What matters instead is: + +1. **Which lock level** the DDL acquires, and +2. **Whether it rewrites the table** (or does a full scan), and therefore how long it holds + that lock. + +The rest of this document breaks down both dimensions per operation. + +## Table of contents + +- [Lock levels (weakest → strongest)](#lock-levels-weakest--strongest) +- [Why DDL is dangerous: the lock queue](#why-ddl-is-dangerous-the-lock-queue) +- [Column operations](#column-operations) +- [Index operations](#index-operations) +- [Constraint operations](#constraint-operations) +- [Table / partition operations](#table--partition-operations) +- [The headline takeaway (what the engine must cover)](#the-headline-takeaway-what-the-engine-must-cover) +- [Aurora PostgreSQL specifics](#aurora-postgresql-specifics) + +## Lock levels (weakest → strongest) + +``` +ACCESS SHARE < ROW SHARE < ROW EXCLUSIVE < SHARE UPDATE EXCLUSIVE + < SHARE < SHARE ROW EXCLUSIVE < EXCLUSIVE < ACCESS EXCLUSIVE +``` + +- `SELECT` takes `ACCESS SHARE`. +- `INSERT/UPDATE/DELETE` take `ROW EXCLUSIVE`. +- **Reads and writes continue concurrently** as long as the DDL holds + `≤ SHARE UPDATE EXCLUSIVE`. That is the "online" threshold. +- `ACCESS EXCLUSIVE` blocks **everything**, including reads. + +### Comparison with MySQL / InnoDB + +The PostgreSQL lock-mode → MySQL 8.0 (MDL + InnoDB) mapping, the `ALGORITHM=`/`LOCK=` contrast, +and the brief-exclusive-lock parallel now live in the dedicated comparison doc: +**12-mysql-vs-postgresql.md § Lock model comparison**. + +## Why DDL is dangerous: the lock queue + +This is covered in the comparison reference: +**12-mysql-vs-postgresql.md § Why DDL is dangerous: the lock queue**. +It explains the three-step lock-queue pile-up, why the DDL (not the long query) is the +catalyst, how long the impact lasts, the mitigations the engine relies on, and why MySQL has +the same dynamic via metadata locks. Read it first if any of that is unfamiliar. + +## Column operations + +| Operation | Lock held | Table rewrite | Concurrent DML | Needs copy-and-swap? | Notes | +| --- | --- | --- | --- | --- | --- | +| `ADD COLUMN` (no default / nullable) | ACCESS EXCLUSIVE (brief) | No | Yes (after lock) | ❌ No | Metadata only | +| `ADD COLUMN ... DEFAULT ` | ACCESS EXCLUSIVE (brief) | **No** (PG 11+) | Yes | ❌ No | "Fast default" stored in catalog; pre-PG11 this rewrote | +| `ADD COLUMN ... DEFAULT ` (e.g. `now()`, `random()`, `uuid_generate_v4()`) | ACCESS EXCLUSIVE | **Yes** (full rewrite) | No | ✅ **Yes** | The expensive case | +| `ADD COLUMN ... GENERATED ALWAYS AS (...) STORED` | ACCESS EXCLUSIVE | Yes | No | ✅ **Yes** | Values must be computed | +| `DROP COLUMN` | ACCESS EXCLUSIVE (brief) | No | Yes | ❌ No | Metadata only; disk space reclaimed lazily by VACUUM | +| `ALTER COLUMN TYPE` — binary-coercible (`varchar(50)→varchar(100)`, `varchar→text`, `numeric(10,2)→numeric(12,2)`) | ACCESS EXCLUSIVE (brief) | **No** | No (brief) | ❌ No | No scan when binary-coercible and no length restriction is added | +| `ALTER COLUMN TYPE` — general (`int→bigint`, `text→jsonb`, `timestamp→timestamptz` w/ conversion) | ACCESS EXCLUSIVE | **Yes** (rewrite + reindex + revalidate FKs) | No | ✅ **Yes** | The classic "needs a tool" case | +| `ALTER COLUMN SET DEFAULT` / `DROP DEFAULT` | ACCESS EXCLUSIVE (brief) | No | Yes | ❌ No | Metadata only | +| `ALTER COLUMN SET NOT NULL` | ACCESS EXCLUSIVE | No, but **full scan** | No | ➖ Native pattern | Use `NOT VALID` `CHECK` + `VALIDATE` (PG 12+) to skip the blocking scan | +| `ALTER COLUMN DROP NOT NULL` | ACCESS EXCLUSIVE (brief) | No | Yes | ❌ No | Metadata only | +| `RENAME COLUMN` | ACCESS EXCLUSIVE (brief) | No | Yes | ❌ No | Metadata only | +| `ALTER COLUMN SET STATISTICS` / `SET STORAGE` / `SET (n_distinct=...)` | SHARE UPDATE EXCLUSIVE / ACCESS EXCLUSIVE | No | varies | ❌ No | Metadata only | + +**Legend for "Needs copy-and-swap?":** +- ✅ **Yes** = genuine table rewrite with no native +online path, so the heavy **shadow-copy + atomic cutover** path is required · +- ➖ Native pattern = no copy-and-swap needed; a safe native sequence does it (`CONCURRENTLY` / +`NOT VALID`+`VALIDATE` / `USING INDEX` / fast-default) +- ❌ No = metadata-only or already online, just guard with `lock_timeout`. + +> **`Needs copy-and-swap? = No` does not mean "don't use the engine".** The engine still +> helps on the ➖ and ❌ rows — it classifies the change and runs the correct *native* +> sequence for you instead of a copy. Only the ✅ rows force a full copy. See +> 03-why-build-this-engine.md § The engine helps on every change, not just rewrites. + +> GitHub-rendered markdown tables are not interactively sortable (no client-side JS). The +> two collapsible views below are the same column operations **pre-sorted** by `Table +> rewrite` and by `Concurrent DML`. The `↓` marks the column each view is sorted on. + +
+Sorted by Table rewrite (No → No (full scan) → Yes) + +| Operation | Table rewrite ↓ | Concurrent DML | Needs copy-and-swap? | Lock held | +| --- | --- | --- | --- | --- | +| `ADD COLUMN` (no default / nullable) | No | Yes | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ADD COLUMN ... DEFAULT ` | No (PG 11+) | Yes | ❌ No | ACCESS EXCLUSIVE (brief) | +| `DROP COLUMN` | No | Yes | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN SET DEFAULT` / `DROP DEFAULT` | No | Yes | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN DROP NOT NULL` | No | Yes | ❌ No | ACCESS EXCLUSIVE (brief) | +| `RENAME COLUMN` | No | Yes | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN SET STATISTICS` / `SET STORAGE` / `SET (n_distinct=...)` | No | varies | ❌ No | SHARE UPDATE EXCLUSIVE / ACCESS EXCLUSIVE | +| `ALTER COLUMN TYPE` — binary-coercible | No | No (brief) | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN SET NOT NULL` | No, but full scan | No | ➖ Native pattern | ACCESS EXCLUSIVE | +| `ADD COLUMN ... DEFAULT ` | Yes | No | ✅ **Yes** | ACCESS EXCLUSIVE | +| `ADD COLUMN ... GENERATED ALWAYS AS (...) STORED` | Yes | No | ✅ **Yes** | ACCESS EXCLUSIVE | +| `ALTER COLUMN TYPE` — general | Yes | No | ✅ **Yes** | ACCESS EXCLUSIVE | + +
+ +
+Sorted by Concurrent DML (Yes → varies → No) + +| Operation | Concurrent DML ↓ | Table rewrite | Needs copy-and-swap? | Lock held | +| --- | --- | --- | --- | --- | +| `ADD COLUMN` (no default / nullable) | Yes | No | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ADD COLUMN ... DEFAULT ` | Yes | No (PG 11+) | ❌ No | ACCESS EXCLUSIVE (brief) | +| `DROP COLUMN` | Yes | No | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN SET DEFAULT` / `DROP DEFAULT` | Yes | No | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN DROP NOT NULL` | Yes | No | ❌ No | ACCESS EXCLUSIVE (brief) | +| `RENAME COLUMN` | Yes | No | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN SET STATISTICS` / `SET STORAGE` / `SET (n_distinct=...)` | varies | No | ❌ No | SHARE UPDATE EXCLUSIVE / ACCESS EXCLUSIVE | +| `ALTER COLUMN TYPE` — binary-coercible | No (brief) | No | ❌ No | ACCESS EXCLUSIVE (brief) | +| `ALTER COLUMN SET NOT NULL` | No | No, but full scan | ➖ Native pattern | ACCESS EXCLUSIVE | +| `ADD COLUMN ... DEFAULT ` | No | Yes | ✅ **Yes** | ACCESS EXCLUSIVE | +| `ADD COLUMN ... GENERATED ALWAYS AS (...) STORED` | No | Yes | ✅ **Yes** | ACCESS EXCLUSIVE | +| `ALTER COLUMN TYPE` — general | No | Yes | ✅ **Yes** | ACCESS EXCLUSIVE | + +
+ +### Safe pattern: `SET NOT NULL` without a blocking scan (PG 12+) + +```sql +-- 1. brief lock, no scan +ALTER TABLE t ADD CONSTRAINT t_col_nn CHECK (col IS NOT NULL) NOT VALID; +-- 2. SHARE UPDATE EXCLUSIVE, scans but allows reads+writes +ALTER TABLE t VALIDATE CONSTRAINT t_col_nn; +-- 3. now this is cheap (PG recognises the validated CHECK) +ALTER TABLE t ALTER COLUMN col SET NOT NULL; +ALTER TABLE t DROP CONSTRAINT t_col_nn; -- optional cleanup +``` + +## Index operations + +Index operations never rewrite the **heap** (the table's row data); their cost is an index +build / rebuild and the table scan(s) it requires. The "Table rewrite" column is therefore +uniformly "No" — included for consistency with the other tables. + +| Operation | Lock held | Table rewrite | Concurrent DML | Needs copy-and-swap? | Notes | +| --- | --- | --- | --- | --- | --- | +| `CREATE INDEX` | SHARE (blocks writes) | No (builds index) | **No** | ➖ Use `CONCURRENTLY` | Do not use on hot tables | +| `CREATE INDEX CONCURRENTLY` | SHARE UPDATE EXCLUSIVE | No (builds index) | **Yes** | ❌ No | Two table scans; slower; **not transactional** — a failure can leave an `INVALID` index that must be dropped and rebuilt | +| `DROP INDEX` | ACCESS EXCLUSIVE (brief) | No | No | ➖ Use `CONCURRENTLY` | | +| `DROP INDEX CONCURRENTLY` | SHARE UPDATE EXCLUSIVE | No | Yes | ❌ No | PG 9.2+ | +| `REINDEX INDEX` | ACCESS EXCLUSIVE | No (rebuilds index) | No | ➖ Use `CONCURRENTLY` | | +| `REINDEX INDEX CONCURRENTLY` | SHARE UPDATE EXCLUSIVE | No (rebuilds index) | Yes | ❌ No | PG 12+ | +| `ALTER INDEX ... RENAME` | SHARE UPDATE EXCLUSIVE | No | Yes | ❌ No | | + +## Constraint operations + +Constraint operations never rewrite the **heap** either; the heavy cost is the **validation +scan** (or, for a directly-added PK/UNIQUE, an index build). "Table rewrite" is therefore +uniformly "No"; the "Validation scan" column captures the part that actually costs time. + +| Operation | Lock held | Table rewrite | Validation scan | Concurrent DML | Needs copy-and-swap? | Notes | +| --- | --- | --- | --- | --- | --- | --- | +| `ADD PRIMARY KEY` / `ADD UNIQUE` (direct) | ACCESS EXCLUSIVE | No | Index build (blocks) | No | ➖ Use `USING INDEX` | Avoid; build the index concurrently first | +| `ADD PRIMARY KEY / UNIQUE USING INDEX ` | ACCESS EXCLUSIVE (brief) | No | No | No (brief) | ❌ No | Pattern: `CREATE UNIQUE INDEX CONCURRENTLY` then attach | +| `ADD CHECK` / `ADD FOREIGN KEY` (default) | ACCESS EXCLUSIVE | No | **Full scan (blocks)** | No | ➖ Use `NOT VALID` | Blocks for the whole scan | +| `ADD CHECK / FOREIGN KEY ... NOT VALID` | ACCESS EXCLUSIVE (brief) | No | No | Yes | ❌ No | First step of the safe pattern | +| `VALIDATE CONSTRAINT` | SHARE UPDATE EXCLUSIVE | No | Scan (non-blocking) | **Yes** | ❌ No | Safe second step | +| `DROP CONSTRAINT` | ACCESS EXCLUSIVE (brief) | No | No | Yes | ❌ No | | + +### Safe pattern: add a foreign key / check without a blocking scan + +```sql +ALTER TABLE child ADD CONSTRAINT fk FOREIGN KEY (pid) REFERENCES parent (id) NOT VALID; -- brief lock +ALTER TABLE child VALIDATE CONSTRAINT fk; -- SHARE UPDATE EXCLUSIVE, online +``` + +### Safe pattern: add a primary key / unique constraint online + +```sql +CREATE UNIQUE INDEX CONCURRENTLY t_pkey ON t (id); -- online build +ALTER TABLE t ADD CONSTRAINT t_pkey PRIMARY KEY USING INDEX t_pkey; -- brief lock +``` + +## Table / partition operations + +| Operation | Lock held | Table rewrite | Concurrent DML | Needs copy-and-swap? | Notes | +| --- | --- | --- | --- | --- | --- | +| `RENAME TABLE` | ACCESS EXCLUSIVE (brief) | No | Yes | ❌ No | Metadata only — basis for cutover swap | +| `SET SCHEMA` | ACCESS EXCLUSIVE (brief) | No | Yes | ❌ No | | +| `SET TABLESPACE` | ACCESS EXCLUSIVE | **Yes** (moves heap) | No | ✅ **Yes** (repack-style) | Rewrite/move; use a repack-style copy instead | +| `SET (fillfactor=...)` and most reloptions | SHARE UPDATE EXCLUSIVE | No | Yes | ❌ No | Applies to new rows | +| `CLUSTER` / `VACUUM FULL` | ACCESS EXCLUSIVE | **Yes** (full rewrite) | No | ✅ **Yes** (`pg_repack`) | Use `pg_repack` | +| `ATTACH PARTITION` | SHARE UPDATE EXCLUSIVE on parent + scan of child | No | Yes | ➖ Native pattern | Add a validated `CHECK` matching the bound on the child first to skip the scan | +| `DETACH PARTITION` | ACCESS EXCLUSIVE | No | No | ➖ Use `CONCURRENTLY` | | +| `DETACH PARTITION CONCURRENTLY` | SHARE UPDATE EXCLUSIVE | No | Yes | ❌ No | PG 14+ | + +## The headline takeaway (what the engine must cover) + +The operations that genuinely need a **log-based copy-and-swap** (shadow-table copy + atomic +cutover) are the **table-rewrite** ones, because PostgreSQL cannot do them online natively: + +- general `ALTER COLUMN TYPE` (`int→bigint`, `text→jsonb`, etc.) +- `ADD COLUMN` with a **volatile** default +- adding a `STORED` generated column +- column reordering / table repack (bloat removal) +- changing a column to/from `NOT NULL` on very large tables where even the scan is too long + +**Everything else can be done natively-safe** with PostgreSQL's own +`CONCURRENTLY` / `NOT VALID`+`VALIDATE` / fast-default / `USING INDEX` patterns. By the +[*classify-before-copy* principle](design-principles.md#classify-first-leverage-native-postgresql), +the engine **detects those and routes them to native DDL** +instead of doing a copy — the same bypass Spirit applies when it attempts `INSTANT`/`INPLACE` +before falling back to a table copy. A shadow-table copy is the last resort, used only when no +native online path exists. + +## Aurora PostgreSQL specifics + +- **Lock behaviour is identical** to community PostgreSQL — Aurora uses the same query + engine; only storage/replication differs. +- **Reader instances** serve `ACCESS EXCLUSIVE`-blocked reads from the same WAL stream, so + a blocking DDL on the writer still stalls readers of that table. +- **`CREATE INDEX CONCURRENTLY` interacts with long-running reader transactions**: it waits + for transactions that can see the table to finish, including on Aurora replicas. A + long analytics query on a reader can stall a concurrent index build on the writer. +- For the engine's CDC path, Aurora supports **logical replication / logical decoding**, but + it must be enabled via the `rds.logical_replication=1` cluster parameter (static → + requires a reboot) which sets `wal_level=logical`. See + [low-level-design.md](low-level-design.md). diff --git a/docs/postgresql-version-support.md b/docs/postgresql-version-support.md new file mode 100644 index 0000000..f7f9f1d --- /dev/null +++ b/docs/postgresql-version-support.md @@ -0,0 +1,112 @@ +# PostgreSQL version support across the OSS tools (and the version we pivot on) + +**We intend to support `pg-sprite` starting from PostgreSQL / Aurora PostgreSQL 14**, and to +validate it against the Aurora-supported matrix 14 → 18. PostgreSQL 14 is the engine's minimum +target; we do not intend to support PostgreSQL 13 or earlier. The rest of this doc is the +evidence behind that floor. + +Before fixing that **minimum PostgreSQL version**, it helps to see what the existing OSS +online-DDL tools actually support, what Aurora PostgreSQL itself still ships, and which +PostgreSQL release each *native idiom* we rely on first appeared in. This doc collects all +three and derives the version floor. + +All figures are sourced from each tool's own README / docs / CI matrix and from the +[Aurora PostgreSQL release calendar](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraPostgreSQLReleaseNotes/aurorapostgresql-release-calendar.html) +as of **June 2026**; they drift, so re-check before relying on an exact bound. + +## Table of contents + +- [Tool support matrix](#tool-support-matrix) +- [Aurora PostgreSQL supported majors](#aurora-postgresql-supported-majors) +- [Native-idiom version floors](#native-idiom-version-floors) +- [The version we pivot on](#the-version-we-pivot-on) + +## Tool support matrix + +| Tool | Pattern | Min PG | Max PG | Form factor | Notes | +| --- | --- | ---: | ---: | --- | --- | +| **pgroll** ([xataio](https://github.com/xataio/pgroll)) | B — expand/contract via versioned views | **14.0** | no stated cap (CI/benchmarks through **18**) | Go single binary; no server extension | PG 14 caveat: versioned views can't use `security_invoker = true` (added PG 15), so **RLS tables are unsafe on PG 14** | +| **pg-osc** ([shayonj](https://github.com/shayonj/pg-osc)) | A — trigger + shadow-table + swap | **9.6** | no stated cap (smoke-tested **9.6, 13.6**) | Ruby gem / Docker | Needs `TRIGGER` priv or `SUPERUSER`; requires a PK; low recent activity (latest v0.9.10, Oct 2024) | +| **pg_repack** ([reorg](https://github.com/reorg/pg_repack)) | A — repack-scoped copy + swap | **9.5** | **19** | C **server extension** (must be installed on the instance) | Client binary version **must match** the in-DB extension version; RDS/Aurora-approved extension; requires PK or UNIQUE-NOT-NULL | + +The headline: **the trigger-based tools reach back to 9.x; pgroll is the one that draws a +hard `>= 14` line** — and 14 is precisely the floor that also unlocks the native idioms below +and matches Aurora's supported majors. + +## Aurora PostgreSQL supported majors + +What Aurora actually runs bounds what we must support. As of June 2026: + +| Aurora PG major | Standard-support status | +| ---: | --- | +| 18 | ✅ Supported (GA June 2026) | +| 17 | ✅ Supported | +| 16 | ✅ Supported | +| 15 | ✅ Supported | +| 14 | ✅ Supported (end of standard support **Feb 2027**) | +| 13 | ❌ End of standard support **28 Feb 2026** (RDS Extended Support only) | +| 12, 11 | ❌ End of standard support passed (Extended Support only) | + +So the **realistic Aurora target window is 14 → 18**. Building for anything below 14 means +building for engines AWS no longer offers under standard support. + +## Native-idiom version floors + +The whole point of [classify-first](design-principles.md#classify-first-leverage-native-postgresql) +is to run the safe native sequence (see +[postgres-online-ddl-reference.md](postgres-online-ddl-reference.md)). Each of those +idioms has a minimum PostgreSQL version: + +| Native idiom (classify-first path) | Available since | Used for | +| --- | ---: | --- | +| Fast default — `ADD COLUMN ... DEFAULT ` without a rewrite | **PG 11** | cheap `ADD COLUMN` with a constant default | +| `ADD CONSTRAINT ... NOT VALID` then `VALIDATE` (incl. the `SET NOT NULL` via validated `CHECK` trick) | **PG 12** | online `SET NOT NULL`, FK, CHECK | +| `REINDEX INDEX CONCURRENTLY` | **PG 12** | online index rebuild / repack-lite | +| `CREATE INDEX CONCURRENTLY`, `ADD ... USING INDEX`, `... NOT VALID` (FK/CHECK) | PG 9.x–11 | concurrent index build, online PK/UNIQUE/FK | +| `DETACH PARTITION CONCURRENTLY` | **PG 14** | online partition detach | +| `security_invoker` views (only needed by the **pgroll** expand/contract backend) | **PG 15** | RLS-safe versioned views | + +Every native idiom the engine depends on is available **at or below PG 14**, except +`security_invoker` views — and those are only relevant to the optional pgroll backend, where +pgroll itself already documents the PG 14 RLS limitation. + +## The version we pivot on + +**Target a minimum of PostgreSQL / Aurora PostgreSQL 14, and validate against the +Aurora-supported matrix 14 → 18 (currently centring on 16/17 LTS).** + +Why 14 is the right floor: + +- **It matches the most restrictive reusable backend.** pgroll (our expand/contract executor) + hard-requires `>= 14`; picking any lower floor would mean the pgroll path is unavailable on + part of our supported range — an inconsistent engine. +- **It matches Aurora reality.** 14 is the oldest Aurora major still under standard support; + 13 and below are EOL/Extended-Support-only. We should not engineer for engines AWS is + sunsetting. +- **Every classify-first native idiom is present.** Fast default (PG 11), `NOT VALID` + + `VALIDATE` and `REINDEX CONCURRENTLY` (PG 12), and `DETACH PARTITION CONCURRENTLY` (PG 14) + are all available, so the native executor has its full toolkit at the floor. +- **Logical decoding is mature — and 14 is specifically where it stops hurting.** The log-based + CDC differentiator (logical decoding via `rds.logical_replication`) is well-established by 14, + so the copy-and-swap path's primary CDC source is solid across the whole target range. Just as + important, **PostgreSQL 14 added streaming of in-progress transactions** (`pgoutput` + protocol v2): before 14, a large transaction is buffered/spilled to disk and only emitted to + the consumer *at commit*, which adds apply lag and inflates slot/WAL retention for exactly the + big batch writes a long migration runs alongside. PG 14 streams those changes incrementally, + and PG 16 adds *parallel apply* (protocol v4). The serialized, commit-ordered apply stream is a + real throughput ceiling for CDC catch-up (see + risks-and-mitigations); 14+ is where the worst + of that is mitigated, which is an independent reason the floor is 14 and not 12/13. + +What this *excludes* and why it's fine: + +- **PG ≤ 13.** Below standard support on Aurora; the trigger-based tools (pg-osc, pg_repack) + still cover those engines if a one-off change is ever needed there, so we lose nothing by + not targeting them ourselves. +- **`security_invoker`-dependent RLS on PG 14.** Only affects the optional pgroll backend on + exactly PG 14; the native and copy-and-swap executors are unaffected, and PG 15+ removes the + limitation entirely. + +See why-build-this-engine.md for why we reuse these tools as +executors rather than replace them, and build-plan.md for how the +version floor feeds the phased build. diff --git a/docs/schemabot-integration.md b/docs/schemabot-integration.md new file mode 100644 index 0000000..332a686 --- /dev/null +++ b/docs/schemabot-integration.md @@ -0,0 +1,121 @@ +# SchemaBot integration + +pg-sprite is **orchestrator-neutral**: the engine is a standalone CLI, and an orchestrator +drives it through a thin adapter — nothing in the core engine changes for it. The reference +orchestrator is [SchemaBot](https://github.com/block/schemabot), which already drives +[Spirit](https://github.com/block/spirit) for Aurora MySQL through a pluggable engine +abstraction; `pg-sprite` ships as a new engine behind that same interface. This doc is the +**single home** for that integration — everywhere else the doc set says "the orchestrator" and +points here. + +## Table of contents + +- [Overview](#overview) +- [Verb mapping (conceptual)](#verb-mapping-conceptual) +- [The concrete contract](#the-concrete-contract) +- [Design constraints the integration imposes](#design-constraints-the-integration-imposes) + +## Overview + +The end goal is for SchemaBot to drive `pg-sprite` for Aurora PostgreSQL exactly the way it +drives Spirit for Aurora MySQL today — same PR workflow, same operator verbs, same status +surfacing: + +- SchemaBot's **migration-engine interface** is the plug-in boundary: plan, apply, progress, + and the control verbs (stop / start / cutover / cancel / revert / volume). Spirit + (`type: mysql`) and PlanetScale (`type: vitess`) are existing implementations; `pg-sprite` + becomes a third (e.g. `type: postgres`). +- The **orchestration layer** is engine-neutral. It hands each change to the engine, polls + progress, and turns control requests (`stop` / `start` / `cutover`, volume 1–11) into + durable, owner-processed operations. Engines are selected by database `type`. + +```diagram + PR comment / CLI / API + │ + ╭────────▼─────────╮ ╭─────────────────────────────────╮ + │ orchestration │ │ migration-engine impls │ + │ layer │────▶│ • spirit (type: mysql) │ + │ plans, applies, │ │ • planetscale (type: vitess) │ + │ control reqs │ │ • pg-sprite (type: postgres) │ ◀── this engine + ╰──────────────────╯ ╰────────────────┬────────────────╯ + ▼ + Aurora PostgreSQL +``` + +## Verb mapping (conceptual) + +The decoupled [planner → router → executor](high-level-design.md) design exists partly to make +this mapping clean — the orchestrator's engine verbs line up almost one-to-one with the layers: + +| Engine verb | `pg-sprite` responsibility | +| --- | --- | +| plan | **Planner**: classify the change / diff declarative schema; return an engine-neutral table change (pure, no side effects) | +| apply | **Router + Executor**: pick native / copy-and-swap / expand-contract and run it **asynchronously** | +| progress | per-table rows-copied / total / percent / ETA / checksum state | +| stop / start | checkpoint and resume (slot + copy + applier state) | +| cutover (+ deferred cutover) | the deferred, operator-gated atomic swap | +| volume | chunk-time / parallelism / throttle level (1–11) | +| cancel | abort and **guarantee logical-slot + shadow-table cleanup** | +| revert | only if the chosen executor supports it (Spirit declines this; see the [reversibility principle](design-principles.md#correctness-and-safety)) | + +## The concrete contract + +**The interface to implement.** `engine.Engine` in `github.com/block/schemabot/pkg/engine`: +`Name`, `Plan`, `Apply` (async), `Progress`, and the controls `Stop` / `Start` / `Cutover` / +`Cancel` / `Revert` / `SkipRevert` / `Volume` (1=slowest … 11=fastest). A PostgreSQL stub +already exists at `pkg/engine/postgres` (`postgres.New()`, methods currently return *"postgres +engine not implemented"*) with a compile-time `var _ engine.Engine` assertion — that stub is +where the adapter is filled in. (Re-verify these specifics against SchemaBot's current API when +the integration phase starts; they drift.) + +**Verb → engine mapping (concrete):** + +| `engine.Engine` method | `pg-sprite` implementation | +| --- | --- | +| `Name()` | a stable identifier, e.g. `"pg-sprite"` | +| `Plan` | run the planner: classify / declarative-diff; return a `PlanResult` whose `SchemaChange.TableChanges` are `engine.TableChange{Table, Operation (statement.StatementType), DDL, IsUnsafe, UnsafeReason}` | +| `Apply` | start the chosen executor asynchronously; return immediately | +| `Progress` | per-table rows-copied / total / percent / ETA / checksum state | +| `Stop` / `Start` | checkpoint and resume (slot + copy + applier watermark) | +| `Cutover` | the deferred, operator-gated atomic swap | +| `Cancel` | abort and **guarantee logical-slot + shadow-table cleanup** | +| `Volume` | map 1–11 onto chunk-time target / parallelism / throttle | +| `Revert` / `SkipRevert` | decline for the copy-and-swap path (like Spirit); only the expand/contract backend could honour them | + +**Registration / selection.** The orchestrator core is `pkg/tern`; `tern.NewLocalClient` has +built-in branches for `storage.DatabaseTypeMySQL` (Spirit) and `storage.DatabaseTypeVitess` +(PlanetScale), and looks up everything else in `LocalConfig.EngineFactories[type]` (an +`EngineFactory func(LocalConfig, *slog.Logger) (engine.Engine, error)`). Server embedders +register via `serve.WithEngine(databaseType, factory)` so the core doesn't import the engine +package. Landing this is one of: + +- register a `postgres` `EngineFactory` (no core changes), or +- add a built-in branch + a `storage.DatabaseType…`/`storage.Engine…` constant + a + `tern.proto` `enum Engine` value and its `engineNameToProto` mapping. + +**Optional capability interfaces** (type-asserted by `LocalClient`): + +- `engine.ExternallyAuthoritativeProgress` — return `true` only if progress is read from + durable state any instance can query (PlanetScale returns `true`). +- `engine.DeferredCutoverSignalChecker` — `DeferredCutoverSignalExists` for durable + deferred-cutover recovery (Spirit implements it). +- `engine.Drainer` — `Drain()` to flush in-flight background work on sequential resume. + +## Design constraints the integration imposes + +These shape the engine's state and API surface from day one, long before the adapter exists — +they are registered as the `OC-*` invariants in +[invariants § orchestration / control-plane](invariants.md#orchestration--control-plane-oc): + +- **Keep PostgreSQL-only machinery inside the adapter.** Logical-decoding slot create/cleanup, + `REPLICA IDENTITY`, `rds.logical_replication` preflight, and the trigger fallback all live + behind `Apply`/`Stop`/`Cancel` so the engine-neutral orchestration layer stays untouched + (OC-6). +- **Shared types stay engine-agnostic** — engine-specific data rides in generic + `Metadata map[string]string` fields (OC-6). +- **ID namespaces never conflate** — the engine's migration identifier is an opaque + `external_id` to the orchestrator; the orchestrator's user-facing identifier is never routed + to the engine (OC-5). +- **The orchestrator is an untrusted request source** like the CLI: control requests are + re-validated against current engine state, fail-closed (OC-1..OC-4; see + [tcb-model.md](tcb-model.md)). diff --git a/docs/tcb-model.md b/docs/tcb-model.md new file mode 100644 index 0000000..ba12912 --- /dev/null +++ b/docs/tcb-model.md @@ -0,0 +1,251 @@ +# The TCB model: a small trusted core, an untrusted periphery + +pg-sprite rewrites production tables in financial systems — a bug is silent data corruption or +an app-wide outage. That puts its core in the **mission-critical** class, and the right structure +for mission-critical software is a **Trusted Computing Base (TCB)**: identify the invariants that +must never be violated, put the code that enforces them behind a small boundary, treat that +boundary as trusted, and treat everything outside as untrusted. Untrusted code may *request* +dangerous operations as many times as it likes — the TCB ensures correctness anyway. + +The [invariant registry](invariants.md) is step one of that recipe. This doc is the rest: +**where the boundary sits, how illegal states are made unrepresentable, and which engineering +rules apply inside the boundary** — drawn from codebases that live or die by this model: +[qmail](https://cr.yp.to/qmail.html) (mutually-distrustful partitioning), +[bitcoin-core](https://github.com/bitcoin/bitcoin) (consensus-critical code isolation), +[s2n-tls](https://github.com/aws/s2n-tls/blob/main/docs/DEVELOPMENT-GUIDE.md) (cognitive-load +minimalism, priority ordering), [TigerBeetle's +TIGER_STYLE](https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md) +(assertion discipline, put a limit on everything), and +[HTMX's Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/). + +## Table of contents + +- [The boundary](#the-boundary) +- [The trust rule: the TCB never trusts its callers](#the-trust-rule-the-tcb-never-trusts-its-callers) +- [Make illegal states unrepresentable](#make-illegal-states-unrepresentable) +- [Rules inside the TCB](#rules-inside-the-tcb) +- [Dependencies inside the TCB become part of the TCB](#dependencies-inside-the-tcb-become-part-of-the-tcb) +- [The verification ladder](#the-verification-ladder) +- [AI-assisted development policy](#ai-assisted-development-policy) +- [What this changes concretely](#what-this-changes-concretely) + +## The boundary + +Membership test: *can a bug here violate a [invariants.md](invariants.md) invariant — +corrupt data, lose writes, swap in a wrong table, strand a slot, or take the application down?* +If yes, it is TCB. If a bug here can only produce a wrong message, an ugly plan, a wasted copy, +or a missed optimization, it is periphery. + +| Component | TCB? | Invariants it enforces | +| --- | --- | --- | +| checksum engine (incl. continuous checker, repair) | ✅ | CO-1, CO-2, CO-3 | +| copier + applier write paths (chunk SQL, flush scheduling, change buffer) | ✅ | CO-4, CO-5, CO-6, LK-3 | +| decode position accounting (slot LSN, snapshot coordination) | ✅ | ST-4, CO-4 | +| cutover (swap txn, final drain, fidelity gate, ambiguity resolution) | ✅ | LK-2, LK-4, ST-5 | +| checkpoint store (write/read/validate) | ✅ | ST-1, ST-2 | +| slot lifecycle (create, reap, lag ceiling) | ✅ | ST-3 | +| `dbconn` dangerous primitives (advisory lock, terminate-blockers, session timeouts) | ✅ | LK-1, LK-2 | +| preflight verifier | ✅ | ST-6, RF-1..RF-5 | +| planner / classifier / router | ⚠️ outside, see below | — | +| declarative diff + `fmt` | ❌ | (its *output* is re-classified and re-preflighted inside) | +| CLI, flags, help, prompts, `--force` UX | ❌ | (the force *gate decision* is TCB; the prompt rendering is not) | +| status / progress / ETA rendering, advisory text, lint wording | ❌ | — | +| metrics/observability emission | ❌ | — | +| orchestrator adapter ([schemabot-integration.md](schemabot-integration.md), Phase 11) | ❌ | OC-5, OC-6 hold *at* the boundary | + +**The planner is deliberately outside.** Its verdicts are *requests*, not permissions — the Notes +.app / cold-storage pattern from the TCB model: transaction *generation* is untrusted because the +signer enforces policy. Concretely: if the classifier wrongly says "native-safe", the native +executor's own `lock_timeout` bound (LK-2) caps the damage; if it wrongly says "copy", the result +is a wasteful but *correct* migration (checksum still gates). The router may choose a bad +strategy; it must never be *able* to cause a wrong result. This is also why the copy-and-swap +executor re-runs preflight itself rather than trusting that the planner did. + +qmail's lesson applies directly: the components are **mutually distrustful**, and each does one +thing. bitcoin-core's lesson is organizational: consensus-critical code gets a different review +bar, a different rate of change, and explicit isolation (`libbitcoinkernel`) — our analog is the +TCB package list above, enforced in CI (see [below](#what-this-changes-concretely)). + +## The trust rule: the TCB never trusts its callers + +Every dangerous operation is behind a trusted interface that **re-verifies its own +preconditions**, regardless of what the caller claims: + +- `cutover` does not take the planner's word that the shadow is ready — it demands a + `VerifiedShadow` (below) and re-checks the fidelity gate (ST-5) inside its own transaction. +- The executors re-run preflight (ST-6) even though the CLI ran it for the dry-run display. +- Control verbs (`stop`/`start`/`cutover`/`cancel`) re-validate current state before acting + (OC-1, OC-4): a `cutover` request against an unverified shadow is refused, not trusted. +- The orchestrator, the CLI, and any future API are all the same thing to the TCB: + **untrusted request sources.** They can retry forever; they cannot make the engine do the wrong thing. + +## Make illegal states unrepresentable + +The domain-type rule: *a domain type may carry the same data as the raw input, +but the domain type proves validation has occurred* — and downstream code accepts only the domain +type. In Go we get this with unexported fields + package-private constructors: the **only** way +to obtain the type is through the function that validates it. + +| Raw / untrusted | Validating passage | Domain type (proof) | Encodes | +| --- | --- | --- | --- | +| `string` (user SQL) | `statement.Parse` | `statement.Classified` | CO-7 — nothing downstream touches unparsed SQL | +| table name | preflight | `PreflightedTable` (carries the proven facts: PK, no FKs/views, replica identity, headroom) | ST-6, RF-* | +| shadow table | full checksum pass | `VerifiedShadow` — constructor private to `pkg/checksum`; `cutover.Swap` accepts **only** this type | CO-1 in the type system | +| chunker low-watermark | all-checkers-clean pass | `CleanWatermark` — unobtainable in a pass that repaired anything | CO-2 | +| — | `dbconn.AcquireTableLock` | `TableLock` token, a required parameter of every mutating operation | LK-1 | +| orchestrator proto/request | adapter validation at the edge | engine domain types; proto types never cross into the engine | OC-5, OC-6 | + +The compile-time effect: **the cutover cannot be called with an unverified shadow because no such +call type-checks.** An LLM (or a tired human) writing periphery code cannot hand a raw string to +the router or a fresh shadow to the swap — the types refuse. This is the cheapest, most durable +enforcement we have; runtime assertions (below) are the second layer for what Go's types can't +express. + +## Rules inside the TCB + +Adopted from the named codebases; these apply to TCB packages and are advisory elsewhere. + +**Priorities, in order (s2n-tls, adapted):** Correctness → Readability → Ease of use → +Performance. When a trade-off is hard, the higher priority wins. This is +[safety over speed](design-principles.md) made operational for code review. + +**Put a limit on everything (TIGER_STYLE).** Every loop bounded, every queue bounded, every +retry counted, every wait deadlined. We already have instances — bounded change buffer, retry +attempts, chunk target time, slot-lag ceiling, `lock_timeout` on everything — the rule makes +them the *default*: an unbounded anything in a TCB package is a review-blocking defect. Where a +loop is intentionally endless (the applier's consume loop), that must be stated and its exit +conditions asserted. + +**Assert the positive and the negative space (TIGER_STYLE).** Assertions detect programmer +errors; operating errors get error handling. In a migration engine, "crash on corrupt logic" is +correct *before* the swap — a failed migration is recoverable (resume/abort), a wrong swap is +not. Concretely: + +- Assert preconditions and postconditions of every TCB function on data it did not produce. +- **Pair assertions** across boundaries where data crosses valid/invalid lines: chunk boundaries + asserted where the copier produces them *and* where the checksum consumes them; the LSN + asserted where decode records it *and* where the drain claims completion; row counts asserted + before write and after read-back. +- Invariant violations map to a distinct error class (`ErrInvariantViolation`) that always + aborts fail-closed and names the [invariants](invariants.md) ID — never a warning, never retried. + +**Simple, explicit control flow (s2n-tls + TIGER_STYLE).** Linear happy path, branch on failure +(Go's early-return idiom is s2n's `GUARD` pattern natively); treat `else` with suspicion; push +`if`s up and `for`s down — the parent function owns control flow and state transitions, leaf +functions stay pure; no recursion in TCB packages; explicit state machines with typed states +rather than booleans that can disagree. + +**Minimize state (TIGER_STYLE).** Derive rather than store; re-derive rather +than persist when possible. The checkpoint (ST-1) holds the *minimum* resumable state — +watermark, slot name, LSN, statement fingerprint, engine version — everything else is +reconstructed from the database on resume. Small state is what makes "work out all system state +by hand" possible during an incident. + +**Locality of behavior (HTMX).** The enforcement of an invariant is *local and visible where it +matters*: the code that enforces CO-2 carries a `// INV: CO-2` comment at the enforcement point, +so a reviewer (or an agent) greps the ID and sees the entire enforcement in one screen — the +Spirit `runner.go` watermark comment is the exemplar. Don't smear one invariant's enforcement +across three packages; if the protocol spans components (CO-4), each side asserts its half and +names the shared ID. + +**Function-size discipline (TIGER_STYLE's 70-line rule, softened to a review heuristic):** if a +TCB function doesn't fit on a screen, look for the hourglass shape — few parameters, meaty pure +middle, simple return. + +## Dependencies inside the TCB become part of the TCB + +A dependency inside the boundary is code we ship with full trust — so the list is explicit and +short: + +| Dependency | Status | Treatment | +| --- | --- | --- | +| `pgx/v5` / `pgconn` | TCB (unavoidable — the wire) | pin, review upgrades like TCB changes, changelog read before bump | +| `pglogrepl` | TCB (decode path) | same | +| `pg_query_go` | boundary (parses untrusted input into `Classified`) | fuzz at our boundary; parse failure is an error (CO-7), never a fallback | +| `kong`, `testcontainers`, testify | periphery / test-only | normal hygiene | + +Rule: **no new dependency inside TCB packages without an explicit recorded decision.** CI +enforces the import boundary (below), so a periphery-only dep physically cannot creep into the +core. The decision rubric, in order: + +1. **Is it load-bearing expertise?** A real SQL grammar (`pg_query_go`), the wire protocol + (`pgx`/`pglogrepl`), crypto — take the dependency, pin it, treat it as TCB. Hand-rolling a + SQL parser to avoid a dependency would be the *opposite* of safety (CO-7 exists because + string-splitting SQL is how tools corrupt data). +2. **Could ~100 lines of copied code do the job?** Then ["a little copying is better than a + little dependency"](https://go-proverbs.github.io/) (Go proverbs): copy it, with an + attributing comment. Retry/backoff, CA-bundle loading, and the keepalive loop are + hand-written for exactly this reason — already the Phase 0 practice. +3. **Neither?** Then the feature is probably too big for its value — reconsider the feature + before reconsidering the rule. + +One decision worth recording now because it will tempt every phase: **pg-sprite must not import +`block/spirit` as a Go module.** We port Spirit's *ideas* — the invariants in +[invariants](invariants.md) carry file-level citations precisely so the lineage survives without a +code dependency. Importing it would drag the MySQL toolchain (go-mysql, the TiDB parser) into +our module graph, couple our releases to Spirit's cadence, and blur the "separate, purpose-built +tool" stance of [low-level-design](low-level-design.md). Small helpers worth having (`CloseAndLog`-style +cleanup, status/state-machine shapes) are copied and re-owned, not imported. (the orchestrator importing *us* is the +correct direction of that arrow.) + +## The verification ladder + +Testing is necessary but not sufficient; each rung catches what the previous +can't. The [phase mapping in 17](invariants.md#build-phase-mapping) says *when*; this says +*what kind*: + +1. **Unit + integration against real PostgreSQL** — the floor, already policy + (build-plan, test-first, no mocked-DB core tests). +2. **Property-based tests** (`pgregory.net/rapid` — already in our module graph via pgx) for the + TCB's algorithmic hearts: the chunker (*property: chunks exactly partition the PK space — no + gap, no overlap — for random PK distributions*), the change buffer (*after any event sequence, + at most one entry per PK holding the latest image* — CO-5), and the applier convergence + protocol (*for random interleavings of chunk-copy and backlog-flush, the shadow converges* — + CO-4, CO-6 including unique-value moves). +3. **Deterministic-interleaving tests**: the copier/applier race tests (Phase 6) drive + interleavings through injected scheduling points rather than sleeps, so every race in + [low-level-design § copy/apply ordering](low-level-design.md#copy-and-apply-ordering-the-core-correctness-subtlety) + is reproducible, not probabilistic. (The affordable slice of TigerBeetle's simulation-testing + idea.) +4. **Fuzzing** (Go native fuzzing) at trust boundaries: statement input → parser/classifier + (must classify, refuse, or error — never panic, never misroute), checksum expression + generation over adversarial column types/collations (CO-1's determinism). +5. **Model checking (optional, high value): TLA+/PlusCal for the two real state machines** — the + cutover protocol (LK-2/LK-4: lock, drain-to-LSN, swap, ambiguity resolution, retry) and the + checkpoint/resume/slot-loss machine (ST-1..ST-4, CO-2's watermark invalidation). These are + exactly the "distributed handshake" shapes TLA+ pays off on; Kani/CBMC don't apply to Go, and + this is our equivalent. Scoped as a build-tracker task, not a build-plan gate. + +Assertions multiply all of this (TIGER_STYLE): the property tests and fuzzers find bugs by +tripping TCB assertions, not just by comparing final outputs. + +## AI-assisted development policy + +The AI-assistance posture differs per side of the boundary: + +- **Inside the TCB: less AI, more steering.** Detailed spec first (05 + the 17 invariant IDs are + the spec), test-first with the invariant's named test obligation, small diffs, careful review + against the rules above. Agents are *excellent* here precisely because the invariants are + written down — but the human owns the mental model (TIGER_STYLE: assertions are a safety net, + not a substitute for understanding). +- **Outside the TCB: more AI, less steering.** Status rendering, advisory wording, docs, CLI + ergonomics — iterate at inference speed; the boundary means a bug here cannot corrupt data. +- **[AGENTS.md](../AGENTS.md) encodes the split**: a short section + naming the TCB packages and the stricter rules that apply inside — and stays short otherwise + (don't restate what an agent can infer from the code). +- **Pinned dev environment + one entry point**: hermit-pinned toolchain and `make + build/test/lint` as the only entry points, so agents never burn effort on environment drift. + +## What this changes concretely + +1. **Repo layout marks the boundary.** TCB packages are enumerated (the repo-root [`SAFETY.md`](../SAFETY.md), listing them and linking here and to the invariant registry), and CI enforces it: an import-boundary lint (depguard) pins which dependencies + TCB packages may import, and `CODEOWNERS` routes TCB paths to maintainer review (the + bitcoin-core discipline). +2. **`cutover`/`swap` API takes domain types only** — the `VerifiedShadow`/`CleanWatermark`/ + `TableLock` types land with their producing packages (Phases 4–7), not retrofitted. +3. **`ErrInvariantViolation`** error class + the `// INV: ` convention land in Phase 0/1 + code as it grows. +4. **The enforcement backlog:** [SAFETY.md](../SAFETY.md) + depguard + CODEOWNERS, the + property/fuzz suites per rung above, and the optional TLA+ models for cutover and resume. +5. **The periphery stays free.** None of this doc applies review friction to status text, CLI + help, or docs — that's the point of having a boundary. From 53c9a33b7e82678bf3b70dcbae41b8fae352f21f Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Thu, 9 Jul 2026 10:43:51 +1000 Subject: [PATCH 04/12] docs: add stripe/pg-schema-diff as declarative-diff candidate (open decision 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It already does the hard parts of the declarative front-end (server-canonicalized desired state via a temp database, dependency-ordered plans, hazard annotations, plan validation) and declares shadow-table rewrites out of scope — exactly where our copy-and-swap starts. Recorded as an open decision: wrap it behind the SchemaDiff seam vs build on pg_query_go; adopt its hazard taxonomy and plan-validation idea either way. --- docs/README.md | 4 +++- docs/architecture.md | 2 +- docs/low-level-design.md | 44 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 8d3c6b0..5a44e96 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,9 @@ Research and design notes for building an online schema migration engine targeti **Aurora PostgreSQL**, by deriving and combining the best practices from established tools — [Spirit](https://github.com/block/spirit) (Aurora MySQL), [pg_osc](https://github.com/shayonj/pg-osc), [pg_repack](https://github.com/reorg/pg_repack), -and [pgroll](https://github.com/xataio/pgroll) — rather than porting any single one of them. +[pgroll](https://github.com/xataio/pgroll), and +[pg-schema-diff](https://github.com/stripe/pg-schema-diff) (declarative diffing) — rather than +porting any single one of them. ## Table of contents diff --git a/docs/architecture.md b/docs/architecture.md index 2e01c43..865c423 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -54,7 +54,7 @@ boundary) is defined in [../SAFETY.md](../SAFETY.md). | `pkg/dbconn` | Pool with bounded session timeouts, retries, RDS/Aurora auto-TLS (embedded CA bundle), terminate-blockers; advisory-lock mutual exclusion lands here | exists | | `pkg/statement` | `pg_query_go` parsing + classification (never hand-parse SQL) | Phase 1–2 | | `pkg/preflight` | Precondition verification and refusals before any write | Phase 1–2 | -| `pkg/planner` / `pkg/schemadiff` / `pkg/lint` | Shared front-end: introspect, declarative diff, classify, lint | Phase 2 | +| `pkg/planner` / `pkg/schemadiff` / `pkg/lint` | Shared front-end: introspect, declarative diff (may wrap [stripe/pg-schema-diff](https://github.com/stripe/pg-schema-diff) — see the low-level design's open decisions), classify, lint | Phase 2 | | `pkg/executor` | The `Executor` contract (`Plan`/`Execute`/`Status`/`Abort`) + native executor | Phase 2–3 | | `pkg/table` | PK-range chunkers (single-column fast path, composite), dynamic time-based sizing | Phase 4 | | `pkg/copier` | Parallel chunked copy into the shadow table (never overwrites) | Phase 4 | diff --git a/docs/low-level-design.md b/docs/low-level-design.md index 7954a54..6cb45e8 100644 --- a/docs/low-level-design.md +++ b/docs/low-level-design.md @@ -40,6 +40,7 @@ for how the Spirit original works and tool-pgroll.md for pgroll. - [2. Scope of v1](#2-scope-of-v1) - [3. Repo location / language](#3-repo-location--language) - [4. Expand/contract (pgroll) as a second execution backend](#4-expandcontract-pgroll-as-a-second-execution-backend) + - [5. Declarative diff engine — build on pg_query_go vs wrap pg-schema-diff](#5-declarative-diff-engine--build-on-pg_query_go-vs-wrap-pg-schema-diff) - [Next step](#next-step) ## Architecture: decoupled planner, router, and executors @@ -242,6 +243,18 @@ CI compute "what would change" — while reusing the entire safe execution path native-vs-copy, checksum, cutover). It is purely additive: the imperative `--alter` path remains the primitive that everything ultimately runs through. +### Build or wrap? + +[stripe/pg-schema-diff](https://github.com/stripe/pg-schema-diff) already implements most of +this front-end: introspection, canonicalization (by applying the desired DDL to a **temp +database** and letting the server itself canonicalize), dependency-ordered emission of the same +safe idioms, per-statement timeouts, typed **hazard annotations**, and **plan validation** +against the temp database. Whether `pkg/schemadiff` wraps it or builds on `pg_query_go` +directly is +[open decision #5](#5-declarative-diff-engine--build-on-pg_query_go-vs-wrap-pg-schema-diff). +Either way its output flows through our classifier and executors unchanged — planner output is +a request, not a permission. + ## Advisory mode and the force escape hatch The [advisory behaviour](high-level-design.md#advisory-mode-suggest-the-safe-rewrite-dont-silently-run-the-risky-one) @@ -534,6 +547,10 @@ pkg/throttler/ -> Aurora PG replica-lag / slot-lag throttle messages, send standby status (LSN flush) updates. This is the binlog-syncer analog. - **`github.com/pganalyze/pg_query_go/v5`** — parse `ALTER`/`CREATE TABLE` (libpg_query, the actual Postgres grammar). Analog of Spirit's TiDB parser. +- **`github.com/stripe/pg-schema-diff`** *(candidate — open decision #5)* — declarative diff + engine: introspection + dependency-ordered plan emission with hazard annotations and + temp-database plan validation; would power `pkg/schemadiff` instead of building the diff on + `pg_query_go` directly. - **`github.com/alecthomas/kong`** — CLI, same as Spirit. ## Design decisions inherited from Spirit (safety over speed) @@ -663,6 +680,33 @@ rewrites use copy-and-swap. one-shot vs start/complete/rollback lifecycles under one `status`; and the default routing policy (auto-route vs explicit `--strategy`) given "decisions, not options". +### 5. Declarative diff engine — build on pg_query_go vs wrap pg-schema-diff + +Whether `pkg/schemadiff` builds the desired-vs-live diff on `pg_query_go` + our own schema +model, or wraps [stripe/pg-schema-diff](https://github.com/stripe/pg-schema-diff) (MIT, Go, +PG 14–17, actively maintained) as the diff engine. + +- **Wrap (evaluate first):** it already does the hardest parts — introspection, + server-canonicalized desired state ("parse by executing" on a temp database), + dependency-ordered plans emitting the same safe idioms our classifier chooses, typed hazard + annotations (≈ our advisory mode, with an `--allow-hazards`-style CI gate), and plan + validation against the temp database. Plan generation is cleanly separated from application, + so our executors keep our own timeout/lock/retry discipline. It is a **periphery** dependency + (plan generation), so the TCB bar does not apply — pinned like any load-bearing dep. +- **Costs of wrapping:** the temp-database factory is an operational precondition + (`CREATE DATABASE` on the target or a scratch instance — needs a deliberate answer for + locked-down production clusters); renames surface as drop+add and **must** sit behind our + destructive-diff gate and never-guess-renames refusals; type support beyond enums is missing; + its embedded timeout policy is replaced by ours at execution. +- **Build:** full control and no temp-database precondition — at the cost of the hardest code + in Phase 2 and permanent drift risk between a hand-rolled schema model and PostgreSQL's real + canonicalization. +- **Recommendation:** prototype the wrap behind our own `SchemaDiff` seam in Phase 2; adopt the + **hazard taxonomy** (advisory-mode vocabulary) and **plan-validation-on-a-throwaway-schema** + (a verification-ladder rung) regardless of which way this lands. Its declared non-goal — + *"stateful online migration techniques, like shadow tables, aren't yet supported"* — is + exactly the gap this engine's copy-and-swap fills, so the tools compose rather than compete. + ## Next step From 751290c852cbb82532d7a78a9f83d5b8bc895c07 Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Thu, 9 Jul 2026 13:58:31 +1000 Subject: [PATCH 05/12] cli: shared DB flags wired to dbconn, version flag, grammar tests Kong-embedded DBFlags (URL/CA/timeouts, env-backed) give every database command the same bounded session defaults; fmt stays offline. Grammar construction is pinned by a test so a bad tag fails in CI, not at first use. --- cmd/pg-sprite/main.go | 4 +++ internal/cli/cli.go | 46 ++++++++++++++++++++++++++++----- internal/cli/cli_test.go | 56 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 internal/cli/cli_test.go diff --git a/cmd/pg-sprite/main.go b/cmd/pg-sprite/main.go index 421257e..462f3ba 100644 --- a/cmd/pg-sprite/main.go +++ b/cmd/pg-sprite/main.go @@ -6,11 +6,15 @@ import ( "github.com/block/pg-sprite/internal/cli" ) +// version is stamped at release time via -ldflags "-X main.version=…". +var version = "dev" + func main() { k := kong.Parse(cli.New(), kong.Name("pg-sprite"), kong.Description("An online schema-change engine for Aurora PostgreSQL."), kong.UsageOnError(), + kong.Vars{"version": version}, ) k.FatalIfErrorf(k.Run()) } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index f5fd0d7..3b3d805 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -1,11 +1,20 @@ -// Package cli defines the pg-sprite command tree. All subcommands are Phase 0 -// stubs; each later build-plan phase fills one in. +// Package cli defines the pg-sprite command tree (Kong). Subcommand Run +// methods are stubs; each build-plan phase fills one in. package cli -import "fmt" +import ( + "fmt" + "time" + + "github.com/alecthomas/kong" + + "github.com/block/pg-sprite/pkg/dbconn" +) // CLI is the root command tree. type CLI struct { + Version kong.VersionFlag `help:"Print version and exit."` + Migrate MigrateCmd `cmd:"" help:"Run a schema change safely."` Diff DiffCmd `cmd:"" help:"Diff a desired-state schema file against the live schema."` Fmt FmtCmd `cmd:"" help:"Canonicalize a schema file."` @@ -20,8 +29,29 @@ func notImplemented(cmd string) error { return fmt.Errorf("%s: not implemented yet (Phase 0 stub)", cmd) } +// DBFlags are the connection flags shared by every command that talks to the +// database, so every entry point carries the same bounded session defaults. +type DBFlags struct { + URL string `help:"PostgreSQL connection URL or key=value DSN." env:"PGSPRITE_URL" required:""` + CACert string `help:"CA bundle path for verify-full TLS. RDS/Aurora endpoints verify with the embedded bundle automatically." env:"PGSPRITE_CA_CERT" type:"existingfile"` + LockTimeout time.Duration `help:"Session lock_timeout applied to every statement." default:"3s"` + StatementTimeout time.Duration `help:"Session statement_timeout applied to every statement." default:"30s"` +} + +// Config translates the flags into the connectivity layer's configuration. +func (f DBFlags) Config() dbconn.Config { + return dbconn.Config{ + URL: f.URL, + CACertPath: f.CACert, + LockTimeout: f.LockTimeout, + StatementTimeout: f.StatementTimeout, + } +} + // MigrateCmd runs a schema change (imperative front-end). type MigrateCmd struct { + DBFlags `embed:""` + Alter string `help:"Imperative ALTER statement to run." name:"alter"` } @@ -30,13 +60,15 @@ func (c *MigrateCmd) Run() error { return notImplemented("migrate") } // DiffCmd derives statements from a desired-state schema (declarative front-end). type DiffCmd struct { - Desired string `help:"Path to the desired-state CREATE TABLE .sql file." name:"desired" type:"existingfile"` + DBFlags `embed:""` + + Desired string `help:"Path to the desired-state CREATE TABLE .sql file." name:"desired" type:"existingfile" required:""` } // Run implements the diff subcommand. func (c *DiffCmd) Run() error { return notImplemented("diff") } -// FmtCmd canonicalizes a schema file. +// FmtCmd canonicalizes a schema file. It is offline — no database flags. type FmtCmd struct { Path string `arg:"" optional:"" help:"Schema file to format." type:"existingfile"` } @@ -51,7 +83,9 @@ type LintCmd struct{} func (c *LintCmd) Run() error { return notImplemented("lint") } // StatusCmd reports migration progress. -type StatusCmd struct{} +type StatusCmd struct { + DBFlags `embed:""` +} // Run implements the status subcommand. func (c *StatusCmd) Run() error { return notImplemented("status") } diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go new file mode 100644 index 0000000..b6fbb71 --- /dev/null +++ b/internal/cli/cli_test.go @@ -0,0 +1,56 @@ +package cli_test + +import ( + "testing" + "time" + + "github.com/alecthomas/kong" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/block/pg-sprite/internal/cli" +) + +func newKong(t *testing.T, c *cli.CLI) *kong.Kong { + t.Helper() + k, err := kong.New(c, kong.Vars{"version": "test"}) + require.NoError(t, err, "the command grammar must construct — a bad tag fails here, not in production") + return k +} + +func TestGrammarIsValid(t *testing.T) { + newKong(t, cli.New()) +} + +func TestMigrateFlagsWireIntoDBConfig(t *testing.T) { + c := cli.New() + k := newKong(t, c) + _, err := k.Parse([]string{ + "migrate", + "--url", "postgres://user@localhost:5432/app", + "--alter", "ALTER TABLE t ADD COLUMN c int", + "--lock-timeout", "5s", + }) + require.NoError(t, err) + + cfg := c.Migrate.Config() + assert.Equal(t, "postgres://user@localhost:5432/app", cfg.URL) + assert.Equal(t, 5*time.Second, cfg.LockTimeout) + assert.Equal(t, 30*time.Second, cfg.StatementTimeout, "statement_timeout keeps its default") + assert.Empty(t, cfg.CACertPath) +} + +func TestURLIsRequiredForDatabaseCommands(t *testing.T) { + c := cli.New() + k := newKong(t, c) + _, err := k.Parse([]string{"migrate", "--alter", "ALTER TABLE t ADD COLUMN c int"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "--url") +} + +func TestFmtIsOffline(t *testing.T) { + c := cli.New() + k := newKong(t, c) + _, err := k.Parse([]string{"fmt"}) + require.NoError(t, err, "fmt must not require database flags") +} From e38b1b2b77d1a10207f9aeae37837ea4b4c9e198 Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Thu, 9 Jul 2026 14:07:02 +1000 Subject: [PATCH 06/12] dbconn: expose the pgx/v5 feature surface behind Config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pool sizing/lifecycle (min/max conns, lifetime+jitter, idle, healthcheck), bounded connect timeout, QueryExecMode for transaction-pooling proxies, statement tracing via tracelog->slog, and a BeforeConnect hook as the RDS IAM-token seam — zero values keep pgx defaults (decisions, not options). Pool config construction is now a pure function so every option's wiring is unit-tested without a server. --- pkg/dbconn/dbconn.go | 162 +++++++++++++++++++++++++++------ pkg/dbconn/pool_config_test.go | 80 ++++++++++++++++ 2 files changed, 212 insertions(+), 30 deletions(-) create mode 100644 pkg/dbconn/pool_config_test.go diff --git a/pkg/dbconn/dbconn.go b/pkg/dbconn/dbconn.go index e0f7218..d574fae 100644 --- a/pkg/dbconn/dbconn.go +++ b/pkg/dbconn/dbconn.go @@ -1,7 +1,7 @@ // Package dbconn is the engine's database connectivity layer: pgx pool // construction with safe session defaults (lock_timeout, statement_timeout), -// optional RDS/Aurora CA TLS, bounded retries for transient errors, and a -// helper to terminate backends blocking a session's lock acquisition. +// RDS/Aurora TLS, bounded retries for transient errors, and a helper to +// terminate backends blocking a session's lock acquisition. package dbconn import ( @@ -9,12 +9,15 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "log/slog" "os" "strconv" "strings" "time" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/tracelog" ) // Defaults for the session timeouts every pooled connection runs under. Every @@ -24,9 +27,13 @@ import ( const ( DefaultLockTimeout = 3 * time.Second DefaultStatementTimeout = 30 * time.Second + // DefaultConnectTimeout bounds each dial attempt. + DefaultConnectTimeout = 10 * time.Second ) -// Config describes a connection target. +// Config describes a connection target. Zero values keep sensible defaults +// (ours for the session timeouts, pgxpool's for pool sizing and lifecycle) — +// decisions, not options. type Config struct { // URL is a libpq connection string or URL (postgres://...). URL string @@ -36,16 +43,62 @@ type Config struct { // StatementTimeout is applied as the session statement_timeout on every // connection. Zero means DefaultStatementTimeout. StatementTimeout time.Duration + // ConnectTimeout bounds each dial attempt. Zero means + // DefaultConnectTimeout. + ConnectTimeout time.Duration // CACertPath, when set, enables verify-full TLS using the given CA bundle - // (e.g. the RDS/Aurora global bundle). + // (e.g. the RDS/Aurora global bundle). Unset, RDS/Aurora endpoints are + // auto-verified with the embedded bundle (see rds.go). CACertPath string - // MaxConns caps the pool size. Zero keeps the pgxpool default. - MaxConns int32 + + // Pool sizing and lifecycle. Zero values keep pgxpool's defaults. + // + // NOTE: the advisory-lock connection (LK-1) must NOT come from this pool: + // session-scoped locks die with their session, and lifetime/idle + // recycling would silently release the lock. The lock helper owns a + // dedicated single-connection pool exempt from recycling. + MaxConns int32 + MinConns int32 + MaxConnLifetime time.Duration + MaxConnLifetimeJitter time.Duration + MaxConnIdleTime time.Duration + HealthCheckPeriod time.Duration + + // QueryExecMode overrides pgx's default protocol usage — e.g. + // pgx.QueryExecModeExec when a transaction-pooling proxy that cannot + // handle prepared statements sits in front of the pool. Zero keeps pgx's + // default (statement caching). + QueryExecMode pgx.QueryExecMode + // Logger, when set, enables statement-level tracing (pgx tracelog) at + // debug level through the given slog logger. + Logger *slog.Logger + // BeforeConnect, when set, can mutate each new connection's config just + // before dialing — the hook for short-lived credentials such as RDS IAM + // authentication tokens. + BeforeConnect func(context.Context, *pgx.ConnConfig) error } // NewPool builds a pgx pool from cfg, applies the session defaults, and // verifies connectivity with a ping before returning. func NewPool(ctx context.Context, cfg Config) (*pgxpool.Pool, error) { + pc, err := buildPoolConfig(cfg) + if err != nil { + return nil, err + } + pool, err := pgxpool.NewWithConfig(ctx, pc) + if err != nil { + return nil, fmt.Errorf("create pool: %w", err) + } + if err := pool.Ping(ctx); err != nil { + pool.Close() + return nil, fmt.Errorf("ping after connect: %w", err) + } + return pool, nil +} + +// buildPoolConfig translates Config into a pgxpool configuration. It is pure +// (no dialing), so every option's wiring is unit-testable without a server. +func buildPoolConfig(cfg Config) (*pgxpool.Config, error) { pc, err := pgxpool.ParseConfig(cfg.URL) if err != nil { return nil, fmt.Errorf("parse connection config: %w", err) @@ -65,22 +118,89 @@ func NewPool(ctx context.Context, cfg Config) (*pgxpool.Pool, error) { rp["statement_timeout"] = strconv.FormatInt(stmtTimeout.Milliseconds(), 10) rp["application_name"] = "pg-sprite" + connectTimeout := cfg.ConnectTimeout + if connectTimeout == 0 { + connectTimeout = DefaultConnectTimeout + } + pc.ConnConfig.ConnectTimeout = connectTimeout + if err := configureTLS(pc, cfg); err != nil { return nil, err } + if cfg.MaxConns > 0 { pc.MaxConns = cfg.MaxConns } + if cfg.MinConns > 0 { + pc.MinConns = cfg.MinConns + } + if cfg.MaxConnLifetime > 0 { + pc.MaxConnLifetime = cfg.MaxConnLifetime + } + if cfg.MaxConnLifetimeJitter > 0 { + pc.MaxConnLifetimeJitter = cfg.MaxConnLifetimeJitter + } + if cfg.MaxConnIdleTime > 0 { + pc.MaxConnIdleTime = cfg.MaxConnIdleTime + } + if cfg.HealthCheckPeriod > 0 { + pc.HealthCheckPeriod = cfg.HealthCheckPeriod + } + if cfg.QueryExecMode != 0 { + pc.ConnConfig.DefaultQueryExecMode = cfg.QueryExecMode + } + if cfg.BeforeConnect != nil { + pc.BeforeConnect = cfg.BeforeConnect + } + if cfg.Logger != nil { + pc.ConnConfig.Tracer = &tracelog.TraceLog{ + Logger: slogTraceLogger{logger: cfg.Logger}, + LogLevel: tracelog.LogLevelDebug, + } + } + return pc, nil +} - pool, err := pgxpool.NewWithConfig(ctx, pc) +// slogTraceLogger adapts slog to pgx's tracelog logger interface. +type slogTraceLogger struct { + logger *slog.Logger +} + +func (l slogTraceLogger) Log(ctx context.Context, level tracelog.LogLevel, msg string, data map[string]any) { + attrs := make([]any, 0, len(data)*2) + for k, v := range data { + attrs = append(attrs, k, v) + } + var slogLevel slog.Level + switch level { + case tracelog.LogLevelTrace, tracelog.LogLevelDebug: + slogLevel = slog.LevelDebug + case tracelog.LogLevelInfo: + slogLevel = slog.LevelInfo + case tracelog.LogLevelWarn: + slogLevel = slog.LevelWarn + default: + slogLevel = slog.LevelError + } + l.logger.Log(ctx, slogLevel, msg, attrs...) +} + +// caTLSConfig builds a verify-full TLS config trusting only the given CA +// bundle, verifying the server certificate against host. +func caTLSConfig(caCertPath, host string) (*tls.Config, error) { + pem, err := os.ReadFile(caCertPath) if err != nil { - return nil, fmt.Errorf("create pool: %w", err) + return nil, fmt.Errorf("read CA bundle: %w", err) } - if err := pool.Ping(ctx); err != nil { - pool.Close() - return nil, fmt.Errorf("ping after connect: %w", err) + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("CA bundle %s contains no usable certificates", caCertPath) } - return pool, nil + return &tls.Config{ + RootCAs: roots, + ServerName: host, + MinVersion: tls.VersionTLS12, + }, nil } // configureTLS decides the pool's TLS setup: an explicit CA bundle wins; @@ -113,21 +233,3 @@ func configureTLS(pc *pgxpool.Config, cfg Config) error { } return nil } - -// caTLSConfig builds a verify-full TLS config trusting only the given CA -// bundle, verifying the server certificate against host. -func caTLSConfig(caCertPath, host string) (*tls.Config, error) { - pem, err := os.ReadFile(caCertPath) - if err != nil { - return nil, fmt.Errorf("read CA bundle: %w", err) - } - roots := x509.NewCertPool() - if !roots.AppendCertsFromPEM(pem) { - return nil, fmt.Errorf("CA bundle %s contains no usable certificates", caCertPath) - } - return &tls.Config{ - RootCAs: roots, - ServerName: host, - MinVersion: tls.VersionTLS12, - }, nil -} diff --git a/pkg/dbconn/pool_config_test.go b/pkg/dbconn/pool_config_test.go new file mode 100644 index 0000000..1f8b333 --- /dev/null +++ b/pkg/dbconn/pool_config_test.go @@ -0,0 +1,80 @@ +package dbconn + +import ( + "context" + "log/slog" + "testing" + "time" + + "github.com/jackc/pgx/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testURL = "postgres://user@localhost:5432/app" + +func TestBuildPoolConfigDefaults(t *testing.T) { + pc, err := buildPoolConfig(Config{URL: testURL}) + require.NoError(t, err) + + rp := pc.ConnConfig.RuntimeParams + assert.Equal(t, "3000", rp["lock_timeout"]) + assert.Equal(t, "30000", rp["statement_timeout"]) + assert.Equal(t, "pg-sprite", rp["application_name"]) + assert.Equal(t, DefaultConnectTimeout, pc.ConnConfig.ConnectTimeout) + + // Unset knobs keep pgxpool's own defaults rather than zeroing them out. + assert.Positive(t, pc.MaxConns) + assert.Positive(t, pc.MaxConnLifetime) + assert.Positive(t, pc.HealthCheckPeriod) + assert.Nil(t, pc.BeforeConnect) + assert.Nil(t, pc.ConnConfig.Tracer) +} + +func TestBuildPoolConfigOverrides(t *testing.T) { + hookCalled := false + cfg := Config{ + URL: testURL, + LockTimeout: 1500 * time.Millisecond, + StatementTimeout: 45 * time.Second, + ConnectTimeout: 7 * time.Second, + MaxConns: 20, + MinConns: 2, + MaxConnLifetime: 30 * time.Minute, + MaxConnLifetimeJitter: 5 * time.Minute, + MaxConnIdleTime: 10 * time.Minute, + HealthCheckPeriod: 30 * time.Second, + QueryExecMode: pgx.QueryExecModeExec, + Logger: slog.Default(), + BeforeConnect: func(context.Context, *pgx.ConnConfig) error { + hookCalled = true + return nil + }, + } + pc, err := buildPoolConfig(cfg) + require.NoError(t, err) + + rp := pc.ConnConfig.RuntimeParams + assert.Equal(t, "1500", rp["lock_timeout"]) + assert.Equal(t, "45000", rp["statement_timeout"]) + assert.Equal(t, 7*time.Second, pc.ConnConfig.ConnectTimeout) + assert.Equal(t, int32(20), pc.MaxConns) + assert.Equal(t, int32(2), pc.MinConns) + assert.Equal(t, 30*time.Minute, pc.MaxConnLifetime) + assert.Equal(t, 5*time.Minute, pc.MaxConnLifetimeJitter) + assert.Equal(t, 10*time.Minute, pc.MaxConnIdleTime) + assert.Equal(t, 30*time.Second, pc.HealthCheckPeriod) + assert.Equal(t, pgx.QueryExecModeExec, pc.ConnConfig.DefaultQueryExecMode) + assert.NotNil(t, pc.ConnConfig.Tracer, "a Logger must enable statement tracing") + + require.NotNil(t, pc.BeforeConnect) + require.NoError(t, pc.BeforeConnect(t.Context(), pc.ConnConfig.Copy())) + assert.True(t, hookCalled, "BeforeConnect must be wired through verbatim") +} + +func TestBuildPoolConfigKeepsPgxExecModeDefaultWhenUnset(t *testing.T) { + pc, err := buildPoolConfig(Config{URL: testURL}) + require.NoError(t, err) + assert.Equal(t, pgx.QueryExecModeCacheStatement, pc.ConnConfig.DefaultQueryExecMode, + "unset QueryExecMode keeps pgx's statement-caching default") +} From a6e24d9bd397552eb09a798936a3573d362f690a Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Mon, 3 Aug 2026 07:42:04 +1000 Subject: [PATCH 07/12] dbconn: use t.Fatalf instead of t.Fatal(fmt.Sprintf(...)) --- pkg/dbconn/dbconn_integration_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/dbconn/dbconn_integration_test.go b/pkg/dbconn/dbconn_integration_test.go index 2ba2152..13c9295 100644 --- a/pkg/dbconn/dbconn_integration_test.go +++ b/pkg/dbconn/dbconn_integration_test.go @@ -1,7 +1,6 @@ package dbconn_test import ( - "fmt" "testing" "time" @@ -109,7 +108,7 @@ func TestPoolIntegration(t *testing.T) { case err := <-insertDone: require.NoError(t, err, "B's insert should succeed once the blocker is evicted") case <-time.After(insertDeadline): - t.Fatal(fmt.Sprintf("B's insert still blocked %s after terminating the blocker", insertDeadline)) + t.Fatalf("B's insert still blocked %s after terminating the blocker", insertDeadline) } }) } From f035b5fce14802e92e2705af32acd824d195cbf6 Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Mon, 3 Aug 2026 07:06:37 +1000 Subject: [PATCH 08/12] docs: remove pg-osc runtime delegation; codify structured refusal Anything not native-safe is refused with a structured verdict (SchemaBot sees ExecutionModeBlocked), never delegated to an external tool. pg-osc stays as studied reference material only; in-house copy-and-swap arrives in Phases 4-7. --- docs/architecture.md | 8 ++++---- docs/high-level-design.md | 23 ++++++++++++----------- docs/low-level-design.md | 32 ++++++++++++++++++-------------- docs/schemabot-integration.md | 9 +++++++-- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 865c423..672bfa4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -28,9 +28,9 @@ changes, the router decides *which strategy*, interchangeable executors decide * ╰───────┬───────╯ ╭────────────────┼────────────────┬───────────────╮ ▼ ▼ ▼ ▼ - native DDL copy-and-swap expand/contract refuse / - CONCURRENTLY (transparent) (reversible, manual - NOT VALID … later) + native DDL copy-and-swap expand/contract refuse with + CONCURRENTLY (later phase) (reversible, not-native-safe + NOT VALID … later) verdict ╰────────────────┴────────────────╯ │ cross-cutting: connection mgmt, │ lock bounding, Aurora-aware throttling @@ -67,7 +67,7 @@ boundary) is defined in [../SAFETY.md](../SAFETY.md). ## The copy-and-swap lifecycle -When the router picks the heavy path: +In a later phase, when the in-house heavy path is available and the router picks it: ``` create shadow table ─▶ start change capture ─▶ bulk-copy existing rows diff --git a/docs/high-level-design.md b/docs/high-level-design.md index 900716f..9ad9a4e 100644 --- a/docs/high-level-design.md +++ b/docs/high-level-design.md @@ -35,8 +35,9 @@ some changes take an `ACCESS EXCLUSIVE` lock that, behind a long transaction, ca whole application (the lock queue); and some changes **rewrite the entire table**, which a single `ALTER` cannot do online. The engine's job is to take the change the user wants and run it **safely** — using the cheap native -PostgreSQL idiom when one exists, and a controlled table-copy when one doesn't — without the user -having to know which case they are in. +PostgreSQL idiom when one exists, and a clear **not native-safe** refusal when one doesn't — +without the user having to know which case they are in. Later phases add an in-house controlled +table-copy path for those refused rewrites. ## The core idea: one planner, many executors @@ -71,7 +72,7 @@ not choose one pattern globally. We route each migration to the pattern whose tr │ per-change strategy ╭───────────────────────────────▼─────────────────────────────--╮ │ EXECUTORS decide HOW (interchangeable) │ -│ native DDL · copy-and-swap · expand/contract · refuse │ +│ native DDL · later: copy-and-swap / expand-contract · refuse│ ╰─────────────────────────────────────────────────────────────--╯ ``` @@ -100,7 +101,7 @@ implementations, and we ship them in order: the errors (then tried known-safe `INPLACE` options). It ships an end-to-end useful tool with almost no parsing logic. - **Classification (full, parse-based).** Parse the statement and introspect the live schema to - **predict the path up front** — native-safe, copy-and-swap, or refuse — without trial + **predict the path up front** — native-safe, needs-rewrite, or refuse — without trial execution. This is what powers dry-run, advisory suggestions, and the declarative diff, and it removes the wasted/aborted attempts that optimistic classification can incur. @@ -132,10 +133,10 @@ implementations, and we ship them in order: ╰───────┬───────╯ ╭────────────────┼────────────────┬───────────────╮ ▼ ▼ ▼ ▼ - native DDL copy-and-swap expand/contract refuse / - CONCURRENTLY (Pattern A, via pgroll manual - NOT VALID … transparent) (Pattern B, - fast default reversible, later) + native DDL copy-and-swap expand/contract refuse with + CONCURRENTLY (Pattern A, via pgroll not-native-safe + NOT VALID … later) (Pattern B, later) verdict + fast default ╰────────────────┴────────────────╯ │ cross-cutting: connection mgmt, │ lock bounding, Aurora-aware throttling @@ -154,9 +155,9 @@ The package-level version of this diagram (with the concrete components for each | Pattern | When the router picks it | Key property | Tradeoff | | --- | --- | --- | --- | | **native DDL** | The change has a safe online PostgreSQL idiom (most changes) | Cheapest correct path; no copy | None beyond bounding the brief lock | -| **copy-and-swap** (Pattern A) | A genuine table rewrite with **no** native online path (`int→bigint`, repack, volatile-default add) | **Transparent** — same table name, no app changes | Heaviest path; needs logical replication for the low-overhead mode | +| **copy-and-swap** (Pattern A, later) | A genuine table rewrite with **no** native online path (`int→bigint`, repack, volatile-default add) | **Transparent** — same table name, no app changes | Not yet available; needs logical replication for the low-overhead mode | | **expand/contract** via pgroll (Pattern B, later) | A **breaking** change where instant reversibility / two live schema versions matter | **Reversible** within the rollout window | Requires the **app to be schema-version aware** | -| **refuse** | Unsafe or unsupported (lossy conversion, PK change, FK/trigger table in v1) | Fails fast and cheaply | n/a — it is the safe outcome | +| **refuse** | Not native-safe, unsafe, or unsupported | First-class verdict with the reason, later-phase copy-and-swap note, and a safer native alternative where one exists | n/a — it is the safe outcome | The crucial point: **reversibility and transparency are properties of the pattern, not features you toggle.** copy-and-swap is transparent but not reversible-by-design; pgroll is reversible but @@ -240,7 +241,7 @@ for the surfacing/approval mechanics. ## The copy-and-swap path, conceptually -When the router picks copy-and-swap, the lifecycle is: +When a later phase adds the in-house copy-and-swap executor, its lifecycle is: ```diagram build a shadow table ─▶ capture concurrent writes ─▶ bulk-copy existing rows diff --git a/docs/low-level-design.md b/docs/low-level-design.md index 6cb45e8..26d15ed 100644 --- a/docs/low-level-design.md +++ b/docs/low-level-design.md @@ -78,7 +78,7 @@ seam inside the copy-and-swap executor is the same idea applied one level down. ┌─────────────────────▼──────────────────── PLANNER / front-end (shared) ────┐ │ pkg/statement parse ALTER/CREATE (pg_query_go) │ │ pkg/schemadiff introspect live schema → diff vs desired → ordered ALTERs │ - │ classifier per op: native-safe | copy-and-swap | refuse │ + │ classifier per op: native-safe | needs-rewrite | refuse │ │ pkg/lint reject unsafe/unsupported up front │ │ │ │ │ ▼ Plan (ordered steps, classified per operation) │ @@ -86,13 +86,13 @@ seam inside the copy-and-swap executor is the same idea applied one level down. ▼ ┌──────────────────── ROUTER (pick an executor per change) ─────────────────┐ │ policy + cluster facts: reversibility? app version-aware? logical repl? │ - │ table shape? → assigns each change to native | copy-and-swap | expand/c. │ + │ table shape? → native now; copy-and-swap / expand-contract in later phases│ └──────┬─────────────────────────────────────────────────────────────────────┘ │ pkg/executor — Executor{ Plan, Execute, Status, Abort } ╭──────┴───────────────┬──────────────────────────────┬─────────────────────╮ ▼ ▼ ▼ ▼ - native copy-and-swap (Pattern A) expand/contract refuse / - executor executor — the heavy path via pgroll (later) manual + native copy-and-swap (Pattern A) expand/contract refuse with + executor executor — later phase via pgroll (later) verdict ┌──────────────┐ ┌───────────────────────────-┐ ┌──────────────────┐ │CONCURRENTLY │ │1 create shadow table │ │versioned views, │ │NOT VALID + │ │2 pkg/decode logical slot │ │dual schema, app │ @@ -150,8 +150,9 @@ pattern *per migration*: The classifier, declarative diff, linting, dry-run, and status reporting are written **once** and shared by every backend. An `Executor` interface (`Plan`, `Execute`, `Status`, `Abort`) -is the contract; copy-and-swap and native are the v1 implementations, pgroll is a strong -candidate to wrap as a third. +is the contract; native is the first implementation. Until the in-house copy-and-swap executor +lands in a later phase, every `needs-rewrite` change is refused as **not native-safe** rather than +delegated to an external tool. pgroll remains a possible still-later backend. ### The honest tradeoffs (why this is an *option*, not a free win) @@ -178,10 +179,11 @@ Routing to pgroll buys reversibility, but the patterns are not silently intercha ### v1 stance -Build the **planner + router + native + log-based copy-and-swap** first (that is the -differentiated, missing capability). Design the `Executor` interface from day one so the **expand/contract -(pgroll) backend can be added later** without reworking the front-end. See the -build plan — the pgroll backend lands in a later phase. +Build the **planner + router + native executor** first. A `needs-rewrite` result is a first-class +**not native-safe** refusal, with the reason, a note that in-house copy-and-swap arrives in later +phases, and a safer native alternative where one exists. It is never delegated to an external +copy tool. Design the `Executor` interface from day one so copy-and-swap and, later, +expand/contract can be added without reworking the front-end. ## Declarative mode (desired-state schema diff) @@ -267,7 +269,8 @@ refuse based on mode and flags. For each parsed statement the classifier produces a record along the lines of: - `original` — the statement as the user wrote it. -- `class` — `native-safe` · `needs-rewrite` (copy-and-swap) · `refuse`. +- `class` — `native-safe` · `needs-rewrite` (refused until in-house copy-and-swap lands) · + `refuse`. - `recommended` — the safe rewrite when the literal is risky but has a native equivalent (e.g. `CREATE INDEX` → `CREATE INDEX CONCURRENTLY`; `ADD CONSTRAINT` → `ADD … NOT VALID` + `VALIDATE`; `ADD PRIMARY KEY` → unique index `CONCURRENTLY` + `ADD PRIMARY KEY USING INDEX`). @@ -284,7 +287,7 @@ mode just **surfaces** it instead of consuming it silently. | Invocation | Behaviour | | --- | --- | | `suggest` / `lint` / `diff --dry-run` | Print `original` → `recommended` + `risk` for every op. **Never executes.** Exit non-zero if any op needs a riskier path than policy allows (the CI gate). | -| `migrate` (default) | If a safer `recommended` form exists, **apply the recommended idiom** (classify-first) and report what was substituted. If the literal is risky with **no** safe equivalent (a genuine rewrite), route to copy-and-swap. If unsafe/unsupported, **refuse** with the reason. The dangerous literal is **never** run by default. | +| `migrate` (default) | If a safer `recommended` form exists, **apply the recommended idiom** (classify-first) and report what was substituted. If the literal is not native-safe, **refuse** with the reason, a later-phase copy-and-swap note, and a safer native alternative where one exists. The dangerous literal is **never** run by default, and no external copy tool is invoked. | | `migrate --force` | Run each statement **exactly as submitted**, bypassing the safe rewrite. Gated — see below. | The distinction the user cares about: a plain `CREATE INDEX` is never executed verbatim by @@ -311,8 +314,9 @@ idle and a plain rewrite is acceptable); it is an escape hatch, not a shortcut, ## Copy-and-swap executor: lifecycle -> This section details the **copy-and-swap executor** (one of the executors above), the -> heavy strategy for genuine table rewrites. The `native` and `expand/contract` +> This section details the later-phase **copy-and-swap executor**, the planned in-house heavy +> strategy for genuine table rewrites. Until it lands, those rewrites receive a **not +> native-safe** refusal. The `native` and `expand/contract` > executors are described in the architecture section and in > tool-pgroll.md. The per-primitive **Spirit (MySQL) → Aurora > PostgreSQL mapping** this executor is built on lives in diff --git a/docs/schemabot-integration.md b/docs/schemabot-integration.md index 332a686..c85077b 100644 --- a/docs/schemabot-integration.md +++ b/docs/schemabot-integration.md @@ -50,7 +50,7 @@ this mapping clean — the orchestrator's engine verbs line up almost one-to-one | Engine verb | `pg-sprite` responsibility | | --- | --- | | plan | **Planner**: classify the change / diff declarative schema; return an engine-neutral table change (pure, no side effects) | -| apply | **Router + Executor**: pick native / copy-and-swap / expand-contract and run it **asynchronously** | +| apply | **Router + Executor**: run native changes **asynchronously**; return a refusal verdict for non-native-safe changes until later-phase executors land | | progress | per-table rows-copied / total / percent / ETA / checksum state | | stop / start | checkpoint and resume (slot + copy + applier state) | | cutover (+ deferred cutover) | the deferred, operator-gated atomic swap | @@ -74,7 +74,7 @@ the integration phase starts; they drift.) | --- | --- | | `Name()` | a stable identifier, e.g. `"pg-sprite"` | | `Plan` | run the planner: classify / declarative-diff; return a `PlanResult` whose `SchemaChange.TableChanges` are `engine.TableChange{Table, Operation (statement.StatementType), DDL, IsUnsafe, UnsafeReason}` | -| `Apply` | start the chosen executor asynchronously; return immediately | +| `Apply` | start the native executor asynchronously, or map a **not native-safe** refusal to `engine.ExecutionModeBlocked`; return immediately | | `Progress` | per-table rows-copied / total / percent / ETA / checksum state | | `Stop` / `Start` | checkpoint and resume (slot + copy + applier watermark) | | `Cutover` | the deferred, operator-gated atomic swap | @@ -82,6 +82,11 @@ the integration phase starts; they drift.) | `Volume` | map 1–11 onto chunk-time target / parallelism / throttle | | `Revert` / `SkipRevert` | decline for the copy-and-swap path (like Spirit); only the expand/contract backend could honour them | +A refusal is a first-class planning verdict, not a delegation fallback: it includes the reason, +notes that copy-and-swap support arrives in later phases, and names a safer native alternative +where one exists. The adapter maps that verdict to `engine.ExecutionModeBlocked`; it never invokes +pg-osc or another external copy tool. + **Registration / selection.** The orchestrator core is `pkg/tern`; `tern.NewLocalClient` has built-in branches for `storage.DatabaseTypeMySQL` (Spirit) and `storage.DatabaseTypeVitess` (PlanetScale), and looks up everything else in `LocalConfig.EngineFactories[type]` (an From 1f2a53a2781b9c36ba8b5d3df7c1e6f74b4e816d Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Mon, 3 Aug 2026 07:06:38 +1000 Subject: [PATCH 09/12] =?UTF-8?q?chore:=20OSS=20scaffolding=20=E2=80=94=20?= =?UTF-8?q?WIP=20banner,=20Apache-2.0=20LICENSE,=20CONTRIBUTING?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepares the repo for publication under block/pg-sprite (OSPO prototype path). README carries a prominent not-ready-for-any-use warning; external contributions are explicitly not accepted yet. Community-standard files (CoC, security, governance) inherit from block/.github org defaults. --- .github/CONTRIBUTING.md | 21 +++++ LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++ README.md | 16 ++++ 3 files changed, 238 insertions(+) create mode 100644 .github/CONTRIBUTING.md create mode 100644 LICENSE diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..386087d --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing + +Thanks for your interest in pg-sprite! + +**We are not accepting external contributions yet.** The project is in +early-stage development (see the warning in the [README](../README.md)): the +design is still settling, interfaces change without notice, and there is no +released version to contribute against. PRs opened at this stage will likely +be closed without review. + +If you've found something that looks like a safety problem — a path where the +engine could take a lock it shouldn't, lose data, or misclassify a change as +native-safe — please open an issue; those we want to hear about even now. + +Once the project reaches a consumable state we'll replace this file with a +real contribution guide (issue-first workflow, testing requirements, and the +review rules for the safety-critical core described in +[SAFETY.md](../SAFETY.md)). + +Community standards (code of conduct, security policy, governance) are +inherited from the [Block organization defaults](https://github.com/block/.github). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 5a5f8ee..1ef6901 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # pg-sprite +> [!WARNING] +> **Work in progress — not ready for any use.** This project is under active +> early-stage development. There are no releases, no stability guarantees, and +> no support. Interfaces, behavior, on-disk/database artifacts, and the CLI +> surface may all change without notice. Do **not** run this against any +> database you care about. + > Working name — see the naming task in the research build tracker. An online schema-change engine for **Aurora PostgreSQL** (and RDS/community @@ -30,3 +37,12 @@ make lint # golangci-lint Integration tests run against a real PostgreSQL via testcontainers. `PG_VERSION` selects the major (default 16); CI runs the matrix 14 → 18. + +## Contributing + +Not yet — see [CONTRIBUTING](.github/CONTRIBUTING.md). Safety-relevant issue +reports are welcome even at this stage. + +## License + +[Apache 2.0](LICENSE) From 64d5645b8e7bf72dedf81749283b2223e1a7cdec Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna Date: Tue, 4 Aug 2026 14:41:44 +1000 Subject: [PATCH 10/12] chore: adopt OSPO-seeded governance files Reconciles local OSS scaffolding with the generated Block template: keep seeded LICENSE (Block, Inc. copyright) and renovate.json verbatim; clean placeholder cruft from CODEOWNERS and the issue-template config; add Code of Conduct; move CONTRIBUTING to repo root. --- .github/ISSUE_TEMPLATE/bug-report.md | 29 +++++ .github/ISSUE_TEMPLATE/config.yml | 4 + CODEOWNERS | 8 ++ CODE_OF_CONDUCT.md | 134 +++++++++++++++++++++ .github/CONTRIBUTING.md => CONTRIBUTING.md | 10 +- GOVERNANCE.md | 1 + LICENSE | 4 +- README.md | 2 +- renovate.json | 4 + 9 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 CODEOWNERS create mode 100644 CODE_OF_CONDUCT.md rename .github/CONTRIBUTING.md => CONTRIBUTING.md (68%) create mode 100644 GOVERNANCE.md create mode 100644 renovate.json diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..74ef7ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,29 @@ +--- +name: 🐛 Bug / safety report +about: Report a bug — especially anything that looks like a safety problem +title: "[Bug] " +labels: bug +assignees: Kiran01bm + +--- + +> pg-sprite is in early-stage development and we are not accepting external +> contributions yet — but bug reports, **especially safety problems** (a path +> where the engine could take a lock it shouldn't, lose data, or misclassify +> a change as native-safe), are welcome even now. + +**Describe the bug** +A clear and concise description of what the bug is. + +**To reproduce** +Steps to reproduce the behavior (DDL, table shape, pg-sprite command). + +**Expected behavior** +What you expected to happen. + +**Environment** +- pg-sprite version / commit: +- PostgreSQL version (and Aurora/RDS/community): + +**Additional context** +Logs, output, or anything else relevant. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ed162d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: ❓ Questions and Help 🤔 + url: https://discord.gg/block-opensource + about: This issue tracker is not for support questions. Please refer to the community for more help. diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..f1abfe9 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,8 @@ +# This CODEOWNERS file denotes the project leads +# and encodes their responsibilities for code review. +# +# The format is described: +# https://github.blog/2017-07-06-introducing-code-owners/ + +# These owners will be the default owners for everything in the repo. +* @Kiran01bm diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b1a0a2c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ +# Block Code of Conduct + +Block's mission is Economic Empowerment. This means opening the global economy to everyone. We extend the same principles of inclusion to our developer ecosystem. We are excited to build with you. So we will ensure our community is truly open, transparent and inclusive. Because of the global nature of our project, diversity and inclusivity is paramount to our success. We not only welcome diverse perspectives, we **need** them! + +The code of conduct below reflects the expectations for ourselves and for our community. + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, physical appearance, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful and welcoming of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +The Block Open Source Governance Committee (GC) is responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +The GC has the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event, or any space where the project is listed as part of your profile. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the Block Open Source Governance Committee (GC) at +`open-source-governance@block.xyz`. All complaints will be reviewed and +investigated promptly and fairly. + +The GC is obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +The GC will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from the GC, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media and forums. + +Although this list cannot be exhaustive, we explicitly honor diversity in age, culture, ethnicity, gender identity or expression, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected characteristics above, including participants with disabilities. + +Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 68% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md index 386087d..9e84e55 100644 --- a/.github/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Thanks for your interest in pg-sprite! **We are not accepting external contributions yet.** The project is in -early-stage development (see the warning in the [README](../README.md)): the +early-stage development (see the warning in the [README](README.md)): the design is still settling, interfaces change without notice, and there is no released version to contribute against. PRs opened at this stage will likely be closed without review. @@ -15,7 +15,9 @@ native-safe — please open an issue; those we want to hear about even now. Once the project reaches a consumable state we'll replace this file with a real contribution guide (issue-first workflow, testing requirements, and the review rules for the safety-critical core described in -[SAFETY.md](../SAFETY.md)). +[SAFETY.md](SAFETY.md)). -Community standards (code of conduct, security policy, governance) are -inherited from the [Block organization defaults](https://github.com/block/.github). +Community standards: see [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and +[GOVERNANCE.md](GOVERNANCE.md); anything not covered there (e.g. the security +policy) is inherited from the +[Block organization defaults](https://github.com/block/.github). diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000..4af3958 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1 @@ +## [Click here for Block Open Source Project governance information](https://github.com/block/.github/blob/main/GOVERNANCE.md) diff --git a/LICENSE b/LICENSE index 261eeb9..862ee3c 100644 --- a/LICENSE +++ b/LICENSE @@ -186,14 +186,14 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +Copyright 2026 Block, Inc. + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/README.md b/README.md index 1ef6901..8dc2a51 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ selects the major (default 16); CI runs the matrix 14 → 18. ## Contributing -Not yet — see [CONTRIBUTING](.github/CONTRIBUTING.md). Safety-relevant issue +Not yet — see [CONTRIBUTING](CONTRIBUTING.md). Safety-relevant issue reports are welcome even at this stage. ## License diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..9582fc5 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", "helpers:pinGitHubActionDigests"] +} From d68151a5abaa20240947be515c258c61e52efe4c Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna <kmuddukrishna@squareup.com> Date: Wed, 5 Aug 2026 19:10:20 +1000 Subject: [PATCH 11/12] chore: list project leads in CODEOWNERS --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index f1abfe9..c31fabe 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,4 +5,4 @@ # https://github.blog/2017-07-06-introducing-code-owners/ # These owners will be the default owners for everything in the repo. -* @Kiran01bm +* @Kiran01bm @aparajon @eeSeeGee @JashLal @jayjanssen @jemiahw @morgo From 8a87ba49174b1f069b2c571113cb7d99acb02d2d Mon Sep 17 00:00:00 2001 From: Kiran Muddukrishna <kmuddukrishna@squareup.com> Date: Wed, 5 Aug 2026 19:20:18 +1000 Subject: [PATCH 12/12] ci: pin golangci-lint-action and lint binary version The action's default binary is built with an older Go than the module targets and cannot load the v2 config; SHA-pinning also satisfies the semgrep and zizmor unpinned-action checks. --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7546aec..9e7fa7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,11 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: go.mod - - uses: golangci/golangci-lint-action@v6 + # Pin the same golangci-lint major used locally (v2 config format); + # the action's default binary lags and cannot load a v2 config. + - uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 + with: + version: v2.12.2 build: runs-on: ubuntu-latest