@openagentsy/memory 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +33 -2
  3. package/dist/acceptance.test.d.ts +2 -0
  4. package/dist/acceptance.test.d.ts.map +1 -0
  5. package/dist/acceptance.test.js +138 -0
  6. package/dist/acceptance.test.js.map +1 -0
  7. package/dist/fixtures.d.ts +30 -0
  8. package/dist/fixtures.d.ts.map +1 -0
  9. package/dist/fixtures.js +49 -0
  10. package/dist/fixtures.js.map +1 -0
  11. package/dist/inbox.d.ts +131 -0
  12. package/dist/inbox.d.ts.map +1 -0
  13. package/dist/inbox.js +161 -0
  14. package/dist/inbox.js.map +1 -0
  15. package/dist/inbox.test.d.ts +2 -0
  16. package/dist/inbox.test.d.ts.map +1 -0
  17. package/dist/inbox.test.js +144 -0
  18. package/dist/inbox.test.js.map +1 -0
  19. package/dist/index.d.ts +15 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +15 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/model.d.ts +85 -0
  24. package/dist/model.d.ts.map +1 -0
  25. package/dist/model.js +71 -0
  26. package/dist/model.js.map +1 -0
  27. package/dist/permissions.test.d.ts +2 -0
  28. package/dist/permissions.test.d.ts.map +1 -0
  29. package/dist/permissions.test.js +53 -0
  30. package/dist/permissions.test.js.map +1 -0
  31. package/dist/store.d.ts +142 -0
  32. package/dist/store.d.ts.map +1 -0
  33. package/dist/store.js +263 -0
  34. package/dist/store.js.map +1 -0
  35. package/dist/store.test.d.ts +2 -0
  36. package/dist/store.test.d.ts.map +1 -0
  37. package/dist/store.test.js +223 -0
  38. package/dist/store.test.js.map +1 -0
  39. package/dist/tools.d.ts +86 -0
  40. package/dist/tools.d.ts.map +1 -0
  41. package/dist/tools.js +108 -0
  42. package/dist/tools.js.map +1 -0
  43. package/dist/tools.test.d.ts +2 -0
  44. package/dist/tools.test.d.ts.map +1 -0
  45. package/dist/tools.test.js +142 -0
  46. package/dist/tools.test.js.map +1 -0
  47. package/package.json +47 -4
package/README.md CHANGED
@@ -1,3 +1,34 @@
1
- # Temporary Holding Version
1
+ # @openagentsy/memory
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The consumer side of the memory pipeline: the decision, the store, and the
4
+ way back in. The producer side is the archivist, which posts candidate
5
+ memories as board records.
6
+
7
+ `MemoryStore` is an append-only `memory.jsonl` at the state root
8
+ (`.openagentsy/memory.jsonl`), one JSON object per line. `MemoryInbox` is
9
+ the board's pending `bean: memory` records, and the decision on them.
10
+ `createMemoryTools` defines `oa_memory_search`, `oa_memory_list` and
11
+ `oa_memory_decide`.
12
+
13
+ - **Redaction runs first.** `redact()` from `@openagentsy/core` runs over the
14
+ text before an entry can be appended, and the count comes back with it.
15
+ - **Dedup on append.** The normalized text is hashed with `etagOf`, and a
16
+ duplicate is refused with the id of the entry that already holds it.
17
+ - **The decision is human-only.** `roles.yaml` denies `oa_memory_decide` and
18
+ `openagentsy_memory_decide` to every role: the tool exists and works, and
19
+ the map is what stops an agent from calling it.
20
+ - **No auto-injection.** Nothing here touches a prompt. A memory is found by
21
+ asking for it.
22
+ - **Unverified memories are redacted in a search** until a person marks
23
+ them verified, and approving a candidate is not verifying it.
24
+
25
+ ## The known limitation
26
+
27
+ The generic-assign redaction pattern in `@openagentsy/core` needs more than
28
+ four bits of entropy per character, so a pure-hex secret is never flagged by
29
+ that pattern. The other six patterns still cover their own shapes. This is
30
+ the plan's own §8.4 specification, and it is a 2f review candidate.
31
+
32
+ ## Licence
33
+
34
+ GPL-3.0-or-later.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=acceptance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acceptance.test.d.ts","sourceRoot":"","sources":["../src/acceptance.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,138 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readdir, readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { toV2Permissions, withProtectedPaths } from '@openagentsy/core';
5
+ import { describe, expect, it } from 'vitest';
6
+ import { candidateRecord, fakeReader, loadRealRoles, repositoryRoot, temporaryRoot, } from './fixtures.js';
7
+ import { MemoryInbox } from './inbox.js';
8
+ import * as memory from './index.js';
9
+ import { MemoryStore } from './store.js';
10
+ import { createMemoryTools } from './tools.js';
11
+ const ctx = { sessionID: 'test-session', agent: 'oa-builder' };
12
+ /** Every file under a directory, so a skill's own folder is read too. */
13
+ async function promptFiles(dir) {
14
+ const files = [];
15
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
16
+ const path = join(dir, entry.name);
17
+ if (entry.isDirectory())
18
+ files.push(...(await promptFiles(path)));
19
+ else
20
+ files.push(path);
21
+ }
22
+ return files;
23
+ }
24
+ /** The two names the decide tool is called by. */
25
+ const DECIDE_NAMES = ['oa_memory_decide', 'openagentsy_memory_decide'];
26
+ /**
27
+ * The prompt body, without the frontmatter.
28
+ *
29
+ * The frontmatter carries the permission map, and the permission map may
30
+ * name a tool: the agent is allowed to search. What the exit test is
31
+ * about is a prompt that calls it, so the frontmatter is not the text
32
+ * under test.
33
+ */
34
+ function promptBody(content) {
35
+ if (!content.startsWith('---'))
36
+ return content;
37
+ const lines = content.split('\n');
38
+ const end = lines.indexOf('---', 1);
39
+ return end === -1 ? content : lines.slice(end + 1).join('\n');
40
+ }
41
+ describe('§4.6 the exit tests', () => {
42
+ it('1. a candidate a person approved is found by a term in its text', async () => {
43
+ const { root, remove } = await temporaryRoot();
44
+ try {
45
+ const record = candidateRecord({
46
+ id: 7,
47
+ action: 'The lease guard refuses a write to a file no lease covers.',
48
+ });
49
+ const records = [record];
50
+ const store = new MemoryStore({ root });
51
+ const inbox = new MemoryInbox({ root, read: fakeReader(records), store });
52
+ const tools = createMemoryTools({ store, inbox });
53
+ const decision = await inbox.decide(7, 'approve', 'daniel');
54
+ expect(decision.ok).toBe(true);
55
+ const path = join(root, '.openagentsy', 'memory.jsonl');
56
+ expect(existsSync(path)).toBe(true);
57
+ const lines = (await readFile(path, 'utf8')).trim().split('\n');
58
+ expect(lines).toHaveLength(1);
59
+ const found = (await tools.oa_memory_search.execute({ q: 'lease' }, ctx));
60
+ expect(found.hits).toHaveLength(1);
61
+ const shown = (await tools.oa_memory_search.execute({ q: 'lease', includeUnverified: true }, ctx));
62
+ expect(shown.hits[0]?.text).toContain('lease guard');
63
+ }
64
+ finally {
65
+ await remove();
66
+ }
67
+ });
68
+ it('2. a rejected candidate never enters the store, and its record says rejected', async () => {
69
+ const { root, remove } = await temporaryRoot();
70
+ try {
71
+ const records = [candidateRecord({ id: 7 })];
72
+ const store = new MemoryStore({ root });
73
+ const inbox = new MemoryInbox({ root, read: fakeReader(records), store });
74
+ const decision = await inbox.decide(7, 'reject', 'daniel');
75
+ expect(decision.ok).toBe(true);
76
+ expect(await store.entries()).toEqual([]);
77
+ expect(existsSync(join(root, '.openagentsy', 'memory.jsonl'))).toBe(false);
78
+ expect(records[0]?.state).toBe('rejected');
79
+ }
80
+ finally {
81
+ await remove();
82
+ }
83
+ });
84
+ it('3. the permission map alone blocks the decide tool, under both of its names', async () => {
85
+ const roles = await loadRealRoles();
86
+ const names = Object.keys(roles.roles);
87
+ expect(names.length).toBeGreaterThan(0);
88
+ for (const name of names) {
89
+ const role = roles.roles[name];
90
+ if (role === undefined)
91
+ continue;
92
+ const permissions = toV2Permissions(withProtectedPaths(role, roles.protectedPaths));
93
+ for (const tool of DECIDE_NAMES) {
94
+ expect(permissions).toContainEqual({ action: tool, resource: '*', effect: 'deny' });
95
+ }
96
+ }
97
+ });
98
+ it('4. a seeded secret never reaches the store, and the count comes back', async () => {
99
+ const { root, remove } = await temporaryRoot();
100
+ try {
101
+ const seeded = 'AKIAIOSFODNN7EXAMPLE';
102
+ const records = [
103
+ candidateRecord({
104
+ id: 7,
105
+ action: `The deploy key is ${seeded} and it must not be kept.`,
106
+ }),
107
+ ];
108
+ const store = new MemoryStore({ root });
109
+ const inbox = new MemoryInbox({ root, read: fakeReader(records), store });
110
+ const decision = await inbox.decide(7, 'approve', 'daniel');
111
+ expect(decision.ok).toBe(true);
112
+ if (!decision.ok)
113
+ return;
114
+ expect(decision.redactions).toBe(1);
115
+ const written = await readFile(join(root, '.openagentsy', 'memory.jsonl'), 'utf8');
116
+ expect(written).not.toContain(seeded);
117
+ }
118
+ finally {
119
+ await remove();
120
+ }
121
+ });
122
+ it('5. the package injects nothing into a prompt, and no prompt calls the search', async () => {
123
+ const exported = Object.keys(memory);
124
+ expect(exported.filter((name) => /transform|system|prime/i.test(name))).toEqual([]);
125
+ for (const dir of ['agents', 'commands', 'skills']) {
126
+ const path = join(repositoryRoot, '.opencode', dir);
127
+ if (!existsSync(path))
128
+ continue;
129
+ for (const file of await promptFiles(path)) {
130
+ const content = await readFile(file, 'utf8');
131
+ // The permission map may name the tool: the agent is allowed to
132
+ // search. What must not happen is a prompt that calls it.
133
+ expect(promptBody(content)).not.toContain('oa_memory_search');
134
+ }
135
+ }
136
+ });
137
+ });
138
+ //# sourceMappingURL=acceptance.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acceptance.test.js","sourceRoot":"","sources":["../src/acceptance.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type RolesFile } from '@openagentsy/core';
2
+ import type { BoardReader, BoardRecord } from './inbox.js';
3
+ /**
4
+ * The fakes the tests share.
5
+ *
6
+ * A board reader that holds its records in memory, a state root in a
7
+ * temporary directory, and the real roles file of the repository. The
8
+ * package never imports the board server, so this is how a test stands in
9
+ * for it.
10
+ */
11
+ /** The root of the repository the tests run in. */
12
+ export declare const repositoryRoot: string;
13
+ /** A state root in a temporary directory, and the way to remove it. */
14
+ export declare function temporaryRoot(): Promise<{
15
+ root: string;
16
+ remove: () => Promise<void>;
17
+ }>;
18
+ /** A candidate as the archivist posts it. */
19
+ export declare function candidateRecord(overrides?: Partial<BoardRecord>): BoardRecord;
20
+ /**
21
+ * A reader that holds its records in memory.
22
+ *
23
+ * The records are the same objects every time, so a decision that marks one
24
+ * is visible to the next read, the way a board that has been written to
25
+ * would be.
26
+ */
27
+ export declare function fakeReader(records: BoardRecord[]): BoardReader;
28
+ /** The real roles file of the repository. */
29
+ export declare function loadRealRoles(): Promise<RolesFile>;
30
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAIA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;GAOG;AAEH,mDAAmD;AACnD,eAAO,MAAM,cAAc,QAAsD,CAAC;AAElF,uEAAuE;AACvE,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAG5F;AAED,6CAA6C;AAC7C,wBAAgB,eAAe,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAYjF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,CAE9D;AAED,6CAA6C;AAC7C,wBAAsB,aAAa,IAAI,OAAO,CAAC,SAAS,CAAC,CAExD"}
@@ -0,0 +1,49 @@
1
+ import { mkdtemp, rm } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { loadRoles } from '@openagentsy/core';
6
+ /**
7
+ * The fakes the tests share.
8
+ *
9
+ * A board reader that holds its records in memory, a state root in a
10
+ * temporary directory, and the real roles file of the repository. The
11
+ * package never imports the board server, so this is how a test stands in
12
+ * for it.
13
+ */
14
+ /** The root of the repository the tests run in. */
15
+ export const repositoryRoot = fileURLToPath(new URL('../../..', import.meta.url));
16
+ /** A state root in a temporary directory, and the way to remove it. */
17
+ export async function temporaryRoot() {
18
+ const root = await mkdtemp(join(tmpdir(), 'openagentsy-memory-'));
19
+ return { root, remove: () => rm(root, { recursive: true, force: true }) };
20
+ }
21
+ /** A candidate as the archivist posts it. */
22
+ export function candidateRecord(overrides = {}) {
23
+ return {
24
+ id: 1,
25
+ bean: 'memory',
26
+ kind: 'finding',
27
+ from: 'oa-archivist',
28
+ to: 'agentsy',
29
+ action: 'The lease guard refuses a write to a file no lease covers.',
30
+ state: 'build · 2a-1',
31
+ confidence: 'inferred',
32
+ ...overrides,
33
+ };
34
+ }
35
+ /**
36
+ * A reader that holds its records in memory.
37
+ *
38
+ * The records are the same objects every time, so a decision that marks one
39
+ * is visible to the next read, the way a board that has been written to
40
+ * would be.
41
+ */
42
+ export function fakeReader(records) {
43
+ return async () => records;
44
+ }
45
+ /** The real roles file of the repository. */
46
+ export async function loadRealRoles() {
47
+ return loadRoles(join(repositoryRoot, 'roles.yaml'));
48
+ }
49
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAkB,MAAM,mBAAmB,CAAC;AAG9D;;;;;;;GAOG;AAEH,mDAAmD;AACnD,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAElF,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAClE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,eAAe,CAAC,YAAkC,EAAE;IAClE,OAAO;QACL,EAAE,EAAE,CAAC;QACL,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,cAAc;QACpB,EAAE,EAAE,SAAS;QACb,MAAM,EAAE,4DAA4D;QACpE,KAAK,EAAE,cAAc;QACrB,UAAU,EAAE,UAAU;QACtB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,OAAsB;IAC/C,OAAO,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC;AAC7B,CAAC;AAED,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,OAAO,SAAS,CAAC,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,131 @@
1
+ import { type MemoryConfidence, type MemoryEntry } from './model.js';
2
+ import type { MemoryAuditSink, MemoryStore } from './store.js';
3
+ /**
4
+ * The inbox of candidate memories.
5
+ *
6
+ * The candidates are the board's records: the archivist posts them as
7
+ * `bean: memory` findings, and a person decides on each one. The inbox is
8
+ * not a second queue - it is the board's pending `bean: memory` records, so
9
+ * there is nothing to drift between what the archivist posted and what the
10
+ * inbox shows.
11
+ */
12
+ /** The bean a candidate memory is posted under. */
13
+ export declare const MEMORY_BEAN = "memory";
14
+ /** The states a decided candidate carries. */
15
+ export declare const DECIDED_STATES: readonly ["promoted", "rejected"];
16
+ /** The state a decided candidate carries. */
17
+ export type DecisionState = (typeof DECIDED_STATES)[number];
18
+ /**
19
+ * A board record, as the inbox reads it.
20
+ *
21
+ * The shape the board already has. The package never imports the board
22
+ * server, so the reader is injected: the wiring that will register these
23
+ * tools passes the real reader, and a test passes a fake one.
24
+ */
25
+ export type BoardRecord = {
26
+ id: number;
27
+ bean: string;
28
+ kind: string;
29
+ from: string;
30
+ to?: string;
31
+ action: string;
32
+ state: string;
33
+ confidence: string;
34
+ createdAt?: string;
35
+ };
36
+ /** The reader the inbox gets its candidates from. */
37
+ export type BoardReader = (root: string) => Promise<BoardRecord[]>;
38
+ export type MemoryInboxOptions = {
39
+ /** The root the reader is given. */
40
+ root: string;
41
+ /** The reader. */
42
+ read: BoardReader;
43
+ /** The store an approved candidate is appended to. */
44
+ store: MemoryStore;
45
+ /** Where a decision is audited. */
46
+ onAudit?: MemoryAuditSink;
47
+ /**
48
+ * How a decided candidate's state is written back to the board.
49
+ *
50
+ * The board's records are written through the board server, and this
51
+ * package never imports it, so the write is injected like the read. When
52
+ * it is absent the decision still updates the record the reader returned,
53
+ * which is what a reader that holds its records in memory sees.
54
+ */
55
+ markState?: (recordId: number, state: DecisionState) => void | Promise<void>;
56
+ };
57
+ /** What a decision did. */
58
+ export type DecideOutcome = {
59
+ ok: true;
60
+ decision: 'approve' | 'reject';
61
+ recordId: number;
62
+ state: DecisionState;
63
+ redactions: number;
64
+ entry?: MemoryEntry;
65
+ } | {
66
+ ok: false;
67
+ reason: 'unknown-record' | 'not-a-candidate' | 'already-decided' | 'duplicate' | 'invalid';
68
+ detail: string;
69
+ recordId: number;
70
+ redactions: number;
71
+ id?: string;
72
+ };
73
+ /** A candidate, read out of a board record. */
74
+ export type Candidate = {
75
+ text: string;
76
+ category: string;
77
+ source: string;
78
+ confidence: MemoryConfidence;
79
+ };
80
+ /**
81
+ * The pending candidates.
82
+ *
83
+ * A candidate is a `bean: memory` record that no person has decided on yet.
84
+ * A record that was promoted or rejected is not pending, whatever else it
85
+ * says.
86
+ */
87
+ export declare class MemoryInbox {
88
+ private readonly root;
89
+ private readonly read;
90
+ private readonly store;
91
+ private readonly onAudit;
92
+ private readonly markState;
93
+ constructor(options: MemoryInboxOptions);
94
+ /** The candidates no person has decided on yet. */
95
+ pending(): Promise<BoardRecord[]>;
96
+ /**
97
+ * Decide on a candidate.
98
+ *
99
+ * Approving redacts the text, appends it to the store and marks the
100
+ * record promoted; rejecting marks the record rejected and the store never
101
+ * sees the text. Either way the decision is audited.
102
+ */
103
+ decide(recordId: number, decision: 'approve' | 'reject', decidedBy: string): Promise<DecideOutcome>;
104
+ /** Write the state back, both on the record and on the board. */
105
+ private mark;
106
+ }
107
+ /** Whether a record is a candidate no person has decided on. */
108
+ export declare function isPending(record: BoardRecord): boolean;
109
+ /** Whether a state says the candidate was decided. */
110
+ export declare function isDecided(state: string): boolean;
111
+ /**
112
+ * The candidate a record carries.
113
+ *
114
+ * The archivist posts the candidate in the action, and its category and
115
+ * source in the state, so the state is where the two are read from. The
116
+ * source of the memory is the record the candidate came from, and what it
117
+ * pointed at.
118
+ */
119
+ export declare function parseCandidate(record: BoardRecord): Candidate;
120
+ /**
121
+ * The category and the source a pending record carries.
122
+ *
123
+ * The two are separated by the same mark the memory's own source uses, so a
124
+ * state of `build · packages/core/src/x.ts:12` names the category `build`
125
+ * and the source `packages/core/src/x.ts:12`.
126
+ */
127
+ export declare function parseCandidateState(state: string): {
128
+ category: string;
129
+ source: string;
130
+ };
131
+ //# sourceMappingURL=inbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"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"}
package/dist/inbox.js ADDED
@@ -0,0 +1,161 @@
1
+ import { DEFAULT_CATEGORY, isMemoryConfidence, } from './model.js';
2
+ /**
3
+ * The inbox of candidate memories.
4
+ *
5
+ * The candidates are the board's records: the archivist posts them as
6
+ * `bean: memory` findings, and a person decides on each one. The inbox is
7
+ * not a second queue - it is the board's pending `bean: memory` records, so
8
+ * there is nothing to drift between what the archivist posted and what the
9
+ * inbox shows.
10
+ */
11
+ /** The bean a candidate memory is posted under. */
12
+ export const MEMORY_BEAN = 'memory';
13
+ /** The states a decided candidate carries. */
14
+ export const DECIDED_STATES = ['promoted', 'rejected'];
15
+ /**
16
+ * The pending candidates.
17
+ *
18
+ * A candidate is a `bean: memory` record that no person has decided on yet.
19
+ * A record that was promoted or rejected is not pending, whatever else it
20
+ * says.
21
+ */
22
+ export class MemoryInbox {
23
+ root;
24
+ read;
25
+ store;
26
+ onAudit;
27
+ markState;
28
+ constructor(options) {
29
+ this.root = options.root;
30
+ this.read = options.read;
31
+ this.store = options.store;
32
+ this.onAudit = options.onAudit;
33
+ this.markState = options.markState;
34
+ }
35
+ /** The candidates no person has decided on yet. */
36
+ async pending() {
37
+ const records = await this.read(this.root);
38
+ return records.filter((record) => isPending(record));
39
+ }
40
+ /**
41
+ * Decide on a candidate.
42
+ *
43
+ * Approving redacts the text, appends it to the store and marks the
44
+ * record promoted; rejecting marks the record rejected and the store never
45
+ * sees the text. Either way the decision is audited.
46
+ */
47
+ async decide(recordId, decision, decidedBy) {
48
+ const records = await this.read(this.root);
49
+ const record = records.find((candidate) => candidate.id === recordId);
50
+ if (record === undefined) {
51
+ return {
52
+ ok: false,
53
+ reason: 'unknown-record',
54
+ detail: `no record carries the id ${recordId}`,
55
+ recordId,
56
+ redactions: 0,
57
+ };
58
+ }
59
+ if (record.bean !== MEMORY_BEAN) {
60
+ return {
61
+ ok: false,
62
+ reason: 'not-a-candidate',
63
+ detail: `the record ${recordId} is not a memory candidate`,
64
+ recordId,
65
+ redactions: 0,
66
+ };
67
+ }
68
+ if (!isPending(record)) {
69
+ return {
70
+ ok: false,
71
+ reason: 'already-decided',
72
+ detail: `the record ${recordId} was already decided`,
73
+ recordId,
74
+ redactions: 0,
75
+ };
76
+ }
77
+ if (decision === 'reject') {
78
+ await this.mark(record, 'rejected');
79
+ await this.onAudit?.('oa.memory.decide', { recordId, decidedBy, decision });
80
+ return { ok: true, decision, recordId, state: 'rejected', redactions: 0 };
81
+ }
82
+ const candidate = parseCandidate(record);
83
+ const appended = await this.store.append({
84
+ text: candidate.text,
85
+ category: candidate.category,
86
+ source: candidate.source,
87
+ confidence: candidate.confidence,
88
+ });
89
+ if (!appended.ok) {
90
+ return {
91
+ ok: false,
92
+ reason: appended.reason,
93
+ detail: appended.reason === 'duplicate' ? 'the memory is already known' : appended.detail,
94
+ recordId,
95
+ redactions: appended.redactions,
96
+ ...(appended.reason === 'duplicate' ? { id: appended.id } : {}),
97
+ };
98
+ }
99
+ await this.mark(record, 'promoted');
100
+ await this.onAudit?.('oa.memory.decide', { recordId, decidedBy, decision });
101
+ return {
102
+ ok: true,
103
+ decision,
104
+ recordId,
105
+ state: 'promoted',
106
+ redactions: appended.redactions,
107
+ entry: appended.entry,
108
+ };
109
+ }
110
+ /** Write the state back, both on the record and on the board. */
111
+ async mark(record, state) {
112
+ record.state = state;
113
+ await this.markState?.(record.id, state);
114
+ }
115
+ }
116
+ /** Whether a record is a candidate no person has decided on. */
117
+ export function isPending(record) {
118
+ return record.bean === MEMORY_BEAN && !isDecided(record.state);
119
+ }
120
+ /** Whether a state says the candidate was decided. */
121
+ export function isDecided(state) {
122
+ return DECIDED_STATES.includes(state);
123
+ }
124
+ /**
125
+ * The candidate a record carries.
126
+ *
127
+ * The archivist posts the candidate in the action, and its category and
128
+ * source in the state, so the state is where the two are read from. The
129
+ * source of the memory is the record the candidate came from, and what it
130
+ * pointed at.
131
+ */
132
+ export function parseCandidate(record) {
133
+ const { category, source } = parseCandidateState(record.state);
134
+ return {
135
+ text: record.action,
136
+ category,
137
+ source: `${record.id} · ${source}`,
138
+ confidence: isMemoryConfidence(record.confidence) ? record.confidence : 'guess',
139
+ };
140
+ }
141
+ /** The mark that separates a candidate's category from its source. */
142
+ const SEPARATOR = ' · ';
143
+ /**
144
+ * The category and the source a pending record carries.
145
+ *
146
+ * The two are separated by the same mark the memory's own source uses, so a
147
+ * state of `build · packages/core/src/x.ts:12` names the category `build`
148
+ * and the source `packages/core/src/x.ts:12`.
149
+ */
150
+ export function parseCandidateState(state) {
151
+ const separator = state.indexOf(SEPARATOR);
152
+ if (separator === -1) {
153
+ return { category: DEFAULT_CATEGORY, source: state.trim() };
154
+ }
155
+ const category = state.slice(0, separator).trim();
156
+ return {
157
+ category: category.length > 0 ? category : DEFAULT_CATEGORY,
158
+ source: state.slice(separator + SEPARATOR.length).trim(),
159
+ };
160
+ }
161
+ //# sourceMappingURL=inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../src/inbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inbox.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.test.d.ts","sourceRoot":"","sources":["../src/inbox.test.ts"],"names":[],"mappings":""}