@brightbeamai/chap-coordinator 0.2.10 → 0.2.12
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-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 +99 -20
- 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 +99 -20
- 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,6 +1722,25 @@ 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;
|
|
@@ -1935,6 +1966,18 @@ var Coordinator = class {
|
|
|
1935
1966
|
if (!isValidEnvelope(envelope) || !envelope.method) {
|
|
1936
1967
|
return reply(envelope, { error: rpcError(E.REQUEST, "Invalid JSON-RPC 2.0 request") });
|
|
1937
1968
|
}
|
|
1969
|
+
const maxBytes = this.options.maxEnvelopeBytes ?? DEFAULT_MAX_ENVELOPE_BYTES;
|
|
1970
|
+
let size = 0;
|
|
1971
|
+
try {
|
|
1972
|
+
size = canonicalize(envelope).length;
|
|
1973
|
+
} catch {
|
|
1974
|
+
}
|
|
1975
|
+
if (size > maxBytes) {
|
|
1976
|
+
return reply(envelope, { error: rpcError(
|
|
1977
|
+
E.REQUEST,
|
|
1978
|
+
`Envelope exceeds max_envelope_bytes (${size} > ${maxBytes})`
|
|
1979
|
+
) });
|
|
1980
|
+
}
|
|
1938
1981
|
const method = envelope.method;
|
|
1939
1982
|
const rawParams = envelope.params;
|
|
1940
1983
|
if (rawParams !== void 0 && rawParams !== null && (typeof rawParams !== "object" || Array.isArray(rawParams))) {
|
|
@@ -1959,7 +2002,7 @@ var Coordinator = class {
|
|
|
1959
2002
|
const stale = this.checkStepUp(params);
|
|
1960
2003
|
if (stale) return reply(envelope, { error: stale });
|
|
1961
2004
|
}
|
|
1962
|
-
if (method.startsWith("control.") || method.startsWith("deliberate.") || method.startsWith("handoff.")) {
|
|
2005
|
+
if (method.startsWith("control.") || method.startsWith("deliberate.") || method.startsWith("handoff.") || method.startsWith("whisper.")) {
|
|
1963
2006
|
const wsId2 = params.workspace;
|
|
1964
2007
|
const ws = typeof wsId2 === "string" ? this.workspaces.get(wsId2) : void 0;
|
|
1965
2008
|
if (ws) {
|
|
@@ -2068,6 +2111,9 @@ var Coordinator = class {
|
|
|
2068
2111
|
if (member.keys.some((k) => k.kid === kid && k.revoked_at !== void 0 && now >= k.revoked_at)) {
|
|
2069
2112
|
return rpcError(E.SIG_KEY_REVOKED, `Key ${kid} is revoked`);
|
|
2070
2113
|
}
|
|
2114
|
+
if (member.keys.some((k) => k.kid === kid && k.valid_until !== void 0 && now >= k.valid_until)) {
|
|
2115
|
+
return rpcError(E.SIG_KEY_NOT_FOUND, `Key ${kid} is no longer valid`);
|
|
2116
|
+
}
|
|
2071
2117
|
const key = this.lookupKey(member, kid, ts);
|
|
2072
2118
|
if (!key) {
|
|
2073
2119
|
const revoked = member.keys.find((k) => k.kid === kid && k.revoked_at);
|
|
@@ -2083,9 +2129,8 @@ var Coordinator = class {
|
|
|
2083
2129
|
return rpcError(E.SIG_VERIFY_FAILED, "Signature failed verification");
|
|
2084
2130
|
}
|
|
2085
2131
|
return null;
|
|
2086
|
-
} catch
|
|
2087
|
-
|
|
2088
|
-
return rpcError(E.INTERNAL, `Signature check error: ${msg}`);
|
|
2132
|
+
} catch {
|
|
2133
|
+
return rpcError(E.SIG_VERIFY_FAILED, "Signature failed verification");
|
|
2089
2134
|
}
|
|
2090
2135
|
}
|
|
2091
2136
|
/** Lookup the key valid for a given participant at a given time. */
|
|
@@ -2209,6 +2254,7 @@ var Coordinator = class {
|
|
|
2209
2254
|
state: ws.state,
|
|
2210
2255
|
mode: ws.mode,
|
|
2211
2256
|
mode_ceiling: ws.mode_ceiling,
|
|
2257
|
+
max_envelope_bytes: this.options.maxEnvelopeBytes ?? DEFAULT_MAX_ENVELOPE_BYTES,
|
|
2212
2258
|
step_up_window_sec: ws.step_up_window_sec,
|
|
2213
2259
|
profiles: ws.profiles,
|
|
2214
2260
|
members: Array.from(ws.members.values()).map(memberToDict),
|
|
@@ -2349,10 +2395,14 @@ var Coordinator = class {
|
|
|
2349
2395
|
history: [{ ts: now, from: p.from, state: "created" }],
|
|
2350
2396
|
paused: false
|
|
2351
2397
|
};
|
|
2352
|
-
if (task.mode === "trial") task.review_required = true;
|
|
2398
|
+
if (ws.profiles.some((pr) => pr.startsWith("modes/")) && task.mode === "trial") task.review_required = true;
|
|
2353
2399
|
else if ("review_required" in p) task.review_required = !!p.review_required;
|
|
2354
2400
|
ws.tasks.set(taskId, task);
|
|
2355
|
-
if (typeof p.idempotency_key === "string")
|
|
2401
|
+
if (typeof p.idempotency_key === "string") {
|
|
2402
|
+
ws.idempotency_keys[p.idempotency_key] = taskId;
|
|
2403
|
+
const keys = Object.keys(ws.idempotency_keys);
|
|
2404
|
+
if (keys.length > MAX_IDEMPOTENCY_KEYS) delete ws.idempotency_keys[keys[0]];
|
|
2405
|
+
}
|
|
2356
2406
|
return { result: { task_id: taskId, state: "created" } };
|
|
2357
2407
|
}
|
|
2358
2408
|
opTaskUpdate(p) {
|
|
@@ -2366,7 +2416,7 @@ var Coordinator = class {
|
|
|
2366
2416
|
const legal = {
|
|
2367
2417
|
created: ["in_progress", "declined", "paused"],
|
|
2368
2418
|
in_progress: ["in_progress", "completed", "declined", "review_requested", "paused"],
|
|
2369
|
-
review_requested: ["in_progress"
|
|
2419
|
+
review_requested: ["in_progress"],
|
|
2370
2420
|
paused: ["in_progress", "cancelled"]
|
|
2371
2421
|
};
|
|
2372
2422
|
if (!legal[task.state]?.includes(newState)) {
|
|
@@ -2392,6 +2442,30 @@ var Coordinator = class {
|
|
|
2392
2442
|
if (task.state !== "created" && task.state !== "in_progress") {
|
|
2393
2443
|
return { error: rpcError(E.PARAMS, `Cannot complete task in state: ${task.state}`) };
|
|
2394
2444
|
}
|
|
2445
|
+
if (task.review_required) {
|
|
2446
|
+
const now = this.now();
|
|
2447
|
+
task.pending_artefact = p.output;
|
|
2448
|
+
if (!task.review) {
|
|
2449
|
+
const producer = p.from;
|
|
2450
|
+
const eligible = [...ws.members.keys()].filter((uri) => uri !== producer && uri !== task.assignee);
|
|
2451
|
+
if (eligible.length === 0) {
|
|
2452
|
+
return { error: rpcError(
|
|
2453
|
+
E.NOT_AUTHORISED,
|
|
2454
|
+
"Task requires review but has no eligible reviewer (needs a member other than its assignee and completer)"
|
|
2455
|
+
) };
|
|
2456
|
+
}
|
|
2457
|
+
task.review = { requested_at: now, requested_to: eligible, rule: "any_one_approves", decisions: [] };
|
|
2458
|
+
}
|
|
2459
|
+
task.state = "review_requested";
|
|
2460
|
+
task.updated_at = now;
|
|
2461
|
+
task.history.push({
|
|
2462
|
+
ts: now,
|
|
2463
|
+
from: p.from,
|
|
2464
|
+
state: "review_requested",
|
|
2465
|
+
note: "review required; opened on task.complete"
|
|
2466
|
+
});
|
|
2467
|
+
return { result: { state: "review_requested", review_id: task.id } };
|
|
2468
|
+
}
|
|
2395
2469
|
task.output = p.output;
|
|
2396
2470
|
if (typeof p.confidence === "number" || typeof p.confidence === "string") task.confidence = p.confidence;
|
|
2397
2471
|
task.state = "completed";
|
|
@@ -2443,6 +2517,9 @@ var Coordinator = class {
|
|
|
2443
2517
|
if (!reviewers.length) return { error: rpcError(E.PARAMS, "review.request needs 'to'") };
|
|
2444
2518
|
const now = this.now();
|
|
2445
2519
|
const rule = p.rule || "any_one_approves";
|
|
2520
|
+
if (!reviewRuleSupported(rule)) {
|
|
2521
|
+
return { error: rpcError(E.PARAMS, `Unsupported review rule ${JSON.stringify(rule)}: review/1.0 supports any_one_approves, all_approve, quorum:<n>`) };
|
|
2522
|
+
}
|
|
2446
2523
|
if (task.state === "review_requested" && task.review) {
|
|
2447
2524
|
if (contentHash(task.pending_artefact) !== contentHash(p.artefact)) {
|
|
2448
2525
|
return { error: rpcError(
|
|
@@ -2539,8 +2616,10 @@ var Coordinator = class {
|
|
|
2539
2616
|
tags: p.tags
|
|
2540
2617
|
});
|
|
2541
2618
|
if (kind === "approve") {
|
|
2542
|
-
task.
|
|
2543
|
-
|
|
2619
|
+
if (reviewSatisfied(task.review)) {
|
|
2620
|
+
task.output = task.pending_artefact;
|
|
2621
|
+
task.state = "completed";
|
|
2622
|
+
}
|
|
2544
2623
|
} else {
|
|
2545
2624
|
task.state = p.request_revision ? "in_progress" : "declined";
|
|
2546
2625
|
}
|