@haverstack/core 0.40.0 → 0.42.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 (287) hide show
  1. package/README.md +121 -59
  2. package/dist/access.d.ts +34 -36
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +65 -42
  5. package/dist/access.js.map +1 -1
  6. package/dist/{combine.d.ts → adapter/combine.d.ts} +8 -1
  7. package/dist/adapter/combine.d.ts.map +1 -0
  8. package/dist/{combine.js → adapter/combine.js} +7 -0
  9. package/dist/adapter/combine.js.map +1 -0
  10. package/dist/{adapter-entry.d.ts → adapter/index.d.ts} +6 -5
  11. package/dist/adapter/index.d.ts.map +1 -0
  12. package/dist/{adapter-entry.js → adapter/index.js} +5 -4
  13. package/dist/adapter/index.js.map +1 -0
  14. package/dist/{sort.d.ts → adapter/sort.d.ts} +20 -20
  15. package/dist/adapter/sort.d.ts.map +1 -0
  16. package/dist/{sort.js → adapter/sort.js} +37 -27
  17. package/dist/adapter/sort.js.map +1 -0
  18. package/dist/associations/identity.d.ts +93 -0
  19. package/dist/associations/identity.d.ts.map +1 -0
  20. package/dist/associations/identity.js +198 -0
  21. package/dist/associations/identity.js.map +1 -0
  22. package/dist/associations/validation.d.ts +90 -0
  23. package/dist/associations/validation.d.ts.map +1 -0
  24. package/dist/associations/validation.js +339 -0
  25. package/dist/associations/validation.js.map +1 -0
  26. package/dist/attachment-records.d.ts +48 -0
  27. package/dist/attachment-records.d.ts.map +1 -0
  28. package/dist/attachment-records.js +52 -0
  29. package/dist/attachment-records.js.map +1 -0
  30. package/dist/collection.d.ts +182 -0
  31. package/dist/collection.d.ts.map +1 -0
  32. package/dist/collection.js +407 -0
  33. package/dist/collection.js.map +1 -0
  34. package/dist/content-match.d.ts +25 -0
  35. package/dist/content-match.d.ts.map +1 -0
  36. package/dist/content-match.js +51 -0
  37. package/dist/content-match.js.map +1 -0
  38. package/dist/content-translation.d.ts +42 -0
  39. package/dist/content-translation.d.ts.map +1 -0
  40. package/dist/content-translation.js +108 -0
  41. package/dist/content-translation.js.map +1 -0
  42. package/dist/did/did.d.ts.map +1 -0
  43. package/dist/{did.js → did/did.js} +3 -4
  44. package/dist/did/did.js.map +1 -0
  45. package/dist/{did-entry.d.ts → did/index.d.ts} +1 -1
  46. package/dist/did/index.d.ts.map +1 -0
  47. package/dist/{did-entry.js → did/index.js} +3 -3
  48. package/dist/did/index.js.map +1 -0
  49. package/dist/errors.d.ts +36 -63
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js +35 -58
  52. package/dist/errors.js.map +1 -1
  53. package/dist/grants.d.ts +20 -43
  54. package/dist/grants.d.ts.map +1 -1
  55. package/dist/grants.js +71 -134
  56. package/dist/grants.js.map +1 -1
  57. package/dist/id.d.ts +13 -31
  58. package/dist/id.d.ts.map +1 -1
  59. package/dist/id.js +13 -31
  60. package/dist/id.js.map +1 -1
  61. package/dist/index.d.ts +14 -8
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +9 -5
  64. package/dist/index.js.map +1 -1
  65. package/dist/install.d.ts +141 -0
  66. package/dist/install.d.ts.map +1 -0
  67. package/dist/install.js +219 -0
  68. package/dist/install.js.map +1 -0
  69. package/dist/merge.d.ts.map +1 -1
  70. package/dist/merge.js +5 -11
  71. package/dist/merge.js.map +1 -1
  72. package/dist/migration-steps.d.ts +102 -0
  73. package/dist/migration-steps.d.ts.map +1 -0
  74. package/dist/migration-steps.js +293 -0
  75. package/dist/migration-steps.js.map +1 -0
  76. package/dist/query-validation.d.ts +28 -94
  77. package/dist/query-validation.d.ts.map +1 -1
  78. package/dist/query-validation.js +156 -336
  79. package/dist/query-validation.js.map +1 -1
  80. package/dist/record-changes.d.ts +2 -117
  81. package/dist/record-changes.d.ts.map +1 -1
  82. package/dist/record-changes.js +10 -212
  83. package/dist/record-changes.js.map +1 -1
  84. package/dist/schema.d.ts +18 -7
  85. package/dist/schema.d.ts.map +1 -1
  86. package/dist/schema.js +59 -30
  87. package/dist/schema.js.map +1 -1
  88. package/dist/scoped-stack/authority.d.ts +156 -0
  89. package/dist/scoped-stack/authority.d.ts.map +1 -0
  90. package/dist/scoped-stack/authority.js +384 -0
  91. package/dist/scoped-stack/authority.js.map +1 -0
  92. package/dist/scoped-stack/scoped-stack.d.ts +338 -0
  93. package/dist/scoped-stack/scoped-stack.d.ts.map +1 -0
  94. package/dist/scoped-stack/scoped-stack.js +776 -0
  95. package/dist/scoped-stack/scoped-stack.js.map +1 -0
  96. package/dist/scoped-stack/subscription.d.ts +55 -0
  97. package/dist/scoped-stack/subscription.d.ts.map +1 -0
  98. package/dist/scoped-stack/subscription.js +110 -0
  99. package/dist/scoped-stack/subscription.js.map +1 -0
  100. package/dist/stack/attachments.d.ts +24 -0
  101. package/dist/stack/attachments.d.ts.map +1 -0
  102. package/dist/stack/attachments.js +113 -0
  103. package/dist/stack/attachments.js.map +1 -0
  104. package/dist/{changes.d.ts → stack/changes.d.ts} +29 -65
  105. package/dist/stack/changes.d.ts.map +1 -0
  106. package/dist/{changes.js → stack/changes.js} +32 -66
  107. package/dist/stack/changes.js.map +1 -0
  108. package/dist/stack/client.d.ts +271 -0
  109. package/dist/stack/client.d.ts.map +1 -0
  110. package/dist/stack/client.js +9 -0
  111. package/dist/stack/client.js.map +1 -0
  112. package/dist/stack/identity-bindings.d.ts.map +1 -0
  113. package/dist/{identity-bindings.js → stack/identity-bindings.js} +8 -14
  114. package/dist/stack/identity-bindings.js.map +1 -0
  115. package/dist/stack/install-app.d.ts +22 -0
  116. package/dist/stack/install-app.d.ts.map +1 -0
  117. package/dist/stack/install-app.js +260 -0
  118. package/dist/stack/install-app.js.map +1 -0
  119. package/dist/stack/integrity.d.ts +94 -0
  120. package/dist/stack/integrity.d.ts.map +1 -0
  121. package/dist/stack/integrity.js +268 -0
  122. package/dist/stack/integrity.js.map +1 -0
  123. package/dist/{limits.d.ts → stack/limits.d.ts} +2 -5
  124. package/dist/stack/limits.d.ts.map +1 -0
  125. package/dist/{limits.js → stack/limits.js} +3 -6
  126. package/dist/stack/limits.js.map +1 -0
  127. package/dist/stack/migrations.d.ts +59 -0
  128. package/dist/stack/migrations.d.ts.map +1 -0
  129. package/dist/stack/migrations.js +141 -0
  130. package/dist/stack/migrations.js.map +1 -0
  131. package/dist/{stack-reads.d.ts → stack/reads.d.ts} +33 -6
  132. package/dist/stack/reads.d.ts.map +1 -0
  133. package/dist/{stack-reads.js → stack/reads.js} +49 -7
  134. package/dist/stack/reads.js.map +1 -0
  135. package/dist/stack/record-id.d.ts +35 -0
  136. package/dist/stack/record-id.d.ts.map +1 -0
  137. package/dist/stack/record-id.js +82 -0
  138. package/dist/stack/record-id.js.map +1 -0
  139. package/dist/stack/stack.d.ts +438 -0
  140. package/dist/stack/stack.d.ts.map +1 -0
  141. package/dist/stack/stack.js +1366 -0
  142. package/dist/stack/stack.js.map +1 -0
  143. package/dist/stack/system-types.d.ts +16 -0
  144. package/dist/stack/system-types.d.ts.map +1 -0
  145. package/dist/stack/system-types.js +111 -0
  146. package/dist/stack/system-types.js.map +1 -0
  147. package/dist/stack/type-grants.d.ts +20 -0
  148. package/dist/stack/type-grants.d.ts.map +1 -0
  149. package/dist/stack/type-grants.js +69 -0
  150. package/dist/stack/type-grants.js.map +1 -0
  151. package/dist/stack/write-expectation.d.ts +78 -0
  152. package/dist/stack/write-expectation.d.ts.map +1 -0
  153. package/dist/stack/write-expectation.js +71 -0
  154. package/dist/stack/write-expectation.js.map +1 -0
  155. package/dist/testing.d.ts +21 -26
  156. package/dist/testing.d.ts.map +1 -1
  157. package/dist/testing.js +50 -70
  158. package/dist/testing.js.map +1 -1
  159. package/dist/type-handle.d.ts +67 -78
  160. package/dist/type-handle.d.ts.map +1 -1
  161. package/dist/type-handle.js +44 -111
  162. package/dist/type-handle.js.map +1 -1
  163. package/dist/types/adapter.d.ts +223 -0
  164. package/dist/types/adapter.d.ts.map +1 -0
  165. package/dist/types/adapter.js +5 -0
  166. package/dist/types/adapter.js.map +1 -0
  167. package/dist/types/associations.d.ts +156 -0
  168. package/dist/types/associations.d.ts.map +1 -0
  169. package/dist/types/associations.js +13 -0
  170. package/dist/types/associations.js.map +1 -0
  171. package/dist/types/capabilities.d.ts +89 -0
  172. package/dist/types/capabilities.d.ts.map +1 -0
  173. package/dist/types/capabilities.js +5 -0
  174. package/dist/types/capabilities.js.map +1 -0
  175. package/dist/types/events.d.ts +155 -0
  176. package/dist/types/events.d.ts.map +1 -0
  177. package/dist/types/events.js +5 -0
  178. package/dist/types/events.js.map +1 -0
  179. package/dist/types/ids.d.ts +43 -0
  180. package/dist/types/ids.d.ts.map +1 -0
  181. package/dist/types/ids.js +5 -0
  182. package/dist/types/ids.js.map +1 -0
  183. package/dist/types/index.d.ts +19 -0
  184. package/dist/types/index.d.ts.map +1 -0
  185. package/dist/types/index.js +19 -0
  186. package/dist/types/index.js.map +1 -0
  187. package/dist/types/journal.d.ts +47 -0
  188. package/dist/types/journal.d.ts.map +1 -0
  189. package/dist/types/journal.js +5 -0
  190. package/dist/types/journal.js.map +1 -0
  191. package/dist/types/query.d.ts +185 -0
  192. package/dist/types/query.d.ts.map +1 -0
  193. package/dist/types/query.js +10 -0
  194. package/dist/types/query.js.map +1 -0
  195. package/dist/types/records.d.ts +76 -0
  196. package/dist/types/records.d.ts.map +1 -0
  197. package/dist/types/records.js +5 -0
  198. package/dist/types/records.js.map +1 -0
  199. package/dist/types/schema.d.ts +74 -0
  200. package/dist/types/schema.d.ts.map +1 -0
  201. package/dist/types/schema.js +5 -0
  202. package/dist/types/schema.js.map +1 -0
  203. package/dist/types/system.d.ts +161 -0
  204. package/dist/types/system.d.ts.map +1 -0
  205. package/dist/types/system.js +32 -0
  206. package/dist/types/system.js.map +1 -0
  207. package/dist/types/tokens.d.ts +39 -0
  208. package/dist/types/tokens.d.ts.map +1 -0
  209. package/dist/types/tokens.js +5 -0
  210. package/dist/types/tokens.js.map +1 -0
  211. package/dist/validate.d.ts +29 -70
  212. package/dist/validate.d.ts.map +1 -1
  213. package/dist/validate.js +65 -97
  214. package/dist/validate.js.map +1 -1
  215. package/dist/{attachment-download.d.ts → wire/attachment-download.d.ts} +0 -57
  216. package/dist/wire/attachment-download.d.ts.map +1 -0
  217. package/dist/{attachment-download.js → wire/attachment-download.js} +0 -61
  218. package/dist/wire/attachment-download.js.map +1 -0
  219. package/dist/{auth.d.ts → wire/auth.d.ts} +5 -8
  220. package/dist/wire/auth.d.ts.map +1 -0
  221. package/dist/{auth.js → wire/auth.js} +5 -8
  222. package/dist/wire/auth.js.map +1 -0
  223. package/dist/{wire-body.d.ts → wire/body.d.ts} +13 -5
  224. package/dist/wire/body.d.ts.map +1 -0
  225. package/dist/{wire-body.js → wire/body.js} +65 -7
  226. package/dist/wire/body.js.map +1 -0
  227. package/dist/{wire-entry.d.ts → wire/index.d.ts} +12 -12
  228. package/dist/wire/index.d.ts.map +1 -0
  229. package/dist/{wire-entry.js → wire/index.js} +13 -17
  230. package/dist/wire/index.js.map +1 -0
  231. package/dist/{wire-record.d.ts → wire/record.d.ts} +10 -22
  232. package/dist/wire/record.d.ts.map +1 -0
  233. package/dist/{wire-record.js → wire/record.js} +12 -24
  234. package/dist/wire/record.js.map +1 -0
  235. package/dist/{wire-request.d.ts → wire/request.d.ts} +20 -37
  236. package/dist/wire/request.d.ts.map +1 -0
  237. package/dist/{wire-request.js → wire/request.js} +79 -54
  238. package/dist/wire/request.js.map +1 -0
  239. package/package.json +7 -7
  240. package/dist/adapter-entry.d.ts.map +0 -1
  241. package/dist/adapter-entry.js.map +0 -1
  242. package/dist/attachment-download.d.ts.map +0 -1
  243. package/dist/attachment-download.js.map +0 -1
  244. package/dist/auth.d.ts.map +0 -1
  245. package/dist/auth.js.map +0 -1
  246. package/dist/changes.d.ts.map +0 -1
  247. package/dist/changes.js.map +0 -1
  248. package/dist/combine.d.ts.map +0 -1
  249. package/dist/combine.js.map +0 -1
  250. package/dist/did-entry.d.ts.map +0 -1
  251. package/dist/did-entry.js.map +0 -1
  252. package/dist/did.d.ts.map +0 -1
  253. package/dist/did.js.map +0 -1
  254. package/dist/identity-bindings.d.ts.map +0 -1
  255. package/dist/identity-bindings.js.map +0 -1
  256. package/dist/limits.d.ts.map +0 -1
  257. package/dist/limits.js.map +0 -1
  258. package/dist/record-id.d.ts +0 -70
  259. package/dist/record-id.d.ts.map +0 -1
  260. package/dist/record-id.js +0 -127
  261. package/dist/record-id.js.map +0 -1
  262. package/dist/scoped-stack.d.ts +0 -532
  263. package/dist/scoped-stack.d.ts.map +0 -1
  264. package/dist/scoped-stack.js +0 -1219
  265. package/dist/scoped-stack.js.map +0 -1
  266. package/dist/sort.d.ts.map +0 -1
  267. package/dist/sort.js.map +0 -1
  268. package/dist/stack-reads.d.ts.map +0 -1
  269. package/dist/stack-reads.js.map +0 -1
  270. package/dist/stack.d.ts +0 -971
  271. package/dist/stack.d.ts.map +0 -1
  272. package/dist/stack.js +0 -2012
  273. package/dist/stack.js.map +0 -1
  274. package/dist/types.d.ts +0 -1250
  275. package/dist/types.d.ts.map +0 -1
  276. package/dist/types.js +0 -49
  277. package/dist/types.js.map +0 -1
  278. package/dist/wire-body.d.ts.map +0 -1
  279. package/dist/wire-body.js.map +0 -1
  280. package/dist/wire-entry.d.ts.map +0 -1
  281. package/dist/wire-entry.js.map +0 -1
  282. package/dist/wire-record.d.ts.map +0 -1
  283. package/dist/wire-record.js.map +0 -1
  284. package/dist/wire-request.d.ts.map +0 -1
  285. package/dist/wire-request.js.map +0 -1
  286. /package/dist/{did.d.ts → did/did.d.ts} +0 -0
  287. /package/dist/{identity-bindings.d.ts → stack/identity-bindings.d.ts} +0 -0
@@ -0,0 +1,260 @@
1
+ /**
2
+ * App install verbs
3
+ * -------------------------------------------------------
4
+ * Planning, applying and withdrawing an app's install over an unscoped
5
+ * Stack: the bodies of Stack.planInstall(), installApp() and
6
+ * uninstallApp(), whose doc comments describe the contract. Written
7
+ * against Stack's public API alone, so every write here passes the same
8
+ * checks any other caller's would. See docs/spec/apps.md.
9
+ */
10
+ import { ARGUMENTS_INVALID, StackConflictError, StackNotFoundError, StackSchemaDriftError, StackValidationError, } from '../errors.js';
11
+ import { checkGrantValid } from '../grants.js';
12
+ import { INSTALL_APP_LABEL, INSTALL_GRANT_LABEL, claimedFamilies, familyStanding, grantIsRequest, installAppLink, installGrantLink, installReader, linkedIds, namespaceOf, ownTypeIds, planFingerprint, sameRequest, snapshotManifest, } from '../install.js';
13
+ import { baseIdOf, diffSchemas, hashSchema, parseTypeId } from '../schema.js';
14
+ import { findAppCardByDid, loadInstallRecords, queryAllPages } from './reads.js';
15
+ import { SYSTEM_TYPES } from '../types/index.js';
16
+ import { validateSchemaFieldNames, validateSchemaReservedNames, validateSchemaShape, } from '../validate.js';
17
+ export async function planInstall(stack, submitted, opts) {
18
+ const { did } = opts;
19
+ const manifest = snapshotManifest(submitted);
20
+ checkManifest(manifest, did);
21
+ const installs = await loadInstallRecords(stack);
22
+ const existing = installs.find((r) => r.content.appId === manifest.appId) ?? null;
23
+ const ownVersions = ownTypeIds(manifest);
24
+ const ownFamilies = new Set(ownVersions.map(baseIdOf));
25
+ const card = await findAppCardByDid(stack, did);
26
+ if (card && card.content.appId !== manifest.appId) {
27
+ throw new StackConflictError(`${did} is registered to "${card.content.appId}", not "${manifest.appId}"`);
28
+ }
29
+ const claimed = existing ? claimedFamilies(existing.content) : new Set();
30
+ const defined = new Set(existing?.content.defines ?? []);
31
+ const prior = existing?.content.requests ?? [];
32
+ const foreignRequests = [];
33
+ for (const r of manifest.requests) {
34
+ const standing = familyStanding(r.baseId, manifest.appId);
35
+ if (standing === 'own')
36
+ continue;
37
+ const owner = standing === 'foreign'
38
+ ? namespaceOf(r.baseId)
39
+ : standing === 'commons'
40
+ ? 'commons'
41
+ : 'system';
42
+ foreignRequests.push({ ...r, owner });
43
+ }
44
+ const typeChanges = [];
45
+ for (const t of manifest.types) {
46
+ const current = await stack.getType(t.id);
47
+ if (!current)
48
+ typeChanges.push({ id: t.id, change: 'new' });
49
+ else if (current.schemaHash !== (await hashSchema(t.schema))) {
50
+ // Refused here, not left to defineType(): installApp() defines types one
51
+ // by one, so a drift found there leaves the earlier ones written.
52
+ const violations = diffSchemas(current.schema, t.schema);
53
+ if (violations.length > 0)
54
+ throw new StackSchemaDriftError(t.id, violations);
55
+ typeChanges.push({ id: t.id, change: 'schema' });
56
+ }
57
+ else if (current.name !== t.name)
58
+ typeChanges.push({ id: t.id, change: 'name' });
59
+ }
60
+ const linkedKeys = [];
61
+ for (const id of existing ? linkedIds(existing, INSTALL_APP_LABEL) : []) {
62
+ const key = (await stack.get(id))?.content?.did;
63
+ if (typeof key === 'string')
64
+ linkedKeys.push(key);
65
+ }
66
+ return {
67
+ manifest,
68
+ did,
69
+ existing,
70
+ newFamilies: [...ownFamilies].filter((f) => !claimed.has(f)),
71
+ newVersions: ownVersions.filter((id) => !defined.has(id)),
72
+ requestsAdded: manifest.requests.filter((r) => !prior.some((p) => sameRequest(p, r))),
73
+ requestsRemoved: prior.filter((p) => !manifest.requests.some((r) => sameRequest(p, r))),
74
+ foreignRequests,
75
+ typeChanges,
76
+ newKey: !existing ||
77
+ !card ||
78
+ card.deletedAt !== undefined ||
79
+ !linkedIds(existing, INSTALL_APP_LABEL).includes(card.id),
80
+ linkedKeys,
81
+ };
82
+ }
83
+ export async function installApp(stack, plan) {
84
+ const fresh = await planInstall(stack, plan.manifest, { did: plan.did });
85
+ if (planFingerprint(fresh) !== planFingerprint(plan)) {
86
+ throw new StackConflictError(`The stack changed since the install of "${plan.manifest.appId}" was planned; plan it again`);
87
+ }
88
+ const { manifest, did, existing } = fresh;
89
+ for (const type of manifest.types)
90
+ await stack.defineType(type);
91
+ const card = await ensureAppCard(stack, manifest, did);
92
+ const defines = [...new Set([...(existing?.content.defines ?? []), ...ownTypeIds(manifest)])];
93
+ const requests = manifest.requests.map((r) => ({
94
+ baseId: r.baseId,
95
+ actions: [...r.actions],
96
+ }));
97
+ let install;
98
+ if (!existing) {
99
+ install = await stack.create(`${SYSTEM_TYPES.INSTALL}@1`, {
100
+ appId: manifest.appId,
101
+ name: manifest.name,
102
+ ...(manifest.version !== undefined && { version: manifest.version }),
103
+ defines,
104
+ requests,
105
+ }, { associations: [installAppLink(card.id)] });
106
+ }
107
+ else {
108
+ const current = existing.deletedAt ? await stack.undelete(existing.id) : existing;
109
+ install = await stack.patchContent(existing.id, { name: manifest.name, version: manifest.version ?? null, defines, requests }, { ifVersion: current.version });
110
+ if (!linkedIds(install, INSTALL_APP_LABEL).includes(card.id)) {
111
+ install = await stack.associate(install.id, [installAppLink(card.id)]);
112
+ }
113
+ }
114
+ return (await reconcileInstallGrants(stack, install));
115
+ }
116
+ export async function uninstallApp(stack, appId) {
117
+ const install = (await loadInstallRecords(stack)).find((r) => r.content.appId === appId && !r.deletedAt);
118
+ if (!install)
119
+ throw new StackNotFoundError(`No install for "${appId}"`);
120
+ const edits = [];
121
+ for (const id of linkedIds(install, INSTALL_GRANT_LABEL)) {
122
+ if (await stack.get(id))
123
+ await stack.delete(id);
124
+ edits.push({ op: 'remove', association: installGrantLink(id) });
125
+ }
126
+ if (edits.length > 0)
127
+ await stack.amendAssociations(install.id, edits);
128
+ const { record } = await stack.deleteAndReturn(install.id);
129
+ return record;
130
+ }
131
+ /** A manifest's own shape, and every request held to grantType()'s rules. */
132
+ function checkManifest(manifest, did) {
133
+ const errors = [];
134
+ if (typeof did !== 'string' || !did.startsWith('did:')) {
135
+ errors.push({ path: 'did', message: 'Expected a DID' });
136
+ }
137
+ if (typeof manifest.appId !== 'string' || manifest.appId === '') {
138
+ errors.push({ path: 'appId', message: 'Expected a non-empty appId' });
139
+ }
140
+ if (typeof manifest.name !== 'string' || manifest.name === '') {
141
+ errors.push({ path: 'name', message: 'Expected a non-empty name' });
142
+ }
143
+ const seen = new Set();
144
+ manifest.types.forEach((t, i) => {
145
+ if (seen.has(t.id)) {
146
+ errors.push({ path: `types[${i}].id`, message: `"${t.id}" is listed more than once` });
147
+ }
148
+ seen.add(t.id);
149
+ const schemaPath = `types[${i}].schema`;
150
+ const shapeErrors = validateSchemaShape(t.schema, schemaPath);
151
+ errors.push(...shapeErrors);
152
+ if (shapeErrors.length === 0) {
153
+ const schema = t.schema;
154
+ for (const e of validateSchemaReservedNames(schema)) {
155
+ errors.push({ ...e, path: `${schemaPath}.${e.path}` });
156
+ }
157
+ validateSchemaFieldNames(schema, schemaPath, errors);
158
+ }
159
+ const parsed = parseTypeId(t.id);
160
+ if (!parsed) {
161
+ errors.push({ path: `types[${i}].id`, message: 'Expected a versioned TypeId' });
162
+ }
163
+ else {
164
+ const standing = familyStanding(parsed.baseId, manifest.appId);
165
+ if (standing === 'system' || standing === 'foreign') {
166
+ errors.push({
167
+ path: `types[${i}].id`,
168
+ message: standing === 'system'
169
+ ? `"${parsed.baseId}" is a system type; no app can define it`
170
+ : `"${parsed.baseId}" is outside the namespace "${manifest.appId}"; request access to it instead of defining it`,
171
+ });
172
+ }
173
+ }
174
+ });
175
+ if (errors.length > 0)
176
+ throw new StackValidationError(errors, ARGUMENTS_INVALID);
177
+ for (const r of manifest.requests)
178
+ checkGrantValid(r.baseId, r.actions);
179
+ }
180
+ /** The key's `_app` card, created or undeleted as needed. */
181
+ async function ensureAppCard(stack, manifest, did) {
182
+ const card = await findAppCardByDid(stack, did);
183
+ if (!card) {
184
+ return stack.create(`${SYSTEM_TYPES.APP}@1`, {
185
+ appId: manifest.appId,
186
+ name: manifest.name,
187
+ ...(manifest.version !== undefined && { version: manifest.version }),
188
+ did,
189
+ });
190
+ }
191
+ return card.deletedAt ? stack.undelete(card.id) : card;
192
+ }
193
+ /**
194
+ * Make the grants linked to `install` exactly its `requests`, once per
195
+ * live linked key: withdraw any that no longer match, write any missing,
196
+ * and drop links to grants that are gone.
197
+ */
198
+ async function reconcileInstallGrants(stack, install) {
199
+ const { requests } = install.content;
200
+ const dids = [];
201
+ for (const id of linkedIds(install, INSTALL_APP_LABEL)) {
202
+ const did = (await stack.get(id))?.content?.did;
203
+ if (typeof did === 'string')
204
+ dids.push(did);
205
+ }
206
+ const edits = [];
207
+ const held = [];
208
+ for (const id of linkedIds(install, INSTALL_GRANT_LABEL)) {
209
+ const grant = await stack.get(id);
210
+ const content = grant?.content;
211
+ const wanted = content !== undefined &&
212
+ baseIdOf(grant.typeId) === SYSTEM_TYPES.GRANT &&
213
+ dids.some((did) => requests.some((r) => grantIsRequest(content, r, did)));
214
+ if (wanted) {
215
+ held.push(content);
216
+ continue;
217
+ }
218
+ if (grant)
219
+ await stack.delete(id);
220
+ edits.push({ op: 'remove', association: installGrantLink(id) });
221
+ }
222
+ for (const did of dids) {
223
+ for (const r of requests) {
224
+ if (held.some((g) => grantIsRequest(g, r, did)))
225
+ continue;
226
+ const grant = await stack.grantType(r.baseId, {
227
+ actions: r.actions,
228
+ grantee: { kind: 'entity', entityId: did },
229
+ });
230
+ held.push(grant.content);
231
+ edits.push({ op: 'add', association: installGrantLink(grant.id) });
232
+ }
233
+ }
234
+ let result = edits.length > 0 ? await stack.amendAssociations(install.id, edits) : install;
235
+ // Each linked key may read its own install, which is how an app learns
236
+ // what was approved; a key of this app no longer linked may not.
237
+ // See docs/spec/apps.md § Over the wire.
238
+ const appKeys = new Set((await queryAllPages((q) => stack.query(q), {
239
+ filter: { baseId: SYSTEM_TYPES.APP, includeDeleted: true, includeUnlisted: true },
240
+ }))
241
+ .map((r) => r.content)
242
+ .filter((c) => c.appId === install.content.appId)
243
+ .map((c) => c.did));
244
+ const readerOf = (p) => p.kind === 'permission' && p.label === 'read' && p.grantee.kind === 'entity'
245
+ ? p.grantee.entityId
246
+ : null;
247
+ const current = (result.permissions ?? []).map(readerOf);
248
+ const access = [
249
+ ...dids
250
+ .filter((did) => !current.includes(did))
251
+ .map((entityId) => ({ op: 'add', association: installReader(entityId) })),
252
+ ...current
253
+ .filter((did) => did !== null && appKeys.has(did) && !dids.includes(did))
254
+ .map((entityId) => ({ op: 'remove', association: installReader(entityId) })),
255
+ ];
256
+ if (access.length > 0)
257
+ result = await stack.amendAccess(install.id, access);
258
+ return result;
259
+ }
260
+ //# sourceMappingURL=install-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-app.js","sourceRoot":"","sources":["../../src/stack/install-app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Write invariants
3
+ * -------------------------------------------------------
4
+ * The integrity rules a Stack write owes whoever makes it — DID and appId
5
+ * bindings, containment, attachment metadata, `_config` ownership and a
6
+ * group's admin roster. They bind every requester, the stack owner
7
+ * included, which is why they live beneath `ScopedStack`'s permission
8
+ * gates rather than among them. Each reads unscoped: through the Stack it
9
+ * is handed, or a by-id `read` straight from its adapter.
10
+ */
11
+ import type { RecordResolver } from '../access.js';
12
+ import type { Association, AttachmentContent, DataAssociation, EntityId, RecordId, StackRecord, TypeId } from '../types/index.js';
13
+ import type { Stack } from './stack.js';
14
+ /** Uniqueness for every unique binding field a newly created card claims. */
15
+ export declare function checkBindingsOnCreate(stack: Stack, typeId: TypeId, content: Record<string, unknown>): Promise<void>;
16
+ /**
17
+ * Immutability for every binding field a patch touches, then uniqueness
18
+ * for the subset that carries it. Fields absent from the patch carry no
19
+ * new claim — a content patch is a merge, so an untouched binding is the one the
20
+ * card already holds.
21
+ */
22
+ export declare function checkBindingsOnUpdate(stack: Stack, typeId: TypeId, id: RecordId, patch: Record<string, unknown | null>, existing: Record<string, unknown>, merged: Record<string, unknown>): Promise<void>;
23
+ /**
24
+ * Bindings across a migration, whose content replaces the old wholesale.
25
+ * Immutability is asked over both families' binding fields, so a card can
26
+ * neither shed a DID by migrating out nor pick one up on the way in;
27
+ * uniqueness, of the destination only. See docs/spec/identity.md § DID bindings.
28
+ */
29
+ export declare function checkBindingsOnMigrate(stack: Stack, fromTypeId: TypeId, toTypeId: TypeId, id: RecordId, existing: Record<string, unknown>, content: Record<string, unknown>): Promise<void>;
30
+ /**
31
+ * A binding is permanent once made: uniqueness stops a second card claiming
32
+ * a value, but only immutability stops an existing card being moved onto
33
+ * one. A subject whose key changes gets a new card.
34
+ * See docs/spec/identity.md § DID bindings.
35
+ */
36
+ export declare function checkBindingImmutable(family: string, field: 'did' | 'appId', existing: unknown, next: unknown): void;
37
+ /**
38
+ * A caller-named `parentId` is a well-formed id of a record that exists —
39
+ * format first, so a malformed one is a 400 naming the problem. A restore
40
+ * names no destination and does not ask this.
41
+ * See docs/spec/data-model.md § Reparenting.
42
+ */
43
+ export declare function assertParentExists(read: RecordResolver, id: string, parentId: string): Promise<void>;
44
+ /**
45
+ * Refuse an edge that would make a record its own ancestor. Walks with an
46
+ * unscoped `read`, since links a requester cannot read could otherwise
47
+ * close a cycle. Read-then-write, so racing moves can both pass.
48
+ * See docs/spec/data-model.md § Reparenting.
49
+ */
50
+ export declare function assertNoParentCycle(read: RecordResolver, id: string, parentId: string): Promise<void>;
51
+ /**
52
+ * The `_attachment@1` fields a write may not move, each with the message it
53
+ * is refused by. Ordered as reported. See docs/spec/attachments.md § The
54
+ * `_attachment` record type.
55
+ */
56
+ declare const ATTACHMENT_IMMUTABLE_FIELDS: readonly [readonly ["mimeType", "mimeType is immutable after creation; delete and re-upload to change it"], readonly ["fileId", "fileId is immutable"], readonly ["size", "size is immutable"]];
57
+ type AttachmentImmutableField = (typeof ATTACHMENT_IMMUTABLE_FIELDS)[number][0];
58
+ /**
59
+ * mimeType belongs to the fileId: its first metadata record fixes it, and
60
+ * a conflicting later one is refused. Best-effort against a racing first
61
+ * upload; what survives the race is a deterministic winner. See
62
+ * docs/spec/attachments.md § The `_attachment` record type.
63
+ */
64
+ export declare function checkAttachmentMimeTypeOnCreate(stack: Stack, content: AttachmentContent): Promise<void>;
65
+ /**
66
+ * An `attachmentRecordId` must name an `_attachment` record for the same
67
+ * `fileId`. Pointers already in `stored` are not re-asked, so restating an
68
+ * association never fails for a record deleted since.
69
+ * See docs/spec/attachments.md § Naming the upload a reference came from.
70
+ */
71
+ export declare function checkAttachmentAssociationPointers(read: RecordResolver, associations: Association[] | undefined, stored?: Association[]): Promise<void>;
72
+ /**
73
+ * Refuse a write that moves an `_attachment@1` record's fileId, size or
74
+ * mimeType. `violates` decides what counts as a move, since a patch names
75
+ * only what it changes while a migration re-sends all three.
76
+ * See docs/spec/attachments.md § The `_attachment` record type.
77
+ */
78
+ export declare function assertAttachmentImmutable(violates: (field: AttachmentImmutableField) => boolean): void;
79
+ /**
80
+ * `_config.entityId` defines stack ownership; neither patchContent() nor
81
+ * restoreVersion() may change it. A conflict with stack integrity, not a
82
+ * schema violation — hence StackConflictError. See docs/spec.md § The
83
+ * `_config` record.
84
+ */
85
+ export declare function checkConfigEntityIdUnchanged(existingEntityId: EntityId, newEntityId: EntityId): void;
86
+ /**
87
+ * Refuse a write that would leave a `_group` Record with no `admin`, asked
88
+ * of the roster the write would produce. An integrity rule, not a
89
+ * permission, so it binds the stack owner too.
90
+ * See docs/spec/identity.md § Group.
91
+ */
92
+ export declare function assertGroupAdminRemains(record: StackRecord, next: DataAssociation[]): void;
93
+ export {};
94
+ //# sourceMappingURL=integrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity.d.ts","sourceRoot":"","sources":["../../src/stack/integrity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AASnD,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,MAAM,EACP,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMxC,6EAA6E;AAC7E,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,EACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,QAAQ,EACZ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAgBf;AAkCD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,GAAG,OAAO,EACtB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,OAAO,GACZ,IAAI,CASN;AAcD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAgBf;AAMD;;;;GAIG;AACH,QAAA,MAAM,2BAA2B,iMAIvB,CAAC;AAEX,KAAK,wBAAwB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhF;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;;;;GAKG;AACH,wBAAsB,kCAAkC,CACtD,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,MAAM,GAAE,WAAW,EAAO,GACzB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,OAAO,GACrD,IAAI,CAON;AAMD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,gBAAgB,EAAE,QAAQ,EAC1B,WAAW,EAAE,QAAQ,GACpB,IAAI,CAON;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,IAAI,CAO1F"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Write invariants
3
+ * -------------------------------------------------------
4
+ * The integrity rules a Stack write owes whoever makes it — DID and appId
5
+ * bindings, containment, attachment metadata, `_config` ownership and a
6
+ * group's admin roster. They bind every requester, the stack owner
7
+ * included, which is why they live beneath `ScopedStack`'s permission
8
+ * gates rather than among them. Each reads unscoped: through the Stack it
9
+ * is handed, or a by-id `read` straight from its adapter.
10
+ */
11
+ import { hasGroupAdmin, isGroupRecord } from '../access.js';
12
+ import { ARGUMENTS_INVALID, StackConflictError, StackValidationError } from '../errors.js';
13
+ import { bindingFieldsOf, uniqueBindingFieldsOf } from './identity-bindings.js';
14
+ import { filtersContent } from '../query-validation.js';
15
+ import { associationIdentical } from '../associations/identity.js';
16
+ import { validateRecordId } from './record-id.js';
17
+ import { baseIdOf } from '../schema.js';
18
+ import { findFirstMatch } from './reads.js';
19
+ import { SYSTEM_TYPES } from '../types/index.js';
20
+ // -------------------------------------------------------
21
+ // DID and appId bindings
22
+ // -------------------------------------------------------
23
+ /** Uniqueness for every unique binding field a newly created card claims. */
24
+ export async function checkBindingsOnCreate(stack, typeId, content) {
25
+ const family = baseIdOf(typeId);
26
+ for (const field of uniqueBindingFieldsOf(family)) {
27
+ await checkBindingUnique(stack, family, field, content[field]);
28
+ }
29
+ }
30
+ /**
31
+ * Immutability for every binding field a patch touches, then uniqueness
32
+ * for the subset that carries it. Fields absent from the patch carry no
33
+ * new claim — a content patch is a merge, so an untouched binding is the one the
34
+ * card already holds.
35
+ */
36
+ export async function checkBindingsOnUpdate(stack, typeId, id, patch, existing, merged) {
37
+ const family = baseIdOf(typeId);
38
+ const unique = uniqueBindingFieldsOf(family);
39
+ for (const field of bindingFieldsOf(family)) {
40
+ if (!(field in patch))
41
+ continue;
42
+ checkBindingImmutable(family, field, existing[field], merged[field]);
43
+ if (unique.includes(field)) {
44
+ await checkBindingUnique(stack, family, field, merged[field], id);
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Bindings across a migration, whose content replaces the old wholesale.
50
+ * Immutability is asked over both families' binding fields, so a card can
51
+ * neither shed a DID by migrating out nor pick one up on the way in;
52
+ * uniqueness, of the destination only. See docs/spec/identity.md § DID bindings.
53
+ */
54
+ export async function checkBindingsOnMigrate(stack, fromTypeId, toTypeId, id, existing, content) {
55
+ const fromFamily = baseIdOf(fromTypeId);
56
+ const toFamily = baseIdOf(toTypeId);
57
+ const checked = new Set();
58
+ for (const family of fromFamily === toFamily ? [fromFamily] : [fromFamily, toFamily]) {
59
+ for (const field of bindingFieldsOf(family)) {
60
+ if (checked.has(field))
61
+ continue;
62
+ checked.add(field);
63
+ checkBindingImmutable(family, field, existing[field], content[field]);
64
+ }
65
+ }
66
+ for (const field of uniqueBindingFieldsOf(toFamily)) {
67
+ await checkBindingUnique(stack, toFamily, field, content[field], id);
68
+ }
69
+ }
70
+ /**
71
+ * Refuse a second card claiming a value an Actor is resolved by. A
72
+ * property of the set rather than the value, so no schema can say it.
73
+ * Read-then-write: two racing creates can both pass.
74
+ * See docs/spec/identity.md § DID bindings.
75
+ */
76
+ async function checkBindingUnique(stack, family, field, value, excludeId) {
77
+ if (typeof value !== 'string' || value === '')
78
+ return;
79
+ const clash = await findFirstMatch((q) => stack.query(q), {
80
+ filter: {
81
+ baseId: family,
82
+ includeDeleted: true,
83
+ includeUnlisted: true,
84
+ ...(filtersContent(stack.capabilities) && { content: { [field]: value } }),
85
+ },
86
+ }, (r) => r.id !== excludeId && r.content[field] === value);
87
+ if (clash) {
88
+ throw new StackConflictError(`Another ${family} record already claims the ${field} "${value}"`);
89
+ }
90
+ }
91
+ /**
92
+ * A binding is permanent once made: uniqueness stops a second card claiming
93
+ * a value, but only immutability stops an existing card being moved onto
94
+ * one. A subject whose key changes gets a new card.
95
+ * See docs/spec/identity.md § DID bindings.
96
+ */
97
+ export function checkBindingImmutable(family, field, existing, next) {
98
+ if (typeof existing !== 'string' || existing === '')
99
+ return;
100
+ if (next === existing)
101
+ return;
102
+ throw new StackValidationError([
103
+ {
104
+ path: field,
105
+ message: `${field} is immutable once set; register a new ${family} record instead`,
106
+ },
107
+ ]);
108
+ }
109
+ // -------------------------------------------------------
110
+ // Containment
111
+ // -------------------------------------------------------
112
+ /**
113
+ * How far a change set's `parentId` will walk a proposed ancestor chain before refusing
114
+ * the move. Bounds the reads one call can cost; a hierarchy deeper than
115
+ * this is beyond what `parentId` is for. See docs/spec/data-model.md
116
+ * § Reparenting.
117
+ */
118
+ const MAX_PARENT_DEPTH = 64;
119
+ /**
120
+ * A caller-named `parentId` is a well-formed id of a record that exists —
121
+ * format first, so a malformed one is a 400 naming the problem. A restore
122
+ * names no destination and does not ask this.
123
+ * See docs/spec/data-model.md § Reparenting.
124
+ */
125
+ export async function assertParentExists(read, id, parentId) {
126
+ validateRecordId(parentId, 'parentId');
127
+ if (!(await read(parentId))) {
128
+ throw new StackConflictError(`Cannot parent record "${id}" to "${parentId}": no such record. A container has to ` +
129
+ 'exist when it is named.');
130
+ }
131
+ }
132
+ /**
133
+ * Refuse an edge that would make a record its own ancestor. Walks with an
134
+ * unscoped `read`, since links a requester cannot read could otherwise
135
+ * close a cycle. Read-then-write, so racing moves can both pass.
136
+ * See docs/spec/data-model.md § Reparenting.
137
+ */
138
+ export async function assertNoParentCycle(read, id, parentId) {
139
+ let cursor = parentId;
140
+ for (let depth = 0; cursor !== undefined; depth++) {
141
+ if (cursor === id) {
142
+ throw new StackConflictError(`Cannot parent record "${id}" to "${parentId}": it is a descendant of "${id}", ` +
143
+ 'and the move would make the record its own ancestor.');
144
+ }
145
+ // The cap bounds work, and guarantees this terminates on a chain that
146
+ // is already cyclic. It does not refuse the move: a chain this long is
147
+ // past what the check can speak to, not evidence of a loop, and
148
+ // refusing it would claim an invariant core does not maintain.
149
+ if (depth >= MAX_PARENT_DEPTH)
150
+ return;
151
+ cursor = (await read(cursor))?.parentId;
152
+ }
153
+ }
154
+ // -------------------------------------------------------
155
+ // Attachments
156
+ // -------------------------------------------------------
157
+ /**
158
+ * The `_attachment@1` fields a write may not move, each with the message it
159
+ * is refused by. Ordered as reported. See docs/spec/attachments.md § The
160
+ * `_attachment` record type.
161
+ */
162
+ const ATTACHMENT_IMMUTABLE_FIELDS = [
163
+ ['mimeType', 'mimeType is immutable after creation; delete and re-upload to change it'],
164
+ ['fileId', 'fileId is immutable'],
165
+ ['size', 'size is immutable'],
166
+ ];
167
+ /**
168
+ * mimeType belongs to the fileId: its first metadata record fixes it, and
169
+ * a conflicting later one is refused. Best-effort against a racing first
170
+ * upload; what survives the race is a deterministic winner. See
171
+ * docs/spec/attachments.md § The `_attachment` record type.
172
+ */
173
+ export async function checkAttachmentMimeTypeOnCreate(stack, content) {
174
+ const { fileId, mimeType } = content;
175
+ if (typeof fileId !== 'string')
176
+ return; // schema validation already rejected this
177
+ const existing = await stack.getAttachmentRecords(fileId);
178
+ if (existing.length === 0)
179
+ return;
180
+ const establishedMimeType = existing[0].content.mimeType;
181
+ if (mimeType !== establishedMimeType) {
182
+ // Deliberately does not name the established mimeType — that would
183
+ // confirm a guessed fileId's content type. See the anti-oracle rule
184
+ // in docs/spec/attachments.md.
185
+ throw new StackValidationError([
186
+ {
187
+ path: 'mimeType',
188
+ message: 'mimeType conflicts with the mimeType already established for this fileId',
189
+ },
190
+ ]);
191
+ }
192
+ }
193
+ /**
194
+ * An `attachmentRecordId` must name an `_attachment` record for the same
195
+ * `fileId`. Pointers already in `stored` are not re-asked, so restating an
196
+ * association never fails for a record deleted since.
197
+ * See docs/spec/attachments.md § Naming the upload a reference came from.
198
+ */
199
+ export async function checkAttachmentAssociationPointers(read, associations, stored = []) {
200
+ const pointed = (associations ?? []).flatMap((association) => association.kind === 'attachment' &&
201
+ association.attachmentRecordId !== undefined &&
202
+ !stored.some((a) => associationIdentical(a, association))
203
+ ? [{ fileId: association.fileId, attachmentRecordId: association.attachmentRecordId }]
204
+ : []);
205
+ // One round trip per pointer, taken together: a change set carries a
206
+ // whole association list, and each pointer is an independent read.
207
+ const named = await Promise.all(pointed.map(({ attachmentRecordId }) => read(attachmentRecordId)));
208
+ pointed.forEach(({ fileId }, i) => {
209
+ const record = named[i];
210
+ const content = record?.content;
211
+ if (record &&
212
+ baseIdOf(record.typeId) === SYSTEM_TYPES.ATTACHMENT &&
213
+ content?.fileId === fileId) {
214
+ return;
215
+ }
216
+ // One message for every way of failing, so this is no existence oracle
217
+ // for records the caller cannot read. See docs/spec/attachments.md
218
+ // § Creating `_attachment@1` records directly.
219
+ throw new StackValidationError([
220
+ {
221
+ path: 'attachmentRecordId',
222
+ message: 'attachmentRecordId must name an `_attachment` record for this fileId',
223
+ },
224
+ ], ARGUMENTS_INVALID);
225
+ });
226
+ }
227
+ /**
228
+ * Refuse a write that moves an `_attachment@1` record's fileId, size or
229
+ * mimeType. `violates` decides what counts as a move, since a patch names
230
+ * only what it changes while a migration re-sends all three.
231
+ * See docs/spec/attachments.md § The `_attachment` record type.
232
+ */
233
+ export function assertAttachmentImmutable(violates) {
234
+ const errors = ATTACHMENT_IMMUTABLE_FIELDS.filter(([field]) => violates(field)).map(([path, message]) => ({ path, message }));
235
+ if (errors.length > 0) {
236
+ throw new StackValidationError(errors);
237
+ }
238
+ }
239
+ // -------------------------------------------------------
240
+ // Stack identity and groups
241
+ // -------------------------------------------------------
242
+ /**
243
+ * `_config.entityId` defines stack ownership; neither patchContent() nor
244
+ * restoreVersion() may change it. A conflict with stack integrity, not a
245
+ * schema violation — hence StackConflictError. See docs/spec.md § The
246
+ * `_config` record.
247
+ */
248
+ export function checkConfigEntityIdUnchanged(existingEntityId, newEntityId) {
249
+ if (newEntityId !== existingEntityId) {
250
+ throw new StackConflictError('Cannot change _config.entityId: it defines stack ownership. ' +
251
+ 'Ownership transfer is not a supported operation.');
252
+ }
253
+ }
254
+ /**
255
+ * Refuse a write that would leave a `_group` Record with no `admin`, asked
256
+ * of the roster the write would produce. An integrity rule, not a
257
+ * permission, so it binds the stack owner too.
258
+ * See docs/spec/identity.md § Group.
259
+ */
260
+ export function assertGroupAdminRemains(record, next) {
261
+ if (!isGroupRecord(record))
262
+ return;
263
+ if (hasGroupAdmin(next))
264
+ return;
265
+ throw new StackConflictError(`Cannot leave group "${record.id}" without an admin: a _group record's roster keeps at ` +
266
+ 'least one `admin` relationship association. Name the incoming admin in the same write.');
267
+ }
268
+ //# sourceMappingURL=integrity.js.map