@cueloop/schema 0.1.0-alpha.75 → 0.1.0-alpha.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueloop/schema",
3
- "version": "0.1.0-alpha.75",
3
+ "version": "0.1.0-alpha.77",
4
4
  "description": "The Thread primitive: types, markdown block model, quote-primary anchors, feedback serialization",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./types";
2
2
  export * from "./participants";
3
+ export * from "./share-access";
3
4
  export * from "./markdown";
4
5
  export * from "./inline";
5
6
  export * from "./working-copy";
@@ -55,4 +55,33 @@ describe("registerParticipant", () => {
55
55
  // Assert
56
56
  expect(next.participants).toEqual([{ id: "SHA256:ana", provider: "ssh", name: "Ana" }]);
57
57
  });
58
+
59
+ test("records a verified github source with its login handle", () => {
60
+ // Act
61
+ const next = registerParticipant(emptySession(), "SHA256:ana", "Ana", {
62
+ provider: "github",
63
+ handle: "ana",
64
+ });
65
+
66
+ // Assert
67
+ expect(next.participants).toEqual([
68
+ { id: "SHA256:ana", provider: "github", name: "Ana", handle: "ana" },
69
+ ]);
70
+ });
71
+
72
+ test("a later nameless write preserves the github provider and handle", () => {
73
+ // Arrange
74
+ const verified = registerParticipant(emptySession(), "SHA256:ana", "Ana", {
75
+ provider: "github",
76
+ handle: "ana",
77
+ });
78
+
79
+ // Act
80
+ const next = registerParticipant(verified, "SHA256:ana");
81
+
82
+ // Assert
83
+ expect(next.participants).toEqual([
84
+ { id: "SHA256:ana", provider: "github", name: "Ana", handle: "ana" },
85
+ ]);
86
+ });
58
87
  });
@@ -13,17 +13,28 @@ import type { Identity, Thread } from "./types";
13
13
  * rail renders anonymous) and never erases a name a past visit set. Immutable:
14
14
  * the input session is not mutated.
15
15
  */
16
- export function registerParticipant(session: Thread, author: string, name?: string): Thread {
16
+ export interface ParticipantSource {
17
+ provider: "ssh" | "github";
18
+ handle?: string;
19
+ }
20
+
21
+ export function registerParticipant(
22
+ session: Thread,
23
+ author: string,
24
+ name?: string,
25
+ source?: ParticipantSource,
26
+ ): Thread {
17
27
  const participants = session.participants ?? [];
18
28
  const existing = participants.find((participant) => participant.id === author);
19
29
  const trimmed = name?.trim();
20
30
 
21
- if (existing && !trimmed) return session;
22
- const next: Identity = {
23
- id: author,
24
- provider: "ssh",
25
- ...(trimmed ? { name: trimmed } : existing?.name ? { name: existing.name } : {}),
26
- };
31
+ if (existing && !trimmed && !source) return session;
32
+ const resolvedName = trimmed ?? existing?.name;
33
+ const resolvedHandle = source?.handle ?? existing?.handle;
34
+ const next: Identity = { id: author, provider: source?.provider ?? existing?.provider ?? "ssh" };
35
+
36
+ if (resolvedName) next.name = resolvedName;
37
+ if (resolvedHandle) next.handle = resolvedHandle;
27
38
 
28
39
  return {
29
40
  ...session,
@@ -0,0 +1,20 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { isShareViewerAllowed } from "./share-access";
3
+
4
+ describe("isShareViewerAllowed", () => {
5
+ test("an unauthenticated viewer is refused", () => {
6
+ expect(isShareViewerAllowed({ githubLogins: ["octocat"] }, undefined)).toBe(false);
7
+ });
8
+
9
+ test("a member is admitted, case-insensitively", () => {
10
+ expect(isShareViewerAllowed({ githubLogins: ["OctoCat"] }, "octocat")).toBe(true);
11
+ });
12
+
13
+ test("a non-member is refused", () => {
14
+ expect(isShareViewerAllowed({ githubLogins: ["octocat"] }, "hubot")).toBe(false);
15
+ });
16
+
17
+ test("an empty allowlist admits no one", () => {
18
+ expect(isShareViewerAllowed({ githubLogins: [] }, "octocat")).toBe(false);
19
+ });
20
+ });
@@ -0,0 +1,13 @@
1
+ /** Whether a github login may view a private share; membership is case-insensitive and an unauthenticated viewer is never allowed. */
2
+
3
+ import type { ShareAccess } from "./types";
4
+
5
+ export function isShareViewerAllowed(
6
+ access: ShareAccess,
7
+ githubLogin: string | undefined,
8
+ ): boolean {
9
+ if (githubLogin === undefined) return false;
10
+ const login = githubLogin.toLowerCase();
11
+
12
+ return access.githubLogins.some((allowed) => allowed.toLowerCase() === login);
13
+ }
package/src/types.ts CHANGED
@@ -224,14 +224,19 @@ export type SessionStatus = "pending" | "resolved";
224
224
  export interface Identity {
225
225
  /** Stable identity key; equals an annotation's `author`. */
226
226
  id: string;
227
- /** Identity source. One value today; widen the union when OAuth lands. */
228
- provider: "ssh";
227
+ /** Identity source: the SSH key that authored, or a verified GitHub login. */
228
+ provider: "ssh" | "github";
229
229
  /** Display name; absent = the collaborator stayed anonymous. */
230
230
  name?: string;
231
231
  /** Provider handle: a github login, an email, or a short fingerprint. */
232
232
  handle?: string;
233
233
  }
234
234
 
235
+ /** Owner-set access control for a private share: only these GitHub logins may open it. Absent = a public share. */
236
+ export interface ShareAccess {
237
+ githubLogins: string[];
238
+ }
239
+
235
240
  export interface Thread {
236
241
  schemaVersion: string;
237
242
  id: string;
@@ -278,6 +283,8 @@ export interface Thread {
278
283
  shareBranch?: string;
279
284
  /** SSH fingerprint that created the share; the gateway stamps it to gate pulls. */
280
285
  owner?: string;
286
+ /** Owner-set allowlist of GitHub logins for a private share; absent = public. */
287
+ access?: ShareAccess;
281
288
  /**
282
289
  * Identities that authored annotations here, keyed by id (union-by-id, like
283
290
  * annotations). The gateway records a collaborator's identity and chosen name;