instar 1.3.478 → 1.3.480

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 (31) hide show
  1. package/.claude/skills/autonomous/SKILL.md +10 -0
  2. package/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +418 -23
  3. package/.claude/skills/autonomous/scripts/setup-autonomous.sh +29 -0
  4. package/dist/commands/server.d.ts.map +1 -1
  5. package/dist/commands/server.js +10 -7
  6. package/dist/commands/server.js.map +1 -1
  7. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  8. package/dist/config/ConfigDefaults.js +39 -8
  9. package/dist/config/ConfigDefaults.js.map +1 -1
  10. package/dist/core/PostUpdateMigrator.d.ts +1 -0
  11. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  12. package/dist/core/PostUpdateMigrator.js +121 -3
  13. package/dist/core/PostUpdateMigrator.js.map +1 -1
  14. package/dist/core/devGatedFeatures.d.ts +31 -0
  15. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  16. package/dist/core/devGatedFeatures.js +121 -0
  17. package/dist/core/devGatedFeatures.js.map +1 -1
  18. package/dist/scaffold/templates.d.ts.map +1 -1
  19. package/dist/scaffold/templates.js +1 -0
  20. package/dist/scaffold/templates.js.map +1 -1
  21. package/dist/server/routes.js +1 -1
  22. package/dist/server/routes.js.map +1 -1
  23. package/package.json +1 -1
  24. package/scripts/lib/dark-gate-attribution.js +148 -0
  25. package/scripts/lint-dev-agent-dark-gate.js +159 -14
  26. package/src/data/builtin-manifest.json +64 -64
  27. package/src/scaffold/templates.ts +1 -0
  28. package/upgrades/1.3.479.md +53 -0
  29. package/upgrades/1.3.480.md +35 -0
  30. package/upgrades/side-effects/autonomous-completion-real-checks.md +88 -0
  31. package/upgrades/side-effects/dev-agent-dark-gate-enforcement.md +95 -0
@@ -100,6 +100,10 @@ Write this content:
100
100
  <!-- COMPLETION_CONDITION_DEFAULT — the Write-tool template defaults to a verifiable
101
101
  completion_condition (judged by an INDEPENDENT model), NOT the self-declared
102
102
  promise. The promise is the recorded fallback. Spec: AUTONOMOUS-COMPLETION-DISCIPLINE.md -->
103
+ <!-- REALCHECK_VERIFY — the template ALSO supports an OPTIONAL verification_command (ACT-152):
104
+ when set, the stop-hook RUNS it on a met:true verdict and only allows the exit if it ALSO
105
+ passes (fail/timeout → keep working). This sentinel is the PostUpdateMigrator marker that
106
+ re-deploys this SKILL.md to existing agents. Spec: autonomous-completion-real-checks.md -->
103
107
 
104
108
  ```markdown
105
109
  ---
@@ -120,6 +124,12 @@ completion_mode: condition # "condition" (default) | "promise-fallback"
120
124
  promise_fallback_reason: "" # one line, REQUIRED iff completion_mode == promise-fallback
121
125
  completion_promise: "ALL_TASKS_COMPLETE" # retained as the fallback token
122
126
  hard_blocker_nonce: "{a random per-run token — get via: openssl rand -hex 8}"
127
+ # OPTIONAL real-check (ACT-152): a command that must exit 0 before the run may stop. When set, a
128
+ # met:true verdict RUNS it and gates the exit on it (fail/timeout → keep working). Omit if the
129
+ # goal isn't checkable by a single command. work_dir is captured by setup so a relative command
130
+ # (e.g. `npm test`) runs in the right tree; override with verification_cwd if the build is elsewhere.
131
+ # verification_command: "npm test --silent"
132
+ # verification_cwd: "/path/to/build/worktree"
123
133
  ---
124
134
 
125
135
  # Autonomous Session
@@ -40,6 +40,10 @@ for _arg in "$@"; do [[ "$_arg" == "--codex" ]] && IS_CODEX=1; done
40
40
 
41
41
  # hook-capability: codex-stdout-json-safe — see emit() below (migration marker; bumped so
42
42
  # existing #28 installs re-deploy this fixed hook even though they already have CODEX_LOOP_ENABLED).
43
+ # hook-capability: REALCHECK_VERIFY — ACT-152 real-check gate (realcheck_gate runs an opt-in
44
+ # verification_command on a met:true verdict and gates the exit on it; fail/timeout/breaker-open →
45
+ # keep working, the safe direction). This sentinel is the PostUpdateMigrator marker that re-deploys
46
+ # this hook to existing agents that carry COMPLETION_DISCIPLINE but not REALCHECK_VERIFY.
43
47
  # emit — human-facing approve/status text. In codex mode the Stop hook's STDOUT must be
44
48
  # ONLY valid decision-JSON (the `{"decision":"block",...}` case far below) or empty:
45
49
  # codex rejects ANY other stdout as "invalid stop hook JSON output" and reports the stop
@@ -187,6 +191,17 @@ fm_get() {
187
191
  local key="$1"
188
192
  printf '%s\n' "$FRONTMATTER" | grep "^${key}:" | head -1 | sed "s/^${key}: *//" | tr -d '"' || true
189
193
  }
194
+ # Quote-PRESERVING field read — identical to fm_get but WITHOUT the `tr -d '"'`,
195
+ # so a value containing literal quotes (e.g. a verification_command with quoted
196
+ # args) survives intact. Strips ONLY a single pair of wrapping double-quotes (the
197
+ # YAML frontmatter the setup script writes as `key: "value"`), never inner quotes.
198
+ fm_get_raw() {
199
+ local key="$1" val
200
+ val=$(printf '%s\n' "$FRONTMATTER" | grep "^${key}:" | head -1 | sed "s/^${key}: *//" || true)
201
+ # Strip one leading + one trailing double-quote if both present (preserve inner).
202
+ if [[ "$val" == \"*\" ]]; then val="${val#\"}"; val="${val%\"}"; fi
203
+ printf '%s' "$val"
204
+ }
190
205
 
191
206
  ACTIVE=$(fm_get active)
192
207
  if [[ "$ACTIVE" != "true" ]]; then
@@ -213,6 +228,13 @@ COMPLETION_PROMISE=$(fm_get completion_promise)
213
228
  COMPLETION_CONDITION=$(fm_get completion_condition)
214
229
  GOAL_MODE=$(fm_get goal_mode) # "native" = the framework's own /goal loop drives completion
215
230
  RUN_GOAL=$(fm_get goal)
231
+ # Real-check verification (ACT-152) — opt-in declared command + its build dir.
232
+ # Read QUOTE-PRESERVING: a verification_command may contain literal quotes that
233
+ # fm_get's `tr -d '"'` would mangle. Absent on older state files → empty → the
234
+ # real-check gate self-disables for that run (byte-identical to today).
235
+ VERIFICATION_COMMAND=$(fm_get_raw verification_command)
236
+ VERIFICATION_CWD=$(fm_get_raw verification_cwd)
237
+ WORK_DIR=$(fm_get_raw work_dir)
216
238
  # COMPLETION_DISCIPLINE — per-run nonce that authenticates a <hard-blocker> exit
217
239
  # marker (mirrors the completion_promise exact-match guard). Absent on older
218
240
  # state files → the marker branch self-disables for that run (no false exit).
@@ -244,25 +266,57 @@ try:
244
266
  bc = a.get('judgeFailCooldownMs', 600000)
245
267
  mc = a.get('markerFieldMaxChars', 500)
246
268
  rb = a.get('hardBlockerLogRotateBytes', 1048576)
247
- print('%s %d %d %d %d %d %d' % (
269
+ # Real-check verification (ACT-152) nested under completionDiscipline.
270
+ rc = a.get('realCheck') or {}
271
+ rce = rc.get('enabled', True)
272
+ rct = rc.get('timeoutMs', 120000)
273
+ try:
274
+ rct = int(rct)
275
+ except Exception:
276
+ rct = 120000
277
+ if rct < 5000:
278
+ rct = 5000
279
+ rcm = rc.get('maxChars', 2000)
280
+ rcc = rc.get('captureBytes', 65536)
281
+ rcbt = rc.get('failBreakerThreshold', 3)
282
+ rcbw = rc.get('failWindowMs', 600000)
283
+ rcbc = rc.get('failCooldownMs', 600000)
284
+ print('%s %d %d %d %d %d %d %s %d %d %d %d %d %d' % (
248
285
  '1' if en else '0', jt,
249
286
  int(bt) if str(bt).isdigit() else 3,
250
287
  int(bw) if str(bw).isdigit() else 600000,
251
288
  int(bc) if str(bc).isdigit() else 600000,
252
289
  int(mc) if str(mc).isdigit() else 500,
253
290
  int(rb) if str(rb).isdigit() else 1048576,
291
+ '1' if rce else '0', rct,
292
+ int(rcm) if str(rcm).isdigit() else 2000,
293
+ int(rcc) if str(rcc).isdigit() else 65536,
294
+ int(rcbt) if str(rcbt).isdigit() else 3,
295
+ int(rcbw) if str(rcbw).isdigit() else 600000,
296
+ int(rcbc) if str(rcbc).isdigit() else 600000,
254
297
  ))
255
298
  except Exception:
256
- print('1 35000 3 600000 600000 500 1048576')
257
- " 2>/dev/null || echo "1 35000 3 600000 600000 500 1048576")
258
- read -r CD_ENABLED JUDGE_TIMEOUT_MS CD_BREAKER_THRESHOLD CD_BREAKER_WINDOW_MS CD_BREAKER_COOLDOWN_MS CD_MARKER_MAX_CHARS CD_LOG_ROTATE_BYTES <<< "$CD_CFG"
299
+ print('1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000')
300
+ " 2>/dev/null || echo "1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000")
301
+ read -r CD_ENABLED JUDGE_TIMEOUT_MS CD_BREAKER_THRESHOLD CD_BREAKER_WINDOW_MS CD_BREAKER_COOLDOWN_MS CD_MARKER_MAX_CHARS CD_LOG_ROTATE_BYTES RC_ENABLED RC_TIMEOUT_MS RC_MAX_CHARS RC_CAPTURE_BYTES RC_BREAKER_THRESHOLD RC_BREAKER_WINDOW_MS RC_BREAKER_COOLDOWN_MS <<< "$CD_CFG"
259
302
  [[ "$CD_ENABLED" =~ ^[01]$ ]] || CD_ENABLED=1
260
303
  [[ "$JUDGE_TIMEOUT_MS" =~ ^[0-9]+$ ]] || JUDGE_TIMEOUT_MS=35000
261
304
  # curl -m takes SECONDS; convert ms→s (ceil), floor 5s.
262
305
  JUDGE_TIMEOUT_S=$(( (JUDGE_TIMEOUT_MS + 999) / 1000 ))
263
306
  [[ $JUDGE_TIMEOUT_S -lt 5 ]] && JUDGE_TIMEOUT_S=5
307
+ # ── Real-check verification dials (ACT-152) — validated + ms→s for the command timeout ──
308
+ [[ "$RC_ENABLED" =~ ^[01]$ ]] || RC_ENABLED=1
309
+ [[ "$RC_TIMEOUT_MS" =~ ^[0-9]+$ ]] || RC_TIMEOUT_MS=120000
310
+ RC_TIMEOUT_S=$(( (RC_TIMEOUT_MS + 999) / 1000 ))
311
+ [[ $RC_TIMEOUT_S -lt 5 ]] && RC_TIMEOUT_S=5
312
+ [[ "$RC_MAX_CHARS" =~ ^[0-9]+$ ]] || RC_MAX_CHARS=2000
313
+ [[ "$RC_CAPTURE_BYTES" =~ ^[0-9]+$ ]] || RC_CAPTURE_BYTES=65536
314
+ [[ "$RC_BREAKER_THRESHOLD" =~ ^[0-9]+$ ]] || RC_BREAKER_THRESHOLD=3
315
+ [[ "$RC_BREAKER_WINDOW_MS" =~ ^[0-9]+$ ]] || RC_BREAKER_WINDOW_MS=600000
316
+ [[ "$RC_BREAKER_COOLDOWN_MS" =~ ^[0-9]+$ ]] || RC_BREAKER_COOLDOWN_MS=600000
264
317
  # logs/ resolves against the agent home we cd'd into above (CWD is the agent home).
265
318
  HARD_BLOCKER_LOG="logs/autonomous-hard-blocker.jsonl"
319
+ REALCHECK_LOG="logs/autonomous-realcheck.jsonl"
266
320
 
267
321
  # ── Layer A: notify-on-stop (2026-05-27 silent-stalls postmortem, Task 2) ──────
268
322
  # When an autonomous run reaches a TERMINAL exit (completion / duration / emergency),
@@ -743,6 +797,329 @@ hb_leak_hit() {
743
797
  printf '%s' "$1" | grep -qiE 'sk-[a-zA-Z0-9]{16,}|xox[baprs]-[a-zA-Z0-9-]{8,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|AIza[0-9A-Za-z_-]{20,}|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' 2>/dev/null && echo 1 || echo 0
744
798
  }
745
799
 
800
+ # ── REAL-CHECK VERIFICATION (ACT-152) — helpers ──────────────────────────────
801
+ # A failing/flaky/mis-scoped verification_command would otherwise re-run on EVERY
802
+ # met:true verdict for the whole duration — and because the judge fires first to
803
+ # produce the met verdict, it also re-spends the LLM judge every iteration. The P19
804
+ # brake (§4) reuses the CD_BACKOFF_STATE sidecar with SIBLING counters so a stuck
805
+ # command is bounded to ~one judge+command cycle per cooldown window.
806
+
807
+ # realcheck_breaker_open → echoes 1 if the real-check breaker is within its cooldown
808
+ # window, else 0. MUST fail CLOSED (echo 0 = "run the check", the SAFE direction for
809
+ # this feature) on ANY sidecar read/parse error — mirroring cd_breaker_open's
810
+ # echo-0-on-failure default. (Fail-OPEN here would silently suppress the check and let
811
+ # an unverified transcript-met exit through — the exact failure this feature prevents.)
812
+ realcheck_breaker_open() {
813
+ [[ "$RC_ENABLED" != "1" ]] && { echo 0; return; }
814
+ local now fails winstart
815
+ now=$(date +%s)
816
+ fails=$(jq -r '.realCheckFailures // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
817
+ winstart=$(jq -r '.realCheckFailWindowStart // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
818
+ [[ "$fails" =~ ^[0-9]+$ ]] || fails=0
819
+ [[ "$winstart" =~ ^[0-9]+$ ]] || winstart=0
820
+ local cooldown_s=$(( RC_BREAKER_COOLDOWN_MS / 1000 ))
821
+ if [[ $fails -ge $RC_BREAKER_THRESHOLD ]] && [[ $winstart -gt 0 ]] && [[ $(( now - winstart )) -lt $cooldown_s ]]; then
822
+ echo 1; return
823
+ fi
824
+ echo 0
825
+ }
826
+ # Record a real-check failure into the sidecar (atomic .tmp.$$ + mv; preserves other keys).
827
+ realcheck_record_failure() {
828
+ [[ "$RC_ENABLED" != "1" ]] && return 0
829
+ local now fails winstart
830
+ now=$(date +%s)
831
+ fails=$(jq -r '.realCheckFailures // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
832
+ winstart=$(jq -r '.realCheckFailWindowStart // 0' "$CD_BACKOFF_STATE" 2>/dev/null || echo 0)
833
+ [[ "$fails" =~ ^[0-9]+$ ]] || fails=0
834
+ [[ "$winstart" =~ ^[0-9]+$ ]] || winstart=0
835
+ local window_s=$(( RC_BREAKER_WINDOW_MS / 1000 ))
836
+ if [[ $winstart -le 0 ]] || [[ $(( now - winstart )) -ge $window_s ]]; then
837
+ winstart=$now; fails=1
838
+ else
839
+ fails=$(( fails + 1 ))
840
+ fi
841
+ if [[ -f "$CD_BACKOFF_STATE" ]]; then
842
+ jq --argjson f "$fails" --argjson w "$winstart" '.realCheckFailures=$f | .realCheckFailWindowStart=$w' "$CD_BACKOFF_STATE" \
843
+ > "${CD_BACKOFF_STATE}.tmp.$$" 2>/dev/null && mv "${CD_BACKOFF_STATE}.tmp.$$" "$CD_BACKOFF_STATE" || true
844
+ else
845
+ printf '{"realCheckFailures":%s,"realCheckFailWindowStart":%s}\n' "$fails" "$winstart" > "$CD_BACKOFF_STATE" 2>/dev/null || true
846
+ fi
847
+ # Cross the threshold → raise ONE deduped Attention item (Close the Loop / P19 cap).
848
+ if [[ $fails -ge $RC_BREAKER_THRESHOLD ]]; then
849
+ realcheck_raise_attention "$fails"
850
+ fi
851
+ }
852
+ # Reset the real-check breaker on a PASS (atomic .tmp.$$ + mv).
853
+ realcheck_reset() {
854
+ [[ "$RC_ENABLED" != "1" ]] && return 0
855
+ [[ -f "$CD_BACKOFF_STATE" ]] || return 0
856
+ jq '.realCheckFailures=0 | .realCheckFailWindowStart=0' "$CD_BACKOFF_STATE" \
857
+ > "${CD_BACKOFF_STATE}.tmp.$$" 2>/dev/null && mv "${CD_BACKOFF_STATE}.tmp.$$" "$CD_BACKOFF_STATE" || true
858
+ }
859
+ # Raise ONE /ack-able Attention item when the real-check breaker crosses the threshold
860
+ # (Close the Loop). Deduped per the Bounded Notification Surface: source-tagged
861
+ # `autonomous-realcheck-stuck`, one per run via a started-at-keyed id. Best-effort.
862
+ realcheck_raise_attention() {
863
+ [[ "$RC_ENABLED" != "1" ]] && return 0
864
+ local fails="$1"
865
+ if [[ -n "${INSTAR_HOOK_ATTENTION_RECORD:-}" ]]; then
866
+ printf '{"id":"autonomous-realcheck-stuck-%s-%s","title":"Autonomous real check failing repeatedly","fails":%s}\n' \
867
+ "${REPORT_TOPIC:-none}" "$(printf '%s' "${STARTED_AT:-}" | tr -cd '0-9')" "${fails:-0}" \
868
+ >> "$INSTAR_HOOK_ATTENTION_RECORD" 2>/dev/null || true
869
+ return 0
870
+ fi
871
+ local at_port at_auth at_id
872
+ at_port=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('port',4040))" 2>/dev/null || echo 4040)
873
+ at_auth=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null || echo "")
874
+ at_id="autonomous-realcheck-stuck-${REPORT_TOPIC:-none}-$(printf '%s' "${STARTED_AT:-}" | tr -cd '0-9')"
875
+ jq -nc \
876
+ --arg id "$at_id" \
877
+ --arg title "Autonomous real check failing repeatedly — \"$(goal_snippet)\"" \
878
+ --arg summary "The declared real check (${VERIFICATION_COMMAND}) has failed ${fails} times — likely an authoring problem (wrong directory, stale, or testing the wrong thing). The run keeps working (bounded by the duration limit) until it passes or you fix it." \
879
+ '{id:$id, title:$title, summary:$summary, priority:"medium", source:"autonomous-realcheck-stuck", sourceContext:"autonomous-realcheck-stuck", category:"autonomous"}' \
880
+ | curl -s -m 8 -H "Authorization: Bearer $at_auth" -H 'Content-Type: application/json' \
881
+ --data-binary @- "http://localhost:${at_port}/attention" >/dev/null 2>&1 || true
882
+ }
883
+
884
+ # realcheck_audit_row — append ONE JSONL row to REALCHECK_LOG per run (same size-rotate
885
+ # as the hard-blocker log). Args: outcome exitCode durationMs command cwd breakerOpen.
886
+ realcheck_audit_row() {
887
+ local outcome="$1" excode="$2" durms="$3" cmd="$4" cwd="$5" bopen="$6"
888
+ mkdir -p logs 2>/dev/null || true
889
+ if [[ -f "$REALCHECK_LOG" ]]; then
890
+ local rc_sz; rc_sz=$(stat -c %s "$REALCHECK_LOG" 2>/dev/null || stat -f %z "$REALCHECK_LOG" 2>/dev/null || echo 0)
891
+ [[ "$rc_sz" =~ ^[0-9]+$ ]] || rc_sz=0
892
+ [[ $rc_sz -ge $CD_LOG_ROTATE_BYTES ]] && mv -f "$REALCHECK_LOG" "${REALCHECK_LOG}.1" 2>/dev/null || true
893
+ fi
894
+ local cmd_clamped; cmd_clamped=$(printf '%s' "$cmd" | cut -c1-"${RC_MAX_CHARS}")
895
+ jq -nc \
896
+ --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg topic "${REPORT_TOPIC:-}" \
897
+ --arg iter "${ITERATION:-}" --arg command "$cmd_clamped" --arg cwd "$cwd" \
898
+ --argjson exitCode "$([[ "$excode" =~ ^-?[0-9]+$ ]] && echo "$excode" || echo 'null')" \
899
+ --argjson durationMs "$([[ "$durms" =~ ^[0-9]+$ ]] && echo "$durms" || echo 0)" \
900
+ --arg outcome "$outcome" \
901
+ --argjson breakerOpen "$([[ "$bopen" == "1" ]] && echo true || echo false)" \
902
+ '{ts:$ts,topic:$topic,iteration:$iter,command:$command,cwd:$cwd,exitCode:$exitCode,durationMs:$durationMs,outcome:$outcome,breakerOpen:$breakerOpen}' \
903
+ >> "$REALCHECK_LOG" 2>/dev/null || true
904
+ }
905
+
906
+ # realcheck_destructive — echoes 1 if the raw command string matches a high-signal
907
+ # destructive shape (L12 pre-block, §6.3). Literal-shape guard on the raw string only;
908
+ # honestly bypassable by obfuscation (the §6.1/§6.2 honest-mistake posture). Catches a
909
+ # fat-fingered/compacted-agent destructive "check", NOT an adversarial agent.
910
+ realcheck_destructive() {
911
+ local cmd="$1"
912
+ # rm -rf | git reset --hard | git clean -f | git push --force(/-f) | truncate (`:>`) |
913
+ # redirect into /dev | mkfs | a write redirect into the instar source tree.
914
+ if printf '%s' "$cmd" | grep -qE 'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f|rm[[:space:]]+-[a-zA-Z]*f[a-zA-Z]*r|git[[:space:]]+reset[[:space:]]+--hard|git[[:space:]]+clean[[:space:]]+-[a-zA-Z]*f|git[[:space:]]+push[[:space:]].*(--force|-f\b)|:[[:space:]]*>|>[[:space:]]*/dev|mkfs' 2>/dev/null; then
915
+ echo 1; return
916
+ fi
917
+ # Write redirect (`> path` / `>> path`) into the instar source tree (src/ at the repo root).
918
+ if printf '%s' "$cmd" | grep -qE '>>?[[:space:]]*(\./)?src/' 2>/dev/null; then
919
+ echo 1; return
920
+ fi
921
+ echo 0
922
+ }
923
+
924
+ # run_verification(cmd, cwd) — the core. Runs the declared command in a SUBSHELL with
925
+ # the resolved CWD (so the hook's own anchored CWD is undisturbed), a scrubbed env, and
926
+ # a portable, GUARANTEED timeout. Sets RC_OUTCOME (pass|fail|timeout|refused-destructive|
927
+ # unavailable), RC_EXIT, RC_DUR_MS, and RC_SANITIZED_OUTPUT (sanitize→UTF-8-scrub→
928
+ # leak-scrub→clamp). CARDINAL INVARIANT: ANY failure mode routes to keep-working — only
929
+ # RC_OUTCOME==pass allows the exit; everything else (including a missing perl) is a
930
+ # keep-working FAIL/unavailable. There is NO path here that CAUSES a premature exit.
931
+ RC_OUTCOME=""
932
+ RC_EXIT=""
933
+ RC_DUR_MS=0
934
+ RC_SANITIZED_OUTPUT=""
935
+ run_verification() {
936
+ local cmd="$1" cwd="$2"
937
+ RC_OUTCOME=""; RC_EXIT=""; RC_DUR_MS=0; RC_SANITIZED_OUTPUT=""
938
+
939
+ # ── TEST SEAM: short-circuit the real command in CI (no real exec). ──
940
+ if [[ -n "${INSTAR_HOOK_VERIFY_OVERRIDE:-}" ]]; then
941
+ case "$INSTAR_HOOK_VERIFY_OVERRIDE" in
942
+ pass) RC_OUTCOME="pass"; RC_EXIT=0 ;;
943
+ fail) RC_OUTCOME="fail"; RC_EXIT=1; RC_SANITIZED_OUTPUT="simulated failure output" ;;
944
+ timeout) RC_OUTCOME="timeout"; RC_EXIT=124; RC_SANITIZED_OUTPUT="simulated timeout" ;;
945
+ unavailable) RC_OUTCOME="unavailable"; RC_EXIT=127; RC_SANITIZED_OUTPUT="simulated unavailable (no timeout binary)" ;;
946
+ *) RC_OUTCOME="fail"; RC_EXIT=1 ;;
947
+ esac
948
+ realcheck_audit_row "$RC_OUTCOME" "$RC_EXIT" "0" "$cmd" "$cwd" "0"
949
+ return 0
950
+ fi
951
+
952
+ # ── L12 destructive-pattern pre-block (refuse → unavailable → keep working). ──
953
+ if [[ "$(realcheck_destructive "$cmd")" == "1" ]]; then
954
+ RC_OUTCOME="refused-destructive"; RC_EXIT=126
955
+ RC_SANITIZED_OUTPUT="[real check refused: the declared command matched a high-signal destructive pattern and was NOT run]"
956
+ echo "[autonomous] real-check REFUSED (destructive pattern) — keeping working" >&2
957
+ realcheck_audit_row "refused-destructive" "126" "0" "$cmd" "$cwd" "0"
958
+ return 0
959
+ fi
960
+
961
+ # ── Scrubbed env: fixed PATH; strip authToken + npm_config_* + NODE_OPTIONS so a
962
+ # failing check that dumps `env` can't self-leak the agent's own bearer token. Build
963
+ # a proper `env -u VAR` arg array (each `-u` and VAR as SEPARATE args). ──
964
+ local rc_path="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
965
+ local -a rc_env_args=(-u authToken -u NODE_OPTIONS)
966
+ local _v
967
+ for _v in $(compgen -e 2>/dev/null); do
968
+ case "$_v" in
969
+ npm_config_*) rc_env_args+=(-u "$_v") ;;
970
+ esac
971
+ done
972
+
973
+ # ── Portable timeout LADDER (§5.1). Each rung bounds the command; NONE runs unbounded. ──
974
+ # Resolve the runner to its ABSOLUTE path during detection (with the hook's full PATH)
975
+ # and invoke it by absolute path inside the scrubbed-PATH subshell — otherwise a runner
976
+ # living outside the fixed PATH (e.g. Homebrew's /opt/homebrew/bin/timeout on macOS)
977
+ # would resolve here but be `command not found` under the scrubbed env. The fixed PATH
978
+ # still scrubs the USER command's env; only the runner binary is referenced absolutely.
979
+ # A test seam (INSTAR_HOOK_VERIFY_NO_TIMEOUT=1) forces the timeout/gtimeout rungs to be
980
+ # treated as absent so the perl path can be exercised even on a box that has GNU timeout.
981
+ local rc_start rc_end rc_raw rc_code rc_runner="" rc_runner_bin=""
982
+ rc_start=$(date +%s)
983
+ if [[ "${INSTAR_HOOK_VERIFY_NO_TIMEOUT:-0}" != "1" ]] && command -v timeout >/dev/null 2>&1; then
984
+ rc_runner="timeout"; rc_runner_bin="$(command -v timeout)"
985
+ elif [[ "${INSTAR_HOOK_VERIFY_NO_TIMEOUT:-0}" != "1" ]] && command -v gtimeout >/dev/null 2>&1; then
986
+ rc_runner="gtimeout"; rc_runner_bin="$(command -v gtimeout)"
987
+ elif command -v perl >/dev/null 2>&1; then
988
+ rc_runner="perl"; rc_runner_bin="$(command -v perl)"
989
+ else
990
+ # No bounded runner at all → UNAVAILABLE → keep working. NEVER run unbounded.
991
+ RC_OUTCOME="unavailable"; RC_EXIT=127
992
+ RC_SANITIZED_OUTPUT="[real check unavailable: no timeout/gtimeout/perl on PATH to bound the command — keeping working]"
993
+ echo "[autonomous] real-check UNAVAILABLE: no timeout/gtimeout/perl to bound the command — keeping working" >&2
994
+ realcheck_audit_row "unavailable" "127" "0" "$cmd" "$cwd" "0"
995
+ return 0
996
+ fi
997
+
998
+ # Run in a subshell so the resolved CWD + scrubbed env never disturb the hook itself.
999
+ # Combined stdout+stderr is byte-capped AT THE SOURCE (head -c) so a runaway log can
1000
+ # never buffer whole. Exit code via ${PIPESTATUS[0]} (the command's, not head's).
1001
+ if [[ "$rc_runner" == "perl" ]]; then
1002
+ rc_raw=$(
1003
+ env "${rc_env_args[@]}" PATH="$rc_path" \
1004
+ bash -c '
1005
+ cd "$1" 2>/dev/null || true
1006
+ "$5" -e '\''my($t,@c)=@ARGV; my $p=fork; if($p==0){setpgrp(0,0); exec @c or exit 127} $SIG{ALRM}=sub{kill("-KILL",$p); exit 124}; alarm($t); waitpid($p,0); exit($?>>8)'\'' "$2" bash -c "$3" 2>&1 | head -c "$4"
1007
+ exit "${PIPESTATUS[0]}"
1008
+ ' _ "$cwd" "$RC_TIMEOUT_S" "$cmd" "$RC_CAPTURE_BYTES" "$rc_runner_bin"
1009
+ )
1010
+ rc_code=$?
1011
+ else
1012
+ rc_raw=$(
1013
+ env "${rc_env_args[@]}" PATH="$rc_path" \
1014
+ bash -c '
1015
+ cd "$1" 2>/dev/null || true
1016
+ "$2" -k 5 "$3" bash -c "$4" 2>&1 | head -c "$5"
1017
+ exit "${PIPESTATUS[0]}"
1018
+ ' _ "$cwd" "$rc_runner_bin" "$RC_TIMEOUT_S" "$cmd" "$RC_CAPTURE_BYTES"
1019
+ )
1020
+ rc_code=$?
1021
+ fi
1022
+ rc_end=$(date +%s)
1023
+ RC_DUR_MS=$(( (rc_end - rc_start) * 1000 ))
1024
+ RC_EXIT=$rc_code
1025
+
1026
+ # ── Output handling — PINNED ORDER: sanitize → UTF-8 scrub → leak-scrub → clamp. ──
1027
+ local rc_san rc_utf8 rc_clamped
1028
+ # 1a. sanitize: strip control chars, collapse whitespace (hb_sanitize clamps to
1029
+ # CD_MARKER_MAX_CHARS, so re-implement the strip WITHOUT that clamp here).
1030
+ rc_san=$(printf '%s' "$rc_raw" | tr -d '\000-\010\013\014\016-\037' | tr '\n\r\t' ' ' | sed 's/ */ /g; s/^ *//; s/ *$//')
1031
+ # 1b. UTF-8 scrub: a source head -c byte-cap can split a multibyte char, leaving a lone
1032
+ # continuation byte that would later break jq --arg. iconv -c drops invalid bytes;
1033
+ # fall back to an LC_ALL=C printable-only filter when iconv is absent.
1034
+ if command -v iconv >/dev/null 2>&1; then
1035
+ rc_utf8=$(printf '%s' "$rc_san" | iconv -c -f utf-8 -t utf-8 2>/dev/null || printf '%s' "$rc_san" | LC_ALL=C tr -cd '\11\12\15\40-\176')
1036
+ else
1037
+ rc_utf8=$(printf '%s' "$rc_san" | LC_ALL=C tr -cd '\11\12\15\40-\176')
1038
+ fi
1039
+ # 2. leak-scrub on the SANITIZED text, BEFORE clamp (a credential split across the
1040
+ # clamp boundary can't evade the regex). hb_leak_hit patterns + the agent's own
1041
+ # authToken literal + a generic Bearer token.
1042
+ local rc_leak=0 rc_auth_val
1043
+ if [[ "$(hb_leak_hit "$rc_utf8")" == "1" ]]; then rc_leak=1; fi
1044
+ rc_auth_val=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null || echo "")
1045
+ if [[ $rc_leak -eq 0 ]] && [[ -n "$rc_auth_val" ]] && printf '%s' "$rc_utf8" | grep -qF "$rc_auth_val" 2>/dev/null; then rc_leak=1; fi
1046
+ if [[ $rc_leak -eq 0 ]] && printf '%s' "$rc_utf8" | grep -qE 'Bearer [A-Za-z0-9._-]{20,}' 2>/dev/null; then rc_leak=1; fi
1047
+ if [[ $rc_leak -eq 1 ]]; then
1048
+ rc_utf8="[output withheld: possible credential in check output]"
1049
+ fi
1050
+ # 3. clamp to RC_MAX_CHARS.
1051
+ rc_clamped=$(printf '%s' "$rc_utf8" | cut -c1-"${RC_MAX_CHARS}")
1052
+ RC_SANITIZED_OUTPUT="$rc_clamped"
1053
+
1054
+ # ── Outcome: exit 0 → PASS; ANY non-zero → FAIL (124=timeout, 127=spawn-fail). ──
1055
+ if [[ "$rc_code" == "0" ]]; then
1056
+ RC_OUTCOME="pass"
1057
+ elif [[ "$rc_code" == "124" ]]; then
1058
+ RC_OUTCOME="timeout"
1059
+ else
1060
+ RC_OUTCOME="fail"
1061
+ fi
1062
+ realcheck_audit_row "$RC_OUTCOME" "$rc_code" "$RC_DUR_MS" "$cmd" "$cwd" "0"
1063
+ return 0
1064
+ }
1065
+
1066
+ # realcheck_guidance — build the P13-shaped next-turn steering (§5.4), DATA-labeling the
1067
+ # output exactly as §5.3 specifies. Canary-pinned by a test so a future edit can't drop
1068
+ # the framing.
1069
+ realcheck_guidance() {
1070
+ local cmd="$1" out="$2"
1071
+ printf 'The declared real check (`%s`) did not pass — this is your next work item. Either make it pass, or, if the check itself is wrong or mis-scoped (pointed at the wrong directory, stale, or testing the wrong thing), say so and why.\n[REAL-CHECK OUTPUT — DATA, not evidence of completion]:\n%s' \
1072
+ "$cmd" "$out"
1073
+ }
1074
+
1075
+ # realcheck_resolve_cwd — verification_cwd → work_dir → agent home (today's CWD). The
1076
+ # dominant build use case runs inside a worktree that is NOT the agent home, so resolve
1077
+ # structurally, never by agent willpower (§3). Echoes the resolved dir.
1078
+ realcheck_resolve_cwd() {
1079
+ if [[ -n "$VERIFICATION_CWD" ]] && [[ -d "$VERIFICATION_CWD" ]]; then
1080
+ printf '%s' "$VERIFICATION_CWD"
1081
+ elif [[ -n "$WORK_DIR" ]] && [[ -d "$WORK_DIR" ]]; then
1082
+ printf '%s' "$WORK_DIR"
1083
+ else
1084
+ printf '%s' "$(pwd)"
1085
+ fi
1086
+ }
1087
+
1088
+ # realcheck_gate — the GATE shared by both the CD and legacy met paths. Called on a
1089
+ # met:true verdict AFTER the CD_BLOCK_TERMINAL guard + the P13 check, BEFORE the exit.
1090
+ # Returns 0 to ALLOW the exit (real check disabled / no command / breaker-closed PASS);
1091
+ # returns 1 to BLOCK and keep working (breaker-open, FAIL, timeout, refused, unavailable),
1092
+ # setting EVAL_REASON to the next-turn guidance. CARDINAL INVARIANT: every non-pass path
1093
+ # returns 1 (keep working) — there is NO path here that allows a premature exit on a
1094
+ # verification problem.
1095
+ realcheck_gate() {
1096
+ # Disabled or no declared command → existing behavior unchanged (allow exit).
1097
+ [[ "$RC_ENABLED" != "1" ]] && return 0
1098
+ [[ -z "$VERIFICATION_COMMAND" ]] && return 0
1099
+
1100
+ if [[ "$(realcheck_breaker_open)" == "1" ]]; then
1101
+ # Breaker OPEN — cheap continue: do NOT re-run the command (and the judge already
1102
+ # fired to produce this met). Surface the breaker guidance + keep working.
1103
+ EVAL_REASON="The declared real check (\`${VERIFICATION_COMMAND}\`) has failed repeatedly — paused re-running it for a cooldown. This is likely an authoring problem (wrong directory, stale, or testing the wrong thing): fix the check or the work, then continue. I've queued it for the operator."
1104
+ realcheck_audit_row "fail" "" "0" "$VERIFICATION_COMMAND" "$(realcheck_resolve_cwd)" "1"
1105
+ echo "[autonomous] real-check breaker OPEN — keeping working (no command run, no judge re-fire)" >&2
1106
+ return 1
1107
+ fi
1108
+
1109
+ local rc_cwd; rc_cwd=$(realcheck_resolve_cwd)
1110
+ run_verification "$VERIFICATION_COMMAND" "$rc_cwd"
1111
+ if [[ "$RC_OUTCOME" == "pass" ]]; then
1112
+ realcheck_reset
1113
+ echo "[autonomous] real-check PASSED — exit allowed (judge MET + real check PASSED)" >&2
1114
+ return 0
1115
+ fi
1116
+ # FAIL | timeout | refused-destructive | unavailable → record + keep working.
1117
+ realcheck_record_failure
1118
+ EVAL_REASON="$(realcheck_guidance "$VERIFICATION_COMMAND" "$RC_SANITIZED_OUTPUT")"
1119
+ echo "[autonomous] real-check ${RC_OUTCOME} (exit=${RC_EXIT}) — keeping working" >&2
1120
+ return 1
1121
+ }
1122
+
746
1123
  if [[ "$CD_ENABLED" == "1" ]] && [[ -n "$HARD_BLOCKER_NONCE" ]] && [[ "$HARD_BLOCKER_NONCE" != "null" ]] \
747
1124
  && [[ -n "$CD_FINAL_TURN" ]] && [[ "$CD_FINAL_TURN" == *"<hard-blocker"* ]]; then
748
1125
  HB_OK="false"
@@ -919,21 +1296,31 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$COMPLETION_CONDITION" ]]
919
1296
  # the completion judge (the SINGLE critical-path call). A "met" verdict here is
920
1297
  # the judge's all-things-considered decision → allow. No standalone P13 call on
921
1298
  # the condition path (spec §2b.2 — folded once the canary verifies the block).
922
- emit "✅ Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
923
- notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished the goal was met."
924
- rm -f "$STATE_FILE" "$CD_BACKOFF_STATE" 2>/dev/null || true
925
- exit 0
926
- fi
927
- if p13_stop_allowed; then
928
- emit " Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
929
- notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — the goal was met."
930
- rm -f "$STATE_FILE"
931
- exit 0
1299
+ # ── REAL-CHECK GATE (ACT-152): judge MET + a declared command → RUN it; the
1300
+ # exit is allowed ONLY if the command ALSO passes. Any fail/timeout/refused/
1301
+ # unavailable/breaker-open keep working (realcheck_gate sets EVAL_REASON). ──
1302
+ if realcheck_gate; then
1303
+ emit "✅ Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
1304
+ notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — the goal was met."
1305
+ rm -f "$STATE_FILE" "$CD_BACKOFF_STATE" 2>/dev/null || true
1306
+ exit 0
1307
+ fi
1308
+ # Real check did not pass → keep working; EVAL_REASON now carries the guidance.
1309
+ elif p13_stop_allowed; then
1310
+ # ── REAL-CHECK GATE (ACT-152) on the legacy (CD-disabled) condition path. ──
1311
+ if realcheck_gate; then
1312
+ emit "✅ Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
1313
+ notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — the goal was met."
1314
+ rm -f "$STATE_FILE"
1315
+ exit 0
1316
+ fi
1317
+ # Real check did not pass → keep working; EVAL_REASON now carries the guidance.
1318
+ else
1319
+ # P13 "The Stop Reason Is the Work": the condition reads as met, but the stop
1320
+ # rests on a judgment-call / needs-engineering deferral → keep working. The
1321
+ # P13 steering becomes the next-turn guidance (surfaced via EVAL_REASON below).
1322
+ EVAL_REASON="$P13_GUIDANCE"
932
1323
  fi
933
- # P13 "The Stop Reason Is the Work": the condition reads as met, but the stop
934
- # rests on a judgment-call / needs-engineering deferral → keep working. The
935
- # P13 steering becomes the next-turn guidance (surfaced via EVAL_REASON below).
936
- EVAL_REASON="$P13_GUIDANCE"
937
1324
  fi
938
1325
  # Not met / unreachable → keep working; EVAL_REASON (if any) becomes next-turn guidance.
939
1326
  fi
@@ -949,11 +1336,19 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$TRANSCRIPT_PATH" ]] && [[
949
1336
  PROMISE_TEXT=$(printf '%s' "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "")
950
1337
  if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
951
1338
  if p13_stop_allowed; then
952
- emit "✅ Autonomous mode: Completion promise detected <promise>$COMPLETION_PROMISE</promise>"
953
- emit " Session is free to exit. Good work!"
954
- notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished all the work is done."
955
- rm -f "$STATE_FILE"
956
- exit 0
1339
+ # ── REAL-CHECK GATE (ACT-152) on the legacy-promise met path (§2.2). The
1340
+ # gate is scoped to the completion-condition AND legacy-promise met paths;
1341
+ # a declared command must ALSO pass before a self-declared promise exits.
1342
+ if realcheck_gate; then
1343
+ emit "✅ Autonomous mode: Completion promise detected — <promise>$COMPLETION_PROMISE</promise>"
1344
+ emit " Session is free to exit. Good work!"
1345
+ notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — all the work is done."
1346
+ rm -f "$STATE_FILE"
1347
+ exit 0
1348
+ fi
1349
+ # Real check did not pass → keep working; EVAL_REASON carries the guidance,
1350
+ # surfaced via the promise-path system message below (P13_GUIDANCE path).
1351
+ P13_GUIDANCE="$EVAL_REASON"
957
1352
  fi
958
1353
  # P13: a completion promise was emitted, but the stop rests on a
959
1354
  # judgment-call / needs-engineering deferral → keep working. P13_GUIDANCE
@@ -16,6 +16,8 @@ TASKS=""
16
16
  COMPLETION_PROMISE=""
17
17
  COMPLETION_CONDITION="" # verifiable end-state; an independent judge decides "done" (mirrors /goal). Preferred over the self-declared promise.
18
18
  REPORT_INTERVAL="30m"
19
+ VERIFICATION_COMMAND="" # opt-in real check (ACT-152): the stop-hook RUNS this on a met:true verdict and gates the exit on exit-0.
20
+ VERIFICATION_CWD="" # directory the verification command runs in (resolves verification_cwd → work_dir → agent home).
19
21
 
20
22
  while [[ $# -gt 0 ]]; do
21
23
  case $1 in
@@ -51,6 +53,14 @@ while [[ $# -gt 0 ]]; do
51
53
  COMPLETION_CONDITION="$2"
52
54
  shift 2
53
55
  ;;
56
+ --verification-command)
57
+ VERIFICATION_COMMAND="$2"
58
+ shift 2
59
+ ;;
60
+ --verification-cwd)
61
+ VERIFICATION_CWD="$2"
62
+ shift 2
63
+ ;;
54
64
  --report-interval)
55
65
  REPORT_INTERVAL="$2"
56
66
  shift 2
@@ -158,6 +168,24 @@ else
158
168
  STATE_PATH=".instar/autonomous-state.local.md"
159
169
  fi
160
170
 
171
+ # ── REALCHECK_VERIFY — Real-check verification fields (ACT-152). work_dir is ALWAYS
172
+ # captured so the hook resolves the command's CWD structurally (verification_cwd →
173
+ # work_dir → agent home) — the worktree-default build runs OUTSIDE the agent home,
174
+ # and a relative `npm test` from the home would test the wrong tree. The verification_*
175
+ # fields are OMITTED when the flags are absent (→ byte-identical to today). This
176
+ # REALCHECK_VERIFY sentinel is the PostUpdateMigrator marker that re-deploys this setup
177
+ # to existing agents carrying COMPLETION_DISCIPLINE but not REALCHECK_VERIFY. ──
178
+ WORK_DIR="$(pwd)"
179
+ VERIFICATION_FIELDS="work_dir: \"$WORK_DIR\""
180
+ if [[ -n "$VERIFICATION_COMMAND" ]]; then
181
+ VERIFICATION_FIELDS="verification_command: \"$VERIFICATION_COMMAND\"
182
+ $VERIFICATION_FIELDS"
183
+ fi
184
+ if [[ -n "$VERIFICATION_CWD" ]]; then
185
+ VERIFICATION_FIELDS="verification_cwd: \"$VERIFICATION_CWD\"
186
+ $VERIFICATION_FIELDS"
187
+ fi
188
+
161
189
  cat > "$STATE_PATH" <<EOF
162
190
  ---
163
191
  active: true
@@ -176,6 +204,7 @@ level_up: $LEVEL_UP
176
204
  completion_promise: "$COMPLETION_PROMISE"
177
205
  completion_condition: "$COMPLETION_CONDITION"
178
206
  hard_blocker_nonce: "$HARD_BLOCKER_NONCE"
207
+ $VERIFICATION_FIELDS
179
208
  ---
180
209
 
181
210
  # Autonomous Session
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAmG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA65BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA0UN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAohVtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAmG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA65BD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA0UN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAyhVtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -7757,7 +7757,7 @@ export async function startServer(options) {
7757
7757
  // Cartographer doc-tree — hierarchical semantic map with git-hash staleness
7758
7758
  // (cartographer-doc-tree-schema spec #1). Ships dark behind cartographer.enabled;
7759
7759
  // null → /cartographer/* routes return 503.
7760
- const cartographerEnabled = config.cartographer?.enabled ?? false;
7760
+ const cartographerEnabled = resolveDevAgentGate(config.cartographer?.enabled, config);
7761
7761
  const cartographer = cartographerEnabled
7762
7762
  ? new CartographerTree({ projectDir: config.projectDir, stateDir: config.stateDir })
7763
7763
  : null;
@@ -7765,16 +7765,19 @@ export async function startServer(options) {
7765
7765
  console.log(pc.green(' Cartographer doc-tree enabled'));
7766
7766
  // Cartographer doc-freshness sweep (spec #2). In-process poller that authors
7767
7767
  // stale/never-authored node summaries on a LIGHT model routed OFF Claude.
7768
- // Ships dark behind freshnessSweep.enabled AND egressAcknowledged (off-Claude
7769
- // authoring transmits source content to a third-party framework a separate
7770
- // consent gate). The off-Claude guarantee needs the IntelligenceRouter
7771
- // (router.for + defaultFramework); if routing is an unrouted provider the
7772
- // sweep refuses to start (it could not enforce off-Claude).
7768
+ // Ships dark behind freshnessSweep.enabled ONLY — the redundant egressAcknowledged
7769
+ // second gate was removed (DEV-AGENT-DARK-GATE-ENFORCEMENT Slice A3): one honest
7770
+ // opt-in flag, not two. This is the one cost-bearing cartographer surface and stays
7771
+ // an explicit opt-in EVEN on a dev agent (it bills a third-party account every pass),
7772
+ // so it is in DARK_GATE_EXCLUSIONS (cost-bearing), NOT dev-gated. The off-Claude
7773
+ // guarantee still needs the IntelligenceRouter (router.for + defaultFramework); if
7774
+ // routing is an unrouted provider the sweep refuses to start (it could not enforce
7775
+ // off-Claude) — that cost-protecting probe is UNCHANGED.
7773
7776
  let cartographerSweepPoller = null;
7774
7777
  if (cartographer) {
7775
7778
  const fsCfg = config.cartographer?.freshnessSweep;
7776
7779
  const num = (v, d) => (typeof v === 'number' && Number.isFinite(v) ? v : d);
7777
- if (fsCfg?.enabled && fsCfg?.egressAcknowledged && sharedLlmQueue) {
7780
+ if (fsCfg?.enabled && sharedLlmQueue) {
7778
7781
  const routerLike = sharedIntelligence &&
7779
7782
  typeof sharedIntelligence.for === 'function' &&
7780
7783
  typeof sharedIntelligence.defaultFramework === 'string'