Architecture: collapse source model, defer config parsing, set v1 contracts
postern has no known deployments. This is the moment to set our contracts before we're locked into them. ## Architecture intent 1. **One `Source` interface** -- `Run(ctx, emit) error` + `Ready()`. Push and pull collapse. Adapters self-register from `init()`. 2. **Deferred-config YAML** -- each source parses its own block; central schema knows nothing about adapter internals. 3. **`secrets.Resolver` interface** -- `EnvResolver` today; Vault/file/K8s drop-in later. Adapters never call `os.Getenv` directly. 4. **`Mux` interface, `Stater` interface, errgroup for run + explicit shutdown choreographer.** 5. **`version: 1` required.** Source types namespaced: `azure.eventgrid`, `gcp.pubsub`, `aws.sqs`, `cloudevents`, `kafka`. 6. **`/livez` + `/readyz` split** (replaces `/healthz`). `readyz` ANDs every source's `Ready()` with dispatcher started. 7. **`postern validate` + `postern fire` subcommands.** validate runs every factory in DryRun mode; fire runs a sample CloudEvent through routes with dry-trigger. 8. **Standardized log keys** (OTel semconv where applicable, postern-snake elsewhere). Optional OTel trace+log export when `OTEL_EXPORTER_OTLP_ENDPOINT` is set; otherwise zero runtime cost. ## Testing plan End-to-end on GCP: - Cloud Run service `postern-v2` in `us-central1` - GCP Pub/Sub topic + push subscription with OIDC - Pipeline triggered on a validation project with extracted variables (`BUCKET`, `OBJECT`, `EVENT_TIME`, `MESSAGE_ID`) flowing through Pub/Sub -> postern -> GitLab CI - `go test -race ./...` green across all packages ## Breaking changes (all acceptable -- no users) - Config schema (version field, deferred parse, flattened adapter blocks) - Source type names - `/healthz` removed - CLI shape (subcommands) ## Why now postern is small enough today (~3000 LOC) to redesign cleanly. Each of these concerns is right; we ship them now or pay a compat tax forever. This builds on @graysongordon-gl's foundation in !9 and goes further than that issue scoped because the window for idiomatic-without-compat-tax closes the moment we have a user.
issue