@drakon-systems/shieldcortex-realtime 4.51.0 → 4.52.1

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/index.js CHANGED
@@ -2761,6 +2761,10 @@ function resolveBrokerRuntime(defenceMod, rawBrokerConfig, api) {
2761
2761
  return {
2762
2762
  config,
2763
2763
  runJudge: defenceMod.runJudge,
2764
+ // #143 residual, and deliberately NOT in `needed`: an older shieldcortex
2765
+ // build has runJudge alone, and the interceptor falls back to it. Missing
2766
+ // it costs an audit field, never a gate.
2767
+ runJudgeDetailed: typeof defenceMod.runJudgeDetailed === 'function' ? defenceMod.runJudgeDetailed : undefined,
2764
2768
  brokerDecision: defenceMod.brokerDecision,
2765
2769
  timeoutOutcome: defenceMod.timeoutOutcome,
2766
2770
  approvalTimeoutMs: typeof defenceMod.approvalTimeoutMs === 'function' ? defenceMod.approvalTimeoutMs : undefined,
@@ -602,13 +602,29 @@ export function createInterceptor(config, pipeline, options) {
602
602
  timeoutMs: broker.config.judgeTimeoutMs,
603
603
  });
604
604
  let judge = null;
605
+ // Only set when a judge pass actually ran: "no seam on this build" and
606
+ // "budget spent" are not timeouts, and claiming otherwise would be the
607
+ // same overclaim in the audit that this residual removes from doctor.
608
+ let judgeMeta;
605
609
  if (invoke && judgeLimiter.shouldAllow()) {
606
- judge = await broker.runJudge({
610
+ const request = {
607
611
  tool: context.toolName,
608
612
  toolInput: context.arguments,
609
613
  verdict: { severity: v.severity, action: v.action, reason: v.reason, signals: v.signals },
610
614
  sessionSummary: buildSessionSummary(),
611
- }, invoke, { timeoutMs: broker.config.judgeTimeoutMs });
615
+ };
616
+ const opts = { timeoutMs: broker.config.judgeTimeoutMs };
617
+ if (typeof broker.runJudgeDetailed === 'function') {
618
+ const detailed = await broker.runJudgeDetailed(request, invoke, opts);
619
+ judge = detailed?.result ?? null;
620
+ judgeMeta = {
621
+ timedOut: detailed?.timedOut === true,
622
+ error: typeof detailed?.error === 'string' ? detailed.error : null,
623
+ };
624
+ }
625
+ else {
626
+ judge = await broker.runJudge(request, invoke, opts);
627
+ }
612
628
  }
613
629
  else if (invoke) {
614
630
  log.warn(`[shieldcortex] approval broker: judge budget spent this minute — holding ${context.toolName} for the operator`);
@@ -618,6 +634,7 @@ export function createInterceptor(config, pipeline, options) {
618
634
  toolInput: context.arguments,
619
635
  verdict: v,
620
636
  judge,
637
+ ...(judgeMeta ? { judgeMeta } : {}),
621
638
  policy: {
622
639
  allowPreClear: broker.config.allowPreClear,
623
640
  preClearConfidence: broker.config.preClearConfidence,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "shieldcortex-realtime",
3
- "version": "4.51.0",
3
+ "version": "4.52.1",
4
4
  "name": "ShieldCortex Real-time Scanner",
5
5
  "description": "Real-time defence scanning on LLM input, memory extraction on LLM output, and active tool call interception with approval gating.",
6
6
  "kind": null,
package/index.ts CHANGED
@@ -3300,6 +3300,11 @@ function resolveBrokerRuntime(
3300
3300
  return {
3301
3301
  config,
3302
3302
  runJudge: defenceMod.runJudge,
3303
+ // #143 residual, and deliberately NOT in `needed`: an older shieldcortex
3304
+ // build has runJudge alone, and the interceptor falls back to it. Missing
3305
+ // it costs an audit field, never a gate.
3306
+ runJudgeDetailed:
3307
+ typeof defenceMod.runJudgeDetailed === 'function' ? defenceMod.runJudgeDetailed : undefined,
3303
3308
  brokerDecision: defenceMod.brokerDecision,
3304
3309
  timeoutOutcome: defenceMod.timeoutOutcome,
3305
3310
  approvalTimeoutMs: typeof defenceMod.approvalTimeoutMs === 'function' ? defenceMod.approvalTimeoutMs : undefined,
package/interceptor.ts CHANGED
@@ -53,8 +53,15 @@ export interface ToolGuardVerdictLike {
53
53
  action: string;
54
54
  reason: string;
55
55
  signals: string[];
56
- /** Rule → matched-span evidence behind `signals` (issue #192). */
57
- matches?: Array<{ signal: string; span: string }>;
56
+ /** Rule → matched-span evidence behind `signals` (issue #192).
57
+ * #184: optional source/line/chain when the match came from folded script. */
58
+ matches?: Array<{
59
+ signal: string;
60
+ span: string;
61
+ source?: string;
62
+ line?: number;
63
+ chain?: string;
64
+ }>;
58
65
  /** Files the reviewed-script allowlist exempted from folding (#189). */
59
66
  reviewedScripts?: string[];
60
67
  }
@@ -100,6 +107,10 @@ export interface BrokerAuditLike {
100
107
  judgeConfidence: number | null;
101
108
  injectionSuspected: boolean;
102
109
  inContext: boolean | null;
110
+ /** #143 residual — "the judge never answered" told apart from "the judge said
111
+ * hold". Audit only; neither field can reach an outcome. */
112
+ judgeTimedOut?: boolean;
113
+ judgeUnavailableReason?: string | null;
103
114
  reason: string;
104
115
  }
105
116
 
@@ -135,11 +146,26 @@ export interface BrokerRuntime {
135
146
  invoke: ModelInvokerLike,
136
147
  opts?: { timeoutMs?: number },
137
148
  ) => Promise<JudgeResultLike | null>;
149
+ /** #143 residual, and OPTIONAL: this plugin is built across a package
150
+ * boundary, so a main package from before the residual injects `runJudge`
151
+ * alone and the pass below falls back to it. Absent costs an audit field,
152
+ * never a gate. */
153
+ runJudgeDetailed?: (
154
+ req: {
155
+ tool: string;
156
+ toolInput: unknown;
157
+ verdict: { severity: string; action: string; reason: string; signals: string[] };
158
+ sessionSummary?: string;
159
+ },
160
+ invoke: ModelInvokerLike,
161
+ opts?: { timeoutMs?: number },
162
+ ) => Promise<{ result: JudgeResultLike | null; timedOut: boolean; error?: string }>;
138
163
  brokerDecision: (input: {
139
164
  tool: string;
140
165
  toolInput: unknown;
141
166
  verdict: ToolGuardVerdictLike;
142
167
  judge: JudgeResultLike | null;
168
+ judgeMeta?: { timedOut?: boolean; error?: string | null };
143
169
  policy?: { allowPreClear: boolean; preClearConfidence: number };
144
170
  }) => BrokerDecisionLike;
145
171
  timeoutOutcome: (decision: BrokerDecisionLike) => 'approve' | 'deny';
@@ -934,17 +960,28 @@ export function createInterceptor(
934
960
  });
935
961
 
936
962
  let judge: JudgeResultLike | null = null;
963
+ // Only set when a judge pass actually ran: "no seam on this build" and
964
+ // "budget spent" are not timeouts, and claiming otherwise would be the
965
+ // same overclaim in the audit that this residual removes from doctor.
966
+ let judgeMeta: { timedOut?: boolean; error?: string | null } | undefined;
937
967
  if (invoke && judgeLimiter.shouldAllow()) {
938
- judge = await broker.runJudge(
939
- {
940
- tool: context.toolName,
941
- toolInput: context.arguments,
942
- verdict: { severity: v.severity, action: v.action, reason: v.reason, signals: v.signals },
943
- sessionSummary: buildSessionSummary(),
944
- },
945
- invoke,
946
- { timeoutMs: broker.config.judgeTimeoutMs },
947
- );
968
+ const request = {
969
+ tool: context.toolName,
970
+ toolInput: context.arguments,
971
+ verdict: { severity: v.severity, action: v.action, reason: v.reason, signals: v.signals },
972
+ sessionSummary: buildSessionSummary(),
973
+ };
974
+ const opts = { timeoutMs: broker.config.judgeTimeoutMs };
975
+ if (typeof broker.runJudgeDetailed === 'function') {
976
+ const detailed = await broker.runJudgeDetailed(request, invoke, opts);
977
+ judge = detailed?.result ?? null;
978
+ judgeMeta = {
979
+ timedOut: detailed?.timedOut === true,
980
+ error: typeof detailed?.error === 'string' ? detailed.error : null,
981
+ };
982
+ } else {
983
+ judge = await broker.runJudge(request, invoke, opts);
984
+ }
948
985
  } else if (invoke) {
949
986
  log.warn(`[shieldcortex] approval broker: judge budget spent this minute — holding ${context.toolName} for the operator`);
950
987
  }
@@ -954,6 +991,7 @@ export function createInterceptor(
954
991
  toolInput: context.arguments,
955
992
  verdict: v,
956
993
  judge,
994
+ ...(judgeMeta ? { judgeMeta } : {}),
957
995
  policy: {
958
996
  allowPreClear: broker.config.allowPreClear,
959
997
  preClearConfidence: broker.config.preClearConfidence,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "shieldcortex-realtime",
3
- "version": "4.51.0",
3
+ "version": "4.52.1",
4
4
  "name": "ShieldCortex Real-time Scanner",
5
5
  "description": "Real-time defence scanning on LLM input, memory extraction on LLM output, and active tool call interception with approval gating.",
6
6
  "kind": null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakon-systems/shieldcortex-realtime",
3
- "version": "4.51.0",
3
+ "version": "4.52.1",
4
4
  "description": "OpenClaw plugin for ShieldCortex real-time defence scanning and optional memory extraction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",