Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/pnpm-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: pnpm audit

on:
workflow_dispatch:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
paths:
- package.json
- pnpm-lock.yaml

permissions:
contents: read

jobs:
audit:
name: audit
runs-on: self-hosted
steps:
- name: Fetch Repository
uses: actions/checkout@v6

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 10.32.1
run_install: false

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "lts/*"

- name: Run pnpm audit
run: pnpm audit --audit-level low
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Observed entry points:
- `main.go`
- `api.New`
- `routes.RegisterRoutes`
- `api-spec/openapi.yaml`
- `api-spec/v0/openapi.yaml`

Observed deployment helpers:

Expand Down
39 changes: 14 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,10 @@ This project uses [pre-commit](https://pre-commit.com/ "pre-commit Docs") to reg
pre-commit install
```

For OpenAPI spec maintenance, the repo also includes utility scripts under
`scripts/`:

```sh
# 1. Rebuild the bundled YAML and JSON artifacts from the design-time source spec
./scripts/bundle-api-spec

# 2. Validate the bundled YAML artifact
./scripts/validate-api-spec

# 3. Lint the bundled YAML artifact with the repo Spectral ruleset
./scripts/lint-api-spec

# 4. Lint hand-authored YAML files
./scripts/lint-yaml-files

# 5. Check formatting for contract YAML and JSON files
./scripts/check-format-contract-files

# 6. Compile standalone JSON Schemas
./scripts/check-json-schemas

# 7. Check for breaking OpenAPI changes against a base ref
./scripts/check-openapi-breaking [base-ref]
```
For contract/spec maintenance, use the repo's `mise` tasks or the underlying
`pnpm` scripts. The checked-in `scripts/` directory only contains a small set
of shell helpers such as `check-format-contract-files`, `check-json-schemas`,
`build-image`, `push-image`, and `deploy-ecs`.

If you use [mise](https://mise.jdx.dev/), install the pinned runtimes from
`mise.toml` and run:
Expand All @@ -71,6 +50,16 @@ You can also run each step individually with `mise run bundle-api-spec`,
`mise run lint-yaml-files`, `mise run check-format-contract-files`,
`mise run check-json-schemas`, and `mise run check-openapi-breaking`.

Without `mise`, the same OpenAPI steps are exposed through `pnpm`:

```sh
pnpm run bundle:api-spec
pnpm run validate:api-spec
pnpm run lint:api-spec
pnpm run check:fmt
pnpm run check:schemas
```

## Policies

### Open Source Policy
Expand Down
16 changes: 9 additions & 7 deletions api-spec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,21 @@ The public contract in this branch is currently defined in

## Bundling And Checks

Use the repo scripts from the project root:
Use the supported task runners from the project root.

With `mise`:

```sh
./scripts/bundle-api-spec
./scripts/validate-api-spec
./scripts/lint-api-spec
mise install
mise run check-api-spec
```

Or, with `mise`:
Or run the underlying `pnpm` scripts directly:

```sh
mise install
mise run check-api-spec
pnpm run bundle:api-spec
pnpm run validate:api-spec
pnpm run lint:api-spec
```

Bundling produces checked-in versioned artifacts under `api-spec/v0/dist/` and
Expand Down
81 changes: 43 additions & 38 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,36 @@ caveats.

## Authentication Behavior

- When `SKIP_AUTH=false`, Cognito middleware is enabled globally.
- Middleware reads access token from header: `x-amzn-oidc-accesstoken`.
- Token checks include:
- valid signature via JWKS
- issuer match
- `token_use=access`
- `client_id` claim equals configured app client ID

If auth fails, response is `401 Unauthorized`.
This applies to `/api/edu` and `/api/v0/veteran-disability-ratings` when auth is
enabled. `/health` is registered before the auth middleware and remains
unauthenticated in the current branch.
- The checked-in public contract in `api-spec/v0/openapi.yaml` requires bearer
tokens via `OAuth2ClientCredentials`.
- The current runtime wiring in `api.New` does not register a Cognito or bearer
token enforcement middleware.
- When `SKIP_AUTH=true`, `SkipAuthMiddleware` injects local identity values from
optional `x-skip-auth-*` headers into Fiber locals for routes registered
after that middleware.
- `/health` is registered before `SkipAuthMiddleware`, so it remains an
unauthenticated health route in the current branch.

## Circuit Breaker Behavior

`/health`, `/api/edu`, and `/api/v0/veteran-disability-ratings` are wrapped by
Redis-backed circuit breaker middleware.
`/health`, `POST /api/v0/education-enrollments`, and
`POST /api/v0/veteran-disability-ratings` are wrapped by Redis-backed circuit
breaker middleware.

- On breaker deny/open state: `503 Service Unavailable`.
- On Redis state read failures with fail-open (default): request is allowed.

## Runtime Endpoints

| Method | Path | Description | Success | Notes |
| ------ | ------------------------------ | -------------------------------------- | ----------- | ------------------------------------------------------------------ |
| `GET` | `/` | Liveness string | `200` text | Returns `Backend running!` |
| `GET` | `/health` | Redis health check | `200` empty | Registered before auth middleware; pings Redis with 2s timeout |
| `GET` | `/api/edu` | NSC education verification scaffold | `200` JSON | Uses a hardcoded request payload in handler; not the v0 contract |
| `POST` | `/api/v0/veteran-disability-ratings` | Veteran disability status from v0 spec | `200` JSON | Accepts caller-provided identity payload and matches the v0 route |
| Method | Path | Description | Success | Notes |
| ------ | ---- | ----------- | ------- | ----- |
| `GET` | `/` | Liveness string | `200` text | Returns `Backend running!` |
| `GET` | `/health` | Redis health check | `200` empty | Uses a 2-second Redis ping timeout and is wrapped by the circuit breaker |
| `GET` | `/api-spec/v1/verify` | Bundled OpenAPI JSON artifact | `200` JSON | Returns `api-spec/v0/dist/openapi.bundled.json` |
| `POST` | `/api/v0/education-enrollments` | Education enrollment lookup | `200` JSON | Parses caller-provided JSON into `education.Request` |
| `POST` | `/api/v0/veteran-disability-ratings` | Veteran disability status from v0 spec | `200` JSON | Parses caller-provided JSON into `veteran.Request` |

| Method | Path | Description | Success | Notes |
| ------ | --------------------- | ----------------------------------- | ----------- | ----- |
| `GET` | `/` | Liveness string | `200` text | Returns `Backend running!` |
| `GET` | `/status` | Redis health check | `200` empty | Uses 2s Redis ping timeout; wrapped by circuit breaker |
| `GET` | `/api-spec/v1/verify` | Bundled OpenAPI JSON artifact | `200` JSON | Returns `api-spec/dist/openapi.bundled.json` |
| `GET` | `/api/edu` | Education verification passthrough | `200` JSON | Uses hardcoded request payload in handler; wrapped by circuit breaker |

### NSC Submit Request model (`pkg/education/models_request.go`)
### Education Submit Request model (`pkg/education/models_request.go`)

```go
type Request struct {
Expand All @@ -66,7 +58,7 @@ type Request struct {
}
```

### NSC Submit Response model (`pkg/education/models_response.go`)
### Education Submit Response model (`pkg/education/models_response.go`)

```go
type Response struct {
Expand All @@ -84,18 +76,27 @@ type Response struct {
curl -i http://localhost:8000/health
```

### `/api-spec/v1/verify`
## Example: `/api-spec/v1/verify`

```bash
curl -i http://localhost:8000/api-spec/v1/verify
```

Returns the checked-in bundled OpenAPI JSON artifact with `Content-Type: application/json`.
Returns the checked-in bundled OpenAPI JSON artifact with
`Content-Type: application/json`.

## Example: `/api/edu` (auth skipped locally)
## Example: `/api/v0/education-enrollments`

```bash
curl -i http://localhost:8000/api/edu
curl -i --request POST http://localhost:8000/api/v0/education-enrollments \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--data '{
"firstName": "Lynette",
"lastName": "Oyola",
"dateOfBirth": "1988-10-24",
"ssn": "123-45-6789"
}'
```

## Example: `/api/v0/veteran-disability-ratings`
Expand All @@ -120,14 +121,18 @@ curl -i --request POST http://localhost:8000/api/v0/veteran-disability-ratings \

## Current-State Caveats

- `/api/edu` currently does not accept caller-provided payload; it submits a hardcoded sample request from handler code.
- `main` now injects Redis into `api.New`, so the health route has the Redis client it expects.
- The intended public contract for this branch is versioned under `api-spec/v0/`, and the veteran disability route matches that contract while `/api/edu` remains a runtime-only scaffold.
- The current runtime does not enforce the bearer-token auth described in the
public OpenAPI contract.
- `main` injects Redis into `api.New`, so the health route and breaker
middleware share the same Redis dependency.
- The intended public contract for this branch is versioned under
`api-spec/v0/`, and both versioned POST routes are registered in the current
runtime.
- Error response bodies come from Fiber error handling and may be plain text.

## Assumptions

- **High confidence:** This page is a runtime reference, not the public API
contract reference.
- **Medium confidence:** `/api/edu` will be removed or reshaped as the runtime
converges on the published v0 contract.
- **High confidence:** `POST /api/v0/education-enrollments` is the current
education verification route in the runtime.
19 changes: 10 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ without route-layer rewrites.
- `runServer` starts `app.Listen` in a goroutine and selects on server error or signal context cancellation.
- graceful shutdown uses `app.ShutdownWithTimeout(5 * time.Second)`.
- Request lifecycle:
- handlers create per-request contexts with timeout (`/health`: 2s, `/api/edu`: 5s, `/api/v0/veteran-disability-ratings`: 5s).
- handlers create per-request contexts with timeout (`/health`: 2s, `/api/v0/education-enrollments`: 30s, `/api/v0/veteran-disability-ratings`: 5s).
- Circuit-breaker middleware:
- breaker registry map guarded with `sync.RWMutex`.
- lazy breaker initialization via double-check lock pattern.
Expand All @@ -82,11 +82,12 @@ without route-layer rewrites.

Ordered middleware in `api.New`:

1. Request ID propagation into `UserContext`
1. Structured request logging (trace/span/request IDs)
1. Recover
2. CORS (`*` origin/headers/methods)
3. OpenTelemetry Fiber middleware
4. Structured request logging (trace/span/request IDs)
5. Conditional Cognito auth middleware
1. CORS (`*` origin/headers/methods)
1. OpenTelemetry Fiber middleware
1. Optional `SkipAuthMiddleware` when `SKIP_AUTH=true`

## Dependency Injection Pattern

Expand All @@ -102,10 +103,10 @@ injects it.

## Technical Caveats (Current State)

- `/api/edu` handler builds a hardcoded request payload instead of binding user input.
- `/health` is registered before the auth middleware, so it remains a runtime-only unauthenticated health route.
- Current runtime routes are a mix of scaffold and contract-aligned paths: `GET /`, `GET /health`, `GET /api/edu`, and `POST /api/v0/veteran-disability-ratings`.
- `GET /api/edu` remains runtime scaffolding, while `POST /api/v0/veteran-disability-ratings` matches the checked-in v0 contract in `api-spec/v0/openapi.yaml`.
- `/health` is registered before `SkipAuthMiddleware`, so it remains an unauthenticated health route.
- No bearer-token or Cognito verifier middleware is currently registered in `api.New`.
- Current runtime routes are `GET /`, `GET /health`, `GET /api-spec/v1/verify`, `POST /api/v0/education-enrollments`, and `POST /api/v0/veteran-disability-ratings`.
- The two versioned POST routes match the checked-in v0 contract paths in `api-spec/v0/openapi.yaml`.
- Some tests require local Redis and fail when unavailable.

## Assumptions
Expand Down
Loading
Loading