/speckit-specify + /speckit-plan output for 001-ci-pipeline: feature spec with 2 user stories and 10 functional requirements, requirements quality checklist, implementation plan with constitution gate check, Phase 0 research (6 decisions incl. secrets-from-credentials-store), Phase 1 data model, pipeline stage contract, and a 5-scenario quickstart validation guide. No Jenkinsfile yet — that's the implementation step after /speckit-tasks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.6 KiB
6.6 KiB
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.prodwere found committed to git with real dev credentials in plain text (fixed separately: untracked,.env.exampleadded, see repo commit2093898).docker-compose.test.yml'sappservice still declaresenv_file: .env.test, so the pipeline's test stage must materialize a.env.testin the workspace from Jenkins credentials immediately beforedocker compose up, then discard it when the stage ends — the checked-in.env.testtemplate must only ever contain non-secret placeholder values from here on, matching.env.prod's existingCHANGE_MEpattern. Production deploy correspondingly generates.env.prodthe same way, from Jenkins prod credentials, never from the repo copy. - Alternatives considered: Docker secrets / mounted files instead of generated
.envfiles — viable but a larger change todocker-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.tsZod 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 dedicatedtsxinvocation pre-build) so a missing/malformed variable fails immediately with the schema's existing descriptive Zod error, satisfying FR-002. - Rationale:
src/config/env.tsalready throws a specific, actionable error (❌ Invalid environment variables: ...) onsafeParsefailure — 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 citypecheck→npm run typechecklint→npm run lint(consider foldingscripts/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:checkunit test→npm run test:unitintegration test→npm run test:integration(requiresdocker-compose.test.yml'spostgres/redisservices running first)e2e test→npm run test:e2e(same dependency)build→npm run build:prod(orbuild:test/build:developmentdepending on target, matching theBUILD_COMMANDpattern already used by eachdocker-compose.*.yml)docker build→docker buildusing the existing rootDockerfile
- 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:
publishpushes the built image to a container registry (registry choice left to implementation/tasks phase — no registry is currently configured in the repo);deployrunsdocker compose --env-file <generated .env> -f docker-compose.<target>.yml up -don the target host/agent, reusing thedocker:up:*npm scripts' underlying compose invocation. - Rationale: The repo already models per-environment deployment as
docker compose -f docker-compose.<env>.yml up -d(seedocker:up:dev,docker:up:test,docker:up:prodinpackage.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.jsonscripts (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.ymlare confirmed present and already encode per-environment deploy shape..husky/pre-commitalready runslint-stagedandscripts/check-architecture.tslocally — 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.