Tenancy
Organization, workspace, environment — the hierarchy every other resource hangs off, and the isolation guarantee behind it.
Three levels, and everything else in Veefore is reachable only through them.
textOrganization the tenant boundary
└── Workspace a team or product area
└── Environment development / staging / production
├── Agents
├── Tools and credentials
├── Policies and delegations
└── Actions and audit
#Organization
The tenant boundary. Created in the same transaction as its first owner, so there is no window in which an organization exists with nobody able to administer it.
An organization owns its members, roles, SSO and SCIM configuration, network restrictions, billing, and every resource below it.
The organization is the isolation boundary
Every query in the data layer is scoped to an organization, and the scoping is not something an individual query opts into — the repository layer requires a tenant scope and refuses without one.
Crossing it needs an explicit, named justification in code, which background
jobs use and request handlers do not. A request that somehow reaches a resource
in another organization is refused with TENANT_MISMATCH rather than returning
404, so the attempt is distinguishable in the audit trail from a typo.
Row-level security is available as a second, independent layer —
DATABASE_ENABLE_RLS — so a bug in application-level scoping does not become a
data leak. See Tenant isolation.
#Workspace
A team or a product area. Workspaces divide an organization without dividing its identity: members, roles and SSO are organization-wide, while agents, tools and policies belong to a workspace.
The practical reason to have more than one is scoped access. A role grant can be limited to a workspace, so the payments team's developers operate payments agents and cannot touch the logistics team's.
#Environment
Where the enforcement mode lives. Three types: DEVELOPMENT, STAGING,
PRODUCTION.
bashcurl -X POST https://veefore.example.com/v1/environments \
-H "Authorization: Bearer $VEEFORE_API_KEY" \
-H 'content-type: application/json' \
-H 'Idempotency-Key: create-payments-prod' \
-d '{
"workspace_id": "wsp_01JAV…",
"name": "Payments production",
"type": "PRODUCTION"
}'
An agent belongs to exactly one environment. An identity provisioned in staging cannot act in production — not because a policy forbids it, but because the agent is not there. That is a stronger guarantee than a rule, because it cannot be misconfigured away.
environment.type is a policy field, which is how you write rules that apply to
production specifically:
json{
"kind": "condition",
"id": "c-prod",
"field": "environment.type",
"operator": "eq",
"value": "PRODUCTION"
}
#Scope headers
Session-authenticated requests carry the active scope in headers:
httpGET /v1/agents
Authorization: Bearer aos_sk_…
x-veefore-workspace: wsp_01JAV7YQ4T2K9M3E8H6R5N1XZ0
x-veefore-environment: env_01JAV7YQ4T4ZC1P8W2M6B9K3QD
Omit them and you get the organization-wide view, subject to your permissions. Supply a scope you have no access to and the request is refused.
The console sets these from the scope switcher. The CLI sets them from
veefore.config.json.
#Policy binding scope
A policy is bound at one of five levels:
| Scope | Applies to |
|---|---|
ORGANIZATION |
Everything in the tenant |
WORKSPACE |
Everything in one workspace |
ENVIRONMENT |
Everything in one environment |
AGENT |
One agent |
TOOL |
One tool |
All in-scope policies are evaluated together, and the strictest decision wins. See Policy.
Bind deny-by-default policies narrowly
A policy with "defaultDecision": "BLOCK" bound to an entire environment denies
everything in that environment, because every other policy's ALLOW loses
to it. Strictest wins, regardless of priority.
This is a real and easy mistake to make — a deny-by-default database guardrail
bound to a whole production environment will make every unrelated agent in it
unreachable, and the symptom is "nothing works" with no obviously relevant rule.
Bind deny-by-default policies to the AGENT or TOOL they are meant to
constrain.
#Lifecycle controls
bash# Stop all protected execution in an environment. Immediate.
curl -X POST https://veefore.example.com/v1/environments/env_01ABC/pause \
-H "Authorization: Bearer $VEEFORE_API_KEY" \
-H 'content-type: application/json' \
-d '{ "reason": "Investigating anomalous refund volume" }'
environment:pause is high impact and step-up gated. A paused environment
refuses every protected action with ENVIRONMENT_PAUSED. This is the correct
emergency control — not dropping the mode to OBSERVE, which stops refusing
anything.
#Shape it for how you work
A common arrangement:
textAcme Payments (organization)
├── Payments (workspace)
│ ├── Development SIMULATE
│ ├── Staging ENFORCE
│ └── Production ENFORCE
└── Logistics (workspace)
├── Development SIMULATE
└── Production ENFORCE
Two rules of thumb. One organization per tenant — do not use organizations to separate your own teams, because that fragments members, roles and audit. And a separate environment per deployment tier, so enforcement mode and agent identity are never shared across tiers.