@vercel/factory 0.0.17 → 0.0.18

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 (186) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/api-contracts.d.mts +665 -715
  3. package/dist/api-contracts.mjs +91 -4
  4. package/dist/api-contracts.mjs.map +1 -1
  5. package/dist/api-task-graph.mjs +14 -5
  6. package/dist/api-task-graph.mjs.map +1 -1
  7. package/dist/api.d.mts +58 -11
  8. package/dist/api.mjs +715 -399
  9. package/dist/api.mjs.map +1 -1
  10. package/dist/approval-access.d.mts +1 -1
  11. package/dist/approval-contracts.d.mts +3 -4
  12. package/dist/approval-contracts.mjs +1 -2
  13. package/dist/approval-contracts.mjs.map +1 -1
  14. package/dist/blob/index.d.mts +1 -1
  15. package/dist/budget.mjs +20 -3
  16. package/dist/budget.mjs.map +1 -1
  17. package/dist/change-verification/brief.d.mts +1 -1
  18. package/dist/change-verification/dispatch.mjs +8 -1
  19. package/dist/change-verification/dispatch.mjs.map +1 -1
  20. package/dist/changes/eve-record-change.mjs +4 -1
  21. package/dist/changes/eve-record-change.mjs.map +1 -1
  22. package/dist/client-stream.d.mts +19 -2
  23. package/dist/client-stream.mjs +115 -53
  24. package/dist/client-stream.mjs.map +1 -1
  25. package/dist/client-transcript.d.mts +25 -2
  26. package/dist/client-transcript.mjs +95 -13
  27. package/dist/client-transcript.mjs.map +1 -1
  28. package/dist/client.d.mts +145 -19
  29. package/dist/client.mjs +89 -16
  30. package/dist/client.mjs.map +1 -1
  31. package/dist/code-review/contracts.d.mts +1 -0
  32. package/dist/code-review/eve-post-review.d.mts +4 -2
  33. package/dist/code-review/eve-post-review.mjs +8 -0
  34. package/dist/code-review/eve-post-review.mjs.map +1 -1
  35. package/dist/code-review/github-reporter.d.mts +6 -2
  36. package/dist/code-review/github-reporter.mjs +86 -35
  37. package/dist/code-review/github-reporter.mjs.map +1 -1
  38. package/dist/code-review/preparation.d.mts +2 -1
  39. package/dist/code-review/preparation.mjs +2 -1
  40. package/dist/code-review/preparation.mjs.map +1 -1
  41. package/dist/code-review/stall.mjs +11 -2
  42. package/dist/code-review/stall.mjs.map +1 -1
  43. package/dist/connectors/github.d.mts +1 -1
  44. package/dist/connectors/index.d.mts +1 -1
  45. package/dist/connectors/slack.d.mts +1 -1
  46. package/dist/delivery-metrics/metrics.d.mts +1 -1
  47. package/dist/dispatch.mjs +3 -3
  48. package/dist/dispatch.mjs.map +1 -1
  49. package/dist/eve/index.d.mts +1 -1
  50. package/dist/eve/index.mjs +8 -1
  51. package/dist/eve/index.mjs.map +1 -1
  52. package/dist/eve/task-execution.d.mts +4 -4
  53. package/dist/eve/transcript.d.mts +3 -1
  54. package/dist/eve/transcript.mjs.map +1 -1
  55. package/dist/finding-remediation/admission.d.mts +1 -1
  56. package/dist/finding-remediation/brief.d.mts +1 -1
  57. package/dist/github-app.d.mts +1 -1
  58. package/dist/github-publication.d.mts +1 -1
  59. package/dist/github.d.mts +9 -1
  60. package/dist/github.mjs +29 -11
  61. package/dist/github.mjs.map +1 -1
  62. package/dist/index.d.mts +1 -1
  63. package/dist/intake-contracts.d.mts +1 -0
  64. package/dist/intake-contracts.mjs +2 -1
  65. package/dist/intake-contracts.mjs.map +1 -1
  66. package/dist/integrations/github-issues.d.mts +53 -0
  67. package/dist/integrations/github-issues.mjs +170 -0
  68. package/dist/integrations/github-issues.mjs.map +1 -0
  69. package/dist/integrations/github.d.mts +2 -1
  70. package/dist/integrations/github.mjs +2 -1
  71. package/dist/multi-repository-changes/coordination.mjs +1 -1
  72. package/dist/planning/reconcile.mjs +15 -2
  73. package/dist/planning/reconcile.mjs.map +1 -1
  74. package/dist/postgres/index.d.mts +2 -1
  75. package/dist/postgres/index.mjs +11 -5
  76. package/dist/postgres/index.mjs.map +1 -1
  77. package/dist/postgres/indexed-codec.mjs +137 -0
  78. package/dist/postgres/indexed-codec.mjs.map +1 -0
  79. package/dist/postgres/indexed-schema.mjs +99 -0
  80. package/dist/postgres/indexed-schema.mjs.map +1 -0
  81. package/dist/postgres/indexed.d.mts +28 -0
  82. package/dist/postgres/indexed.mjs +277 -0
  83. package/dist/postgres/indexed.mjs.map +1 -0
  84. package/dist/postgres/record-predicates.mjs +23 -0
  85. package/dist/postgres/record-predicates.mjs.map +1 -0
  86. package/dist/presets/software-development/deepsec-brief.d.mts +1 -1
  87. package/dist/presets/software-development/dispatch.mjs +1 -1
  88. package/dist/presets/software-development/dispatch.mjs.map +1 -1
  89. package/dist/presets/software-development/recovery.mjs +9 -2
  90. package/dist/presets/software-development/recovery.mjs.map +1 -1
  91. package/dist/pull-requests/eve-fetch.d.mts +4 -1
  92. package/dist/pull-requests/eve-fetch.mjs +9 -0
  93. package/dist/pull-requests/eve-fetch.mjs.map +1 -1
  94. package/dist/pull-requests/github-publisher.d.mts +1 -1
  95. package/dist/pull-requests/github-publisher.mjs +19 -7
  96. package/dist/pull-requests/github-publisher.mjs.map +1 -1
  97. package/dist/repositories.mjs +20 -13
  98. package/dist/repositories.mjs.map +1 -1
  99. package/dist/sandbox/execution-run-ledger.d.mts +2 -2
  100. package/dist/sandbox/execution-run-ledger.mjs.map +1 -1
  101. package/dist/sandbox/execution.d.mts +2 -2
  102. package/dist/sandbox/execution.mjs.map +1 -1
  103. package/dist/schema/catalog.mjs +1 -1
  104. package/dist/schema/change.d.mts +1 -1
  105. package/dist/schema/communication.d.mts +2 -2
  106. package/dist/schema/factory-config.d.mts +1 -1
  107. package/dist/schema/outbox.d.mts +6 -6
  108. package/dist/schema/session-summary.d.mts +218 -0
  109. package/dist/schema/session-summary.mjs +68 -0
  110. package/dist/schema/session-summary.mjs.map +1 -0
  111. package/dist/schema/session.d.mts +39 -59
  112. package/dist/schema/session.mjs +6 -6
  113. package/dist/schema/session.mjs.map +1 -1
  114. package/dist/schema/task-graph.d.mts +24 -24
  115. package/dist/schema/transcript.d.mts +141 -3
  116. package/dist/schema/transcript.mjs +19 -3
  117. package/dist/schema/transcript.mjs.map +1 -1
  118. package/dist/schema/transitions.d.mts +1 -1
  119. package/dist/schema/transitions.mjs +1 -1
  120. package/dist/schema/transitions.mjs.map +1 -1
  121. package/dist/signals.d.mts +1 -1
  122. package/dist/stall.mjs +10 -2
  123. package/dist/stall.mjs.map +1 -1
  124. package/dist/storage.d.mts +10 -1
  125. package/dist/storage.mjs +7 -1
  126. package/dist/store/approvals.d.mts +14 -0
  127. package/dist/store/approvals.mjs +78 -0
  128. package/dist/store/approvals.mjs.map +1 -0
  129. package/dist/store/conversations.d.mts +59 -0
  130. package/dist/store/conversations.mjs +549 -0
  131. package/dist/store/conversations.mjs.map +1 -0
  132. package/dist/store/driver.d.mts +1 -1
  133. package/dist/store/driver.mjs.map +1 -1
  134. package/dist/store/engine.d.mts +38 -16
  135. package/dist/store/engine.mjs +229 -106
  136. package/dist/store/engine.mjs.map +1 -1
  137. package/dist/store/indexed-engine.d.mts +57 -0
  138. package/dist/store/indexed-engine.mjs +155 -0
  139. package/dist/store/indexed-engine.mjs.map +1 -0
  140. package/dist/store/indexed-memory.d.mts +7 -0
  141. package/dist/store/indexed-memory.mjs +200 -0
  142. package/dist/store/indexed-memory.mjs.map +1 -0
  143. package/dist/store/indexed-projections.d.mts +280 -0
  144. package/dist/store/indexed-projections.mjs +481 -0
  145. package/dist/store/indexed-projections.mjs.map +1 -0
  146. package/dist/store/indexed.d.mts +101 -0
  147. package/dist/store/indexed.mjs +19 -0
  148. package/dist/store/indexed.mjs.map +1 -0
  149. package/dist/store/iterate.d.mts +9 -0
  150. package/dist/store/iterate.mjs +23 -0
  151. package/dist/store/iterate.mjs.map +1 -0
  152. package/dist/store/query-scope.mjs +62 -0
  153. package/dist/store/query-scope.mjs.map +1 -0
  154. package/dist/store/query.d.mts +32 -0
  155. package/dist/store/query.mjs +103 -0
  156. package/dist/store/query.mjs.map +1 -0
  157. package/dist/store/session-history.mjs +24 -0
  158. package/dist/store/session-history.mjs.map +1 -0
  159. package/dist/store/slack-pr-notifications.d.mts +2 -2
  160. package/dist/store/slack-pr-notifications.mjs.map +1 -1
  161. package/dist/store/state.d.mts +26 -0
  162. package/dist/store/state.mjs +95 -0
  163. package/dist/store/state.mjs.map +1 -0
  164. package/dist/store/task-effects.d.mts +1 -1
  165. package/dist/store/task-graphs.d.mts +1 -1
  166. package/dist/store/task-messages.d.mts +1 -1
  167. package/dist/store/task-work.d.mts +1 -1
  168. package/dist/store/task-work.mjs +1 -1
  169. package/dist/store/transcripts.mjs +308 -0
  170. package/dist/store/transcripts.mjs.map +1 -0
  171. package/dist/sweep.d.mts +1 -1
  172. package/dist/sweep.mjs +12 -7
  173. package/dist/sweep.mjs.map +1 -1
  174. package/dist/task-communication.mjs +15 -1
  175. package/dist/task-communication.mjs.map +1 -1
  176. package/dist/task-protocols.d.mts +3 -3
  177. package/dist/tasks.d.mts +1 -1
  178. package/dist/vercel.d.mts +1 -1
  179. package/dist/work-triage/find-existing-work.d.mts +1 -1
  180. package/dist/work-triage/find-existing-work.mjs +12 -2
  181. package/dist/work-triage/find-existing-work.mjs.map +1 -1
  182. package/docs/index.md +4 -0
  183. package/docs/recipes/persistence-recovery.md +15 -9
  184. package/docs/recipes/postgres-indexed-engine.md +108 -0
  185. package/docs/recipes/postgres-indexed-storage.md +145 -0
  186. package/package.json +3 -1
@@ -0,0 +1,57 @@
1
+ import { StoreDriver } from "./driver.mjs";
2
+ import { IndexedStore } from "./indexed.mjs";
3
+ import { CreateStoresOptions, FactoryStores } from "./engine.mjs";
4
+ import { ApprovalStore } from "./approvals.mjs";
5
+ import { ConversationProjectionOptions, ConversationStore } from "./conversations.mjs";
6
+ import { FactoryIndexScope, FactoryRecordSummary } from "./indexed-projections.mjs";
7
+ //#region src/store/indexed-engine.d.ts
8
+ /** Storage and workflow configuration for fresh indexed state; images use a separate object store. */
9
+ interface CreateIndexedStoresOptions extends Omit<CreateStoresOptions, "driver"> {
10
+ /** Authoritative structured persistence, with no historical backend or fallback. */
11
+ readonly store: IndexedStore;
12
+ /** Explicit operator read-model routing and automatic-work owner. */
13
+ readonly conversations?: ConversationProjectionOptions;
14
+ /** Object storage used exclusively for uploaded image bytes; never receives structured state. */
15
+ readonly images: Pick<StoreDriver, "get" | "insert">;
16
+ }
17
+ /** One validated compact summary and the canonical record version that produced it. */
18
+ interface FactoryIndexedEntry {
19
+ readonly summary: FactoryRecordSummary;
20
+ readonly version: number;
21
+ }
22
+ /** Bounded source-summary page; cursors continue only the same scope and ordering. */
23
+ interface FactoryIndexedPage {
24
+ readonly entries: readonly FactoryIndexedEntry[];
25
+ readonly nextCursor: string | null;
26
+ }
27
+ /** Engine write operations and point reads, with bounded queries replacing unbounded catalog-list methods. */
28
+ interface IndexedFactoryStores extends FactoryStores {
29
+ readonly conversations: ConversationStore;
30
+ readonly approvals: ApprovalStore;
31
+ /** Trusted source queries. Source attention counts are not deduplicated conversation counts. */
32
+ readonly indexes: {
33
+ query(input: {
34
+ scope: FactoryIndexScope;
35
+ limit: number;
36
+ cursor?: string;
37
+ order?: "asc" | "desc";
38
+ }): Promise<FactoryIndexedPage>;
39
+ count(scope: FactoryIndexScope): Promise<number>;
40
+ };
41
+ }
42
+ /**
43
+ * Creates the validating Factory write engine over atomic indexed persistence.
44
+ *
45
+ * @remarks
46
+ * New canonical state and its compact memberships/counts commit together. No catalog shards,
47
+ * Blob fallback, backfill, or unbounded list methods exist on the returned API. Images alone use the
48
+ * supplied object store. Authentication and query-scope authorization remain the application's
49
+ * responsibility. Repository membership does not grant access to a multi-repository record.
50
+ * The Postgres adapter's record/summary/membership bounds apply; source attention counts must not
51
+ * be presented as deduplicated conversation counts. Configure application conversation projections
52
+ * for the operator API; growing histories use bounded pages.
53
+ */
54
+ declare function createIndexedStores(options: CreateIndexedStoresOptions): IndexedFactoryStores;
55
+ //#endregion
56
+ export { CreateIndexedStoresOptions, FactoryIndexedEntry, FactoryIndexedPage, IndexedFactoryStores, createIndexedStores };
57
+ //# sourceMappingURL=indexed-engine.d.mts.map
@@ -0,0 +1,155 @@
1
+ import { taskSchema } from "../schema/task.mjs";
2
+ import { agentSessionSchema } from "../schema/session.mjs";
3
+ import { factoryIndexKey, factoryIndexScopeSchema, factoryRecordSummarySchema, projectIndexedRecord } from "./indexed-projections.mjs";
4
+ import { createIndexedTranscriptStore } from "./transcripts.mjs";
5
+ import { createStoreEngine } from "./engine.mjs";
6
+ import { createApprovalStore, projectApproval } from "./approvals.mjs";
7
+ import { conversationTaskMemberships, createConversationStore, refreshConversations } from "./conversations.mjs";
8
+ import { randomUUID } from "node:crypto";
9
+ import { z } from "zod";
10
+ //#region src/store/indexed-engine.ts
11
+ const catalogs = /* @__PURE__ */ new Set([
12
+ "taskCatalogs",
13
+ "changeCatalogs",
14
+ "sessionCatalogs",
15
+ "externalSessionCatalogs",
16
+ "taskRoutes"
17
+ ]);
18
+ function storedJson(value) {
19
+ const json = JSON.stringify(value, function(_key, entry) {
20
+ if (entry === void 0 && Array.isArray(this)) throw new Error("Undefined array element");
21
+ if (typeof entry === "number" && !Number.isFinite(entry)) throw new Error("Non-finite storage number");
22
+ if (typeof entry === "bigint" || typeof entry === "function" || typeof entry === "symbol") throw new Error("Storage requires JSON values");
23
+ return entry;
24
+ });
25
+ if (json === void 0) throw new Error("Storage requires a JSON value");
26
+ return JSON.parse(json);
27
+ }
28
+ /**
29
+ * Creates the validating Factory write engine over atomic indexed persistence.
30
+ *
31
+ * @remarks
32
+ * New canonical state and its compact memberships/counts commit together. No catalog shards,
33
+ * Blob fallback, backfill, or unbounded list methods exist on the returned API. Images alone use the
34
+ * supplied object store. Authentication and query-scope authorization remain the application's
35
+ * responsibility. Repository membership does not grant access to a multi-repository record.
36
+ * The Postgres adapter's record/summary/membership bounds apply; source attention counts must not
37
+ * be presented as deduplicated conversation counts. Configure application conversation projections
38
+ * for the operator API; growing histories use bounded pages.
39
+ */
40
+ function createIndexedStores(options) {
41
+ return indexedStores(options, true);
42
+ }
43
+ function indexedStores(options, observe) {
44
+ const { images } = options;
45
+ function observingStore(raw) {
46
+ const projectionOptions = options.conversations;
47
+ return {
48
+ ...raw,
49
+ transaction: (run) => raw.transaction((tx) => run(observingStore(tx))),
50
+ write: (input) => raw.transaction(async (tx) => {
51
+ const canonical = indexedStores({
52
+ ...options,
53
+ store: tx,
54
+ conversations: void 0
55
+ }, false);
56
+ const memberships = input.collection === "tasks" ? await conversationTaskMemberships(canonical, taskSchema.parse(input.value)) : [];
57
+ const result = await tx.write({
58
+ ...input,
59
+ indexes: [...input.indexes, ...memberships]
60
+ });
61
+ const execution = input.collection === "tasks" ? taskSchema.parse(input.value).execution : input.collection === "sessions" || input.collection === "externalSessions" ? agentSessionSchema.parse(input.value).execution : void 0;
62
+ if (execution && await canonical.transcripts.head(execution) === null) await canonical.transcripts.put(execution, {
63
+ blocks: [],
64
+ inputs: [],
65
+ cursor: 0,
66
+ state: "connecting",
67
+ tokens: 0
68
+ });
69
+ if (input.collection === "tasks") await projectApproval(tx, canonical, input.value);
70
+ if (projectionOptions) await refreshConversations(tx, canonical, input.collection, input.id, input.value, projectionOptions);
71
+ return result;
72
+ })
73
+ };
74
+ }
75
+ const store = observe ? observingStore(options.store) : options.store;
76
+ function assertCanonical(collection) {
77
+ if (catalogs.has(collection)) throw new Error("Catalog persistence is unavailable in indexed stores");
78
+ }
79
+ async function write(collection, id, value, expectedVersion) {
80
+ assertCanonical(collection);
81
+ if (collection === "images") throw new Error("Images must use object storage");
82
+ const canonical = storedJson(value);
83
+ const intent = {
84
+ collection,
85
+ id,
86
+ value: canonical,
87
+ expectedVersion,
88
+ indexes: projectIndexedRecord(collection, id, canonical).map((entry) => ({
89
+ ...entry,
90
+ summary: storedJson(entry.summary)
91
+ }))
92
+ };
93
+ const operationId = randomUUID();
94
+ await store.write({
95
+ ...intent,
96
+ operationId
97
+ });
98
+ }
99
+ const driver = {
100
+ name: store.name,
101
+ async get(collection, id) {
102
+ assertCanonical(collection);
103
+ return collection === "images" ? images.get(collection, id) : store.get(collection, id);
104
+ },
105
+ async insert(input) {
106
+ if (input.collection === "images") return images.insert(input);
107
+ await write(input.collection, input.id, input.value, 0);
108
+ },
109
+ async update(input) {
110
+ await write(input.collection, input.id, input.value, input.expectedVersion);
111
+ },
112
+ async list() {
113
+ throw new Error("Indexed stores require bounded queries instead of collection lists");
114
+ }
115
+ };
116
+ return {
117
+ ...createStoreEngine({
118
+ ...options,
119
+ driver
120
+ }, store),
121
+ conversations: createConversationStore(store),
122
+ approvals: createApprovalStore(store),
123
+ transcripts: createIndexedTranscriptStore(store, options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())),
124
+ indexes: {
125
+ async query(input) {
126
+ const scope = factoryIndexScopeSchema.parse(input.scope);
127
+ const index = factoryIndexKey(scope);
128
+ const page = await store.query({
129
+ index,
130
+ limit: input.limit,
131
+ cursor: input.cursor,
132
+ order: input.order
133
+ });
134
+ return {
135
+ entries: page.entries.map((entry) => {
136
+ const summary = factoryRecordSummarySchema.parse(entry.summary);
137
+ if (entry.index !== index || entry.collection !== scope.collection || summary.collection !== entry.collection || summary.id !== entry.id) throw new Error("Indexed summary identity mismatch");
138
+ return {
139
+ summary,
140
+ version: z.number().int().positive().parse(entry.version)
141
+ };
142
+ }),
143
+ nextCursor: page.nextCursor
144
+ };
145
+ },
146
+ count(scope) {
147
+ return store.count(factoryIndexKey(scope));
148
+ }
149
+ }
150
+ };
151
+ }
152
+ //#endregion
153
+ export { createIndexedStores };
154
+
155
+ //# sourceMappingURL=indexed-engine.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexed-engine.mjs","names":[],"sources":["../../src/store/indexed-engine.ts"],"sourcesContent":["import { createApprovalStore, projectApproval, type ApprovalStore } from \"./approvals\";\nimport {\n createConversationStore,\n refreshConversations,\n conversationTaskMemberships,\n type ConversationProjectionOptions,\n type ConversationStore,\n} from \"./conversations\";\nimport { taskSchema } from \"../schema/task\";\nimport { agentSessionSchema } from \"../schema/session\";\nimport { randomUUID } from \"node:crypto\";\nimport { z } from \"zod\";\nimport { createStoreEngine, type CreateStoresOptions, type FactoryStores } from \"./engine\";\nimport type { CollectionName, StoreDriver, StoredRecord } from \"./driver\";\nimport type { IndexedStore } from \"./indexed\";\nimport { createIndexedTranscriptStore } from \"./transcripts\";\nimport {\n factoryIndexKey,\n factoryIndexScopeSchema,\n factoryRecordSummarySchema,\n projectIndexedRecord,\n type FactoryIndexScope,\n type FactoryRecordSummary,\n} from \"./indexed-projections\";\n\n/** Storage and workflow configuration for fresh indexed state; images use a separate object store. */\nexport interface CreateIndexedStoresOptions extends Omit<CreateStoresOptions, \"driver\"> {\n /** Authoritative structured persistence, with no historical backend or fallback. */\n readonly store: IndexedStore;\n /** Explicit operator read-model routing and automatic-work owner. */\n readonly conversations?: ConversationProjectionOptions;\n /** Object storage used exclusively for uploaded image bytes; never receives structured state. */\n readonly images: Pick<StoreDriver, \"get\" | \"insert\">;\n}\n\n/** One validated compact summary and the canonical record version that produced it. */\nexport interface FactoryIndexedEntry {\n readonly summary: FactoryRecordSummary;\n readonly version: number;\n}\n\n/** Bounded source-summary page; cursors continue only the same scope and ordering. */\nexport interface FactoryIndexedPage {\n readonly entries: readonly FactoryIndexedEntry[];\n readonly nextCursor: string | null;\n}\n\n/** Engine write operations and point reads, with bounded queries replacing unbounded catalog-list methods. */\nexport interface IndexedFactoryStores extends FactoryStores {\n readonly conversations: ConversationStore;\n readonly approvals: ApprovalStore;\n /** Trusted source queries. Source attention counts are not deduplicated conversation counts. */\n readonly indexes: {\n query(input: {\n scope: FactoryIndexScope;\n limit: number;\n cursor?: string;\n order?: \"asc\" | \"desc\";\n }): Promise<FactoryIndexedPage>;\n count(scope: FactoryIndexScope): Promise<number>;\n };\n}\n\nconst catalogs = new Set<CollectionName>([\n \"taskCatalogs\",\n \"changeCatalogs\",\n \"sessionCatalogs\",\n \"externalSessionCatalogs\",\n \"taskRoutes\",\n]);\n\n// Engine schemas may retain optional undefined properties. JSON persistence omits those properties,\n// but must not silently turn non-finite numbers or missing array elements into null.\nfunction storedJson(value: unknown): unknown {\n const json = JSON.stringify(value, function (_key, entry: unknown) {\n if (entry === undefined && Array.isArray(this)) throw new Error(\"Undefined array element\");\n if (typeof entry === \"number\" && !Number.isFinite(entry))\n throw new Error(\"Non-finite storage number\");\n if (typeof entry === \"bigint\" || typeof entry === \"function\" || typeof entry === \"symbol\")\n throw new Error(\"Storage requires JSON values\");\n return entry;\n });\n if (json === undefined) throw new Error(\"Storage requires a JSON value\");\n return JSON.parse(json) as unknown;\n}\n\n/**\n * Creates the validating Factory write engine over atomic indexed persistence.\n *\n * @remarks\n * New canonical state and its compact memberships/counts commit together. No catalog shards,\n * Blob fallback, backfill, or unbounded list methods exist on the returned API. Images alone use the\n * supplied object store. Authentication and query-scope authorization remain the application's\n * responsibility. Repository membership does not grant access to a multi-repository record.\n * The Postgres adapter's record/summary/membership bounds apply; source attention counts must not\n * be presented as deduplicated conversation counts. Configure application conversation projections\n * for the operator API; growing histories use bounded pages.\n */\nexport function createIndexedStores(options: CreateIndexedStoresOptions): IndexedFactoryStores {\n return indexedStores(options, true);\n}\n\nfunction indexedStores(\n options: CreateIndexedStoresOptions,\n observe: boolean,\n): IndexedFactoryStores {\n const { images } = options;\n function observingStore(raw: IndexedStore): IndexedStore {\n const projectionOptions = options.conversations;\n return {\n ...raw,\n transaction: (run) => raw.transaction((tx) => run(observingStore(tx))),\n write: (input) =>\n raw.transaction(async (tx) => {\n const canonical = indexedStores(\n { ...options, store: tx, conversations: undefined },\n false,\n );\n const memberships =\n input.collection === \"tasks\"\n ? await conversationTaskMemberships(canonical, taskSchema.parse(input.value))\n : [];\n const result = await tx.write({ ...input, indexes: [...input.indexes, ...memberships] });\n const execution =\n input.collection === \"tasks\"\n ? taskSchema.parse(input.value).execution\n : input.collection === \"sessions\" || input.collection === \"externalSessions\"\n ? agentSessionSchema.parse(input.value).execution\n : undefined;\n if (execution && (await canonical.transcripts.head(execution)) === null)\n await canonical.transcripts.put(execution, {\n blocks: [],\n inputs: [],\n cursor: 0,\n state: \"connecting\",\n tokens: 0,\n });\n if (input.collection === \"tasks\") await projectApproval(tx, canonical, input.value);\n if (projectionOptions)\n await refreshConversations(\n tx,\n canonical,\n input.collection,\n input.id,\n input.value,\n projectionOptions,\n );\n return result;\n }),\n };\n }\n const store = observe ? observingStore(options.store) : options.store;\n function assertCanonical(collection: CollectionName) {\n if (catalogs.has(collection))\n throw new Error(\"Catalog persistence is unavailable in indexed stores\");\n }\n async function write(\n collection: CollectionName,\n id: string,\n value: unknown,\n expectedVersion: number,\n ) {\n assertCanonical(collection);\n if (collection === \"images\") throw new Error(\"Images must use object storage\");\n const canonical = storedJson(value);\n const indexes = projectIndexedRecord(collection, id, canonical).map((entry) => ({\n ...entry,\n summary: storedJson(entry.summary),\n }));\n const intent = { collection, id, value: canonical, expectedVersion, indexes };\n // Driver calls retain insert/CAS conflict semantics. The indexed adapter uses this identity\n // to reconcile an ambiguous response within this call, not to replay a later engine call.\n const operationId = randomUUID();\n await store.write({ ...intent, operationId });\n }\n const driver: StoreDriver = {\n name: store.name,\n async get(collection, id): Promise<StoredRecord | null> {\n assertCanonical(collection);\n return collection === \"images\" ? images.get(collection, id) : store.get(collection, id);\n },\n async insert(input) {\n if (input.collection === \"images\") return images.insert(input);\n await write(input.collection, input.id, input.value, 0);\n },\n async update(input) {\n await write(input.collection, input.id, input.value, input.expectedVersion);\n },\n async list() {\n throw new Error(\"Indexed stores require bounded queries instead of collection lists\");\n },\n };\n const stores = createStoreEngine({ ...options, driver }, store);\n return {\n ...stores,\n conversations: createConversationStore(store),\n approvals: createApprovalStore(store),\n transcripts: createIndexedTranscriptStore(\n store,\n options.now ?? (() => new Date().toISOString()),\n ),\n indexes: {\n async query(input) {\n const scope = factoryIndexScopeSchema.parse(input.scope);\n const index = factoryIndexKey(scope);\n const page = await store.query({\n index,\n limit: input.limit,\n cursor: input.cursor,\n order: input.order,\n });\n return {\n entries: page.entries.map((entry) => {\n const summary = factoryRecordSummarySchema.parse(entry.summary);\n if (\n entry.index !== index ||\n entry.collection !== scope.collection ||\n summary.collection !== entry.collection ||\n summary.id !== entry.id\n )\n throw new Error(\"Indexed summary identity mismatch\");\n return { summary, version: z.number().int().positive().parse(entry.version) };\n }),\n nextCursor: page.nextCursor,\n };\n },\n count(scope) {\n return store.count(factoryIndexKey(scope));\n },\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { IndexedStore } from "./indexed.mjs";
2
+ //#region src/store/indexed-memory.d.ts
3
+ /** Creates an isolated transactional indexed store for local fixtures and tests. */
4
+ declare function createInMemoryIndexedStore(): IndexedStore;
5
+ //#endregion
6
+ export { createInMemoryIndexedStore };
7
+ //# sourceMappingURL=indexed-memory.d.mts.map
@@ -0,0 +1,200 @@
1
+ import { RecordExistsError, RecordNotFoundError, VersionConflictError } from "./driver.mjs";
2
+ import { compareStorageText, matchesRecord, queryIdentity, recordQuerySchema } from "./query.mjs";
3
+ import { StoreOperationConflictError } from "./indexed.mjs";
4
+ import { canonicalJson, collectionNameSchema, decodeCursor, digest, encodeCursor, indexKeySchema, recordIdSchema, sortKeySchema } from "../postgres/indexed-codec.mjs";
5
+ import { randomUUID } from "node:crypto";
6
+ import { z } from "zod";
7
+ //#region src/store/indexed-memory.ts
8
+ /** Creates an isolated transactional indexed store for local fixtures and tests. */
9
+ function createInMemoryIndexedStore() {
10
+ const namespace = randomUUID();
11
+ let state = {
12
+ countAttributes: /* @__PURE__ */ new Map(),
13
+ records: /* @__PURE__ */ new Map(),
14
+ entries: /* @__PURE__ */ new Map(),
15
+ operations: /* @__PURE__ */ new Map(),
16
+ byIndex: /* @__PURE__ */ new Map()
17
+ };
18
+ let available = Promise.resolve();
19
+ function key(collection, id) {
20
+ return JSON.stringify([collectionNameSchema.parse(collection), recordIdSchema.parse(id)]);
21
+ }
22
+ function ordered(left, right) {
23
+ return compareStorageText(left.sortKey, right.sortKey) || compareStorageText(left.collection, right.collection) || compareStorageText(left.id, right.id);
24
+ }
25
+ function pageRows(rows, index, order, limit, encoded) {
26
+ const cursor = encoded === void 0 ? void 0 : decodeCursor(encoded, namespace, index, order);
27
+ const sign = order === "asc" ? 1 : -1;
28
+ const filtered = rows.filter((row) => !cursor || sign * ordered(row, {
29
+ ...cursor,
30
+ summary: null,
31
+ version: 0
32
+ }) > 0).sort((left, right) => sign * ordered(left, right));
33
+ const entries = filtered.slice(0, limit);
34
+ const last = entries.at(-1);
35
+ return {
36
+ entries: structuredClone(entries),
37
+ nextCursor: filtered.length > entries.length && last ? encodeCursor({
38
+ version: 1,
39
+ namespace,
40
+ index,
41
+ order,
42
+ sortKey: last.sortKey,
43
+ collection: last.collection,
44
+ id: last.id
45
+ }) : null
46
+ };
47
+ }
48
+ function create(snapshot) {
49
+ const current = () => snapshot ?? state;
50
+ const changedIndexes = /* @__PURE__ */ new Set();
51
+ function writableIndex(index) {
52
+ if (!snapshot) throw new Error("Index writes require a transaction");
53
+ if (!changedIndexes.has(index)) {
54
+ snapshot.byIndex.set(index, new Map(snapshot.byIndex.get(index)));
55
+ changedIndexes.add(index);
56
+ }
57
+ return snapshot.byIndex.get(index);
58
+ }
59
+ const store = {
60
+ name: "memory",
61
+ async transaction(run) {
62
+ if (snapshot) return run(store);
63
+ const preceding = available;
64
+ let release;
65
+ available = new Promise((resolve) => {
66
+ release = resolve;
67
+ });
68
+ await preceding;
69
+ try {
70
+ const next = {
71
+ records: new Map(state.records),
72
+ entries: new Map(state.entries),
73
+ operations: new Map(state.operations),
74
+ countAttributes: new Map(state.countAttributes),
75
+ byIndex: new Map(state.byIndex)
76
+ };
77
+ const value = await run(create(next));
78
+ state = next;
79
+ return value;
80
+ } finally {
81
+ release();
82
+ }
83
+ },
84
+ async get(collection, id) {
85
+ return structuredClone(current().records.get(key(collection, id)) ?? null);
86
+ },
87
+ async write(input) {
88
+ if (!snapshot) return store.transaction((transaction) => transaction.write(input));
89
+ const recordKey = key(input.collection, input.id);
90
+ const operationId = recordIdSchema.parse(input.operationId);
91
+ const expectedVersion = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER - 1).parse(input.expectedVersion);
92
+ if (input.indexes.length > 32) throw new Error("A record can belong to at most 32 indexes");
93
+ const indexes = input.indexes.map((entry) => ({
94
+ index: indexKeySchema.parse(entry.index),
95
+ sortKey: sortKeySchema.parse(entry.sortKey),
96
+ summary: JSON.parse(canonicalJson(entry.summary, 8192)),
97
+ ...entry.countGroup ? { countGroup: {
98
+ group: indexKeySchema.parse(entry.countGroup.group),
99
+ attributes: JSON.parse(canonicalJson(entry.countGroup.attributes, 8192))
100
+ } } : {}
101
+ })).sort((a, b) => compareStorageText(a.index, b.index));
102
+ if (new Set(indexes.map((entry) => entry.index)).size !== indexes.length) throw new Error("Duplicate index membership");
103
+ const value = JSON.parse(canonicalJson(input.value, 262144));
104
+ const fingerprint = digest(canonicalJson({
105
+ collection: input.collection,
106
+ id: input.id,
107
+ expectedVersion,
108
+ value,
109
+ indexes
110
+ }, 614400));
111
+ const previous = snapshot.operations.get(operationId);
112
+ if (previous) {
113
+ if (previous.fingerprint !== fingerprint) throw new StoreOperationConflictError(operationId);
114
+ return { version: previous.version };
115
+ }
116
+ const existing = snapshot.records.get(recordKey);
117
+ if (expectedVersion === 0 && existing) throw new RecordExistsError(input.collection, input.id);
118
+ if (expectedVersion > 0 && !existing) throw new RecordNotFoundError(input.collection, input.id);
119
+ if (existing && existing.version !== expectedVersion) throw new VersionConflictError(input.collection, input.id, expectedVersion);
120
+ for (const entry of indexes) {
121
+ const attributes = canonicalJson(entry.countGroup ?? null, 16384);
122
+ const previous = snapshot.countAttributes.get(entry.index);
123
+ if (previous !== void 0 && previous !== attributes) throw new Error("Index count attributes are immutable");
124
+ snapshot.countAttributes.set(entry.index, attributes);
125
+ }
126
+ const version = expectedVersion + 1;
127
+ snapshot.records.set(recordKey, {
128
+ value,
129
+ version
130
+ });
131
+ for (const entry of snapshot.entries.get(recordKey) ?? []) writableIndex(entry.index).delete(recordKey);
132
+ const entries = indexes.map((entry) => ({
133
+ ...entry,
134
+ collection: input.collection,
135
+ id: input.id,
136
+ version
137
+ }));
138
+ for (const entry of entries) writableIndex(entry.index).set(recordKey, entry);
139
+ snapshot.entries.set(recordKey, entries);
140
+ snapshot.operations.set(operationId, {
141
+ fingerprint,
142
+ version
143
+ });
144
+ return { version };
145
+ },
146
+ async query(input) {
147
+ const index = indexKeySchema.parse(input.index);
148
+ const limit = z.number().int().min(1).max(100).parse(input.limit);
149
+ const order = z.enum(["asc", "desc"]).parse(input.order ?? "desc");
150
+ return pageRows([...current().byIndex.get(index)?.values() ?? []], index, order, limit, input.cursor);
151
+ },
152
+ async queryRecords({ sources: inputSources, ...input }) {
153
+ const sources = z.array(z.strictObject({
154
+ collection: collectionNameSchema,
155
+ index: indexKeySchema
156
+ })).min(1).max(8).parse(inputSources).sort((a, b) => compareStorageText(a.collection, b.collection));
157
+ if (new Set(sources.map((source) => source.collection)).size !== sources.length) throw new Error("Record queries require exactly one source index per collection");
158
+ const query = recordQuerySchema.parse(input);
159
+ const index = `records:${queryIdentity(JSON.stringify(sources), query)}`;
160
+ const records = current();
161
+ const rows = sources.flatMap((source) => [...records.byIndex.get(source.index)?.values() ?? []].filter((entry) => entry.collection === source.collection)).filter((entry) => matchesRecord(records.records.get(key(entry.collection, entry.id)).value, {
162
+ ...query,
163
+ since: void 0,
164
+ until: void 0
165
+ }) && (!query.since || entry.sortKey >= query.since) && (!query.until || entry.sortKey < query.until));
166
+ const page = pageRows(rows, index, query.order, query.limit, query.cursor);
167
+ return {
168
+ ...query.includeTotal ? { totalCount: rows.length } : {},
169
+ entries: page.entries.map((entry) => ({
170
+ collection: entry.collection,
171
+ id: entry.id,
172
+ record: structuredClone(records.records.get(key(entry.collection, entry.id)))
173
+ })),
174
+ nextCursor: page.nextCursor
175
+ };
176
+ },
177
+ async sumCounts(group, query) {
178
+ let total = 0;
179
+ for (const [index, attributes] of current().countAttributes) {
180
+ const metadata = JSON.parse(attributes);
181
+ if (metadata?.group === group && matchesRecord(metadata.attributes, {
182
+ ...query,
183
+ limit: 1
184
+ })) total += current().byIndex.get(index)?.size ?? 0;
185
+ }
186
+ return total;
187
+ },
188
+ async count(index) {
189
+ indexKeySchema.parse(index);
190
+ return current().byIndex.get(index)?.size ?? 0;
191
+ }
192
+ };
193
+ return store;
194
+ }
195
+ return create();
196
+ }
197
+ //#endregion
198
+ export { createInMemoryIndexedStore };
199
+
200
+ //# sourceMappingURL=indexed-memory.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexed-memory.mjs","names":[],"sources":["../../src/store/indexed-memory.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { z } from \"zod\";\nimport {\n canonicalJson,\n collectionNameSchema,\n decodeCursor,\n digest,\n encodeCursor,\n indexKeySchema,\n recordIdSchema,\n sortKeySchema,\n} from \"../postgres/indexed-codec\";\nimport {\n RecordExistsError,\n RecordNotFoundError,\n VersionConflictError,\n type StoredRecord,\n} from \"./driver\";\nimport { StoreOperationConflictError, type IndexedStore, type StoreIndexRecord } from \"./indexed\";\nimport { compareStorageText, matchesRecord, queryIdentity, recordQuerySchema } from \"./query\";\n\ninterface MemoryState {\n countAttributes: Map<string, string>;\n records: Map<string, StoredRecord>;\n entries: Map<string, StoreIndexRecord[]>;\n operations: Map<string, { fingerprint: string; version: number }>;\n byIndex: Map<string, Map<string, StoreIndexRecord>>;\n}\n\n/** Creates an isolated transactional indexed store for local fixtures and tests. */\nexport function createInMemoryIndexedStore(): IndexedStore {\n const namespace = randomUUID();\n let state: MemoryState = {\n countAttributes: new Map(),\n records: new Map(),\n entries: new Map(),\n operations: new Map(),\n byIndex: new Map(),\n };\n let available = Promise.resolve();\n function key(collection: string, id: string) {\n return JSON.stringify([collectionNameSchema.parse(collection), recordIdSchema.parse(id)]);\n }\n function ordered(left: StoreIndexRecord, right: StoreIndexRecord) {\n return (\n compareStorageText(left.sortKey, right.sortKey) ||\n compareStorageText(left.collection, right.collection) ||\n compareStorageText(left.id, right.id)\n );\n }\n function pageRows(\n rows: StoreIndexRecord[],\n index: string,\n order: \"asc\" | \"desc\",\n limit: number,\n encoded?: string,\n ) {\n const cursor =\n encoded === undefined ? undefined : decodeCursor(encoded, namespace, index, order);\n const sign = order === \"asc\" ? 1 : -1;\n const filtered = rows\n .filter((row) => !cursor || sign * ordered(row, { ...cursor, summary: null, version: 0 }) > 0)\n .sort((left, right) => sign * ordered(left, right));\n const entries = filtered.slice(0, limit);\n const last = entries.at(-1);\n return {\n entries: structuredClone(entries),\n nextCursor:\n filtered.length > entries.length && last\n ? encodeCursor({\n version: 1,\n namespace,\n index,\n order,\n sortKey: last.sortKey,\n collection: last.collection,\n id: last.id,\n })\n : null,\n };\n }\n function create(snapshot?: MemoryState): IndexedStore {\n const current = () => snapshot ?? state;\n const changedIndexes = new Set<string>();\n function writableIndex(index: string) {\n if (!snapshot) throw new Error(\"Index writes require a transaction\");\n if (!changedIndexes.has(index)) {\n snapshot.byIndex.set(index, new Map(snapshot.byIndex.get(index)));\n changedIndexes.add(index);\n }\n return snapshot.byIndex.get(index)!;\n }\n const store: IndexedStore = {\n name: \"memory\",\n async transaction(run) {\n if (snapshot) return run(store);\n const preceding = available;\n let release!: () => void;\n available = new Promise<void>((resolve) => {\n release = resolve;\n });\n await preceding;\n try {\n // Values are replaced on write and cloned at every public read boundary.\n const next: MemoryState = {\n records: new Map(state.records),\n entries: new Map(state.entries),\n operations: new Map(state.operations),\n countAttributes: new Map(state.countAttributes),\n byIndex: new Map(state.byIndex),\n };\n const value = await run(create(next));\n state = next;\n return value;\n } finally {\n release();\n }\n },\n async get(collection, id) {\n return structuredClone(current().records.get(key(collection, id)) ?? null);\n },\n async write(input) {\n if (!snapshot) return store.transaction((transaction) => transaction.write(input));\n const recordKey = key(input.collection, input.id);\n const operationId = recordIdSchema.parse(input.operationId);\n const expectedVersion = z\n .number()\n .int()\n .min(0)\n .max(Number.MAX_SAFE_INTEGER - 1)\n .parse(input.expectedVersion);\n if (input.indexes.length > 32) throw new Error(\"A record can belong to at most 32 indexes\");\n const indexes = input.indexes\n .map((entry) => ({\n index: indexKeySchema.parse(entry.index),\n sortKey: sortKeySchema.parse(entry.sortKey),\n summary: JSON.parse(canonicalJson(entry.summary, 8 * 1024)) as unknown,\n ...(entry.countGroup\n ? {\n countGroup: {\n group: indexKeySchema.parse(entry.countGroup.group),\n attributes: JSON.parse(\n canonicalJson(entry.countGroup.attributes, 8 * 1024),\n ) as unknown,\n },\n }\n : {}),\n }))\n .sort((a, b) => compareStorageText(a.index, b.index));\n if (new Set(indexes.map((entry) => entry.index)).size !== indexes.length)\n throw new Error(\"Duplicate index membership\");\n const value = JSON.parse(canonicalJson(input.value, 256 * 1024)) as unknown;\n const fingerprint = digest(\n canonicalJson(\n { collection: input.collection, id: input.id, expectedVersion, value, indexes },\n 600 * 1024,\n ),\n );\n const previous = snapshot.operations.get(operationId);\n if (previous) {\n if (previous.fingerprint !== fingerprint)\n throw new StoreOperationConflictError(operationId);\n return { version: previous.version };\n }\n const existing = snapshot.records.get(recordKey);\n if (expectedVersion === 0 && existing)\n throw new RecordExistsError(input.collection, input.id);\n if (expectedVersion > 0 && !existing)\n throw new RecordNotFoundError(input.collection, input.id);\n if (existing && existing.version !== expectedVersion)\n throw new VersionConflictError(input.collection, input.id, expectedVersion);\n for (const entry of indexes) {\n const attributes = canonicalJson(entry.countGroup ?? null, 16 * 1024);\n const previous = snapshot.countAttributes.get(entry.index);\n if (previous !== undefined && previous !== attributes)\n throw new Error(\"Index count attributes are immutable\");\n snapshot.countAttributes.set(entry.index, attributes);\n }\n const version = expectedVersion + 1;\n snapshot.records.set(recordKey, { value, version });\n for (const entry of snapshot.entries.get(recordKey) ?? [])\n writableIndex(entry.index).delete(recordKey);\n const entries = indexes.map((entry) => ({\n ...entry,\n collection: input.collection,\n id: input.id,\n version,\n }));\n for (const entry of entries) writableIndex(entry.index).set(recordKey, entry);\n snapshot.entries.set(recordKey, entries);\n snapshot.operations.set(operationId, { fingerprint, version });\n return { version };\n },\n async query(input) {\n const index = indexKeySchema.parse(input.index);\n const limit = z.number().int().min(1).max(100).parse(input.limit);\n const order = z.enum([\"asc\", \"desc\"]).parse(input.order ?? \"desc\");\n return pageRows(\n [...(current().byIndex.get(index)?.values() ?? [])],\n index,\n order,\n limit,\n input.cursor,\n );\n },\n async queryRecords({ sources: inputSources, ...input }) {\n const sources = z\n .array(z.strictObject({ collection: collectionNameSchema, index: indexKeySchema }))\n .min(1)\n .max(8)\n .parse(inputSources)\n .sort((a, b) => compareStorageText(a.collection, b.collection));\n if (new Set(sources.map((source) => source.collection)).size !== sources.length)\n throw new Error(\"Record queries require exactly one source index per collection\");\n const query = recordQuerySchema.parse(input);\n const index = `records:${queryIdentity(JSON.stringify(sources), query)}`;\n const records = current();\n const rows = sources\n .flatMap((source) =>\n [...(records.byIndex.get(source.index)?.values() ?? [])].filter(\n (entry) => entry.collection === source.collection,\n ),\n )\n .filter(\n (entry) =>\n matchesRecord(records.records.get(key(entry.collection, entry.id))!.value, {\n ...query,\n since: undefined,\n until: undefined,\n }) &&\n (!query.since || entry.sortKey >= query.since) &&\n (!query.until || entry.sortKey < query.until),\n );\n const page = pageRows(rows, index, query.order, query.limit, query.cursor);\n return {\n ...(query.includeTotal ? { totalCount: rows.length } : {}),\n entries: page.entries.map((entry) => ({\n collection: entry.collection,\n id: entry.id,\n record: structuredClone(records.records.get(key(entry.collection, entry.id))!),\n })),\n nextCursor: page.nextCursor,\n };\n },\n async sumCounts(group, query) {\n let total = 0;\n for (const [index, attributes] of current().countAttributes) {\n const metadata = JSON.parse(attributes) as StoreIndexRecord[\"countGroup\"] | null;\n if (\n metadata?.group === group &&\n matchesRecord(metadata.attributes, { ...query, limit: 1 })\n )\n total += current().byIndex.get(index)?.size ?? 0;\n }\n return total;\n },\n async count(index) {\n indexKeySchema.parse(index);\n return current().byIndex.get(index)?.size ?? 0;\n },\n };\n return store;\n }\n return create();\n}\n"],"mappings":"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"}