@graview/core 0.1.0 → 0.1.2

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 (292) hide show
  1. package/README.md +7 -0
  2. package/dist/app.d.ts +17 -0
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js.map +1 -1
  5. package/dist/arrange.d.ts +3 -2
  6. package/dist/arrange.d.ts.map +1 -1
  7. package/dist/arrange.js +19 -2
  8. package/dist/arrange.js.map +1 -1
  9. package/dist/beginning.d.ts +5 -2
  10. package/dist/beginning.d.ts.map +1 -1
  11. package/dist/beginning.js +28 -0
  12. package/dist/beginning.js.map +1 -1
  13. package/dist/capabilities.d.ts +32 -0
  14. package/dist/capabilities.d.ts.map +1 -0
  15. package/dist/capabilities.js +31 -0
  16. package/dist/capabilities.js.map +1 -0
  17. package/dist/cli/check/index.d.ts +1 -0
  18. package/dist/cli/check/index.d.ts.map +1 -1
  19. package/dist/cli/check/index.js +1 -0
  20. package/dist/cli/check/index.js.map +1 -1
  21. package/dist/cli/check/lenses.d.ts.map +1 -1
  22. package/dist/cli/check/lenses.js +25 -0
  23. package/dist/cli/check/lenses.js.map +1 -1
  24. package/dist/cli/check/policy.d.ts.map +1 -1
  25. package/dist/cli/check/policy.js +35 -0
  26. package/dist/cli/check/policy.js.map +1 -1
  27. package/dist/cli/check/relations.d.ts.map +1 -1
  28. package/dist/cli/check/relations.js +31 -0
  29. package/dist/cli/check/relations.js.map +1 -1
  30. package/dist/cli/check/views.d.ts +14 -0
  31. package/dist/cli/check/views.d.ts.map +1 -0
  32. package/dist/cli/check/views.js +27 -0
  33. package/dist/cli/check/views.js.map +1 -0
  34. package/dist/cli/check.d.ts.map +1 -1
  35. package/dist/cli/check.js +2 -1
  36. package/dist/cli/check.js.map +1 -1
  37. package/dist/cli/create.d.ts +1 -1
  38. package/dist/cli/create.d.ts.map +1 -1
  39. package/dist/cli/create.js +63 -1
  40. package/dist/cli/create.js.map +1 -1
  41. package/dist/cli/index.d.ts +18 -1
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +66 -1
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/conformance/fixtures.d.ts +41 -0
  46. package/dist/conformance/fixtures.d.ts.map +1 -0
  47. package/dist/conformance/fixtures.js +950 -0
  48. package/dist/conformance/fixtures.js.map +1 -0
  49. package/dist/conformance/index.d.ts +54 -0
  50. package/dist/conformance/index.d.ts.map +1 -0
  51. package/dist/conformance/index.js +81 -0
  52. package/dist/conformance/index.js.map +1 -0
  53. package/dist/document/canonical.d.ts +4 -0
  54. package/dist/document/canonical.d.ts.map +1 -0
  55. package/dist/document/canonical.js +29 -0
  56. package/dist/document/canonical.js.map +1 -0
  57. package/dist/document/compile.d.ts +43 -0
  58. package/dist/document/compile.d.ts.map +1 -0
  59. package/dist/document/compile.js +787 -0
  60. package/dist/document/compile.js.map +1 -0
  61. package/dist/document/diff.d.ts +32 -0
  62. package/dist/document/diff.d.ts.map +1 -0
  63. package/dist/document/diff.js +143 -0
  64. package/dist/document/diff.js.map +1 -0
  65. package/dist/document/edit.d.ts +63 -0
  66. package/dist/document/edit.d.ts.map +1 -0
  67. package/dist/document/edit.js +1420 -0
  68. package/dist/document/edit.js.map +1 -0
  69. package/dist/document/expr/analyze.d.ts +14 -0
  70. package/dist/document/expr/analyze.d.ts.map +1 -0
  71. package/dist/document/expr/analyze.js +52 -0
  72. package/dist/document/expr/analyze.js.map +1 -0
  73. package/dist/document/expr/evaluate.d.ts +36 -0
  74. package/dist/document/expr/evaluate.d.ts.map +1 -0
  75. package/dist/document/expr/evaluate.js +344 -0
  76. package/dist/document/expr/evaluate.js.map +1 -0
  77. package/dist/document/expr/index.d.ts +8 -0
  78. package/dist/document/expr/index.d.ts.map +1 -0
  79. package/dist/document/expr/index.js +5 -0
  80. package/dist/document/expr/index.js.map +1 -0
  81. package/dist/document/expr/parse.d.ts +50 -0
  82. package/dist/document/expr/parse.d.ts.map +1 -0
  83. package/dist/document/expr/parse.js +240 -0
  84. package/dist/document/expr/parse.js.map +1 -0
  85. package/dist/document/expr/print.d.ts +4 -0
  86. package/dist/document/expr/print.d.ts.map +1 -0
  87. package/dist/document/expr/print.js +81 -0
  88. package/dist/document/expr/print.js.map +1 -0
  89. package/dist/document/findings.d.ts +15 -0
  90. package/dist/document/findings.d.ts.map +1 -0
  91. package/dist/document/findings.js +30 -0
  92. package/dist/document/findings.js.map +1 -0
  93. package/dist/document/graview-template.d.ts +148 -0
  94. package/dist/document/graview-template.d.ts.map +1 -0
  95. package/dist/document/graview-template.js +450 -0
  96. package/dist/document/graview-template.js.map +1 -0
  97. package/dist/document/index.d.ts +27 -0
  98. package/dist/document/index.d.ts.map +1 -0
  99. package/dist/document/index.js +16 -0
  100. package/dist/document/index.js.map +1 -0
  101. package/dist/document/migrate.d.ts +41 -0
  102. package/dist/document/migrate.d.ts.map +1 -0
  103. package/dist/document/migrate.js +252 -0
  104. package/dist/document/migrate.js.map +1 -0
  105. package/dist/document/patch.d.ts +16 -0
  106. package/dist/document/patch.d.ts.map +1 -0
  107. package/dist/document/patch.js +79 -0
  108. package/dist/document/patch.js.map +1 -0
  109. package/dist/document/rules.d.ts +40 -0
  110. package/dist/document/rules.d.ts.map +1 -0
  111. package/dist/document/rules.js +88 -0
  112. package/dist/document/rules.js.map +1 -0
  113. package/dist/document/schema.d.ts +459 -0
  114. package/dist/document/schema.d.ts.map +1 -0
  115. package/dist/document/schema.js +197 -0
  116. package/dist/document/schema.js.map +1 -0
  117. package/dist/document/template.d.ts +28 -0
  118. package/dist/document/template.d.ts.map +1 -0
  119. package/dist/document/template.js +117 -0
  120. package/dist/document/template.js.map +1 -0
  121. package/dist/document/to-document.d.ts +34 -0
  122. package/dist/document/to-document.d.ts.map +1 -0
  123. package/dist/document/to-document.js +182 -0
  124. package/dist/document/to-document.js.map +1 -0
  125. package/dist/document/upgrade.d.ts +9 -0
  126. package/dist/document/upgrade.d.ts.map +1 -0
  127. package/dist/document/upgrade.js +19 -0
  128. package/dist/document/upgrade.js.map +1 -0
  129. package/dist/document/views.d.ts +65 -0
  130. package/dist/document/views.d.ts.map +1 -0
  131. package/dist/document/views.js +264 -0
  132. package/dist/document/views.js.map +1 -0
  133. package/dist/formats.d.ts +50 -0
  134. package/dist/formats.d.ts.map +1 -0
  135. package/dist/formats.js +75 -0
  136. package/dist/formats.js.map +1 -0
  137. package/dist/graph/graph.d.ts +52 -3
  138. package/dist/graph/graph.d.ts.map +1 -1
  139. package/dist/graph/graph.js +169 -16
  140. package/dist/graph/graph.js.map +1 -1
  141. package/dist/index.d.ts +40 -17
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +29 -10
  144. package/dist/index.js.map +1 -1
  145. package/dist/integrity.d.ts +46 -0
  146. package/dist/integrity.d.ts.map +1 -0
  147. package/dist/integrity.js +179 -0
  148. package/dist/integrity.js.map +1 -0
  149. package/dist/invariants/engine.d.ts +8 -0
  150. package/dist/invariants/engine.d.ts.map +1 -1
  151. package/dist/invariants/engine.js +40 -2
  152. package/dist/invariants/engine.js.map +1 -1
  153. package/dist/invariants/types.d.ts +19 -0
  154. package/dist/invariants/types.d.ts.map +1 -1
  155. package/dist/labels.d.ts +65 -0
  156. package/dist/labels.d.ts.map +1 -0
  157. package/dist/labels.js +123 -0
  158. package/dist/labels.js.map +1 -0
  159. package/dist/mutations/define-mutation.d.ts.map +1 -1
  160. package/dist/mutations/define-mutation.js +2 -3
  161. package/dist/mutations/define-mutation.js.map +1 -1
  162. package/dist/mutations/derive-edits.d.ts.map +1 -1
  163. package/dist/mutations/derive-edits.js +20 -1
  164. package/dist/mutations/derive-edits.js.map +1 -1
  165. package/dist/mutations/form.d.ts +9 -0
  166. package/dist/mutations/form.d.ts.map +1 -1
  167. package/dist/mutations/form.js +20 -0
  168. package/dist/mutations/form.js.map +1 -1
  169. package/dist/mutations/node-ref.d.ts +6 -0
  170. package/dist/mutations/node-ref.d.ts.map +1 -1
  171. package/dist/mutations/node-ref.js +22 -8
  172. package/dist/mutations/node-ref.js.map +1 -1
  173. package/dist/mutations/types.d.ts +8 -0
  174. package/dist/mutations/types.d.ts.map +1 -1
  175. package/dist/mutations/words.d.ts +49 -0
  176. package/dist/mutations/words.d.ts.map +1 -0
  177. package/dist/mutations/words.js +61 -0
  178. package/dist/mutations/words.js.map +1 -0
  179. package/dist/ops/log.d.ts +145 -4
  180. package/dist/ops/log.d.ts.map +1 -1
  181. package/dist/ops/log.js +220 -51
  182. package/dist/ops/log.js.map +1 -1
  183. package/dist/ops/revisions.d.ts +66 -0
  184. package/dist/ops/revisions.d.ts.map +1 -0
  185. package/dist/ops/revisions.js +97 -0
  186. package/dist/ops/revisions.js.map +1 -0
  187. package/dist/ops/types.d.ts +49 -1
  188. package/dist/ops/types.d.ts.map +1 -1
  189. package/dist/ops/undo.d.ts +23 -2
  190. package/dist/ops/undo.d.ts.map +1 -1
  191. package/dist/ops/undo.js +107 -1
  192. package/dist/ops/undo.js.map +1 -1
  193. package/dist/ops/withheld.d.ts +37 -0
  194. package/dist/ops/withheld.d.ts.map +1 -0
  195. package/dist/ops/withheld.js +84 -0
  196. package/dist/ops/withheld.js.map +1 -0
  197. package/dist/permissions/policy.d.ts +15 -0
  198. package/dist/permissions/policy.d.ts.map +1 -1
  199. package/dist/permissions/policy.js +28 -1
  200. package/dist/permissions/policy.js.map +1 -1
  201. package/dist/permissions/sight.d.ts +50 -0
  202. package/dist/permissions/sight.d.ts.map +1 -0
  203. package/dist/permissions/sight.js +76 -0
  204. package/dist/permissions/sight.js.map +1 -0
  205. package/dist/permissions/types.d.ts +26 -0
  206. package/dist/permissions/types.d.ts.map +1 -1
  207. package/dist/permissions/types.js.map +1 -1
  208. package/dist/persistence/memory.d.ts.map +1 -1
  209. package/dist/persistence/memory.js +25 -0
  210. package/dist/persistence/memory.js.map +1 -1
  211. package/dist/persistence/sql.d.ts +43 -0
  212. package/dist/persistence/sql.d.ts.map +1 -0
  213. package/dist/persistence/sql.js +154 -0
  214. package/dist/persistence/sql.js.map +1 -0
  215. package/dist/persistence/sqlite.d.ts +17 -3
  216. package/dist/persistence/sqlite.d.ts.map +1 -1
  217. package/dist/persistence/sqlite.js +32 -96
  218. package/dist/persistence/sqlite.js.map +1 -1
  219. package/dist/persistence/types.d.ts +19 -0
  220. package/dist/persistence/types.d.ts.map +1 -1
  221. package/dist/presence.d.ts +31 -0
  222. package/dist/presence.d.ts.map +1 -1
  223. package/dist/presence.js +36 -0
  224. package/dist/presence.js.map +1 -1
  225. package/dist/scaffold/from-template.d.ts +16 -0
  226. package/dist/scaffold/from-template.d.ts.map +1 -0
  227. package/dist/scaffold/from-template.js +157 -0
  228. package/dist/scaffold/from-template.js.map +1 -0
  229. package/dist/scaffold/index.d.ts +10 -1
  230. package/dist/scaffold/index.d.ts.map +1 -1
  231. package/dist/scaffold/index.js +35 -7
  232. package/dist/scaffold/index.js.map +1 -1
  233. package/dist/scaffold/names.d.ts +4 -0
  234. package/dist/scaffold/names.d.ts.map +1 -1
  235. package/dist/scaffold/names.js.map +1 -1
  236. package/dist/scaffold/project.d.ts +1 -1
  237. package/dist/scaffold/project.d.ts.map +1 -1
  238. package/dist/scaffold/project.js +44 -13
  239. package/dist/scaffold/project.js.map +1 -1
  240. package/dist/scaffold/ui.d.ts.map +1 -1
  241. package/dist/scaffold/ui.js +19 -10
  242. package/dist/scaffold/ui.js.map +1 -1
  243. package/dist/schema/define-node.d.ts +20 -0
  244. package/dist/schema/define-node.d.ts.map +1 -1
  245. package/dist/schema/define-node.js +27 -2
  246. package/dist/schema/define-node.js.map +1 -1
  247. package/dist/schema/path.d.ts +19 -0
  248. package/dist/schema/path.d.ts.map +1 -0
  249. package/dist/schema/path.js +34 -0
  250. package/dist/schema/path.js.map +1 -0
  251. package/dist/schema/types.d.ts +6 -0
  252. package/dist/schema/types.d.ts.map +1 -1
  253. package/dist/seen.d.ts +58 -0
  254. package/dist/seen.d.ts.map +1 -0
  255. package/dist/seen.js +194 -0
  256. package/dist/seen.js.map +1 -0
  257. package/dist/store.d.ts +317 -8
  258. package/dist/store.d.ts.map +1 -1
  259. package/dist/store.js +720 -37
  260. package/dist/store.js.map +1 -1
  261. package/dist/testing.d.ts +6 -0
  262. package/dist/testing.d.ts.map +1 -1
  263. package/dist/testing.js +3 -1
  264. package/dist/testing.js.map +1 -1
  265. package/dist/theme/palettes.d.ts.map +1 -1
  266. package/dist/theme/palettes.js +4 -0
  267. package/dist/theme/palettes.js.map +1 -1
  268. package/dist/theme/types.d.ts +9 -0
  269. package/dist/theme/types.d.ts.map +1 -1
  270. package/dist/theme/types.js +4 -0
  271. package/dist/theme/types.js.map +1 -1
  272. package/dist/validate-graph.d.ts +83 -0
  273. package/dist/validate-graph.d.ts.map +1 -0
  274. package/dist/validate-graph.js +288 -0
  275. package/dist/validate-graph.js.map +1 -0
  276. package/dist/version.d.ts +3 -0
  277. package/dist/version.d.ts.map +1 -0
  278. package/dist/version.js +3 -0
  279. package/dist/version.js.map +1 -0
  280. package/dist/views/types.d.ts +8 -0
  281. package/dist/views/types.d.ts.map +1 -1
  282. package/dist/views/types.js +5 -0
  283. package/dist/views/types.js.map +1 -1
  284. package/dist/watched.d.ts +39 -0
  285. package/dist/watched.d.ts.map +1 -1
  286. package/dist/watched.js +94 -1
  287. package/dist/watched.js.map +1 -1
  288. package/dist/who.d.ts +24 -1
  289. package/dist/who.d.ts.map +1 -1
  290. package/dist/who.js +49 -2
  291. package/dist/who.js.map +1 -1
  292. package/package.json +9 -1
@@ -0,0 +1,97 @@
1
+ /**
2
+ * WHICH OP LAST WROTE EACH FIELD (FR-05) — read off the log, never stored.
3
+ *
4
+ * A stale write is one made against a value somebody has changed since:
5
+ * two people open one record, both change its status, and whoever sends
6
+ * second used to win without knowing the first had spoken. To say so, a
7
+ * call carries the revision of each field it changes as its sender last
8
+ * saw it, and the store compares that with the field's revision now.
9
+ *
10
+ * A field's revision is the `seq` of the op that last wrote it: an op that
11
+ * added its record, or one that patched it. It is DERIVED from the log
12
+ * rather than kept beside it, so no stored format changes and every store,
13
+ * every client and every host that folds the same log agrees on it without
14
+ * being told. A field no op in the log has written (a seeded one, or one
15
+ * written before the log's first op) is at `NEVER_WRITTEN`.
16
+ */
17
+ export const NEVER_WRITTEN = -1;
18
+ const key = (node, field) => `${node}\u0000${field}`;
19
+ /** The fields one primitive writes, by record. */
20
+ export function fieldsWritten(primitive) {
21
+ if (primitive.op === "add-node") {
22
+ return Object.keys(primitive.node)
23
+ .filter((field) => field !== "id" && field !== "kind")
24
+ .map((field) => [primitive.node.id, field]);
25
+ }
26
+ if (primitive.op === "patch-node")
27
+ return Object.keys(primitive.after).map((field) => [primitive.id, field]);
28
+ return [];
29
+ }
30
+ /**
31
+ * The revision of every field a log has written, kept current as ops land.
32
+ * A revision only moves forward: an op noted twice, or out of order, leaves
33
+ * a later one standing.
34
+ */
35
+ export class FieldRevisions {
36
+ revs = new Map();
37
+ /** Revisions read from a whole log (or the part of one a seat may see). */
38
+ static of(ops) {
39
+ const revisions = new FieldRevisions();
40
+ revisions.note(ops);
41
+ return revisions;
42
+ }
43
+ /** Takes in ops as they land, by the seq each carries. */
44
+ note(ops) {
45
+ for (const op of ops) {
46
+ for (const primitive of op.primitives) {
47
+ for (const [node, field] of fieldsWritten(primitive)) {
48
+ const at = key(node, field);
49
+ if ((this.revs.get(at) ?? NEVER_WRITTEN) < op.seq)
50
+ this.revs.set(at, op.seq);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ /** The seq of the op that last wrote this field, or `NEVER_WRITTEN`. */
56
+ of(node, field) {
57
+ return this.revs.get(key(node, field)) ?? NEVER_WRITTEN;
58
+ }
59
+ /** The base a call made of these ops would carry: each field they patch on a record they did not add, at its revision here. */
60
+ baseFor(ops, skip = new Set()) {
61
+ const created = new Set();
62
+ const base = new Map();
63
+ for (const op of ops) {
64
+ for (const primitive of op.primitives) {
65
+ if (primitive.op === "add-node")
66
+ created.add(primitive.node.id);
67
+ if (primitive.op !== "patch-node" || created.has(primitive.id) || skip.has(primitive.id))
68
+ continue;
69
+ for (const field of Object.keys(primitive.after)) {
70
+ const at = key(primitive.id, field);
71
+ if (skip.has(at) || base.has(at))
72
+ continue;
73
+ base.set(at, { node: primitive.id, field, rev: this.of(primitive.id, field) });
74
+ }
75
+ }
76
+ }
77
+ return [...base.values()];
78
+ }
79
+ /** The base entries that no longer hold: each field written since its sender read it. */
80
+ stale(base) {
81
+ return base.filter((entry) => this.of(entry.node, entry.field) !== entry.rev);
82
+ }
83
+ }
84
+ /** Every record and every `record\0field` these ops write: what a later call need not claim to have seen. */
85
+ export function writtenBy(ops) {
86
+ const out = new Set();
87
+ for (const op of ops) {
88
+ for (const primitive of op.primitives) {
89
+ if (primitive.op === "add-node")
90
+ out.add(primitive.node.id);
91
+ for (const [node, field] of fieldsWritten(primitive))
92
+ out.add(key(node, field));
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ //# sourceMappingURL=revisions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revisions.js","sourceRoot":"","sources":["../../src/ops/revisions.ts"],"names":[],"mappings":"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"}
@@ -14,7 +14,25 @@ export interface Author {
14
14
  readonly kind: "human" | "agent" | "rule" | "system";
15
15
  readonly id?: string;
16
16
  readonly session?: string;
17
+ /**
18
+ * The author's own name, for a reader who has no record or seat to look
19
+ * it up in — another person in a hosted app, an agent from a chat. Said
20
+ * before any id is (FR-17).
21
+ */
22
+ readonly name?: string;
23
+ /**
24
+ * WHO THIS IS FOR. An agent acting for a person records both — "Claude,
25
+ * for Nick" — and is bounded by that person's roles as well as its own
26
+ * (FR-06).
27
+ */
28
+ readonly onBehalfOf?: Author;
17
29
  }
30
+ /**
31
+ * THE CHANNEL an op came through: `web` (a person at the interface),
32
+ * `mcp:<client>` (an agent's tool call), `view:<name>` (a view acting for
33
+ * its viewer), `api`, `cli`. A fact in the log, beside who and for whom.
34
+ */
35
+ export type Via = "web" | "api" | "cli" | `mcp:${string}` | `view:${string}` | (string & {});
18
36
  /**
19
37
  * One entry in the append-only log. The graph is a fold over these.
20
38
  *
@@ -29,8 +47,20 @@ export interface Operation {
29
47
  /** One user gesture, or one agent turn. */
30
48
  readonly batch: string;
31
49
  readonly author: Author;
32
- /** Human-readable statement of what was meant, not what changed. */
50
+ /**
51
+ * Human-readable statement of what was meant, not what changed: the act's
52
+ * own `describe()` sentence ("Add “Book the van”"), or "Undo: …".
53
+ */
33
54
  readonly intent: string;
55
+ /**
56
+ * What the whole gesture was for, when its caller said ("Plan the move"),
57
+ * on every op of the batch. Beside `intent`, never instead of it (FR-18):
58
+ * an op keeps its own sentence, so a blocked undo, the record's history
59
+ * and an audit still say what each op did, and the batch reads as what
60
+ * was meant. Absent when no intent was given, and the batch reads as its
61
+ * ops, as it always did.
62
+ */
63
+ readonly batchIntent?: string;
34
64
  readonly mutation: MutationCall | null;
35
65
  readonly primitives: readonly Primitive[];
36
66
  readonly inverse: readonly Primitive[];
@@ -39,6 +69,24 @@ export interface Operation {
39
69
  readonly at: string;
40
70
  /** Set when this op exists to undo another one. */
41
71
  readonly undoes?: string;
72
+ /** What the change came through, when the caller said (FR-06). */
73
+ readonly via?: Via;
74
+ /**
75
+ * SOMETHING YOU CANNOT SEE HAPPENED HERE (FR-16). Set on an op a store
76
+ * served to a seat that may not see what it touched: it keeps its id,
77
+ * seq, batch and time, so the log has no hole, and nothing else of its
78
+ * own — its author, intent, call and inverse are withheld, and its
79
+ * primitives, reads and writes keep only what the seat may see. Absent
80
+ * on every op a store makes; an op without it reads as before.
81
+ */
82
+ readonly withheld?: true;
83
+ /**
84
+ * THE MODULES THIS OP LEAVES ON (FR-12). Set only on an op authored
85
+ * `system · modules`, which turns modules off or on and touches no
86
+ * record: the store's enabled set is the one the last such op says. An
87
+ * op without it reads as before, and folds the same.
88
+ */
89
+ readonly enabledModules?: readonly string[];
42
90
  }
43
91
  export interface Batch {
44
92
  readonly id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ops/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,MAAM;IACrB;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,CAAC;IACnC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ops/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,MAAM;IACrB;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,MAAM,GAAG,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,MAAM,EAAE,GAAG,QAAQ,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE7F;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,CAAC;IACnC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B"}
@@ -1,4 +1,5 @@
1
- import type { OperationLog } from "./log.js";
1
+ import { GraphError } from "../graph/graph.js";
2
+ import type { LogReading } from "./log.js";
2
3
  import type { Operation } from "./types.js";
3
4
  export interface UndoBlock {
4
5
  /** The op that read something the undo target wrote. */
@@ -17,13 +18,33 @@ export type UndoCheck = {
17
18
  readonly includeBatches: readonly string[];
18
19
  readonly message: string;
19
20
  };
21
+ /** An undo check that said no. */
22
+ export type UndoRefused = Extract<UndoCheck, {
23
+ readonly ok: false;
24
+ }>;
25
+ /**
26
+ * AN UNDO THAT CANNOT RUN, AND WHY (FR-18): a later op read what it wrote
27
+ * (`blockedBy` names each, with what it read), it would reach back across a
28
+ * declaration change or behind the undo horizon (FR-23), or there was
29
+ * nothing live to undo. `check` is the `canUndo` answer it was refused on,
30
+ * so a host can offer to bring the blocking batches along
31
+ * (`check.includeBatches`) without asking again.
32
+ *
33
+ * Still a `GraphError`: a caller that caught those before catches this.
34
+ */
35
+ export declare class UndoBlockedError extends GraphError {
36
+ readonly check: UndoRefused;
37
+ constructor(check: UndoRefused);
38
+ /** The ops in the way, with what each read that the undo would take back. */
39
+ get blockedBy(): readonly UndoBlock[];
40
+ }
20
41
  /**
21
42
  * Undoing an op out of order is legal exactly when no later live op read
22
43
  * something it wrote. That is a checkable condition rather than a policy —
23
44
  * so when it fails the framework can name the blocking op and offer to bring
24
45
  * it along, instead of refusing or corrupting state.
25
46
  */
26
- export declare function checkUndo(log: OperationLog, batchIds: readonly string[]): UndoCheck;
47
+ export declare function checkUndo(log: LogReading, batchIds: readonly string[]): UndoCheck;
27
48
  /**
28
49
  * The primitives an undo would apply: each op's stored inverse, newest first,
29
50
  * so dependent writes come apart in the order they went together. (An op's
@@ -1 +1 @@
1
- {"version":3,"file":"undo.d.ts","sourceRoot":"","sources":["../../src/ops/undo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,CAAA;CAAE,GACzD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,SAAS,SAAS,EAAE,CAAC;IACzC,sEAAsE;IACtE,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,YAAY,EACjB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,SAAS,CAgDX;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,qCAEvD"}
1
+ {"version":3,"file":"undo.d.ts","sourceRoot":"","sources":["../../src/ops/undo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,MAAM,WAAW,SAAS;IACxB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,CAAA;CAAE,GACzD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,SAAS,SAAS,EAAE,CAAC;IACzC,sEAAsE;IACtE,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,kCAAkC;AAClC,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,EAAE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC;AAErE;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAClC,QAAQ,CAAC,KAAK,EAAE,WAAW;gBAAlB,KAAK,EAAE,WAAW;IAKvC,6EAA6E;IAC7E,IAAI,SAAS,IAAI,SAAS,SAAS,EAAE,CAEpC;CACF;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,SAAS,CAsIX;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,qCAEvD"}
package/dist/ops/undo.js CHANGED
@@ -1,3 +1,27 @@
1
+ import { GraphError } from "../graph/graph.js";
2
+ import { isWithheld } from "./withheld.js";
3
+ /**
4
+ * AN UNDO THAT CANNOT RUN, AND WHY (FR-18): a later op read what it wrote
5
+ * (`blockedBy` names each, with what it read), it would reach back across a
6
+ * declaration change or behind the undo horizon (FR-23), or there was
7
+ * nothing live to undo. `check` is the `canUndo` answer it was refused on,
8
+ * so a host can offer to bring the blocking batches along
9
+ * (`check.includeBatches`) without asking again.
10
+ *
11
+ * Still a `GraphError`: a caller that caught those before catches this.
12
+ */
13
+ export class UndoBlockedError extends GraphError {
14
+ check;
15
+ constructor(check) {
16
+ super(check.message);
17
+ this.check = check;
18
+ this.name = "UndoBlockedError";
19
+ }
20
+ /** The ops in the way, with what each read that the undo would take back. */
21
+ get blockedBy() {
22
+ return this.check.blockedBy;
23
+ }
24
+ }
1
25
  /**
2
26
  * Undoing an op out of order is legal exactly when no later live op read
3
27
  * something it wrote. That is a checkable condition rather than a policy —
@@ -10,6 +34,26 @@ export function checkUndo(log, batchIds) {
10
34
  const ops = log
11
35
  .all()
12
36
  .filter((op) => targets.has(op.batch) && !undone.has(op.id));
37
+ /*
38
+ * NOT BEHIND THE UNDO HORIZON (FR-23). A compacted log holds nothing
39
+ * before its checkpoint, so a batch it holds no op of is behind the
40
+ * horizon, or was never made: either way there is nothing here to put
41
+ * back, and the sentence names where history stops.
42
+ */
43
+ const horizon = [...log.epochs()].reverse().find((epoch) => epoch.horizon);
44
+ if (horizon) {
45
+ const held = new Set(log.all().map((op) => op.batch));
46
+ const behind = batchIds.filter((id) => !held.has(id));
47
+ if (behind.length > 0) {
48
+ return {
49
+ ok: false,
50
+ ops,
51
+ blockedBy: [],
52
+ includeBatches: [],
53
+ message: `Cannot undo ${behind.map((id) => `batch ${id}`).join(", ")}: it is not after the undo horizon at op ${horizon.seq}${horizon.at ? ` (${horizon.at})` : ""}. What was done before the horizon is archived, and undo does not reach behind it.`,
54
+ };
55
+ }
56
+ }
13
57
  if (ops.length === 0) {
14
58
  return {
15
59
  ok: false,
@@ -21,11 +65,58 @@ export function checkUndo(log, batchIds) {
21
65
  : `Nothing live to undo in ${batchIds.join(", ")} — already undone, or never applied`,
22
66
  };
23
67
  }
68
+ /*
69
+ * NOT A CHANGE YOU CANNOT SEE (FR-16). A withheld op carries no inverse
70
+ * to put back, and taking it back is for somebody who can see it; the
71
+ * sentence says so without saying what it was.
72
+ */
73
+ if (ops.some(isWithheld)) {
74
+ return {
75
+ ok: false,
76
+ ops,
77
+ blockedBy: [],
78
+ includeBatches: [],
79
+ message: ops.every(isWithheld)
80
+ ? "Cannot undo a change you cannot see: only somebody who can see it can take it back."
81
+ : "Cannot undo this here: part of it is a change you cannot see, and only somebody who can see it can take it back.",
82
+ };
83
+ }
84
+ /*
85
+ * NOT A MODULE TURNED OFF OR ON (FR-12). It touches no record, so there
86
+ * is nothing to put back, and the workspace's set is changed by turning
87
+ * the module the other way — which is the host's to do.
88
+ */
89
+ if (ops.some((op) => op.enabledModules !== undefined)) {
90
+ return {
91
+ ok: false,
92
+ ops,
93
+ blockedBy: [],
94
+ includeBatches: [],
95
+ message: "Cannot undo turning a module off or on: it is changed by turning the module the other way.",
96
+ };
97
+ }
24
98
  const writes = new Set();
25
99
  for (const op of ops)
26
100
  for (const id of op.writes)
27
101
  writes.add(id);
28
102
  const earliest = Math.min(...ops.map((op) => op.seq));
103
+ /*
104
+ * NOT ACROSS A DECLARATION CHANGE (FR-27). An op from before the change
105
+ * carries an inverse written in the old declaration's words: putting it
106
+ * back would write the old shape into the new graph. The change is named,
107
+ * since it is what stands in the way, not anything a person did since.
108
+ */
109
+ const crossed = log.epochs().find((epoch) => epoch.change !== undefined && epoch.seq > earliest);
110
+ if (crossed) {
111
+ const before = ops.find((op) => op.seq < crossed.seq);
112
+ return {
113
+ ok: false,
114
+ ops,
115
+ blockedBy: [],
116
+ includeBatches: [],
117
+ message: `Cannot undo "${before.intent}": it was done before the declaration changed (${crossed.change}), and undo does not reach back across that change.`,
118
+ };
119
+ }
29
120
  const blockedBy = [];
30
121
  for (const op of log.all()) {
31
122
  if (op.seq <= earliest)
@@ -40,10 +131,25 @@ export function checkUndo(log, batchIds) {
40
131
  }
41
132
  if (blockedBy.length === 0)
42
133
  return { ok: true, ops };
43
- const includeBatches = [...new Set(blockedBy.map((b) => b.op.batch))];
134
+ /*
135
+ * A LATER CHANGE YOU CANNOT SEE is said to be one, and nothing more: not
136
+ * its sentence, its id or what it read (FR-16). It cannot come along
137
+ * either, so no batch is offered while one stands in the way.
138
+ */
44
139
  const named = blockedBy
140
+ .filter((b) => !isWithheld(b.op))
45
141
  .map((b) => `"${b.op.intent}" (op ${b.op.id}, read ${b.overlap.join(", ")})`)
46
142
  .join("; ");
143
+ if (blockedBy.some((b) => isWithheld(b.op))) {
144
+ return {
145
+ ok: false,
146
+ ops,
147
+ blockedBy,
148
+ includeBatches: [],
149
+ message: `Cannot undo on its own — ${named ? `a later operation depends on it: ${named}; and ` : ""}a later change you cannot see depends on it, which only somebody who can see it can take back.`,
150
+ };
151
+ }
152
+ const includeBatches = [...new Set(blockedBy.map((b) => b.op.batch))];
47
153
  return {
48
154
  ok: false,
49
155
  ops,
@@ -1 +1 @@
1
- {"version":3,"file":"undo.js","sourceRoot":"","sources":["../../src/ops/undo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"undo.js","sourceRoot":"","sources":["../../src/ops/undo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Primitive } from "../graph/primitives.js";
2
+ import type { Author, Operation } from "./types.js";
3
+ /**
4
+ * A LOG A SEAT MAY NOT FULLY SEE IS REDACTED, NOT GAPPED (FR-16).
5
+ *
6
+ * A store that keeps some records from a seat cannot simply drop the ops
7
+ * that touched them: a log is contiguous by construction (undo, replay and
8
+ * `OperationLog.from` all count on it), and a hole would also say less than
9
+ * the truth — something did happen there. So the op stays, as a WITHHELD
10
+ * op: its place in the log and nothing that says what it was.
11
+ */
12
+ /** What a withheld op says it was, wherever it is read out. */
13
+ export declare const WITHHELD_INTENT = "A change you cannot see";
14
+ /** Who a withheld op says made it: nobody this seat may be told about. */
15
+ export declare const WITHHELD_AUTHOR: Author;
16
+ /** Whether an op is one a store withheld from the seat reading it. */
17
+ export declare function isWithheld(op: Pick<Operation, "withheld">): boolean;
18
+ /** The record ids a primitive touches. */
19
+ export declare function touchedBy(primitive: Primitive): readonly string[];
20
+ /**
21
+ * Whether an op touched, read or named anything `sees` says no to: then it
22
+ * is withheld whole, because its sentence ("Book a test drive for Freya")
23
+ * names what it touched as surely as its primitives do.
24
+ */
25
+ export declare function touchesUnseen(op: Operation, sees: (id: string) => boolean): boolean;
26
+ /**
27
+ * THE OP AS A SEAT THAT MAY NOT SEE IT RECEIVES IT. Its id, seq, batch and
28
+ * time stand; who made it, what it meant, the call and the inverse go. Its
29
+ * primitives keep the ones that touch only what the seat sees — a change to
30
+ * a record the seat can see still reaches it, or its copy of the graph
31
+ * would go stale — and its reads and writes keep only seen ids, so an undo
32
+ * the seat asks for is still blocked by it where it should be.
33
+ */
34
+ export declare function withhold(op: Operation, sees: (id: string) => boolean): Operation;
35
+ /** Every op of a log as a seat receives it: what it may see as it is, the rest withheld in place. */
36
+ export declare function redact(ops: readonly Operation[], sees: (id: string) => boolean): Operation[];
37
+ //# sourceMappingURL=withheld.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withheld.d.ts","sourceRoot":"","sources":["../../src/ops/withheld.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;;GAQG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,eAAe,4BAA4B,CAAC;AAEzD,0EAA0E;AAC1E,eAAO,MAAM,eAAe,EAAE,MAA4D,CAAC;AAE3F,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,OAAO,CAEnE;AAED,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,MAAM,EAAE,CAWjE;AAUD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAInF;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,CAgBhF;AAED,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,EAAE,CAE5F"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * A LOG A SEAT MAY NOT FULLY SEE IS REDACTED, NOT GAPPED (FR-16).
3
+ *
4
+ * A store that keeps some records from a seat cannot simply drop the ops
5
+ * that touched them: a log is contiguous by construction (undo, replay and
6
+ * `OperationLog.from` all count on it), and a hole would also say less than
7
+ * the truth — something did happen there. So the op stays, as a WITHHELD
8
+ * op: its place in the log and nothing that says what it was.
9
+ */
10
+ /** What a withheld op says it was, wherever it is read out. */
11
+ export const WITHHELD_INTENT = "A change you cannot see";
12
+ /** Who a withheld op says made it: nobody this seat may be told about. */
13
+ export const WITHHELD_AUTHOR = { kind: "system", id: "withheld", name: "Someone" };
14
+ /** Whether an op is one a store withheld from the seat reading it. */
15
+ export function isWithheld(op) {
16
+ return op.withheld === true;
17
+ }
18
+ /** The record ids a primitive touches. */
19
+ export function touchedBy(primitive) {
20
+ switch (primitive.op) {
21
+ case "add-node":
22
+ case "remove-node":
23
+ return [primitive.node.id];
24
+ case "patch-node":
25
+ return [primitive.id];
26
+ case "add-edge":
27
+ case "remove-edge":
28
+ return [primitive.edge.from, primitive.edge.to];
29
+ }
30
+ }
31
+ /** Every string a call's arguments hold, at any depth: where a record's id would be. */
32
+ function strings(value, into = []) {
33
+ if (typeof value === "string")
34
+ into.push(value);
35
+ else if (Array.isArray(value))
36
+ for (const inner of value)
37
+ strings(inner, into);
38
+ else if (value !== null && typeof value === "object")
39
+ for (const inner of Object.values(value))
40
+ strings(inner, into);
41
+ return into;
42
+ }
43
+ /**
44
+ * Whether an op touched, read or named anything `sees` says no to: then it
45
+ * is withheld whole, because its sentence ("Book a test drive for Freya")
46
+ * names what it touched as surely as its primitives do.
47
+ */
48
+ export function touchesUnseen(op, sees) {
49
+ if (op.primitives.some((primitive) => touchedBy(primitive).some((id) => !sees(id))))
50
+ return true;
51
+ if (op.writes.some((id) => !sees(id)) || op.reads.some((id) => !sees(id)))
52
+ return true;
53
+ return op.mutation !== null && strings(op.mutation.args).some((value) => !sees(value));
54
+ }
55
+ /**
56
+ * THE OP AS A SEAT THAT MAY NOT SEE IT RECEIVES IT. Its id, seq, batch and
57
+ * time stand; who made it, what it meant, the call and the inverse go. Its
58
+ * primitives keep the ones that touch only what the seat sees — a change to
59
+ * a record the seat can see still reaches it, or its copy of the graph
60
+ * would go stale — and its reads and writes keep only seen ids, so an undo
61
+ * the seat asks for is still blocked by it where it should be.
62
+ */
63
+ export function withhold(op, sees) {
64
+ return {
65
+ id: op.id,
66
+ seq: op.seq,
67
+ batch: op.batch,
68
+ author: WITHHELD_AUTHOR,
69
+ intent: WITHHELD_INTENT,
70
+ mutation: null,
71
+ primitives: op.primitives.filter((primitive) => touchedBy(primitive).every(sees)),
72
+ inverse: [],
73
+ reads: op.reads.filter(sees),
74
+ writes: op.writes.filter(sees),
75
+ at: op.at,
76
+ ...(op.undoes !== undefined ? { undoes: op.undoes } : {}),
77
+ withheld: true,
78
+ };
79
+ }
80
+ /** Every op of a log as a seat receives it: what it may see as it is, the rest withheld in place. */
81
+ export function redact(ops, sees) {
82
+ return ops.map((op) => (op.withheld || !touchesUnseen(op, sees) ? op : withhold(op, sees)));
83
+ }
84
+ //# sourceMappingURL=withheld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withheld.js","sourceRoot":"","sources":["../../src/ops/withheld.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,21 @@
1
1
  import type { AnyMutationDefinition } from "../mutations/types.js";
2
2
  import type { AnySchema } from "../schema/schema.js";
3
3
  import type { Policy, Principal, Refusal } from "./types.js";
4
+ /**
5
+ * THE SEAT A POLICY JUDGES, delegation read through (FR-06). An agent acting
6
+ * for a person may do what BOTH may: its roles are the intersection of its
7
+ * own and theirs (an agent that declared none takes the person's), and a
8
+ * `self` grant — "you, on yours" — is about the person, not the agent. A
9
+ * principal acting for nobody is judged as itself.
10
+ */
11
+ export declare function actingAs(principal: Principal): Principal;
12
+ /**
13
+ * THE HOST'S OWN WORK HAS A SEAT (FR-17). A template's setup, a seed, a
14
+ * migration applied by the system is not a person a policy was written
15
+ * about, and `roles: ["*"]` matched no grant — so a host gave its system
16
+ * every role the policy named. `system` is a kind, not a role: it passes.
17
+ */
18
+ export declare const isSystem: (principal: Principal) => boolean;
4
19
  /**
5
20
  * WHAT A REFUSAL CALLS THINGS. The policy knows acts and roles by their
6
21
  * names; a person reads "Not permitted: close-deal on a deal — sales-manager
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/permissions/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAS,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAWpE;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1C;AAQD;;;;;;GAMG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM;AACb;;;;;GAKG;AACH,GAAG,CAAC,EAAE,SAAS,MAAM,EAAE;AACvB;;;GAGG;AACH,SAAS,CAAC,EAAE,MAAM;AAClB,qFAAqF;AACrF,KAAK,GAAE,WAAgB,GACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CA8D3E;AAwBD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAUzF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,SAAS,MAAM,EAAE,CAcnB;AAED,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAQzD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,SAAS,EACpD,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,qBAAqB,CAAC,CAAC,CAAC,EAAE,GAC7C,SAAS,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAUrC"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/permissions/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAS,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAKpE;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAMxD;AAED;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,WAAW,SAAS,KAAG,OAA4E,CAAC;AAQ7H;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1C;AAQD;;;;;;GAMG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM;AACb;;;;;GAKG;AACH,GAAG,CAAC,EAAE,SAAS,MAAM,EAAE;AACvB;;;GAGG;AACH,SAAS,CAAC,EAAE,MAAM;AAClB,qFAAqF;AACrF,KAAK,GAAE,WAAgB,GACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAgE3E;AAwBD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAUzF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,SAAS,MAAM,EAAE,CAcnB;AAED,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAQzD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,SAAS,EACpD,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,qBAAqB,CAAC,CAAC,CAAC,EAAE,GAC7C,SAAS,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAYrC"}
@@ -1,5 +1,27 @@
1
1
  import { humaniseField, withArticle } from "../schema/define-node.js";
2
2
  const matches = (allowed, value) => allowed === "*" || allowed.includes(value);
3
+ /**
4
+ * THE SEAT A POLICY JUDGES, delegation read through (FR-06). An agent acting
5
+ * for a person may do what BOTH may: its roles are the intersection of its
6
+ * own and theirs (an agent that declared none takes the person's), and a
7
+ * `self` grant — "you, on yours" — is about the person, not the agent. A
8
+ * principal acting for nobody is judged as itself.
9
+ */
10
+ export function actingAs(principal) {
11
+ const person = principal.onBehalfOf;
12
+ if (!person)
13
+ return principal;
14
+ const theirs = person.roles ?? [];
15
+ const roles = principal.roles === undefined ? theirs : principal.roles.filter((role) => theirs.includes(role));
16
+ return { ...principal, ...(person.id !== undefined ? { id: person.id } : {}), roles };
17
+ }
18
+ /**
19
+ * THE HOST'S OWN WORK HAS A SEAT (FR-17). A template's setup, a seed, a
20
+ * migration applied by the system is not a person a policy was written
21
+ * about, and `roles: ["*"]` matched no grant — so a host gave its system
22
+ * every role the policy named. `system` is a kind, not a role: it passes.
23
+ */
24
+ export const isSystem = (principal) => principal.kind === "system" && principal.onBehalfOf === undefined;
3
25
  /** Whether a grant covers a principal at all, before looking at the call. */
4
26
  function grantsTo(grant, principal) {
5
27
  if (grant.roles === "*")
@@ -36,6 +58,9 @@ words = {}) {
36
58
  // No policy means permission is not a concern in this installation.
37
59
  if (!policy)
38
60
  return { ok: true };
61
+ if (isSystem(principal))
62
+ return { ok: true };
63
+ principal = actingAs(principal);
39
64
  /*
40
65
  * A derived act is permitted when the policy names it (or says `*`) —
41
66
  * the grants are read first, as for any act — and otherwise when any of
@@ -185,7 +210,9 @@ export function permittedMutations(policy, principal, mutations) {
185
210
  const kinds = subject && subject.kinds !== "*" ? subject.kinds : [undefined];
186
211
  // Permitted for ANY of its subject kinds is enough to offer the tool; the
187
212
  // store still refuses the individual call that is not allowed.
188
- return kinds.some((kind) => permits(policy, principal, mutation.name, kind).ok);
213
+ // A `self` grant is offered with the principal as its subject: "ask, on
214
+ // yours" is an act the principal has, though only ever on themselves.
215
+ return kinds.some((kind) => permits(policy, principal, mutation.name, kind, undefined, actingAs(principal).id).ok);
189
216
  });
190
217
  }
191
218
  //# sourceMappingURL=policy.js.map