Files
support_backend/specs/001-ci-pipeline/research.md
T

103 lines
6.6 KiB
Markdown
Raw Normal View History

# Phase 0 Research: Continuous Integration Pipeline
No `NEEDS CLARIFICATION` markers remained in the Technical Context after `/speckit-plan`'s
Technical Context pass — this document records the decisions behind that context rather than
resolving open unknowns.
## Decision: CI system — Jenkins declarative pipeline
- **Decision**: Use a single `Jenkinsfile` (declarative syntax) at the repo root.
- **Rationale**: The constitution's Technology & Platform Constraints section and
`docs/09-testing-observability-cicd.md` §3 both name Jenkins explicitly, with a defined stage
order. This isn't a free choice — using anything else would need a constitution amendment.
- **Alternatives considered**: GitHub Actions / GitLab CI — rejected only because the governing
docs already commit to Jenkins; otherwise equally viable for this repo's needs.
## Decision: Environment/secrets handling in the pipeline
- **Decision**: The pipeline injects `POSTGRES_PASSWORD`, `REDIS_PASSWORD`, `JWT_SECRET`, and AWS
credentials from Jenkins' credentials store as environment variables / a generated `.env.*`
file written into the workspace at runtime — never read from a file committed to the
repository.
- **Rationale**: `.env.development`, `.env.test`, and `.env.prod` were found committed to git
with real dev credentials in plain text (fixed separately: untracked, `.env.example` added,
see repo commit `2093898`). `docker-compose.test.yml`'s `app` service still declares
`env_file: .env.test`, so the pipeline's test stage must materialize a `.env.test` in the
workspace from Jenkins credentials immediately before `docker compose up`, then discard it when
the stage ends — the checked-in `.env.test` template must only ever contain non-secret
placeholder values from here on, matching `.env.prod`'s existing `CHANGE_ME` pattern.
Production deploy correspondingly generates `.env.prod` the same way, from Jenkins prod
credentials, never from the repo copy.
- **Alternatives considered**: Docker secrets / mounted files instead of generated `.env` files —
viable but a larger change to `docker-compose.*.yml`; deferred as out of scope since it doesn't
change the pipeline's external behavior (FR-008 is satisfied either way).
## Decision: Environment validation stage
- **Decision**: The environment-validation stage runs the existing `env.ts` Zod schema
(`src/config/env.ts`) against the materialized environment before any test stage starts, by
invoking a lightweight script (e.g. `node --env-file=.env.<target> -e "require('./dist/src/config/env.js')"`
post-build, or a dedicated `tsx` invocation pre-build) so a missing/malformed variable fails
immediately with the schema's existing descriptive Zod error, satisfying FR-002.
- **Rationale**: `src/config/env.ts` already throws a specific, actionable error
(`❌ Invalid environment variables: ...`) on `safeParse` failure — no new validation logic is
needed, just an early pipeline invocation of the existing one.
- **Alternatives considered**: A separate shell script re-implementing required-var checks —
rejected as duplicate logic that could drift from the real Zod schema.
## Decision: Quality stage contents
- **Decision**: Stage-to-script mapping is direct:
- `install``npm ci`
- `typecheck``npm run typecheck`
- `lint``npm run lint` (consider folding `scripts/check-architecture.ts`'s module-boundary
check into this stage, since it enforces constitution Principle III and already runs in
`.husky/pre-commit` — confirmed as in-scope, see Assumptions below)
- `format check``npm run format:check`
- `unit test``npm run test:unit`
- `integration test``npm run test:integration` (requires `docker-compose.test.yml`'s
`postgres`/`redis` services running first)
- `e2e test``npm run test:e2e` (same dependency)
- `build``npm run build:prod` (or `build:test`/`build:development` depending on target,
matching the `BUILD_COMMAND` pattern already used by each `docker-compose.*.yml`)
- `docker build``docker build` using the existing root `Dockerfile`
- **Rationale**: Every stage maps to a script that already exists and is already exercised
locally/in the pre-commit hook — the pipeline's job is orchestration and environment isolation,
not defining new checks (matches plan.md's Summary).
- **Alternatives considered**: None — this mapping is essentially forced by "don't introduce new
checks" (spec.md Assumptions).
## Decision: Publish/deploy mechanism
- **Decision**: `publish` pushes the built image to a container registry (registry choice left to
implementation/tasks phase — no registry is currently configured in the repo); `deploy` runs
`docker compose --env-file <generated .env> -f docker-compose.<target>.yml up -d` on the target
host/agent, reusing the `docker:up:*` npm scripts' underlying compose invocation.
- **Rationale**: The repo already models per-environment deployment as
`docker compose -f docker-compose.<env>.yml up -d` (see `docker:up:dev`, `docker:up:test`,
`docker:up:prod` in `package.json`) — the pipeline should drive the same mechanism an engineer
would run by hand today, not invent a new one.
- **Alternatives considered**: Kubernetes/Helm deploy — no k8s manifests exist in the repo today;
out of scope unless a future feature introduces them.
## Decision: Concurrent-run isolation (FR-009)
- **Decision**: Rely on Jenkins' per-build workspace isolation (each pipeline run gets its own
workspace directory and, for the Docker-dependent stages, project-scoped Compose project names
e.g. `-p support-test-${BUILD_NUMBER}`) rather than building custom isolation logic.
- **Rationale**: This is a built-in Jenkins guarantee once each build uses its own workspace and
Compose project name; no additional application code is needed.
- **Alternatives considered**: None needed — default Jenkins behavior already satisfies this when
Compose project names are parameterized by build number.
## Assumptions carried over from spec.md, confirmed against the codebase
- `package.json` scripts (`typecheck`, `lint`, `format:check`, `test:unit`, `test:integration`,
`test:e2e`, `build*`, `docker:*`) are confirmed present and are the source of truth for stage
behavior.
- `docker-compose.development.yml` / `.test.yml` / `.prod.yml` are confirmed present and already
encode per-environment deploy shape.
- `.husky/pre-commit` already runs `lint-staged` and `scripts/check-architecture.ts` locally —
the CI lint stage should run the same architecture check server-side so a bypassed/missing
local hook can't let a boundary violation merge.