*Social Media Manual — [index](../MANUAL.md)*

# 3. Ownership, sharing and authorization

Three separate ideas, and the tools keep them separate on purpose: **ownership** (who controls
an account), **sharing** (who else can see or draft on it) and **authorization** (who may
publish on it when the owner is not the one calling).

## Ownership

The member who connects an account is its **owner**. Ownership never transfers; disconnecting
and letting someone else connect the same platform account is the only way to change hands.
If a second member tries to connect a platform account that another member already owns, they
get `ACCOUNT_OWNED_ELSEWHERE` and must ask that owner for an invitation.

Only the owner can:

- invite members (`social_invite_member`), list access (`social_list_access`), revoke access
  (`social_revoke_access`);
- disconnect the account (`social_disconnect_account`, which erases the credential and revokes
  every share);
- grant and revoke authorizations (`social_grant_authorization`, `social_revoke_authorization`);
- read the usage ledger for the account (`social_get_usage`).

## Roles

| role | may |
|---|---|
| `owner` | everything, including publishing by their own call |
| `editor` | create, edit and archive drafts, upload media, preview, fill and skip calendar slots, pause/resume/cancel schedules; publish and create schedules **only with an authorization** |
| `viewer` | read: accounts, posts, publications, schedules, calendar, authorizations that apply to them |

`social_list_accounts` reports the caller's `role` on each account. A call that needs a higher
role answers `ACCESS_DENIED`; a member whose access was revoked gets `ACCESS_REVOKED`.

A draft can target several accounts. The caller needs editor or owner on **every** destination
to create or edit it (`POST_NOT_ON_ACCOUNT` names the problem account).

## Sharing by invitation

Sharing is a one-time claim token, never a lookup by name.

1. Owner: `social_invite_member` with `account_id`, `role` (`editor` or `viewer`), optional
   `expires_in_hours` (default 72, at most 336 = 14 days) and a `note` the claimant will see.
   The reply's `claim_token` (prefix `sinv_`) is shown once; only its hash is stored.
2. The owner passes the token to the other person out of band.
3. That person's session: `social_claim_invitation` with the token. The invited role lands on
   the claimant's own member. They now see the account in `social_list_accounts`.

Invited members cannot invite others. `social_list_access` shows the owner, each member with
their `access_id` and role, and open invitations (with a `token_hint`, not the token).
`social_revoke_access` takes either an `access_id` (remove a member) or an `invitation_id`
(cancel an unclaimed invitation).

Error codes in this area: `ROLE_INVALID`, `EXPIRY_INVALID`, `INVITATION_INVALID` (malformed
token), `INVITATION_NOT_FOUND`, `INVITATION_EXPIRED`, `INVITATION_CLAIMED`, `INVITATION_SELF`
(the owner claiming their own token), `ACCESS_NOT_FOUND`.

## Authorization: who may publish

Connecting an account grants **nothing** beyond access to the account. Something is sent to a
platform only on:

- the owner's own `social_publish_now` call (the publication's evidence records the basis
  `owner_instruction` for that account);
- a schedule the owner created (creating it inserts a standing authorization whose evidence is
  the schedule; publications from it record the basis `owner_schedule`);
- an **authorization** the owner granted to someone else.

Editors who call `social_publish_now`, create a schedule, or change a live post without a
matching authorization get `AUTHORIZATION_REQUIRED`. The next action is always the same: ask
the owner to call `social_grant_authorization`.

### Kinds

| kind | allows | required / optional |
|---|---|---|
| `post` | one specific post, on one account | `post_id` required; `member_id` optional (anyone with editor access otherwise) |
| `standing` | any post on one account | `member_id` optional; `valid_until` (ISO-8601) optional; `max_uses` optional |

Both kinds may carry a `note` (why it was granted). Resolution order when an editor publishes:
a `post` authorization for that post matches before a `standing` one. Each publication consumes
one use of the authorization it rested on. Status is `active`, `revoked`, `expired` (past
`valid_until`) or `exhausted` (`max_uses` reached).

### Listing and revoking

`social_list_authorizations` on an account: owners see all of them, other members see the ones
that apply to them (`include_inactive` adds the revoked / expired / exhausted ones).
`social_revoke_authorization` (owner) cancels every schedule and every upcoming occurrence that
relied on it; the reply's `occurrences_cancelled` says how many. Occurrences whose
authorization disappears later are marked `blocked` by the scheduler rather than published.

## The evidence trail

Every publication stores `evidence`: `basis[]` with one entry per destination account
(`owner_instruction`, `owner_schedule` with the authorization id, or `authorization` with the
authorization id and kind), `tool` (the call that triggered it: tool name, principal, request
id), `schedule_id` / `occurrence_id` when a schedule fired it, and later `changes[]`, one entry
per live edit or deletion with who did it and when. `social_get_publication` returns it.
When a user asks "who posted this and on whose say-so", that is where the answer is.
