Assignment brief: see requirements/requirements.md (Part 1) and requirements/cli-implementation.md (CLI implementation spec). Part 2 assignment text: requirements/requirements-part2.md.
- Agentic runs (workflow snapshots)
- Stack
- Third-party dependencies
- Environment
- Quick Start (Docker)
- Quick Start (No Docker)
- API (summary)
- Run with Docker Compose
- Run without Docker (local Node)
- Tests
- Canonical example payload
- Blueprint CLI (Part 2 — Issue #65)
- Implementation Summary
- Key Decisions
- Run & Verify Locally
This repository was built across four agentic passes (issue-driven Cursor runs). Each pass produced a self-contained copy of the product workflow artifacts under its own folder. Together they form a chronological trail: clarified requirements, implementation plan, decision log, and validation report for that run.
| Run | Workflow folder | Scope | Summary |
|---|---|---|---|
| 1 | workflow.1/ |
Part 1 API baseline (Issue #59) | Initial Blueprint Manager API: Express, pg, Zod, Flyway V1__create_blueprints.sql, Docker Compose (db / flyway / api), Vitest unit + integration suites, and scripts/run-integration-tests.sh for local integration verification. |
| 2 | workflow.2/ |
ORM, OOP, idempotent POST (Issue #61) | Migrated persistence to Prisma with IBlueprintRepository, Flyway V2__add_idempotency_key.sql, and Idempotency-Key semantics (201 / 200 / 409); public JSON omits idempotency_key. |
| 3 | workflow.3/ |
Sort tests + JSON errors (Issue #63) | Integration tests assert list row order for sort=name and sort=created_at; malformed JSON on POST /blueprints maps to 400 validation_error / Invalid JSON body via isMalformedJsonBodyError. |
| 4 | workflow.4/ |
Go CLI + httptest (Issue #65) | cli/ blueprintctl: Cobra, net/http, base URL via env/flag, httptest coverage, README examples with bricks.json; verify with go test ./... (see Run & Verify Locally). No ci/ folder in the assignment deliverable. |
Inside each folder you will find the same five files: product_requirements_clarified.md, requirements.md, plan.md, decision_log.md, and validation.md. Paths inside those snapshots may still mention historical assignments/bluebricks/ layout; the current tree is the repository root shown here. This assignment does not include a ci/ directory; use scripts/run-integration-tests.sh and npm run test:integration (see Tests and Run & Verify Locally).
Validation reports only: Run 1 · Run 2 · Run 3 · Run 4.
- Node.js 18+, TypeScript, Express 5, Prisma (
@prisma/client), Zod - PostgreSQL 16 (
postgres:16-alpine) - Schema: Flyway SQL in
db/migration/(source of truth);prisma/schema.prismamirrors tables for the ORM (npm run buildrunsprisma generate) - Docker Compose:
db, one-shotflyway, andapi(seedocker-compose.yml)
Libraries and tools pulled in via package.json and cli/go.mod. Base images (Node, Postgres, Flyway in Docker) are omitted here.
| Package | Kind | Summary |
|---|---|---|
@prisma/client |
runtime | Generated Prisma Client for type-safe queries against PostgreSQL. |
express |
runtime | HTTP server, routing, and middleware (including JSON body parsing). |
zod |
runtime | Schema validation for request bodies and list query parameters. |
prisma |
dev / build | CLI for prisma generate (runs on npm run build / postinstall). |
typescript |
dev / build | Compiles TypeScript to JavaScript in dist/. |
vitest |
dev / test | Unit and integration test runner. |
supertest |
dev / test | HTTP-level assertions against the Express app in tests. |
tsx |
dev | Runs TypeScript directly for start:dev / watch workflows. |
@types/express |
dev | TypeScript typings for Express. |
@types/node |
dev | TypeScript typings for Node.js APIs. |
@types/supertest |
dev | TypeScript typings for Supertest. |
| Module | Kind | Summary |
|---|---|---|
github.com/spf13/cobra |
direct | CLI framework: subcommands (create, get, …) and persistent flags. |
github.com/spf13/pflag |
transitive | POSIX-style flag parsing used by Cobra. |
github.com/inconshreveable/mousetrap |
transitive | Windows console helper bundled with Cobra for nicer CLI behavior. |
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string (required for npm start / tests) |
PORT |
HTTP port (default 3000) |
Default local URL when using Compose for the API: http://localhost:3000.
From repository root:
docker compose up --buildVerify the API:
curl http://localhost:3000/blueprints?page=1&page_size=20Prerequisite: a running PostgreSQL instance and a valid DATABASE_URL.
npm ci
npm run build
npm startRun migrations before first start (Flyway runs in Docker):
bash scripts/migrate-flyway.shBase path: /blueprints
POST /blueprints— create (201). Optional headerIdempotency-Key: same key + same JSON body as an existing successful create → 200 with the sameid/created_at; same key + different body → 409{ "error": "conflict", "message": "..." }. Empty/whitespace key is ignored (normal create). Key max length 255 after trim.GET /blueprints?page=&page_size=&sort=&order=— list with pagination (sort:name|version|created_at; default order:created_at DESC, id DESC)GET /blueprints/:id— single (404 JSON if missing; 400 if id invalid)PUT /blueprints/:id— merge updateDELETE /blueprints/:id— 204
Responses do not include the internal idempotency_key column.
Validation errors: 400 with { "error": "validation_error", "message": "..." }. Malformed JSON (invalid syntax with Content-Type: application/json): 400 with { "error": "validation_error", "message": "Invalid JSON body" }. Idempotency key too long: 400 validation_error. DB unreachable: 503 with { "error": "service_unavailable", "message": "Database unavailable" }.
From repository root:
docker compose up --buildFlyway runs once against db before api starts. Postgres is exposed on host port 5432.
Start Postgres yourself and set DATABASE_URL, then:
npm ci
bash scripts/migrate-flyway.sh # requires Docker for flyway/flyway image
npm run build
npm startnpm ci
npm run test:unit # no databaseIntegration tests need Postgres on DATABASE_URL (default postgresql://blueprint:blueprint@127.0.0.1:5432/blueprints) and Docker for Flyway:
docker compose up -d db
# wait for healthy
npm run test:integration
docker compose down -vThe integration script runs Flyway then Vitest unless SKIP_FLYWAY_INTEGRATION=1 (used by scripts/run-integration-tests.sh after Flyway already ran).
bricks.json matches the assignment’s example and is used in integration tests.
Go module under cli/: blueprintctl talks to this API over HTTP only (no direct Postgres). Requires Go 1.22+.
| Setting | Description |
|---|---|
BLUEPRINTS_API_BASE |
API base URL (default http://localhost:3000) |
--base-url |
Global flag; non-empty value overrides the env var and default |
Trailing slashes on the base URL are normalized so paths resolve to {base}/blueprints and {base}/blueprints/{id} without double slashes.
Exit codes: 0 success; 1 usage / client-side validation / 4xx; 2 network errors / 5xx. Successful JSON responses are printed to stdout. API error bodies (4xx / 5xx) are printed to stderr. delete on 204 prints nothing to stdout. Each HTTP call uses a 60-second client timeout.
cd cli
go test ./...
go build -o blueprintctl ./cmd/blueprintctlWith the API running (e.g. docker compose up --build from repository root), from cli/:
export BLUEPRINTS_API_BASE=http://localhost:3000
./blueprintctl create --file ../bricks.json
./blueprintctl create --file ../bricks.json --idempotency-key my-key-1
./blueprintctl list --page 1 --page-size 20
./blueprintctl list --page 1 --page-size 20 --sort name --order asc
./blueprintctl get --id 1
./blueprintctl update --id 1 --file ../bricks.json
./blueprintctl delete --id 1The service uses Prisma for all database access, with IBlueprintRepository implemented by PrismaBlueprintRepository. Flyway migration V2__add_idempotency_key.sql adds a nullable, uniquely indexed idempotency_key column. POST /blueprints reads the Idempotency-Key header: duplicate key with matching body returns 200 and the original row; duplicate key with a different body returns 409. Concurrent creates with the same new key rely on the unique index and P2002 handling. List queries use Prisma orderBy with fixed sort branches (no user-controlled SQL fragments). The Dockerfile copies prisma/ before npm ci so postinstall can run prisma generate, and copies the generated .prisma engine from the build stage into the production image.
Issue #63: Integration tests assert row order for sort=name&order=asc and sort=created_at&order=asc. Invalid JSON bodies on POST are mapped in blueprintErrorHandler from Express body-parser’s entity.parse.failed (400) to { "error": "validation_error", "message": "Invalid JSON body" } so clients never see 500 for a simple syntax error.
Issue #65: The cli/ Go module implements blueprintctl with Cobra subcommands (create, get, list, update, delete), net/http, and httptest-backed tests for request shape and exit codes. scripts/run-integration-tests.sh covers the Node API integration suite only; run cd cli && go test ./... separately (documented under Run & Verify Locally).
- Prisma as the ORM (issue #61); Flyway remains authoritative for DDL.
IBlueprintRepository+PrismaBlueprintRepositoryfor OOP boundaries.- Idempotency via
Idempotency-Keyheader + DB unique constraint + payload deep equality (util.isDeepStrictEqualonblueprint_data). - Public JSON omits
idempotency_key. isMalformedJsonBodyError(issue #63) detects body-parser JSON parse failures and returns structured 400 like other validation errors.- Integration sort tests (issue #63) filter rows by unique name prefix and assert ordering (
page_size≤ 100 per API rules). - Issue #65 —
blueprintctl: Cobra CLI,internal/runner(commands + exit mapping),internal/client(60s timeout),internal/config/internal/urls/internal/validate; 4xx → stderr + exit 1, 5xx / network → stderr + exit 2;delete204 silent stdout.
npm ci
npm run test:unit
bash scripts/run-integration-tests.sh
npm run build
docker compose build api
cd cli && go test ./...Expected: unit tests pass (Vitest 19 tests in tests/unit/); integration script runs Flyway (if needed), integration tests pass, go test ./... in cli/ passes when Go is available, Compose teardown succeeds; TypeScript build succeeds; API image builds successfully.
End-to-end CLI smoke (live API): With the service reachable at http://127.0.0.1:3000 (for example docker compose up --build when ports 3000 / Postgres are free), run bash scripts/verify-cli-against-api.sh. Set BLUEPRINTS_API_BASE if the API uses another origin.