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
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ Observed entry points:

- `main.go`
- `api.New`
- `routes.RegisterRoutes`
- `api-spec/openapi.yaml`
- `routes.StatusRouter` and `routes.RegisterRoutes`
- `api-spec/v0/openapi.yaml`

Observed deployment helpers:

Expand All @@ -56,7 +56,8 @@ Do not modify these without an explicit task and approval:
- `SECURITY.md`
- `LICENSE`
- `public.jwk`
- `api-spec/dist/*` unless the source spec changed too
- `api-spec/v0/dist/*` unless the source spec changed too
- `api-spec/dist/v0/*` unless the source spec changed too

## 3. Agent Roles

Expand Down
122 changes: 52 additions & 70 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,54 @@
The server port is configured through `PORT` and defaults to `3000` in code;
the local example environment sets `PORT=8000`. The intended public API
contract for this branch is defined in `api-spec/v0/openapi.yaml`; this page
documents the currently wired Go runtime endpoints and their operational
caveats.
documents the endpoints and operational behavior that are actually wired in the
current Go runtime.

## 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
- The current branch does not register a Cognito or bearer-token validation
middleware.
- When `SKIP_AUTH=true`, `SkipAuthMiddleware` injects a stable local identity
into Fiber locals using the local values `sub`, `username`, `scope`, and
`groups`.
- When `SKIP_AUTH=false`, requests still reach the route handlers without an
additional auth gate in the current code path.

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 v0 contract still documents OAuth 2.0 client credentials. Treat
that as the intended public contract, not proof of current runtime enforcement.

## 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` | `/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`)

```go
type Request struct {
AccountID string `json:"accountId"`
OrganizationName string `json:"organizationName,omitempty"`
CaseReferenceID string `json:"caseReferenceId,omitempty"`
ContactEmail string `json:"contactEmail,omitempty"`
DateOfBirth string `json:"dateOfBirth"`
LastName string `json:"lastName"`
FirstName string `json:"firstName"`
SSN string `json:"ssn,omitempty"`
IdentityDetails []IdentityDetails `json:"identityDetails,omitempty"`
EndClient string `json:"endClient"`
PreviousNames []PreviousName `json:"previousNames,omitempty"`
Terms string `json:"terms"`
}
```
| Method | Path | Description | Success | Notes |
|---|---|---|---|---|
| `GET` | `/` | Liveness string | `200` text | Returns `Backend running!` |
| `GET` | `/health` | Redis health check | `200` empty | Pings Redis with a 2-second timeout and is wrapped by the breaker middleware |
| `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 | Binds request JSON, validates required identity fields, then calls NSC service |
| `POST` | `/api/v0/veteran-disability-ratings` | Veteran disability lookup | `200` JSON | Binds request JSON and requires either SSN or a complete address block |

### NSC Submit Response model (`pkg/education/models_response.go`)
## Request Validation and Error Semantics

```go
type Response struct {
ClientData ClientDataResponse `json:"clientData"`
IdentityDetails []IdentityDetailsResponse `json:"identityDetails"`
Status StatusResponse `json:"status"`
StudentInfoProvided StudentInfoProvidedResponse `json:"studentInfoProvided"`
TransactionDetails TransactionDetailsResponse `json:"transactionDetails"`
}
```
- Education requests require `firstName`, `lastName`, and `dateOfBirth`.
- Veteran requests require `firstName`, `lastName`, and `dateOfBirth`, plus
either `ssn` or a complete address (`street1`, `city`, `state`,
`postalCode`, `country`).
- Fiber error handling sends plain-text response bodies for `400`, `502`, and
`503` cases produced with `fiber.NewError(...)`.
- Both verification handlers return bare `404` responses for not-found cases.

## Examples

## Example: `/health`
### `/health`

```bash
curl -i http://localhost:8000/health
Expand All @@ -90,20 +64,24 @@ curl -i http://localhost:8000/health
curl -i http://localhost:8000/api-spec/v1/verify
```

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

## Example: `/api/edu` (auth skipped locally)
### `/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' \
--data '{
"firstName": "Lynette",
"lastName": "Oyola",
"dateOfBirth": "1988-10-24",
"ssn": "123-45-6789"
}'
```

## Example: `/api/v0/veteran-disability-ratings`
### `/api/v0/veteran-disability-ratings`

```bash
curl -i --request POST http://localhost:8000/api/v0/veteran-disability-ratings \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--data '{
"firstName": "Lynette",
"lastName": "Oyola",
Expand All @@ -120,14 +98,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.
- Error response bodies come from Fiber error handling and may be plain text.
- The route surface now matches the checked-in v0 contract for both public
verification operations, but the runtime still serves plain-text error bodies
instead of a versioned public error envelope.
- `GET /api-spec/v1/verify` is a runtime convenience route; the design-time
source of truth remains `api-spec/v0/openapi.yaml`.
- The current branch does not enforce the contract's documented OAuth 2.0
security scheme in Fiber middleware.

## 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` and
`POST /api/v0/veteran-disability-ratings` are the current verification
endpoints exposed by the Go service.
23 changes: 12 additions & 11 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ flowchart LR
## Interfaces and Abstractions

- `pkg/education/service.go`
- `type EducationService interface { Submit(ctx context.Context, req Request) (Response, error) }`
- `type Service interface { LookupEnrollmentStatus(ctx context.Context, req Request) (Response, error) }`
- `type HTTPTransport interface { Do(req *http.Request) (*http.Response, error) }`
- `pkg/core/otel.go`
- `type OtelService interface { SpanFromContext; LoggerProvider; Shutdown }`
Expand All @@ -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,13 @@ without route-layer rewrites.

Ordered middleware in `api.New`:

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. Request ID context propagation
2. Structured request logging (trace/span/request IDs)
3. Panic recovery
4. CORS (`*` origin/headers/methods)
5. OpenTelemetry Fiber middleware
6. `GET /health` registration
7. Conditional local skip-auth middleware when `SKIP_AUTH=true`

## Dependency Injection Pattern

Expand All @@ -102,10 +104,9 @@ 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 the optional skip-auth middleware, so it does not depend on injected local identity.
- 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 public contract is maintained separately in `api-spec/v0/openapi.yaml`; runtime error bodies are still plain text from Fiber rather than a shared JSON error envelope.
- Some tests require local Redis and fail when unavailable.

## Assumptions
Expand Down
110 changes: 110 additions & 0 deletions docs/audit/2026-04-15_09:02:39.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Documentation Accuracy Review (as of April 15, 2026)

## Section A: Executive Summary

Score: `95/100`

Risk statement: Primary runtime docs had drifted around live route shape,
auth wiring, setup envs, and several feature pages. This audit-and-fix pass
corrected the verified branch-local mismatches without changing Terraform
behavior or the public OpenAPI source contract.

Gate: `PASS`

Scope reviewed: repository root docs plus `docs/**/*.md`, with runtime code,
config, and tests treated as source of truth.

## Section B: Severity-Ranked Findings

| Severity | Doc location | Observed mismatch | Source-of-truth evidence on current branch | Recommended fix |
|---|---|---|---|---|
| `P1` | `docs/api.md`; `docs/overview.md`; `docs/architecture.md`; `docs/features/security/cognito-auth.md`; `docs/guides/02-authentication.md` | Runtime docs still described a removed `/api/edu` path and Cognito/JWKS validation, but the current branch registers `POST /api/v0/education-enrollments` and has no checked-in bearer-token validation middleware. | `api/routes/router.go:24`; `api/routes/router.go:45`; `api/routes/router.go:46`; `api/app.go:121`; `api/middleware/middleware.go:23`; `api/middleware/middleware.go:108` | Rewrite runtime and security docs to reflect the live route surface, the optional skip-auth identity injection, and the absence of current request-token enforcement. |
| `P2` | `docs/setup.md`; `docs/features/infrastructure/redis.md` | Setup docs listed unsupported Cognito config keys, understated the compose services, and missed the checked-in OTLP env-name mismatch; Redis feature docs still claimed a stale nil-Redis wiring caveat. | `pkg/core/config.go:15`; `pkg/core/config.go:93`; `.env.example:1`; `docker-compose.yml:1`; `docker-compose.yml:4`; `main.go:74`; `main.go:95`; `api/handlers/openapi_spec_handler.go:11` | Update setup and Redis docs to match current config keys, compose services, Redis injection, and the observed OTLP env mismatch. |
| `P2` | `docs/features/core/nsc-education.md` | The NSC feature page still claimed the handler built a hardcoded payload and lacked validation, but the current handler parses request JSON and validates required identity fields before lookup. | `api/handlers/education_handler.go:43`; `api/handlers/education_handler.go:50`; `api/handlers/education_handler.go:56`; `pkg/education/service.go:15` | Refresh the NSC feature page around the live request flow, current status codes, and remaining future work. |
| `P2` | `AGENTS.md`; `docs/research/auth-token-validation-strategy.md` | Root agent guidance and research notes overstated current contract and auth paths by pointing at stale artifact paths or presenting the old Cognito direction as current-state behavior. | `api-spec/v0/openapi.yaml:1`; `api-spec/v0/dist/openapi.bundled.json`; `api-spec/dist/v0/openapi.json`; `api/middleware/middleware.go:23`; `api/app.go:126` | Repair the observable contract paths in `AGENTS.md` and explicitly relabel the auth research note as historical rather than live runtime documentation. |

No unresolved `P1` findings remain after this pass.

## Section C: Update Backlog Checklist by Doc File

- [x] `docs/api.md`: replace stale `/api/edu` and Cognito claims with the live
endpoint surface, runtime auth caveat, and current error semantics.
- [x] `docs/overview.md`: update system context, package summary, and request
flow to match the current branch.
- [x] `docs/architecture.md`: correct middleware order, route inventory,
timeout values, and runtime caveats.
- [x] `docs/setup.md`: align supported env vars, compose services, and OTLP env
caveat with checked-in config.
- [x] `docs/features/core/nsc-education.md`: update handler behavior and status
semantics.
- [x] `docs/features/security/cognito-auth.md`: replace nonexistent Cognito
verifier claims with current auth wiring.
- [x] `docs/features/README.md`: rename the security entry so the index matches
the repaired feature page.
- [x] `docs/features/infrastructure/redis.md`: remove the stale nil-Redis
injection caveat.
- [x] `docs/guides/01-getting-started.md`: fix the malformed email link.
- [x] `docs/guides/02-authentication.md`: add a contract-vs-runtime caveat.
- [x] `docs/research/auth-token-validation-strategy.md`: relabel the Cognito
note as historical research instead of current implementation guidance.
- [x] `AGENTS.md`: repair versioned OpenAPI entry-point and bundled-artifact
paths.

## Section D: Hygiene Appendix

Commands run:

- `git status --short --branch`
- `git log --oneline -n 12`
- `sed -n '1,240p' main.go`
- `sed -n '1,220p' api/routes/router.go`
- `sed -n '1,220p' api/routes/status_router.go`
- `sed -n '1,220p' api/app.go`
- `sed -n '1,260p' pkg/core/config.go`
- `sed -n '1,240p' .env.example`
- `sed -n '1,260p' docker-compose.yml`
- `sed -n '1,220p' api/handlers/education_handler.go`
- `sed -n '1,240p' api/handlers/veteran_handler.go`
- `sed -n '1,260p' api/middleware/middleware.go`
- `sed -n '1,220p' pkg/education/service.go`
- `sed -n '1,220p' pkg/veteran/service.go`
- `sed -n '1,260p' api-spec/README.md`
- `sed -n '1,260p' api-spec/v0/openapi.yaml`
- `git ls-files -z | xargs -0 rg -n --hidden --no-ignore "(BEGIN (RSA|EC|OPENSSH|DSA) PRIVATE KEY|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|xox[baprs]-[A-Za-z0-9-]+|aws_secret_access_key|AIza[0-9A-Za-z_-]{35})" || true`
- `node - <<'EOF' ... EOF` for relative Markdown link/path validation across `README.md`, `AGENTS.md`, `api-spec/README.md`, `schema/README.md`, and `docs/**/*.md`
- `pnpm exec markdownlint-cli2 "**/*.md" "**/*.MD" "#node_modules" "#.github"`

Sensitive-file scan:

- The regex scan found no committed private keys, access tokens, or live
credential material in the tracked documentation or config files reviewed for
this run.
- Existing `public.jwk` remains untouched and outside the edit set.

Markdown validation:

- Relative link/path validation passed after fixing the malformed contact link
in `docs/guides/01-getting-started.md`.
- `markdownlint-cli2` completed successfully with `0 error(s)`.

Hygiene verdict:

- `PASS`

## Section E: Deferred Watchlist (Non-blocking)

- If real bearer-token validation returns to the runtime, update:
- `docs/api.md`
- `docs/overview.md`
- `docs/architecture.md`
- `docs/features/security/cognito-auth.md`
- `docs/guides/01-getting-started.md`
- `docs/guides/02-authentication.md`
- `docs/research/auth-token-validation-strategy.md`
- If compose or app config is changed to use the same OTLP env key, update:
- `docs/setup.md`
- `docs/features/infrastructure/opentelemetry.md`
- If the public contract adopts a versioned non-2xx error envelope, update:
- `docs/api.md`
- `docs/features/core/nsc-education.md`
- `docs/features/core/edu-openapi-spec.md`
Loading
Loading