@anchrd/intel-contract 0.25.0 → 0.27.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.
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ export declare const FeedAction: z.ZodEnum<{
3
+ "node.create": "node.create";
4
+ "node.save": "node.save";
5
+ "node.update": "node.update";
6
+ "node.append": "node.append";
7
+ "node.archive": "node.archive";
8
+ "node.share": "node.share";
9
+ "node.revoke": "node.revoke";
10
+ }>;
11
+ export type FeedAction = z.infer<typeof FeedAction>;
12
+ export declare const FeedPathSegment: z.ZodObject<{
13
+ id: z.ZodString;
14
+ title: z.ZodString;
15
+ }, z.core.$strict>;
16
+ export type FeedPathSegment = z.infer<typeof FeedPathSegment>;
17
+ export declare const FeedEvent: z.ZodObject<{
18
+ id: z.ZodString;
19
+ actorId: z.ZodString;
20
+ action: z.ZodEnum<{
21
+ "node.create": "node.create";
22
+ "node.save": "node.save";
23
+ "node.update": "node.update";
24
+ "node.append": "node.append";
25
+ "node.archive": "node.archive";
26
+ "node.share": "node.share";
27
+ "node.revoke": "node.revoke";
28
+ }>;
29
+ nodeId: z.ZodString;
30
+ nodeTitle: z.ZodString;
31
+ path: z.ZodArray<z.ZodObject<{
32
+ id: z.ZodString;
33
+ title: z.ZodString;
34
+ }, z.core.$strict>>;
35
+ metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
36
+ occurredAt: z.ZodISODateTime;
37
+ }, z.core.$strict>;
38
+ export type FeedEvent = z.infer<typeof FeedEvent>;
39
+ export declare const FeedCursor: z.ZodString;
40
+ export declare const FeedListRequest: z.ZodObject<{
41
+ actor: z.ZodOptional<z.ZodString>;
42
+ before: z.ZodOptional<z.ZodString>;
43
+ limit: z.ZodDefault<z.ZodNumber>;
44
+ }, z.core.$strict>;
45
+ export type FeedListRequest = z.infer<typeof FeedListRequest>;
46
+ export declare const FeedListResponse: z.ZodObject<{
47
+ events: z.ZodArray<z.ZodObject<{
48
+ id: z.ZodString;
49
+ actorId: z.ZodString;
50
+ action: z.ZodEnum<{
51
+ "node.create": "node.create";
52
+ "node.save": "node.save";
53
+ "node.update": "node.update";
54
+ "node.append": "node.append";
55
+ "node.archive": "node.archive";
56
+ "node.share": "node.share";
57
+ "node.revoke": "node.revoke";
58
+ }>;
59
+ nodeId: z.ZodString;
60
+ nodeTitle: z.ZodString;
61
+ path: z.ZodArray<z.ZodObject<{
62
+ id: z.ZodString;
63
+ title: z.ZodString;
64
+ }, z.core.$strict>>;
65
+ metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
66
+ occurredAt: z.ZodISODateTime;
67
+ }, z.core.$strict>>;
68
+ nextCursor: z.ZodNullable<z.ZodString>;
69
+ }, z.core.$strict>;
70
+ export type FeedListResponse = z.infer<typeof FeedListResponse>;
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ import { IntelId, IsoDateTime } from "./contract.js";
3
+ // What a person did, in the words a person would use. This is a POSITIVE LIST and not the set of
4
+ // actions the journal carries (#740).
5
+ //
6
+ // ⚠️ Two omissions are the point of the list, not an oversight. `node.version` is written by the
7
+ // same batch as `node.save`, so admitting both would show every edit twice — and because this feed
8
+ // does not group anything yet, twice means two cards. `node.link` and `node.import` are machine
9
+ // traces of a save rather than something anybody did.
10
+ //
11
+ // ⚠️ And it stays a positive list. "Everything except" would silently admit whatever the next
12
+ // migration starts writing, on a surface whose whole job is to be readable.
13
+ export const FeedAction = z.enum([
14
+ "node.create",
15
+ "node.save",
16
+ "node.update",
17
+ "node.append",
18
+ "node.archive",
19
+ "node.share",
20
+ "node.revoke",
21
+ ]);
22
+ // One step on the way down to the node, from the root. It carries the id as well as the title
23
+ // because the surface links each step, and two folders may share a name.
24
+ export const FeedPathSegment = z.strictObject({
25
+ id: IntelId,
26
+ title: z.string().min(1),
27
+ });
28
+ // One card's worth of journal.
29
+ //
30
+ // ⚠️ `nodeTitle` and `path` are read from `nodes` at query time, so they are TODAY's title and
31
+ // today's place — not the ones the node had when the event happened. That is the right answer for a
32
+ // feed (the reader wants to find the thing now) and the wrong one for an audit trail, which is one
33
+ // more reason these are two doors and not one.
34
+ //
35
+ // `metadata` is whatever the write site recorded, untyped on purpose: a schema per action would
36
+ // have to be kept in step with eleven write sites and would go stale in silence. What the surface
37
+ // needs from it today is `sequence` on a `node.save`, which is the version number behind the card's
38
+ // button.
39
+ export const FeedEvent = z.strictObject({
40
+ id: IntelId,
41
+ actorId: z.string().min(1),
42
+ action: FeedAction,
43
+ nodeId: IntelId,
44
+ nodeTitle: z.string().min(1),
45
+ path: z.array(FeedPathSegment),
46
+ metadata: z.record(z.string(), z.unknown()),
47
+ occurredAt: IsoDateTime,
48
+ });
49
+ // ⚠️ Opaque BY CONTRACT, like the audit cursor and for the same reason: it is a PAIR
50
+ // (`occurredAt`, `id`), and a timestamp alone cannot separate two events written in the same
51
+ // millisecond — the normal case inside one batch. A reader continuing on the timestamp alone skips
52
+ // the second one with no error and no log.
53
+ //
54
+ // ⚠️ It is deliberately NOT interchangeable with an audit cursor, although both encode the same
55
+ // pair. The two doors walk the journal in opposite directions, so handing one's position to the
56
+ // other reads on from the wrong end — and would look like a feed that suddenly starts at the
57
+ // beginning of time rather than like a mistake. The encoding therefore carries its own marker and
58
+ // the other door's cursor is refused.
59
+ export const FeedCursor = z.string().min(1).max(400);
60
+ export const FeedListRequest = z.strictObject({
61
+ actor: z
62
+ .string()
63
+ .min(1)
64
+ .optional()
65
+ .describe("Show only what this person or agent did. Omit it for everybody whose work you may see anyway."),
66
+ before: FeedCursor.optional().describe("Where to continue: the `nextCursor` of a previous answer, passed back unchanged. Omit it to start at the most recent event. Do not build one — it is opaque on purpose, and a cursor from `audit_list` is refused rather than followed backwards."),
67
+ limit: z
68
+ .number()
69
+ .int()
70
+ .min(1)
71
+ .max(100)
72
+ .default(30)
73
+ .describe("How many events to return at most. The answer may be shorter."),
74
+ });
75
+ // `nextCursor` is present exactly when another page may exist, and it is the cursor of the LAST
76
+ // returned row.
77
+ //
78
+ // ⚠️ Present does not promise the next page is non-empty: the rows a reader may see can shrink
79
+ // between calls. Treating "cursor present" as "there is more" is fair; treating an empty answer as
80
+ // "you have reached the beginning of the journal" is not.
81
+ export const FeedListResponse = z.strictObject({
82
+ events: z.array(FeedEvent),
83
+ nextCursor: FeedCursor.nullable(),
84
+ });
@@ -120,9 +120,31 @@ export declare const PurgeNodePreviewInput: z.ZodObject<{
120
120
  nodeId: z.ZodString;
121
121
  }, z.core.$strict>;
122
122
  export type PurgeNodePreviewInput = z.infer<typeof PurgeNodePreviewInput>;
123
+ /**
124
+ * ⚠️ **A count is not an answer before a deletion that cannot be undone** (D71, #733). `totalItems`
125
+ * alone reads as "82 items" for a folder holding somebody's whole memory, and `destructive.md` asks
126
+ * for the number to be named BEFORE the confirmation — one step more precise is the difference
127
+ * between a number and something a person can decide on.
128
+ *
129
+ * `items` is capped, and the cap is visible rather than silent: `totalItems` keeps counting past it,
130
+ * so a caller can always say "and 40 more". A list of ten thousand rows would be an answer nobody
131
+ * reads and a response nobody can render.
132
+ */
123
133
  export declare const PurgeNodePreview: z.ZodObject<{
124
134
  inboundLinks: z.ZodNumber;
125
135
  totalItems: z.ZodNumber;
136
+ items: z.ZodArray<z.ZodObject<{
137
+ id: z.ZodString;
138
+ kind: z.ZodUnion<readonly [z.ZodEnum<{
139
+ folder: "folder";
140
+ document: "document";
141
+ table: "table";
142
+ attachment: "attachment";
143
+ board: "board";
144
+ task: "task";
145
+ }>, z.ZodLiteral<"flow">]>;
146
+ title: z.ZodString;
147
+ }, z.core.$strict>>;
126
148
  }, z.core.$strict>;
127
149
  export type PurgeNodePreview = z.infer<typeof PurgeNodePreview>;
128
150
  export declare const PurgeNodeResult: z.ZodObject<{
@@ -187,9 +187,27 @@ export const PurgeNodeInput = z.strictObject({
187
187
  export const PurgeNodePreviewInput = z.strictObject({
188
188
  nodeId: IntelId.describe("Archived node a purge would delete. Answers how many items would go with it and how many documents link to it from outside, and changes nothing. A node that is not archived is refused here for the same reason node_purge refuses it."),
189
189
  });
190
+ /**
191
+ * ⚠️ **A count is not an answer before a deletion that cannot be undone** (D71, #733). `totalItems`
192
+ * alone reads as "82 items" for a folder holding somebody's whole memory, and `destructive.md` asks
193
+ * for the number to be named BEFORE the confirmation — one step more precise is the difference
194
+ * between a number and something a person can decide on.
195
+ *
196
+ * `items` is capped, and the cap is visible rather than silent: `totalItems` keeps counting past it,
197
+ * so a caller can always say "and 40 more". A list of ten thousand rows would be an answer nobody
198
+ * reads and a response nobody can render.
199
+ */
190
200
  export const PurgeNodePreview = z.strictObject({
191
201
  inboundLinks: z.number().int().nonnegative(),
192
202
  totalItems: z.number().int().positive(),
203
+ // ⚠️ `flow` beside the node kinds, because a flow filed in the folder goes with it and is counted
204
+ // in `totalItems`. Naming everything except flows would leave out the one thing the cascade had to
205
+ // be widened for (#733).
206
+ items: z.array(z.strictObject({
207
+ id: IntelId,
208
+ kind: z.union([NodeKind, z.literal("flow")]),
209
+ title: z.string(),
210
+ })),
193
211
  });
194
212
  // The title travels back because after this call nothing can look it up any more — not the row, not
195
213
  // a version, not the index. A caller that wants to say what it just deleted has this one chance.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-contract",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -28,6 +28,10 @@
28
28
  "types": "./dist/contract/bundle.d.ts",
29
29
  "default": "./dist/contract/bundle.js"
30
30
  },
31
+ "./feed": {
32
+ "types": "./dist/contract/feed.d.ts",
33
+ "default": "./dist/contract/feed.js"
34
+ },
31
35
  "./flow": {
32
36
  "types": "./dist/contract/flow.d.ts",
33
37
  "default": "./dist/contract/flow.js"