@vincemakes/kiso-core 0.1.14 → 0.1.16

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.
@@ -592,6 +592,8 @@ async function* executeOne(call, registry, hooks, ctx, log, resolveApproval, res
592
592
  let chainVerdict;
593
593
  let deniedReason;
594
594
  let deniedBy;
595
+ let firstSpeaker; // the first non-abstain verdict's extension
596
+ let anySpoke = false;
595
597
  if (durable === undefined && approvalPolicies !== undefined && approvalPolicies.length > 0) {
596
598
  for (const { extension, policy } of approvalPolicies) {
597
599
  let v;
@@ -599,8 +601,12 @@ async function* executeOne(call, registry, hooks, ctx, log, resolveApproval, res
599
601
  v = await raceAbort(Promise.resolve(policy.decide(payload, ctx)), signal);
600
602
  }
601
603
  catch {
602
- v = { action: "ask" }; // 抛错 = 该扩展计为 ask
604
+ v = { action: "ask" }; // 抛错 = 该扩展计为 ask —— 发声,绝不静默
603
605
  }
606
+ if (v.action === "abstain")
607
+ continue; // no opinion — not a verdict
608
+ anySpoke = true;
609
+ firstSpeaker ??= extension;
604
610
  if (v.action === "deny") {
605
611
  deniedBy ??= extension;
606
612
  deniedReason ??= v.reason; // the FIRST denial's reason
@@ -612,19 +618,26 @@ async function* executeOne(call, registry, hooks, ctx, log, resolveApproval, res
612
618
  if (deniedBy !== undefined) {
613
619
  chainVerdict = { action: "deny", reason: deniedReason ?? "denied" };
614
620
  }
621
+ else if (chainVerdict === undefined && anySpoke) {
622
+ chainVerdict = { action: "allow" }; // 全发声者皆 allow
623
+ }
615
624
  else if (chainVerdict === undefined) {
616
- chainVerdict = { action: "allow" };
625
+ // 全员 abstain (ADR-0042): NO policy speaks — the call falls to
626
+ // the ask flow below, never to a silent auto-approve. The human
627
+ // decides; absent a channel, awaitHumanApproval's honest denial.
628
+ chainVerdict = { action: "ask" };
617
629
  }
618
630
  if (chainVerdict.action !== "ask") {
619
- // allow/deny are PERSISTED FACTS (decidedBy = the extension) —
620
- // never a human pause.
631
+ // allow/deny are PERSISTED FACTS (decidedBy = a SPEAKING
632
+ // extension — never the chain head on behalf of a non-speaker)
633
+ // — never a human pause.
621
634
  yield log.append({
622
635
  type: "permission_decided",
623
636
  decisionId: `d-${log.lastSeq + 1}`,
624
637
  callId: call.callId,
625
638
  decision: chainVerdict.action === "allow" ? "approved" : "denied",
626
639
  ...(chainVerdict.action === "deny" ? { reason: chainVerdict.reason } : {}),
627
- decidedBy: deniedBy ?? approvalPolicies[0].extension,
640
+ decidedBy: deniedBy ?? firstSpeaker,
628
641
  });
629
642
  }
630
643
  }
@@ -16,7 +16,13 @@ export interface PolicyCall {
16
16
  }
17
17
  /**
18
18
  * A policy's verdict. `ask` defers to the existing human approval flow;
19
- * `deny` carries the reason the model sees; `allow` auto-approves.
19
+ * `deny` carries the reason the model sees; `allow` auto-approves;
20
+ * `abstain` is NO opinion — the policy does not speak (it neither allows,
21
+ * denies, nor asks), and the chain composes only over the SPEAKING
22
+ * verdicts. An all-abstain chain falls to the ask flow (the human
23
+ * decides; absent a channel, an honest denial) — abstaining is never a
24
+ * silent allow (ADR-0042: `allow` as "no opinion" auto-approved tools no
25
+ * policy meant to approve and misattributed decidedBy to a non-speaker).
20
26
  */
21
27
  export type PolicyVerdict = {
22
28
  readonly action: "allow";
@@ -25,6 +31,8 @@ export type PolicyVerdict = {
25
31
  readonly reason: string;
26
32
  } | {
27
33
  readonly action: "ask";
34
+ } | {
35
+ readonly action: "abstain";
28
36
  };
29
37
  /** One approval policy — a pure decide function over a tool call. */
30
38
  export interface ApprovalPolicy {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "kiso(基礎) core — protocol, event log, loop, hooks, modes, permissions, compaction, delivery truth. The 2,000-line kernel at the bottom of the kiso framework.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "openai"
34
34
  ],
35
35
  "devDependencies": {
36
- "@vincemakes/kiso-evals": "0.1.14",
36
+ "@vincemakes/kiso-evals": "0.1.16",
37
37
  "@types/node": "^26.1.2",
38
38
  "typescript": "^5.7.2",
39
39
  "vitest": "^3.0.0"