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 | 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:
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 and Agent instruction 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.