@brightbeamai/chap-coordinator 0.2.10 → 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 +14 -4
- package/dist/{api-Cvkhcalw.d.ts → api-BOGEBNmX.d.ts} +2 -0
- package/dist/{api-CcT_8H5j.d.cts → api-DiiL0sil.d.cts} +2 -0
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/index.cjs +112 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +112 -22
- package/dist/index.js.map +1 -1
- package/dist/patch.cjs +8 -7
- package/dist/patch.cjs.map +1 -1
- package/dist/patch.js +8 -7
- package/dist/patch.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CoordinatorOptions, a as Coordinator } from './api-
|
|
2
|
-
export { b as CoordinatorApi, c as CoordinatorError, d as CredentialVerifier, E as EscalationPolicyFn, H as Handler, I as IdFactory, P as PRIVILEGED_METHODS, R as ReviewDepthPolicyFn, e as RoutingPolicyFn, S as ScittReceiptVerifier, f as ScittSubmitter, T as TokenVerifier, g as call, m as makeApi } from './api-
|
|
1
|
+
import { C as CoordinatorOptions, a as Coordinator } from './api-DiiL0sil.cjs';
|
|
2
|
+
export { b as CoordinatorApi, c as CoordinatorError, d as CredentialVerifier, E as EscalationPolicyFn, H as Handler, I as IdFactory, P as PRIVILEGED_METHODS, R as ReviewDepthPolicyFn, e as RoutingPolicyFn, S as ScittReceiptVerifier, f as ScittSubmitter, T as TokenVerifier, g as call, m as makeApi } from './api-DiiL0sil.cjs';
|
|
3
3
|
export { MethodName, MethodParams, MethodResult, MethodTable } from './methods.cjs';
|
|
4
4
|
export { MemoryStore, Store, WorkspaceRecord } from './storage/store.cjs';
|
|
5
5
|
export { ArtefactId, ArtefactRoutingHints, AuditEntry, AuditListener, Deliberation, DeliberationVote, Envelope, Handoff, HandoffTaskItem, JsonPatchOp, Jwk, KeyRecord, Member, Mode, OverrideArtefact, ParticipantType, ParticipantUri, PauseScope, ReviewDecision, RouteDecisionArtefact, SnapshotArtefact, Task, TaskId, TaskRoutingHints, TaskState, WhisperPrompt, Workspace, WorkspaceId, WorkspaceState, modeLE } from './types.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CoordinatorOptions, a as Coordinator } from './api-
|
|
2
|
-
export { b as CoordinatorApi, c as CoordinatorError, d as CredentialVerifier, E as EscalationPolicyFn, H as Handler, I as IdFactory, P as PRIVILEGED_METHODS, R as ReviewDepthPolicyFn, e as RoutingPolicyFn, S as ScittReceiptVerifier, f as ScittSubmitter, T as TokenVerifier, g as call, m as makeApi } from './api-
|
|
1
|
+
import { C as CoordinatorOptions, a as Coordinator } from './api-BOGEBNmX.js';
|
|
2
|
+
export { b as CoordinatorApi, c as CoordinatorError, d as CredentialVerifier, E as EscalationPolicyFn, H as Handler, I as IdFactory, P as PRIVILEGED_METHODS, R as ReviewDepthPolicyFn, e as RoutingPolicyFn, S as ScittReceiptVerifier, f as ScittSubmitter, T as TokenVerifier, g as call, m as makeApi } from './api-BOGEBNmX.js';
|
|
3
3
|
export { MethodName, MethodParams, MethodResult, MethodTable } from './methods.js';
|
|
4
4
|
export { MemoryStore, Store, WorkspaceRecord } from './storage/store.js';
|
|
5
5
|
export { ArtefactId, ArtefactRoutingHints, AuditEntry, AuditListener, Deliberation, DeliberationVote, Envelope, Handoff, HandoffTaskItem, JsonPatchOp, Jwk, KeyRecord, Member, Mode, OverrideArtefact, ParticipantType, ParticipantUri, PauseScope, ReviewDecision, RouteDecisionArtefact, SnapshotArtefact, Task, TaskId, TaskRoutingHints, TaskState, WhisperPrompt, Workspace, WorkspaceId, WorkspaceState, modeLE } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -275,6 +275,11 @@ function isValidEnvelope(env) {
|
|
|
275
275
|
var PatchError = class extends Error {
|
|
276
276
|
};
|
|
277
277
|
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
278
|
+
var ARRAY_INDEX_RE = /^(0|[1-9][0-9]*)$/;
|
|
279
|
+
function arrayIndex(seg) {
|
|
280
|
+
if (!ARRAY_INDEX_RE.test(seg)) throw new PatchError(`Array index expected at ${JSON.stringify(seg)}`);
|
|
281
|
+
return Number(seg);
|
|
282
|
+
}
|
|
278
283
|
function unescape(token) {
|
|
279
284
|
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
280
285
|
}
|
|
@@ -299,8 +304,8 @@ function navigate(doc, parts) {
|
|
|
299
304
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
300
305
|
const part = parts[i];
|
|
301
306
|
if (Array.isArray(parent)) {
|
|
302
|
-
const idx =
|
|
303
|
-
if (
|
|
307
|
+
const idx = arrayIndex(part);
|
|
308
|
+
if (idx >= parent.length) {
|
|
304
309
|
throw new PatchError(`Index out of range at /${parts.slice(0, i + 1).join("/")}`);
|
|
305
310
|
}
|
|
306
311
|
parent = parent[idx];
|
|
@@ -316,11 +321,7 @@ function navigate(doc, parts) {
|
|
|
316
321
|
const last = parts[parts.length - 1];
|
|
317
322
|
if (Array.isArray(parent)) {
|
|
318
323
|
if (last === "-") return [parent, "-"];
|
|
319
|
-
|
|
320
|
-
if (!Number.isInteger(idx)) {
|
|
321
|
-
throw new PatchError(`Array index expected at ${JSON.stringify(last)}`);
|
|
322
|
-
}
|
|
323
|
-
return [parent, idx];
|
|
324
|
+
return [parent, arrayIndex(last)];
|
|
324
325
|
}
|
|
325
326
|
return [parent, last];
|
|
326
327
|
}
|
|
@@ -771,7 +772,7 @@ function registerControl(coord) {
|
|
|
771
772
|
if (scope === "task") {
|
|
772
773
|
const task = ws.tasks.get(p.task_id);
|
|
773
774
|
if (!task) return { error: rpcError(E.PARAMS, "Unknown task") };
|
|
774
|
-
if (task.state === "completed" || task.state === "declined" || task.state === "cancelled") {
|
|
775
|
+
if (task.state === "completed" || task.state === "declined" || task.state === "cancelled" || task.state === "superseded") {
|
|
775
776
|
return { error: rpcError(E.CONTROL_NOT_AUTHORISED, `Cannot pause ${task.state} task`) };
|
|
776
777
|
}
|
|
777
778
|
task.paused = true;
|
|
@@ -828,7 +829,7 @@ function registerControl(coord) {
|
|
|
828
829
|
if (!ws) return { error: rpcError(E.PARAMS, "Unknown workspace") };
|
|
829
830
|
const task = ws.tasks.get(p.task_id);
|
|
830
831
|
if (!task) return { error: rpcError(E.PARAMS, "Unknown task") };
|
|
831
|
-
if (task.state === "completed" || task.state === "declined" || task.state === "cancelled") {
|
|
832
|
+
if (task.state === "completed" || task.state === "declined" || task.state === "cancelled" || task.state === "superseded") {
|
|
832
833
|
return { error: rpcError(E.CONTROL_NOT_AUTHORISED, `Cannot cancel ${task.state} task`) };
|
|
833
834
|
}
|
|
834
835
|
task.state = "cancelled";
|
|
@@ -850,7 +851,7 @@ function registerControl(coord) {
|
|
|
850
851
|
}));
|
|
851
852
|
}
|
|
852
853
|
if (include.includes("open_tasks")) {
|
|
853
|
-
state.open_tasks = Array.from(ws.tasks.values()).filter((t) => t.state !== "completed" && t.state !== "declined" && t.state !== "cancelled").map((t) => ({ id: t.id, kind: t.kind, state: t.state, assignee: t.assignee }));
|
|
854
|
+
state.open_tasks = Array.from(ws.tasks.values()).filter((t) => t.state !== "completed" && t.state !== "declined" && t.state !== "cancelled" && t.state !== "superseded").map((t) => ({ id: t.id, kind: t.kind, state: t.state, assignee: t.assignee }));
|
|
854
855
|
}
|
|
855
856
|
if (include.includes("mode_ceiling")) state.mode_ceiling = ws.mode_ceiling;
|
|
856
857
|
if (include.includes("policy")) state.routing_policy_uri = ws.routing_policy_uri;
|
|
@@ -918,6 +919,13 @@ function registerControl(coord) {
|
|
|
918
919
|
if (!ws.members.has(assignee)) {
|
|
919
920
|
return { error: rpcError(E.PARAMS, "successor assignee not in workspace") };
|
|
920
921
|
}
|
|
922
|
+
const requestedMode = newSpec.mode ?? old.mode;
|
|
923
|
+
if (!modeLE(requestedMode, ws.mode_ceiling)) {
|
|
924
|
+
return { error: rpcError(
|
|
925
|
+
E.MODE_CEILING_EXCEEDED,
|
|
926
|
+
`Requested mode ${requestedMode} exceeds ceiling ${ws.mode_ceiling}`
|
|
927
|
+
) };
|
|
928
|
+
}
|
|
921
929
|
const now = coord.now();
|
|
922
930
|
const newId = coord.ids.taskId();
|
|
923
931
|
const newTask = {
|
|
@@ -929,7 +937,7 @@ function registerControl(coord) {
|
|
|
929
937
|
input: newSpec.input ?? {},
|
|
930
938
|
created_at: now,
|
|
931
939
|
updated_at: now,
|
|
932
|
-
mode:
|
|
940
|
+
mode: requestedMode,
|
|
933
941
|
supersedes: old.id,
|
|
934
942
|
history: [{
|
|
935
943
|
ts: now,
|
|
@@ -939,6 +947,8 @@ function registerControl(coord) {
|
|
|
939
947
|
}],
|
|
940
948
|
paused: false
|
|
941
949
|
};
|
|
950
|
+
if (ws.profiles.some((pr) => pr.startsWith("modes/")) && newTask.mode === "trial") newTask.review_required = true;
|
|
951
|
+
else if ("review_required" in newSpec) newTask.review_required = !!newSpec.review_required;
|
|
942
952
|
ws.tasks.set(newId, newTask);
|
|
943
953
|
old.state = "superseded";
|
|
944
954
|
old.superseded_by = newId;
|
|
@@ -1692,6 +1702,8 @@ var READ_ONLY_METHODS = /* @__PURE__ */ new Set([
|
|
|
1692
1702
|
"audit.verify_chain",
|
|
1693
1703
|
"audit.verify_receipt"
|
|
1694
1704
|
]);
|
|
1705
|
+
var MAX_IDEMPOTENCY_KEYS = 1e4;
|
|
1706
|
+
var DEFAULT_MAX_ENVELOPE_BYTES = 1048576;
|
|
1695
1707
|
function nowIso(clockMs) {
|
|
1696
1708
|
const d = clockMs === void 0 ? /* @__PURE__ */ new Date() : new Date(clockMs);
|
|
1697
1709
|
return d.toISOString().replace(/\.(\d{3})Z$/, ".$1Z");
|
|
@@ -1710,12 +1722,32 @@ function tagsError(p) {
|
|
|
1710
1722
|
function linkHash(envelope, prev) {
|
|
1711
1723
|
return sha256Hex(Buffer.concat([canonicalize(envelope), Buffer.from(prev, "utf-8")]));
|
|
1712
1724
|
}
|
|
1725
|
+
function reviewRuleSupported(rule) {
|
|
1726
|
+
if (rule === "any_one_approves" || rule === "all_approve") return true;
|
|
1727
|
+
if (rule.startsWith("quorum:")) {
|
|
1728
|
+
const arg = rule.slice("quorum:".length);
|
|
1729
|
+
return /^[0-9]+$/.test(arg) && parseInt(arg, 10) >= 1;
|
|
1730
|
+
}
|
|
1731
|
+
return false;
|
|
1732
|
+
}
|
|
1733
|
+
function reviewSatisfied(review) {
|
|
1734
|
+
const approvers = new Set(review.decisions.filter((d) => d.kind === "approve").map((d) => d.reviewer));
|
|
1735
|
+
const rule = review.rule || "any_one_approves";
|
|
1736
|
+
if (rule === "all_approve") {
|
|
1737
|
+
const named = review.requested_to.filter((r) => !(r.startsWith("workspace:") || r.startsWith("group:")));
|
|
1738
|
+
if (named.length === 0) return approvers.size >= 1;
|
|
1739
|
+
return named.every((r) => approvers.has(r));
|
|
1740
|
+
}
|
|
1741
|
+
if (rule.startsWith("quorum:")) return approvers.size >= parseInt(rule.slice("quorum:".length), 10);
|
|
1742
|
+
return approvers.size >= 1;
|
|
1743
|
+
}
|
|
1713
1744
|
function reply(env, body) {
|
|
1714
1745
|
const out = { jsonrpc: "2.0", id: env.id };
|
|
1715
1746
|
if (body.error !== void 0) out.error = body.error;
|
|
1716
1747
|
else out.result = body.result;
|
|
1717
1748
|
return out;
|
|
1718
1749
|
}
|
|
1750
|
+
var REVIEW_REQUEST_STOPPED = ["cancelled", "superseded", "paused"];
|
|
1719
1751
|
var Coordinator = class {
|
|
1720
1752
|
workspaces = /* @__PURE__ */ new Map();
|
|
1721
1753
|
options;
|
|
@@ -1935,6 +1967,18 @@ var Coordinator = class {
|
|
|
1935
1967
|
if (!isValidEnvelope(envelope) || !envelope.method) {
|
|
1936
1968
|
return reply(envelope, { error: rpcError(E.REQUEST, "Invalid JSON-RPC 2.0 request") });
|
|
1937
1969
|
}
|
|
1970
|
+
const maxBytes = this.options.maxEnvelopeBytes ?? DEFAULT_MAX_ENVELOPE_BYTES;
|
|
1971
|
+
let size = 0;
|
|
1972
|
+
try {
|
|
1973
|
+
size = canonicalize(envelope).length;
|
|
1974
|
+
} catch {
|
|
1975
|
+
}
|
|
1976
|
+
if (size > maxBytes) {
|
|
1977
|
+
return reply(envelope, { error: rpcError(
|
|
1978
|
+
E.REQUEST,
|
|
1979
|
+
`Envelope exceeds max_envelope_bytes (${size} > ${maxBytes})`
|
|
1980
|
+
) });
|
|
1981
|
+
}
|
|
1938
1982
|
const method = envelope.method;
|
|
1939
1983
|
const rawParams = envelope.params;
|
|
1940
1984
|
if (rawParams !== void 0 && rawParams !== null && (typeof rawParams !== "object" || Array.isArray(rawParams))) {
|
|
@@ -1959,7 +2003,7 @@ var Coordinator = class {
|
|
|
1959
2003
|
const stale = this.checkStepUp(params);
|
|
1960
2004
|
if (stale) return reply(envelope, { error: stale });
|
|
1961
2005
|
}
|
|
1962
|
-
if (method.startsWith("control.") || method.startsWith("deliberate.") || method.startsWith("handoff.")) {
|
|
2006
|
+
if (method.startsWith("control.") || method.startsWith("deliberate.") || method.startsWith("handoff.") || method.startsWith("whisper.")) {
|
|
1963
2007
|
const wsId2 = params.workspace;
|
|
1964
2008
|
const ws = typeof wsId2 === "string" ? this.workspaces.get(wsId2) : void 0;
|
|
1965
2009
|
if (ws) {
|
|
@@ -2068,6 +2112,9 @@ var Coordinator = class {
|
|
|
2068
2112
|
if (member.keys.some((k) => k.kid === kid && k.revoked_at !== void 0 && now >= k.revoked_at)) {
|
|
2069
2113
|
return rpcError(E.SIG_KEY_REVOKED, `Key ${kid} is revoked`);
|
|
2070
2114
|
}
|
|
2115
|
+
if (member.keys.some((k) => k.kid === kid && k.valid_until !== void 0 && now >= k.valid_until)) {
|
|
2116
|
+
return rpcError(E.SIG_KEY_NOT_FOUND, `Key ${kid} is no longer valid`);
|
|
2117
|
+
}
|
|
2071
2118
|
const key = this.lookupKey(member, kid, ts);
|
|
2072
2119
|
if (!key) {
|
|
2073
2120
|
const revoked = member.keys.find((k) => k.kid === kid && k.revoked_at);
|
|
@@ -2083,9 +2130,8 @@ var Coordinator = class {
|
|
|
2083
2130
|
return rpcError(E.SIG_VERIFY_FAILED, "Signature failed verification");
|
|
2084
2131
|
}
|
|
2085
2132
|
return null;
|
|
2086
|
-
} catch
|
|
2087
|
-
|
|
2088
|
-
return rpcError(E.INTERNAL, `Signature check error: ${msg}`);
|
|
2133
|
+
} catch {
|
|
2134
|
+
return rpcError(E.SIG_VERIFY_FAILED, "Signature failed verification");
|
|
2089
2135
|
}
|
|
2090
2136
|
}
|
|
2091
2137
|
/** Lookup the key valid for a given participant at a given time. */
|
|
@@ -2209,6 +2255,7 @@ var Coordinator = class {
|
|
|
2209
2255
|
state: ws.state,
|
|
2210
2256
|
mode: ws.mode,
|
|
2211
2257
|
mode_ceiling: ws.mode_ceiling,
|
|
2258
|
+
max_envelope_bytes: this.options.maxEnvelopeBytes ?? DEFAULT_MAX_ENVELOPE_BYTES,
|
|
2212
2259
|
step_up_window_sec: ws.step_up_window_sec,
|
|
2213
2260
|
profiles: ws.profiles,
|
|
2214
2261
|
members: Array.from(ws.members.values()).map(memberToDict),
|
|
@@ -2349,10 +2396,14 @@ var Coordinator = class {
|
|
|
2349
2396
|
history: [{ ts: now, from: p.from, state: "created" }],
|
|
2350
2397
|
paused: false
|
|
2351
2398
|
};
|
|
2352
|
-
if (task.mode === "trial") task.review_required = true;
|
|
2399
|
+
if (ws.profiles.some((pr) => pr.startsWith("modes/")) && task.mode === "trial") task.review_required = true;
|
|
2353
2400
|
else if ("review_required" in p) task.review_required = !!p.review_required;
|
|
2354
2401
|
ws.tasks.set(taskId, task);
|
|
2355
|
-
if (typeof p.idempotency_key === "string")
|
|
2402
|
+
if (typeof p.idempotency_key === "string") {
|
|
2403
|
+
ws.idempotency_keys[p.idempotency_key] = taskId;
|
|
2404
|
+
const keys = Object.keys(ws.idempotency_keys);
|
|
2405
|
+
if (keys.length > MAX_IDEMPOTENCY_KEYS) delete ws.idempotency_keys[keys[0]];
|
|
2406
|
+
}
|
|
2356
2407
|
return { result: { task_id: taskId, state: "created" } };
|
|
2357
2408
|
}
|
|
2358
2409
|
opTaskUpdate(p) {
|
|
@@ -2366,12 +2417,18 @@ var Coordinator = class {
|
|
|
2366
2417
|
const legal = {
|
|
2367
2418
|
created: ["in_progress", "declined", "paused"],
|
|
2368
2419
|
in_progress: ["in_progress", "completed", "declined", "review_requested", "paused"],
|
|
2369
|
-
review_requested: ["in_progress"
|
|
2420
|
+
review_requested: ["in_progress"],
|
|
2370
2421
|
paused: ["in_progress", "cancelled"]
|
|
2371
2422
|
};
|
|
2372
2423
|
if (!legal[task.state]?.includes(newState)) {
|
|
2373
2424
|
return { error: rpcError(E.PARAMS, `Illegal transition ${task.state} -> ${newState}`) };
|
|
2374
2425
|
}
|
|
2426
|
+
if (newState === "completed" && task.review_required) {
|
|
2427
|
+
return { error: rpcError(
|
|
2428
|
+
E.PARAMS,
|
|
2429
|
+
"This task requires review. Submit the output with task.complete, which opens the review; a reviewer decision completes it."
|
|
2430
|
+
) };
|
|
2431
|
+
}
|
|
2375
2432
|
task.state = newState;
|
|
2376
2433
|
task.updated_at = this.now();
|
|
2377
2434
|
task.history.push({
|
|
@@ -2392,6 +2449,30 @@ var Coordinator = class {
|
|
|
2392
2449
|
if (task.state !== "created" && task.state !== "in_progress") {
|
|
2393
2450
|
return { error: rpcError(E.PARAMS, `Cannot complete task in state: ${task.state}`) };
|
|
2394
2451
|
}
|
|
2452
|
+
if (task.review_required) {
|
|
2453
|
+
const now = this.now();
|
|
2454
|
+
task.pending_artefact = p.output;
|
|
2455
|
+
if (!task.review) {
|
|
2456
|
+
const producer = p.from;
|
|
2457
|
+
const eligible = [...ws.members.entries()].filter(([uri, member]) => uri !== producer && uri !== task.assignee && member.type === "human").map(([uri]) => uri);
|
|
2458
|
+
if (eligible.length === 0) {
|
|
2459
|
+
return { error: rpcError(
|
|
2460
|
+
E.NOT_AUTHORISED,
|
|
2461
|
+
"Task requires review but has no eligible human reviewer (needs a human member other than its assignee and completer)"
|
|
2462
|
+
) };
|
|
2463
|
+
}
|
|
2464
|
+
task.review = { requested_at: now, requested_to: eligible, rule: "any_one_approves", decisions: [] };
|
|
2465
|
+
}
|
|
2466
|
+
task.state = "review_requested";
|
|
2467
|
+
task.updated_at = now;
|
|
2468
|
+
task.history.push({
|
|
2469
|
+
ts: now,
|
|
2470
|
+
from: p.from,
|
|
2471
|
+
state: "review_requested",
|
|
2472
|
+
note: "review required; opened on task.complete"
|
|
2473
|
+
});
|
|
2474
|
+
return { result: { state: "review_requested", review_id: task.id } };
|
|
2475
|
+
}
|
|
2395
2476
|
task.output = p.output;
|
|
2396
2477
|
if (typeof p.confidence === "number" || typeof p.confidence === "string") task.confidence = p.confidence;
|
|
2397
2478
|
task.state = "completed";
|
|
@@ -2438,11 +2519,18 @@ var Coordinator = class {
|
|
|
2438
2519
|
if (notMember) return notMember;
|
|
2439
2520
|
const task = ws.tasks.get(p.task_id);
|
|
2440
2521
|
if (!task) return { error: rpcError(E.PARAMS, "Unknown task") };
|
|
2522
|
+
if (REVIEW_REQUEST_STOPPED.includes(task.state)) {
|
|
2523
|
+
return { error: rpcError(E.NOT_REVIEWABLE, `Cannot request review for task in state: ${task.state}`) };
|
|
2524
|
+
}
|
|
2441
2525
|
const to = p.to;
|
|
2442
2526
|
const reviewers = Array.isArray(to) ? to : typeof to === "string" ? [to] : [];
|
|
2443
2527
|
if (!reviewers.length) return { error: rpcError(E.PARAMS, "review.request needs 'to'") };
|
|
2444
2528
|
const now = this.now();
|
|
2445
|
-
const
|
|
2529
|
+
const declaredRule = p.rule;
|
|
2530
|
+
const rule = declaredRule || "any_one_approves";
|
|
2531
|
+
if (!reviewRuleSupported(rule)) {
|
|
2532
|
+
return { error: rpcError(E.PARAMS, `Unsupported review rule ${JSON.stringify(rule)}: review/1.0 supports any_one_approves, all_approve, quorum:<n>`) };
|
|
2533
|
+
}
|
|
2446
2534
|
if (task.state === "review_requested" && task.review) {
|
|
2447
2535
|
if (contentHash(task.pending_artefact) !== contentHash(p.artefact)) {
|
|
2448
2536
|
return { error: rpcError(
|
|
@@ -2450,7 +2538,7 @@ var Coordinator = class {
|
|
|
2450
2538
|
"A review is already open on this task with different content. Decide, abstain, escalate or cancel it before requesting review of a new artefact."
|
|
2451
2539
|
) };
|
|
2452
2540
|
}
|
|
2453
|
-
if (
|
|
2541
|
+
if (declaredRule !== void 0 && declaredRule !== task.review.rule) {
|
|
2454
2542
|
return { error: rpcError(
|
|
2455
2543
|
E.REVIEW_ALREADY_OPEN,
|
|
2456
2544
|
`Cannot change the decision rule of an open review (currently ${task.review.rule})`
|
|
@@ -2539,8 +2627,10 @@ var Coordinator = class {
|
|
|
2539
2627
|
tags: p.tags
|
|
2540
2628
|
});
|
|
2541
2629
|
if (kind === "approve") {
|
|
2542
|
-
task.
|
|
2543
|
-
|
|
2630
|
+
if (reviewSatisfied(task.review)) {
|
|
2631
|
+
task.output = task.pending_artefact;
|
|
2632
|
+
task.state = "completed";
|
|
2633
|
+
}
|
|
2544
2634
|
} else {
|
|
2545
2635
|
task.state = p.request_revision ? "in_progress" : "declined";
|
|
2546
2636
|
}
|