Skip to content

feat(server): agent API surface with API key auth (COD-379) - #6

Merged
shivros merged 1 commit into
mainfrom
runner/cod-379-agent-api
Jul 25, 2026
Merged

feat(server): agent API surface with API key auth (COD-379)#6
shivros merged 1 commit into
mainfrom
runner/cod-379-agent-api

Conversation

@shivros

@shivros shivros commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements COD-379: agent API surface with API key auth for PostGhost.

Adds LLM-first /agent/* endpoints protected by Bearer token middleware, plus a key management API for local operators.

Changes

Auth middleware (auth.rs, new)

  • SHA-256 hashed API keys stored in agent_keys table
  • Axum middleware protects all /agent/* routes
  • Bearer scheme matched case-insensitively (RFC 7235)
  • Raw keys never persisted — only returned once at creation

Agent endpoints (agent_api.rs, new)

  • POST /agent/draft — idempotent (same title+body within 60s returns existing, atomic lookup+insert under single lock)
  • GET /agent/posts — filterable by state, platform, limit (SQL-level filtering via EXISTS subquery)
  • GET/PUT/DELETE /agent/posts/{id} — 404 on missing IDs
  • POST /agent/posts/{id}/schedule — creates pending schedule
  • GET /agent/calendar — upcoming scheduled content

Key management (/api/v1/keys)

  • POST generates pgk_<uuid> key, returns raw key exactly once
  • GET lists keys without exposing hashes
  • DELETE revokes a key
  • Unauthenticated (local operator bootstrap via CLI)

CLI

  • postghost key create --name <n> — mints a key, prints raw value
  • postghost key list — lists keys with last_used timestamps
  • postghost key revoke --id <id> — revokes

Storage

  • agent_keys table (id, key_hash, name, created_at, last_used_at)
  • save_content_if_not_recent_duplicate() — atomic idempotent insert
  • list_content_filtered() — filtered query with state + platform filters
  • delete_content() now returns Result<bool> (was Result<()>)
  • update_content_fields() — partial update of title/body/tags

Review panel findings (both fixed before PR)

Both GPT-5.5 and Gemini 3 Flash flagged 4 issues, all addressed:

  1. DELETE returned 204 for nonexistent IDs → now returns 404
  2. platform filter silently ignored → implemented SQL-level EXISTS filtering
  3. Idempotency TOCTOU race → atomic lookup+insert under single mutex lock
  4. Case-sensitive Bearer scheme → case-insensitive matching per RFC 7235

Test plan

  • cargo build --all-targets — clean
  • cargo test --all-targets — 56 tests pass (32 lib + 21 integration + 3 cli)
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo fmt --all -- --check — clean

Integration tests cover: auth (401 on missing/invalid/malformed keys, case-insensitive scheme), key lifecycle (create/list/revoke, hash not exposed), draft idempotency (within window + different body), all 6 endpoints, delete-404, platform filter.

Closes COD-379.

🤖 Generated with Archon. Co-authored-by: Archon archon@purelymail.com

Add LLM-first /agent/* endpoints protected by Bearer token middleware:
- POST /agent/draft (idempotent: same title+body within 60s returns existing)
- GET /agent/posts (filterable by state, platform, limit)
- GET/PUT/DELETE /agent/posts/{id}
- POST /agent/posts/{id}/schedule
- GET /agent/calendar (upcoming scheduled content)

Add key management API (/api/v1/keys) for local operator bootstrap:
- POST creates a pgk_-prefixed key, returns raw key exactly once
- GET lists keys without exposing hashes
- DELETE revokes a key

Add postghost key create/list/revoke CLI commands.

Auth uses SHA-256 hashed keys stored in agent_keys table. Raw keys are
never persisted. 53 tests pass (28 existing + 25 new).

Co-authored-by: Archon <archon@purelymail.com>
@shivros

shivros commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Automated Review Panel

GPT-5.5 (openai/gpt-5.5)

Verdict: Functionally achieves the stated goal. All 6 agent endpoints work, auth middleware protects /agent/*, keys hashed at rest, CLI works, all CI gates pass (50 tests). Several correctness defects and convention violations identified — all addressed in this PR.

🔴 Correctness bugs (FIXED before PR):

  1. DELETE /agent/posts/:id returned 204 for nonexistent IDs — delete_content ignored rows_affected. Fixed: now returns Result<bool>, handler maps 0→404.
  2. platform filter in GET /agent/posts was silently ignored — advertised but did nothing. Fixed: implemented SQL-level EXISTS subquery filtering against content_variants.

🟡 Convention violations (FIXED):
3. Idempotency check was non-atomic read-then-write (TOCTOU). Fixed: save_content_if_not_recent_duplicate() holds the mutex for the entire lookup+insert.
4. delete_content silently returned Ok(()) on 0 rows. Fixed: returns Result<bool>.

🟢 Done correctly: Platform parsing uses from_db_key(), bare-string SQL matching, SHA-256 key hashing, {id} route syntax, list_keys omits hashes, thorough integration tests.


Gemini 3 Flash (google/gemini-3-flash-preview)

Verdict: Functionally complete and CI-green. Several real correctness gaps addressed before merge.

Goal alignment: ✅ All 6 endpoints + auth + key table + CLI present. ⚠️ platform filter was silently ignored (FIXED).

Correctness bugs (FIXED):

  1. create_draft idempotency was racy (TOCTOU) — fixed with atomic save_content_if_not_recent_duplicate().
  2. delete_post didn't distinguish 404 from 204 — fixed.
  3. extract_bearer was case-sensitive on scheme — fixed: case-insensitive per RFC 7235.
  4. list_content_filtered now implements the platform filter at SQL level via EXISTS subquery.

Minor (deferred): update_post/schedule_post existence checks are TOCTOU against concurrent deletes (low practical risk). schedule_post doesn't validate workflow state — by design for LLM simplicity. Key management routes are unauthenticated (local operator bootstrap; documented).

No regressions: Router merge is additive, existing routes untouched, agent_keys table uses IF NOT EXISTS, 32 pre-existing tests still pass.

@shivros
shivros marked this pull request as ready for review July 25, 2026 06:10
@shivros
shivros merged commit ae6234a into main Jul 25, 2026
5 checks passed
@shivros
shivros deleted the runner/cod-379-agent-api branch July 25, 2026 06:10
@shivros

shivros commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Auto-Merge Gate — Approval

Confidence: 0.82 (threshold: 0.80)

Goal alignment

PR implements COD-379 — agent API surface with API key auth for PostGhost. All 6 /agent/* endpoints, Bearer middleware (SHA-256 hashed keys), key management API, and postghost key create/list/revoke CLI are present. Linked Linear ticket acceptance criteria all satisfied:

  • ✅ API key auth middleware protects /agent/* routes
  • ✅ All 6 agent endpoints functional (draft idempotency, list with filters, get/update/delete, schedule, calendar)
  • ✅ Unauthenticated requests get 401
  • ✅ CLI postghost key create generates and stores a key
  • ✅ All CI gates green

Checks observed (all SUCCESS)

  • Formatting, Clippy, Tests (56 pass), Build, Secret Scanning

Review panel outcome

Dual-model review (GPT-5.5 + Gemini 3 Flash) run before PR open. 4 issues flagged and fixed before this PR was created:

  1. DELETE returned 204 for nonexistent IDs → now 404
  2. platform filter silently ignored → SQL-level EXISTS filtering
  3. Idempotency TOCTOU race → atomic lookup+insert under mutex
  4. Case-sensitive Bearer scheme → case-insensitive per RFC 7235

No unresolved blocking language in any review comment.

Scope limits / risk notes

  • Feature branch only; no production deployment/cutover touched
  • Raw keys never persisted (SHA-256 hashed at rest, returned exactly once at creation) — safe credential handling
  • Deferred (low-risk, documented): update_post/schedule_post existence checks have TOCTOU against concurrent deletes; key management routes unauthenticated (local operator bootstrap by design)

Rationale

PR clearly fulfills the stated ticket goal. CI green, mergeable, dual-reviewed with all findings resolved, well-tested (56 tests). Confidence 0.82 reflects medium-large scope (10 files, +1641/-3) and the auth-adjacent nature of the change, balanced against complete goal alignment and fully-resolved review findings.

🤖 Merged by CodeFold Auto-Merge Gate (cron ceb0befd1f30).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant