Skip to content

Admission client and platform endpoint were built to different wire contracts — align and document the canonical shape #359

Description

@initializ-mk

Context

The #201 platform admission client (forge-cli/runtime/admission_engine.go) and security-next's admission endpoint were implemented to different specs and have never successfully interoperated:

forge client security-next endpoint (pre-fix)
Method GET POST only
Payload ?agent_id=<id> query JSON body {agent_id, workspace_id, org_id}
Org header Org-Id (hyphen) Org_id (underscore) read in-handler
Response flat {decision, reason, scope, window, reset_at} wrapped in ApplicationResponse{Status, Message, Data}

Field symptom (every deployment, all forge versions): the GET fell through to security-next's GET /audit-events/:eventId route (eventId="admission") → HTTP 400 → forge fail-open:

{"error":"platform returned HTTP 400","level":"warn","msg":"admission: call failed, admitting"}

Token-quota admission has therefore been silently disabled on every deployed agent since both sides shipped. The only signal was the warn line — fail-open (#201's design) meant nothing visibly broke.

Immediate fix (platform side)

initializ/security-next#17 makes the endpoint serve forge's actual shape (GET + query + verified-token tenancy, flat response). That restores enforcement for all deployed agents without image rebuilds. No forge change is required for correctness.

What this issue asks of forge

  1. Document the admission wire contract as canonical in-repo — method, query params, headers (Authorization: Bearer <FORGE_PLATFORM_TOKEN>, Org-Id, Workspace-Id, omitted-when-empty semantics), and the exact admissionResponse JSON — so the platform side has one source of truth to implement against. Today the shape only exists implicitly in admission_engine.go.
  2. Consider a contract test (golden request/response fixtures) that the platform repo can mirror, so the two implementations can't drift apart silently again. The failure mode here — both sides fail open, nobody notices — is exactly the kind that only a pinned contract catches.
  3. Optional: on fallback, log the response body snippet alongside the status (the bare "HTTP 400" hid the fact that the 400 came from an event-lookup handler, which would have made this a five-minute diagnosis).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions