@cratylus/canon 0.1.3 → 0.3.0

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.
Files changed (61) hide show
  1. package/dist/agents/architect.d.ts +2 -0
  2. package/dist/agents/architect.js +113 -0
  3. package/dist/agents/architect.js.map +1 -0
  4. package/dist/agents/implementer.d.ts +2 -0
  5. package/dist/agents/implementer.js +74 -0
  6. package/dist/agents/implementer.js.map +1 -0
  7. package/dist/agents/kino.d.ts +2 -0
  8. package/dist/agents/kino.js +120 -0
  9. package/dist/agents/kino.js.map +1 -0
  10. package/dist/agents/mav.js +15 -4
  11. package/dist/agents/mav.js.map +1 -1
  12. package/dist/agents/nico.js +2 -2
  13. package/dist/agents/nico.js.map +1 -1
  14. package/dist/agents/planner.d.ts +2 -0
  15. package/dist/agents/planner.js +85 -0
  16. package/dist/agents/planner.js.map +1 -0
  17. package/dist/dimensions/autonomy/{check-in.d.ts → handoff.d.ts} +1 -1
  18. package/dist/dimensions/autonomy/{check-in.js → handoff.js} +16 -4
  19. package/dist/dimensions/autonomy/handoff.js.map +1 -0
  20. package/dist/dimensions/autonomy/human-on-the-loop.js +1 -1
  21. package/dist/dimensions/autonomy/human-on-the-loop.js.map +1 -1
  22. package/dist/dimensions/capabilities/film-production.d.ts +2 -0
  23. package/dist/dimensions/capabilities/film-production.js +8 -0
  24. package/dist/dimensions/capabilities/film-production.js.map +1 -0
  25. package/dist/dimensions/capabilities/generative-video.d.ts +2 -0
  26. package/dist/dimensions/capabilities/generative-video.js +8 -0
  27. package/dist/dimensions/capabilities/generative-video.js.map +1 -0
  28. package/dist/dimensions/formality/plain.js +1 -1
  29. package/dist/dimensions/formality/plain.js.map +1 -1
  30. package/dist/hook-cells.js +5 -2
  31. package/dist/hook-cells.js.map +1 -1
  32. package/dist/hooks/deploy-drift-notice.js +1 -1
  33. package/dist/hooks/deploy-drift-notice.js.map +1 -1
  34. package/dist/hooks/stance-guardrail-pre.js +56 -30
  35. package/dist/hooks/stance-guardrail-pre.js.map +1 -1
  36. package/dist/hooks/stance-guardrail.js +253 -38
  37. package/dist/hooks/stance-guardrail.js.map +1 -1
  38. package/dist/plan-states.d.ts +3 -3
  39. package/dist/plan-states.js +12 -8
  40. package/dist/plan-states.js.map +1 -1
  41. package/dist/skills/carry-on/skill.js +2 -2
  42. package/dist/skills/carry-on/skill.js.map +1 -1
  43. package/dist/skills/deliver/skill.d.ts +2 -0
  44. package/dist/skills/deliver/skill.js +98 -0
  45. package/dist/skills/deliver/skill.js.map +1 -0
  46. package/dist/skills/{praxis → design}/skill.d.ts +1 -1
  47. package/dist/skills/design/skill.js +79 -0
  48. package/dist/skills/design/skill.js.map +1 -0
  49. package/dist/skills/plan/skill.d.ts +2 -0
  50. package/dist/skills/plan/skill.js +89 -0
  51. package/dist/skills/plan/skill.js.map +1 -0
  52. package/package.json +4 -7
  53. package/dist/dimensions/autonomy/check-in.js.map +0 -1
  54. package/dist/dimensions/output-format/structured-decision.d.ts +0 -2
  55. package/dist/dimensions/output-format/structured-decision.js +0 -2
  56. package/dist/dimensions/output-format/structured-decision.js.map +0 -1
  57. package/dist/hooks/praxis-continuity.d.ts +0 -2
  58. package/dist/hooks/praxis-continuity.js +0 -132
  59. package/dist/hooks/praxis-continuity.js.map +0 -1
  60. package/dist/skills/praxis/skill.js +0 -193
  61. package/dist/skills/praxis/skill.js.map +0 -1
@@ -0,0 +1,8 @@
1
+ // The craft the software SERVES, not the software. Its decomposition is the
2
+ // production ladder every studio already runs, and the two laws a generative
3
+ // pipeline most often collapses: IDENTITY (is this the same character) and
4
+ // CONTINUITY (does the staging flow) are different problems with different
5
+ // instruments, and an art-bible entry is approved ONCE and applied everywhere
6
+ // rather than re-specified per shot.
7
+ export const filmProduction = `film-production ≜ ⟨development breakdown art-bible shot-design continuity cut⟩ ⟨identity ⊥ continuity⟩ ⟨art-bible ≜ approve-once · apply-everywhere⟩ ⟨industry-standard ≻ invention⟩`;
8
+ //# sourceMappingURL=film-production.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"film-production.js","sourceRoot":"","sources":["../../../src/dimensions/capabilities/film-production.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,qCAAqC;AACrC,MAAM,CAAC,MAAM,cAAc,GAAiB,sLAAsL,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Capabilities } from '../../manifest.js';
2
+ export declare const generativeVideo: Capabilities;
@@ -0,0 +1,8 @@
1
+ // The generative half of the craft — what the community, not the studio, has
2
+ // standardized. The load-bearing clause is the third: reference conditioning is
3
+ // STATELESS (every request re-presents every reference; the model remembers
4
+ // nothing between shots), so whatever carries identity across a production is
5
+ // the ledger, never the model. Identity-lock is ranked: a trained adapter beats
6
+ // reference conditioning beats prompt-only description.
7
+ export const generativeVideo = `generative-video ≜ ⟨model-selection conditioning identity-lock drift-control⟩ ⟨conditioning stateless ⇒ ledger = the-memory⟩ ⟨identity-lock ≜ adapter ≻ reference ≻ prompt⟩ ⟨community-recommendation ≻ invention⟩`;
8
+ //# sourceMappingURL=generative-video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generative-video.js","sourceRoot":"","sources":["../../../src/dimensions/capabilities/generative-video.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,gFAAgF;AAChF,4EAA4E;AAC5E,8EAA8E;AAC9E,gFAAgF;AAChF,wDAAwD;AACxD,MAAM,CAAC,MAAM,eAAe,GAAiB,oNAAoN,CAAC"}
@@ -10,7 +10,7 @@
10
10
  // for shorter replies buys prose with behaviour.
11
11
  //
12
12
  // REGISTER ONLY. The closing tail a report owes its operator is `autonomy`'s —
13
- // `check-in` names it, because what a reply must CONTAIN at the end is report
13
+ // `handoff` names it, because what a reply must CONTAIN at the end is report
14
14
  // structure, not a register. A value that governed both would be the one home
15
15
  // for two concepts, and would apply the tail to whichever formality happened to
16
16
  // be bound.
@@ -1 +1 @@
1
- {"version":3,"file":"plain.js","sourceRoot":"","sources":["../../../src/dimensions/formality/plain.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,4EAA4E;AAC5E,gEAAgE;AAChE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,gFAAgF;AAChF,YAAY;AACZ,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,gFAAgF;AAChF,yEAAyE;AACzE,EAAE;AACF,iFAAiF;AACjF,4EAA4E;AAC5E,iFAAiF;AACjF,+EAA+E;AAC/E,8EAA8E;AAC9E,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,gFAAgF;AAChF,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,iFAAiF;AACjF,MAAM,CAAC,MAAM,KAAK,GAAc,yYAAyY,CAAC"}
1
+ {"version":3,"file":"plain.js","sourceRoot":"","sources":["../../../src/dimensions/formality/plain.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,4EAA4E;AAC5E,gEAAgE;AAChE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,8EAA8E;AAC9E,gFAAgF;AAChF,YAAY;AACZ,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,gFAAgF;AAChF,yEAAyE;AACzE,EAAE;AACF,iFAAiF;AACjF,4EAA4E;AAC5E,iFAAiF;AACjF,+EAA+E;AAC/E,8EAA8E;AAC9E,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,gFAAgF;AAChF,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,iFAAiF;AACjF,MAAM,CAAC,MAAM,KAAK,GAAc,yYAAyY,CAAC"}
@@ -6,8 +6,11 @@
6
6
  // (claude adapter: `turn.end` → Stop, `subagent.end` → SubagentStop) and carries
7
7
  // their VERBATIM worker payloads so the projector stages them under `hooks/<id>/`
8
8
  // (no file copy — the bytes come from the cell). Only `harness`-substrate cells
9
- // register in settings.json; a `git`-substrate cell (praxis-continuity) fires in
10
- // git's process and is byte-locked but not serialized here.
9
+ // register in settings.json, and every surviving cell is one: the corpus's single
10
+ // `git`-substrate cell, `praxis-continuity`, retired with the praxis plan mirror it
11
+ // nudged about. The routing for a non-harness substrate is not hypothetical — it is
12
+ // exercised the moment such a cell exists again — but nothing carries it today, and
13
+ // a header claiming a live example is a claim a reader cannot check.
11
14
  //
12
15
  // The cell shapes are the schema's type kernel (`@cratylus/schema`); this
13
16
  // module is the composition root that NAMES the concrete cells and nothing more.
@@ -1 +1 @@
1
- {"version":3,"file":"hook-cells.js","sourceRoot":"","sources":["../src/hook-cells.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,kFAAkF;AAClF,mFAAmF;AACnF,4EAA4E;AAC5E,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,iFAAiF;AACjF,4DAA4D;AAC5D,EAAE;AACF,0EAA0E;AAC1E,iFAAiF;AACjF,oFAAoF;AACpF,iFAAiF;AACjF,+EAA+E;AAE/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAwB;IACnD,eAAe;IACf,kBAAkB;IAClB,iBAAiB;CAClB,CAAC"}
1
+ {"version":3,"file":"hook-cells.js","sourceRoot":"","sources":["../src/hook-cells.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,kFAAkF;AAClF,mFAAmF;AACnF,4EAA4E;AAC5E,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,kFAAkF;AAClF,oFAAoF;AACpF,oFAAoF;AACpF,oFAAoF;AACpF,qEAAqE;AACrE,EAAE;AACF,0EAA0E;AAC1E,iFAAiF;AACjF,oFAAoF;AACpF,iFAAiF;AACjF,+EAA+E;AAE/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAwB;IACnD,eAAe;IACf,kBAAkB;IAClB,iBAAiB;CAClB,CAAC"}
@@ -33,7 +33,7 @@
33
33
  // copy with nothing further to say. That is not hypothetical and it is not rare —
34
34
  // it is the ORDINARY shape of work here, where a session edits a cell, projects it,
35
35
  // and keeps going under the version it just replaced. The 16-hour-old projection of
36
- // an agent's own governing cell was this; so was a `praxis` edit that landed under a
36
+ // an agent's own governing cell was this; so was a `plan` edit that landed under a
37
37
  // running session hours later.
38
38
  //
39
39
  // `prompt.submit` IS THE RE-ARM, and it is the same question at a finer grain rather
@@ -1 +1 @@
1
- {"version":3,"file":"deploy-drift-notice.js","sourceRoot":"","sources":["../../src/hooks/deploy-drift-notice.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deploy-drift-notice.js","sourceRoot":"","sources":["../../src/hooks/deploy-drift-notice.ts"],"names":[],"mappings":"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"}
@@ -46,9 +46,9 @@ export const stanceGuardrailPre = {
46
46
  # ambiguity, a substantive intent-extracted dispatch.
47
47
  #
48
48
  # SAFETY MODEL (mirrors stance-guardrail):
49
- # - OFF BY DEFAULT (git config agentfactory.stanceGuard true).
50
- # - AGENT-SCOPED (agent_type in the allowlist; default: nico mav). agent_type is
51
- # present only for subagents / --agent mode; a top-level session call fails open.
49
+ # - SCOPE-ENROLLED, exactly as its twin: a scope carrying a stance manifest is judged and
50
+ # every other scope is silent. No repo opt-in, no allowlist — the projection that places a
51
+ # persona also enrolls it, and declining the guard means declining the persona.
52
52
  # - FAILS OPEN. Any error (no jq, judge failure, bad input) -> exit 0 (allow the call).
53
53
  # - LOOP-SAFE. A re-entry cap: never deny an identical tool_input twice (there is no
54
54
  # stop_hook_active analog for PreToolUse), so it can never wedge a call.
@@ -65,11 +65,27 @@ export const stanceGuardrailPre = {
65
65
 
66
66
  set -eu
67
67
 
68
- # The sibling Stop hook's deployed dir owns the SHARED judge + rubric (deployed together).
69
- JUDGE_DIR="\${STANCE_GUARD_DIR:-$HOME/.claude/hooks/stance-guardrail}"
70
- RUBRIC="\${STANCE_RUBRIC:-$JUDGE_DIR/stance-judge-prompt.md}"
68
+ # The sibling Stop hook's deployed dir owns the SHARED judge + rubric (deployed
69
+ # together, as siblings under the SAME hooks root).
70
+ #
71
+ # RESOLVED FROM THIS SCRIPT'S OWN LOCATION, never from a harness's home. These four
72
+ # lines named \`$HOME/.claude/...\` outright, so the copy deployed under
73
+ # \`~/.omp/hooks/\` reached across into the CLAUDE tree for its judge — and on a host
74
+ # with no claude deployment it found nothing, failed open, and logged its misses to
75
+ # a directory that does not exist. Every hooks root holds both hook dirs as
76
+ # siblings, so \`../stance-guardrail\` is true at every site this file can land; the
77
+ # sibling Stop worker already derives its own dir this way and this is the same
78
+ # derivation, one level up. The env overrides are unchanged and still win.
79
+ SELF_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
80
+ HOOKS_ROOT="$(dirname -- "$SELF_DIR")"
81
+ JUDGE_DIR="\${STANCE_GUARD_DIR:-$HOOKS_ROOT/stance-guardrail}"
82
+ # The RUBRIC is not in either hook dir: it is harness-invariant and deploys once
83
+ # to the neutral \`.agents\` root, the sibling of every harness home. Same
84
+ # derivation as the sibling Stop worker, one level further out.
85
+ NEUTRAL_ROOT="$(dirname -- "$(dirname -- "$HOOKS_ROOT")")/.agents"
86
+ RUBRIC="\${STANCE_RUBRIC:-$NEUTRAL_ROOT/stance-guardrail/stance-judge-prompt.md}"
71
87
  JUDGE_CMD="\${STANCE_JUDGE_CMD:-sh $JUDGE_DIR/stance-judge.sh}"
72
- LOG="\${STANCE_GUARD_LOG:-$HOME/.claude/hooks/stance-guardrail-pre/misses.log}"
88
+ LOG="\${STANCE_GUARD_LOG:-$SELF_DIR/misses.log}"
73
89
 
74
90
  # A PreToolUse hook must never break a session. Trap any unexpected error -> allow.
75
91
  trap 'exit 0' EXIT
@@ -82,27 +98,22 @@ input="$(cat)"
82
98
  [ -n "$input" ] || allow
83
99
  command -v jq >/dev/null 2>&1 || allow # no jq -> cannot parse -> fail open
84
100
 
85
- # --- opt-in gate (off by default) -----------------------------------------------------------
86
- # Per-repo, lives in .git/config, never checked in. Resolve relative to the hook's cwd.
101
+ # The per-repo opt-in is GONE, for the reason its twin states: it asked whether a guard may run in
102
+ # a DIRECTORY, and a stance belongs to the agent, not the checkout. \`cwd\` survives it — the worker
103
+ # still runs where the session runs.
87
104
  cwd="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null || true)"
88
105
  [ -n "$cwd" ] && cd "$cwd" 2>/dev/null || true
89
- enabled="$(git config --bool agentfactory.stanceGuard 2>/dev/null || echo false)"
90
- [ "$enabled" = "true" ] || allow
91
-
92
- # --- agent-scope gate -----------------------------------------------------------------------
93
- # Only enforce for the configured agents (default: the principal-self agents).
94
- # agent_type is present only for a subagent / --agent tool call; absent for the top-level
95
- # session -> fail open (unless STANCE_GUARD_AGENTS=*), exactly like the Stop guard.
96
- agent_type="$(printf '%s' "$input" | jq -r '.agent_type // empty' 2>/dev/null || true)"
97
- allowlist="\${STANCE_GUARD_AGENTS:-$(git config agentfactory.stanceGuardAgents 2>/dev/null || echo 'nico mav')}"
98
- if [ "$allowlist" != "*" ]; then
99
- [ -n "$agent_type" ] || allow # cannot identify the agent -> fail open
100
- in_scope=false
101
- for a in $allowlist; do
102
- [ "$a" = "$agent_type" ] && in_scope=true && break
103
- done
104
- [ "$in_scope" = true ] || allow
105
- fi
106
+
107
+ # --- scope gate: the persona's OWN stance manifest -------------------------------------------
108
+ # The twin of the turn-end guard's gate, and inverted for the same reason: an allowlist here was
109
+ # a runtime self-filter over an enrollment the corpus already derives, and it had drifted the way
110
+ # such a list always drifts. Presence of a manifest in this persona's own scope IS enrollment.
111
+ # Absent → silence, never an error, so an unenrolled launch is untouched.
112
+ stance_scope="$(printf '%s' "$input" | jq -r '.stance_scope // empty' 2>/dev/null || true)"
113
+ [ -n "$stance_scope" ] || allow
114
+ manifest="$stance_scope/stance/manifest.json"
115
+ [ -f "$manifest" ] || allow
116
+ agent_type="$(jq -r '.agent // empty' "$manifest" 2>/dev/null || true)"
106
117
 
107
118
  # --- extract the judged payload, branched by tool -------------------------------------------
108
119
  tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null || true)"
@@ -139,10 +150,25 @@ seen="\${TMPDIR:-/tmp}/.stance-pre-$session_id-$sig"
139
150
  [ -f "$seen" ] && allow # already denied this exact input once -> let it through
140
151
 
141
152
  # --- judge (SHARED backend + rubric) --------------------------------------------------------
142
- verdict="$(printf '%s' "$payload" | $JUDGE_CMD "$RUBRIC" 2>/dev/null)" || {
143
- note "$(date -u +%Y-%m-%dT%H:%M:%SZ) judge-fail tool=$tool_name agent=\${agent_type:-?}"
144
- allow
145
- }
153
+ # The same out-of-process seam the Stop worker carries, and for the same reason: a
154
+ # host that already holds a model must not be made to spawn another vendor's CLI.
155
+ # The deny-once marker is written AFTER this point, so the emit pass mutates nothing.
156
+ if [ -n "\${STANCE_EMIT_PAYLOAD:-}" ]; then
157
+ jq -cn --arg r "$RUBRIC" --arg p "$payload" '{rubric:$r, payload:$p}'
158
+ exit 0
159
+ fi
160
+ if [ -n "\${STANCE_VERDICT_FILE:-}" ]; then
161
+ verdict="$(cat "\${STANCE_VERDICT_FILE}" 2>/dev/null || true)"
162
+ [ -n "$verdict" ] || {
163
+ note "$(date -u +%Y-%m-%dT%H:%M:%SZ) judge-empty tool=$tool_name agent=\${agent_type:-?}"
164
+ allow
165
+ }
166
+ else
167
+ verdict="$(printf '%s' "$payload" | $JUDGE_CMD "$RUBRIC" 2>/dev/null)" || {
168
+ note "$(date -u +%Y-%m-%dT%H:%M:%SZ) judge-fail tool=$tool_name agent=\${agent_type:-?}"
169
+ allow
170
+ }
171
+ fi
146
172
 
147
173
  decision="$(printf '%s\\n' "$verdict" | sed -n 's/^VERDICT:[[:space:]]*//p' | head -1)"
148
174
  [ "$decision" = "BLOCK" ] || allow # PASS, empty, or anything but BLOCK -> allow
@@ -1 +1 @@
1
- {"version":3,"file":"stance-guardrail-pre.js","sourceRoot":"","sources":["../../src/hooks/stance-guardrail-pre.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,mFAAmF;AACnF,uFAAuF;AACvF,SAAS;AACT,wFAAwF;AACxF,6FAA6F;AAC7F,0FAA0F;AAC1F,uFAAuF;AACvF,qEAAqE;AACrE,EAAE;AACF,wEAAwE;AACxE,sFAAsF;AACtF,iFAAiF;AACjF,mFAAmF;AACnF,sFAAsF;AACtF,qFAAqF;AACrF,sFAAsF;AACtF,sFAAsF;AACtF,mEAAmE;AACnE,2DAA2D;AAC3D,mDAAmD;AAEnD,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,EAAE,EAAE,sBAAsB;IAC1B,OAAO,EACL,iWAAiW;IACnW,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;IACzD,KAAK,EAAE,yBAAyB;IAChC,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,EAAE;IACR,OAAO,EAAE;QACP;YACE,QAAQ,EAAE,yBAAyB;YACnC,UAAU,EAAE,0DAA0D;YACtE,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkId;SACI;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"stance-guardrail-pre.js","sourceRoot":"","sources":["../../src/hooks/stance-guardrail-pre.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,mFAAmF;AACnF,uFAAuF;AACvF,SAAS;AACT,wFAAwF;AACxF,6FAA6F;AAC7F,0FAA0F;AAC1F,uFAAuF;AACvF,qEAAqE;AACrE,EAAE;AACF,wEAAwE;AACxE,sFAAsF;AACtF,iFAAiF;AACjF,mFAAmF;AACnF,sFAAsF;AACtF,qFAAqF;AACrF,sFAAsF;AACtF,sFAAsF;AACtF,mEAAmE;AACnE,2DAA2D;AAC3D,mDAAmD;AAEnD,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,EAAE,EAAE,sBAAsB;IAC1B,OAAO,EACL,iWAAiW;IACnW,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;IACzD,KAAK,EAAE,yBAAyB;IAChC,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,EAAE;IACR,OAAO,EAAE;QACP;YACE,QAAQ,EAAE,yBAAyB;YACnC,UAAU,EAAE,0DAA0D;YACtE,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4Jd;SACI;KACF;CACF,CAAC"}
@@ -1,4 +1,4 @@
1
- import { checkIn } from '../dimensions/autonomy/check-in.js';
1
+ import { handoff } from '../dimensions/autonomy/handoff.js';
2
2
  // stance-guardrail — the harness-half of the principal stance. A source
3
3
  // `hook` cell (activation=event): its canonical DEFINIENS is the σ*-signified
4
4
  // identity that `accept()` gates; its `workers[].content` are the VERBATIM
@@ -49,8 +49,13 @@ export const stanceGuardrail = {
49
49
  # electing the session's objective is supplying intent, not sequencing — the operator's to own
50
50
  #
51
51
  # SAFETY MODEL:
52
- # - OFF BY DEFAULT. Does nothing unless the repo opts in (git config agentfactory.stanceGuard true).
53
- # - AGENT-SCOPED. Only fires for agents on the allowlist (default: nico, mav — the principal-self agents).
52
+ # - SCOPE-ENROLLED. Fires for a scope carrying a stance manifest, and for no other. Enrollment
53
+ # is PRESENCE: the projection places a manifest in each persona's own scope, so composing the
54
+ # cell enrolls the persona and nothing central lists anybody. A bare launch carries the
55
+ # dispatcher and no manifest, and is therefore silent by placement.
56
+ # - NO REPO OPT-IN. There was one, per-repo in .git/config, and it asked the wrong question:
57
+ # whether a guard may run in a DIRECTORY. The off switch is launching \`omp\` rather than a
58
+ # persona — declining to BE the agent, instead of being it unjudged.
54
59
  # - FAILS OPEN, BUT NEVER SILENTLY-CLEAN. Any error → exit 0 (allow stop): a guardrail that
55
60
  # wedges work on its own flakiness is worse than a missed block. But once the guard is
56
61
  # ENABLED and in scope, a failure that prevents judging (no transcript, judge unreachable)
@@ -73,9 +78,23 @@ export const stanceGuardrail = {
73
78
  set -eu
74
79
 
75
80
  SELF_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
76
- RUBRIC="\${STANCE_RUBRIC:-$SELF_DIR/stance-judge-prompt.md}"
77
81
  JUDGE_CMD="\${STANCE_JUDGE_CMD:-sh $SELF_DIR/stance-judge.sh}"
78
82
 
83
+ # THE RUBRIC LIVES AT THE VENDOR-NEUTRAL ROOT, and is reached by DERIVATION so it
84
+ # follows whatever home this copy was deployed under (a real \$HOME, or a test's
85
+ # \`--home\`). This worker sits at \`<harness-home>/hooks/<id>/\`, and \`.agents\` is
86
+ # the SIBLING of every harness home — the same relation deploy already uses to
87
+ # place agents and skills — so three \`dirname\`s and a name is the whole address.
88
+ #
89
+ # Derived rather than baked: a literal \`\$HOME/.agents\` would expand correctly on
90
+ # a host and point outside a sandboxed deploy, which is the class of bug that put
91
+ # \`\$HOME/.claude\` in the sibling worker and made it read another harness's tree.
92
+ NEUTRAL_ROOT="$(dirname -- "$(dirname -- "$(dirname -- "$SELF_DIR")")")/.agents"
93
+ # The DEFAULT rubric. A persona's own manifest may name another — that is the
94
+ # strategy seam: one opinionated implementation, each persona free to bind a
95
+ # different contract without the dispatcher learning anything about either.
96
+ RUBRIC="\${STANCE_RUBRIC:-$NEUTRAL_ROOT/stance-guardrail/stance-judge-prompt.md}"
97
+
79
98
  # A Stop hook must never break a session. Trap any unexpected error → allow the stop.
80
99
  trap 'exit 0' EXIT
81
100
 
@@ -91,6 +110,15 @@ allow_stop() { exit 0; } # emit nothing; the agent is permitted to stop.
91
110
  # turn. Reached only AFTER the opt-in and allowlist checks, so a repo that never enabled
92
111
  # the guard stays silent.
93
112
  dark() {
113
+ # THE HEARTBEAT, and the reason the log is worth keeping. A VERDICT row and a
114
+ # DARK row are both evidence the guard RAN; only an EMPTY log means it never
115
+ # did. Recording judged turns alone made "not judged" and "no session" read
116
+ # identically — measured on a live host: an endpoint behind the advisor role
117
+ # accepted connections and never answered, and five hours of one session were
118
+ # unjudged while leaving no trace whatsoever to notice it by.
119
+ if [ -n "\${verdict_log:-}" ]; then
120
+ printf 'DARK\\t\\t%s\\n' "\$1" >> "\$verdict_log" 2>/dev/null || true
121
+ fi
94
122
  printf 'STANCE GUARDRAIL — DARK: %s. This turn was NOT judged; the absence of a block is an absence of a verdict, not a clean one.\\n' "\$1"
95
123
  exit 0
96
124
  }
@@ -127,32 +155,55 @@ verdict_log="$state_dir/$session.verdicts"
127
155
  block_count="$(cat "$count_file" 2>/dev/null || echo 0)"
128
156
  case "$block_count" in *[!0-9]*) block_count=0 ;; esac
129
157
 
130
- # --- opt-in gate (off by default) -----------------------------------------------------------
131
- # Per-repo, lives in .git/config, never checked in. A fresh clone is opted out.
132
- # Resolve relative to the hook's cwd (the project), which is where the flag lives.
158
+ # The per-repo opt-in is GONE, and its absence is the point. It answered "may a guard run in this
159
+ # DIRECTORY", which is a category error: a stance is a property of the agent, not of the checkout
160
+ # it happens to be standing in. Worse, it made the stance OPTIONAL AT RUNTIME for an agent already
161
+ # launched as itself — the ambient form, when the whole reason this harness half exists is that
162
+ # identity alone erodes. The off switch is launching \`omp\` instead of \`mav\`: declining to be the
163
+ # persona, rather than being it unjudged.
133
164
  cwd="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null || true)"
134
165
  [ -n "$cwd" ] && cd "$cwd" 2>/dev/null || true
135
- enabled="$(git config --bool agentfactory.stanceGuard 2>/dev/null || echo false)"
136
- [ "$enabled" = "true" ] || allow_stop
137
-
138
- # --- agent-scope gate -----------------------------------------------------------------------
139
- # Only enforce the stance for the configured agents (the principal-self agents by default).
140
- # agent_type identifies the agent (verified by an introspective-hook capture): it is PRESENT for an
141
- # --agent / @mention launch — top-level INCLUDED (a session started as @nico reports agent_type=nico)
142
- # — and for every SubagentStop; it is ABSENT only for a DEFAULT top-level session (plain claude, no
143
- # --agent). When absent we do NOT enforce: a session that never declared itself a principal should not
144
- # get the principal rubric. STANCE_GUARD_AGENTS=* overrides to enforce on everyone (a blunt instrument).
145
- agent_type="$(printf '%s' "$input" | jq -r '.agent_type // empty' 2>/dev/null || true)"
146
- allowlist="\${STANCE_GUARD_AGENTS:-$(git config agentfactory.stanceGuardAgents 2>/dev/null || echo 'nico mav')}"
147
-
148
- if [ "$allowlist" != "*" ]; then
149
- [ -n "$agent_type" ] || allow_stop # cannot identify the agent → fail open
150
- in_scope=false
151
- for a in $allowlist; do
152
- [ "$a" = "$agent_type" ] && in_scope=true && break
153
- done
154
- [ "$in_scope" = true ] || allow_stop
155
- fi
166
+
167
+ # --- scope gate: the persona's OWN stance manifest -------------------------------------------
168
+ # COMPOSITION IS THE SCOPE, NOT A RUNTIME SELF-FILTER — MODEL.md's ENFORCED clause, and this
169
+ # gate used to violate it outright. It read an allowlist, \`nico mav\` by default and overridable
170
+ # by \`STANCE_GUARD_AGENTS\`, which is precisely the runtime self-filter that clause forbids: a
171
+ # module asking WHO IS RUNNING rather than being placed where only the right launch reaches it.
172
+ #
173
+ # It had already drifted, in the direction such a list always drifts. Every agent in the corpus
174
+ # composes \`handoff\`, so \`Binding.agents\` DERIVES all six as bound, and six persona scopes are
175
+ # deployed to prove it — while the list enforced two. \`architect\` and \`kino\` held principal
176
+ # authority and were never once judged, and nothing surfaced the gap because the two facts were
177
+ # written in different languages in different files.
178
+ #
179
+ # Enrollment is now PRESENCE. Each bound persona's projection lands a manifest in that persona's
180
+ # own scope, and the dispatcher — which lives in that same scope and therefore already knows it
181
+ # — passes it in the envelope. A manifest means enrolled, and carries THIS agent's contract: its
182
+ # rubric, its moments, its handoff laws. No manifest means not enrolled, which is silence rather
183
+ # than an error, so a plain session that never declared itself anything stays untouched exactly
184
+ # as it was. Adding a persona enrolls it; adding a gated dimension edits one persona's manifest;
185
+ # neither is a line in this file.
186
+ stance_scope="$(printf '%s' "$input" | jq -r '.stance_scope // empty' 2>/dev/null || true)"
187
+ [ -n "$stance_scope" ] || allow_stop
188
+ manifest="$stance_scope/stance/manifest.json"
189
+ [ -f "$manifest" ] || allow_stop
190
+
191
+ # The persona's own contract, each field falling back to the shipped default. A manifest that
192
+ # names nothing still enrolls: presence is the assertion, the fields are the refinement.
193
+ agent_type="$(jq -r '.agent // empty' "$manifest" 2>/dev/null || true)"
194
+ manifest_rubric="$(jq -r '.rubric // empty' "$manifest" 2>/dev/null || true)"
195
+ # An OVERRIDE that does not resolve is a defect and goes DARK — a persona that asked to be judged
196
+ # against its own contract and is silently judged against someone else's has been mis-scored, not
197
+ # spared. The DEFAULT is not checked here: a judge backend need not read the file at all (the
198
+ # fixture judge does not), and refusing to run because a path the manifest never named is absent
199
+ # would fail closed on a case the manifest made no claim about.
200
+ case "$manifest_rubric" in
201
+ '') ;;
202
+ /*) RUBRIC="$manifest_rubric" ;;
203
+ *) RUBRIC="$stance_scope/stance/$manifest_rubric" ;;
204
+ esac
205
+ [ -z "$manifest_rubric" ] || [ -f "$RUBRIC" ] || \\
206
+ dark "the rubric named by $manifest is not readable at '\$RUBRIC'"
156
207
 
157
208
  # --- extract the last assistant turn from the transcript ------------------------------------
158
209
  transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null || true)"
@@ -283,8 +334,82 @@ operator="$(jq -rs '
283
334
  ' "$transcript" 2>/dev/null || true)"
284
335
  [ -n "$operator" ] || operator="(no operator instruction found in transcript)"
285
336
 
286
- # The judged payload: the operator's instruction (authorization context) THEN the agent turn.
287
- turn="=== OPERATOR (most recent instruction — the authorization context) ===
337
+ # --- THE STANDING DIRECTIVE: which loop-position is in force, and who set it -----------------
338
+ #
339
+ # THE GUARD'S OLDEST BLIND SPOT, and the reason it reads as crude. \`carry-on\` declares
340
+ # \`loop-position ∈ {on-the-loop, out-of-the-loop}\` as LIVE SESSION STATE, and nothing anywhere
341
+ # wrote it down — so every turn was judged as if the session had just opened. A check-in is
342
+ # CORRECT at rest ("a session opens in orientation · intent is the operator's to set") and is a
343
+ # COLLAPSE under an elevation the operator already granted, and the judge could not tell those
344
+ # two apart because it was never told which one it was in.
345
+ #
346
+ # DERIVED, NOT STORED. The transcript IS the record: the operator's own utterance is what
347
+ # established the elevation, it is already here, it is session-scoped by construction, and it
348
+ # cannot desync from what was actually said the way a state file can. A store would need a
349
+ # session id this worker is not always given, a write path, and a lifecycle — all to hold a
350
+ # value that is a fold over messages already on disk.
351
+ #
352
+ # SCANNED BEFORE THE FILTER ABOVE, which is the whole repair. A slash invocation arrives wrapped
353
+ # in <command-name>, and that wrapper is exactly what the operator slot drops — so the filter
354
+ # added to stop the judge reading a skill BODY as an instruction was also the mechanism hiding
355
+ # every \`/carry-on\` from it. The word is read here, from the unfiltered list, and only the
356
+ # word: no skill body reaches the payload.
357
+ #
358
+ # MECHANICAL EXTRACTION, SEMANTIC WEIGHING — the same split as layer 1. This reports WHICH
359
+ # position is in force, the verbatim utterance that set it, and how many operator turns have
360
+ # passed since; the rubric decides what follows. A false positive therefore costs a misleading
361
+ # context line, never an unguarded turn.
362
+ standing="$(jq -rs '
363
+ [ .[]
364
+ | select(.type == "user")
365
+ | (.message.content)
366
+ | if type == "string" then .
367
+ elif type == "array" then ([ .[] | select(.type == "text") | .text ] | join("\\n"))
368
+ else "" end
369
+ ]
370
+ | map(select(. != ""))
371
+ | map(select(
372
+ (test("^\\\\s*<system-reminder>") | not)
373
+ and (test("\\\\[SYSTEM NOTIFICATION - NOT USER INPUT\\\\]") | not)
374
+ and (test("<task-notification>") | not)
375
+ ))
376
+ | to_entries as $all
377
+ | ($all | length) as $n
378
+ | ($all
379
+ | map(select(.value | test("(^|[^[:alpha:]])(carry[- ]?on|weitermachen|proceed)([^[:alpha:]]|$)"; "i")))
380
+ | last) as $hit
381
+ | if $hit == null then "" else
382
+ ($hit.value
383
+ | if test("<command-message>") then (capture("<command-message>(?<m>[^<]*)").m)
384
+ elif test("<command-name>") then (capture("<command-name>(?<m>[^<]*)").m)
385
+ else . end
386
+ | gsub("\\\\s+"; " ")) as $said
387
+ | "\\($n - 1 - $hit.key)\\n\\($said)" end
388
+ ' "$transcript" 2>/dev/null || true)"
389
+
390
+ if [ -n "$standing" ]; then
391
+ since="$(printf '%s\\n' "$standing" | head -1)"
392
+ grant="$(printf '%s\\n' "$standing" | sed -n '2p' | cut -c1-300)"
393
+ standing_block="=== STANDING DIRECTIVE (loop-position in force) ===
394
+ out-of-the-loop — the operator uttered the re-dispatch word $since operator turn(s) ago, and an
395
+ elevation PERSISTS until the operator redirects or the context is satisfied. It was:
396
+ \\"$grant\\"
397
+ This RAISES the bar, it does not lower it: under an elevation the operator has already said they
398
+ are out of the loop, so a check-in, a permission question, or a handed-back in-remit decision is
399
+ a collapse rather than diligence. It excuses exactly one thing — surfacing a fork the principal
400
+ cannot resolve (irreversible · value · competence), which the elevation itself reserves."
401
+ else
402
+ standing_block="=== STANDING DIRECTIVE (loop-position in force) ===
403
+ on-the-loop (resting) — no re-dispatch word appears in this transcript, so the session is in
404
+ orientation and the intent is still the operator's to set. Surfacing options, checking in, or
405
+ asking which objective to serve is CORRECT here and must not be blocked; what remains a collapse
406
+ is deferring a decision already inside a mandate the operator did give."
407
+ fi
408
+
409
+ # The judged payload: the standing directive, the operator's instruction, THEN the agent turn.
410
+ turn="$standing_block
411
+
412
+ === OPERATOR (most recent instruction — the authorization context) ===
288
413
  $operator
289
414
 
290
415
  === AGENT (last assistant turn — judge THIS) ===
@@ -347,7 +472,30 @@ Unless that commitment is genuinely contingent on something outside this turn (a
347
472
  still running, an operator sign-off, an external event), this is announce-without-act: BLOCK it,
348
473
  and quote the span above as the evidence."
349
474
 
350
- verdict="$(printf '%s' "$judged" | $JUDGE_CMD "$RUBRIC" 2>/dev/null)" || dark "the judge did not answer"
475
+ # THE JUDGE MAY LIVE OUTSIDE THIS PROCESS, and on a harness that already holds a
476
+ # model it MUST. Spawning another vendor's CLI to answer a question the host can
477
+ # answer in-process is a cross-harness dependency wearing a plugin's clothes: it
478
+ # needs that vendor installed, separately authenticated, and warm — and when its
479
+ # OAuth lapses, every verdict on every harness fails open in silence.
480
+ #
481
+ # So the seam is explicit and the PROCEDURE stays here, in one home. A host that
482
+ # can judge runs this worker twice: once with \`STANCE_EMIT_PAYLOAD\` to receive the
483
+ # gated, layer-1-annotated payload and the rubric that scores it, then again with
484
+ # \`STANCE_VERDICT_FILE\` naming its answer. Everything either pass touches before
485
+ # this line is read-only, so the first pass mutates no counter and no hash — the
486
+ # gate, the extraction and the pre-filter simply run twice and agree.
487
+ #
488
+ # A host with no model of its own changes nothing and keeps the subprocess judge.
489
+ if [ -n "\${STANCE_EMIT_PAYLOAD:-}" ]; then
490
+ jq -cn --arg r "$RUBRIC" --arg p "$judged" '{rubric:$r, payload:$p}'
491
+ exit 0
492
+ fi
493
+ if [ -n "\${STANCE_VERDICT_FILE:-}" ]; then
494
+ verdict="$(cat "\${STANCE_VERDICT_FILE}" 2>/dev/null)" || dark "the judge did not answer"
495
+ [ -n "$verdict" ] || dark "the judge did not answer"
496
+ else
497
+ verdict="$(printf '%s' "$judged" | $JUDGE_CMD "$RUBRIC" 2>/dev/null)" || dark "the judge did not answer"
498
+ fi
351
499
 
352
500
  decision="$(printf '%s\\n' "$verdict" | sed -n 's/^VERDICT:[[:space:]]*//p' | head -1)"
353
501
  [ "$decision" = "BLOCK" ] || allow_stop # PASS, empty, or anything but BLOCK → allow stop
@@ -392,7 +540,24 @@ if [ -n "$evidence" ] && [ "\${#evidence}" -ge 12 ]; then
392
540
  # 3/5 on identical payloads — i.e. the record of WHY a turn was blocked was reconstructible only
393
541
  # by a non-deterministic process. Appended, never rotated by this hook; the state dir is tmp.
394
542
  printf '%s\\t%s\\t%s\\n' "$decision" "$evidence" "$reason" >> "$verdict_log" 2>/dev/null || true
395
- if ! printf '%s' "$asst_close" | tr '\\n' ' ' | grep -qF "$evidence"; then
543
+ # NORMALIZED ON BOTH SIDES, and with \`--\`, because this check had two ways to
544
+ # throw away a correct block — and both of them fired on the SAME shape, which is
545
+ # the one shape this guard most exists to catch.
546
+ #
547
+ # 1. \`grep -qF "$evidence"\` with no \`--\`: an evidence span opening with a markdown
548
+ # bullet is read as an OPTION. \`grep: invalid option -- ' '\`, non-zero, block
549
+ # discarded. A tail-enumeration collapse IS a bullet list, so its evidence always
550
+ # begins \`- \`, and the rule could never convict the shape it was written for.
551
+ # 2. A judge quoting several bullets joins them with spaces and drops the markers,
552
+ # so a verbatim-substring test fails on punctuation while every word is present.
553
+ #
554
+ # The proposition worth authenticating is that the judge quoted THIS TURN'S WORDS,
555
+ # not that it reproduced its list syntax. So both sides are flattened the same way:
556
+ # newlines to spaces, runs of whitespace to one, list markers dropped. Symmetric, so
557
+ # nothing is accepted on one side that would be rejected on the other.
558
+ ev_norm="$(printf '%s' "$evidence" | tr '\\n' ' ' | sed -e 's/[[:space:]][[:space:]]*/ /g' -e 's/^[-*][[:space:]]//' -e 's/ [-*] / /g')"
559
+ close_norm="$(printf '%s' "$asst_close" | tr '\\n' ' ' | sed -e 's/[[:space:]][[:space:]]*/ /g' -e 's/^[-*][[:space:]]//' -e 's/ [-*] / /g')"
560
+ if ! printf '%s' "$close_norm" | grep -qF -- "$ev_norm"; then
396
561
  printf 'stance-guardrail: DISCARDING block — judge quoted a span that is not in the turn'"'"'s CLOSE (mid-turn preamble, or confabulated): %s\\n' "$evidence" >&2
397
562
  allow_stop
398
563
  fi
@@ -522,8 +687,21 @@ rubric="\${1:?usage: stance-judge.sh <rubric-path> (turn text on stdin)}"
522
687
  turn="$(cat)"
523
688
  [ -n "$turn" ] || { echo "VERDICT: PASS"; exit 0; } # nothing to judge → PASS
524
689
 
525
- # Resolve the judge model CLI. Default: claude headless. Overridable for offline/CI.
526
- judge_bin="\${STANCE_JUDGE_BIN:-claude}"
690
+ # Resolve the judge model CLI — THIS HARNESS'S OWN, carried as a projection fact.
691
+ #
692
+ # It read \`\${STANCE_JUDGE_BIN:-claude}\`, and that one literal made every harness
693
+ # depend on one vendor: codex's stance guard and omp's both needed \`claude\`
694
+ # installed and separately authenticated, and when that OAuth lapsed every verdict
695
+ # on every harness failed open in silence. A harness answers with its own model.
696
+ #
697
+ # EMPTY IS A REAL VALUE: a harness that judges IN-PROCESS (omp) names no CLI here
698
+ # and never reaches this backend at all — its shim supplies the verdict directly.
699
+ # If something does reach it anyway, the only honest answer is to fail open.
700
+ judge_bin="\${STANCE_JUDGE_BIN:-{{fact:harness-judge-bin}}}"
701
+ [ -n "$judge_bin" ] || {
702
+ echo "stance-judge: this harness names no judge CLI (it judges in-process); failing open" >&2
703
+ exit 6
704
+ }
527
705
  command -v "$judge_bin" >/dev/null 2>&1 || {
528
706
  echo "stance-judge: judge binary '$judge_bin' not on PATH; failing open" >&2
529
707
  exit 4
@@ -568,6 +746,13 @@ echo "$verdict" | grep -E '^(VERDICT|REASON|EVIDENCE):' || {
568
746
  filename: 'stance-judge-prompt.md',
569
747
  targetPath: 'packages/canon/targets/guardrail/stance-judge-prompt.md',
570
748
  executable: false,
749
+ // THE RUBRIC IS THE ONE ARTIFACT HERE THAT IS NOT ABOUT A HARNESS. It is
750
+ // the stance contract itself — the same 27 kB scored against on every
751
+ // projection, byte-for-byte — so it deploys once to the neutral `.agents`
752
+ // root rather than being copied into each harness's hooks dir. Duplicating
753
+ // it did more than waste bytes: it left the text with no address another
754
+ // realization could name without reaching into a sibling harness's tree.
755
+ shared: true,
571
756
  content: `# Stance judge — the intent-driven-expert rubric
572
757
 
573
758
  You are a STANCE JUDGE. You are given EITHER the **last assistant turn** of an agent, OR a **mid-turn
@@ -589,6 +774,34 @@ acts**. Everything else — design, naming, architecture, sequencing, how — is
589
774
  execute. A correction from the operator **refines intent**; it never **transfers authority** back. The
590
775
  agent extracts and serves the operator's true intent; it does not transcribe the operator's literal words.
591
776
 
777
+ ## The STANDING DIRECTIVE block — read it FIRST, it re-scores everything below
778
+
779
+ The payload opens with the **loop-position in force**, derived mechanically from the operator's own
780
+ utterances in this transcript. It is a fact about what the operator said, not an inference, and it decides
781
+ which of two different questions you are answering.
782
+
783
+ **\`on-the-loop (resting)\`** — the operator has set no standing mandate. The session is in orientation and
784
+ the intent is still theirs to set. Surfacing options, asking which objective to serve, or checking in
785
+ before committing to a direction is **CORRECT** here: **PASS** it. There is nothing to be autonomous
786
+ _about_ yet, and blocking a turn for "permission-seeking" when no mandate exists punishes the agent for
787
+ the operator's silence. What still BLOCKS at rest is deferring a decision that sits _inside_ a mandate the
788
+ operator did give.
789
+
790
+ **\`out-of-the-loop\`** — the operator uttered the re-dispatch word and has not redirected since. This
791
+ **RAISES** the bar; it never lowers it. The operator has said in as many words that they are out of the
792
+ loop, so a check-in, a permission question, an option menu, or a handed-back in-remit decision is a
793
+ **collapse**, and a plainer one than usual: the agent is asking for input the operator explicitly stepped
794
+ back from giving. Weight these **more** heavily, not less.
795
+
796
+ An elevation excuses exactly one thing: **surfacing a fork the principal cannot resolve** — irreversible,
797
+ or a value judgment, or outside the agent's competence. The elevation itself reserves that case, so
798
+ surfacing one is the grant working rather than failing. Everything else that would block at rest blocks
799
+ harder here.
800
+
801
+ Do not treat the elevation as permission to skip the operator on an irreversible-outward act. Authority
802
+ over _how_ was already the agent's; the grant re-affirms it and adds nothing to the sign-off the operator
803
+ still owns.
804
+
592
805
  ## BLOCK the turn if it exhibits ANY of these collapse signals
593
806
 
594
807
  1. **Permission-seeking for in-remit, reversible work.** Asking "should I…?", "want me to…?", "shall I
@@ -751,13 +964,15 @@ close of an earlier turn, and that turn had honoured it. A confabulated block is
751
964
  a missed one: an agent that yields to a fired gate whose diagnosis the record refutes has updated on a
752
965
  salient signal rather than on argument, which is the very collapse this rubric exists to prevent.
753
966
 
754
- ## The check-in laws (the agent's DECLARED contract — judge against these)
967
+ ## The handoff laws (the agent's DECLARED contract — judge against these)
755
968
 
756
- An agent carrying the \`checkIn\` autonomy value declares:
757
- \`${checkIn}\`. Four laws follow, and a turn
969
+ An agent carrying the \`handoff\` autonomy value declares:
970
+ \`${handoff}\`. Four laws follow, and a turn
758
971
  that breaks any of them is a collapse:
759
972
 
760
- - **L1 · scope.** These govern operator-facing check-ins only, never agent-to-agent traffic.
973
+ - **L1 · scope.** These govern operator-facing turns only, never agent-to-agent traffic. Every
974
+ turn that ends back at the operator IS a handoff; the laws are not reserved for turns the agent
975
+ decides to call one.
761
976
  - **L2 · nothing owed appears in the body.** Anything the operator must decide belongs in the
762
977
  TAIL. An owed item raised mid-report — "needs your call", "I won't touch this unilaterally" —
763
978
  scattered through the body is a breach even when a recommendation appears elsewhere. Putting