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 (
subclaim). Legacy integeruser_idis 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),scopestring (orscopes[]array),tenant_idUUID string. Thescopeparser 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.go→BudgetClient→infranotes-financial-intelligenceGetBudgetIDByCode→ degrades gracefully; logs WARN, returnsSkipped: true, PO continues without budget validation.ValidateBudgetAvailabilityandReserveFunds→ hard fail. Correct-by-design: the service must not silently reserve$0against an unvalidatable budget. If FI is unreachable, the PO approval path returns an error to the caller.
Vendor analytics
internal/services/spend_analytics_service.goenrichment (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_adminlands onRoleAdminend-to-end (covered bypkg/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.