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