pi-mono/apps/p2/plugins/sharing/contract.ts
Mario Zechner 20d86c3657 feat(p2): Chord v2 cleanup pass
p2 rewritten by the Chord v2 authoring rules (decision "App authoring rules confirmed
by the p2 audit"), in four parts merged together:
- core: session lifecycle (relocate, clone, fork, archive) and search rebuild are
  persisted jobs on a new server store; Sessions v6 with client-chosen ids and visible
  operations; Retry answers with the worker's real outcome; workers push branch,
  subagent and status state to the server; roots take the method's ctx.
- services: models, envs, ports, login, mcp, logs, sharing and voice follow external
  state (self.follow, followNative) instead of copy loops; methods answer after their
  state commits; presence from host.references.
- chat: chat mode, artifacts and prompt resources follow Durable and native state;
  shell commands are Chord jobs (shellId is the requestId); status pushed before
  send and stop answer; race-free draft claim.
- ui: optimistic updates on web (useOptimistic) and terminal (TuiScope.optimistic,
  extended in Chord with a reducer and a pending flag), pending states for long work,
  visible rollback, load waterfalls removed; merged with the one composer and
  immediate first send (ccff9876e) and the folder + button (5a31a6d5f).
docs/optimistic-ui.md describes the pattern.
2026-11-05 18:22:13 +00:00

137 lines
5.4 KiB
TypeScript

import { arr, bool, nul, num, obj, opt, type Static, str, union } from "@earendil-works/pi-schema";
import { literal, local, method, param, record, remote, serverHost } from "@p2/app/v1";
export const ModeSchema = union([literal("view"), literal("full")]);
export type ShareMode = Static<typeof ModeSchema>;
export const LinkSchema = obj({
id: str(),
session: str(),
mode: ModeSchema,
token: str(),
});
export type ShareLink = Static<typeof LinkSchema>;
/** A guest's current principal: the sessions shared with it now (see `GuestPrincipal`). */
export const GuestGrantSchema = obj({
kind: literal("guest"),
id: str(),
name: str(),
email: opt(str()),
home: str(),
sessions: record(ModeSchema),
revision: num(),
});
export const PersonSchema = obj({ id: str(), name: str(), session: str(), mode: ModeSchema });
export const ConnectedSchema = obj({
id: str(),
name: str(),
sessions: record(ModeSchema),
});
/**
* Share links, guests and their grants (server, SQLite `shares.sqlite`). Host-internal. A guest holds one grant per
* session shared with it; every change of its grants (share, join, removal, link reset, archive, restore) bumps its
* revision and invalidates the objects of the older principal.
*
* Sessions are shared two ways, into the same grants:
*
* - Links, for browsers on loopback or a tunnel host. A session has at most one link per mode. Joining mints a cookie
* (the name is self-chosen); the same browser joining more links gains their sessions.
* - By email, for people who reach the brain through Radius (only offered while it is connected to Radius). Radius
* verifies the email of whoever opens the brain, and admits only emails the brain reported (`emails`).
*/
export const Shares = local("p2.shares", {
version: 1,
params: {},
members: {
/** Every link (small: at most two per session). */
links: arr(LinkSchema),
/** Normalized emails sessions that are not archived are shared with: whom Radius may admit. */
emails: arr(str()),
/** Every grant to a person by email that is not removed (archived sessions included). */
people: arr(PersonSchema),
/** The principal of a guest cookie value (`<id>.<secret>`), or null. */
authenticate: method(obj({ cookie: str() }), union([GuestGrantSchema, nul()])),
/** The principal of a person Radius authenticated by their normalized email, or null. */
authenticateEmail: method(obj({ email: str() }), union([GuestGrantSchema, nul()])),
/** A guest's current principal, or null (nothing shared with it now). */
grant: method(obj({ id: str() }), union([GuestGrantSchema, nul()])),
/** The live link of a token, or null (reset, archived). */
active: method(obj({ token: str() }), union([LinkSchema, nul()])),
link: method(
obj({
session: str(),
mode: ModeSchema,
reset: bool(),
remoteConfirmed: opt(bool()),
}),
),
/** Shares `session` with the person of `email` (as the owner wrote it), or changes their mode. */
share: method(obj({ session: str(), email: str(), mode: ModeSchema, remoteConfirmed: opt(bool()) })),
remove: method(obj({ session: str(), guestId: str() })),
/**
* Joins through a link: the browser's guest (its cookie) gains the link's session; otherwise a new guest is
* created and `cookie` is its value. Throws when the link ended or the guest was removed from that session.
*/
join: method(obj({ token: str(), name: str(), cookie: opt(str()) }), obj({ session: str(), cookie: opt(str()) })),
},
});
/** Guests whose browsers are connected now (server, in memory). */
export const Presence = local("p2.share-presence", {
/** 2: derived from the references to `GuestPresence` (no enter and leave). */
version: 2,
params: {},
members: { guests: record(ConnectedSchema) },
});
/** Held by a guest's browser for its page's lifetime: marks the guest connected. Nothing for the owner. */
export const GuestPresence = remote("p2.guest-presence", {
version: 1,
params: {},
hostAddress: serverHost,
variants: { owner: {}, guest: {} },
});
/**
* Sharing of one session (owner only): its links, the people it is shared with by email, and connected guests.
* `peopleUrl` is where those people open the session (the brain's page in Radius), or null while sharing with people
* is unavailable: the brain is not connected to Radius, or does not know its address there yet.
*/
export const Sharing = remote("p2.sharing", {
version: 1,
params: { session: param.str() },
hostAddress: serverHost,
variants: {
owner: {
state: {
/** The remote environments (comma-separated) the session's conversations work on, or null: this host. */
env: union([str(), nul()]),
links: arr(obj({ mode: ModeSchema, token: str(), url: str() })),
guests: arr(obj({ id: str(), name: str(), mode: ModeSchema })),
people: arr(obj({ id: str(), name: str(), mode: ModeSchema })),
peopleUrl: union([str(), nul()]),
},
methods: {
/** Creates the link of `mode`, or with `reset` replaces it (new token) and ends its guests' access. */
link: method(
obj({
mode: ModeSchema,
reset: opt(bool()),
remoteConfirmed: opt(bool()),
}),
),
/**
* Shares the session with the person of `email`, or changes their mode. They open it through Radius, signed in
* with that email.
*/
share: method(obj({ email: str(), mode: ModeSchema, remoteConfirmed: opt(bool()) })),
/** Ends one guest's or person's access to the session; a link keeps working for others. */
remove: method(obj({ guestId: str() })),
},
},
},
});