@brightbeamai/chap-coordinator 0.2.12 → 0.2.13

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/README.md CHANGED
@@ -13,8 +13,9 @@ surface, so the two interoperate on the same JSON-RPC 2.0 wire.
13
13
  npm install @brightbeamai/chap-coordinator
14
14
  ```
15
15
 
16
- Node 20+ required. Zero external runtime dependencies; uses Node's
17
- built-in `crypto` for Ed25519 and JCS.
16
+ Node 20+ required. No required runtime dependencies; uses Node's built-in
17
+ `crypto` for Ed25519 and JCS. `better-sqlite3` is an optional dependency,
18
+ needed only for `SqliteStore`.
18
19
 
19
20
  ## Companion packages
20
21
 
@@ -85,7 +86,7 @@ console.log(resp.result); // { task_id: "tsk_...", state: "created" }
85
86
 
86
87
  | Profile | Methods (per profile spec) |
87
88
  |-----------------------|-------------------------------------------------------------------------------------------|
88
- | `core/1.0` | `workspace.create`, `workspace.describe`, `workspace.set_profiles`, `participant.join`, `participant.leave`, `task.create`, `task.update`, `task.complete`, `audit.read` |
89
+ | `core/1.0` | `workspace.create`, `workspace.describe`, `workspace.set_profiles`, `participant.join`, `participant.leave`, `task.create`, `task.update`, `task.complete`, `audit.read`; a task created with `review_required` completes only on a reviewer decision |
89
90
  | `review/1.0` | `review.request`, `decide.approve`, `decide.reject`, `decide.override`, `abstain.declare`, `escalate.raise` |
90
91
  | `whisper/1.0` | `whisper.ask`, `whisper.answer`; lapse hook via `coord.checkWhisperLapses(workspaceId, now?)` |
91
92
  | `deliberation/1.0` | `deliberate.open`, `deliberate.comment`, `deliberate.vote`, `deliberate.close` |
@@ -100,6 +101,15 @@ console.log(resp.result); // { task_id: "tsk_...", state: "created" }
100
101
 
101
102
  **39 method handlers in total**, matching the Python reference exactly.
102
103
 
104
+ **The review gate.** A task carrying `review_required` does not complete on
105
+ `task.complete`. The call opens a review, holding the submitted output as the
106
+ artefact under review, and only a `decide.*` reaches `completed`. `task.update`
107
+ cannot complete such a task either, and answers `-32602` naming the route
108
+ through. Where no reviewer was named, the set is the human members other than
109
+ the completer and the assignee; a workspace with no eligible human refuses the
110
+ completion rather than opening a review nobody can decide. Under `modes/1.0` a
111
+ trial-mode task has `review_required` set for it.
112
+
103
113
  ## Architecture
104
114
 
105
115
  The coordinator is transport-agnostic, persistence-agnostic, and
@@ -171,7 +181,7 @@ Key lifecycle:
171
181
  - `participant.revoke_key` is an admin operation; envelopes signed
172
182
  with the revoked key after revocation are rejected.
173
183
 
174
- Helpers exported from `@brightbeamai/chap-coordinator/crypto`:
184
+ Helpers exported from `@brightbeamai/chap-coordinator`:
175
185
 
176
186
  ```typescript
177
187
  import {
package/dist/index.cjs CHANGED
@@ -1795,6 +1795,7 @@ function reply(env, body) {
1795
1795
  else out.result = body.result;
1796
1796
  return out;
1797
1797
  }
1798
+ var REVIEW_REQUEST_STOPPED = ["cancelled", "superseded", "paused"];
1798
1799
  var Coordinator = class {
1799
1800
  workspaces = /* @__PURE__ */ new Map();
1800
1801
  options;
@@ -2470,6 +2471,12 @@ var Coordinator = class {
2470
2471
  if (!legal[task.state]?.includes(newState)) {
2471
2472
  return { error: rpcError(E.PARAMS, `Illegal transition ${task.state} -> ${newState}`) };
2472
2473
  }
2474
+ if (newState === "completed" && task.review_required) {
2475
+ return { error: rpcError(
2476
+ E.PARAMS,
2477
+ "This task requires review. Submit the output with task.complete, which opens the review; a reviewer decision completes it."
2478
+ ) };
2479
+ }
2473
2480
  task.state = newState;
2474
2481
  task.updated_at = this.now();
2475
2482
  task.history.push({
@@ -2495,11 +2502,11 @@ var Coordinator = class {
2495
2502
  task.pending_artefact = p.output;
2496
2503
  if (!task.review) {
2497
2504
  const producer = p.from;
2498
- const eligible = [...ws.members.keys()].filter((uri) => uri !== producer && uri !== task.assignee);
2505
+ const eligible = [...ws.members.entries()].filter(([uri, member]) => uri !== producer && uri !== task.assignee && member.type === "human").map(([uri]) => uri);
2499
2506
  if (eligible.length === 0) {
2500
2507
  return { error: rpcError(
2501
2508
  E.NOT_AUTHORISED,
2502
- "Task requires review but has no eligible reviewer (needs a member other than its assignee and completer)"
2509
+ "Task requires review but has no eligible human reviewer (needs a human member other than its assignee and completer)"
2503
2510
  ) };
2504
2511
  }
2505
2512
  task.review = { requested_at: now, requested_to: eligible, rule: "any_one_approves", decisions: [] };
@@ -2560,11 +2567,15 @@ var Coordinator = class {
2560
2567
  if (notMember) return notMember;
2561
2568
  const task = ws.tasks.get(p.task_id);
2562
2569
  if (!task) return { error: rpcError(E.PARAMS, "Unknown task") };
2570
+ if (REVIEW_REQUEST_STOPPED.includes(task.state)) {
2571
+ return { error: rpcError(E.NOT_REVIEWABLE, `Cannot request review for task in state: ${task.state}`) };
2572
+ }
2563
2573
  const to = p.to;
2564
2574
  const reviewers = Array.isArray(to) ? to : typeof to === "string" ? [to] : [];
2565
2575
  if (!reviewers.length) return { error: rpcError(E.PARAMS, "review.request needs 'to'") };
2566
2576
  const now = this.now();
2567
- const rule = p.rule || "any_one_approves";
2577
+ const declaredRule = p.rule;
2578
+ const rule = declaredRule || "any_one_approves";
2568
2579
  if (!reviewRuleSupported(rule)) {
2569
2580
  return { error: rpcError(E.PARAMS, `Unsupported review rule ${JSON.stringify(rule)}: review/1.0 supports any_one_approves, all_approve, quorum:<n>`) };
2570
2581
  }
@@ -2575,7 +2586,7 @@ var Coordinator = class {
2575
2586
  "A review is already open on this task with different content. Decide, abstain, escalate or cancel it before requesting review of a new artefact."
2576
2587
  ) };
2577
2588
  }
2578
- if (rule !== task.review.rule) {
2589
+ if (declaredRule !== void 0 && declaredRule !== task.review.rule) {
2579
2590
  return { error: rpcError(
2580
2591
  E.REVIEW_ALREADY_OPEN,
2581
2592
  `Cannot change the decision rule of an open review (currently ${task.review.rule})`