@book.dev/sdk 1.60.0 → 1.65.0

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.
Files changed (49) hide show
  1. package/dist/account.d.ts +11 -0
  2. package/dist/account.js +27 -0
  3. package/dist/account.js.map +1 -1
  4. package/dist/ai.d.ts +10 -0
  5. package/dist/ai.js +12 -0
  6. package/dist/ai.js.map +1 -1
  7. package/dist/authorize.d.ts +104 -0
  8. package/dist/authorize.js +151 -0
  9. package/dist/authorize.js.map +1 -0
  10. package/dist/authors.d.ts +26 -0
  11. package/dist/authors.js +81 -0
  12. package/dist/authors.js.map +1 -0
  13. package/dist/backup.d.ts +46 -0
  14. package/dist/backup.js +15 -0
  15. package/dist/backup.js.map +1 -1
  16. package/dist/client.d.ts +124 -3
  17. package/dist/client.js +113 -10
  18. package/dist/client.js.map +1 -1
  19. package/dist/connection.d.ts +23 -0
  20. package/dist/connection.js +56 -0
  21. package/dist/connection.js.map +1 -1
  22. package/dist/forwarding/index.d.ts +2 -1
  23. package/dist/forwarding/index.js +2 -1
  24. package/dist/forwarding/index.js.map +1 -1
  25. package/dist/forwarding/rosterAssertion.d.ts +49 -0
  26. package/dist/forwarding/rosterAssertion.js +89 -0
  27. package/dist/forwarding/rosterAssertion.js.map +1 -0
  28. package/dist/forwarding/tunnelClient.js +11 -1
  29. package/dist/forwarding/tunnelClient.js.map +1 -1
  30. package/dist/forwarding/tunnelProtocol.d.ts +11 -0
  31. package/dist/forwarding/tunnelProtocol.js +11 -0
  32. package/dist/forwarding/tunnelProtocol.js.map +1 -1
  33. package/dist/identity.d.ts +238 -0
  34. package/dist/identity.js +273 -0
  35. package/dist/identity.js.map +1 -0
  36. package/dist/index.d.ts +13 -9
  37. package/dist/index.js +9 -5
  38. package/dist/index.js.map +1 -1
  39. package/dist/provenance.d.ts +168 -0
  40. package/dist/provenance.js +31 -0
  41. package/dist/provenance.js.map +1 -0
  42. package/dist/routes.d.ts +53 -0
  43. package/dist/routes.js +50 -0
  44. package/dist/routes.js.map +1 -1
  45. package/dist/suggestions.d.ts +17 -2
  46. package/dist/types.d.ts +100 -0
  47. package/dist/types.js +9 -0
  48. package/dist/types.js.map +1 -1
  49. package/package.json +6 -1
package/dist/account.d.ts CHANGED
@@ -52,6 +52,17 @@ export declare class AccountClient {
52
52
  putSettings(token: string, settings: Record<string, unknown>): Promise<{
53
53
  updatedAt: string;
54
54
  }>;
55
+ /**
56
+ * Mint a verifiable identity assertion (JWS) for the OpenBook data server
57
+ * (OB-165). The data server verifies it against the account's JWKS and
58
+ * attributes the user's changes to `iss#sub`. Returns `{identity, expiresAt}`,
59
+ * or `null` when the account doesn't issue identities (501) — the app then
60
+ * acts as a named guest. Throws `AccountError(401)` on an invalid/revoked token.
61
+ */
62
+ getIdentityToken(token: string, aud?: string): Promise<{
63
+ identity: string;
64
+ expiresAt: string;
65
+ } | null>;
55
66
  /** Cheap token check (a settings GET): true if accepted, false on 401. */
56
67
  validate(token: string): Promise<boolean>;
57
68
  }
package/dist/account.js CHANGED
@@ -88,6 +88,33 @@ export class AccountClient {
88
88
  const body = (await res.json());
89
89
  return { updatedAt: body.updatedAt };
90
90
  }
91
+ /**
92
+ * Mint a verifiable identity assertion (JWS) for the OpenBook data server
93
+ * (OB-165). The data server verifies it against the account's JWKS and
94
+ * attributes the user's changes to `iss#sub`. Returns `{identity, expiresAt}`,
95
+ * or `null` when the account doesn't issue identities (501) — the app then
96
+ * acts as a named guest. Throws `AccountError(401)` on an invalid/revoked token.
97
+ */
98
+ async getIdentityToken(token, aud) {
99
+ // `aud` scopes the assertion to one data server (OB-177), so it can't be
100
+ // replayed to another. Required by the issuer only when it runs an audience
101
+ // allowlist; harmless (and unscoped) otherwise.
102
+ const url = new URL('/api/identity/token', this.baseUrl + '/');
103
+ if (aud)
104
+ url.searchParams.set('aud', aud);
105
+ const res = await fetch(url, {
106
+ headers: { authorization: `Bearer ${token}` },
107
+ cache: 'no-store',
108
+ });
109
+ if (res.status === 501)
110
+ return null; // issuance not configured on this account
111
+ if (!res.ok)
112
+ throw new AccountError(res.status, `account identity token failed (${res.status})`);
113
+ const body = (await res.json());
114
+ if (!body.identity || !body.expiresAt)
115
+ return null;
116
+ return { identity: body.identity, expiresAt: body.expiresAt };
117
+ }
91
118
  /** Cheap token check (a settings GET): true if accepted, false on 401. */
92
119
  async validate(token) {
93
120
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"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"}
package/dist/ai.d.ts CHANGED
@@ -63,6 +63,16 @@ export interface AiConfig {
63
63
  * and both UIs read settings through this, so old configs keep working.
64
64
  */
65
65
  export declare function providerSettings(config: AiConfig, provider: AiProvider): AiProviderSettings;
66
+ /**
67
+ * Whether a provider is a **paid / hosted** engine — `openai` (an OpenAI-style
68
+ * endpoint that may be `api.openai.com`) or `claude` (Anthropic's hosted API).
69
+ * Both can bill per token and send content off the machine, so paid inference is
70
+ * fenced behind sign-in on a claimed multi-user instance (a guest must not be
71
+ * able to rack up inference cost). The rest — `off`/`mock`/`llama`/`mlx` — run
72
+ * locally and free, and stay open. The single source of truth for "is this
73
+ * inference paid"; routes and UI both classify the configured provider through it.
74
+ */
75
+ export declare function isPaidProvider(provider: AiProvider): boolean;
66
76
  export interface AiStatus {
67
77
  config: AiConfig;
68
78
  /** The engine can generate text right now. */
package/dist/ai.js CHANGED
@@ -33,4 +33,16 @@ export function providerSettings(config, provider) {
33
33
  }
34
34
  return {};
35
35
  }
36
+ /**
37
+ * Whether a provider is a **paid / hosted** engine — `openai` (an OpenAI-style
38
+ * endpoint that may be `api.openai.com`) or `claude` (Anthropic's hosted API).
39
+ * Both can bill per token and send content off the machine, so paid inference is
40
+ * fenced behind sign-in on a claimed multi-user instance (a guest must not be
41
+ * able to rack up inference cost). The rest — `off`/`mock`/`llama`/`mlx` — run
42
+ * locally and free, and stay open. The single source of truth for "is this
43
+ * inference paid"; routes and UI both classify the configured provider through it.
44
+ */
45
+ export function isPaidProvider(provider) {
46
+ return provider === 'openai' || provider === 'claude';
47
+ }
36
48
  //# sourceMappingURL=ai.js.map
package/dist/ai.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAgDH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAgB,EAAE,QAAoB;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC;QACjC,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAC,CAAC;IAC5G,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAgDH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAgB,EAAE,QAAoB;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC;QACjC,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAC,CAAC;IAC5G,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,QAAoB;IACjD,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,QAAQ,CAAC;AACxD,CAAC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The one authorization function (OB-189; contract
3
+ * `docs/sharing-access-contract-spike-OB-182.md` §1).
4
+ *
5
+ * `authorize(principal, page, ctx)` is the single, **pure, isomorphic** access
6
+ * decision for the whole product. The origin calls it on every content
7
+ * request/event; the client reuses it to grey out UI. It has **no I/O**: the
8
+ * roster role, the resolved (post-`inherit`) visibility, and the email-authority
9
+ * gate are all computed by the caller (the store) and handed in via {@link
10
+ * AccessCtx}. This module only composes them through the §1.2 precedence ladder.
11
+ *
12
+ * Wiring it into routes, streams and the request → {@link AccessCtx} build is
13
+ * OB-190 (enforcement) and is intentionally NOT here.
14
+ */
15
+ import type { Principal } from './identity';
16
+ import type { InstanceConfig } from './provenance';
17
+ import type { AclLevel, MemberRole, PageVisibility } from './types';
18
+ /**
19
+ * Roster role, in the authorize layer (contract §1.1 calls this union `Role`).
20
+ * An ALIAS of {@link MemberRole} — there is exactly one role union in the SDK.
21
+ */
22
+ export type Role = MemberRole;
23
+ /** A resolved (never `inherit`) page visibility scope. */
24
+ export type EffectiveVisibility = Exclude<PageVisibility, 'inherit'>;
25
+ /**
26
+ * One per-page ACL grant, as seen by {@link authorize} (the in-memory shape of a
27
+ * `page_acl` row, §2.3). Exactly one grantee key is set: `subject` (a grantee
28
+ * already bound to any trusted issuer) XOR `email` (a grantee by persona email,
29
+ * lowercased). An `email` entry MUST carry the pinned `issuer` — the
30
+ * email-authority — or it can never match (B1).
31
+ */
32
+ export interface AclEntry {
33
+ /** Grantee already bound to a member/handle (any trusted issuer). */
34
+ subject?: string;
35
+ /** Grantee by persona email (lowercased). */
36
+ email?: string;
37
+ /** REQUIRED for an email entry — the pinned email-authority (B1). */
38
+ issuer?: string;
39
+ level: AclLevel;
40
+ }
41
+ /** The per-page inputs to a decision: the page's scope + its ACL grants. */
42
+ export interface AccessPage {
43
+ visibility: PageVisibility;
44
+ acl: AclEntry[];
45
+ }
46
+ /**
47
+ * The non-page, caller-resolved inputs to a decision (contract §1.1). Everything
48
+ * here is computed by the store before the call: the active-persona role, the
49
+ * effective visibility after resolving `inherit`, and whether the principal's
50
+ * email may be trusted.
51
+ */
52
+ export interface AccessCtx {
53
+ config: Pick<InstanceConfig, 'guestAccess' | 'ownerSubject' | 'defaultVisibility' | 'emailAuthority'>;
54
+ /**
55
+ * Role of the principal's ACTIVE persona, resolved by the store: a roster row
56
+ * with `status='active'` whose BOUND `subject === principal.subject` (and, for
57
+ * an email persona, whose email === principal.email under the pinned authority).
58
+ * `invited`/`suspended` rows resolve to `null` — a dormant row grants nothing
59
+ * (S3). `null` ⇒ the principal is not an active member.
60
+ */
61
+ role: Role | null;
62
+ /**
63
+ * Effective visibility after resolving `inherit` (up the PARENT chain for an
64
+ * ordinary page, or via the DATABASE HOST PAGE for a database row — N9), down to
65
+ * `config.defaultVisibility` at the root. Never `inherit`.
66
+ */
67
+ effectiveVisibility: EffectiveVisibility;
68
+ /**
69
+ * True iff the principal's email may be trusted for persona / email-ACL
70
+ * matching: `verifiedVia==='jws'` AND `principal.issuer === config.emailAuthority`
71
+ * (B1). Only then does email matching fire. See {@link isEmailAuthoritative}.
72
+ */
73
+ emailIsAuthoritative: boolean;
74
+ }
75
+ /** The decision: independent read/write grants plus the deciding rung's reason. */
76
+ export interface Decision {
77
+ canRead: boolean;
78
+ canWrite: boolean;
79
+ reason: string;
80
+ }
81
+ /**
82
+ * The `emailIsAuthoritative` predicate (contract §1.1, B1) as a reusable pure
83
+ * helper the store uses to build {@link AccessCtx.emailIsAuthoritative}. An email
84
+ * is trustworthy ONLY on a fresh JWS from the one pinned email-authority — a
85
+ * federated self-issuer asserting arbitrary emails can never satisfy it.
86
+ */
87
+ export declare function isEmailAuthoritative(principal: Principal, config: Pick<InstanceConfig, 'emailAuthority'>): boolean;
88
+ /**
89
+ * Decide read+write for a principal on a page (contract §1.2 precedence ladder).
90
+ *
91
+ * Ladder, highest authority first; the first rule that grants a *field* wins, and
92
+ * write is only ever granted by rules 0/1/2/3/4:
93
+ *
94
+ * 0. Unclaimed instance (`ownerSubject` unset) → legacy guest-gate short-circuit
95
+ * (loopback-only by the §2.6 exposure invariant).
96
+ * 1. Loopback owner (`verifiedVia==='local'`) → read+write.
97
+ * 2. Owner (`jws` && `subject===ownerSubject`) → read+write.
98
+ * 3. Per-page ACL: `write` → read+write; `read` → read (write falls through).
99
+ * 4. Roster role: `admin` → read+write; `viewer` → read per scope, write denied.
100
+ * 5. Visibility scope vs principal class → governs read for everyone else.
101
+ * 6. Guest-gate floor (`guestAccess='off'` denies the guest class even `public`).
102
+ * 7. Default deny.
103
+ */
104
+ export declare function authorize(principal: Principal, page: AccessPage, ctx: AccessCtx): Decision;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The one authorization function (OB-189; contract
3
+ * `docs/sharing-access-contract-spike-OB-182.md` §1).
4
+ *
5
+ * `authorize(principal, page, ctx)` is the single, **pure, isomorphic** access
6
+ * decision for the whole product. The origin calls it on every content
7
+ * request/event; the client reuses it to grey out UI. It has **no I/O**: the
8
+ * roster role, the resolved (post-`inherit`) visibility, and the email-authority
9
+ * gate are all computed by the caller (the store) and handed in via {@link
10
+ * AccessCtx}. This module only composes them through the §1.2 precedence ladder.
11
+ *
12
+ * Wiring it into routes, streams and the request → {@link AccessCtx} build is
13
+ * OB-190 (enforcement) and is intentionally NOT here.
14
+ */
15
+ /**
16
+ * The `emailIsAuthoritative` predicate (contract §1.1, B1) as a reusable pure
17
+ * helper the store uses to build {@link AccessCtx.emailIsAuthoritative}. An email
18
+ * is trustworthy ONLY on a fresh JWS from the one pinned email-authority — a
19
+ * federated self-issuer asserting arbitrary emails can never satisfy it.
20
+ */
21
+ export function isEmailAuthoritative(principal, config) {
22
+ return (principal.verifiedVia === 'jws' &&
23
+ !!config.emailAuthority &&
24
+ principal.issuer === config.emailAuthority);
25
+ }
26
+ /**
27
+ * The highest ACL grant a principal matches on this page (§1.2 rule 3), or `null`.
28
+ * `'write'` outranks `'read'`. A **subject** entry matches only a `jws` principal
29
+ * with the same subject (any trusted issuer); an **email** entry matches only when
30
+ * the email is authoritative AND the lowercased emails are equal AND the entry's
31
+ * `issuer` is the pinned email-authority (B1) — so a federated issuer can never
32
+ * satisfy an `account.book.pub`-scoped email grant.
33
+ */
34
+ function matchAcl(principal, acl, ctx) {
35
+ const isJws = principal.verifiedVia === 'jws';
36
+ let best = null;
37
+ for (const entry of acl) {
38
+ let matches = false;
39
+ if (entry.subject) {
40
+ matches = isJws && entry.subject === principal.subject;
41
+ }
42
+ else if (entry.email) {
43
+ matches =
44
+ ctx.emailIsAuthoritative &&
45
+ !!principal.email &&
46
+ entry.email.toLowerCase() === principal.email.toLowerCase() &&
47
+ entry.issuer === ctx.config.emailAuthority;
48
+ }
49
+ if (matches) {
50
+ if (entry.level === 'write')
51
+ return 'write';
52
+ best = 'read';
53
+ }
54
+ }
55
+ return best;
56
+ }
57
+ /** Does the principal get READ purely from the visibility scope (§1.2 rule 5)? */
58
+ function scopeAllowsRead(principal, ctx, guestBlocked) {
59
+ switch (ctx.effectiveVisibility) {
60
+ case 'public':
61
+ // Read for ALL — incl. anonymous — overriding the guest gate, EXCEPT a
62
+ // guest when `guestAccess='off'` (rule 6 / footnote ¹).
63
+ return !guestBlocked;
64
+ case 'authenticated':
65
+ // Any signed-in (jws) user; guests denied (N8).
66
+ return principal.verifiedVia === 'jws';
67
+ case 'members':
68
+ // Only active roster members — an active row resolves to a non-null role.
69
+ // A signed-in non-member or invited/suspended persona (role null) gets
70
+ // nothing here; it may still read via an ACL above.
71
+ return ctx.role !== null;
72
+ case 'restricted':
73
+ // Only owner/admin/ACL, all handled above; everyone else: no.
74
+ return false;
75
+ }
76
+ }
77
+ /**
78
+ * Decide read+write for a principal on a page (contract §1.2 precedence ladder).
79
+ *
80
+ * Ladder, highest authority first; the first rule that grants a *field* wins, and
81
+ * write is only ever granted by rules 0/1/2/3/4:
82
+ *
83
+ * 0. Unclaimed instance (`ownerSubject` unset) → legacy guest-gate short-circuit
84
+ * (loopback-only by the §2.6 exposure invariant).
85
+ * 1. Loopback owner (`verifiedVia==='local'`) → read+write.
86
+ * 2. Owner (`jws` && `subject===ownerSubject`) → read+write.
87
+ * 3. Per-page ACL: `write` → read+write; `read` → read (write falls through).
88
+ * 4. Roster role: `admin` → read+write; `viewer` → read per scope, write denied.
89
+ * 5. Visibility scope vs principal class → governs read for everyone else.
90
+ * 6. Guest-gate floor (`guestAccess='off'` denies the guest class even `public`).
91
+ * 7. Default deny.
92
+ */
93
+ export function authorize(principal, page, ctx) {
94
+ const { config } = ctx;
95
+ const isLocal = principal.verifiedVia === 'local';
96
+ const isJws = principal.verifiedVia === 'jws';
97
+ // Rule 0 — unclaimed instance: legacy short-circuit, preserving today's
98
+ // loopback behaviour exactly. Roster/scope/ACL don't exist until claimed; the
99
+ // in-process (`local`) and any `jws` caller always read+write (truth-table
100
+ // footer), everyone else is judged by the guest gate. Reachable only on
101
+ // loopback (the §2.6 exposure invariant — a claim is required before exposure).
102
+ if (config.ownerSubject === undefined) {
103
+ const privileged = isJws || isLocal;
104
+ return {
105
+ canRead: config.guestAccess !== 'off' || privileged,
106
+ canWrite: config.guestAccess === 'write' || privileged,
107
+ reason: 'legacy-guest-gate',
108
+ };
109
+ }
110
+ // ── Claimed instance ──────────────────────────────────────────────────────
111
+ const isOwner = isJws && principal.subject === config.ownerSubject; // rule 2
112
+ const aclMatch = matchAcl(principal, page.acl, ctx); // rule 3
113
+ const isAdmin = ctx.role === 'admin'; // rule 4
114
+ const isViewer = ctx.role === 'viewer'; // rule 4
115
+ // Rule 6 floor: the guest class is denied even `public` when guests are
116
+ // disabled. Hardened (OB-190, OB-189 security review #1) to the WHOLE
117
+ // unauthenticated class — any principal that is neither an authenticated `jws`
118
+ // user nor the loopback owner. `kind==='guest'` is the common case, but a
119
+ // non-request-assertable `unverified`/`synced` `user` principal must never slip
120
+ // past the `guestAccess='off'` floor onto a `public` page either (defence in
121
+ // depth behind the middleware, which already rejects such principals at the gate
122
+ // on an identity-enabled instance).
123
+ const guestBlocked = !isJws && !isLocal && config.guestAccess === 'off';
124
+ const scopeRead = scopeAllowsRead(principal, ctx, guestBlocked); // rule 5
125
+ // WRITE — only rules 1/2/3(write)/4(admin) ever grant it. `viewer` is locked,
126
+ // jws-non-members and guests are read-only on a claimed instance.
127
+ const canWrite = isLocal || isOwner || aclMatch === 'write' || isAdmin;
128
+ // READ — granted by the first rung that allows it.
129
+ const canRead = isLocal || isOwner || aclMatch !== null || isAdmin || scopeRead;
130
+ let reason;
131
+ if (isLocal)
132
+ reason = 'local-owner';
133
+ else if (isOwner)
134
+ reason = 'owner';
135
+ else if (aclMatch === 'write')
136
+ reason = 'acl-write';
137
+ else if (isAdmin)
138
+ reason = 'admin';
139
+ else if (aclMatch === 'read')
140
+ reason = 'acl-read';
141
+ else if (isViewer)
142
+ reason = canRead ? 'viewer-readonly' : 'no-grant';
143
+ else if (canRead)
144
+ reason = 'visibility-scope';
145
+ else if (guestBlocked)
146
+ reason = 'guest-disabled';
147
+ else
148
+ reason = 'no-grant';
149
+ return { canRead, canWrite, reason };
150
+ }
151
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../src/authorize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { PageSnapshot } from './types';
2
+ /**
3
+ * Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
4
+ * their prior author; changed/new blocks get `authorSubject` when it is a
5
+ * verified identity (a non-empty subject), and are otherwise left unattributed
6
+ * (an anonymous/guest edit honestly clears a block's verified author rather than
7
+ * falsely keeping the previous one). Returns a sparse map — only blocks with a
8
+ * known author — or `null` when none are attributed (so the field stays absent
9
+ * on single-user / unverified documents).
10
+ */
11
+ export declare function computeBlockAuthors(prev: PageSnapshot | null | undefined, next: PageSnapshot, authorSubject: string): Array<[string, string]> | null;
12
+ /**
13
+ * Return `next` with its `authors` stamped relative to `prev`. `authorSubject` is
14
+ * the request's *verified* principal subject (`iss#sub`), or `''` for an
15
+ * unverified/guest/local write (which carries no new attribution). Idempotent
16
+ * when the document is unchanged. Omits the `authors` key entirely when nothing
17
+ * is attributed, so it never appears on single-user/unverified snapshots.
18
+ */
19
+ export declare function stampSnapshotAuthors(prev: PageSnapshot | null | undefined, next: PageSnapshot, authorSubject: string): PageSnapshot;
20
+ /**
21
+ * The verified author of a snapshot's most-recently-changed attributed block —
22
+ * the snapshot's "last verified editor", read on the receiving instance to
23
+ * attribute a synced edit. Uses `mtimes` to find the newest block, falling back
24
+ * to any attributed block. Returns `null` when nothing is attributed.
25
+ */
26
+ export declare function latestSnapshotAuthor(data: PageSnapshot | null | undefined): string | null;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Per-block authorship that travels WITH a page snapshot (OB-170), so an edit
3
+ * made on one instance is correctly attributed when the snapshot reaches another
4
+ * (the sync/merge path). Parallel to `mtimes` (which records *when* each block
5
+ * changed): `authors` records *who* — but only for **verified** identities, so
6
+ * guest/local/unverified edits never pollute the carried attribution.
7
+ *
8
+ * A snapshot's `authors` is a sparse `[blockId, subject]` map (only blocks with
9
+ * a known verified author). The server stamps it on write from the request's
10
+ * verified principal, carrying forward each unchanged block's prior author and
11
+ * setting the current author on changed/new blocks. Pure + isomorphic, mirroring
12
+ * `mtime.ts` so it runs on the server write path and in tests alike.
13
+ */
14
+ import { snapshotBlocks } from './mtime';
15
+ /**
16
+ * Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
17
+ * their prior author; changed/new blocks get `authorSubject` when it is a
18
+ * verified identity (a non-empty subject), and are otherwise left unattributed
19
+ * (an anonymous/guest edit honestly clears a block's verified author rather than
20
+ * falsely keeping the previous one). Returns a sparse map — only blocks with a
21
+ * known author — or `null` when none are attributed (so the field stays absent
22
+ * on single-user / unverified documents).
23
+ */
24
+ export function computeBlockAuthors(prev, next, authorSubject) {
25
+ const prevBlocks = snapshotBlocks(prev);
26
+ const prevHash = new Map();
27
+ for (const b of prevBlocks)
28
+ prevHash.set(b.id, b.hash);
29
+ const prevAuthor = new Map(prev?.authors ?? []);
30
+ const verified = authorSubject.length > 0;
31
+ const out = [];
32
+ for (const b of snapshotBlocks(next)) {
33
+ const unchanged = prevHash.get(b.id) === b.hash;
34
+ const author = unchanged ? prevAuthor.get(b.id) ?? '' : verified ? authorSubject : '';
35
+ if (author)
36
+ out.push([b.id, author]);
37
+ }
38
+ return out.length > 0 ? out : null;
39
+ }
40
+ /**
41
+ * Return `next` with its `authors` stamped relative to `prev`. `authorSubject` is
42
+ * the request's *verified* principal subject (`iss#sub`), or `''` for an
43
+ * unverified/guest/local write (which carries no new attribution). Idempotent
44
+ * when the document is unchanged. Omits the `authors` key entirely when nothing
45
+ * is attributed, so it never appears on single-user/unverified snapshots.
46
+ */
47
+ export function stampSnapshotAuthors(prev, next, authorSubject) {
48
+ const authors = computeBlockAuthors(prev, next, authorSubject);
49
+ if (!authors) {
50
+ // Nothing attributed: drop any stale `authors` rather than carry an empty map.
51
+ if (next.authors === undefined)
52
+ return next;
53
+ const rest = { ...next };
54
+ delete rest.authors;
55
+ return rest;
56
+ }
57
+ return { ...next, authors };
58
+ }
59
+ /**
60
+ * The verified author of a snapshot's most-recently-changed attributed block —
61
+ * the snapshot's "last verified editor", read on the receiving instance to
62
+ * attribute a synced edit. Uses `mtimes` to find the newest block, falling back
63
+ * to any attributed block. Returns `null` when nothing is attributed.
64
+ */
65
+ export function latestSnapshotAuthor(data) {
66
+ const authors = new Map(data?.authors ?? []);
67
+ if (authors.size === 0)
68
+ return null;
69
+ let latestIso = null;
70
+ let latestSubject = null;
71
+ for (const [blockId, iso] of data?.mtimes ?? []) {
72
+ const subject = authors.get(blockId);
73
+ if (subject && (latestIso === null || iso > latestIso)) {
74
+ latestIso = iso;
75
+ latestSubject = subject;
76
+ }
77
+ }
78
+ // No mtimes overlap (e.g. a snapshot without mtimes) → any attributed author.
79
+ return latestSubject ?? authors.values().next().value ?? null;
80
+ }
81
+ //# sourceMappingURL=authors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authors.js","sourceRoot":"","sources":["../src/authors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAGvC;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAqC,EACrC,IAAkB,EAClB,aAAqB;IAErB,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,UAAU;QAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAiB,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;IAE1C,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,IAAI,MAAM;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAqC,EACrC,IAAkB,EAClB,aAAqB;IAErB,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,+EAA+E;QAC/E,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,IAAI,GAAG,EAAC,GAAG,IAAI,EAAC,CAAC;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAC,GAAG,IAAI,EAAE,OAAO,EAAC,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAqC;IACxE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAiB,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAC7D,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,IAAI,aAAa,GAAkB,IAAI,CAAC;IACxC,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC;YACvD,SAAS,GAAG,GAAG,CAAC;YAChB,aAAa,GAAG,OAAO,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,OAAO,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC;AAChE,CAAC"}
package/dist/backup.d.ts CHANGED
@@ -36,6 +36,52 @@ export interface ImportResult {
36
36
  renamed: number;
37
37
  /** old page id → new page id (copy mode; identity in overwrite). */
38
38
  idMap: Record<string, string>;
39
+ /**
40
+ * True when this apply was a **replay** of an already-imported bundle (ER-6):
41
+ * the bundle's content hash matched a prior import, so nothing was written and
42
+ * the recorded counts/`idMap` are echoed back. Lets the caller skip side effects
43
+ * (e.g. appending a `space.import` provenance entry) on a no-op re-apply.
44
+ */
45
+ deduped?: boolean;
46
+ }
47
+ /**
48
+ * Backup cadences, in increasing interval. The server keeps a rolling set per
49
+ * cadence (grandfather-father-son rotation), so short cadences churn fast and
50
+ * long ones are retained sparsely — automatic, tiered data safety on top of the
51
+ * ad-hoc export.
52
+ */
53
+ export type BackupCadence = 'daily' | 'weekly' | 'monthly' | 'yearly';
54
+ export declare const BACKUP_CADENCES: readonly BackupCadence[];
55
+ /** Interval of each cadence, in milliseconds. */
56
+ export declare const BACKUP_CADENCE_MS: Record<BackupCadence, number>;
57
+ /** Scheduled-backup policy, persisted server-side in the `settings` table. */
58
+ export interface BackupConfig {
59
+ /** Master switch — when false the scheduler is idle (the default). */
60
+ enabled: boolean;
61
+ /** Where backups are written; `null` = the server default (`<dataDir>/backups`). */
62
+ dir: string | null;
63
+ /** Which cadences are active. */
64
+ cadences: Record<BackupCadence, boolean>;
65
+ /** How many snapshots to retain per cadence before pruning the oldest. */
66
+ keep: Record<BackupCadence, number>;
67
+ /** Last successful run per cadence (ISO), so a reboot catches up overdue ones. */
68
+ lastRun: Partial<Record<BackupCadence, string>>;
69
+ }
70
+ export declare const DEFAULT_BACKUP_CONFIG: BackupConfig;
71
+ /** A derived, per-cadence view for the UI (last/next run + how many are on disk). */
72
+ export interface BackupCadenceStatus {
73
+ cadence: BackupCadence;
74
+ enabled: boolean;
75
+ lastRun: string | null;
76
+ nextDue: string | null;
77
+ count: number;
78
+ }
79
+ /** What `GET /api/backups` returns: the policy + resolved dir + derived status. */
80
+ export interface BackupStatus {
81
+ config: BackupConfig;
82
+ /** The resolved output directory (config.dir, or the server default). */
83
+ resolvedDir: string | null;
84
+ cadences: BackupCadenceStatus[];
39
85
  }
40
86
  /**
41
87
  * Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
package/dist/backup.js CHANGED
@@ -1,4 +1,19 @@
1
1
  export const BACKUP_VERSION = 1;
2
+ export const BACKUP_CADENCES = ['daily', 'weekly', 'monthly', 'yearly'];
3
+ /** Interval of each cadence, in milliseconds. */
4
+ export const BACKUP_CADENCE_MS = {
5
+ daily: 24 * 60 * 60 * 1000,
6
+ weekly: 7 * 24 * 60 * 60 * 1000,
7
+ monthly: 30 * 24 * 60 * 60 * 1000,
8
+ yearly: 365 * 24 * 60 * 60 * 1000,
9
+ };
10
+ export const DEFAULT_BACKUP_CONFIG = {
11
+ enabled: false,
12
+ dir: null,
13
+ cadences: { daily: true, weekly: true, monthly: true, yearly: true },
14
+ keep: { daily: 7, weekly: 5, monthly: 12, yearly: 3 },
15
+ lastRun: {},
16
+ };
2
17
  /**
3
18
  * Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
4
19
  * database, remaps every internal reference (`parentId`, `databaseId`,
@@ -1 +1 @@
1
- {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AA+BhC;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,KAAmB,EACnB,SAA2B,EAC3B,KAAmB;IAEnB,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC;IAC7C,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,SAAS;QAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC;IAEjD,MAAM,aAAa,GAAG,CAAC,IAAwB,EAAsB,EAAE;QACrE,IAAI,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAChC,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,CAAC;QACrF,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAC;IAChD,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,GAAG,CAAC;QACJ,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACf,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;QACpE,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QAC5E,gBAAgB,EAAE,CAAC,CAAC,gBAAgB,IAAI,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI;QACpG,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;KAC5B,CAAC,CAAC,CAAC;IACJ,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAC,CAAC,CAAC,CAAC;IACzG,OAAO,EAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAC,CAAC;AAC/D,CAAC"}
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}