Skip to content
Merged
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
44 changes: 44 additions & 0 deletions .tasks/done/2026-02-17-api-input-validation-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Complete API input validation hardening
status: done
created: 2026-02-17
updated: 2026-02-17
---

## Objective

Harden all API endpoints by validating path parameters, query parameters,
and adding missing 400 responses to OpenAPI specs. The prior validation audit
only covered request body fields via `validate:` struct tags.

## Changes

### OpenAPI specs (400 responses added)

- `internal/api/job/gen/api.yaml` — Added 400 to GET /job, GET /job/{id},
DELETE /job/{id}
- `internal/api/system/gen/api.yaml` — Added 400 to GET /system/hostname,
GET /system/status

### Handler validation (8 handlers)

- `network_dns_get_by_interface.go` — interfaceName (required,alphanum) +
target_hostname (min=1 when provided)
- `network_dns_put_by_interface.go` — target_hostname validation
- `network_ping_post.go` — target_hostname validation
- `system_hostname_get.go` — target_hostname validation
- `system_status_get.go` — target_hostname validation
- `job_get.go` — ID (required,uuid) validation
- `job_delete.go` — ID (required,uuid) validation
- `job_list.go` — status enum validation

### Tests (10 files)

- Updated 5 existing public test files with validation cases
- Created 2 new public test files (system_hostname_get, system_status_get)
- Created 1 new integration test (network_dns_get_by_interface)

## Outcome

All handlers now validate path params, query params, and body fields.
0 lint issues, all tests pass.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Feature: API endpoint to list registered NATS workers"
status: backlog
status: done
created: 2026-02-17
updated: 2026-02-17
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Add consistent host targeting to all CLI commands
status: backlog
status: done
created: 2026-02-17
updated: 2026-02-17
---
Expand Down
71 changes: 71 additions & 0 deletions .tasks/done/2026-02-17-migrate-client-job-to-rest-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: Migrate client job commands from direct NATS to REST API
status: done
created: 2026-02-17
updated: 2026-02-17
completed: 2026-02-17
---

## Objective

All `client job` CLI commands currently bypass the REST API and connect
directly to NATS. This is inconsistent with `client system` and
`client network`, which go through the REST API. Everything under
`client` should use the same path: CLI → REST API → NATS.

Current state:

```
client system * → REST API → NATS (correct)
client network * → REST API → NATS (correct)
client job * → NATS directly (inconsistent)
```

Target state:

```
client * → REST API → NATS (all consistent)
```

## Benefits

- One entry point (API URL) — no NATS host/port needed in CLI
- Auth/middleware applies uniformly
- CLI doesn't need NATS credentials
- Can put load balancer/proxy in front of the API

## Commands to Migrate

All REST API endpoints already exist:

| CLI Command | Current Path | REST Endpoint |
|---|---|---|
| `client job add` | Direct NATS | `POST /job` |
| `client job list` | Direct NATS | `GET /job` |
| `client job get` | Direct NATS | `GET /job/{id}` |
| `client job delete` | Direct NATS | `DELETE /job/{id}` |
| `client job status` | Direct NATS | `GET /job/status` |
| `client job run` | Direct NATS | `POST /job` + poll `GET /job/{id}` |
| `client job workers list` | Direct NATS | `GET /job/workers` |

## Tasks

- [x] Rewrite each command to use the REST client (`handler`) instead
of `jobClient`
- [x] Remove NATS setup from `client_job.go` `PersistentPreRun`
- [x] Remove `natsClient`, `jobsKV`, `jobClient` package vars
- [x] Remove NATS-related flags from `client_job.go` (nats-host,
nats-port, kv-bucket, stream-name, etc.)
- [x] Add any missing REST client handler methods if needed
- [x] Update tests
- [x] Verify `client job run` polling works through REST API

## Notes

- The generated REST client (`internal/client/gen/client.gen.go`)
already has methods for all endpoints.
- The `client` parent command already creates the REST client in its
`PersistentPreRun`.
- `client job run` is the most complex — it creates a job then polls
for completion. This maps to `POST /job` followed by polling
`GET /job/{id}`.
103 changes: 99 additions & 4 deletions .tasks/sessions/2026-02-17.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
date: 2026-02-17
---

## Summary
## Summary (Session 1)

Implemented broadcast response collection for `_all` jobs, completing all
gaps identified in the 2026-02-16 task.

## Changes
## Changes (Session 1)

- `internal/job/client/client.go` — Added `publishAndCollect()` method
that waits full timeout and collects all worker responses into a map
Expand All @@ -33,7 +33,7 @@ gaps identified in the 2026-02-16 task.
- Regenerated `internal/api/job/gen/job.gen.go` and
`internal/job/mocks/job_client.gen.go`.

## Decisions
## Decisions (Session 1)

- Used full timeout wait strategy (collect all responses until timeout,
then return what was collected).
Expand All @@ -42,6 +42,101 @@ gaps identified in the 2026-02-16 task.
- Failed worker responses are skipped in `QuerySystemStatusAll` but
still exposed in the REST API `responses` map.

## Summary (Session 2)

Renamed `--host` CLI flag to `--target` and added worker discovery
endpoint (`GET /job/workers`) with CLI command.

## Changes (Session 2)

- `cmd/client.go` — Renamed `--host`/`-H` flag to `--target`/`-T`
- `cmd/client_system_status_get.go` — Updated flag read to `"target"`
- `cmd/client_system_hostname_get.go` — Updated flag read to `"target"`
- `cmd/client_network_dns_get.go` — Updated flag read to `"target"`
- `cmd/client_network_dns_update.go` — Updated flag read to `"target"`
- `cmd/client_network_ping.go` — Updated flag read to `"target"`
- `internal/job/types.go` — Added `WorkerInfo` struct
- `internal/job/client/types.go` — Added `ListWorkers` to interface
- `internal/job/client/query.go` — Implemented `ListWorkers`
- `internal/api/job/gen/api.yaml` — Added `/job/workers` endpoint
- `internal/client/gen/api.yaml` — Added `/job/workers` endpoint
- `internal/api/job/job_workers_get.go` — New handler
- `internal/api/job/job_workers_get_public_test.go` — 3 test cases
- `cmd/client_job_workers.go` — Parent `workers` command
- `cmd/client_job_workers_list.go` — `list` subcommand
- Regenerated all `*.gen.go` files

## Decisions (Session 2)

- `ListWorkers` reuses `QuerySystemHostnameAll` (broadcast
`system.hostname.get`) — no new operation type needed.
- CLI split into parent `workers` + `list` subcommand for extensibility.
- `--host` renamed to `--target`/`-T` per user preference.

## Summary (Session 3)

Migrated all `client job` CLI commands from direct NATS access to the REST API,
making them consistent with `client system` and `client network` commands.

## Changes (Session 3)

- `internal/client/gen/api.yaml` — Added `responses` and `worker_states`
fields to `JobDetailResponse` schema in client spec
- `internal/client/gen/client.gen.go` — Regenerated
- `internal/client/handler.go` — Added `JobHandler` interface (6 methods),
added to `CombinedHandler`
- `internal/client/job_post.go` — New handler implementation
- `internal/client/job_get_by_id.go` — New handler implementation
- `internal/client/job_delete_by_id.go` — New handler implementation
- `internal/client/job_list.go` — New handler implementation
- `internal/client/job_status_get.go` — New handler implementation
- `internal/client/job_workers_get.go` — New handler implementation
- `cmd/client_job.go` — Gutted: removed NATS setup, 30+ flags, viper bindings
- `cmd/client_job_add.go` — Rewritten to use REST API
- `cmd/client_job_get.go` — Rewritten to use REST API
- `cmd/client_job_delete.go` — Rewritten to use REST API
- `cmd/client_job_list.go` — Rewritten to use REST API
- `cmd/client_job_status.go` — Rewritten to use REST API
- `cmd/client_job_run.go` — Rewritten to use REST API
- `cmd/client_job_workers_list.go` — Rewritten to use REST API
- `cmd/ui.go` — Replaced `displayJobDetails` with `displayJobDetailResponse`;
removed `job` import
- `internal/client/client_public_test.go` — Added 6 handler tests
- Updated 5 doc files to remove NATS/KV-store wording
- Created `docs/.../job/workers/workers.md` and `list.md`

## Decisions (Session 3)

- Followed exact `SystemHandler`/`NetworkHandler` pattern for `JobHandler`
- `checkJobComplete()` now takes `jobHandler` parameter instead of package var
- Removed `extractTargetFromSubject()` — replaced with `Hostname` field

## Summary (Session 4)

Completed API input validation hardening for path params, query params,
and missing 400 responses.

## Changes (Session 4)

- `internal/api/job/gen/api.yaml` — Added 400 responses to GET /job,
GET /job/{id}, DELETE /job/{id}
- `internal/api/system/gen/api.yaml` — Added 400 responses to
GET /system/hostname, GET /system/status
- 8 handler files — Added inline struct validation for interfaceName
(alphanum), job ID (uuid), target_hostname (min=1), status (enum)
- Updated 6 existing public test files with validation cases
- Created system_hostname_get_public_test.go,
system_status_get_public_test.go (new)
- Created network_dns_get_by_interface_integration_test.go (new)

## Decisions (Session 4)

- Nil-check on `*string` pointer before validating target_hostname
(not `omitempty` which skips empty strings)
- Renamed inline struct field `Id` to `ID` per revive linter

## Next Steps

- Pick up next task from `.tasks/backlog/`.
- Manual smoke test with running API server + NATS
- Consider adding integration tests for job_get, job_delete, job_list,
job_status, job_workers_get
2 changes: 2 additions & 0 deletions cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ func init() {

clientCmd.PersistentFlags().
StringP("url", "", "http://0.0.0.0:8080", "URL the client will connect to")
clientCmd.PersistentFlags().
StringP("target", "T", "_any", "Target hostname (_any, _all, or specific hostname)")

_ = viper.BindPFlag("api.client.url", clientCmd.PersistentFlags().Lookup("url"))
}
Loading
Loading