@cratylus/canon 0.12.1 → 0.12.2
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.
|
@@ -122,9 +122,13 @@ export const stanceGuardrail = {
|
|
|
122
122
|
# answers unparseably, a block it could not verify, an unexpected error. Silence is reserved for
|
|
123
123
|
# "judged, no collapse" and for "not enrolled" (no persona manifest in scope, or nothing
|
|
124
124
|
# judgeable in the turn): there, not-checking is the correct answer, not a failure to report.
|
|
125
|
-
# - A REFUSAL HOLDS
|
|
126
|
-
#
|
|
127
|
-
#
|
|
125
|
+
# - A REFUSAL HOLDS, EXCEPT AT THE TURN END, AND THERE ONLY FOR ONE STOP. Judges every turn, one
|
|
126
|
+
# identical to a refused turn included, and a BLOCK blocks the stop. The turn end is the one
|
|
127
|
+
# exception to a refused act staying refused: refusing it holds back no effect, it only makes
|
|
128
|
+
# the agent redo its turn. So a stop that follows a block of this guard (\`stop_hook_active\`)
|
|
129
|
+
# continues a RUN of blocks, and a BLOCK that would be the repeat of the turn last blocked in
|
|
130
|
+
# the run, or the one after the run's third block, is let through, SAID, UNRESOLVED. The bound
|
|
131
|
+
# is on one stop and never on the session: a stop that follows no block starts a new run at 0.
|
|
128
132
|
# - POSITION-SOUND. Every rubric rule that can fire is a claim about the turn's CLOSE. So the
|
|
129
133
|
# L1 window and the EVIDENCE check both run against \`asst_close\` — the text AFTER the last
|
|
130
134
|
# tool call — never the whole-turn blob. A span from a mid-turn preamble is out of scope for
|
|
@@ -134,7 +138,8 @@ export const stanceGuardrail = {
|
|
|
134
138
|
# agent_type, session_id, cwd, …).
|
|
135
139
|
# OUTPUT : on collapse → stdout \`{"decision":"block","reason":"…"}\` + exit 0 (Stop-hook block).
|
|
136
140
|
# judged, no collapse → no stdout + exit 0 (allow stop).
|
|
137
|
-
# let through with no verdict → the notice \`say\`
|
|
141
|
+
# let through with no verdict, or a refused stop let through UNRESOLVED → the notice \`say\`
|
|
142
|
+
# prints + exit 0 (allow stop).
|
|
138
143
|
#
|
|
139
144
|
# POSIX sh. Depends on: jq (transcript parse). Missing jq → the guard says so and allows the stop.
|
|
140
145
|
|
|
@@ -241,32 +246,52 @@ NEUTRAL_ROOT="$(dirname -- "$(dirname -- "$(dirname -- "$SELF_DIR")")")/.agents"
|
|
|
241
246
|
# different contract without the dispatcher learning anything about either.
|
|
242
247
|
RUBRIC="\${STANCE_RUBRIC:-$NEUTRAL_ROOT/stance-guardrail/stance-judge-prompt.md}"
|
|
243
248
|
|
|
244
|
-
# --- loop safety: a refusal holds
|
|
245
|
-
# EVERY TURN IS JUDGED,
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
249
|
+
# --- loop safety: a refusal holds, but the turn end is bounded --------------------------------
|
|
250
|
+
# EVERY TURN IS JUDGED, and a BLOCK blocks the stop. What follows a BLOCK is the only thing bounded.
|
|
251
|
+
# The turn end is the one exception to a refused act staying refused: refusing it holds back no
|
|
252
|
+
# effect, it only makes the agent redo its turn, so a stop the judge keeps blocking would lock the
|
|
253
|
+
# session in an endless loop if every one of them blocked. The bound is on ONE STOP, never on the
|
|
254
|
+
# session — it is not a budget spent over the session's life and not a switch that stays thrown:
|
|
255
|
+
#
|
|
256
|
+
# - A stop that follows a block of this guard (\`stop_hook_active\` true) CONTINUES the run of
|
|
257
|
+
# blocks. A stop with \`stop_hook_active\` false or absent follows no block and STARTS A NEW
|
|
258
|
+
# RUN AT 0, whatever came before, so a let-through never leaves enforcement off.
|
|
259
|
+
# - After the judge gives a BLOCK that survives the evidence checks, the stop is let through
|
|
260
|
+
# instead of blocked — and SAID, naming the finding as standing and unresolved — when either
|
|
261
|
+
# (a) the run already holds a block and this turn's text is byte-identical to the turn last
|
|
262
|
+
# blocked in it (a repeated stop: blocking it again cannot help), or (b) the run already holds
|
|
263
|
+
# RUN_CAP blocks (a several-times-refused stop). Otherwise it blocks and records the run and the
|
|
264
|
+
# blocked turn's hash.
|
|
265
|
+
# - A stop that follows a block but whose run cannot be read or written (a run record that is
|
|
266
|
+
# unreadable, or a state dir that is unwritable) is let through on a BLOCK, and said: a bound
|
|
267
|
+
# that cannot be counted cannot hold. A fresh stop there still blocks. No record at all, in a
|
|
268
|
+
# dir that can be written, is a run of 0: nothing was blocked, so the stop blocks and starts one.
|
|
269
|
+
#
|
|
270
|
+
# RUN_CAP is a constant of this cell; no environment variable sets it. A judged PASS stays silent
|
|
271
|
+
# and the DARK and BLOCK DISCARDED paths are untouched. Nor is the old \`stop_hook_active=true →
|
|
272
|
+
# allow_stop\` short-circuit back: that let the agent be blocked, emit anything, and end again
|
|
273
|
+
# UNJUDGED, which made the rubric's judgement of a response to a verdict dead code. The re-fired
|
|
274
|
+
# stop is judged like any other; only the BLOCK that follows the bound is waved through, and said.
|
|
275
|
+
#
|
|
276
|
+
# The state in the tmp dir, keyed by session id, is the run: the blocks it holds and the hash of
|
|
277
|
+
# the turn last blocked in it.
|
|
278
|
+
RUN_CAP=3
|
|
262
279
|
session="$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null || echo nosession)"
|
|
263
280
|
state_dir="\${TMPDIR:-/tmp}/stance-guardrail"
|
|
264
281
|
mkdir -p "$state_dir" 2>/dev/null || true
|
|
265
|
-
|
|
282
|
+
run_file="$state_dir/$session.run"
|
|
266
283
|
verdict_log="$state_dir/$session.verdicts"
|
|
267
284
|
|
|
268
|
-
|
|
269
|
-
|
|
285
|
+
stop_active="$(printf '%s' "$input" | jq -r 'if .stop_hook_active == true then "yes" else "" end' 2>/dev/null || true)"
|
|
286
|
+
run=0
|
|
287
|
+
run_hash=none
|
|
288
|
+
run_known=1
|
|
289
|
+
if [ -n "$stop_active" ] && [ -e "$run_file" ]; then
|
|
290
|
+
run_state="$(cat "$run_file" 2>/dev/null || true)"
|
|
291
|
+
run="\${run_state%% *}"
|
|
292
|
+
run_hash="\${run_state#* }"
|
|
293
|
+
case "$run" in '' | *[!0-9]*) run=0; run_hash=none; run_known=0 ;; esac
|
|
294
|
+
fi
|
|
270
295
|
|
|
271
296
|
# The per-repo opt-in is GONE, and its absence is the point. It answered "may a guard run in this
|
|
272
297
|
# DIRECTORY", which is a category error: a stance is a property of the agent, not of the checkout
|
|
@@ -742,19 +767,37 @@ printf '%s\\t%s\\t%s\\n' "$decision" "$evidence" "$reason" >> "$verdict_log" 2>/
|
|
|
742
767
|
fi
|
|
743
768
|
fi
|
|
744
769
|
|
|
745
|
-
# --- A REFUSAL HOLDS
|
|
746
|
-
# A block blocks the stop
|
|
747
|
-
#
|
|
748
|
-
#
|
|
749
|
-
#
|
|
750
|
-
#
|
|
751
|
-
#
|
|
752
|
-
#
|
|
753
|
-
#
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
770
|
+
# --- A REFUSAL HOLDS, EXCEPT AT THE TURN END, AND THERE ONLY FOR ONE STOP ---------------------
|
|
771
|
+
# A block blocks the stop. The turn end is the one exception to a refused act staying refused: a
|
|
772
|
+
# refused stop holds back no effect, it only makes the agent redo its turn, so the bound below lets
|
|
773
|
+
# a repeated or several-times-refused stop through rather than lock the session in an endless loop.
|
|
774
|
+
# It lets through ONE STOP — the next stop that follows no block starts a new run at 0 and is
|
|
775
|
+
# judged and blocked like any other — and it SAYS SO: the finding stands and the stop is
|
|
776
|
+
# UNRESOLVED. Two caps once let a refused turn through silently, and a counter that never reset
|
|
777
|
+
# turned a one-turn escape into a session-wide disable; neither is restored. What ends a run of
|
|
778
|
+
# blocks is a turn the judge passes, or this bound, said aloud.
|
|
779
|
+
turn_hash="$(printf '%s' "$asst_text" | cksum)"
|
|
780
|
+
run_next=$((run + 1))
|
|
781
|
+
let_through=""
|
|
782
|
+
if [ -n "$stop_active" ]; then
|
|
783
|
+
if [ "$run_known" -eq 0 ]; then
|
|
784
|
+
let_through="the run of refused stops it follows cannot be read, and a bound that cannot be counted cannot hold"
|
|
785
|
+
elif [ "$run" -ge 1 ] && [ "$turn_hash" = "$run_hash" ]; then
|
|
786
|
+
let_through="this stop is byte-identical to the turn it already refused, so refusing it again cannot help"
|
|
787
|
+
elif [ "$run" -ge "$RUN_CAP" ]; then
|
|
788
|
+
let_through="it has refused this run of stops $run times"
|
|
789
|
+
fi
|
|
790
|
+
fi
|
|
791
|
+
if [ -z "$let_through" ] && ! printf '%s %s' "$run_next" "$turn_hash" > "$run_file" 2>/dev/null; then
|
|
792
|
+
[ -z "$stop_active" ] || let_through="the run of refused stops cannot be written, and a bound that cannot be counted cannot hold"
|
|
793
|
+
fi
|
|
794
|
+
if [ -n "$let_through" ]; then
|
|
795
|
+
rm -f "$run_file" 2>/dev/null || true
|
|
796
|
+
printf 'UNRESOLVED\\t%s\\t%s\\n' "$evidence" "$reason" >> "$verdict_log" 2>/dev/null || true
|
|
797
|
+
say "STANCE GUARDRAIL — stop let through UNRESOLVED: the judge blocked this stop, and the finding STANDS and is unaddressed — $let_through. The next stop that follows no block is judged and blocked again. The judge's reason was: $reason"
|
|
798
|
+
allow_stop
|
|
799
|
+
fi
|
|
800
|
+
reason="$reason (stance-guardrail block $run_next in this run of refused stops.)"
|
|
758
801
|
|
|
759
802
|
# --- BLOCK ----------------------------------------------------------------------------------
|
|
760
803
|
# Emit the Stop-hook block decision. The \`reason\` is fed back to the agent as a corrective
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stance-guardrail.js","sourceRoot":"","sources":["../../src/hooks/stance-guardrail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAG5D,wEAAwE;AACxE,8EAA8E;AAC9E,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,gFAAgF;AAEhF,wFAAwF;AACxF,0FAA0F;AAC1F,yFAAyF;AACzF,4FAA4F;AAC5F,8FAA8F;AAC9F,6FAA6F;AAC7F,uFAAuF;AACvF,mEAAmE;AACnE,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC,qFAAqF;AACrF,6FAA6F;AAC7F,wFAAwF;AACxF,sFAAsF;AACtF,wFAAwF;AACxF,0FAA0F;AAC1F,sCAAsC;AACtC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;oBAKb,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCvC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAa;IACvC,EAAE,EAAE,kBAAkB;IACtB,OAAO,EACL,8UAA8U;IAChV,SAAS,EAAE,SAAS;IACpB,mFAAmF;IACnF,+DAA+D;IAC/D,KAAK,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;IAC1D,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC,UAAU,CAAC;IACpB,KAAK,EAAE,qBAAqB;IAC5B,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,EAAE;IACR,OAAO,EAAE;QACP;YACE,QAAQ,EAAE,qBAAqB;YAC/B,UAAU,EAAE,sDAAsD;YAClE,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"stance-guardrail.js","sourceRoot":"","sources":["../../src/hooks/stance-guardrail.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratylus/canon",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "The canonical corpus of discovered optimal-signifiers — agents, skills, dimensions and rules as typed TS source. Markdown is a projection of these modules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|