InfraForge Docs

Infra Vendor Service · v0

Welcome

Select a document from the sidebar to read it.

Vendor & Procurement — Release 0 Canonical API

  • Jira: INFT-71
  • Source of truth: this file. Frozen contract for the Release 0 frontend handoff and the workspace-level RELEASE_0_HANDOFF_SUMMARY.md.
  • Owner: infra-vendor
  • Service version baseline: v1.x (post canonical-IAM cutover, see PRs #15/#16)

Authentication contract

  • Issuer: https://iam.staging.infranotes.io (staging)
  • Audience: infra-vendor
  • JWKS: https://iam.staging.infranotes.io/.well-known/jwks.json
  • Trust profile: B (gateway-forwarded headers in production; direct JWT validation in local dev)
  • Subject: canonical IAM UUID (sub claim). Legacy integer user_id is converted to a deterministic UUID at ingress for backward compatibility — no new code should rely on the integer form.
  • Claims accepted: roles[] (canonical IAM role-template names), scope string (or scopes[] array), tenant_id UUID string. The scope parser tolerates both space- and comma-separated forms.

Canonical IAM role mapping

pkg/auth/jwt.go:mapCoreRoleToVendorRole translates IAM role-template names to vendor-native role constants at ingress so handlers authorise against vendor concepts only. Vendor-native role names pass through unchanged; unknown roles also pass through (downstream RequireRole / HasPermission gates still reject them).

IAM role-template Vendor role Intent
tenant_admin RoleAdmin Full vendor admin, including write/approve on every prefix
admin RoleAdmin Same as above
finance_manager RoleProcurementManager Procurement-style oversight (POs, contracts, payments)
manager RoleVendorManager Vendor lifecycle management
accountant RoleViewer Read-only vendor / reports / analytics
analyst RoleViewer Read-only vendor / reports / analytics
user RoleRequester Submit vendor and PO requests
premium RoleRequester Read-mostly with request capability
readonly RoleViewer Read-only
<vendor-native> passthrough Vendor-native tokens or service-to-service principals keep their declared role

Vendor-native roles declared in pkg/auth/jwt.go:

RoleAdmin               = "admin"
RoleVendorManager       = "vendor_manager"
RoleProcurementManager  = "procurement_manager"
RoleApprover            = "approver"
RoleRequester           = "requester"
RoleViewer              = "viewer"

Canonical IAM scope mapping

pkg/auth/jwt.go:mapCoreScopesToVendorPermissions translates IAM scopes to vendor-native permissions (see the function for the full table). Summary:

IAM scope Grants vendor permissions
api:read vendor/PO/contract/requisition/reports/analytics read
api:write vendor/PO/contract/requisition create/update/delete/approve, PO send, config update
finances:read vendor/PO/contract read + reports read
finances:write vendor/PO/contract update
analytics:read analytics + reports read

Vendor role-to-permission bridge

pkg/auth/jwt.go:vendorRolePermissions resolves vendor-native role names (post-translation via mapCoreRoleToVendorRole) to vendor permissions. This is the Release 1-safe authorisation path the frontend relies on today: the IAM-issued human-user token carries scope="openid profile email" only, with none of vendor's recognised scopes; without role-derived permissions every authenticated browser session would 403 on every vendor read.

convertCoreClaims unions role-derived and scope-derived permissions, so callers carrying either source — or both — get the most permissive total. Role-derived permissions are the default path for canonical-IAM browser sessions; scope-derived permissions remain in place for service-to-service tokens and any future workload flow.

Vendor role Lands canonical IAM Permissions granted
RoleAdmin tenant_admin, admin All vendor / PO / requisition / contract permissions, reports, analytics, config update
RoleProcurementManager finance_manager Vendor read+update; PO/contract/requisition full CRUD + approve; PO send; reports + analytics
RoleVendorManager manager Vendor full CRUD + approve; PO/requisition read/create/update; contract read/update; reports + analytics
RoleApprover (vendor-native only) Read everything; approve PO/vendor/contract/requisition; reports
RoleRequester user, premium Read everything; create vendor/PO/requisition; reports + analytics
RoleViewer accountant, analyst, readonly Read everything; reports + analytics; no writes

A token with no roles and no recognised vendor scopes (only OIDC openid profile email) receives no vendor-privilege permissions and is rejected by every authorisation gate — the safe failure mode.

Canonical route surface (/api/v1/*)

The full backend surface is implemented and stable. Frontend wiring of vendor pages is not in Release 0 scope — this section is the FE-handoff contract for whenever those pages get built under INFT-66 follow-ups.

Vendor lifecycle (/api/v1/vendors)

POST   /vendors
GET    /vendors
GET    /vendors/{id}
PUT    /vendors/{id}
DELETE /vendors/{id}
POST   /vendors/{id}/approve
GET    /vendors/{id}/documents
POST   /vendors/{id}/documents

Procurement (/api/v1/procurement)

Includes purchase orders, requisitions, supplier matching, approvals. Routes are registered under the procurement group in internal/api/routes/routes.go.

Contracts, supplier diversity, ESG

/api/v1/contracts/**
/api/v1/supplier-diversity/**
/api/v1/esg/**

Vendor analytics, reports, benchmarks

/api/v1/analytics/**
/api/v1/reports/**
/api/v1/benchmarks/**

Bill matching, workflows, rules, approvals

/api/v1/matching/**
/api/v1/workflows/**
/api/v1/rules/**
/api/v1/approvals/**

Notifications, vendor portal, payments

/api/v1/notifications/**
/api/v1/portal/**
/api/v1/payments/**

Documents, health

/api/v1/documents/**
GET /health
GET /health/auth
GET /readiness
GET /liveness

Cross-service integration

Financial Intelligence (budget commitments)

  • internal/api/handlers/procurement_handler.goBudgetClientinfranotes-financial-intelligence
  • GetBudgetIDByCode → degrades gracefully; logs WARN, returns Skipped: true, PO continues without budget validation.
  • ValidateBudgetAvailability and ReserveFundshard fail. Correct-by-design: the service must not silently reserve $0 against an unvalidatable budget. If FI is unreachable, the PO approval path returns an error to the caller.

Vendor analytics

  • internal/services/spend_analytics_service.go enrichment (trends, metrics) → degrades gracefully on partial failures and continues. Core spend-data fetch hard-fails on the local DB to surface tenant-scoped errors clearly.

No direct frontend cross-service stitching

The frontend should consume the read-model and analytics endpoints directly rather than fanning out to FI, Core, or core-ledger from the browser.

Out of scope for Release 0

  • Frontend wiring of vendor workspaces. The FE migration is owned by INFT-66; this doc is the handoff artifact.
  • Workload-auth / Profile C signed-header service-to-service hardening (per ADR-0003). Spans IAM + gateway + downstream services; not vendor-only.
  • Vertical-specific procurement workflows or industry packs (per Jira INFT-71 scope: deferred to later releases).
  • Vendor-feature breadth (deeper procurement governance, ESG analytics expansion, supplier diversity reporting). Tracked in later Jira releases.

Acceptance evidence

Required gate checks for the Release 0 readiness slice:

  • canonical IAM role tenant_admin lands on RoleAdmin end-to-end (covered by pkg/auth/jwt_canonical_claims_test.go).
  • every canonical IAM role resolves to a declared vendor role constant — guards against the undefined-role class of bug previously seen on PF (covered by TestMapCoreRoleToVendorRole_CanonicalIAMRoles).
  • one authenticated happy path against a vendor read endpoint via the gateway.
  • one negative path: missing token returns 401.

Evidence is appended to the workspace RELEASE_0_HANDOFF_SUMMARY.md.

Gateway ownership

Vendor route ownership entries should be present in infranotes-gateway/configs/route_ownership.yaml. The gateway-side change is shipped in a separate PR alongside this readiness slice; entries cover /api/v1/vendors, /api/v1/procurement/**, /api/v1/contracts, /api/v1/supplier-diversity, /api/v1/esg, /api/v1/matching, /api/v1/workflows, /api/v1/rules, /api/v1/notifications, /api/v1/portal, /api/v1/reports, /api/v1/benchmarks, /api/v1/approvals, /api/v1/payments, /api/v1/documents with owner infra-vendor, entitlement business_financial, idempotency required on POST/PUT/PATCH/DELETE, tenant-aware.