@haverstack/core 0.33.0 → 0.36.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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/access.d.ts +39 -34
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +94 -68
  5. package/dist/access.js.map +1 -1
  6. package/dist/changes.d.ts +74 -58
  7. package/dist/changes.d.ts.map +1 -1
  8. package/dist/changes.js +115 -101
  9. package/dist/changes.js.map +1 -1
  10. package/dist/combine.d.ts.map +1 -1
  11. package/dist/combine.js +7 -0
  12. package/dist/combine.js.map +1 -1
  13. package/dist/grants.d.ts +78 -33
  14. package/dist/grants.d.ts.map +1 -1
  15. package/dist/grants.js +164 -72
  16. package/dist/grants.js.map +1 -1
  17. package/dist/index.d.ts +3 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/query-validation.d.ts +30 -1
  21. package/dist/query-validation.d.ts.map +1 -1
  22. package/dist/query-validation.js +128 -2
  23. package/dist/query-validation.js.map +1 -1
  24. package/dist/record-changes.d.ts +77 -57
  25. package/dist/record-changes.d.ts.map +1 -1
  26. package/dist/record-changes.js +153 -89
  27. package/dist/record-changes.js.map +1 -1
  28. package/dist/scoped-stack.d.ts +120 -120
  29. package/dist/scoped-stack.d.ts.map +1 -1
  30. package/dist/scoped-stack.js +210 -190
  31. package/dist/scoped-stack.js.map +1 -1
  32. package/dist/stack.d.ts +178 -168
  33. package/dist/stack.d.ts.map +1 -1
  34. package/dist/stack.js +346 -277
  35. package/dist/stack.js.map +1 -1
  36. package/dist/testing.d.ts +16 -5
  37. package/dist/testing.d.ts.map +1 -1
  38. package/dist/testing.js +124 -52
  39. package/dist/testing.js.map +1 -1
  40. package/dist/types.d.ts +155 -113
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/validate.d.ts.map +1 -1
  44. package/dist/validate.js +0 -2
  45. package/dist/validate.js.map +1 -1
  46. package/dist/wire-record.d.ts.map +1 -1
  47. package/dist/wire-record.js.map +1 -1
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -66,7 +66,7 @@ await stack.associate(note.id, { kind: 'tag', label: 'favourite' });
66
66
  // Or change several things at once — one version, one atomic write
67
67
  await stack.mutate(note.id, {
68
68
  contentPatch: { title: 'Final title' },
69
- permissions: [{ access: 'public' }],
69
+ permissions: [{ kind: 'anyone', label: 'read' }],
70
70
  unlisted: false,
71
71
  });
72
72
 
package/dist/access.d.ts CHANGED
@@ -3,10 +3,16 @@
3
3
  * -------------------------------------------------------
4
4
  * Implements the Permissions model from the spec: a pure predicate over a
5
5
  * Record's `permissions` field, with no dependency on a transport layer or
6
- * storage backend. Used by ScopedStack (see stack.ts) to enforce access
7
- * control; exported standalone for callers that want the raw predicate.
6
+ * storage backend. Used by ScopedStack (see scoped-stack.ts) to enforce
7
+ * access control; exported standalone for callers that want the raw
8
+ * predicate.
9
+ *
10
+ * A permission set is read as data throughout, never as the type: it can
11
+ * arrive from an import or a foreign server, where no compiler has seen
12
+ * it. Every tier is affirmative, so an element read past its own kind —
13
+ * or carrying no kind at all — confers nothing.
8
14
  */
9
- import type { Association, EntityId, Permission, RecordId, StackRecord } from './types.js';
15
+ import type { Association, AuthorityAssociation, DataAssociation, EntityId, RecordId, StackRecord } from './types.js';
10
16
  import type { ValidationError } from './validate.js';
11
17
  export type AccessMode = 'read' | 'write';
12
18
  /** An entity's standing within a `_group` Record's roster. `admin` implies `member` for ACL purposes. */
@@ -17,64 +23,63 @@ export type GroupRole = 'member' | 'admin';
17
23
  */
18
24
  export type RecordResolver = (id: RecordId) => Promise<StackRecord | null>;
19
25
  /**
20
- * Check whether an entity has read or write access to a Record: absent
21
- * permissions = owner only; public = anyone reads; entity = direct match;
22
- * group = walk the _group Record's roster associations. See
23
- * docs/spec/access-control.md § Record-level permissions.
26
+ * Whether an entity has read or write access to a Record.
24
27
  *
25
28
  * The entity here is the **subject** — record-level permissions are written
26
29
  * about who data is for, never about the software that carried the request.
27
30
  * A delegated app's own authority is a separate question, asked of the
28
- * principal. See docs/spec/access-control.md § Delegation: principal and
29
- * subject.
31
+ * principal. See docs/spec/access-control.md § Record-level permissions and
32
+ * § Delegation: principal and subject.
30
33
  */
31
34
  export declare function checkAccess(record: StackRecord, subjectEntityId: EntityId | null, ownerEntityId: EntityId | null, mode: AccessMode, resolveRecord: RecordResolver): Promise<boolean>;
32
35
  /**
33
- * Rejects permission entries that convey write without read — the shape
34
- * entryConveys() refuses to honor, caught at the point of storage so an
35
- * owner writing one is told rather than left with a bit that does nothing.
36
+ * Rejects a permission set where some grantee holds `write` with no `read`
37
+ * beside it — the shape checkAccess() refuses to honor. A cross-element
38
+ * invariant, so it is asked of the set the write would *produce*, the same
39
+ * way hasGroupAdmin() reads its post-state below.
36
40
  * See docs/spec/access-control.md § Write implies read.
37
41
  */
38
- export declare function validatePermissions(permissions: Permission[] | undefined, path?: string): ValidationError[];
42
+ export declare function validatePermissions(permissions: AuthorityAssociation[] | undefined, path?: string): ValidationError[];
43
+ /**
44
+ * Whether a Record is a roster anything may resolve against, on two counts.
45
+ * The family check: without it an app modelling its own `member` links
46
+ * would turn every record it points a permission at into an ACL. The
47
+ * tombstone check: deleting a Group is how a Group is withdrawn. Shared
48
+ * with the grant path, so the two layers cannot disagree about which Groups
49
+ * still reach anyone. See docs/spec/identity.md § Group.
50
+ */
51
+ export declare function carriesRoster(record: StackRecord): boolean;
39
52
  /**
40
- * Determine an entity's role within a `_group` Record's roster from its
41
- * relationship associations. `admin` short-circuits — it's strictly more
42
- * privileged than `member`, so a matching admin association wins regardless
43
- * of association order.
53
+ * An entity's role within a `_group` Record's roster. `admin`
54
+ * short-circuits, so a matching admin association wins regardless of order.
44
55
  *
45
- * Reads associations alone and cannot tell whose they are: every caller
46
- * must first establish that the Record is in the `_group` family, or an
47
- * app's own `member` relationships become a roster.
56
+ * Reads associations alone and cannot tell whose they are: callers pass
57
+ * carriesRoster() first, or an app's own `member` relationships become a
58
+ * roster.
48
59
  */
49
- export declare function groupRoleFromAssociations(associations: Association[] | undefined, entityId: EntityId): GroupRole | null;
60
+ export declare function groupRoleFromAssociations(associations: DataAssociation[] | undefined, entityId: EntityId): GroupRole | null;
50
61
  /**
51
62
  * Whether a roster carries at least one `admin` — the invariant every write
52
63
  * to a `_group` Record must leave standing. Asked of the roster a write
53
64
  * would *produce*, never the one it started from: that is what lets an
54
65
  * admin remove themselves while another remains, and refuses the same
55
66
  * removal when they are the last, without either case naming who is going.
56
- *
57
67
  * Reads associations alone, on the same terms as
58
- * groupRoleFromAssociations() above: the caller must have established that
59
- * the Record is in the `_group` family first. See docs/spec/identity.md
60
- * § Group.
68
+ * groupRoleFromAssociations() above.
61
69
  */
62
- export declare function hasGroupAdmin(associations: Association[] | undefined): boolean;
70
+ export declare function hasGroupAdmin(associations: DataAssociation[] | undefined): boolean;
63
71
  /**
64
72
  * Whether one association is an `admin` roster entry. The `entity` scope is
65
- * what makes a roster entry a roster entry — a `record` target carrying the
66
- * same label confers nothing, so it must not count toward the invariant
67
- * either. See docs/spec/data-model.md § Relationship targets.
73
+ * what makes it one — a `record` target carrying the same label confers
74
+ * nothing, so it must not count toward the invariant either.
75
+ * See docs/spec/data-model.md § Relationship targets.
68
76
  */
69
77
  export declare function isGroupAdminAssociation(association: Association): boolean;
70
78
  /**
71
79
  * Whether a request is the stack owner acting alone — the tier behind every
72
80
  * unconditional owner authority. Being the owner is never sufficient on its
73
- * own, whichever side of a delegation the owner is on. Both identities are
74
- * asked about by value rather than compared to each other, so a caller
75
- * holding a session and `ScopedStack`, which holds the pair separately, get
76
- * one answer. See docs/spec/access-control.md § Delegation: principal and
77
- * subject.
81
+ * own, whichever side of a delegation the owner is on.
82
+ * See docs/spec/access-control.md § Delegation: principal and subject.
78
83
  */
79
84
  export declare function isOwnerActingAlone(session: {
80
85
  principalId: EntityId | null;
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C,yGAAyG;AACzG,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;AAE3E;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,QAAQ,GAAG,IAAI,EAChC,aAAa,EAAE,QAAQ,GAAG,IAAI,EAC9B,IAAI,EAAE,UAAU,EAChB,aAAa,EAAE,cAAc,GAC5B,OAAO,CAAC,OAAO,CAAC,CAwBlB;AAeD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,UAAU,EAAE,GAAG,SAAS,EACrC,IAAI,SAAgB,GACnB,eAAe,EAAE,CAanB;AAqBD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,QAAQ,EAAE,QAAQ,GACjB,SAAS,GAAG,IAAI,CAalB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAMzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IAAE,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,EACrE,aAAa,EAAE,QAAQ,GACtB,OAAO,CAET"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,QAAQ,EAER,QAAQ,EACR,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C,yGAAyG;AACzG,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;AAE3E;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,QAAQ,GAAG,IAAI,EAChC,aAAa,EAAE,QAAQ,GAAG,IAAI,EAC9B,IAAI,EAAE,UAAU,EAChB,aAAa,EAAE,cAAc,GAC5B,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAyCD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,oBAAoB,EAAE,GAAG,SAAS,EAC/C,IAAI,SAAgB,GACnB,eAAe,EAAE,CAanB;AAaD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAE1D;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,eAAe,EAAE,GAAG,SAAS,EAC3C,QAAQ,EAAE,QAAQ,GACjB,SAAS,GAAG,IAAI,CAalB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG,SAAS,GAAG,OAAO,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAMzE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IAAE,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,EACrE,aAAa,EAAE,QAAQ,GACtB,OAAO,CAET"}
package/dist/access.js CHANGED
@@ -3,25 +3,28 @@
3
3
  * -------------------------------------------------------
4
4
  * Implements the Permissions model from the spec: a pure predicate over a
5
5
  * Record's `permissions` field, with no dependency on a transport layer or
6
- * storage backend. Used by ScopedStack (see stack.ts) to enforce access
7
- * control; exported standalone for callers that want the raw predicate.
6
+ * storage backend. Used by ScopedStack (see scoped-stack.ts) to enforce
7
+ * access control; exported standalone for callers that want the raw
8
+ * predicate.
9
+ *
10
+ * A permission set is read as data throughout, never as the type: it can
11
+ * arrive from an import or a foreign server, where no compiler has seen
12
+ * it. Every tier is affirmative, so an element read past its own kind —
13
+ * or carrying no kind at all — confers nothing.
8
14
  */
9
15
  import { SYSTEM_TYPES } from './types.js';
16
+ import { granteeEqual } from './record-changes.js';
10
17
  import { baseIdOf } from './schema.js';
11
18
  /**
12
- * Check whether an entity has read or write access to a Record: absent
13
- * permissions = owner only; public = anyone reads; entity = direct match;
14
- * group = walk the _group Record's roster associations. See
15
- * docs/spec/access-control.md § Record-level permissions.
19
+ * Whether an entity has read or write access to a Record.
16
20
  *
17
21
  * The entity here is the **subject** — record-level permissions are written
18
22
  * about who data is for, never about the software that carried the request.
19
23
  * A delegated app's own authority is a separate question, asked of the
20
- * principal. See docs/spec/access-control.md § Delegation: principal and
21
- * subject.
24
+ * principal. See docs/spec/access-control.md § Record-level permissions and
25
+ * § Delegation: principal and subject.
22
26
  */
23
27
  export async function checkAccess(record, subjectEntityId, ownerEntityId, mode, resolveRecord) {
24
- // Owner always has full access.
25
28
  if (subjectEntityId && subjectEntityId === ownerEntityId)
26
29
  return true;
27
30
  const perms = record.permissions;
@@ -29,77 +32,106 @@ export async function checkAccess(record, subjectEntityId, ownerEntityId, mode,
29
32
  if (!perms || perms.length === 0)
30
33
  return false;
31
34
  for (const p of perms) {
32
- if (p.access === 'public' && mode === 'read')
33
- return true;
34
- if (p.access === 'entity' && p.entityId === subjectEntityId) {
35
- if (entryConveys(p, mode))
36
- return true;
37
- }
38
- if (p.access === 'group' && subjectEntityId) {
39
- const role = await resolveGroupRole(p.groupId, subjectEntityId, resolveRecord);
40
- const satisfiesRole = p.role === 'admin' ? role === 'admin' : role !== null;
41
- if (satisfiesRole && entryConveys(p, mode))
35
+ if (p?.kind === 'anyone') {
36
+ if (mode === 'read' && isWorldRead(p))
42
37
  return true;
38
+ continue;
43
39
  }
40
+ if (p?.kind !== 'permission' || !p.grantee)
41
+ continue;
42
+ if (p.label !== mode)
43
+ continue;
44
+ // The write bit is inert without read alongside it: the mutate surface
45
+ // hands back the record it wrote and opens its whole history, so a
46
+ // write element standing alone would withhold nothing. Refused at the
47
+ // write by validatePermissions() and again here.
48
+ // See docs/spec/access-control.md § Write implies read.
49
+ if (mode === 'write' && !holdsRead(perms, p.grantee))
50
+ continue;
51
+ if (await granteeCovers(p.grantee, subjectEntityId, resolveRecord))
52
+ return true;
44
53
  }
45
54
  return false;
46
55
  }
47
56
  /**
48
- * Whether an entry the requester already matches conveys `mode`. The write
49
- * bit is inert without read alongside it: the mutate surface hands back the
50
- * record it wrote and opens its whole history, so a write bit without read
51
- * would disclose exactly what withholding read asks to withhold. Refused at
52
- * the write by validatePermissions() and again here, since a `permissions`
53
- * array can also arrive from an import or a foreign server. See
54
- * docs/spec/access-control.md § Write implies read.
57
+ * Whether the set conveys read to this grantee — a `read` of its own, or
58
+ * an `anyone` that already reaches everyone. Demanding a second, redundant
59
+ * `read` beside the write would refuse a set that withholds nothing;
60
+ * revoking the `anyone` is what the invariant catches instead.
61
+ * See docs/spec/access-control.md § Write implies read.
55
62
  */
56
- function entryConveys(p, mode) {
57
- return mode === 'read' ? p.read : p.write && p.read;
63
+ function holdsRead(permissions, grantee) {
64
+ return permissions.some((p) => p?.kind === 'anyone'
65
+ ? isWorldRead(p)
66
+ : p?.kind === 'permission' && p.label === 'read' && granteeEqual(p.grantee, grantee));
58
67
  }
59
68
  /**
60
- * Rejects permission entries that convey write without read — the shape
61
- * entryConveys() refuses to honor, caught at the point of storage so an
62
- * owner writing one is told rather than left with a bit that does nothing.
69
+ * Whether an `anyone` element reaches the world. `read` is the only bit it
70
+ * can carry, so one labelled otherwise names no reach — refused at the
71
+ * write by validateAssociation() and read as nothing here.
72
+ */
73
+ function isWorldRead(association) {
74
+ return association.label === 'read';
75
+ }
76
+ /**
77
+ * Whether a grantee reaches this subject. `role: 'member'` is the wider
78
+ * set — an admin satisfies it — and `role: 'admin'` the narrower.
79
+ */
80
+ async function granteeCovers(grantee, subjectEntityId, resolveRecord) {
81
+ if (!subjectEntityId)
82
+ return false;
83
+ if (grantee.scope === 'entity')
84
+ return grantee.entityId === subjectEntityId;
85
+ const role = await resolveGroupRole(grantee.groupId, subjectEntityId, resolveRecord);
86
+ return grantee.role === 'admin' ? role === 'admin' : role !== null;
87
+ }
88
+ /**
89
+ * Rejects a permission set where some grantee holds `write` with no `read`
90
+ * beside it — the shape checkAccess() refuses to honor. A cross-element
91
+ * invariant, so it is asked of the set the write would *produce*, the same
92
+ * way hasGroupAdmin() reads its post-state below.
63
93
  * See docs/spec/access-control.md § Write implies read.
64
94
  */
65
95
  export function validatePermissions(permissions, path = 'permissions') {
96
+ const list = permissions ?? [];
66
97
  const errors = [];
67
- (permissions ?? []).forEach((p, i) => {
68
- if (p.access === 'public')
98
+ list.forEach((p, i) => {
99
+ if (p?.kind !== 'permission' || p.label !== 'write')
69
100
  return;
70
- if (p.write && !p.read) {
71
- errors.push({
72
- path: `${path}[${i}]`,
73
- message: 'write requires read: a write-holder reaches the record and its history through the mutate surface, so `write: true, read: false` withholds nothing',
74
- });
75
- }
101
+ if (holdsRead(list, p.grantee))
102
+ return;
103
+ errors.push({
104
+ path: `${path}[${i}]`,
105
+ message: 'write requires read: a write-holder reaches the record and its history through the mutate surface, so a `write` element with no `read` for the same grantee withholds nothing',
106
+ });
76
107
  });
77
108
  return errors;
78
109
  }
79
- /**
80
- * Resolve a role from the `_group` Record a permission's `groupId` names.
81
- * Only a real `_group` Record carries a roster: without the family check
82
- * any Record's relationship associations would serve as one, so an app
83
- * modelling its own `member` links would silently turn every record it
84
- * points a permission at into an ACL. The same rule the grant path applies
85
- * (see resolveGroupRoleMemoized in stack.ts). See
86
- * docs/spec/access-control.md § Record-level permissions.
87
- */
110
+ /** Resolve a role from the `_group` Record a permission's `groupId` names. */
88
111
  async function resolveGroupRole(groupRecordId, entityId, resolveRecord) {
89
112
  const group = await resolveRecord(groupRecordId);
90
- if (!group || baseIdOf(group.typeId) !== SYSTEM_TYPES.GROUP)
113
+ if (!group || !carriesRoster(group))
91
114
  return null;
92
115
  return groupRoleFromAssociations(group.associations, entityId);
93
116
  }
94
117
  /**
95
- * Determine an entity's role within a `_group` Record's roster from its
96
- * relationship associations. `admin` short-circuits — it's strictly more
97
- * privileged than `member`, so a matching admin association wins regardless
98
- * of association order.
118
+ * Whether a Record is a roster anything may resolve against, on two counts.
119
+ * The family check: without it an app modelling its own `member` links
120
+ * would turn every record it points a permission at into an ACL. The
121
+ * tombstone check: deleting a Group is how a Group is withdrawn. Shared
122
+ * with the grant path, so the two layers cannot disagree about which Groups
123
+ * still reach anyone. See docs/spec/identity.md § Group.
124
+ */
125
+ export function carriesRoster(record) {
126
+ return baseIdOf(record.typeId) === SYSTEM_TYPES.GROUP && !record.deletedAt;
127
+ }
128
+ /**
129
+ * An entity's role within a `_group` Record's roster. `admin`
130
+ * short-circuits, so a matching admin association wins regardless of order.
99
131
  *
100
- * Reads associations alone and cannot tell whose they are: every caller
101
- * must first establish that the Record is in the `_group` family, or an
102
- * app's own `member` relationships become a roster.
132
+ * Reads associations alone and cannot tell whose they are: callers pass
133
+ * carriesRoster() first, or an app's own `member` relationships become a
134
+ * roster.
103
135
  */
104
136
  export function groupRoleFromAssociations(associations, entityId) {
105
137
  let role = null;
@@ -121,20 +153,17 @@ export function groupRoleFromAssociations(associations, entityId) {
121
153
  * would *produce*, never the one it started from: that is what lets an
122
154
  * admin remove themselves while another remains, and refuses the same
123
155
  * removal when they are the last, without either case naming who is going.
124
- *
125
156
  * Reads associations alone, on the same terms as
126
- * groupRoleFromAssociations() above: the caller must have established that
127
- * the Record is in the `_group` family first. See docs/spec/identity.md
128
- * § Group.
157
+ * groupRoleFromAssociations() above.
129
158
  */
130
159
  export function hasGroupAdmin(associations) {
131
160
  return (associations ?? []).some(isGroupAdminAssociation);
132
161
  }
133
162
  /**
134
163
  * Whether one association is an `admin` roster entry. The `entity` scope is
135
- * what makes a roster entry a roster entry — a `record` target carrying the
136
- * same label confers nothing, so it must not count toward the invariant
137
- * either. See docs/spec/data-model.md § Relationship targets.
164
+ * what makes it one — a `record` target carrying the same label confers
165
+ * nothing, so it must not count toward the invariant either.
166
+ * See docs/spec/data-model.md § Relationship targets.
138
167
  */
139
168
  export function isGroupAdminAssociation(association) {
140
169
  return (association.kind === 'relationship' &&
@@ -144,11 +173,8 @@ export function isGroupAdminAssociation(association) {
144
173
  /**
145
174
  * Whether a request is the stack owner acting alone — the tier behind every
146
175
  * unconditional owner authority. Being the owner is never sufficient on its
147
- * own, whichever side of a delegation the owner is on. Both identities are
148
- * asked about by value rather than compared to each other, so a caller
149
- * holding a session and `ScopedStack`, which holds the pair separately, get
150
- * one answer. See docs/spec/access-control.md § Delegation: principal and
151
- * subject.
176
+ * own, whichever side of a delegation the owner is on.
177
+ * See docs/spec/access-control.md § Delegation: principal and subject.
152
178
  */
153
179
  export function isOwnerActingAlone(session, ownerEntityId) {
154
180
  return session.principalId === ownerEntityId && session.subjectId === ownerEntityId;
@@ -1 +1 @@
1
- {"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"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"}
package/dist/changes.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * property of one function here, not a convention every emission site has
14
14
  * to remember. See docs/spec/events.md.
15
15
  */
16
- import type { Association, ChangeActor, JournalEntryInput, ChangeFilter, ChangeKind, ChangeOp, RecordChange, StackRecord, SubscribeOptions, Unsubscribe } from './types.js';
16
+ import type { AssociationChange, ChangeActor, JournalEntryInput, ChangeFilter, ChangeKind, ChangeOp, RecordChange, StackRecord, SubscribeOptions, Unsubscribe } from './types.js';
17
17
  /**
18
18
  * What the emitter knows: the envelope, plus the record it describes.
19
19
  * `record` is the filtering input and is never delivered unprojected —
@@ -46,15 +46,10 @@ export type EmittedChange = {
46
46
  export declare function matchesFilter(emitted: EmittedChange, filter?: ChangeFilter): boolean;
47
47
  /**
48
48
  * Whether an emission passes the unlisted-enumeration boundary for one
49
- * subscription. Unlisted records are excluded from the feed by default,
50
- * exactly as they are from an unfiltered `query()` — with one exception:
51
- * the `unlist` transition itself must still reach a subscriber lacking
52
- * `includeUnlisted`, despite its post-change record (`unlistedAt` now set)
53
- * otherwise failing this very check. Without that exception a subscriber
54
- * who already knows the record would never be told to drop it. Every other
55
- * transition (create-unlisted, an edit while already unlisted, a purge of
56
- * a record that was never listed, the `list` transition itself) needs no
57
- * special-casing: it falls out of checking the record's current state.
49
+ * subscription. The `unlist` transition is the one exception to the
50
+ * default exclusion: without it a subscriber who already knows the record
51
+ * would never be told to drop it. Every other transition falls out of
52
+ * checking the record's current state.
58
53
  * See docs/spec/events.md § The unlisted transition.
59
54
  */
60
55
  export declare function passesUnlistedBoundary(emitted: EmittedChange, includeUnlisted?: boolean): boolean;
@@ -67,9 +62,7 @@ export declare function passesUnlistedBoundary(emitted: EmittedChange, includeUn
67
62
  * nothing.
68
63
  *
69
64
  * The record rides by reference, shared across every frame projected from
70
- * one emission: handlers are contractually read-only over it, and copying
71
- * per subscriber would charge every consumer for a defect none of them
72
- * have. See docs/spec/events.md § The event shape.
65
+ * one emission: handlers are contractually read-only over it.
73
66
  */
74
67
  export declare function emitted(emission: EmittedChange, includeRecords: boolean): RecordChange;
75
68
  /**
@@ -87,12 +80,7 @@ export declare abstract class Subscription {
87
80
  abstract accept(emission: EmittedChange): void;
88
81
  get isClosed(): boolean;
89
82
  close(): void;
90
- /**
91
- * Hand a frame to the handler. A handler that throws cannot fail the
92
- * write it is being told about — the write is already durable — so the
93
- * error goes to onError, or is rethrown asynchronously so that it
94
- * surfaces as an unhandled error rather than vanishing.
95
- */
83
+ /** Hand a frame to the handler; anything it throws goes to reportError(). */
96
84
  protected deliver(emission: EmittedChange): void;
97
85
  protected reportError(err: unknown): void;
98
86
  }
@@ -117,42 +105,70 @@ export declare class RelayDelivery {
117
105
  close(): void;
118
106
  }
119
107
  /**
120
- * Assemble what the emitter knows about one mutation.
108
+ * One change, held between the two halves it is written in.
121
109
  *
122
- * The actor is read off the record for every version-bumping op, because
123
- * `updatedBy`/`updatedVia` were stamped by that same write and so agree
124
- * with what was persisted by construction. `associate`/`dissociate` don't
125
- * bump, so they stamp nothing on the record either — their actor travels
126
- * as `opts.actor` instead, the same way hard delete's does, since reading
127
- * the record would report whoever's *last version-bumping write* this is,
128
- * not who just changed the association. Hard delete stamps nothing and
129
- * leaves nothing to read, so its actor is the requester, passed in. A
130
- * purge also drops `parentId` and the create-time `appId`: the frame says
131
- * that a record of some type was destroyed, and nothing further about
132
- * whose it was. See docs/spec/events.md § Purged records carry nothing.
110
+ * A change lands twice: the durable half travels into the adapter's
111
+ * transaction ahead of the write, and the live half is built from the
112
+ * record that write produced. Naming the change once — its ops, its kind,
113
+ * its actor, what it moved — is what makes "the entry set is the event
114
+ * set" true by construction rather than by convention. No verb can journal
115
+ * one thing and announce another, and a verb that writes only one half has
116
+ * to say so where it does it. See docs/spec/journal.md § The entry set is
117
+ * the event set.
133
118
  */
134
- export declare function buildEmission(ops: ChangeOp | ChangeOp[], record: StackRecord, opts?: {
135
- actor?: ChangeActor;
136
- at?: Date;
137
- previousParentId?: string | null;
138
- associationsAdded?: Association[];
139
- associationsRemoved?: Association[];
140
- }): EmittedChange;
141
- /**
142
- * The durable half of a change, built before the write that it describes
143
- * — where an emission is built after one. The two carry the same facts
144
- * about what moved and who moved it; they differ in what they can read
145
- * off the record, which at this point has not been written yet. So the
146
- * adapter stamps the record-derived fields and this names the rest.
147
- * See docs/spec/versioning.md § The change journal.
148
- */
149
- export declare function buildJournalEntry(ops: ChangeOp | ChangeOp[], opts?: {
150
- actor?: ChangeActor;
151
- previousParentId?: string | null;
152
- associationsAdded?: Association[];
153
- associationsRemoved?: Association[];
154
- associationsReplaced?: Association[];
155
- }): JournalEntryInput;
119
+ export declare class PendingChange {
120
+ private readonly moved;
121
+ readonly ops: ChangeOp[];
122
+ readonly kind: ChangeKind;
123
+ constructor(ops: ChangeOp | ChangeOp[], moved?: {
124
+ /**
125
+ * The requester behind a write that stamps nothing on the record
126
+ * itself. A version-bumping write stamps `updatedBy`/`updatedVia`,
127
+ * so its emission reads them back instead — see emission() below.
128
+ */
129
+ actor?: ChangeActor;
130
+ previousParentId?: string | null;
131
+ /**
132
+ * What the write moved, association by association. One list for
133
+ * both halves: the journal takes it as it stands and the feed is
134
+ * flattened out of it, so neither can report an edit the other
135
+ * doesn't.
136
+ */
137
+ associations?: AssociationChange[];
138
+ });
139
+ /**
140
+ * The durable half, handed to the adapter as `journal` so that it lands
141
+ * in the same write as the mutation it describes.
142
+ *
143
+ * Absent for a purge, which destroys the log an entry would be appended
144
+ * to. The rule lives here rather than at each call site that would
145
+ * otherwise have to remember it. See docs/spec/journal.md § A hard
146
+ * delete destroys the journal.
147
+ */
148
+ get journal(): JournalEntryInput | undefined;
149
+ /**
150
+ * The live half, built from the record the write produced — for a purge,
151
+ * from the record as it stood immediately before destruction.
152
+ *
153
+ * The actor is read off the record for every version-bumping op, so it
154
+ * agrees with what was persisted by construction. A no-bump op stamps
155
+ * nothing, so the requester travels here instead — reading the record
156
+ * would report whoever's last version-bumping write this is. A purged
157
+ * frame drops `parentId` and the create-time `appId` too: it says a
158
+ * record of some type was destroyed and nothing about whose it was.
159
+ */
160
+ emission(record: StackRecord, at?: Date): EmittedChange;
161
+ /**
162
+ * The frame's two flat lists, flattened out of the one the journal
163
+ * takes. Present only where non-empty.
164
+ *
165
+ * The data half alone: the `permissions` op is what announces an ACL
166
+ * move, so a subscriber watching tags is never handed the sharing graph
167
+ * as a side effect of the partition sharing one delta.
168
+ * See docs/spec/events.md § The event shape.
169
+ */
170
+ private associationDeltas;
171
+ }
156
172
  /**
157
173
  * The subscriber registry. One per `Stack`; `ScopedStack` filters the same
158
174
  * stream rather than opening its own, so a scoped view can never observe a
@@ -166,6 +182,7 @@ export declare class ChangeEmitter {
166
182
  /** Ends every subscription — a closed stack emits nothing further. */
167
183
  closeAll(): void;
168
184
  }
185
+ /** The kind each op produces. See docs/spec/events.md § The event shape. */
169
186
  export declare const CHANGE_KINDS: Record<ChangeOp, ChangeKind>;
170
187
  /**
171
188
  * `since` only means something where a relay exists: a stack with no
@@ -174,11 +191,10 @@ export declare const CHANGE_KINDS: Record<ChangeOp, ChangeKind>;
174
191
  * caller believe it resumed when it did not, so a stack that cannot honor
175
192
  * `since` refuses it rather than ignoring it.
176
193
  *
177
- * Its shape is checked here rather than left to the adapter, so that a
178
- * malformed cursor is the same error whoever is underneath — the posture
179
- * query() already takes with a filter no adapter declared. The value stays
180
- * opaque: this asks whether it is framable, never what it means. See
181
- * docs/spec/events.md § Subscribing.
194
+ * Its shape is checked here rather than left to the adapter, so a malformed
195
+ * cursor is the same error whoever is underneath. The value stays opaque:
196
+ * this asks whether it is framable, never what it means.
197
+ * See docs/spec/events.md § Subscribing.
182
198
  */
183
199
  export declare function assertSinceUsable(since: string | undefined, relaysChanges: boolean): void;
184
200
  //# sourceMappingURL=changes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"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"}