Company integration scope policies

Availability: 0.12.0 contract. Versions through 0.11.0 do not accept scope_policy or the scope-authority endpoint. Check /health and the installation's published OpenAPI before using this contract.

A company integration can use externally managed project, agent, and mission IDs without issuing a credential or adding an exact grant for every new ID. A company administrator explicitly selects a versioned scope policy for that integration. The application owns ID generation, membership, and resource lifecycle. IDs are opaque, case-sensitive UTF-8 strings; QilbeeDB does not normalize them or infer identity from display names.

This feature authorizes dynamic scopes. The 0.12.0 automatic agent registration contract records an external agent after its first successful authorized resource request. Delegated child-key issuance and company memory inventory have separate authority contracts.

Choose an access model#

Credential configuration Effective resource authority
grants with no policy, or scope_policy: null Exact project, agent, mission, and visibility tuples
grants: [] with no policy No data scopes, including on existing credentials
grants: [] with company_scopes_v1 Scopes satisfying every selector and visibility rule
Nonempty grants together with a policy Rejected as ambiguous configuration

Capabilities remain independent. A matching policy with memory_read does not permit a memory write, human review, procedure evaluation, or credential issuance. Company integrations cannot contain credential_admin, because that capability could issue a less restricted key. Use a separate company administrator for access administration. Tenant-level capabilities such as policy_admin and tool_admin retain their documented company-level behavior; project selectors do not turn those operations into project-specific administrative roles.

The tenant comes from the authenticating credential. A policy cannot name another company. Private memory continues to include the credential's subject in its namespace; covering all agents does not change private ownership. Preserve the established subject when migrating an integration so existing private memory remains addressable. Company administrators can explicitly delegate access to company subjects; ordinary integrations cannot supply an arbitrary owner in a memory request.

Issue an integration key#

Send POST /api/v1/credentials with a company administrator bearer token:

{
  "contract_version": 1,
  "spec": {
    "subject_id": "company-agent-system",
    "capabilities": ["memory_read", "memory_write"],
    "grants": [],
    "scope_policy": {
      "version": "company_scopes_v1",
      "projects": {"mode": "all"},
      "agents": {"mode": "all"},
      "missions": {"mode": "all"},
      "allow_unassigned_mission": true,
      "visibilities": ["shared", "private"]
    },
    "expires_at_millis": null
  }
}

HTTP 201 returns sanitized credential metadata and a one-time secret. Configure expiry according to company policy; null means no configured expiry. Store the secret in the consuming system's secret store. Issuance retains its existing one-time delivery semantics: if the response is lost, inspect and rotate through a surviving administrator instead of repeatedly issuing keys without recovery.

Use the integration key on POST /api/v1/memory/commands with a new external ID:

{
  "contract_version": 1,
  "idempotency_key": "observation-0001",
  "scope": {
    "project_id": "consumer-project",
    "agent_id": "consumer-agent-928",
    "mission_id": null,
    "visibility": "private"
  },
  "operation": {
    "type": "create",
    "record": {
      "episode_type": "Observation",
      "event_time_millis": 1700000000000,
      "content": {"primary": "The agent observed a recoverable tool failure."},
      "tags": ["tool-failure"],
      "metadata": {"source": "consumer-run-0001"}
    }
  }
}

The receipt identifies the durable memory revision. An embedding is not required for this operation. Existing memory idempotency and revision rules apply. The same key can handle another external agent ID if its selectors permit it.

Restrict the integration#

Every policy field is required. There are no implicit wildcard defaults.

Field Contract
version Exactly company_scopes_v1; unknown versions are rejected
projects {"mode":"all"} or {"mode":"only","ids":[...]}
agents The same explicit selector form for externally supplied agent IDs
missions Selects assigned mission IDs only
allow_unassigned_mission Independently permits or denies a missing/null mission
visibilities One or two distinct values from shared and private

An empty only list permits no IDs; it never means all. Each list accepts at most 256 distinct IDs, each with 1–256 UTF-8 bytes, no control characters, and at least one non-whitespace character. These are representation/work limits, not a quota on agents covered by all. The complete body remains bounded by 65,536 bytes. Duplicate values, extra selector fields, and missing policy fields fail validation.

For example, one project, any agent in it, and no assigned missions:

{
  "version": "company_scopes_v1",
  "projects": {"mode": "only", "ids": ["support"]},
  "agents": {"mode": "all"},
  "missions": {"mode": "only", "ids": []},
  "allow_unassigned_mission": true,
  "visibilities": ["shared"]
}

Change access without replacing the secret#

Use POST /api/v1/credentials/{id}/scope-authority with a company administrator token. First inspect the target credential to obtain its current revision.

{
  "contract_version": 1,
  "expected_revision": 1,
  "authority": {
    "grants": [],
    "scope_policy": {
      "version": "company_scopes_v1",
      "projects": {"mode": "only", "ids": ["support"]},
      "agents": {"mode": "all"},
      "missions": {"mode": "only", "ids": []},
      "allow_unassigned_mission": true,
      "visibilities": ["shared"]
    }
  }
}

HTTP 200 returns contract_version and credential, without a secret. This replaces the complete scope authority; it does not merge policies. Tenant, subject, secret, capabilities, and expiry remain unchanged. To restore exact grants, provide them and omit the policy. To suspend resource access while retaining authentication, use {"grants":[]}. Tenant-level capabilities remain active; revoke the credential to disable all authentication.

Every success increments the revision and appends a scope_authority_changed event with actor, time, and full previous and current scope authorities. The target and administrator revision guards, new authority, and audit event commit in one synchronous WAL-backed metadata batch. Eight competing updates at one expected revision produce one success and seven conflicts.

Status Meaning and handling
200 Scope authority committed; retain the returned revision
400 Invalid authority/version/ID, expired target, or invalid revision representation
401 Calling credential/session is absent, expired, revoked, rotated, or otherwise invalid
403 Caller lacks company administration, target belongs to another company, or target is revoked
409 Target or authorizing authority changed; inspect before deciding a new update
413 Request exceeds the transport body bound
500 Storage or integrity failure; no success is implied

If the response is lost, inspect the credential and compare its event and revision. Replaying a stale revision returns 409 and cannot overwrite a later change. This is revision-based reconciliation, not an idempotent secret-delivery protocol.

The API key remains usable under its new limits. Login sessions bound to the changed credential become invalid because they pin its old revision; sign in again. New authorization checks read durable state. Requests already authorized may complete; this contract does not cancel in-flight work or provide a cross-database transaction with memory writes.

Upgrade and qualification#

Existing credential records omit the optional policy and audit fields and retain their previous wire representation. Exact-grant namespaces are identical to policy-authorized namespaces for the same company, scope, and private subject. Changing access does not move or delete memories.

An older binary rejects credentials containing the new fields. After enabling a policy or recording a scope-authority change, do not roll back only the binary. Use a compatible binary or an explicitly reconciled pre-upgrade backup. Restoring a backup requires reconciling newer memories, credentials, and revocations.

Qualification covers real HTTP responses against the served OpenAPI, new external IDs, same-ID company separation, shared/private subject boundaries, legacy empty grants, negative selectors, stale writes, concurrent updates, login-session invalidation, rotation/revocation, expiry, reopen, and inconsistent policy history. A subprocess test kills the server after acknowledged policy and memory writes, then verifies the policy, receipt replay, scope denial, and a later revocation after restart. This is process-interruption evidence, not hardware power-loss testing. Authorization design follows OWASP request-level checks and object-level authorization guidance. These references guide implementation; they are not a security certification or evidence of improved agent reasoning.

Company-wide inspection#

Company administrators can use the 0.12.0 memory inventory to discover canonical retained workspaces and inspect each private subject. This route uses credential_admin; it does not synthesize a company integration key or treat an empty grant list as implicit agent access.