@brightbeamai/chap-coordinator 0.3.1 → 0.3.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.
- package/dist/{api-BkvwN42r.d.cts → api-59aU01Bs.d.cts} +48 -3
- package/dist/{api-Ddy9fqkY.d.ts → api-CxciGFHA.d.ts} +48 -3
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/index.cjs +535 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +534 -125
- package/dist/index.js.map +1 -1
- package/dist/storage/sqlite.cjs +270 -34
- package/dist/storage/sqlite.cjs.map +1 -1
- package/dist/storage/sqlite.d.cts +60 -10
- package/dist/storage/sqlite.d.ts +60 -10
- package/dist/storage/sqlite.js +270 -34
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/store.cjs +150 -9
- package/dist/storage/store.cjs.map +1 -1
- package/dist/storage/store.d.cts +118 -40
- package/dist/storage/store.d.ts +118 -40
- package/dist/storage/store.js +143 -8
- package/dist/storage/store.js.map +1 -1
- package/package.json +1 -1
package/dist/storage/store.d.cts
CHANGED
|
@@ -1,78 +1,156 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pluggable persistence for the Coordinator.
|
|
3
3
|
*
|
|
4
|
-
* A Store
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A Store keeps workspace state between process restarts. The Coordinator
|
|
5
|
+
* calls `store.load()` once at start, and writes after every call that adds
|
|
6
|
+
* to a workspace's audit log.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* - MemoryStore (default, no persistence; existing behaviour)
|
|
10
|
-
* - SqliteStore (real SQLite via better-sqlite3, opt-in)
|
|
8
|
+
* How the Coordinator writes depends on what the store implements:
|
|
11
9
|
*
|
|
12
|
-
*
|
|
10
|
+
* - `apply(change)`: the Coordinator sends what the call changed: the new
|
|
11
|
+
* audit entries, the objects it touched and the workspace's own fields.
|
|
12
|
+
* The cost of a write stays flat as the log grows; it grows with the
|
|
13
|
+
* objects a call changes, such as a task with a long history. MemoryStore
|
|
14
|
+
* and SqliteStore implement it.
|
|
15
|
+
* - `save(record)` alone: the Coordinator sends the whole workspace on every
|
|
16
|
+
* write, as releases before 0.3.2 did, so the cost of a write grows with
|
|
17
|
+
* the log. Stores written for earlier releases keep working this way.
|
|
18
|
+
*
|
|
19
|
+
* MemoryStore is the default and keeps nothing across processes. SqliteStore
|
|
20
|
+
* keeps state in a SQLite file through better-sqlite3. Third-party stores
|
|
21
|
+
* (Postgres, Redis and so on) implement this interface.
|
|
13
22
|
*
|
|
14
23
|
* Concurrency model
|
|
15
24
|
* -----------------
|
|
16
25
|
*
|
|
17
|
-
* A Coordinator is a **single-writer** component: dispatch is serial,
|
|
18
|
-
* state is held in memory, and each chain link is computed from the
|
|
19
|
-
* instance holds.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
26
|
+
* A Coordinator is a **single-writer** component: dispatch is serial,
|
|
27
|
+
* workspace state is held in memory, and each chain link is computed from the
|
|
28
|
+
* head the instance holds. Run one Coordinator per workspace.
|
|
29
|
+
*
|
|
30
|
+
* Every change names the number of audit entries the Coordinator expects the
|
|
31
|
+
* store to hold already (`base`). MemoryStore and SqliteStore refuse a change
|
|
32
|
+
* whose base differs from what they hold: they throw a StoreConflictError and
|
|
33
|
+
* write nothing. A second Coordinator writing the same workspace is refused
|
|
34
|
+
* and reported, and the entries already stored stay as they are. The refused
|
|
35
|
+
* instance's memory no longer matches the store; restart it to reload.
|
|
22
36
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* re-linked from the surviving head, and `audit.verify_chain` still reports
|
|
27
|
-
* `ok`, because the surviving chain is internally consistent. The loss is
|
|
28
|
-
* silent and CHAP's own verification will not reveal it.
|
|
37
|
+
* `save` makes no such check: it replaces what the store holds for the
|
|
38
|
+
* workspace. A store that implements only `save` therefore still loses
|
|
39
|
+
* entries silently when two instances write one workspace.
|
|
29
40
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* an external lock. See SPECIFICATION.md section 10.3.
|
|
41
|
+
* To run more than one instance, partition workspaces so that no workspace is
|
|
42
|
+
* ever written by two, or hold an external lock. See SPECIFICATION.md 10.3.
|
|
33
43
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* payloads; this is fast for workspaces in the low-thousands of audit
|
|
37
|
-
* entries and trades query flexibility for simplicity. Production
|
|
38
|
-
* deployments needing rich query semantics should project the audit log
|
|
39
|
-
* into their own analytics store via the `onAudit` listener.
|
|
44
|
+
* Production deployments that need rich queries over the log can project it
|
|
45
|
+
* into their own analytics store with the `onAudit` listener.
|
|
40
46
|
*/
|
|
47
|
+
/** The workspace maps a store keeps object by object. */
|
|
48
|
+
declare const COLLECTIONS: readonly ["members", "tasks", "overrides", "whispers", "deliberations", "handoffs", "snapshots", "route_decisions"];
|
|
49
|
+
/** task.create idempotency keys, kept as one more collection of key to task id. */
|
|
50
|
+
declare const IDEMPOTENCY_KEYS = "idempotency_keys";
|
|
41
51
|
interface WorkspaceRecord {
|
|
42
52
|
/** Workspace identifier. */
|
|
43
53
|
id: string;
|
|
44
|
-
/** JSON-serialisable payload
|
|
54
|
+
/** JSON-serialisable payload in the shape `Coordinator.snapshot()` produces. */
|
|
45
55
|
data: unknown;
|
|
46
56
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* concurrency model in this module's header.
|
|
57
|
+
* For `save`, a counter the Coordinator increments on each write. For a
|
|
58
|
+
* record a store loads after `apply`, the number of audit entries. Recorded
|
|
59
|
+
* for diagnostics; `save` does not check it.
|
|
51
60
|
*/
|
|
52
61
|
version: number;
|
|
53
62
|
/** Wall-clock timestamp of the last write, ISO-8601. */
|
|
54
63
|
updated_at: string;
|
|
55
64
|
}
|
|
65
|
+
/** One write to one workspace: what one or more calls changed. */
|
|
66
|
+
interface StoreChange {
|
|
67
|
+
/** Workspace identifier. */
|
|
68
|
+
id: string;
|
|
69
|
+
/**
|
|
70
|
+
* Audit entries the store must already hold for this workspace. A store
|
|
71
|
+
* that holds a different number refuses the change with a
|
|
72
|
+
* StoreConflictError and writes nothing. Not checked when `replace` is set.
|
|
73
|
+
*/
|
|
74
|
+
base: number;
|
|
75
|
+
/** Audit entries the store holds after the change: `base + entries.length`. */
|
|
76
|
+
version: number;
|
|
77
|
+
/** Wall-clock timestamp of the write, ISO-8601. */
|
|
78
|
+
updated_at: string;
|
|
79
|
+
/** The workspace's own fields: everything except its collections and log. */
|
|
80
|
+
meta: Record<string, unknown>;
|
|
81
|
+
/** New audit entries in order. The first has seq `base`. */
|
|
82
|
+
entries: unknown[];
|
|
83
|
+
/**
|
|
84
|
+
* Objects written, by collection and then by key. A null value removes the
|
|
85
|
+
* object. The collections are those in COLLECTIONS and IDEMPOTENCY_KEYS.
|
|
86
|
+
*/
|
|
87
|
+
objects: Record<string, Record<string, unknown>>;
|
|
88
|
+
/**
|
|
89
|
+
* Replace everything the store holds for this workspace with this change,
|
|
90
|
+
* which then carries the whole workspace from seq 0.
|
|
91
|
+
*/
|
|
92
|
+
replace?: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A change whose base does not match the audit entries the store holds.
|
|
96
|
+
* Usually a second Coordinator writing the same workspace.
|
|
97
|
+
*/
|
|
98
|
+
declare class StoreConflictError extends Error {
|
|
99
|
+
readonly workspace: string;
|
|
100
|
+
/** Audit entries the store holds. */
|
|
101
|
+
readonly held: number;
|
|
102
|
+
/** Audit entries the change expected. */
|
|
103
|
+
readonly base: number;
|
|
104
|
+
constructor(workspace: string, held: number, base: number);
|
|
105
|
+
/**
|
|
106
|
+
* Matched by name, so `instanceof` holds across the package's entry points,
|
|
107
|
+
* each of which is bundled with its own copy of this class.
|
|
108
|
+
*/
|
|
109
|
+
static [Symbol.hasInstance](value: unknown): boolean;
|
|
110
|
+
}
|
|
56
111
|
interface Store {
|
|
57
|
-
/** Load all workspace records
|
|
112
|
+
/** Load all workspace records. Called once when the Coordinator starts. */
|
|
58
113
|
load(): Promise<WorkspaceRecord[]> | WorkspaceRecord[];
|
|
59
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Replace what the store holds for one workspace. The Coordinator calls it
|
|
116
|
+
* after every write when the store has no `apply`.
|
|
117
|
+
*/
|
|
60
118
|
save(record: WorkspaceRecord): Promise<void> | void;
|
|
61
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Write one change. Optional; when present the Coordinator calls it in
|
|
121
|
+
* place of `save`. It must be atomic: the whole change is written, or
|
|
122
|
+
* nothing is. The change refers to the Coordinator's live objects, so
|
|
123
|
+
* serialise or copy everything before returning, including in an async
|
|
124
|
+
* store. Refuse a change whose `base` differs from the entries held, unless
|
|
125
|
+
* `replace` is set, by throwing (or rejecting with) a StoreConflictError.
|
|
126
|
+
*/
|
|
127
|
+
apply?(change: StoreChange): Promise<void> | void;
|
|
128
|
+
/** Remove one workspace. */
|
|
62
129
|
delete(id: string): Promise<void> | void;
|
|
63
130
|
/** Release resources. Idempotent. */
|
|
64
131
|
close?(): Promise<void> | void;
|
|
65
132
|
}
|
|
133
|
+
/** The key an object is stored under in its collection. */
|
|
134
|
+
declare function objectKey(collection: string, item: unknown): string | undefined;
|
|
135
|
+
/** A whole-workspace snapshot as a change that replaces what the store holds. */
|
|
136
|
+
declare function changeFromRecord(record: WorkspaceRecord): StoreChange;
|
|
137
|
+
/**
|
|
138
|
+
* Rows read back from a store, assembled into the snapshot shape
|
|
139
|
+
* `Coordinator.restore()` reads: arrays for the collections, an object for
|
|
140
|
+
* the idempotency keys, and the log in seq order.
|
|
141
|
+
*/
|
|
142
|
+
declare function assembleSnapshot(meta: Record<string, unknown>, entries: unknown[], objects: Iterable<[collection: string, key: string, value: unknown]>): Record<string, unknown>;
|
|
66
143
|
/**
|
|
67
|
-
* In-memory store.
|
|
68
|
-
*
|
|
144
|
+
* In-memory store. The default: it keeps nothing across processes. Like a
|
|
145
|
+
* database it keeps each row as JSON text, so a Coordinator restarted on it
|
|
146
|
+
* sees what was written and nothing changed later.
|
|
69
147
|
*/
|
|
70
148
|
declare class MemoryStore implements Store {
|
|
71
|
-
private
|
|
149
|
+
private held;
|
|
72
150
|
load(): WorkspaceRecord[];
|
|
73
151
|
save(record: WorkspaceRecord): void;
|
|
152
|
+
apply(change: StoreChange): void;
|
|
74
153
|
delete(id: string): void;
|
|
75
|
-
close(): void;
|
|
76
154
|
}
|
|
77
155
|
|
|
78
|
-
export { MemoryStore, type Store, type WorkspaceRecord };
|
|
156
|
+
export { COLLECTIONS, IDEMPOTENCY_KEYS, MemoryStore, type Store, type StoreChange, StoreConflictError, type WorkspaceRecord, assembleSnapshot, changeFromRecord, objectKey };
|
package/dist/storage/store.d.ts
CHANGED
|
@@ -1,78 +1,156 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pluggable persistence for the Coordinator.
|
|
3
3
|
*
|
|
4
|
-
* A Store
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A Store keeps workspace state between process restarts. The Coordinator
|
|
5
|
+
* calls `store.load()` once at start, and writes after every call that adds
|
|
6
|
+
* to a workspace's audit log.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* - MemoryStore (default, no persistence; existing behaviour)
|
|
10
|
-
* - SqliteStore (real SQLite via better-sqlite3, opt-in)
|
|
8
|
+
* How the Coordinator writes depends on what the store implements:
|
|
11
9
|
*
|
|
12
|
-
*
|
|
10
|
+
* - `apply(change)`: the Coordinator sends what the call changed: the new
|
|
11
|
+
* audit entries, the objects it touched and the workspace's own fields.
|
|
12
|
+
* The cost of a write stays flat as the log grows; it grows with the
|
|
13
|
+
* objects a call changes, such as a task with a long history. MemoryStore
|
|
14
|
+
* and SqliteStore implement it.
|
|
15
|
+
* - `save(record)` alone: the Coordinator sends the whole workspace on every
|
|
16
|
+
* write, as releases before 0.3.2 did, so the cost of a write grows with
|
|
17
|
+
* the log. Stores written for earlier releases keep working this way.
|
|
18
|
+
*
|
|
19
|
+
* MemoryStore is the default and keeps nothing across processes. SqliteStore
|
|
20
|
+
* keeps state in a SQLite file through better-sqlite3. Third-party stores
|
|
21
|
+
* (Postgres, Redis and so on) implement this interface.
|
|
13
22
|
*
|
|
14
23
|
* Concurrency model
|
|
15
24
|
* -----------------
|
|
16
25
|
*
|
|
17
|
-
* A Coordinator is a **single-writer** component: dispatch is serial,
|
|
18
|
-
* state is held in memory, and each chain link is computed from the
|
|
19
|
-
* instance holds.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
26
|
+
* A Coordinator is a **single-writer** component: dispatch is serial,
|
|
27
|
+
* workspace state is held in memory, and each chain link is computed from the
|
|
28
|
+
* head the instance holds. Run one Coordinator per workspace.
|
|
29
|
+
*
|
|
30
|
+
* Every change names the number of audit entries the Coordinator expects the
|
|
31
|
+
* store to hold already (`base`). MemoryStore and SqliteStore refuse a change
|
|
32
|
+
* whose base differs from what they hold: they throw a StoreConflictError and
|
|
33
|
+
* write nothing. A second Coordinator writing the same workspace is refused
|
|
34
|
+
* and reported, and the entries already stored stay as they are. The refused
|
|
35
|
+
* instance's memory no longer matches the store; restart it to reload.
|
|
22
36
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* re-linked from the surviving head, and `audit.verify_chain` still reports
|
|
27
|
-
* `ok`, because the surviving chain is internally consistent. The loss is
|
|
28
|
-
* silent and CHAP's own verification will not reveal it.
|
|
37
|
+
* `save` makes no such check: it replaces what the store holds for the
|
|
38
|
+
* workspace. A store that implements only `save` therefore still loses
|
|
39
|
+
* entries silently when two instances write one workspace.
|
|
29
40
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* an external lock. See SPECIFICATION.md section 10.3.
|
|
41
|
+
* To run more than one instance, partition workspaces so that no workspace is
|
|
42
|
+
* ever written by two, or hold an external lock. See SPECIFICATION.md 10.3.
|
|
33
43
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* payloads; this is fast for workspaces in the low-thousands of audit
|
|
37
|
-
* entries and trades query flexibility for simplicity. Production
|
|
38
|
-
* deployments needing rich query semantics should project the audit log
|
|
39
|
-
* into their own analytics store via the `onAudit` listener.
|
|
44
|
+
* Production deployments that need rich queries over the log can project it
|
|
45
|
+
* into their own analytics store with the `onAudit` listener.
|
|
40
46
|
*/
|
|
47
|
+
/** The workspace maps a store keeps object by object. */
|
|
48
|
+
declare const COLLECTIONS: readonly ["members", "tasks", "overrides", "whispers", "deliberations", "handoffs", "snapshots", "route_decisions"];
|
|
49
|
+
/** task.create idempotency keys, kept as one more collection of key to task id. */
|
|
50
|
+
declare const IDEMPOTENCY_KEYS = "idempotency_keys";
|
|
41
51
|
interface WorkspaceRecord {
|
|
42
52
|
/** Workspace identifier. */
|
|
43
53
|
id: string;
|
|
44
|
-
/** JSON-serialisable payload
|
|
54
|
+
/** JSON-serialisable payload in the shape `Coordinator.snapshot()` produces. */
|
|
45
55
|
data: unknown;
|
|
46
56
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* concurrency model in this module's header.
|
|
57
|
+
* For `save`, a counter the Coordinator increments on each write. For a
|
|
58
|
+
* record a store loads after `apply`, the number of audit entries. Recorded
|
|
59
|
+
* for diagnostics; `save` does not check it.
|
|
51
60
|
*/
|
|
52
61
|
version: number;
|
|
53
62
|
/** Wall-clock timestamp of the last write, ISO-8601. */
|
|
54
63
|
updated_at: string;
|
|
55
64
|
}
|
|
65
|
+
/** One write to one workspace: what one or more calls changed. */
|
|
66
|
+
interface StoreChange {
|
|
67
|
+
/** Workspace identifier. */
|
|
68
|
+
id: string;
|
|
69
|
+
/**
|
|
70
|
+
* Audit entries the store must already hold for this workspace. A store
|
|
71
|
+
* that holds a different number refuses the change with a
|
|
72
|
+
* StoreConflictError and writes nothing. Not checked when `replace` is set.
|
|
73
|
+
*/
|
|
74
|
+
base: number;
|
|
75
|
+
/** Audit entries the store holds after the change: `base + entries.length`. */
|
|
76
|
+
version: number;
|
|
77
|
+
/** Wall-clock timestamp of the write, ISO-8601. */
|
|
78
|
+
updated_at: string;
|
|
79
|
+
/** The workspace's own fields: everything except its collections and log. */
|
|
80
|
+
meta: Record<string, unknown>;
|
|
81
|
+
/** New audit entries in order. The first has seq `base`. */
|
|
82
|
+
entries: unknown[];
|
|
83
|
+
/**
|
|
84
|
+
* Objects written, by collection and then by key. A null value removes the
|
|
85
|
+
* object. The collections are those in COLLECTIONS and IDEMPOTENCY_KEYS.
|
|
86
|
+
*/
|
|
87
|
+
objects: Record<string, Record<string, unknown>>;
|
|
88
|
+
/**
|
|
89
|
+
* Replace everything the store holds for this workspace with this change,
|
|
90
|
+
* which then carries the whole workspace from seq 0.
|
|
91
|
+
*/
|
|
92
|
+
replace?: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A change whose base does not match the audit entries the store holds.
|
|
96
|
+
* Usually a second Coordinator writing the same workspace.
|
|
97
|
+
*/
|
|
98
|
+
declare class StoreConflictError extends Error {
|
|
99
|
+
readonly workspace: string;
|
|
100
|
+
/** Audit entries the store holds. */
|
|
101
|
+
readonly held: number;
|
|
102
|
+
/** Audit entries the change expected. */
|
|
103
|
+
readonly base: number;
|
|
104
|
+
constructor(workspace: string, held: number, base: number);
|
|
105
|
+
/**
|
|
106
|
+
* Matched by name, so `instanceof` holds across the package's entry points,
|
|
107
|
+
* each of which is bundled with its own copy of this class.
|
|
108
|
+
*/
|
|
109
|
+
static [Symbol.hasInstance](value: unknown): boolean;
|
|
110
|
+
}
|
|
56
111
|
interface Store {
|
|
57
|
-
/** Load all workspace records
|
|
112
|
+
/** Load all workspace records. Called once when the Coordinator starts. */
|
|
58
113
|
load(): Promise<WorkspaceRecord[]> | WorkspaceRecord[];
|
|
59
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Replace what the store holds for one workspace. The Coordinator calls it
|
|
116
|
+
* after every write when the store has no `apply`.
|
|
117
|
+
*/
|
|
60
118
|
save(record: WorkspaceRecord): Promise<void> | void;
|
|
61
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Write one change. Optional; when present the Coordinator calls it in
|
|
121
|
+
* place of `save`. It must be atomic: the whole change is written, or
|
|
122
|
+
* nothing is. The change refers to the Coordinator's live objects, so
|
|
123
|
+
* serialise or copy everything before returning, including in an async
|
|
124
|
+
* store. Refuse a change whose `base` differs from the entries held, unless
|
|
125
|
+
* `replace` is set, by throwing (or rejecting with) a StoreConflictError.
|
|
126
|
+
*/
|
|
127
|
+
apply?(change: StoreChange): Promise<void> | void;
|
|
128
|
+
/** Remove one workspace. */
|
|
62
129
|
delete(id: string): Promise<void> | void;
|
|
63
130
|
/** Release resources. Idempotent. */
|
|
64
131
|
close?(): Promise<void> | void;
|
|
65
132
|
}
|
|
133
|
+
/** The key an object is stored under in its collection. */
|
|
134
|
+
declare function objectKey(collection: string, item: unknown): string | undefined;
|
|
135
|
+
/** A whole-workspace snapshot as a change that replaces what the store holds. */
|
|
136
|
+
declare function changeFromRecord(record: WorkspaceRecord): StoreChange;
|
|
137
|
+
/**
|
|
138
|
+
* Rows read back from a store, assembled into the snapshot shape
|
|
139
|
+
* `Coordinator.restore()` reads: arrays for the collections, an object for
|
|
140
|
+
* the idempotency keys, and the log in seq order.
|
|
141
|
+
*/
|
|
142
|
+
declare function assembleSnapshot(meta: Record<string, unknown>, entries: unknown[], objects: Iterable<[collection: string, key: string, value: unknown]>): Record<string, unknown>;
|
|
66
143
|
/**
|
|
67
|
-
* In-memory store.
|
|
68
|
-
*
|
|
144
|
+
* In-memory store. The default: it keeps nothing across processes. Like a
|
|
145
|
+
* database it keeps each row as JSON text, so a Coordinator restarted on it
|
|
146
|
+
* sees what was written and nothing changed later.
|
|
69
147
|
*/
|
|
70
148
|
declare class MemoryStore implements Store {
|
|
71
|
-
private
|
|
149
|
+
private held;
|
|
72
150
|
load(): WorkspaceRecord[];
|
|
73
151
|
save(record: WorkspaceRecord): void;
|
|
152
|
+
apply(change: StoreChange): void;
|
|
74
153
|
delete(id: string): void;
|
|
75
|
-
close(): void;
|
|
76
154
|
}
|
|
77
155
|
|
|
78
|
-
export { MemoryStore, type Store, type WorkspaceRecord };
|
|
156
|
+
export { COLLECTIONS, IDEMPOTENCY_KEYS, MemoryStore, type Store, type StoreChange, StoreConflictError, type WorkspaceRecord, assembleSnapshot, changeFromRecord, objectKey };
|
package/dist/storage/store.js
CHANGED
|
@@ -1,20 +1,155 @@
|
|
|
1
1
|
// src/storage/store.ts
|
|
2
|
+
var COLLECTIONS = [
|
|
3
|
+
"members",
|
|
4
|
+
"tasks",
|
|
5
|
+
"overrides",
|
|
6
|
+
"whispers",
|
|
7
|
+
"deliberations",
|
|
8
|
+
"handoffs",
|
|
9
|
+
"snapshots",
|
|
10
|
+
"route_decisions"
|
|
11
|
+
];
|
|
12
|
+
var IDEMPOTENCY_KEYS = "idempotency_keys";
|
|
13
|
+
var StoreConflictError = class extends Error {
|
|
14
|
+
workspace;
|
|
15
|
+
/** Audit entries the store holds. */
|
|
16
|
+
held;
|
|
17
|
+
/** Audit entries the change expected. */
|
|
18
|
+
base;
|
|
19
|
+
constructor(workspace, held, base) {
|
|
20
|
+
super(
|
|
21
|
+
`The store holds ${held} audit entries for workspace ${workspace} and this write expected ${base}, so nothing was written. Another coordinator may be writing to this workspace, the store may have been changed outside this coordinator, or this coordinator could not load the workspace when it started. Restart this coordinator to reload.`
|
|
22
|
+
);
|
|
23
|
+
this.name = "StoreConflictError";
|
|
24
|
+
this.workspace = workspace;
|
|
25
|
+
this.held = held;
|
|
26
|
+
this.base = base;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Matched by name, so `instanceof` holds across the package's entry points,
|
|
30
|
+
* each of which is bundled with its own copy of this class.
|
|
31
|
+
*/
|
|
32
|
+
static [Symbol.hasInstance](value) {
|
|
33
|
+
return value instanceof Error && value.name === "StoreConflictError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function isCollection(name) {
|
|
37
|
+
return COLLECTIONS.includes(name);
|
|
38
|
+
}
|
|
39
|
+
function objectKey(collection, item) {
|
|
40
|
+
if (item === null || typeof item !== "object") return void 0;
|
|
41
|
+
const o = item;
|
|
42
|
+
const key = collection === "members" ? o.uri ?? o.id : o.id ?? o.uri;
|
|
43
|
+
return typeof key === "string" ? key : void 0;
|
|
44
|
+
}
|
|
45
|
+
function keyed(collection, value) {
|
|
46
|
+
const out = {};
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
for (const item of value) {
|
|
49
|
+
const key = objectKey(collection, item);
|
|
50
|
+
if (key !== void 0) out[key] = item;
|
|
51
|
+
}
|
|
52
|
+
} else if (value !== null && typeof value === "object") {
|
|
53
|
+
Object.assign(out, value);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function changeFromRecord(record) {
|
|
58
|
+
const data = record.data !== null && typeof record.data === "object" ? record.data : {};
|
|
59
|
+
const meta = {};
|
|
60
|
+
const objects = {};
|
|
61
|
+
let entries = [];
|
|
62
|
+
for (const [name, value] of Object.entries(data)) {
|
|
63
|
+
if (name === "audit") entries = Array.isArray(value) ? value : [];
|
|
64
|
+
else if (name === IDEMPOTENCY_KEYS || isCollection(name)) objects[name] = keyed(name, value);
|
|
65
|
+
else meta[name] = value;
|
|
66
|
+
}
|
|
67
|
+
if (meta.id === void 0) meta.id = record.id;
|
|
68
|
+
return {
|
|
69
|
+
id: record.id,
|
|
70
|
+
base: 0,
|
|
71
|
+
version: record.version,
|
|
72
|
+
updated_at: record.updated_at,
|
|
73
|
+
meta,
|
|
74
|
+
entries,
|
|
75
|
+
objects,
|
|
76
|
+
replace: true
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function assembleSnapshot(meta, entries, objects) {
|
|
80
|
+
const data = { ...meta };
|
|
81
|
+
for (const name of COLLECTIONS) data[name] = [];
|
|
82
|
+
const keys = {};
|
|
83
|
+
data[IDEMPOTENCY_KEYS] = keys;
|
|
84
|
+
for (const [collection, key, value] of objects) {
|
|
85
|
+
if (collection === IDEMPOTENCY_KEYS) {
|
|
86
|
+
keys[key] = value;
|
|
87
|
+
} else {
|
|
88
|
+
const list = Array.isArray(data[collection]) ? data[collection] : data[collection] = [];
|
|
89
|
+
list.push(value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
data.audit = entries;
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
2
95
|
var MemoryStore = class {
|
|
3
|
-
|
|
96
|
+
held = /* @__PURE__ */ new Map();
|
|
4
97
|
load() {
|
|
5
|
-
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const [id, h] of this.held) {
|
|
100
|
+
const objects = [];
|
|
101
|
+
for (const [collection, items] of h.objects) {
|
|
102
|
+
for (const [key, value] of items) objects.push([collection, key, JSON.parse(value)]);
|
|
103
|
+
}
|
|
104
|
+
out.push({
|
|
105
|
+
id,
|
|
106
|
+
data: assembleSnapshot(JSON.parse(h.meta), h.entries.map((e) => JSON.parse(e)), objects),
|
|
107
|
+
version: h.version,
|
|
108
|
+
updated_at: h.updated_at
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
6
112
|
}
|
|
7
113
|
save(record) {
|
|
8
|
-
this.
|
|
114
|
+
this.apply(changeFromRecord(record));
|
|
9
115
|
}
|
|
10
|
-
|
|
11
|
-
|
|
116
|
+
apply(change) {
|
|
117
|
+
const meta = JSON.stringify(change.meta);
|
|
118
|
+
const entries = change.entries.map((e) => JSON.stringify(e));
|
|
119
|
+
const objects = Object.entries(change.objects).map(([collection, items]) => [collection, Object.entries(items).map(([key, value]) => [key, value === null || value === void 0 ? null : JSON.stringify(value)])]);
|
|
120
|
+
let h = this.held.get(change.id);
|
|
121
|
+
if (!change.replace) {
|
|
122
|
+
const holds = h ? h.entries.length : 0;
|
|
123
|
+
if (holds !== change.base) throw new StoreConflictError(change.id, holds, change.base);
|
|
124
|
+
}
|
|
125
|
+
if (!h || change.replace) {
|
|
126
|
+
h = { meta: "{}", version: 0, updated_at: "", entries: [], objects: /* @__PURE__ */ new Map() };
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) h.entries.push(entry);
|
|
129
|
+
for (const [collection, items] of objects) {
|
|
130
|
+
let held = h.objects.get(collection);
|
|
131
|
+
if (!held) h.objects.set(collection, held = /* @__PURE__ */ new Map());
|
|
132
|
+
for (const [key, value] of items) {
|
|
133
|
+
if (value === null) held.delete(key);
|
|
134
|
+
else held.set(key, value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
h.meta = meta;
|
|
138
|
+
h.version = change.version;
|
|
139
|
+
h.updated_at = change.updated_at;
|
|
140
|
+
this.held.set(change.id, h);
|
|
12
141
|
}
|
|
13
|
-
|
|
14
|
-
this.
|
|
142
|
+
delete(id) {
|
|
143
|
+
this.held.delete(id);
|
|
15
144
|
}
|
|
16
145
|
};
|
|
17
146
|
export {
|
|
18
|
-
|
|
147
|
+
COLLECTIONS,
|
|
148
|
+
IDEMPOTENCY_KEYS,
|
|
149
|
+
MemoryStore,
|
|
150
|
+
StoreConflictError,
|
|
151
|
+
assembleSnapshot,
|
|
152
|
+
changeFromRecord,
|
|
153
|
+
objectKey
|
|
19
154
|
};
|
|
20
155
|
//# sourceMappingURL=store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/store.ts"],"sourcesContent":["/**\n * Pluggable persistence for the Coordinator.\n *\n * A Store persists workspace state between process restarts. Coordinators\n * call `store.save(workspaceId, snapshot)` after every mutation that\n * appends to the audit chain, and `store.load()` on startup to restore.\n *\n * Two implementations ship in-tree:\n * - MemoryStore (default, no persistence; existing behaviour)\n * - SqliteStore (real SQLite via better-sqlite3, opt-in)\n *\n * Third-party stores (Postgres, Redis, etc.) implement this interface.\n *\n * Concurrency model\n * -----------------\n *\n * A Coordinator is a **single-writer** component: dispatch is serial, workspace\n * state is held in memory, and each chain link is computed from the head the\n * instance holds. This interface is a persistence mechanism, not a\n * concurrency-control one. `save` is an unconditional write and no\n * implementation rejects a stale record.\n *\n * Running two or more Coordinator instances against one shared store is\n * therefore not supported. A stale instance's write replaces a newer one\n * wholesale: entries written by the other instance are lost, the chain is\n * re-linked from the surviving head, and `audit.verify_chain` still reports\n * `ok`, because the surviving chain is internally consistent. The loss is\n * silent and CHAP's own verification will not reveal it.\n *\n * If you need more than one instance, serialise writes above the Coordinator by\n * partitioning workspaces so no workspace is ever written by two, or by holding\n * an external lock. See SPECIFICATION.md section 10.3.\n *\n * Stores hold per-workspace JSON snapshots, not a decomposed relational\n * model. The Coordinator's snapshot/restore methods produce JSON-safe\n * payloads; this is fast for workspaces in the low-thousands of audit\n * entries and trades query flexibility for simplicity. Production\n * deployments needing rich query semantics should project the audit log\n * into their own analytics store via the `onAudit` listener.\n */\n\nexport interface WorkspaceRecord {\n /** Workspace identifier. */\n id: string;\n /** JSON-serialisable payload produced by `Coordinator.snapshot()`. */\n data: unknown;\n /**\n * Incremented on each save. Recorded for diagnostics and for stores that\n * want it; it is NOT enforced. Nothing here performs a compare-and-swap,\n * so this field does not give you optimistic concurrency. See the\n * concurrency model in this module's header.\n */\n version: number;\n /** Wall-clock timestamp of the last write, ISO-8601. */\n updated_at: string;\n}\n\nexport interface Store {\n /** Load all workspace records into memory. Called once on coordinator start. */\n load(): Promise<WorkspaceRecord[]> | WorkspaceRecord[];\n\n /** Persist one workspace record. Called after every mutation. */\n save(record: WorkspaceRecord): Promise<void> | void;\n\n /** Remove one workspace. Used by `workspace.delete` semantics (not in v0.2). */\n delete(id: string): Promise<void> | void;\n\n /** Release resources. Idempotent. */\n close?(): Promise<void> | void;\n}\n\n/**\n * In-memory store. Default behaviour: no persistence, fast tests.\n * State lives in a Map; closing it discards everything.\n */\nexport class MemoryStore implements Store {\n private records = new Map<string, WorkspaceRecord>();\n\n load(): WorkspaceRecord[] {\n return Array.from(this.records.values());\n }\n\n save(record: WorkspaceRecord): void {\n this.records.set(record.id, record);\n }\n\n delete(id: string): void {\n this.records.delete(id);\n }\n\n close(): void {\n this.records.clear();\n }\n}\n"],"mappings":";AA2EO,IAAM,cAAN,MAAmC;AAAA,EAChC,UAAU,oBAAI,IAA6B;AAAA,EAEnD,OAA0B;AACxB,WAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC;AAAA,EACzC;AAAA,EAEA,KAAK,QAA+B;AAClC,SAAK,QAAQ,IAAI,OAAO,IAAI,MAAM;AAAA,EACpC;AAAA,EAEA,OAAO,IAAkB;AACvB,SAAK,QAAQ,OAAO,EAAE;AAAA,EACxB;AAAA,EAEA,QAAc;AACZ,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/storage/store.ts"],"sourcesContent":["/**\n * Pluggable persistence for the Coordinator.\n *\n * A Store keeps workspace state between process restarts. The Coordinator\n * calls `store.load()` once at start, and writes after every call that adds\n * to a workspace's audit log.\n *\n * How the Coordinator writes depends on what the store implements:\n *\n * - `apply(change)`: the Coordinator sends what the call changed: the new\n * audit entries, the objects it touched and the workspace's own fields.\n * The cost of a write stays flat as the log grows; it grows with the\n * objects a call changes, such as a task with a long history. MemoryStore\n * and SqliteStore implement it.\n * - `save(record)` alone: the Coordinator sends the whole workspace on every\n * write, as releases before 0.3.2 did, so the cost of a write grows with\n * the log. Stores written for earlier releases keep working this way.\n *\n * MemoryStore is the default and keeps nothing across processes. SqliteStore\n * keeps state in a SQLite file through better-sqlite3. Third-party stores\n * (Postgres, Redis and so on) implement this interface.\n *\n * Concurrency model\n * -----------------\n *\n * A Coordinator is a **single-writer** component: dispatch is serial,\n * workspace state is held in memory, and each chain link is computed from the\n * head the instance holds. Run one Coordinator per workspace.\n *\n * Every change names the number of audit entries the Coordinator expects the\n * store to hold already (`base`). MemoryStore and SqliteStore refuse a change\n * whose base differs from what they hold: they throw a StoreConflictError and\n * write nothing. A second Coordinator writing the same workspace is refused\n * and reported, and the entries already stored stay as they are. The refused\n * instance's memory no longer matches the store; restart it to reload.\n *\n * `save` makes no such check: it replaces what the store holds for the\n * workspace. A store that implements only `save` therefore still loses\n * entries silently when two instances write one workspace.\n *\n * To run more than one instance, partition workspaces so that no workspace is\n * ever written by two, or hold an external lock. See SPECIFICATION.md 10.3.\n *\n * Production deployments that need rich queries over the log can project it\n * into their own analytics store with the `onAudit` listener.\n */\n\n/** The workspace maps a store keeps object by object. */\nexport const COLLECTIONS = [\n \"members\", \"tasks\", \"overrides\", \"whispers\",\n \"deliberations\", \"handoffs\", \"snapshots\", \"route_decisions\",\n] as const;\n\n/** task.create idempotency keys, kept as one more collection of key to task id. */\nexport const IDEMPOTENCY_KEYS = \"idempotency_keys\";\n\nexport interface WorkspaceRecord {\n /** Workspace identifier. */\n id: string;\n /** JSON-serialisable payload in the shape `Coordinator.snapshot()` produces. */\n data: unknown;\n /**\n * For `save`, a counter the Coordinator increments on each write. For a\n * record a store loads after `apply`, the number of audit entries. Recorded\n * for diagnostics; `save` does not check it.\n */\n version: number;\n /** Wall-clock timestamp of the last write, ISO-8601. */\n updated_at: string;\n}\n\n/** One write to one workspace: what one or more calls changed. */\nexport interface StoreChange {\n /** Workspace identifier. */\n id: string;\n /**\n * Audit entries the store must already hold for this workspace. A store\n * that holds a different number refuses the change with a\n * StoreConflictError and writes nothing. Not checked when `replace` is set.\n */\n base: number;\n /** Audit entries the store holds after the change: `base + entries.length`. */\n version: number;\n /** Wall-clock timestamp of the write, ISO-8601. */\n updated_at: string;\n /** The workspace's own fields: everything except its collections and log. */\n meta: Record<string, unknown>;\n /** New audit entries in order. The first has seq `base`. */\n entries: unknown[];\n /**\n * Objects written, by collection and then by key. A null value removes the\n * object. The collections are those in COLLECTIONS and IDEMPOTENCY_KEYS.\n */\n objects: Record<string, Record<string, unknown>>;\n /**\n * Replace everything the store holds for this workspace with this change,\n * which then carries the whole workspace from seq 0.\n */\n replace?: boolean;\n}\n\n/**\n * A change whose base does not match the audit entries the store holds.\n * Usually a second Coordinator writing the same workspace.\n */\nexport class StoreConflictError extends Error {\n readonly workspace: string;\n /** Audit entries the store holds. */\n readonly held: number;\n /** Audit entries the change expected. */\n readonly base: number;\n\n constructor(workspace: string, held: number, base: number) {\n super(\n `The store holds ${held} audit entries for workspace ${workspace} and ` +\n `this write expected ${base}, so nothing was written. Another ` +\n `coordinator may be writing to this workspace, the store may have been ` +\n `changed outside this coordinator, or this coordinator could not load ` +\n `the workspace when it started. Restart this coordinator to reload.`,\n );\n this.name = \"StoreConflictError\";\n this.workspace = workspace;\n this.held = held;\n this.base = base;\n }\n\n /**\n * Matched by name, so `instanceof` holds across the package's entry points,\n * each of which is bundled with its own copy of this class.\n */\n static [Symbol.hasInstance](value: unknown): boolean {\n return value instanceof Error && value.name === \"StoreConflictError\";\n }\n}\n\nexport interface Store {\n /** Load all workspace records. Called once when the Coordinator starts. */\n load(): Promise<WorkspaceRecord[]> | WorkspaceRecord[];\n\n /**\n * Replace what the store holds for one workspace. The Coordinator calls it\n * after every write when the store has no `apply`.\n */\n save(record: WorkspaceRecord): Promise<void> | void;\n\n /**\n * Write one change. Optional; when present the Coordinator calls it in\n * place of `save`. It must be atomic: the whole change is written, or\n * nothing is. The change refers to the Coordinator's live objects, so\n * serialise or copy everything before returning, including in an async\n * store. Refuse a change whose `base` differs from the entries held, unless\n * `replace` is set, by throwing (or rejecting with) a StoreConflictError.\n */\n apply?(change: StoreChange): Promise<void> | void;\n\n /** Remove one workspace. */\n delete(id: string): Promise<void> | void;\n\n /** Release resources. Idempotent. */\n close?(): Promise<void> | void;\n}\n\nfunction isCollection(name: string): boolean {\n return (COLLECTIONS as readonly string[]).includes(name);\n}\n\n/** The key an object is stored under in its collection. */\nexport function objectKey(collection: string, item: unknown): string | undefined {\n if (item === null || typeof item !== \"object\") return undefined;\n const o = item as Record<string, unknown>;\n const key = collection === \"members\" ? (o.uri ?? o.id) : (o.id ?? o.uri);\n return typeof key === \"string\" ? key : undefined;\n}\n\n/**\n * A collection as a snapshot holds it, keyed. The TypeScript snapshot holds\n * arrays and the Python snapshot holds objects keyed by id; both are read.\n */\nfunction keyed(collection: string, value: unknown): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (Array.isArray(value)) {\n for (const item of value) {\n const key = objectKey(collection, item);\n if (key !== undefined) out[key] = item;\n }\n } else if (value !== null && typeof value === \"object\") {\n Object.assign(out, value);\n }\n return out;\n}\n\n/** A whole-workspace snapshot as a change that replaces what the store holds. */\nexport function changeFromRecord(record: WorkspaceRecord): StoreChange {\n const data = (record.data !== null && typeof record.data === \"object\"\n ? record.data : {}) as Record<string, unknown>;\n const meta: Record<string, unknown> = {};\n const objects: Record<string, Record<string, unknown>> = {};\n let entries: unknown[] = [];\n for (const [name, value] of Object.entries(data)) {\n if (name === \"audit\") entries = Array.isArray(value) ? value : [];\n else if (name === IDEMPOTENCY_KEYS || isCollection(name)) objects[name] = keyed(name, value);\n else meta[name] = value;\n }\n if (meta.id === undefined) meta.id = record.id;\n return {\n id: record.id, base: 0, version: record.version, updated_at: record.updated_at,\n meta, entries, objects, replace: true,\n };\n}\n\n/**\n * Rows read back from a store, assembled into the snapshot shape\n * `Coordinator.restore()` reads: arrays for the collections, an object for\n * the idempotency keys, and the log in seq order.\n */\nexport function assembleSnapshot(\n meta: Record<string, unknown>,\n entries: unknown[],\n objects: Iterable<[collection: string, key: string, value: unknown]>,\n): Record<string, unknown> {\n const data: Record<string, unknown> = { ...meta };\n for (const name of COLLECTIONS) data[name] = [];\n const keys: Record<string, unknown> = {};\n data[IDEMPOTENCY_KEYS] = keys;\n for (const [collection, key, value] of objects) {\n if (collection === IDEMPOTENCY_KEYS) {\n keys[key] = value;\n } else {\n const list = (Array.isArray(data[collection]) ? data[collection] : (data[collection] = [])) as unknown[];\n list.push(value);\n }\n }\n data.audit = entries;\n return data;\n}\n\ninterface Held {\n meta: string;\n version: number;\n updated_at: string;\n entries: string[];\n objects: Map<string, Map<string, string>>;\n}\n\n/**\n * In-memory store. The default: it keeps nothing across processes. Like a\n * database it keeps each row as JSON text, so a Coordinator restarted on it\n * sees what was written and nothing changed later.\n */\nexport class MemoryStore implements Store {\n private held = new Map<string, Held>();\n\n load(): WorkspaceRecord[] {\n const out: WorkspaceRecord[] = [];\n for (const [id, h] of this.held) {\n const objects: Array<[string, string, unknown]> = [];\n for (const [collection, items] of h.objects) {\n for (const [key, value] of items) objects.push([collection, key, JSON.parse(value)]);\n }\n out.push({\n id,\n data: assembleSnapshot(JSON.parse(h.meta), h.entries.map(e => JSON.parse(e)), objects),\n version: h.version,\n updated_at: h.updated_at,\n });\n }\n return out;\n }\n\n save(record: WorkspaceRecord): void {\n this.apply(changeFromRecord(record));\n }\n\n apply(change: StoreChange): void {\n // Serialised before anything is held, so a change that cannot be written\n // leaves the store as it was.\n const meta = JSON.stringify(change.meta);\n const entries = change.entries.map(e => JSON.stringify(e));\n const objects = Object.entries(change.objects).map(([collection, items]) =>\n [collection, Object.entries(items).map(([key, value]) =>\n [key, value === null || value === undefined ? null : JSON.stringify(value)] as const)] as const);\n let h = this.held.get(change.id);\n if (!change.replace) {\n const holds = h ? h.entries.length : 0;\n if (holds !== change.base) throw new StoreConflictError(change.id, holds, change.base);\n }\n if (!h || change.replace) {\n h = { meta: \"{}\", version: 0, updated_at: \"\", entries: [], objects: new Map() };\n }\n for (const entry of entries) h.entries.push(entry);\n for (const [collection, items] of objects) {\n let held = h.objects.get(collection);\n if (!held) h.objects.set(collection, (held = new Map()));\n for (const [key, value] of items) {\n if (value === null) held.delete(key);\n else held.set(key, value);\n }\n }\n h.meta = meta;\n h.version = change.version;\n h.updated_at = change.updated_at;\n this.held.set(change.id, h);\n }\n\n delete(id: string): void {\n this.held.delete(id);\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brightbeamai/chap-coordinator",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Collaborative Human-Agent Protocol (CHAP): reference TypeScript implementation. Coordinator with Core + every shipped profile, plus pluggable storage and typed method facade.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Brightbeam AI <oss@brightbeam.com>",
|