Skip to content

Organization & Facility Management (OFM)

OFM — Organization & Facility Management

Org spine: invitations, orgs, cost centres, facilities (physical/legal/logical), zones, owners, members, teams, and resolvers.

Surface map (contract-only)

  • Public API (API Gateway): base https://api.g3nretailstack.com/ofm; endpoints under /ofm/*.
  • Direct Lambda (operator-only): invitation ops (invitationCreate, invitationDoom, etc.) are IAM-gated and are not exposed via API Gateway. All other operations live on API Gateway.
  • Operator-only verification/freeze: org lifecycle transitions like unverified → verified or any transition to frozen are IAM-gated (direct Lambda). Until verified, org writes across services return 403 org-write-blocked.
  • Auth: canonical headers x-session-guid (human USM session) or x-api-key (USM service-account). Body session_guid / api_key accepted for compatibility. Org-level/governance mutations require ownership (owner session or API-key role owner); selected facility-scoped mutations can be delegated via explicit logical assignments + facility grants.
  • Optimistic concurrency: for revisioned records, state-changing operations require expected_revision; missing returns 428 expected-revision-required, mismatch returns 409 conflict with the current record snapshot + current revision.
  • Pagination: default limit 8, clamp 1–256; next_token JSON cursors.
  • CLI: cli/bin/g3n.js (g3n ofm ...), --profile required; --base-url override and JSON --next-token supported.
  • MCP: protocol doc at https://mcp.g3nretailstack.com/ofm/PROTOCOL.md (mirrored here at /ofm/PROTOCOL.md).
  • OpenAPI: https://doc.g3nretailstack.com/ofm/openapi.yaml
  • Anti-enumeration: non-associated callers receive 404 not-found on org-scoped reads/lists (see /common/troubleshooting.html).
  • Governance hardening: association required for org-scoped reads/lists; owner-only operations return 403 not-owner when associated but not owner; service-account reads require a view role (ofm_view/pvv/pma/vca/pmc_view/pmc_publish), else 403 forbidden-role.
  • Role matrix: /common/role-matrix.html
  • Headers and identity cheat sheet: /common/headers-identity.html

Responses use { success, data?, error?, build, stats? } with build ID + UTC timestamp in the footer. Stats include call, service, request id, latency, and passthrough context when provided.

Why POST for reads?

OFM uses POST for reads/lists/resolves to keep auth out of URLs, accept structured JSON bodies (org GUIDs, codes, filters), and maintain a uniform surface. Treat read endpoints as POST-only (no GET).

Lifecycles (FSM) and operations

  • Terminal doom: for any record with status/state doomed, OFM rejects further mutations (no transitions out; updates and association changes are blocked).
  • Invitations: pending → accepted/rejected/expired/doomed. Ops: create (Lambda), reject, get, list, doom (Lambda). Use for controlled org creation/onboarding.
  • Organizations: unverified → verified/parked/suspended → frozen → doomed. Ops: create, get, list, update, status set. frozen is operator-only (UTL offboarding) and blocks org reads/writes across services.
  • Cost centres: active ↔ suspended → doomed. Ops: create/get/list/update/status set. cccode is auto-generated and unique; a master cost centre is auto-created on org create.
  • Facilities: physical/legal/logical each active ↔ inactive → doomed. Ops: create/get/list/update/status set. Codes unique per org; logical requires physical+legal (same org) and optional cost_centre_guid.
  • Zones: active ↔ inactive → doomed; depth ≤32; ROOT is seeded on facility/logical/create (and zone/create ensures it exists for legacy logicals). Ops: create/get/list/status set. Codes unique per logical; code ROOT is reserved.
  • Owners: states active/suspended/doomed. Ops: list, primary set, secondary add/remove, state set. Owners outrank members.
  • Members: states active/suspended/doomed. Ops: invite-create, invite-accept, state set, assign-logical, detach-logical, list, assignments.
  • Teams: active/suspended/doomed. Ops: create/get/list/update/status set, member add/remove, members, by-member (org-scoped; self allowed, other users require owner).
  • Resolvers: orgcode→org_guid, invitation code→inv_guid, facility/zone codes scoped to org/logical, cccode→cc_guid.

Governance hardening (auth + anti-enumeration)

  • Non-associated callers receive 404 not-found on org-scoped reads/lists/resolves (anti-enumeration).
  • Suspended members are treated as non-associated (404 not-found).
  • Suspended owners are still associated, but owner-only operations return 403 not-owner.
  • Owner-only endpoints return 403 not-owner for associated non-owners.
  • Service accounts must be org-bound; mismatched org returns 404 not-found. Reads require a view role (ofm_view, pvv, pma, vca, pmc_view, or pmc_publish) or owner; otherwise 403 forbidden-role.
  • Facility-scoped mutations require explicit logical assignments; missing assignment returns 403 forbidden-facility.
  • Org frozen blocks access across OFM with 403 org-access-blocked.
  • Tenant writes require org.status=verified; otherwise 409 org-write-blocked.

Clarifications (B3)

  • Org lifecycle & write gating: org status must be verified for tenant writes; operator-only transitions include unverified → verified and any transition to frozen/doomed.
  • Owner vs primary owner: primary owner is a single designated owner record (operator-only set) used for governance/audit. Owner-management actions (owner/primary/set, owner/secondary/add|remove, owner/state/set) require the active primary owner; other owner-only ops (org/cost-centre/facility mutations) allow any active owner.
  • Master cost centre: org/create auto-creates a master cost centre and returns it in cost_centre; org/get includes cost_centre_guid plus a cost_centre snapshot (cc_guid, cccode).
  • Membership: invite → accept is bound to the invitee; roles can be time-bounded via effective_from / effective_to; suspending a member blocks access without deleting history.
  • Facilities: logical facilities represent operational locations; multiple logical facilities per org are supported; physical/legal facilities are the address/legal registries backing logicals.
  • Zones: hierarchical with reserved ROOT code, depth ≤ 32. Bins are owned by ICS (not OFM).
  • Sales channels: declarations are org-scoped with channel_code, market_code, and locale_codes; channel_guid is the identity. After draft activation, logical_guid/channel_code/market_code are immutable.

Quick start (happy path)

  1. Invitation (Lambda): g3n ofm invitation-create --caption "Q1 invite" --profile g3nretailstackcode.
  2. Org create (API): POST /org/create with { orgcode, invitation_code, user_guid, session_guid }org_guid + owners + auto-created master cost centre.
  3. Facilities: create physical + legal, then logical (optional cost_centre_guid).
  4. Zones: create with parent_zone_guid (ROOT allowed; ROOT is seeded on logical create); manage lifecycle via status (no update endpoint).
  5. People: member/invite/createmember/invite/accept, optional member/assign-logical, team/create then team/member/add; team/by-member is org-scoped.
  6. Resolve: code→GUID via resolve/orgcode|invitation|facility|zone|cost-centre.

Examples (full envelope + CLI)

orgCreate (success)

json
{
  "success": true,
  "data": {
    "org_guid": "org-123",
    "orgcode": "ACMECORP",
    "status": "unverified",
    "owners": { "create_owner_user_guid": "user-1", "primary_owner_user_guid": "user-1" },
    "cost_centre": { "cc_guid": "cc-1", "cccode": "ABCD-EF12-GHI3" }
  },
  "build": { "build_major": "MONDAY", "build_minor": "1770263336", "build_id": "MONDAY-1770263336" },
  "revision": "rev-org-1",
  "stats": { "call": "orgCreate", "service": "ofm", "timestamp_utc": "2026-02-05T00:00:00Z", "request_id": "req-123" }
}

CLI:

g3n ofm org-create --orgcode ACMECORP --invitation-code ABC-DEF-1234 --user-guid user-1 --session-guid $SESSION_GUID --profile g3nretailstack

orgStatusSet (error: missing expected_revision)

json
{
  "success": false,
  "error": {
    "major": { "tag": "expected-revision-required", "message": { "en_US": "expected_revision required" } },
    "details": { "current_revision": "rev-org-1", "current_record": { "org_guid": "org-123", "status": "unverified" } }
  },
  "build": { "build_major": "MONDAY", "build_minor": "1770263336", "build_id": "MONDAY-1770263336" },
  "stats": { "call": "orgStatusSet", "service": "ofm", "timestamp_utc": "2026-02-05T00:00:01Z", "request_id": "req-124" }
}

CLI (with expected_revision):

g3n ofm org-status-set --org-guid org-123 --status verified --expected-revision rev-org-1 --session-guid $SESSION_GUID --profile g3nretailstack

memberAssignLogical (success)

json
{
  "success": true,
  "data": { "org_guid": "org-123", "user_guid": "user-2", "logical_guid": "lq-1", "state": "active" },
  "build": { "build_major": "MONDAY", "build_minor": "1770263336", "build_id": "MONDAY-1770263336" },
  "stats": { "call": "memberAssignLogical", "service": "ofm", "timestamp_utc": "2026-02-05T00:00:02Z", "request_id": "req-125" }
}

CLI:

g3n ofm member-assign-logical --org-guid org-123 --user-guid user-2 --logical-guid lq-1 --role-profile-id inventory_clerk --grants '["facility:zones_write"]' --session-guid $SESSION_GUID --profile g3nretailstack

Error tags (representative)

invalid-code, invalid-fsm-transition, not-owner, not-found, uniqueness-conflict, code-generation-exhausted, duplicate-member, invitation-consumed, invitation-expired, invalid-depth, invalid-session, forbidden-role, forbidden-facility, org-access-blocked, org-write-blocked, throttled, internal-error.

See Surfaces for payloads, examples, and error conditions per endpoint. Protocol details live at https://mcp.g3nretailstack.com/ofm/PROTOCOL.md (mirrored to /ofm/PROTOCOL.md here).