---
title: "Collaboration policy"
---

# Collaboration policy

Set what an organisation, department, team or project allows to be shared, with tighten-only inheritance, immutable versions, an as-of lookup and administrator oversight of every share.

The collaboration policy is how a regulated organisation controls what its people share through the Context Platform: lenses, handoffs, pair session invitations, team memory, presence, the work registry and agent instruction files. It does not grant access to content; it only limits sharing.

## The policy document

One JSON document per node (organisation, department, team or project), every key optional. An absent key inherits.

| Key | Values | Platform default | A lower level may |
|---|---|---|---|
| `lens_sharing`, `handoffs`, `pair_sessions`, `team_memory`, `presence`, `work_registry`, `agent_files` | bool | on | switch off |
| `agent_file_personal_sharing` | bool | off | switch off (only the organisation switches it on) |
| `work_registry_visibility` | `own_team`, `department`, `organization` | `organization` | narrow |
| `work_registry_sensitive_paths` | `exclude`, `hash_only` | `exclude` | go back to `exclude` (only the organisation may choose `hash_only`) |
| `share_scope` | `self`, `team`, `department`, `org` | `org` | narrow |
| `max_share_targets` | 0 to 100 | 100 | lower |
| `classification_ceiling` | `public`, `internal`, `confidential`, `restricted` | `restricted` | lower |
| `approval_above` | a classification | none | lower |
| `share_approvers` | `team_lead`, `department_admin`, `org_admin` | all three | narrow (an empty result falls back to `org_admin`) |
| `default_share_expiry_seconds` | 0 to 10 years (0: none) | none | lower |
| `max_share_expiry_seconds` | 0 to 10 years (0: unbounded) | unbounded | lower |
| `allowed_access_levels` | subset of `view`, `use`, `edit` | all three | narrow (at least one) |
| `agent_file_policy` | object, see [Agent instruction files](https://neuralinverse.com/docs/context/agent-files/) | | tighten |

Unknown keys are refused. An organisation that sets nothing sees no change from before the policy existed; the opt-in keys (personal file sharing, custom file shares, `hash_only`, the custom file name allow-list) start in their most restrictive state.

## Inheritance: lower levels only tighten

The policy that applies to a person is folded from, in order: the organisation; the departments of the person's teams and of the content's chain; the teams of the person's groups and of the content's chain; the projects of the content's chain. Switches combine with AND, limits take the minimum, allow-lists intersect, deny-lists unite.

- A person in several teams gets the tightest of them. This is deliberate and fails closed, and it can surprise.
- Tighten-only is checked twice. On write, a document that would loosen what the level above allows is refused with `422 context.collab_policy.loosens_parent`, naming the fields. At read time the merge can only tighten, so a document that became looser than a parent tightened later is simply overridden.
- The built-in deny list of files (`.env`, keys, credentials) cannot be changed by any policy: `422 context.collab_policy.builtin_deny_immutable`.
- A policy that cannot be resolved refuses the operation: `503 context.collab_policy.unavailable`. Nothing reads an error as "no policy".

## Manage the policy

Administration routes need `context:space:admin` on the node's space (the organisation's root space for the organisation), always enforced.

| Method | Path | What |
|---|---|---|
| `GET` | `/api/v1/context/collaboration/policy` | A node's document (`?nodeKind=org\|department\|team\|project&nodeId=`; default the organisation), the bounds the level above leaves it, and what applies there |
| `PUT` | `/api/v1/context/collaboration/policy` | A new version of the whole document: `{"nodeKind"?, "nodeId"?, "policy", "reason", "expectedVersion"?}`; the reason is required |
| `DELETE` | `/api/v1/context/collaboration/policy` | A new, empty version (the node inherits everything); reason required |
| `GET` | `/api/v1/context/collaboration/policy/versions` | The node's versions, newest first |
| `GET` | `/api/v1/context/collaboration/policy/at?time=<RFC 3339>` | The version in force at a time and the effective policy of its chain then |
| `GET` | `/api/v1/context/collaboration/policy/effective` | What applies to the caller (any member): `?spaceId=` or `?nodeId=`, or neither for their own position |

Example: limit the payments team to sharing within the team, at most `confidential`, with approval for anything above `internal`, and shares lasting at most 30 days:

```http
PUT /api/v1/context/collaboration/policy
Content-Type: application/json

{
  "nodeKind": "team",
  "nodeId": "<payments team id>",
  "reason": "CHG-2041: payments data handling",
  "policy": {
    "share_scope": "team",
    "classification_ceiling": "confidential",
    "approval_above": "internal",
    "max_share_expiry_seconds": 2592000,
    "default_share_expiry_seconds": 604800
  }
}
```

The answer includes `violatingShares`, `violatingHandoffRecipients` and `violatingSessionInvitations`: the numbers of existing grants that now break the policy. They are ineffective at once, listed, and never deleted.

### Versions and audit

Every change is an immutable version with its author, time and reason. The versions table refuses update, delete and truncate for every role. Clearing a policy is a new, empty version, so the history stays whole. Every change is audited with its input (`context.collab_policy.put`, `.clear`).

The as-of lookup uses the current hierarchy: renaming or moving a team after the fact is not replayed.

## Where it is enforced

| Operation | Check |
|---|---|
| Lens share | `lens_sharing`; every space of the lens at or below the ceiling (above `approval_above`: held for approval); audience within `share_scope`; number of targets; level; expiry |
| Lens read paths (get, list, apply, fork, retrieval, snapshots, the feed) | The share must still comply with its sharer's policy, judged at read time |
| Lens source change | Refused when it lifts a shared lens above the ceiling |
| Handoff create | `handoffs`; snapshot spaces at or below the ceiling; recipients within scope and number |
| Handoff read | `handoffs` on for the reader |
| Pair session create, join, set lens | `pair_sessions` at the session space's node |
| Pair session invite | The same, plus ceiling, scope and number for the invited |
| Presence heartbeat | `presence` at the space's node |
| Team memory create, edit, accept | `team_memory` at the entry's space node (reading, superseding and archiving stay possible) |
| Work registry and agent files | Their own typed decisions; see [Work registry](https://neuralinverse.com/docs/context/work-registry/) and [Agent instruction files](https://neuralinverse.com/docs/context/agent-files/) |

Refusals carry stable codes: `context.collab_policy.` followed by `capability_disabled`, `classification_ceiling`, `approval_required`, `scope_exceeded`, `too_many_targets`, `level_not_allowed`, `expiry_exceeds_max` (all `403`), or `expiry_in_past`, `invalid_policy`, `loosens_parent`, `builtin_deny_immutable`, `reason_required` (`422`), `version_conflict` (`409`), `not_found` (`404`), `unavailable` (`503`).

Ending and leaving a pair session stay possible when pair sessions are switched off, so nobody is stuck in one. Presence entries already written expire within their 30-second lifetime.

## Share oversight

Administrators see and revoke every grant of the organisation, or of one node:

| Method | Path | What |
|---|---|---|
| `GET` | `/api/v1/context/collaboration/shares` | Lens shares, handoff recipients and session invitations (`type=lens_share,handoff_recipient,session_invitation`). Filters: `sharedBy`, `targetKind`, `targetId`, `ownerId`, `lensId`, `classification`, `expiresBefore`, `expiresAfter`, `noExpiry`, `expired`, `violating`; `limit` 1 to 500 |
| `DELETE` | `/api/v1/context/collaboration/shares/{lensId}/{kind}/{targetId}` | Revoke one lens share at once, frozen lenses included; reason required |
| `DELETE` | `/api/v1/context/collaboration/handoff-recipients/{handoffId}/{kind}/{targetId}` | Revoke a handoff recipient, or the whole audience of a handoff without recipients (`kind` `everyone`, `targetId` `all`); reason required |
| `DELETE` | `/api/v1/context/collaboration/session-invitations/{sessionId}/{invitationId}` | Revoke an invitation or join link; reason required |

Administrators never see content they cannot read: a lens's name and spaces appear only when the administrator may read every space of the lens; otherwise the row is `restricted: true` and keeps who, with whom, level, expiry and classification. A revocation needs `context:space:admin` on every space the content covers and is audited with its reason. A revoked handoff recipient is an append-only record; the recipient row stays.

The policy and share oversight have no console page yet; use the API.
